mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
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:
@@ -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: {}
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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{})
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package template
|
||||
|
||||
type FakeTemplateManager struct {
|
||||
*manager
|
||||
}
|
||||
|
||||
func NewFakeTemplateManager() *FakeTemplateManager {
|
||||
return &FakeTemplateManager{
|
||||
manager: newManager(),
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Vendored
-35
@@ -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: {}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,5 @@ const BaseTemplate = `
|
||||
|
||||
context: {
|
||||
name: string
|
||||
image: string
|
||||
}
|
||||
`
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user