diff --git a/docs/en/application.md b/docs/en/application.md index 48a7d2db1..b71a4511a 100644 --- a/docs/en/application.md +++ b/docs/en/application.md @@ -151,7 +151,6 @@ After the `Application` resource is applied to Kubernetes cluster, the KubeVela |`app.oam.dev/name=` | The name of the application it belongs to | |`app.oam.dev/component=` | The name of the component it belongs to | |`trait.oam.dev/resource=` | The name of trait resource instance | - -> TBD: the revision names and labels for resource instances are currently work in progress. +|`app.oam.dev/appRevision=` | The name of the application revision it belongs to | > TBD: a demo for kubectl apply above Application CR and show full detailed underlying resources. diff --git a/docs/en/cue/component.md b/docs/en/cue/component.md index 0e4747358..81e89c830 100644 --- a/docs/en/cue/component.md +++ b/docs/en/cue/component.md @@ -175,7 +175,7 @@ spec: KubeVela allows you to reference the runtime information of your application via `conext` keyword. -The most widely used context is application name(`context.appName`) and component name(`context.name`). +The most widely used context is application name(`context.appName`) component name(`context.name`). ```cue context: { @@ -204,7 +204,17 @@ output: { > Note that `context` information are auto-injected before resources are applied to target cluster. -> TBD: full available information in CUE `context`. +### Full available information in CUE `context` + +| Context Variable | Description | +| :--: | :---------: | +| `context.appRevision` | The revision of the application | +| `context.appName` | The name of the application | +| `context.name` | The name of the component of the application | +| `context.namespace` | The namespace of the application | +| `context.output` | The rendered workload API resource of the component, this usually used in trait | +| `context.outputs.` | The rendered trait API resource of the component, this usually used in trait | + ## Composition diff --git a/docs/examples/appdeployment/README.md b/docs/examples/appdeployment/README.md index a7c6f28f7..a14b4a99a 100644 --- a/docs/examples/appdeployment/README.md +++ b/docs/examples/appdeployment/README.md @@ -90,7 +90,7 @@ $ kubectl apply -f appdeployment-2-traffic.yaml ``` - Note that for traffic split to work, your must have the following labels set in pods (This is automatically set in default `webservice` workload template): + Note that for traffic split to work, your must have the following labels set in pods (This is automatically set by KubeVela Revision Mechanism): ```shell "app.oam.dev/component": "testsvc" diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index a1514ef26..8a0fadac4 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -534,12 +534,8 @@ func evalWorkloadWithContext(pCtx process.Context, wl *Workload, appName, compNa return nil, nil, errors.Wrapf(err, "evaluate base template component=%s app=%s", compName, appName) } - labels := map[string]string{ - oam.WorkloadTypeLabel: wl.Type, - oam.LabelAppName: appName, - oam.LabelAppComponent: compName, - } - util.AddLabels(componentWorkload, labels) + var commonLabels = definition.GetCommonLabels(pCtx.BaseContextLabels()) + util.AddLabels(componentWorkload, util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.WorkloadTypeLabel: wl.Type})) component := &v1alpha2.Component{} // we need to marshal the workload to byte array before sending them to the k8s @@ -551,11 +547,7 @@ func evalWorkloadWithContext(pCtx process.Context, wl *Workload, appName, compNa if err != nil { return nil, nil, errors.Wrapf(err, "evaluate trait=%s template for component=%s app=%s", assist.Name, compName, appName) } - labels := map[string]string{ - oam.TraitTypeLabel: assist.Type, - oam.LabelAppName: appName, - oam.LabelAppComponent: compName, - } + labels := util.MergeMapOverrideWithDst(commonLabels, map[string]string{oam.TraitTypeLabel: assist.Type}) if assist.Name != "" { labels[oam.TraitResource] = assist.Name } diff --git a/pkg/appfile/parser_test.go b/pkg/appfile/parser_test.go index 8ebb8522f..daaab7e5b 100644 --- a/pkg/appfile/parser_test.go +++ b/pkg/appfile/parser_test.go @@ -287,7 +287,8 @@ var _ = Describe("Test appFile parser", func() { It("application without-trait will only create appfile with workload", func() { // TestApp is test data var TestApp = &Appfile{ - Name: "test", + RevisionName: "test-v1", + Name: "test", Workloads: []*Workload{ { Name: "myweb", @@ -385,10 +386,11 @@ var _ = Describe("Test appFile parser", func() { "kind": "ManualScalerTrait", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "app.oam.dev/component": "myweb", - "app.oam.dev/name": "test", - "trait.oam.dev/type": "scaler", - "trait.oam.dev/resource": "scaler", + "app.oam.dev/component": "myweb", + "app.oam.dev/appRevision": "test-v1", + "app.oam.dev/name": "test", + "trait.oam.dev/type": "scaler", + "trait.oam.dev/resource": "scaler", }, }, "spec": map[string]interface{}{"replicaCount": int64(10)}, @@ -443,9 +445,10 @@ var _ = Describe("Test appFile parser", func() { "kind": "Deployment", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "workload.oam.dev/type": "worker", - "app.oam.dev/component": "myweb", - "app.oam.dev/name": "test", + "workload.oam.dev/type": "worker", + "app.oam.dev/component": "myweb", + "app.oam.dev/appRevision": "test-v1", + "app.oam.dev/name": "test", }, }, "spec": map[string]interface{}{ @@ -507,7 +510,8 @@ var _ = Describe("Test appfile parser to parse helm module", func() { It("Test application containing helm module", func() { appFile := &Appfile{ - Name: appName, + Name: appName, + RevisionName: appName + "-v1", Workloads: []*Workload{ { Name: compName, @@ -571,10 +575,11 @@ var _ = Describe("Test appfile parser to parse helm module", func() { "kind": "ManualScalerTrait", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "app.oam.dev/component": compName, - "app.oam.dev/name": appName, - "trait.oam.dev/type": "scaler", - "trait.oam.dev/resource": "scaler", + "app.oam.dev/component": compName, + "app.oam.dev/name": appName, + "trait.oam.dev/type": "scaler", + "trait.oam.dev/resource": "scaler", + "app.oam.dev/appRevision": appName + "-v1", }, }, "spec": map[string]interface{}{"replicaCount": int64(10)}, @@ -655,9 +660,10 @@ var _ = Describe("Test appfile parser to parse helm module", func() { "kind": "Deployment", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "workload.oam.dev/type": "webapp-chart", - "app.oam.dev/component": compName, - "app.oam.dev/name": appName, + "workload.oam.dev/type": "webapp-chart", + "app.oam.dev/component": compName, + "app.oam.dev/name": appName, + "app.oam.dev/appRevision": appName + "-v1", }, }, }), @@ -719,7 +725,8 @@ spec: } var testAppfile = func() *Appfile { return &Appfile{ - Name: appName, + RevisionName: appName + "-v1", + Name: appName, Workloads: []*Workload{ { Name: compName, @@ -780,10 +787,11 @@ spec: "kind": "ManualScalerTrait", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "app.oam.dev/component": compName, - "app.oam.dev/name": appName, - "trait.oam.dev/type": "scaler", - "trait.oam.dev/resource": "scaler", + "app.oam.dev/component": compName, + "app.oam.dev/name": appName, + "app.oam.dev/appRevision": appName + "-v1", + "trait.oam.dev/type": "scaler", + "trait.oam.dev/resource": "scaler", }, }, "spec": map[string]interface{}{"replicaCount": int64(10)}, diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go index 8aadc2a25..fb95269f4 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go @@ -26,8 +26,6 @@ import ( "strconv" "time" - common2 "github.com/oam-dev/kubevela/pkg/utils/common" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -37,9 +35,11 @@ import ( "github.com/stretchr/testify/assert" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + v1beta12 "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -50,6 +50,7 @@ import ( "github.com/oam-dev/kubevela/pkg/controller/utils" "github.com/oam-dev/kubevela/pkg/oam" "github.com/oam-dev/kubevela/pkg/oam/util" + common2 "github.com/oam-dev/kubevela/pkg/utils/common" ) // TODO: Refactor the tests to not copy and paste duplicated code 10 times @@ -126,9 +127,10 @@ var _ = Describe("Test Application Controller", func() { }, ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ - "workload.oam.dev/type": "worker", - "app.oam.dev/component": compName, - "app.oam.dev/name": appName, + "workload.oam.dev/type": "worker", + "app.oam.dev/component": compName, + "app.oam.dev/name": appName, + "app.oam.dev/appRevision": appName + "-v1", }, }, Spec: v1.DeploymentSpec{ @@ -164,10 +166,11 @@ var _ = Describe("Test Application Controller", func() { "kind": "ManualScalerTrait", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "trait.oam.dev/type": "scaler", - "app.oam.dev/component": compName, - "app.oam.dev/name": appName, - "trait.oam.dev/resource": "scaler", + "trait.oam.dev/type": "scaler", + "app.oam.dev/component": compName, + "app.oam.dev/name": appName, + "app.oam.dev/appRevision": appName + "-v1", + "trait.oam.dev/resource": "scaler", }, }, "spec": map[string]interface{}{ @@ -557,10 +560,11 @@ spec: "kind": "Service", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "trait.oam.dev/type": "AuxiliaryWorkload", - "app.oam.dev/name": "app-with-composedworkload-trait", - "app.oam.dev/component": "myweb-composed-3", - "trait.oam.dev/resource": "service", + "trait.oam.dev/type": "AuxiliaryWorkload", + "app.oam.dev/name": "app-with-composedworkload-trait", + "app.oam.dev/appRevision": "app-with-composedworkload-trait-v1", + "app.oam.dev/component": "myweb-composed-3", + "trait.oam.dev/resource": "service", }, }, "spec": map[string]interface{}{ @@ -754,7 +758,7 @@ spec: fmt.Println(cmp.Diff(expDeployment6, gotD2)) Expect(gotD2).Should(BeEquivalentTo(expDeployment6)) - By("update component5 with new spec, rename component6 it should create new component ") + By("Update Application with new revision, component5 with new spec, rename component6 it should create new component ") curApp.SetNamespace(app.Namespace) curApp.Spec.Components[0] = v1beta1.ApplicationComponent{ @@ -809,6 +813,7 @@ spec: }, component5)).Should(BeNil()) Expect(json.Unmarshal(component5.Spec.Workload.Raw, gotD)).Should(BeNil()) expDeployment.Spec.Template.Spec.Containers[0].Image = "busybox3" + expDeployment.Labels["app.oam.dev/appRevision"] = app.Name + "-v2" Expect(gotD).Should(BeEquivalentTo(expDeployment)) expDeployment7 := getExpDeployment("myweb7", app.Name) @@ -820,6 +825,7 @@ spec: Expect(component7.ObjectMeta.Labels).Should(BeEquivalentTo(map[string]string{oam.LabelAppName: app.Name})) gotD3 := &v1.Deployment{} Expect(json.Unmarshal(component7.Spec.Workload.Raw, gotD3)).Should(BeNil()) + expDeployment7.Labels["app.oam.dev/appRevision"] = app.Name + "-v2" fmt.Println(cmp.Diff(gotD3, expDeployment7)) Expect(gotD3).Should(BeEquivalentTo(expDeployment7)) Expect(k8sClient.Delete(ctx, app)).Should(BeNil()) @@ -1134,10 +1140,11 @@ spec: "kind": "ConfigMap", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "trait.oam.dev/type": "AuxiliaryWorkload", - "app.oam.dev/component": compName, - "app.oam.dev/name": app.Name, - "trait.oam.dev/resource": "gameconfig", + "trait.oam.dev/type": "AuxiliaryWorkload", + "app.oam.dev/component": compName, + "app.oam.dev/name": app.Name, + "trait.oam.dev/resource": "gameconfig", + "app.oam.dev/appRevision": app.Name + "-v1", }, }, "data": map[string]interface{}{ @@ -1154,10 +1161,11 @@ spec: "kind": "Ingress", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "trait.oam.dev/type": "ingress", - "trait.oam.dev/resource": "ingress", - "app.oam.dev/component": compName, - "app.oam.dev/name": app.Name, + "trait.oam.dev/type": "ingress", + "trait.oam.dev/resource": "ingress", + "app.oam.dev/component": compName, + "app.oam.dev/name": app.Name, + "app.oam.dev/appRevision": app.Name + "-v1", }, }, "spec": map[string]interface{}{ @@ -1177,10 +1185,11 @@ spec: "kind": "Service", "metadata": map[string]interface{}{ "labels": map[string]interface{}{ - "trait.oam.dev/type": "ingress", - "trait.oam.dev/resource": "service", - "app.oam.dev/component": compName, - "app.oam.dev/name": app.Name, + "trait.oam.dev/type": "ingress", + "trait.oam.dev/resource": "service", + "app.oam.dev/component": compName, + "app.oam.dev/name": app.Name, + "app.oam.dev/appRevision": app.Name + "-v1", }, }, "spec": map[string]interface{}{ @@ -1379,8 +1388,41 @@ spec: }, appRevision)).Should(BeNil()) appConfig, err := util.RawExtension2AppConfig(appRevision.Spec.ApplicationConfiguration) Expect(err).ShouldNot(HaveOccurred()) - Expect(string(appConfig.Spec.Components[0].Traits[0].Trait.Raw)).Should(BeEquivalentTo("{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"labels\":{\"app.oam.dev/component\":\"myweb\",\"app.oam.dev/name\":\"app-import-pkg\",\"trait.oam.dev/resource\":\"service\",\"trait.oam.dev/type\":\"ingress-import\"},\"name\":\"myweb\"},\"spec\":{\"ports\":[{\"port\":80,\"targetPort\":80}],\"selector\":{\"app.oam.dev/component\":\"myweb\"}}}")) - Expect(string(appConfig.Spec.Components[0].Traits[1].Trait.Raw)).Should(BeEquivalentTo("{\"apiVersion\":\"networking.k8s.io/v1beta1\",\"kind\":\"Ingress\",\"metadata\":{\"labels\":{\"app.oam.dev/component\":\"myweb\",\"app.oam.dev/name\":\"app-import-pkg\",\"trait.oam.dev/resource\":\"ingress\",\"trait.oam.dev/type\":\"ingress-import\"},\"name\":\"myweb\"},\"spec\":{\"rules\":[{\"host\":\"abc.com\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"myweb\",\"servicePort\":80},\"path\":\"/\"}]}}]}}")) + var gotSvc corev1.Service + Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[0].Trait.Raw, &gotSvc)).ShouldNot(HaveOccurred()) + Expect(cmp.Diff(&gotSvc, &corev1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "myweb", + Labels: map[string]string{ + "app.oam.dev/component": "myweb", + "app.oam.dev/name": "app-import-pkg", + "trait.oam.dev/resource": "service", + "trait.oam.dev/type": "ingress-import", + "app.oam.dev/appRevision": "app-import-pkg-v1", + }}, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 80, TargetPort: intstr.FromInt(80)}}, + Selector: map[string]string{"app.oam.dev/component": "myweb"}, + }})).Should(BeEquivalentTo("")) + var gotIngress v1beta12.Ingress + Expect(json.Unmarshal(appConfig.Spec.Components[0].Traits[1].Trait.Raw, &gotIngress)).ShouldNot(HaveOccurred()) + Expect(cmp.Diff(&gotIngress, &v1beta12.Ingress{ + TypeMeta: metav1.TypeMeta{Kind: "Ingress", APIVersion: "networking.k8s.io/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "myweb", + Labels: map[string]string{ + "app.oam.dev/component": "myweb", + "app.oam.dev/name": "app-import-pkg", + "trait.oam.dev/resource": "ingress", + "trait.oam.dev/type": "ingress-import", + "app.oam.dev/appRevision": "app-import-pkg-v1", + }}, + Spec: v1beta12.IngressSpec{Rules: []v1beta12.IngressRule{{Host: "abc.com", + IngressRuleValue: v1beta12.IngressRuleValue{HTTP: &v1beta12.HTTPIngressRuleValue{Paths: []v1beta12.HTTPIngressPath{{ + Path: "/", + Backend: v1beta12.IngressBackend{ServiceName: "myweb", ServicePort: intstr.FromInt(80)}}}}}}}, + }})).Should(BeEquivalentTo("")) By("Check ApplicationContext created") appContext := &v1alpha2.ApplicationContext{} diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go index 58c07e3ad..f6dba31b3 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/apply.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/apply.go @@ -110,6 +110,7 @@ func (h *appHandler) apply(ctx context.Context, appRev *v1beta1.ApplicationRevis // don't create components and AC if revision-only annotation is set if ac.Annotations[oam.AnnotationAppRevisionOnly] == "true" { + h.FinalizeAppRevision(appRev, ac, comps) return h.createOrUpdateAppRevision(ctx, appRev) } diff --git a/pkg/dsl/definition/template.go b/pkg/dsl/definition/template.go index f5c06f724..411faa5b6 100644 --- a/pkg/dsl/definition/template.go +++ b/pkg/dsl/definition/template.go @@ -146,7 +146,7 @@ func (wd *workloadDef) Complete(ctx process.Context, abstractTemplate string, pa func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader, ns string) (map[string]interface{}, error) { var root = initRoot(ctx.BaseContextLabels()) - var commonLabels = getCommonLabels(ctx.BaseContextLabels()) + var commonLabels = GetCommonLabels(ctx.BaseContextLabels()) base, assists := ctx.Output() componentWorkload, err := base.Unstructured() @@ -334,7 +334,8 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string, param return nil } -func getCommonLabels(contextLabels map[string]string) map[string]string { +// GetCommonLabels will convert context based labels to OAM standard labels +func GetCommonLabels(contextLabels map[string]string) map[string]string { var commonLabels = map[string]string{} for k, v := range contextLabels { switch k { @@ -343,7 +344,7 @@ func getCommonLabels(contextLabels map[string]string) map[string]string { case process.ContextName: commonLabels[oam.LabelAppComponent] = v case process.ContextAppRevision: - // TODO(wonderflow): do we need to add appRevision into common labels ? Actually it's appConfig name in our current design. + commonLabels[oam.LabelAppRevision] = v } } return commonLabels @@ -359,7 +360,7 @@ func initRoot(contextLabels map[string]string) map[string]interface{} { func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, ns string) (map[string]interface{}, error) { var root = initRoot(ctx.BaseContextLabels()) - var commonLabels = getCommonLabels(ctx.BaseContextLabels()) + var commonLabels = GetCommonLabels(ctx.BaseContextLabels()) _, assists := ctx.Output() outputs := make(map[string]interface{}) diff --git a/pkg/dsl/process/handle.go b/pkg/dsl/process/handle.go index 739a7bad8..1eaef033a 100644 --- a/pkg/dsl/process/handle.go +++ b/pkg/dsl/process/handle.go @@ -48,11 +48,11 @@ const ( type Context interface { SetBase(base model.Instance) AppendAuxiliaries(auxiliaries ...Auxiliary) - SetConfigs(configs []map[string]string) Output() (model.Instance, []Auxiliary) BaseContextFile() string ExtendedContextFile() string BaseContextLabels() map[string]string + SetConfigs(configs []map[string]string) InsertSecrets(outputSecretName string, requiredSecrets []RequiredSecrets) }