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  })