[Backport release-1.5] Feat: Check def binding to a component (#4539)

* finish logic

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit aae7af45cb)

* finish tests

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

small fix

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix ci

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 0418ad5207)

* fix comments

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix ci

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 7469b0497e)

* fix test

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 49749d7c17)

* fix tests

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
(cherry picked from commit 5b029147c1)

Co-authored-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
This commit is contained in:
github-actions[bot]
2022-08-02 21:37:53 +08:00
committed by GitHub
co-authored by 楚岳
parent 992e636211
commit 62df98818e
9 changed files with 145 additions and 4 deletions
@@ -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
}
}
+1
View File
@@ -0,0 +1 @@
Test addon readme.md file
+16 -1
View File
@@ -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 {
+21
View File
@@ -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
}
+31
View File
@@ -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
+1
View File
@@ -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
}
+19 -3
View File
@@ -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
+3
View File
@@ -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 (
+3
View File
@@ -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() {