diff --git a/apis/core.oam.dev/v1beta1/policy_definition.go b/apis/core.oam.dev/v1beta1/policy_definition.go index 9890acaaf..f37e53c80 100644 --- a/apis/core.oam.dev/v1beta1/policy_definition.go +++ b/apis/core.oam.dev/v1beta1/policy_definition.go @@ -43,6 +43,9 @@ type PolicyDefinitionStatus struct { // ConditionedStatus reflects the observed status of a resource condition.ConditionedStatus `json:",inline"` + // ConfigMapRef refer to a ConfigMap which contains OpenAPI V3 JSON schema of Component parameters. + ConfigMapRef string `json:"configMapRef,omitempty"` + // LatestRevision of the component definition // +optional LatestRevision *common.Revision `json:"latestRevision,omitempty"` diff --git a/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml b/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml index 59586009f..4fbb238c4 100644 --- a/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml +++ b/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml @@ -3390,6 +3390,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains + OpenAPI V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/charts/vela-core/crds/core.oam.dev_definitionrevisions.yaml b/charts/vela-core/crds/core.oam.dev_definitionrevisions.yaml index 04d0ee836..fb6c5650f 100644 --- a/charts/vela-core/crds/core.oam.dev_definitionrevisions.yaml +++ b/charts/vela-core/crds/core.oam.dev_definitionrevisions.yaml @@ -636,6 +636,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains + OpenAPI V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/charts/vela-core/crds/core.oam.dev_policydefinitions.yaml b/charts/vela-core/crds/core.oam.dev_policydefinitions.yaml index 59218f5bf..e3d9895fe 100644 --- a/charts/vela-core/crds/core.oam.dev_policydefinitions.yaml +++ b/charts/vela-core/crds/core.oam.dev_policydefinitions.yaml @@ -244,6 +244,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains OpenAPI + V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml b/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml index 59586009f..4fbb238c4 100644 --- a/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml +++ b/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml @@ -3390,6 +3390,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains + OpenAPI V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/charts/vela-minimal/crds/core.oam.dev_definitionrevisions.yaml b/charts/vela-minimal/crds/core.oam.dev_definitionrevisions.yaml index 04d0ee836..fb6c5650f 100644 --- a/charts/vela-minimal/crds/core.oam.dev_definitionrevisions.yaml +++ b/charts/vela-minimal/crds/core.oam.dev_definitionrevisions.yaml @@ -636,6 +636,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains + OpenAPI V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/charts/vela-minimal/crds/core.oam.dev_policydefinitions.yaml b/charts/vela-minimal/crds/core.oam.dev_policydefinitions.yaml index 59218f5bf..e3d9895fe 100644 --- a/charts/vela-minimal/crds/core.oam.dev_policydefinitions.yaml +++ b/charts/vela-minimal/crds/core.oam.dev_policydefinitions.yaml @@ -244,6 +244,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains OpenAPI + V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml index cb17750af..8281e121b 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml @@ -3390,6 +3390,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains + OpenAPI V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_definitionrevisions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_definitionrevisions.yaml index 34853d734..a33e02b27 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_definitionrevisions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_definitionrevisions.yaml @@ -636,6 +636,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains + OpenAPI V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_policydefinitions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_policydefinitions.yaml index c855a4867..f3910a52f 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_policydefinitions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_policydefinitions.yaml @@ -244,6 +244,10 @@ spec: - type type: object type: array + configMapRef: + description: ConfigMapRef refer to a ConfigMap which contains OpenAPI + V3 JSON schema of Component parameters. + type: string latestRevision: description: LatestRevision of the component definition properties: diff --git a/pkg/apiserver/rest/usecase/definition.go b/pkg/apiserver/rest/usecase/definition.go index 476073118..8938114a2 100644 --- a/pkg/apiserver/rest/usecase/definition.go +++ b/pkg/apiserver/rest/usecase/definition.go @@ -190,7 +190,7 @@ func (d *definitionUsecaseImpl) listDefinitions(ctx context.Context, list *unstr // DetailDefinition get definition detail func (d *definitionUsecaseImpl) DetailDefinition(ctx context.Context, name, defType string) (*apisv1.DetailDefinitionResponse, error) { - if !utils.StringsContain([]string{"component", "trait", "workflowstep"}, defType) { + if !utils.StringsContain([]string{"component", "trait", "workflowstep", "policy"}, defType) { return nil, bcode.ErrDefinitionTypeNotSupport } var cm v1.ConfigMap diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/policies/policydefinition/policydefinition_controller.go b/pkg/controller/core.oam.dev/v1alpha2/core/policies/policydefinition/policydefinition_controller.go index 26fc3645b..73aefb17f 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/core/policies/policydefinition/policydefinition_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/core/policies/policydefinition/policydefinition_controller.go @@ -124,6 +124,29 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.record.Event(&policydefinition, event.Warning("failed to garbage collect DefinitionRevision of type PolicyDefinition", err)) } + def := utils.NewCapabilityPolicyDef(&policydefinition) + def.Name = req.NamespacedName.Name + // Store the parameter of policyDefinition to configMap + cmName, err := def.StoreOpenAPISchema(ctx, r.Client, r.pd, req.Namespace, req.Name, defRev.Name) + if err != nil { + klog.InfoS("Could not capability in ConfigMap", "err", err) + r.record.Event(&(policydefinition), event.Warning("Could not store capability in ConfigMap", err)) + return ctrl.Result{}, util.PatchCondition(ctx, r, &(policydefinition), + condition.ReconcileError(fmt.Errorf(util.ErrStoreCapabilityInConfigMap, def.Name, err))) + } + + if policydefinition.Status.ConfigMapRef != cmName { + policydefinition.Status.ConfigMapRef = cmName + if err := r.UpdateStatus(ctx, &policydefinition); err != nil { + klog.InfoS("Could not update policyDefinition Status", "err", err) + r.record.Event(&policydefinition, event.Warning("cannot update PolicyDefinition Status", err)) + return ctrl.Result{}, util.PatchCondition(ctx, r, &policydefinition, + condition.ReconcileError(fmt.Errorf(util.ErrUpdatePolicyDefinition, policydefinition.Name, err))) + } + klog.InfoS("Successfully updated the status.configMapRef of the PolicyDefinition", "policyDefinition", + klog.KRef(req.Namespace, req.Name), "status.configMapRef", cmName) + } + return ctrl.Result{}, nil } diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/policies/policydefinition/policydefinition_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/core/policies/policydefinition/policydefinition_controller_test.go new file mode 100644 index 000000000..8130de528 --- /dev/null +++ b/pkg/controller/core.oam.dev/v1alpha2/core/policies/policydefinition/policydefinition_controller_test.go @@ -0,0 +1,268 @@ +/* + + Copyright 2021 The KubeVela Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +package policydefinition + +import ( + "context" + "fmt" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/yaml" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" + "github.com/oam-dev/kubevela/apis/types" + "github.com/oam-dev/kubevela/pkg/oam/testutil" + "github.com/oam-dev/kubevela/pkg/oam/util" +) + +var _ = Describe("Apply PolicyDefinition to store its schema to ConfigMap Test", func() { + ctx := context.Background() + var ns corev1.Namespace + + Context("When the PolicyDefinition is valid, but the namespace doesn't exist, should occur errors", func() { + It("Apply PolicyDefinition", func() { + By("Apply PolicyDefinition") + var validPolicyDefinition = ` +apiVersion: core.oam.dev/v1beta1 +kind: PolicyDefinition +metadata: + annotations: + definition.oam.dev/description: Apply raw kubernetes objects for your policy + name: apply-object + namespace: not-exist +spec: + schematic: + cue: + template: | + output: { + apiVersion: "core.oam.dev/v1alpha1" + kind: "EnvBinding" + spec: { + engine: parameter.engine + appTemplate: { + apiVersion: "core.oam.dev/v1beta1" + kind: "Application" + metadata: { + name: context.appName + namespace: context.namespace + } + spec: { + components: context.components + } + } + envs: parameter.envs + } + } + + #Env: { + name: string + patch: components: [...{ + name: string + type: string + properties: {...} + }] + placement: clusterSelector: { + labels?: [string]: string + name?: string + } + } + + parameter: { + engine: *"ocm" | string + envs: [...#Env] + } +` + + var def v1beta1.PolicyDefinition + Expect(yaml.Unmarshal([]byte(validPolicyDefinition), &def)).Should(BeNil()) + Expect(k8sClient.Create(ctx, &def)).Should(Not(Succeed())) + }) + }) + + Context("When the PolicyDefinition is valid, should create a ConfigMap", func() { + var PolicyDefinitionName = "policy-obj" + var namespace = "ns-plc-def-1" + req := reconcile.Request{NamespacedName: client.ObjectKey{Name: PolicyDefinitionName, Namespace: namespace}} + + It("Apply PolicyDefinition", func() { + ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + By("Create a namespace") + Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{})) + + By("Apply PolicyDefinition") + var validPolicyDefinition = ` +apiVersion: core.oam.dev/v1beta1 +kind: PolicyDefinition +metadata: + annotations: + definition.oam.dev/description: Apply raw kubernetes objects for your Policy + name: policy-obj + namespace: ns-plc-def-1 +spec: + schematic: + cue: + template: | + output: { + apiVersion: "core.oam.dev/v1alpha1" + kind: "EnvBinding" + spec: { + engine: parameter.engine + appTemplate: { + apiVersion: "core.oam.dev/v1beta1" + kind: "Application" + metadata: { + name: context.appName + namespace: context.namespace + } + spec: { + components: context.components + } + } + envs: parameter.envs + } + } + + #Env: { + name: string + patch: components: [...{ + name: string + type: string + properties: {...} + }] + placement: clusterSelector: { + labels?: [string]: string + name?: string + } + } + + parameter: { + engine: *"ocm" | string + envs: [...#Env] + } +` + + var def v1beta1.PolicyDefinition + Expect(yaml.Unmarshal([]byte(validPolicyDefinition), &def)).Should(BeNil()) + Expect(k8sClient.Create(ctx, &def)).Should(Succeed()) + testutil.ReconcileRetry(&r, req) + + By("Check whether ConfigMap is created") + var cm corev1.ConfigMap + name := fmt.Sprintf("policy-%s%s", types.CapabilityConfigMapNamePrefix, PolicyDefinitionName) + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, &cm) + return err == nil + }, 30*time.Second, time.Second).Should(BeTrue()) + Expect(cm.Data[types.OpenapiV3JSONSchema]).Should(Not(Equal(""))) + Expect(cm.Labels["definition.oam.dev/name"]).Should(Equal(PolicyDefinitionName)) + + By("Check whether ConfigMapRef refer to right") + Eventually(func() string { + _ = k8sClient.Get(ctx, client.ObjectKey{Namespace: def.Namespace, Name: def.Name}, &def) + return def.Status.ConfigMapRef + }, 30*time.Second, time.Second).Should(Equal(name)) + + By("Delete the policy") + Expect(k8sClient.Delete(ctx, &def)).Should(Succeed()) + testutil.ReconcileRetry(&r, req) + }) + }) + + Context("When the PolicyDefinition is invalid, should report issues", func() { + var invalidPolicyDefinitionName = "invalid-plc1" + var namespace = "ns-plc-def2" + BeforeEach(func() { + ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + By("Create a namespace") + Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(Succeed(), &util.AlreadyExistMatcher{})) + }) + + It("Applying invalid PolicyDefinition", func() { + By("Apply the PolicyDefinition") + var invalidPolicyDefinition = ` +apiVersion: core.oam.dev/v1beta1 +kind: PolicyDefinition +metadata: + annotations: + definition.oam.dev/description: Apply raw kubernetes objects for your policy + name: invalid-plc1 + namespace: ns-plc-def2 +spec: + schematic: + cue: + template: | + output: { + apiVersion: "core.oam.dev/v1alpha1" + kind: "EnvBinding" + spec: { + engine: parameter.engine + appTemplate: { + apiVersion: "core.oam.dev/v1beta1" + kind: "Application" + metadata: { + name: context.appName + namespace: context.namespace + } + spec: { + components: context.components + } + } + envs: parameter.envs + } + } + + #Env: { + name: string + patch: components: [...{ + name: string + type: string + properties: {...} + }] + placement: clusterSelector: { + labels?: [string]: string + name?: string + } + } + + +` + + var invalidDef v1beta1.PolicyDefinition + Expect(yaml.Unmarshal([]byte(invalidPolicyDefinition), &invalidDef)).Should(BeNil()) + Expect(k8sClient.Create(ctx, &invalidDef)).Should(Succeed()) + gotPolicyDefinition := &v1beta1.PolicyDefinition{} + Expect(k8sClient.Get(ctx, client.ObjectKey{Name: invalidPolicyDefinitionName, Namespace: namespace}, gotPolicyDefinition)).Should(BeNil()) + }) + }) +}) diff --git a/pkg/controller/utils/capability.go b/pkg/controller/utils/capability.go index 33655dec4..bbec3471e 100644 --- a/pkg/controller/utils/capability.go +++ b/pkg/controller/utils/capability.go @@ -72,6 +72,7 @@ const ( typeTraitDefinition = "trait" typeComponentDefinition = "component" typeWorkflowStepDefinition = "workflowstep" + typePolicyStepDefinition = "policy" ) // ErrNoSectionParameterInCue means there is not parameter section in Cue template of a workload @@ -567,6 +568,76 @@ func (def *CapabilityStepDefinition) StoreOpenAPISchema(ctx context.Context, k8s return cmName, nil } +// CapabilityPolicyDefinition is the Capability struct for PolicyDefinition +type CapabilityPolicyDefinition struct { + Name string `json:"name"` + PolicyDefinition v1beta1.PolicyDefinition `json:"policyDefinition"` + + CapabilityBaseDefinition +} + +// NewCapabilityPolicyDef will create a CapabilityPolicyDefinition +func NewCapabilityPolicyDef(policydefinition *v1beta1.PolicyDefinition) CapabilityPolicyDefinition { + var def CapabilityPolicyDefinition + def.Name = policydefinition.Name + def.PolicyDefinition = *policydefinition.DeepCopy() + return def +} + +// GetOpenAPISchema gets OpenAPI v3 schema by StepDefinition name +func (def *CapabilityPolicyDefinition) GetOpenAPISchema(pd *packages.PackageDiscover, name string) ([]byte, error) { + capability, err := appfile.ConvertTemplateJSON2Object(name, nil, def.PolicyDefinition.Spec.Schematic) + if err != nil { + return nil, fmt.Errorf("failed to convert WorkflowStepDefinition to Capability Object") + } + return getOpenAPISchema(capability, pd) +} + +// StoreOpenAPISchema stores OpenAPI v3 schema from StepDefinition in ConfigMap +func (def *CapabilityPolicyDefinition) StoreOpenAPISchema(ctx context.Context, k8sClient client.Client, + pd *packages.PackageDiscover, namespace, name, revName string) (string, error) { + var jsonSchema []byte + var err error + + jsonSchema, err = def.GetOpenAPISchema(pd, name) + if err != nil { + return "", fmt.Errorf("failed to generate OpenAPI v3 JSON schema for capability %s: %w", def.Name, err) + } + + policyDefinition := def.PolicyDefinition + ownerReference := []metav1.OwnerReference{{ + APIVersion: policyDefinition.APIVersion, + Kind: policyDefinition.Kind, + Name: policyDefinition.Name, + UID: policyDefinition.GetUID(), + Controller: pointer.BoolPtr(true), + BlockOwnerDeletion: pointer.BoolPtr(true), + }} + cmName, err := def.CreateOrUpdateConfigMap(ctx, k8sClient, namespace, policyDefinition.Name, typePolicyStepDefinition, policyDefinition.Labels, nil, jsonSchema, ownerReference) + if err != nil { + return cmName, err + } + + // Create a configmap to store parameter for each definitionRevision + defRev := new(v1beta1.DefinitionRevision) + if err = k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: revName}, defRev); err != nil { + return "", err + } + ownerReference = []metav1.OwnerReference{{ + APIVersion: defRev.APIVersion, + Kind: defRev.Kind, + Name: defRev.Name, + UID: defRev.GetUID(), + Controller: pointer.BoolPtr(true), + BlockOwnerDeletion: pointer.BoolPtr(true), + }} + _, err = def.CreateOrUpdateConfigMap(ctx, k8sClient, namespace, revName, typePolicyStepDefinition, defRev.Spec.PolicyDefinition.Labels, nil, jsonSchema, ownerReference) + if err != nil { + return cmName, err + } + return cmName, nil +} + // CapabilityBaseDefinition is the base struct for CapabilityWorkloadDefinition and CapabilityTraitDefinition type CapabilityBaseDefinition struct { }