diff --git a/e2e/addon/mock/testdata/mock-addon/definitions/json-patch.cue b/e2e/addon/mock/testdata/mock-addon/definitions/json-patch.cue new file mode 100644 index 000000000..8097a665e --- /dev/null +++ b/e2e/addon/mock/testdata/mock-addon/definitions/json-patch.cue @@ -0,0 +1,50 @@ +"kustomize-json-patch-mock-adddon": { + attributes: { + podDisruptive: false + } + description: "A list of JSON6902 patch to selected target" + labels: { + "ui-hidden": "true" + } + type: "trait" +} + +template: { + patch: { + spec: { + patches: parameter.patchesJson + } + } + + parameter: { + // +usage=A list of JSON6902 patch. + patchesJson: [...#jsonPatchItem] + } + + // +usage=Contains a JSON6902 patch + #jsonPatchItem: { + target: #selector + patch: [...{ + // +usage=operation to perform + op: string | "add" | "remove" | "replace" | "move" | "copy" | "test" + // +usage=operate path e.g. /foo/bar + path: string + // +usage=specify source path when op is copy/move + from?: string + // +usage=specify opraation value when op is test/add/replace + value?: string + }] + } + + // +usage=Selector specifies a set of resources + #selector: { + group?: string + version?: string + kind?: string + namespace?: string + name?: string + annotationSelector?: string + labelSelector?: string + } + +} diff --git a/e2e/addon/mock/testdata/mock-addon/readme.md b/e2e/addon/mock/testdata/mock-addon/readme.md new file mode 100644 index 000000000..7edc58492 --- /dev/null +++ b/e2e/addon/mock/testdata/mock-addon/readme.md @@ -0,0 +1 @@ +Test addon readme.md file \ No newline at end of file diff --git a/pkg/addon/addon.go b/pkg/addon/addon.go index 7ee65829f..f214d1ab8 100644 --- a/pkg/addon/addon.go +++ b/pkg/addon/addon.go @@ -79,6 +79,9 @@ const ( // ReadmeFileName is the addon readme file name ReadmeFileName string = "README.md" + // LegacyReadmeFileName is the addon readme lower case file name + LegacyReadmeFileName string = "readme.md" + // MetadataFileName is the addon meatadata.yaml file name MetadataFileName string = "metadata.yaml" @@ -205,7 +208,7 @@ type Pattern struct { var Patterns = []Pattern{ {Value: ReadmeFileName}, {Value: MetadataFileName}, {Value: TemplateFileName}, {Value: ParameterFileName}, {IsDir: true, Value: ResourcesDirName}, {IsDir: true, Value: DefinitionsDirName}, - {IsDir: true, Value: DefSchemaName}, {IsDir: true, Value: ViewDirName}, {Value: AppTemplateCueFileName}, {Value: GlobalParameterFileName}} + {IsDir: true, Value: DefSchemaName}, {IsDir: true, Value: ViewDirName}, {Value: AppTemplateCueFileName}, {Value: GlobalParameterFileName}, {Value: LegacyReadmeFileName}} // GetPatternFromItem will check if the file path has a valid pattern, return empty string if it's invalid. // AsyncReader is needed to calculate relative path @@ -285,6 +288,7 @@ func GetUIDataFromReader(r AsyncReader, meta *SourceMeta, opt ListOptions) (*UID read func(a *UIData, reader AsyncReader, readPath string) error }{ ReadmeFileName: {!opt.GetDetail, readReadme}, + LegacyReadmeFileName: {!opt.GetDetail, readReadme}, MetadataFileName: {false, readMetadata}, DefinitionsDirName: {!opt.GetDefinition, readDefFile}, ParameterFileName: {!opt.GetParameter, readParamFile}, @@ -481,6 +485,10 @@ func readMetadata(a *UIData, reader AsyncReader, readPath string) error { } func readReadme(a *UIData, reader AsyncReader, readPath string) error { + // the detail will contain readme.md or README.md, if the content already is filled, don't read another. + if len(a.Detail) != 0 { + return nil + } content, err := reader.ReadFile(readPath) if err != nil { return err @@ -1099,6 +1107,10 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { } for _, def := range defs { + if !checkBondComponentExist(*def, *app) { + continue + } + // if binding component exist, apply the definition addOwner(def, app) err = h.apply.Apply(h.ctx, def, apply.DisableUpdateAnnotation()) if err != nil { @@ -1123,6 +1135,9 @@ func (h *Installer) dispatchAddonResource(addon *InstallPackage) error { } for _, o := range auxiliaryOutputs { + if !checkBondComponentExist(*o, *app) { + continue + } addOwner(o, app) err = h.apply.Apply(h.ctx, o, apply.DisableUpdateAnnotation()) if err != nil { diff --git a/pkg/addon/utils.go b/pkg/addon/utils.go index 07af51cdb..72e76e6ee 100644 --- a/pkg/addon/utils.go +++ b/pkg/addon/utils.go @@ -58,6 +58,10 @@ const ( func passDefInAppAnnotation(defs []*unstructured.Unstructured, app *v1beta1.Application) error { var comps, traits, workflowSteps, policies []string for _, def := range defs { + if !checkBondComponentExist(*def, *app) { + // if the definition binding a component, and the component not exist, skip recording. + continue + } switch def.GetObjectKind().GroupVersionKind().Kind { case v1beta1.ComponentDefinitionKind: comps = append(comps, def.GetName()) @@ -474,3 +478,20 @@ func produceDefConflictError(conflictDefs map[string]string) error { errorInfo += "if you want override them, please use argument '--override-definitions' to enable \n" return errors.New(errorInfo) } + +// checkBondComponentExistt will check the ready-to-apply object(def or auxiliary outputs) whether bind to a component +// if the target component not exist, return false. +func checkBondComponentExist(u unstructured.Unstructured, app v1beta1.Application) bool { + comp, existKey := u.GetAnnotations()[oam.AnnotationIgnoreWithoutCompKey] + if !existKey { + // if an object(def or auxiliary outputs ) binding no components return true + return true + } + for _, component := range app.Spec.Components { + if component.Name == comp { + // the bond component exists, return ture + return true + } + } + return false +} diff --git a/pkg/addon/utils_test.go b/pkg/addon/utils_test.go index 7c01cd928..f77b853ca 100644 --- a/pkg/addon/utils_test.go +++ b/pkg/addon/utils_test.go @@ -33,6 +33,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" + "github.com/oam-dev/kubevela/apis/core.oam.dev/common" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" velatypes "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/oam" @@ -287,6 +288,36 @@ func TestMakeChart(t *testing.T) { assert.Equal(t, isChartDir, true) } +func TestCheckObjectBindingComponent(t *testing.T) { + existingBindingDef := unstructured.Unstructured{} + existingBindingDef.SetAnnotations(map[string]string{oam.AnnotationIgnoreWithoutCompKey: "kustomize"}) + + emptyAnnoDef := unstructured.Unstructured{} + emptyAnnoDef.SetAnnotations(map[string]string{"test": "onlyForTest"}) + testCases := map[string]struct { + object unstructured.Unstructured + app v1beta1.Application + res bool + }{ + "bindingExist": {object: existingBindingDef, + app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{Name: "kustomize"}}}}, + res: true}, + "NotExisting": {object: existingBindingDef, + app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{Name: "helm"}}}}, + res: false}, + "NoBidingAnnotation": {object: emptyAnnoDef, + app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{Name: "kustomize"}}}}, + res: true}, + "EmptyApp": {object: existingBindingDef, + app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{}}}, + res: false}, + } + for _, s := range testCases { + result := checkBondComponentExist(s.object, s.app) + assert.Equal(t, result, s.res) + } +} + const ( compDefYaml = ` apiVersion: core.oam.dev/v1beta1 diff --git a/pkg/addon/versioned_registry.go b/pkg/addon/versioned_registry.go index 571090dc5..c18337bab 100644 --- a/pkg/addon/versioned_registry.go +++ b/pkg/addon/versioned_registry.go @@ -86,6 +86,7 @@ func (i *versionedRegistry) GetAddonUIData(ctx context.Context, addonName, versi Detail: wholePackage.Detail, Definitions: wholePackage.Definitions, AvailableVersions: wholePackage.AvailableVersions, + CUEDefinitions: wholePackage.CUEDefinitions, }, nil } diff --git a/pkg/apiserver/domain/service/addon.go b/pkg/apiserver/domain/service/addon.go index 8aedcf178..c3a203ee8 100644 --- a/pkg/apiserver/domain/service/addon.go +++ b/pkg/apiserver/domain/service/addon.go @@ -26,6 +26,7 @@ import ( "sync" "time" + errors3 "github.com/pkg/errors" v1 "k8s.io/api/core/v1" errors2 "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -44,6 +45,7 @@ import ( "github.com/oam-dev/kubevela/pkg/apiserver/utils" "github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode" "github.com/oam-dev/kubevela/pkg/apiserver/utils/log" + "github.com/oam-dev/kubevela/pkg/definition" "github.com/oam-dev/kubevela/pkg/multicluster" "github.com/oam-dev/kubevela/pkg/oam" addonutil "github.com/oam-dev/kubevela/pkg/utils/addon" @@ -68,7 +70,7 @@ type AddonService interface { } // AddonImpl2AddonRes convert pkgaddon.UIData to the type apiserver need -func AddonImpl2AddonRes(impl *pkgaddon.UIData) (*apis.DetailAddonResponse, error) { +func AddonImpl2AddonRes(impl *pkgaddon.UIData, config *rest.Config) (*apis.DetailAddonResponse, error) { var defs []*apis.AddonDefinition for _, def := range impl.Definitions { obj := &unstructured.Unstructured{} @@ -83,6 +85,20 @@ func AddonImpl2AddonRes(impl *pkgaddon.UIData) (*apis.DetailAddonResponse, error Description: obj.GetAnnotations()["definition.oam.dev/description"], }) } + + for _, cueDef := range impl.CUEDefinitions { + def := definition.Definition{Unstructured: unstructured.Unstructured{}} + err := def.FromCUEString(cueDef.Data, config) + if err != nil { + return nil, errors3.Wrapf(err, "fail to render definition: %s in cue's format", cueDef.Name) + } + defs = append(defs, &apis.AddonDefinition{ + Name: def.GetName(), + DefType: def.GetKind(), + Description: def.GetAnnotations()["definition.oam.dev/description"], + }) + } + if impl.Meta.DeployTo != nil && impl.Meta.DeployTo.LegacyRuntimeCluster != impl.Meta.DeployTo.RuntimeCluster { impl.Meta.DeployTo.LegacyRuntimeCluster = impl.Meta.DeployTo.LegacyRuntimeCluster || impl.Meta.DeployTo.RuntimeCluster impl.Meta.DeployTo.RuntimeCluster = impl.Meta.DeployTo.LegacyRuntimeCluster || impl.Meta.DeployTo.RuntimeCluster @@ -175,7 +191,7 @@ func (u *addonServiceImpl) GetAddon(ctx context.Context, name string, registry s addon.UISchema = renderAddonCustomUISchema(ctx, u.kubeClient, name, renderDefaultUISchema(addon.APISchema)) - a, err := AddonImpl2AddonRes(addon) + a, err := AddonImpl2AddonRes(addon, u.config) if err != nil { return nil, err } @@ -284,7 +300,7 @@ func (u *addonServiceImpl) ListAddons(ctx context.Context, registry, query strin var addonResources []*apis.DetailAddonResponse for _, a := range addons { - addonRes, err := AddonImpl2AddonRes(a) + addonRes, err := AddonImpl2AddonRes(a, u.config) if err != nil { log.Logger.Errorf("err while converting AddonImpl to DetailAddonResponse: %v", err) continue diff --git a/pkg/oam/labels.go b/pkg/oam/labels.go index 312aa2adb..158af3328 100644 --- a/pkg/oam/labels.go +++ b/pkg/oam/labels.go @@ -226,6 +226,9 @@ const ( // AnnotationResourceURL records the source url of the Kubernetes object AnnotationResourceURL = "app.oam.dev/resource-url" + + // AnnotationIgnoreWithoutCompKey indicates the bond component + AnnotationIgnoreWithoutCompKey = "addon.oam.dev/ignore-without-component" ) const ( diff --git a/test/e2e-apiserver-test/addon_test.go b/test/e2e-apiserver-test/addon_test.go index 774bdb2d7..6e2d4cecb 100644 --- a/test/e2e-apiserver-test/addon_test.go +++ b/test/e2e-apiserver-test/addon_test.go @@ -101,6 +101,9 @@ var _ = Describe("Test addon rest api", func() { var addon apisv1.DetailAddonResponse Expect(decodeResponseBody(res, &addon)).Should(Succeed()) Expect(addon.Name).Should(BeEquivalentTo("mock-addon")) + Expect(addon.Detail).Should(BeEquivalentTo("Test addon readme.md file")) + Expect(len(addon.Definitions)).Should(BeEquivalentTo(1)) + Expect(addon.Definitions[0].Name).Should(BeEquivalentTo("kustomize-json-patch-mock-adddon")) }) It("enable addon ", func() {