diff --git a/docs/examples/envbinding/envpatch-with-wild-match.yaml b/docs/examples/envbinding/envpatch-with-wild-match.yaml new file mode 100644 index 000000000..e66167a88 --- /dev/null +++ b/docs/examples/envbinding/envpatch-with-wild-match.yaml @@ -0,0 +1,74 @@ +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: example-app + namespace: default +spec: + components: + - name: podinfo + type: webservice + properties: + image: stefanprodan/podinfo + traits: + - type: scaler + properties: + replicas: 1 + - name: hello-world + type: webservice + properties: + image: crccheck/hello-world + traits: + - type: scaler + properties: + replicas: 1 + - name: nginx + type: worker + properties: + image: nginx + traits: + - type: scaler + properties: + replicas: 1 + policies: + - name: example-multi-env-policy + type: env-binding + properties: + envs: + - name: test + placement: + clusterSelector: + name: local + namespaceSelector: + name: test + patch: + components: + - name: podinfo # patch to component named podinfo, no type check + traits: + - type: scaler + properties: + replicas: 2 + + - name: staging + placement: + clusterSelector: + name: remote + patch: + components: # patch to all webservice components + - type: webservice + traits: + - type: scaler + properties: + replicas: 3 + + - name: prod + placement: + clusterSelector: + name: remote + namespaceSelector: + name: prod + patch: + components: # patch to all components + - traits: + - type: scaler + properties: + replicas: 3 \ No newline at end of file diff --git a/pkg/policy/envbinding/patch.go b/pkg/policy/envbinding/patch.go index 5c0fa2321..60ebd49b8 100644 --- a/pkg/policy/envbinding/patch.go +++ b/pkg/policy/envbinding/patch.go @@ -146,8 +146,17 @@ func PatchApplication(base *v1beta1.Application, patch *v1alpha1.EnvPatch, selec var errs errors2.ErrorList var err error for _, comp := range patch.Components { - if baseComp, exists := compMaps[comp.Name]; exists { - if baseComp.Type != comp.Type { + if comp.Name == "" { + for compName, baseComp := range compMaps { + if comp.Type == "" || comp.Type == baseComp.Type { + compMaps[compName], err = MergeComponent(baseComp, comp.DeepCopy()) + if err != nil { + errs = append(errs, errors.Wrapf(err, "failed to merge component %s", compName)) + } + } + } + } else if baseComp, exists := compMaps[comp.Name]; exists { + if baseComp.Type != comp.Type && comp.Type != "" { compMaps[comp.Name] = comp.ToApplicationComponent() } else { compMaps[comp.Name], err = MergeComponent(baseComp, comp.DeepCopy()) diff --git a/pkg/policy/envbinding/patch_test.go b/pkg/policy/envbinding/patch_test.go index e90e75485..d73ddc0fc 100644 --- a/pkg/policy/envbinding/patch_test.go +++ b/pkg/policy/envbinding/patch_test.go @@ -294,6 +294,240 @@ func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) { }, }, }, + "patch-all-comp": { + baseApp: &v1beta1.Application{ + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{{ + Name: "express-server", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a.example.com", + }), + }, { + Type: "ingress-1-20", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a-1-20.example.com", + }), + }}, + }, { + Name: "express-worker", + Type: "worker", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + }}, + }, + }, + envName: "prod", + envPatch: v1alpha1.EnvPatch{ + Components: []v1alpha1.EnvComponentPatch{{ + Name: "", + Type: "", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "busybox", + }), + Traits: []v1alpha1.EnvTraitPatch{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "b.example.com", + }), + }}, + }}, + }, + expectedApp: &v1beta1.Application{ + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{{ + Name: "express-server", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "busybox", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "b.example.com", + }), + }, { + Type: "ingress-1-20", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a-1-20.example.com", + }), + }}, + }, { + Name: "express-worker", + Type: "worker", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "busybox", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "b.example.com", + }), + }}, + }}, + }, + }, + }, + "patch-type-comp": { + baseApp: &v1beta1.Application{ + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{{ + Name: "express-server", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a.example.com", + }), + }, { + Type: "ingress-1-20", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a-1-20.example.com", + }), + }}, + }, { + Name: "express-worker", + Type: "worker", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + }}, + }, + }, + envName: "prod", + envPatch: v1alpha1.EnvPatch{ + Components: []v1alpha1.EnvComponentPatch{{ + Name: "", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "busybox", + }), + Traits: []v1alpha1.EnvTraitPatch{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "b.example.com", + }), + }}, + }}, + }, + expectedApp: &v1beta1.Application{ + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{{ + Name: "express-server", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "busybox", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "b.example.com", + }), + }, { + Type: "ingress-1-20", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a-1-20.example.com", + }), + }}, + }, { + Name: "express-worker", + Type: "worker", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + }}, + }, + }, + }, + "patch-without-type-specified": { + baseApp: &v1beta1.Application{ + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{{ + Name: "express-server", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a.example.com", + }), + }, { + Type: "ingress-1-20", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a-1-20.example.com", + }), + }}, + }, { + Name: "express-worker", + Type: "worker", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + }}, + }, + }, + envName: "prod", + envPatch: v1alpha1.EnvPatch{ + Components: []v1alpha1.EnvComponentPatch{{ + Name: "express-worker", + Type: "", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "busybox", + }), + Traits: []v1alpha1.EnvTraitPatch{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "b.example.com", + }), + }}, + }}, + }, + expectedApp: &v1beta1.Application{ + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{{ + Name: "express-server", + Type: "webservice", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "crccheck/hello-world", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a.example.com", + }), + }, { + Type: "ingress-1-20", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "a-1-20.example.com", + }), + }}, + }, { + Name: "express-worker", + Type: "worker", + Properties: util.Object2RawExtension(map[string]interface{}{ + "image": "busybox", + }), + Traits: []common.ApplicationTrait{{ + Type: "ingress", + Properties: util.Object2RawExtension(map[string]interface{}{ + "domain": "b.example.com", + }), + }}, + }}, + }, + }, + }, } for name, tc := range testCases { diff --git a/pkg/stdlib/pkgs/multicluster.cue b/pkg/stdlib/pkgs/multicluster.cue index 751f107df..aa0691bdf 100644 --- a/pkg/stdlib/pkgs/multicluster.cue +++ b/pkg/stdlib/pkgs/multicluster.cue @@ -15,8 +15,8 @@ } #Component: { - name: string - type: string + name?: string + type?: string properties?: {...} traits?: [...{ type: string