github.com/oam-dev/kubevela@v1.9.11/pkg/addon/utils_test.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package addon 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "reflect" 24 "testing" 25 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 "github.com/stretchr/testify/assert" 29 "helm.sh/helm/v3/pkg/chartutil" 30 v1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 33 "sigs.k8s.io/yaml" 34 35 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 36 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 37 velatypes "github.com/oam-dev/kubevela/apis/types" 38 "github.com/oam-dev/kubevela/pkg/oam" 39 "github.com/oam-dev/kubevela/pkg/oam/util" 40 ) 41 42 var _ = Describe("Test definition check", func() { 43 var compDef v1beta1.ComponentDefinition 44 var traitDef v1beta1.TraitDefinition 45 var wfStepDef v1beta1.WorkflowStepDefinition 46 47 BeforeEach(func() { 48 compDef = v1beta1.ComponentDefinition{} 49 traitDef = v1beta1.TraitDefinition{} 50 wfStepDef = v1beta1.WorkflowStepDefinition{} 51 52 Expect(yaml.Unmarshal([]byte(compDefYaml), &compDef)).Should(BeNil()) 53 Expect(k8sClient.Create(ctx, &compDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{})) 54 55 Expect(yaml.Unmarshal([]byte(traitDefYaml), &traitDef)).Should(BeNil()) 56 Expect(k8sClient.Create(ctx, &traitDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{})) 57 58 Expect(yaml.Unmarshal([]byte(wfStepDefYaml), &wfStepDef)).Should(BeNil()) 59 Expect(k8sClient.Create(ctx, &wfStepDef)).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{})) 60 }) 61 62 It("Test pass def to app annotation", func() { 63 c := v1beta1.ComponentDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "ComponentDefinition"}} 64 c.SetName("my-comp") 65 66 t := v1beta1.TraitDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "TraitDefinition"}} 67 t.SetName("my-trait") 68 69 w := v1beta1.WorkflowStepDefinition{TypeMeta: metav1.TypeMeta{APIVersion: "core.oam.dev/v1beta1", Kind: "WorkflowStepDefinition"}} 70 w.SetName("my-wfstep") 71 72 var defs []*unstructured.Unstructured 73 cDef, err := util.Object2Unstructured(c) 74 Expect(err).Should(BeNil()) 75 defs = append(defs, cDef) 76 tDef, err := util.Object2Unstructured(t) 77 defs = append(defs, tDef) 78 Expect(err).Should(BeNil()) 79 wDef, err := util.Object2Unstructured(w) 80 Expect(err).Should(BeNil()) 81 defs = append(defs, wDef) 82 83 addonApp := v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "addon-app", Namespace: velatypes.DefaultKubeVelaNS}} 84 err = passDefInAppAnnotation(defs, &addonApp) 85 Expect(err).Should(BeNil()) 86 87 anno := addonApp.GetAnnotations() 88 Expect(len(anno)).Should(BeEquivalentTo(3)) 89 Expect(anno[compDefAnnotation]).Should(BeEquivalentTo("my-comp")) 90 Expect(anno[traitDefAnnotation]).Should(BeEquivalentTo("my-trait")) 91 Expect(anno[workflowStepDefAnnotation]).Should(BeEquivalentTo("my-wfstep")) 92 }) 93 94 It("Test checkAddonHasBeenUsed func", func() { 95 addonApp := v1beta1.Application{} 96 Expect(yaml.Unmarshal([]byte(addonAppYaml), &addonApp)).Should(BeNil()) 97 98 app1 := v1beta1.Application{} 99 Expect(yaml.Unmarshal([]byte(testApp1Yaml), &app1)).Should(BeNil()) 100 Expect(k8sClient.Create(ctx, &app1)).Should(BeNil()) 101 102 app2 := v1beta1.Application{} 103 Expect(yaml.Unmarshal([]byte(testApp2Yaml), &app2)).Should(BeNil()) 104 Expect(k8sClient.Create(ctx, &app2)).Should(BeNil()) 105 106 Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-ns"}})) 107 app3 := v1beta1.Application{} 108 Expect(yaml.Unmarshal([]byte(testApp3Yaml), &app3)).Should(BeNil()) 109 Expect(k8sClient.Create(ctx, &app3)).Should(BeNil()) 110 111 app4 := v1beta1.Application{} 112 Expect(yaml.Unmarshal([]byte(testApp4Yaml), &app4)).Should(BeNil()) 113 Expect(k8sClient.Create(ctx, &app4)).Should(BeNil()) 114 115 usedApps, err := checkAddonHasBeenUsed(ctx, k8sClient, "my-addon", addonApp, cfg) 116 Expect(err).Should(BeNil()) 117 Expect(len(usedApps)).Should(BeEquivalentTo(4)) 118 }) 119 }) 120 121 func TestMerge2Map(t *testing.T) { 122 res := make(map[string]bool) 123 merge2DefMap(compDefAnnotation, "my-comp1,my-comp2", res) 124 merge2DefMap(traitDefAnnotation, "my-trait1,my-trait2", res) 125 merge2DefMap(workflowStepDefAnnotation, "my-wfStep1,my-wfStep2", res) 126 assert.Equal(t, 6, len(res)) 127 } 128 129 func TestUsingAddonInfo(t *testing.T) { 130 apps := []v1beta1.Application{ 131 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-1"}}, 132 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-2", Name: "app-2"}}, 133 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-3"}}, 134 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-3", Name: "app-3"}}, 135 } 136 res := appsDependsOnAddonErrInfo(apps) 137 assert.Contains(t, res, "and other 1 more applications. Please delete all of them before removing.") 138 139 apps = []v1beta1.Application{ 140 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-1"}}, 141 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-2", Name: "app-2"}}, 142 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-3"}}, 143 } 144 res = appsDependsOnAddonErrInfo(apps) 145 assert.Contains(t, res, "Please delete all of them before removing.") 146 147 apps = []v1beta1.Application{ 148 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-1"}}, 149 } 150 res = appsDependsOnAddonErrInfo(apps) 151 assert.Contains(t, res, "this addon is being used by: namespace-1/app-1 applications. Please delete all of them before removing.") 152 153 apps = []v1beta1.Application{ 154 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-1", Name: "app-1"}}, 155 {ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-2", Name: "app-2"}}, 156 } 157 res = appsDependsOnAddonErrInfo(apps) 158 assert.Contains(t, res, ". Please delete all of them before removing.") 159 } 160 161 func TestIsAddonDir(t *testing.T) { 162 var isAddonDir bool 163 var err error 164 var meta *Meta 165 var metaYaml []byte 166 167 // Non-existent dir 168 isAddonDir, err = IsAddonDir("non-existent-dir") 169 assert.Equal(t, isAddonDir, false) 170 assert.Error(t, err) 171 172 // Not a directory (a file) 173 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "local", "metadata.yaml")) 174 assert.Equal(t, isAddonDir, false) 175 assert.Contains(t, err.Error(), "not a directory") 176 177 // No metadata.yaml 178 isAddonDir, err = IsAddonDir(".") 179 assert.Equal(t, isAddonDir, false) 180 assert.Contains(t, err.Error(), "exists in directory") 181 182 // Empty metadata.yaml 183 err = os.MkdirAll(filepath.Join("testdata", "testaddon"), 0700) 184 assert.NoError(t, err) 185 defer func() { 186 os.RemoveAll(filepath.Join("testdata", "testaddon")) 187 }() 188 err = os.WriteFile(filepath.Join("testdata", "testaddon", MetadataFileName), []byte{}, 0644) 189 assert.NoError(t, err) 190 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon")) 191 assert.Equal(t, isAddonDir, false) 192 assert.Contains(t, err.Error(), "missing") 193 194 // Empty addon name 195 meta = &Meta{} 196 metaYaml, err = yaml.Marshal(meta) 197 assert.NoError(t, err) 198 err = os.WriteFile(filepath.Join("testdata", "testaddon", MetadataFileName), metaYaml, 0644) 199 assert.NoError(t, err) 200 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon")) 201 assert.Equal(t, isAddonDir, false) 202 assert.Contains(t, err.Error(), "addon name is empty") 203 204 // Empty addon version 205 meta = &Meta{ 206 Name: "name", 207 } 208 metaYaml, err = yaml.Marshal(meta) 209 assert.NoError(t, err) 210 err = os.WriteFile(filepath.Join("testdata", "testaddon", MetadataFileName), metaYaml, 0644) 211 assert.NoError(t, err) 212 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon")) 213 assert.Equal(t, isAddonDir, false) 214 assert.Contains(t, err.Error(), "addon version is empty") 215 216 // No metadata.yaml 217 meta = &Meta{ 218 Name: "name", 219 Version: "1.0.0", 220 } 221 metaYaml, err = yaml.Marshal(meta) 222 assert.NoError(t, err) 223 err = os.WriteFile(filepath.Join("testdata", "testaddon", MetadataFileName), metaYaml, 0644) 224 assert.NoError(t, err) 225 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon")) 226 assert.Equal(t, isAddonDir, false) 227 assert.Contains(t, err.Error(), "exists in directory") 228 229 // Empty template.yaml 230 err = os.WriteFile(filepath.Join("testdata", "testaddon", TemplateFileName), []byte{}, 0644) 231 assert.NoError(t, err) 232 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon")) 233 assert.Equal(t, isAddonDir, false) 234 assert.Contains(t, err.Error(), "missing") 235 236 // Empty template.cue 237 err = os.WriteFile(filepath.Join("testdata", "testaddon", AppTemplateCueFileName), []byte{}, 0644) 238 assert.NoError(t, err) 239 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon")) 240 assert.Equal(t, isAddonDir, false) 241 assert.Contains(t, err.Error(), renderOutputCuePath) 242 243 // Pass all checks 244 cmd := InitCmd{ 245 Path: filepath.Join("testdata", "testaddon2"), 246 AddonName: "testaddon2", 247 } 248 err = cmd.CreateScaffold() 249 assert.NoError(t, err) 250 defer func() { 251 _ = os.RemoveAll(filepath.Join("testdata", "testaddon2")) 252 }() 253 isAddonDir, err = IsAddonDir(filepath.Join("testdata", "testaddon2")) 254 assert.Equal(t, isAddonDir, true) 255 assert.NoError(t, err) 256 } 257 258 func TestMakeChart(t *testing.T) { 259 var err error 260 261 // Not a addon dir 262 err = MakeChartCompatible(".", true) 263 assert.Contains(t, err.Error(), "not an addon dir") 264 265 // Valid addon dir 266 cmd := InitCmd{ 267 Path: filepath.Join("testdata", "testaddon"), 268 AddonName: "testaddon", 269 } 270 err = cmd.CreateScaffold() 271 assert.NoError(t, err) 272 defer func() { 273 _ = os.RemoveAll(filepath.Join("testdata", "testaddon")) 274 }() 275 err = MakeChartCompatible(filepath.Join("testdata", "testaddon"), true) 276 assert.NoError(t, err) 277 isChartDir, err := chartutil.IsChartDir(filepath.Join("testdata", "testaddon")) 278 assert.NoError(t, err) 279 assert.Equal(t, isChartDir, true) 280 281 // Already a chart dir 282 err = MakeChartCompatible(filepath.Join("testdata", "testaddon"), false) 283 assert.NoError(t, err) 284 isChartDir, err = chartutil.IsChartDir(filepath.Join("testdata", "testaddon")) 285 assert.NoError(t, err) 286 assert.Equal(t, isChartDir, true) 287 } 288 289 func TestCheckObjectBindingComponent(t *testing.T) { 290 existingBindingDef := unstructured.Unstructured{} 291 existingBindingDef.SetAnnotations(map[string]string{oam.AnnotationAddonDefinitionBondCompKey: "kustomize"}) 292 293 emptyAnnoDef := unstructured.Unstructured{} 294 emptyAnnoDef.SetAnnotations(map[string]string{"test": "onlyForTest"}) 295 296 legacyAnnoDef := unstructured.Unstructured{} 297 legacyAnnoDef.SetAnnotations(map[string]string{oam.AnnotationIgnoreWithoutCompKey: "kustomize"}) 298 testCases := map[string]struct { 299 object unstructured.Unstructured 300 app v1beta1.Application 301 res bool 302 }{ 303 "bindingExist": {object: existingBindingDef, 304 app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{Name: "kustomize"}}}}, 305 res: true}, 306 "NotExisting": {object: existingBindingDef, 307 app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{Name: "helm"}}}}, 308 res: false}, 309 "NoBidingAnnotation": {object: emptyAnnoDef, 310 app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{Name: "kustomize"}}}}, 311 res: true}, 312 "EmptyApp": {object: existingBindingDef, 313 app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{}}}, 314 res: false}, 315 "LegacyApp": {object: legacyAnnoDef, 316 app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{Name: "kustomize"}}}}, 317 res: true, 318 }, 319 "LegacyAppWithoutComp": {object: legacyAnnoDef, 320 app: v1beta1.Application{Spec: v1beta1.ApplicationSpec{Components: []common.ApplicationComponent{{}}}}, 321 res: false, 322 }, 323 } 324 for _, s := range testCases { 325 result := checkBondComponentExist(s.object, s.app) 326 assert.Equal(t, result, s.res) 327 } 328 } 329 330 func TestFilterDependencyRegistries(t *testing.T) { 331 testCases := []struct { 332 registries []Registry 333 index int 334 res []Registry 335 origin []Registry 336 }{ 337 { 338 registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 339 index: 0, 340 res: []Registry{{Name: "r2"}, {Name: "r3"}}, 341 origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 342 }, 343 { 344 registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 345 index: 1, 346 res: []Registry{{Name: "r1"}, {Name: "r3"}}, 347 origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 348 }, 349 { 350 registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 351 index: 2, 352 res: []Registry{{Name: "r1"}, {Name: "r2"}}, 353 origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 354 }, 355 { 356 registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 357 index: 3, 358 res: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 359 origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 360 }, 361 { 362 registries: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 363 index: -1, 364 res: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 365 origin: []Registry{{Name: "r1"}, {Name: "r2"}, {Name: "r3"}}, 366 }, 367 { 368 registries: []Registry{}, 369 index: 0, 370 res: []Registry{}, 371 origin: []Registry{}, 372 }, 373 } 374 for _, testCase := range testCases { 375 res := FilterDependencyRegistries(testCase.index, testCase.registries) 376 assert.Equal(t, res, testCase.res) 377 assert.Equal(t, testCase.registries, testCase.origin) 378 } 379 } 380 381 func TestCheckAddonPackageValid(t *testing.T) { 382 testCases := []struct { 383 testCase Meta 384 err error 385 }{{ 386 testCase: Meta{}, 387 err: fmt.Errorf("the addon package doesn't have `metadata.yaml`"), 388 }, { 389 testCase: Meta{Version: "v1.4.0"}, 390 err: fmt.Errorf("`matadata.yaml` must define the name of addon"), 391 }, { 392 testCase: Meta{Name: "test-addon"}, 393 err: fmt.Errorf("`matadata.yaml` must define the version of addon"), 394 }, { 395 testCase: Meta{Name: "test-addon", Version: "1.4.5"}, 396 err: nil, 397 }, 398 } 399 for _, testCase := range testCases { 400 err := validateAddonPackage(&InstallPackage{Meta: testCase.testCase}) 401 assert.Equal(t, reflect.DeepEqual(err, testCase.err), true) 402 } 403 } 404 405 const ( 406 compDefYaml = ` 407 apiVersion: core.oam.dev/v1beta1 408 kind: ComponentDefinition 409 metadata: 410 name: my-comp 411 namespace: vela-system 412 ` 413 traitDefYaml = ` 414 apiVersion: core.oam.dev/v1beta1 415 kind: TraitDefinition 416 metadata: 417 name: my-trait 418 namespace: vela-system 419 ` 420 wfStepDefYaml = ` 421 apiVersion: core.oam.dev/v1beta1 422 kind: WorkflowStepDefinition 423 metadata: 424 name: my-wfstep 425 namespace: vela-system 426 ` 427 ) 428 429 const ( 430 addonAppYaml = ` 431 apiVersion: core.oam.dev/v1beta1 432 kind: Application 433 metadata: 434 labels: 435 addons.oam.dev/name: myaddon 436 addons.oam.dev/registry: KubeVela 437 annotations: 438 addon.oam.dev/componentDefinitions: "my-comp" 439 addon.oam.dev/traitDefinitions: "my-trait" 440 addon.oam.dev/workflowStepDefinitions: "my-wfstep" 441 addon.oam.dev/policyDefinitions: "my-policy" 442 name: addon-myaddon 443 namespace: vela-system 444 spec: 445 ` 446 testApp1Yaml = ` 447 apiVersion: core.oam.dev/v1beta1 448 kind: Application 449 metadata: 450 labels: 451 name: app-1 452 namespace: default 453 spec: 454 components: 455 - name: comp1 456 type: my-comp 457 traits: 458 - type: my-trait 459 ` 460 testApp2Yaml = ` 461 apiVersion: core.oam.dev/v1beta1 462 kind: Application 463 metadata: 464 labels: 465 name: app-2 466 namespace: default 467 spec: 468 components: 469 - name: comp2 470 type: webservice 471 traits: 472 - type: my-trait 473 ` 474 testApp3Yaml = ` 475 apiVersion: core.oam.dev/v1beta1 476 kind: Application 477 metadata: 478 name: app-3 479 namespace: test-ns 480 spec: 481 components: 482 - name: podinfo 483 type: webservice 484 485 workflow: 486 steps: 487 - type: my-wfstep 488 name: deploy 489 ` 490 testApp4Yaml = ` 491 apiVersion: core.oam.dev/v1beta1 492 kind: Application 493 metadata: 494 name: app-4 495 namespace: test-ns 496 spec: 497 components: 498 - name: podinfo 499 type: webservice 500 501 policies: 502 - type: my-policy 503 name: topology 504 ` 505 506 registryCmYaml = ` 507 apiVersion: v1 508 data: 509 registries: '{ "KubeVela":{ "name": "KubeVela", "oss": { "end_point": "TEST_SERVER_URL", 510 "bucket": "", "path": "" } } }' 511 kind: ConfigMap 512 metadata: 513 name: vela-addon-registry 514 namespace: vela-system 515 ` 516 addonDisableTestAppYaml = ` 517 apiVersion: core.oam.dev/v1beta1 518 kind: Application 519 metadata: 520 name: addon-test-disable-addon 521 namespace: vela-system 522 labels: 523 addons.oam.dev/name: test-disable-addon 524 addons.oam.dev/registry: KubeVela 525 spec: 526 components: 527 - name: podinfo 528 type: webservice 529 ` 530 )