github.com/oam-dev/kubevela@v1.9.11/pkg/component/ref_objects_suite_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 component 18 19 import ( 20 "context" 21 "math/rand" 22 "testing" 23 "time" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 corev1 "k8s.io/api/core/v1" 28 rbacv1 "k8s.io/api/rbac/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 utilfeature "k8s.io/apiserver/pkg/util/feature" 32 "k8s.io/client-go/rest" 33 featuregatetesting "k8s.io/component-base/featuregate/testing" 34 "k8s.io/utils/pointer" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 "sigs.k8s.io/controller-runtime/pkg/envtest" 37 38 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 39 "github.com/oam-dev/kubevela/pkg/features" 40 pkgcommon "github.com/oam-dev/kubevela/pkg/utils/common" 41 ) 42 43 var cfg *rest.Config 44 var k8sClient client.Client 45 var testEnv *envtest.Environment 46 47 func TestUtils(t *testing.T) { 48 RegisterFailHandler(Fail) 49 RunSpecs(t, "Utils Suite") 50 } 51 52 var _ = BeforeSuite(func() { 53 rand.Seed(time.Now().UnixNano()) 54 By("bootstrapping test environment for utils test") 55 56 testEnv = &envtest.Environment{ 57 ControlPlaneStartTimeout: time.Minute * 3, 58 ControlPlaneStopTimeout: time.Minute, 59 UseExistingCluster: pointer.Bool(false), 60 CRDDirectoryPaths: []string{"./testdata"}, 61 } 62 63 By("start kube test env") 64 var err error 65 cfg, err = testEnv.Start() 66 Expect(err).ShouldNot(HaveOccurred()) 67 Expect(cfg).ToNot(BeNil()) 68 69 By("new kube client") 70 cfg.Timeout = time.Minute * 2 71 k8sClient, err = client.New(cfg, client.Options{Scheme: pkgcommon.Scheme}) 72 Expect(err).Should(Succeed()) 73 }) 74 75 var _ = AfterSuite(func() { 76 By("tearing down the test environment") 77 err := testEnv.Stop() 78 Expect(err).ToNot(HaveOccurred()) 79 }) 80 81 var _ = Describe("Test ref-objects functions", func() { 82 It("Test SelectRefObjectsForDispatch", func() { 83 defer featuregatetesting.SetFeatureGateDuringTest(&testing.T{}, utilfeature.DefaultFeatureGate, features.LegacyObjectTypeIdentifier, true)() 84 defer featuregatetesting.SetFeatureGateDuringTest(&testing.T{}, utilfeature.DefaultFeatureGate, features.DeprecatedObjectLabelSelector, true)() 85 By("Create objects") 86 Expect(k8sClient.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test"}})).Should(Succeed()) 87 for _, obj := range []client.Object{&corev1.ConfigMap{ 88 ObjectMeta: metav1.ObjectMeta{ 89 Name: "dynamic", 90 Namespace: "test", 91 }, 92 }, &corev1.Service{ 93 ObjectMeta: metav1.ObjectMeta{ 94 Name: "dynamic", 95 Namespace: "test", 96 Generation: int64(5), 97 }, 98 Spec: corev1.ServiceSpec{ 99 ClusterIP: "10.0.0.254", 100 Ports: []corev1.ServicePort{{Port: 80}}, 101 }, 102 }, &corev1.ConfigMap{ 103 ObjectMeta: metav1.ObjectMeta{ 104 Name: "by-label-1", 105 Namespace: "test", 106 Labels: map[string]string{"key": "value"}, 107 }, 108 }, &corev1.ConfigMap{ 109 ObjectMeta: metav1.ObjectMeta{ 110 Name: "by-label-2", 111 Namespace: "test", 112 Labels: map[string]string{"key": "value"}, 113 }, 114 }, &rbacv1.ClusterRole{ 115 ObjectMeta: metav1.ObjectMeta{ 116 Name: "test-cluster-role", 117 }, 118 }} { 119 Expect(k8sClient.Create(context.Background(), obj)).Should(Succeed()) 120 } 121 createUnstructured := func(apiVersion string, kind string, name string, namespace string, labels map[string]interface{}) *unstructured.Unstructured { 122 un := &unstructured.Unstructured{ 123 Object: map[string]interface{}{ 124 "apiVersion": apiVersion, 125 "kind": kind, 126 "metadata": map[string]interface{}{"name": name}, 127 }, 128 } 129 if namespace != "" { 130 un.SetNamespace(namespace) 131 } 132 if labels != nil { 133 un.Object["metadata"].(map[string]interface{})["labels"] = labels 134 } 135 return un 136 } 137 testcases := map[string]struct { 138 Input v1alpha1.ObjectReferrer 139 compName string 140 appNs string 141 Output []*unstructured.Unstructured 142 Error string 143 Scope string 144 IsService bool 145 IsClusterRole bool 146 }{ 147 "normal": { 148 Input: v1alpha1.ObjectReferrer{ 149 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 150 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic"}, 151 }, 152 appNs: "test", 153 Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)}, 154 }, 155 "legacy-type-identifier": { 156 Input: v1alpha1.ObjectReferrer{ 157 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{LegacyObjectTypeIdentifier: v1alpha1.LegacyObjectTypeIdentifier{Kind: "ConfigMap", APIVersion: "v1"}}, 158 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic"}, 159 }, 160 appNs: "test", 161 Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)}, 162 }, 163 "invalid-apiVersion": { 164 Input: v1alpha1.ObjectReferrer{ 165 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{LegacyObjectTypeIdentifier: v1alpha1.LegacyObjectTypeIdentifier{Kind: "ConfigMap", APIVersion: "a/b/v1"}}, 166 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic"}, 167 }, 168 appNs: "test", 169 Error: "invalid APIVersion", 170 }, 171 "invalid-type-identifier": { 172 Input: v1alpha1.ObjectReferrer{ 173 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{}, 174 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic"}, 175 }, 176 appNs: "test", 177 Error: "neither resource or apiVersion/kind is set", 178 }, 179 "name-and-selector-both-set": { 180 Input: v1alpha1.ObjectReferrer{ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic", LabelSelector: map[string]string{"key": "value"}}}, 181 appNs: "test", 182 Error: "invalid object selector for ref-objects, name and labelSelector cannot be both set", 183 }, 184 "empty-ref-object-name": { 185 Input: v1alpha1.ObjectReferrer{ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}}, 186 compName: "dynamic", 187 appNs: "test", 188 Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)}, 189 }, 190 "cannot-find-ref-object": { 191 Input: v1alpha1.ObjectReferrer{ 192 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 193 ObjectSelector: v1alpha1.ObjectSelector{Name: "static"}, 194 }, 195 appNs: "test", 196 Error: "failed to load ref object", 197 }, 198 "modify-service": { 199 Input: v1alpha1.ObjectReferrer{ 200 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "service"}, 201 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic"}, 202 }, 203 appNs: "test", 204 IsService: true, 205 }, 206 "by-labels": { 207 Input: v1alpha1.ObjectReferrer{ 208 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 209 ObjectSelector: v1alpha1.ObjectSelector{LabelSelector: map[string]string{"key": "value"}}, 210 }, 211 appNs: "test", 212 Output: []*unstructured.Unstructured{ 213 createUnstructured("v1", "ConfigMap", "by-label-1", "test", map[string]interface{}{"key": "value"}), 214 createUnstructured("v1", "ConfigMap", "by-label-2", "test", map[string]interface{}{"key": "value"}), 215 }, 216 }, 217 "by-deprecated-labels": { 218 Input: v1alpha1.ObjectReferrer{ 219 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 220 ObjectSelector: v1alpha1.ObjectSelector{DeprecatedLabelSelector: map[string]string{"key": "value"}}, 221 }, 222 appNs: "test", 223 Output: []*unstructured.Unstructured{ 224 createUnstructured("v1", "ConfigMap", "by-label-1", "test", map[string]interface{}{"key": "value"}), 225 createUnstructured("v1", "ConfigMap", "by-label-2", "test", map[string]interface{}{"key": "value"}), 226 }, 227 }, 228 "no-kind-for-resource": { 229 Input: v1alpha1.ObjectReferrer{ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "unknown"}}, 230 appNs: "test", 231 Error: "no matches", 232 }, 233 "cross-namespace": { 234 Input: v1alpha1.ObjectReferrer{ 235 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 236 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic", Namespace: "test"}, 237 }, 238 appNs: "demo", 239 Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)}, 240 }, 241 "cross-namespace-forbidden": { 242 Input: v1alpha1.ObjectReferrer{ 243 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 244 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic", Namespace: "test"}, 245 }, 246 appNs: "demo", 247 Scope: RefObjectsAvailableScopeNamespace, 248 Error: "cannot refer to objects outside the application's namespace", 249 }, 250 "cross-cluster": { 251 Input: v1alpha1.ObjectReferrer{ 252 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 253 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic", Cluster: "demo"}, 254 }, 255 appNs: "test", 256 Output: []*unstructured.Unstructured{createUnstructured("v1", "ConfigMap", "dynamic", "test", nil)}, 257 }, 258 "cross-cluster-forbidden": { 259 Input: v1alpha1.ObjectReferrer{ 260 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "configmap"}, 261 ObjectSelector: v1alpha1.ObjectSelector{Name: "dynamic", Cluster: "demo"}, 262 }, 263 appNs: "test", 264 Scope: RefObjectsAvailableScopeCluster, 265 Error: "cannot refer to objects outside control plane", 266 }, 267 "test-cluster-scope-resource": { 268 Input: v1alpha1.ObjectReferrer{ 269 ObjectTypeIdentifier: v1alpha1.ObjectTypeIdentifier{Resource: "clusterrole"}, 270 ObjectSelector: v1alpha1.ObjectSelector{Name: "test-cluster-role"}, 271 }, 272 appNs: "test", 273 Scope: RefObjectsAvailableScopeCluster, 274 Output: []*unstructured.Unstructured{createUnstructured("rbac.authorization.k8s.io/v1", "ClusterRole", "test-cluster-role", "", nil)}, 275 IsClusterRole: true, 276 }, 277 } 278 for name, tt := range testcases { 279 By("Test " + name) 280 if tt.Scope == "" { 281 tt.Scope = RefObjectsAvailableScopeGlobal 282 } 283 RefObjectsAvailableScope = tt.Scope 284 output, err := SelectRefObjectsForDispatch(context.Background(), k8sClient, tt.appNs, tt.compName, tt.Input) 285 if tt.Error != "" { 286 Expect(err).ShouldNot(BeNil()) 287 Expect(err.Error()).Should(ContainSubstring(tt.Error)) 288 } else { 289 Expect(err).Should(Succeed()) 290 if tt.IsService { 291 Expect(output[0].Object["kind"]).Should(Equal("Service")) 292 Expect(output[0].Object["spec"].(map[string]interface{})["clusterIP"]).Should(BeNil()) 293 } else { 294 if tt.IsClusterRole { 295 delete(output[0].Object, "rules") 296 } 297 Expect(output).Should(Equal(tt.Output)) 298 } 299 } 300 } 301 }) 302 303 It("Test AppendUnstructuredObjects", func() { 304 testCases := map[string]struct { 305 Inputs []*unstructured.Unstructured 306 Input *unstructured.Unstructured 307 Outputs []*unstructured.Unstructured 308 }{ 309 "overlap": { 310 Inputs: []*unstructured.Unstructured{{Object: map[string]interface{}{ 311 "apiVersion": "v1", 312 "kind": "ConfigMap", 313 "metadata": map[string]interface{}{"name": "x", "namespace": "default"}, 314 "data": "a", 315 }}, {Object: map[string]interface{}{ 316 "apiVersion": "v1", 317 "kind": "ConfigMap", 318 "metadata": map[string]interface{}{"name": "y", "namespace": "default"}, 319 "data": "b", 320 }}}, 321 Input: &unstructured.Unstructured{Object: map[string]interface{}{ 322 "apiVersion": "v1", 323 "kind": "ConfigMap", 324 "metadata": map[string]interface{}{"name": "y", "namespace": "default"}, 325 "data": "c", 326 }}, 327 Outputs: []*unstructured.Unstructured{{Object: map[string]interface{}{ 328 "apiVersion": "v1", 329 "kind": "ConfigMap", 330 "metadata": map[string]interface{}{"name": "x", "namespace": "default"}, 331 "data": "a", 332 }}, {Object: map[string]interface{}{ 333 "apiVersion": "v1", 334 "kind": "ConfigMap", 335 "metadata": map[string]interface{}{"name": "y", "namespace": "default"}, 336 "data": "c", 337 }}}, 338 }, 339 "append": { 340 Inputs: []*unstructured.Unstructured{{Object: map[string]interface{}{ 341 "apiVersion": "v1", 342 "kind": "ConfigMap", 343 "metadata": map[string]interface{}{"name": "x", "namespace": "default"}, 344 "data": "a", 345 }}, {Object: map[string]interface{}{ 346 "apiVersion": "v1", 347 "kind": "ConfigMap", 348 "metadata": map[string]interface{}{"name": "y", "namespace": "default"}, 349 "data": "b", 350 }}}, 351 Input: &unstructured.Unstructured{Object: map[string]interface{}{ 352 "apiVersion": "v1", 353 "kind": "ConfigMap", 354 "metadata": map[string]interface{}{"name": "z", "namespace": "default"}, 355 "data": "c", 356 }}, 357 Outputs: []*unstructured.Unstructured{{Object: map[string]interface{}{ 358 "apiVersion": "v1", 359 "kind": "ConfigMap", 360 "metadata": map[string]interface{}{"name": "x", "namespace": "default"}, 361 "data": "a", 362 }}, {Object: map[string]interface{}{ 363 "apiVersion": "v1", 364 "kind": "ConfigMap", 365 "metadata": map[string]interface{}{"name": "y", "namespace": "default"}, 366 "data": "b", 367 }}, {Object: map[string]interface{}{ 368 "apiVersion": "v1", 369 "kind": "ConfigMap", 370 "metadata": map[string]interface{}{"name": "z", "namespace": "default"}, 371 "data": "c", 372 }}}, 373 }, 374 } 375 for name, tt := range testCases { 376 By("Test " + name) 377 Expect(AppendUnstructuredObjects(tt.Inputs, tt.Input)).Should(Equal(tt.Outputs)) 378 } 379 }) 380 381 })