refactor pkg/application to use Appfile (#402)

* refactor pkg/application to use Appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* fix build

* fix workload

* refactor pkg/application to use Appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* rebase

* fix

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* e2e

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* update design

* add test coverage for appfile

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>

* comment

Signed-off-by: Hongchao Deng <hongchaodeng1@gmail.com>
This commit is contained in:
Hongchao Deng
2020-10-21 13:01:46 +08:00
committed by GitHub
parent fa575a0103
commit 20a5457d5f
33 changed files with 689 additions and 497 deletions
+2 -1
View File
@@ -141,10 +141,11 @@ services:
express-server:
type: webservice # workload type
build:
image: oamdev/testapp:v1
docker:
file: Dockerfile
context: .
image: oamdev/testapp:v1
cmd: ["node", "server.js"]
ports:
- 8080:80
@@ -49,4 +49,5 @@ spec:
}
}
parameter: #metrics
output: {}
+8 -4
View File
@@ -24,8 +24,11 @@ Here's an example to deploy a NodeJS express service:
```yaml
services:
express-server:
# this image will be used in both build and deploy config
image: oamdev/testapp:v1
build:
image: oamdev/testapp:v1
# Here more runtime specific build templates will be supported, like NodeJS, Go, Python, Ruby.
docker:
file: Dockerfile
context: .
@@ -93,6 +96,8 @@ spec:
env: [...string]
files: [...string]
image: string
}
output: {
@@ -112,7 +117,7 @@ spec:
spec: {
containers: [{
name: context.name
image: context.image
image: parameter.image
command: parameter.cmd
}]
}
@@ -217,7 +222,7 @@ output: {
...
containers: [{
name: context.name
image: context.image
image: parameter.image
command: parameter.cmd
}]
}
@@ -231,7 +236,6 @@ Here is the takeout:
```yaml
context:
name: express-server
image: oamdev/testapp:v1
```
You can check the definition of `context` block via `vela template context`.
+1 -1
View File
@@ -30,7 +30,7 @@ var _ = ginkgo.Describe("Trait", func() {
cli := fmt.Sprintf("vela %s %s", traitAlias, applicationNotExistedName)
output, err := e2e.Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("Error: " + applicationNotExistedName + " not exist"))
gomega.Expect(output).To(gomega.ContainSubstring("component name (" + applicationNotExistedName + ") doesn't exist"))
})
})
+8 -3
View File
@@ -3,13 +3,18 @@ name: testapp
services:
express-server:
# this image will be used in both build and deploy config
image: oamdev/testapp:v1
build:
image: oamdev/testapp:v1
# Here more runtime specific build templates will be supported, like NodeJS, Go, Python, Ruby.
docker:
file: Dockerfile
context: .
push: # without any setting, by default push image directly
local: kind
# Uncomment the following to push to local kind cluster
# push:
# local: kind
# type: webservice (default) | task
+3 -1
View File
@@ -8,6 +8,8 @@ parameter: #webservice
env: [...string]
files: [...string]
image: string
}
output: {
@@ -27,7 +29,7 @@ output: {
spec: {
containers: [{
name: context.name
image: context.image
image: parameter.image
command: parameter.cmd
}]
}
+3 -1
View File
@@ -17,6 +17,8 @@ spec:
env: [...string]
files: [...string]
image: string
}
output: {
@@ -36,7 +38,7 @@ spec:
spec: {
containers: [{
name: context.name
image: context.image
image: parameter.image
command: parameter.cmd
}]
}
+17 -22
View File
@@ -13,6 +13,10 @@ import (
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
)
var (
ErrImageNotDefined = errors.New("image not defined")
)
const DefaultAppfilePath = "./vela.yml"
type AppFile struct {
@@ -49,24 +53,19 @@ func LoadFromFile(filename string) (*AppFile, error) {
}
// BuildOAM renders Appfile into AppConfig, Components. It also builds images for services if defined.
func (app *AppFile) BuildOAM(ns string, io cmdutil.IOStreams) (
func (app *AppFile) BuildOAM(ns string, io cmdutil.IOStreams, tm template.Manager) (
[]*v1alpha2.Component, *v1alpha2.ApplicationConfiguration, error) {
return app.buildOAM(ns, io, true)
return app.buildOAM(ns, io, true, tm)
}
// RenderOAM renders Appfile into AppConfig, Components.
func (app *AppFile) RenderOAM(ns string, io cmdutil.IOStreams) (
func (app *AppFile) RenderOAM(ns string, io cmdutil.IOStreams, tm template.Manager) (
[]*v1alpha2.Component, *v1alpha2.ApplicationConfiguration, error) {
return app.buildOAM(ns, io, false)
return app.buildOAM(ns, io, false, tm)
}
func (app *AppFile) buildOAM(ns string, io cmdutil.IOStreams, buildImage bool) (
func (app *AppFile) buildOAM(ns string, io cmdutil.IOStreams, buildImage bool, tm template.Manager) (
[]*v1alpha2.Component, *v1alpha2.ApplicationConfiguration, error) {
io.Info("Loading templates ...")
tm, err := template.Load()
if err != nil {
return nil, nil, err
}
appConfig := &v1alpha2.ApplicationConfiguration{
ObjectMeta: metav1.ObjectMeta{
@@ -78,26 +77,22 @@ func (app *AppFile) buildOAM(ns string, io cmdutil.IOStreams, buildImage bool) (
var comps []*v1alpha2.Component
for sname, svc := range app.GetServices() {
build := svc.GetBuild()
var image string
if build != nil {
image = build.Image
v, ok := svc["image"]
if ok {
image = v.(string)
} else {
return nil, nil, ErrImageNotDefined
}
if b := svc.GetBuild(); b != nil {
if buildImage {
io.Infof("\nBuilding service (%s)...\n", sname)
if err := build.BuildImage(io); err != nil {
if err := b.BuildImage(io, image); err != nil {
return nil, nil, err
}
}
}
if image == "" {
v, ok := svc["image"]
if ok {
image = v.(string)
} else {
return nil, nil, errors.New("no image is defined")
}
}
io.Infof("\nRendering component configs for service (%s)...\n", sname)
acComp, comp, err := svc.RenderService(tm, sname, ns, image)
+349
View File
@@ -0,0 +1,349 @@
package appfile
import (
"os"
"testing"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/appfile/template"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
)
func TestRenderOAM(t *testing.T) {
yamlOneService := `name: myapp
services:
express-server:
image: oamdev/testapp:v1
cmd: ["node", "server.js"]
route:
domain: example.com
http:
"/": 8080
`
yamlTwoServices := yamlOneService + `
mongodb:
type: backend
image: bitnami/mongodb:3.6.20
cmd: ["mongodb"]
`
yamlNoImage := `name: myapp
services:
bad-server:
cmd: ["node", "server.js"]
`
templateWebservice := `parameter: #webservice
#webservice: {
cmd: [...string]
image: string
}
output: {
apiVersion: "test.oam.dev/v1"
kind: "WebService"
metadata: {
name: context.name
}
spec: {
image: parameter.image
command: parameter.cmd
}
}
`
templateBackend := `parameter: #backend
#backend: {
cmd: [...string]
image: string
}
output: {
apiVersion: "test.oam.dev/v1"
kind: "Worker"
metadata: {
name: context.name
}
spec: {
image: parameter.image
command: parameter.cmd
}
}`
templateRoute := `parameter: #route
#route: {
domain: string
http: [string]: int
}
// trait template can have multiple outputs and they are all traits
outputs: service: {
apiVersion: "v1"
kind: "Service"
metadata:
name: context.name
spec: {
selector:
app: context.name
ports: [
for k, v in parameter.http {
port: v
targetPort: v
}
]
}
}
outputs: ingress: {
apiVersion: "networking.k8s.io/v1beta1"
kind: "Ingress"
spec: {
rules: [{
host: parameter.domain
http: {
paths: [
for k, v in parameter.http {
path: k
backend: {
serviceName: context.name
servicePort: v
}
}
]
}
}]
}
}
`
ac1 := &v1alpha2.ApplicationConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
Namespace: "default",
},
Spec: v1alpha2.ApplicationConfigurationSpec{
Components: []v1alpha2.ApplicationConfigurationComponent{{
ComponentName: "express-server",
Traits: []v1alpha2.ComponentTrait{{
Trait: runtime.RawExtension{
Object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Service",
"metadata": map[string]interface{}{
"name": "express-server",
},
"spec": map[string]interface{}{
"selector": map[string]interface{}{
"app": "express-server",
},
"ports": []interface{}{
map[string]interface{}{
"port": float64(8080),
"targetPort": float64(8080),
},
},
},
},
},
},
}, {
Trait: runtime.RawExtension{
Object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "networking.k8s.io/v1beta1",
"kind": "Ingress",
"spec": map[string]interface{}{
"rules": []interface{}{
map[string]interface{}{
"http": map[string]interface{}{
"paths": []interface{}{
map[string]interface{}{
"path": "/",
"backend": map[string]interface{}{
"serviceName": "express-server",
"servicePort": float64(8080),
},
},
},
},
"host": "example.com",
},
},
},
},
},
},
}},
}},
},
}
ac2 := ac1.DeepCopy()
ac2.Spec.Components = append(ac2.Spec.Components, v1alpha2.ApplicationConfigurationComponent{
ComponentName: "mongodb",
Traits: []v1alpha2.ComponentTrait{},
})
comp1 := &v1alpha2.Component{
ObjectMeta: metav1.ObjectMeta{
Name: "express-server",
Namespace: "default",
},
Spec: v1alpha2.ComponentSpec{
Workload: runtime.RawExtension{
Object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.oam.dev/v1",
"kind": "WebService",
"metadata": map[string]interface{}{"name": "express-server"},
"spec": map[string]interface{}{
"image": "oamdev/testapp:v1",
"command": []interface{}{"node", "server.js"},
},
},
},
},
},
}
comp2 := &v1alpha2.Component{
ObjectMeta: metav1.ObjectMeta{
Name: "mongodb",
Namespace: "default",
},
Spec: v1alpha2.ComponentSpec{
Workload: runtime.RawExtension{
Object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.oam.dev/v1",
"kind": "Worker",
"metadata": map[string]interface{}{"name": "mongodb"},
"spec": map[string]interface{}{
"image": "bitnami/mongodb:3.6.20",
"command": []interface{}{"mongodb"},
},
},
},
},
},
}
type args struct {
appfileData string
workloadTemplates map[string]string
traitTemplates map[string]string
}
type want struct {
components []*v1alpha2.Component
appConfig *v1alpha2.ApplicationConfiguration
err error
}
cases := map[string]struct {
args args
want want
}{
"one service should generate one component and one appconfig": {
args: args{
appfileData: yamlOneService,
workloadTemplates: map[string]string{
"webservice": templateWebservice,
},
traitTemplates: map[string]string{
"route": templateRoute,
},
},
want: want{
appConfig: ac1,
components: []*v1alpha2.Component{comp1},
},
},
"two services should generate two components and one appconfig": {
args: args{
appfileData: yamlTwoServices,
workloadTemplates: map[string]string{
"webservice": templateWebservice,
"backend": templateBackend,
},
traitTemplates: map[string]string{
"route": templateRoute,
},
},
want: want{
appConfig: ac2,
components: []*v1alpha2.Component{comp1, comp2},
},
},
"no image should fail": {
args: args{
appfileData: yamlNoImage,
},
want: want{
err: ErrImageNotDefined,
},
},
}
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
for caseName, c := range cases {
t.Run(caseName, func(t *testing.T) {
app := NewAppFile()
err := yaml.Unmarshal([]byte(c.args.appfileData), app)
if err != nil {
t.Fatal(err)
}
tm := template.NewFakeTemplateManager()
for k, v := range c.args.traitTemplates {
tm.Templates[k] = &template.Template{
Captype: types.TypeTrait,
Raw: v,
}
}
for k, v := range c.args.workloadTemplates {
tm.Templates[k] = &template.Template{
Captype: types.TypeWorkload,
Raw: v,
}
}
comps, ac, err := app.RenderOAM("default", io, tm)
if err != nil {
assert.Equal(t, c.want.err, err)
return
}
assert.Equal(t, ac.ObjectMeta, c.want.appConfig.ObjectMeta)
for _, cp1 := range c.want.appConfig.Spec.Components {
found := false
for _, cp2 := range ac.Spec.Components {
if cp1.ComponentName != cp2.ComponentName {
continue
}
assert.Equal(t, cp1, cp2)
found = true
break
}
if !found {
t.Errorf("ac component (%s) not found", cp1.ComponentName)
}
}
for _, cp1 := range c.want.components {
found := false
for _, cp2 := range comps {
if cp1.Name != cp2.Name {
continue
}
assert.Equal(t, cp1, cp2)
found = true
break
}
if !found {
t.Errorf("component (%s) not found", cp1.Name)
}
}
})
}
}
+8 -8
View File
@@ -7,7 +7,6 @@ import (
)
type Build struct {
Image string `json:"image,omitempty"`
Push Push `json:"push,omitempty"`
Docker Docker `json:"docker,omitempty"`
}
@@ -22,27 +21,28 @@ type Push struct {
Registry string `json:"registry,omitempty"`
}
func (b *Build) BuildImage(io cmdutil.IOStreams) error {
cmd := exec.Command("docker", "build", "-t", b.Image, "-f", b.Docker.File, b.Docker.Context)
func (b *Build) BuildImage(io cmdutil.IOStreams, image string) error {
cmd := exec.Command("docker", "build", "-t", image, "-f", b.Docker.File, b.Docker.Context)
out, err := cmd.CombinedOutput()
io.Infof("%s\n", out)
if err != nil {
return err
}
return b.pushImage(io)
return b.pushImage(io, image)
}
func (b *Build) pushImage(io cmdutil.IOStreams) error {
io.Infof("pushing image (%s)...\n", b.Image)
func (b *Build) pushImage(io cmdutil.IOStreams, image string) error {
io.Infof("pushing image (%s)...\n", image)
switch {
case b.Push.Local == "kind":
cmd := exec.Command("kind", "load", "docker-image", b.Image)
cmd := exec.Command("kind", "load", "docker-image", image)
out, err := cmd.CombinedOutput()
io.Infof("%s\n", out)
return err
}
cmd := exec.Command("docker", "push", b.Image)
cmd := exec.Command("docker", "push", image)
out, err := cmd.CombinedOutput()
io.Infof("%s\n", out)
return err
+2 -1
View File
@@ -183,10 +183,11 @@ func renderOneOutput(appValue *cue.Struct) (*unstructured.Unstructured, error) {
if err != nil {
return nil, fmt.Errorf("FieldByName('output'): %w", err)
}
final := outputField.Value
data, err := cueJson.Marshal(final)
if err != nil {
return nil, fmt.Errorf("marshal final value err %v", err)
return nil, fmt.Errorf("marshal final value failed: %v", err)
}
obj := make(map[string]interface{})
+11
View File
@@ -0,0 +1,11 @@
package template
type FakeTemplateManager struct {
*manager
}
func NewFakeTemplateManager() *FakeTemplateManager {
return &FakeTemplateManager{
manager: newManager(),
}
}
+15 -24
View File
@@ -7,7 +7,6 @@ import (
type Manager interface {
IsTrait(key string) bool
IsWorkload(key string) bool
LoadTemplate(key string) string
}
@@ -18,49 +17,41 @@ func Load() (Manager, error) {
}
m := newManager()
for _, cap := range caps {
t := &template{}
t.captype = cap.Type
t.raw = cap.CueTemplate
m.templates[cap.Name] = t
t := &Template{}
t.Captype = cap.Type
t.Raw = cap.CueTemplate
m.Templates[cap.Name] = t
}
return m, nil
}
type Template struct {
Captype types.CapType
Raw string
}
type manager struct {
templates map[string]*template
Templates map[string]*Template
}
func newManager() *manager {
return &manager{
templates: make(map[string]*template),
Templates: make(map[string]*Template),
}
}
type template struct {
captype types.CapType
raw string
}
func (m *manager) IsTrait(key string) bool {
t, ok := m.templates[key]
t, ok := m.Templates[key]
if !ok {
return false
}
return t.captype == types.TypeTrait
}
func (m *manager) IsWorkload(key string) bool {
t, ok := m.templates[key]
if !ok {
return false
}
return t.captype == types.TypeWorkload
return t.Captype == types.TypeTrait
}
func (m *manager) LoadTemplate(key string) string {
t, ok := m.templates[key]
t, ok := m.Templates[key]
if !ok {
return ""
}
return t.raw
return t.Raw
}
+95 -228
View File
@@ -11,48 +11,49 @@ import (
"strings"
"time"
"cuelang.org/go/cue"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/api/types"
mycue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/plugins"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/appfile/template"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/utils/system"
)
const (
Traits = "traits"
Scopes = "scopes"
)
type Application struct {
Name string `json:"name"`
// key of map is component name
Components map[string]map[string]interface{} `json:"components"`
Secrets map[string]map[string]interface{} `json:"secrets"`
Scopes map[string]map[string]interface{} `json:"globalScopes"`
CreateTime time.Time `json:"createTime,omitempty"`
UpdateTime time.Time `json:"updateTime,omitempty"`
*appfile.AppFile `json:",inline"`
tm template.Manager
}
func newApplication(f *appfile.AppFile, tm template.Manager) *Application {
if f == nil {
f = appfile.NewAppFile()
}
return &Application{AppFile: f, tm: tm}
}
func LoadFromFile(fileName string) (*Application, error) {
var app = &Application{}
data, err := ioutil.ReadFile(fileName)
tm, err := template.Load()
if err != nil {
return nil, err
}
_, err = ioutil.ReadFile(fileName)
if err != nil {
if os.IsNotExist(err) {
return app, nil
return newApplication(nil, tm), nil
}
return nil, err
}
err = yaml.Unmarshal(data, app)
f, err := appfile.LoadFromFile(fileName)
if err != nil {
return nil, err
}
app := newApplication(f, tm)
return app, app.Validate()
}
@@ -130,87 +131,54 @@ func (app *Application) Save(envName string) error {
}
func (app *Application) Validate() error {
if app == nil {
return errors.New("app is nil")
}
if app.Name == "" {
return errors.New("please provide an existed App name")
return errors.New("name is required")
}
if len(app.Components) == 0 {
return errors.New("at least one component is required")
if len(app.Services) == 0 {
return errors.New("at least one service is required")
}
for name, comp := range app.Components {
lenth := len(comp)
if traits, ok := comp[Traits]; ok {
lenth--
switch trs := traits.(type) {
case map[string]map[string]interface{}:
case map[string]interface{}:
for traitName, tr := range trs {
_, ok := tr.(map[string]interface{})
if !ok {
return fmt.Errorf("trait %s in '%s' must be map", traitName, name)
}
for name, svc := range app.Services {
for traitName, traitData := range svc.GetConfig() {
if app.tm.IsTrait(traitName) {
if _, ok := traitData.(map[string]interface{}); !ok {
return fmt.Errorf("trait %s in '%s' must be map", traitName, name)
}
default:
return fmt.Errorf("format of traits in '%s' must be nested map instead of %v", name, reflect.TypeOf(traits))
}
}
if scopes, ok := comp[Scopes]; ok {
lenth--
_, ok := scopes.([]string)
if !ok {
return fmt.Errorf("format of scopes in '%s' must be string array", name)
}
//TODO(wonderflow) check scope exist
}
if lenth != 1 {
return fmt.Errorf("you must have only one workload in component '%s'", name)
}
for workloadType, workload := range comp {
if NotWorkload(workloadType) {
continue
}
_, ok := workload.(map[string]interface{})
if !ok {
return fmt.Errorf("format of workload in %s must be map", name)
}
//TODO(wonderflow) check workload type exists
//TODO(wonderflow) check arguments of workload is valid
}
}
//TODO(wonderflow) check scope types
return nil
}
func NotWorkload(tp string) bool {
if tp == Scopes || tp == Traits {
return true
}
return false
}
func (app *Application) GetComponents() []string {
var components []string
for name := range app.Components {
for name := range app.Services {
components = append(components, name)
}
sort.Strings(components)
return components
}
func (app *Application) GetWorkload(componentName string) (string, map[string]interface{}) {
comp, ok := app.Components[componentName]
func (app *Application) GetServiceConfig(componentName string) (string, map[string]interface{}) {
svc, ok := app.Services[componentName]
if !ok {
return "", make(map[string]interface{})
}
for tp, workload := range comp {
if NotWorkload(tp) {
return svc.GetType(), svc.GetConfig()
}
func (app *Application) GetWorkload(componentName string) (string, map[string]interface{}) {
svcType, config := app.GetServiceConfig(componentName)
if svcType == "" {
return "", make(map[string]interface{})
}
workloadData := make(map[string]interface{})
for k, v := range config {
if app.tm.IsTrait(k) {
continue
}
return tp, workload.(map[string]interface{})
workloadData[k] = v
}
return "", make(map[string]interface{})
return svcType, workloadData
}
func (app *Application) GetTraitNames(componentName string) ([]string, error) {
@@ -226,177 +194,76 @@ func (app *Application) GetTraitNames(componentName string) ([]string, error) {
}
func (app *Application) GetTraits(componentName string) (map[string]map[string]interface{}, error) {
comp, ok := app.Components[componentName]
if !ok {
return nil, fmt.Errorf("%s not exist", componentName)
}
t, ok := comp[Traits]
if !ok {
return make(map[string]map[string]interface{}), nil
}
// assume it's valid, use Validate() to check
switch trs := t.(type) {
case map[string]interface{}:
traits := make(map[string]map[string]interface{})
for k, v := range trs {
traits[k] = v.(map[string]interface{})
_, config := app.GetServiceConfig(componentName)
traitsData := make(map[string]map[string]interface{})
for k, v := range config {
if !app.tm.IsTrait(k) {
continue
}
return traits, nil
case map[string]map[string]interface{}:
return trs, nil
newV, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%s is trait, but with invalid format %s, should be map[string]interface{}", k, reflect.TypeOf(v))
}
traitsData[k] = newV
}
return nil, fmt.Errorf("invalid traits data format in %s, expect nested map but got %v", componentName, reflect.TypeOf(t))
return traitsData, nil
}
func (app *Application) GetTraitsByType(componentName, traitType string) (map[string]interface{}, error) {
traits, err := app.GetTraits(componentName)
if err != nil {
return nil, err
service, ok := app.Services[componentName]
if !ok {
return nil, fmt.Errorf("component name (%s) doesn't exist", componentName)
}
for t, tt := range traits {
if t == traitType {
return tt, nil
}
t, ok := service[traitType]
if !ok {
return make(map[string]interface{}), nil
}
return make(map[string]interface{}), nil
}
func (app *Application) GetWorkloadObject(componentName string) (*unstructured.Unstructured, string, error) {
workloadType, workloadData := app.GetWorkload(componentName)
if workloadType == "" {
return nil, workloadType, errors.New(componentName + " workload not exist")
}
obj, err := InstantiateTemplateToCR(workloadType, workloadData)
if err != nil {
return nil, "", err
}
return obj, workloadType, nil
}
// ConvertDataByType will fix int become float after yaml.unmarshal
func ConvertDataByType(val interface{}, tp cue.Kind) interface{} {
switch tp {
case cue.FloatKind:
switch rv := val.(type) {
case int64:
return float64(rv)
case int:
return float64(rv)
}
case cue.IntKind:
switch rv := val.(type) {
case float64:
return int64(rv)
}
}
return val
}
// instantiate the template with the given value to generate the CR
func InstantiateTemplateToCR(capName string, data map[string]interface{}) (*unstructured.Unstructured, error) {
cap, err := plugins.LoadCapabilityByName(capName)
if err != nil {
return nil, err
}
for _, v := range cap.Parameters {
val, ok := data[v.Name]
if ok {
data[v.Name] = ConvertDataByType(val, v.Type)
}
}
cr, err := mycue.Eval(cap.DefinitionPath, data)
if err != nil {
return nil, err
}
if cap.CrdInfo != nil {
cr.SetAPIVersion(cap.CrdInfo.APIVersion)
cr.SetKind(cap.CrdInfo.Kind)
}
return cr, nil
}
func (app *Application) GetComponentTraits(componentName string, env *types.EnvMeta) ([]v1alpha2.ComponentTrait, error) {
var traits []v1alpha2.ComponentTrait
rawTraits, err := app.GetTraits(componentName)
if err != nil {
return nil, err
}
for traitType, traitData := range rawTraits {
obj, err := InstantiateTemplateToCR(traitType, traitData)
if err != nil {
return nil, err
}
//TODO(wonderflow): handle trait data input/output here
obj.SetLabels(map[string]string{oam.TraitTypeLabel: traitType})
traits = append(traits, v1alpha2.ComponentTrait{Trait: runtime.RawExtension{Object: obj}})
}
return traits, nil
}
func (app *Application) VelaCoreInjection(obj *unstructured.Unstructured, env *types.EnvMeta, traitType string) {
switch traitType {
case "route":
}
return t.(map[string]interface{}), nil
}
func FormatDefaultHealthScopeName(appName string) string {
return appName + "-default-health"
}
//TODO(wonderflow) add scope support here
func (app *Application) OAM(env *types.EnvMeta) ([]v1alpha2.Component, v1alpha2.ApplicationConfiguration, []oam.Object, error) {
var appConfig v1alpha2.ApplicationConfiguration
if err := app.Validate(); err != nil {
return nil, appConfig, nil, err
// TODO(wonderflow) add scope support here
func (app *Application) OAM(env *types.EnvMeta, io cmdutil.IOStreams) ([]*v1alpha2.Component, *v1alpha2.ApplicationConfiguration, []oam.Object, error) {
comps, appConfig, err := app.RenderOAM(env.Namespace, io, app.tm)
if err != nil {
return nil, nil, nil, err
}
appConfig.Name = app.Name
appConfig.Namespace = env.Namespace
addWorkloadTypeLabel(comps, app.Services)
health := addHealthScope(appConfig)
return comps, appConfig, []oam.Object{health}, nil
}
var health v1alpha2.HealthScope
health.Name = FormatDefaultHealthScopeName(app.Name)
health.Namespace = env.Namespace
health.Spec.WorkloadReferences = make([]v1alpha1.TypedReference, 0)
var components []v1alpha2.Component
for name := range app.Components {
// fulfill component
var component v1alpha2.Component
component.Name = name
component.Namespace = env.Namespace
obj, workloadType, err := app.GetWorkloadObject(name)
if err != nil {
return nil, v1alpha2.ApplicationConfiguration{}, nil, err
}
labels := obj.GetLabels()
func addWorkloadTypeLabel(comps []*v1alpha2.Component, services map[string]appfile.Service) {
for _, comp := range comps {
workloadType := services[comp.Name].GetType()
workloadObject := comp.Spec.Workload.Object.(*unstructured.Unstructured)
labels := workloadObject.GetLabels()
if labels == nil {
labels = map[string]string{oam.WorkloadTypeLabel: workloadType}
} else {
labels[oam.WorkloadTypeLabel] = workloadType
}
obj.SetLabels(labels)
component.Spec.Workload.Object = obj
components = append(components, component)
var appConfigComp v1alpha2.ApplicationConfigurationComponent
appConfigComp.ComponentName = name
//TODO(wonderflow): Temporarily we add health scope here, should change to use scope framework
appConfigComp.Scopes = append(appConfigComp.Scopes, v1alpha2.ComponentScope{ScopeReference: v1alpha1.TypedReference{
APIVersion: v1alpha2.SchemeGroupVersion.String(),
Kind: v1alpha2.HealthScopeKind,
Name: health.Name,
}})
//TODO(wonderflow): handle component data input/output here
compTraits, err := app.GetComponentTraits(name, env)
if err != nil {
return nil, v1alpha2.ApplicationConfiguration{}, nil, err
}
appConfigComp.Traits = compTraits
appConfig.Spec.Components = append(appConfig.Spec.Components, appConfigComp)
workloadObject.SetLabels(labels)
}
}
return components, appConfig, []oam.Object{&health}, nil
func addHealthScope(appConfig *v1alpha2.ApplicationConfiguration) *v1alpha2.HealthScope {
health := &v1alpha2.HealthScope{}
health.Name = FormatDefaultHealthScopeName(appConfig.Name)
health.Namespace = appConfig.Namespace
health.Spec.WorkloadReferences = make([]v1alpha1.TypedReference, 0)
for i := range appConfig.Spec.Components {
// TODO(wonderflow): Temporarily we add health scope here, should change to use scope framework
appConfig.Spec.Components[i].Scopes = append(appConfig.Spec.Components[i].Scopes, v1alpha2.ComponentScope{
ScopeReference: v1alpha1.TypedReference{
APIVersion: v1alpha2.SchemeGroupVersion.String(),
Kind: v1alpha2.HealthScopeKind,
Name: health.Name,
},
})
}
return health
}
+50 -73
View File
@@ -7,62 +7,41 @@ import (
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/appfile/template"
)
func TestApplication(t *testing.T) {
yaml1 := `name: myapp
components:
yamlNormal := `name: myapp
services:
frontend:
deployment:
image: inanimate/echo-server
env:
PORT: 8080
traits:
autoscaling:
max: 10
min: 1
rollout:
strategy: canary
step: 5
image: inanimate/echo-server
env:
PORT: 8080
autoscaling:
max: 10
min: 1
rollout:
strategy: canary
step: 5
backend:
cloneset:
image: "back:v1"
type: cloneset
image: "back:v1"
`
yaml2 := `name: myapp`
yaml3 := `components:
yamlNoService := `name: myapp`
yamlNoName := `services:
frontend:
deployment:
image: inanimate/echo-server
env:
PORT: 8080`
yaml4 := `name: myapp
components:
image: inanimate/echo-server
env:
PORT: 8080`
yamlTraitNotMap := `name: myapp
services:
frontend:
deployment:
image: inanimate/echo-server
scopes:
public-scope: true
appScopes:
public-scope:
networkPolicy: public`
yaml5 := `name: myapp
components:
frontend:
traits:
rollout:
strategy: canary
step: 5
backend:
cloneset:
image: "back:v1"
`
yaml6 := `name: myapp
components:
frontend:
deployment:
image: inanimate/echo-server
traits:
autoscaling: 10`
image: inanimate/echo-server
env:
PORT: 8080
autoscaling: 10`
cases := map[string]struct {
raw string
@@ -71,33 +50,33 @@ components:
ExpName string
ExpComponents []string
WantWorkload string
ExpWorklaod map[string]interface{}
ExpWorkload map[string]interface{}
ExpWorkloadType string
ExpTraits map[string]map[string]interface{}
}{
"normal case backend": {
raw: yaml1,
raw: yamlNormal,
ExpName: "myapp",
ExpComponents: []string{"backend", "frontend"},
WantWorkload: "backend",
ExpWorklaod: map[string]interface{}{
ExpWorkload: map[string]interface{}{
"image": "back:v1",
},
ExpWorkloadType: "cloneset",
ExpTraits: map[string]map[string]interface{}{},
},
"normal case frontend": {
raw: yaml1,
raw: yamlNormal,
ExpName: "myapp",
ExpComponents: []string{"backend", "frontend"},
WantWorkload: "frontend",
ExpWorklaod: map[string]interface{}{
ExpWorkload: map[string]interface{}{
"image": "inanimate/echo-server",
"env": map[string]interface{}{
"PORT": float64(8080),
},
},
ExpWorkloadType: "deployment",
ExpWorkloadType: "webservice",
ExpTraits: map[string]map[string]interface{}{
"autoscaling": {
"max": float64(10),
@@ -110,38 +89,36 @@ components:
},
},
"no component": {
raw: yaml2,
raw: yamlNoService,
ExpName: "myapp",
InValid: true,
InvalidReason: errors.New("at least one component is required"),
InvalidReason: errors.New("at least one service is required"),
},
"no name": {
raw: yaml3,
raw: yamlNoName,
ExpName: "",
InValid: true,
InvalidReason: errors.New("please provide an existed App name"),
},
"scopes not array": {
raw: yaml4,
ExpName: "myapp",
InValid: true,
InvalidReason: fmt.Errorf("format of scopes in 'frontend' must be string array"),
},
"workload not exist": {
raw: yaml5,
ExpName: "myapp",
InValid: true,
InvalidReason: fmt.Errorf("you must have only one workload in component 'frontend'"),
InvalidReason: errors.New("name is required"),
},
"trait must be map": {
raw: yaml6,
raw: yamlTraitNotMap,
ExpTraits: map[string]map[string]interface{}{
"autoscaling": {},
},
ExpName: "myapp",
InValid: true,
InvalidReason: fmt.Errorf("trait autoscaling in 'frontend' must be map"),
},
}
for caseName, c := range cases {
var app Application
tm := template.NewFakeTemplateManager()
for k := range c.ExpTraits {
tm.Templates[k] = &template.Template{
Captype: types.TypeTrait,
}
}
app := newApplication(nil, tm)
err := yaml.Unmarshal([]byte(c.raw), &app)
assert.NoError(t, err, caseName)
err = app.Validate()
@@ -152,7 +129,7 @@ components:
assert.Equal(t, c.ExpName, app.Name, caseName)
assert.Equal(t, c.ExpComponents, app.GetComponents(), caseName)
workloadType, workload := app.GetWorkload(c.WantWorkload)
assert.Equal(t, c.ExpWorklaod, workload, caseName)
assert.Equal(t, c.ExpWorkload, workload, caseName)
assert.Equal(t, c.ExpWorkloadType, workloadType, caseName)
traits, err := app.GetTraits(c.WantWorkload)
assert.NoError(t, err, caseName)
+33 -38
View File
@@ -3,22 +3,25 @@ package application
import (
"errors"
"strings"
"github.com/oam-dev/kubevela/pkg/appfile"
)
func (app *Application) SetWorkload(componentName, workloadType string, workloadData map[string]interface{}) error {
if app == nil {
return errors.New("app is nil pointer")
}
if workloadData == nil {
workloadData = make(map[string]interface{})
s, ok := app.Services[componentName]
if !ok {
s = appfile.Service{}
}
workloadData["name"] = strings.ToLower(componentName)
if app.Components == nil {
app.Components = make(map[string]map[string]interface{})
}
app.Components[componentName] = map[string]interface{}{
workloadType: workloadData,
s["type"] = workloadType
s["name"] = strings.ToLower(componentName)
for k, v := range workloadData {
s[k] = v
}
app.Services[componentName] = s
return app.Validate()
}
@@ -29,20 +32,22 @@ func (app *Application) SetTrait(componentName, traitType string, traitData map[
if traitData == nil {
traitData = make(map[string]interface{})
}
if app.Components == nil {
app.Components = make(map[string]map[string]interface{})
s, ok := app.Services[componentName]
if !ok {
s = appfile.Service{}
}
comp := app.Components[componentName]
if comp == nil {
comp = make(map[string]interface{})
t, ok := s[traitType]
if !ok {
t = make(map[string]interface{})
}
traits, err := app.GetTraits(componentName)
if err != nil {
return err
tm := t.(map[string]interface{})
for k, v := range traitData {
tm[k] = v
}
traits[traitType] = traitData
comp[Traits] = traits
app.Components[componentName] = comp
s[traitType] = t
app.Services[componentName] = s
return app.Validate()
}
@@ -50,30 +55,20 @@ func (app *Application) RemoveTrait(componentName, traitType string) error {
if app == nil {
return errors.New("app is nil pointer")
}
if app.Components == nil {
app.Components = make(map[string]map[string]interface{})
s, ok := app.Services[componentName]
if !ok {
return nil
}
comp := app.Components[componentName]
if comp == nil {
comp = make(map[string]interface{})
}
traits, err := app.GetTraits(componentName)
if err != nil {
return err
}
delete(traits, traitType)
comp[Traits] = traits
app.Components[componentName] = comp
return app.Validate()
delete(s, traitType)
return nil
}
func (app *Application) RemoveComponent(componentName string) error {
if app == nil {
return errors.New("app is nil pointer")
}
if app.Components == nil {
app.Components = make(map[string]map[string]interface{})
}
delete(app.Components, componentName)
return app.Validate()
delete(app.Services, componentName)
return nil
}
+9 -8
View File
@@ -10,10 +10,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/api/types"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
)
func (app *Application) Run(ctx context.Context, client client.Client, env *types.EnvMeta) error {
components, appconfig, scopes, err := app.OAM(env)
func (app *Application) Run(ctx context.Context, client client.Client, env *types.EnvMeta, io cmdutil.IOStreams) error {
components, appconfig, scopes, err := app.OAM(env, io)
if err != nil {
return err
}
@@ -28,20 +29,20 @@ func (app *Application) Run(ctx context.Context, client client.Client, env *type
return CreateOrUpdateAppConfig(ctx, client, appconfig)
}
func CreateOrUpdateComponent(ctx context.Context, client client.Client, comp v1alpha2.Component) error {
func CreateOrUpdateComponent(ctx context.Context, client client.Client, comp *v1alpha2.Component) error {
var getc v1alpha2.Component
key := ctypes.NamespacedName{Name: comp.Name, Namespace: comp.Namespace}
if err := client.Get(ctx, key, &getc); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
return client.Create(ctx, &comp)
return client.Create(ctx, comp)
}
comp.ResourceVersion = getc.ResourceVersion
return client.Update(ctx, &comp)
return client.Update(ctx, comp)
}
func CreateOrUpdateAppConfig(ctx context.Context, client client.Client, appConfig v1alpha2.ApplicationConfiguration) error {
func CreateOrUpdateAppConfig(ctx context.Context, client client.Client, appConfig *v1alpha2.ApplicationConfiguration) error {
var geta v1alpha2.ApplicationConfiguration
key := ctypes.NamespacedName{Name: appConfig.Name, Namespace: appConfig.Namespace}
var exist = true
@@ -52,10 +53,10 @@ func CreateOrUpdateAppConfig(ctx context.Context, client client.Client, appConfi
exist = false
}
if !exist {
return client.Create(ctx, &appConfig)
return client.Create(ctx, appConfig)
}
appConfig.ResourceVersion = geta.ResourceVersion
return client.Update(ctx, &appConfig)
return client.Update(ctx, appConfig)
}
func CreateScopes(ctx context.Context, client client.Client, scopes []oam.Object) error {
-35
View File
@@ -1,35 +0,0 @@
name: myapp
components:
frontend:
deployment:
image: inanimate/echo-server
env:
- PORT: "8080"
traits:
scale:
replica: 2
maxUnavailbe: 1
rollout:
strategy: canary
step: 5
expose:
service:
type: LoadBalancer
ports:
http:
service_port: 80
container_port: 8080
scopes:
- public-scope
- myapp-default-health
secrets:
secret-foo:
key1: 'pass-word'
globalScopes:
network:
public-scope:
networkPolicy: public
private-scope:
networkPolicy: private
health:
myapp-default-health: {}
+4 -3
View File
@@ -6,6 +6,7 @@ import (
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/commands/util"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/plugins"
@@ -78,7 +79,7 @@ func NewCompDeployCommands(c types.Args, ioStreams util.IOStreams) *cobra.Comman
if err := o.Complete(cmd, args); err != nil {
return err
}
return o.Run(cmd)
return o.Run(cmd, ioStreams)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
@@ -162,12 +163,12 @@ func (o *runOptions) Complete(cmd *cobra.Command, args []string) error {
return err
}
func (o *runOptions) Run(cmd *cobra.Command) error {
func (o *runOptions) Run(cmd *cobra.Command, io cmdutil.IOStreams) error {
staging, err := cmd.Flags().GetBool(Staging)
if err != nil {
return err
}
msg, err := oam.BaseRun(staging, o.App, o.KubeClient, o.Env)
msg, err := oam.BaseRun(staging, o.App, o.KubeClient, o.Env, io)
if err != nil {
return err
}
+1 -1
View File
@@ -97,7 +97,7 @@ func NewCompDeleteCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comm
}
ioStreams.Infof("Deleting Component '%s' from Application '%s'\n", o.CompName, o.AppName)
message, err := o.DeleteComponent()
message, err := o.DeleteComponent(ioStreams)
if err != nil {
return err
}
+1 -1
View File
@@ -66,7 +66,7 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
if err = o.Traits(); err != nil {
return err
}
_, err = oam.BaseRun(false, o.app, o.client, o.Env)
_, err = oam.BaseRun(false, o.app, o.client, o.Env, ioStreams)
if err != nil {
return err
}
+1 -1
View File
@@ -117,7 +117,7 @@ func mergeStagingComponents(deployed []apis.ComponentMeta, env *types.EnvMeta, i
}
var all []apis.ComponentMeta
for _, app := range apps {
comps, appConfig, _, err := app.OAM(env)
comps, appConfig, _, err := app.OAM(env, ioStreams)
if err != nil {
ioStreams.Errorf("convert app %s err %v\n", app.Name, err)
continue
+3 -3
View File
@@ -46,7 +46,7 @@ func NewRunCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
if err = o.LoadApp(cmd, args); err != nil {
return err
}
return o.Run()
return o.Run(ioStreams)
},
}
cmd.Flags().StringP("file", "f", "", "launch application from provided appfile")
@@ -77,9 +77,9 @@ func (o *appRunOptions) LoadApp(cmd *cobra.Command, args []string) error {
return nil
}
func (o *appRunOptions) Run() error {
func (o *appRunOptions) Run(io cmdutil.IOStreams) error {
o.Infof("Launching App Bundle \"%s\"\n", o.appName)
if err := o.app.Run(context.Background(), o.client, o.Env); err != nil {
if err := o.app.Run(context.Background(), o.client, o.Env, io); err != nil {
return err
}
o.Info("SUCCEED")
+2 -2
View File
@@ -73,7 +73,7 @@ func showApplication(cmd *cobra.Command, env *types.EnvMeta, appName string) err
table.AddRow(" Name", "Type", "Traits")
for compName := range app.Components {
for compName := range app.Services {
wtype, _ := app.GetWorkload(compName)
var outPutTraits []string
traits, _ := app.GetTraits(compName)
@@ -133,7 +133,7 @@ func showComponent(cmd *cobra.Command, env *types.EnvMeta, compName, appName str
return err
}
for cname := range app.Components {
for cname := range app.Services {
if cname != compName {
continue
}
+3 -3
View File
@@ -66,7 +66,7 @@ func AddTraitCommands(parentCmd *cobra.Command, c types.Args, ioStreams cmdutil.
return err
}
}
return o.Run(ctx, cmd)
return o.Run(ctx, cmd, ioStreams)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeTraits,
@@ -126,7 +126,7 @@ func (o *commandOptions) DetachTrait(cmd *cobra.Command, args []string) error {
return o.app.Save(o.Env.Name)
}
func (o *commandOptions) Run(ctx context.Context, cmd *cobra.Command) error {
func (o *commandOptions) Run(ctx context.Context, cmd *cobra.Command, io cmdutil.IOStreams) error {
if o.Detach {
o.Infof("Detaching %s from app %s\n", o.traitType, o.workloadName)
} else {
@@ -136,7 +136,7 @@ func (o *commandOptions) Run(ctx context.Context, cmd *cobra.Command) error {
if err != nil {
return err
}
msg, err := oam.TraitOperationRun(ctx, o.Client, o.Env, o.app, staging)
msg, err := oam.TraitOperationRun(ctx, o.Client, o.Env, o.app, staging, io)
if err != nil {
return err
}
+31 -18
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
@@ -20,6 +19,8 @@ import (
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/appfile/template"
"github.com/oam-dev/kubevela/pkg/application"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
)
@@ -72,7 +73,13 @@ func (o *appfileOptions) Run() error {
return err
}
comps, appConfig, err := app.BuildOAM(o.Env.Namespace, o.IO)
o.IO.Info("Loading templates ...")
tm, err := template.Load()
if err != nil {
return err
}
comps, appConfig, err := app.BuildOAM(o.Env.Namespace, o.IO, tm)
if err != nil {
return err
}
@@ -114,7 +121,7 @@ func (o *appfileOptions) Run() error {
}
o.IO.Infof("\nApplying deploy configs ...\n")
return o.ApplyAppConfig(appConfig)
return o.ApplyAppConfig(appConfig, comps)
}
// Apply deploy config resources for the app.
@@ -122,7 +129,7 @@ func (o *appfileOptions) Run() error {
// - for create, it displays app status along with information of url, metrics, ssh, logging.
// - for update, it rolls out a canary deployment and prints its information. User can verify the canary deployment.
// This will wait for user approval. If approved, it continues upgrading the whole; otherwise, it would rollback.
func (o *appfileOptions) ApplyAppConfig(ac *v1alpha2.ApplicationConfiguration) error {
func (o *appfileOptions) ApplyAppConfig(ac *v1alpha2.ApplicationConfiguration, comps []*v1alpha2.Component) error {
key := apitypes.NamespacedName{
Namespace: ac.Namespace,
Name: ac.Name,
@@ -138,21 +145,27 @@ func (o *appfileOptions) ApplyAppConfig(ac *v1alpha2.ApplicationConfiguration) e
default:
return err
}
return o.apply(ac)
}
func (o *appfileOptions) apply(ac *v1alpha2.ApplicationConfiguration) error {
cmd := exec.Command("kubectl", "apply", "-f", ".vela/deploy.yaml")
out, err := cmd.CombinedOutput()
o.IO.Infof("deploying======\n%s\n", out)
if err != nil {
if err := o.apply(ac, comps); err != nil {
return err
}
o.IO.Infof("app has been deployed %s%s%s\n", emojiRocket, emojiRocket, emojiRocket)
o.IO.Infof("\tURL: http://%s/\n", o.Env.Domain)
o.IO.Infof("\tPort forward: vela port-forward %s <port>\n", ac.Name)
o.IO.Infof("\tSSH: vela exec %s\n", ac.Name)
o.IO.Infof("\tLogging: vela log %s\n", ac.Name)
o.IO.Infof("\tMetric: TODO\n")
o.info(ac.Name)
return nil
}
func (o *appfileOptions) apply(ac *v1alpha2.ApplicationConfiguration, comps []*v1alpha2.Component) error {
for _, comp := range comps {
if err := application.CreateOrUpdateComponent(context.TODO(), o.Kubecli, comp); err != nil {
return err
}
}
return application.CreateOrUpdateAppConfig(context.TODO(), o.Kubecli, ac)
}
func (o *appfileOptions) info(name string) {
o.IO.Infof("app has been deployed %s%s%s\n", emojiRocket, emojiRocket, emojiRocket)
o.IO.Infof("\tURL: http://%s/\n", o.Env.Domain)
o.IO.Infof("\tPort forward: vela listen %s <port>\n", name)
o.IO.Infof("\tSSH: vela exec %s\n", name)
o.IO.Infof("\tLogging: vela logs %s\n", name)
o.IO.Infof("\tMetric: TODO\n")
}
-1
View File
@@ -4,6 +4,5 @@ const BaseTemplate = `
context: {
name: string
image: string
}
`
+2 -2
View File
@@ -191,7 +191,7 @@ func (o *DeleteOptions) DeleteApp() (string, error) {
return fmt.Sprintf("delete apps succeed %s from %s", o.AppName, o.Env.Name), nil
}
func (o *DeleteOptions) DeleteComponent() (string, error) {
func (o *DeleteOptions) DeleteComponent(io cmdutil.IOStreams) (string, error) {
var app *application.Application
var err error
if o.AppName != "" {
@@ -217,7 +217,7 @@ func (o *DeleteOptions) DeleteComponent() (string, error) {
// Remove component from appConfig in k8s cluster
ctx := context.Background()
if err := app.Run(ctx, o.Client, o.Env); err != nil {
if err := app.Run(ctx, o.Client, o.Env, io); err != nil {
return "", err
}
+9 -4
View File
@@ -3,6 +3,7 @@ package oam
import (
"context"
"fmt"
"os"
"strconv"
"strings"
@@ -14,6 +15,7 @@ import (
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/application"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/plugins"
"github.com/oam-dev/kubevela/pkg/server/apis"
)
@@ -225,14 +227,16 @@ func AttachTrait(c *gin.Context, body apis.TraitBody) (string, error) {
return "", err
}
kubeClient := c.MustGet("KubeClient")
return TraitOperationRun(c, kubeClient.(client.Client), env, appObj, staging)
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
return TraitOperationRun(c, kubeClient.(client.Client), env, appObj, staging, io)
}
func TraitOperationRun(ctx context.Context, c client.Client, env *types.EnvMeta, appObj *application.Application, staging bool) (string, error) {
func TraitOperationRun(ctx context.Context, c client.Client, env *types.EnvMeta, appObj *application.Application,
staging bool, io cmdutil.IOStreams) (string, error) {
if staging {
return "Staging saved", nil
}
err := appObj.Run(ctx, c, env)
err := appObj.Run(ctx, c, env, io)
if err != nil {
return "", err
}
@@ -270,5 +274,6 @@ func DetachTrait(c *gin.Context, envName string, traitType string, componentName
return "", err
}
kubeClient := c.MustGet("KubeClient")
return TraitOperationRun(c, kubeClient.(client.Client), env, appObj, staging)
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
return TraitOperationRun(c, kubeClient.(client.Client), env, appObj, staging, io)
}
+5 -7
View File
@@ -9,6 +9,7 @@ import (
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/application"
"github.com/oam-dev/kubevela/pkg/commands/util"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/plugins"
"cuelang.org/go/cue"
@@ -35,13 +36,10 @@ func LoadIfExist(envName string, workloadName string, appGroup string) (*applica
}
app, err := application.Load(envName, appName)
if err != nil {
return app, err
return nil, err
}
app.Name = appName
if app.Components == nil {
app.Components = make(map[string]map[string]interface{})
}
return app, nil
}
@@ -110,13 +108,13 @@ func BaseComplete(envName string, workloadName string, appName string, flagSet *
return app, app.Save(envName)
}
func BaseRun(staging bool, App *application.Application, kubeClient client.Client, Env *types.EnvMeta) (string, error) {
func BaseRun(staging bool, app *application.Application, kubeClient client.Client, Env *types.EnvMeta, io cmdutil.IOStreams) (string, error) {
if staging {
return "Staging saved", nil
}
var msg string
msg = fmt.Sprintf("Creating App %s\n", App.Name)
if err := App.Run(context.Background(), kubeClient, Env); err != nil {
msg = fmt.Sprintf("Creating App %s\n", app.Name)
if err := app.Run(context.Background(), kubeClient, Env, io); err != nil {
err = fmt.Errorf("create app err: %s", err)
return "", err
}
+1 -1
View File
@@ -129,7 +129,7 @@ func SinkTemp2Local(templates []types.Capability, dir string) int {
fmt.Printf("sync %s err: %v\n", tmp.Name, err)
continue
}
err = ioutil.WriteFile(filepath.Join(subDir, tmp.Name), data, 0644)
err = ioutil.WriteFile(filepath.Join(subDir, tmp.Name), data, 0o644)
if err != nil {
fmt.Printf("sync %s err: %v\n", tmp.Name, err)
continue
+6 -1
View File
@@ -1,7 +1,10 @@
package handler
import (
"os"
"github.com/gin-gonic/gin"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/server/util"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -43,6 +46,8 @@ func DeleteComponent(c *gin.Context) {
Env: envMeta,
AppName: appName,
CompName: componentName}
message, err := o.DeleteComponent()
message, err := o.DeleteComponent(
cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
util.AssembleResponse(c, message, err)
}
+5 -1
View File
@@ -1,11 +1,14 @@
package handler
import (
"os"
"github.com/gin-gonic/gin"
"github.com/spf13/pflag"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/api/types"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/plugins"
"github.com/oam-dev/kubevela/pkg/server/apis"
@@ -36,7 +39,8 @@ func CreateWorkload(c *gin.Context) {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return
}
msg, err := oam.BaseRun(body.Staging, appObj, kubeClient.(client.Client), env)
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
msg, err := oam.BaseRun(body.Staging, appObj, kubeClient.(client.Client), env, io)
if err != nil {
util.HandleError(c, util.StatusInternalServerError, err.Error())
return