github.com/verrazzano/verrazzano@v1.7.0/pkg/k8sutil/apply_yaml_test.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package k8sutil_test
     5  
     6  import (
     7  	"context"
     8  	"github.com/google/go-cmp/cmp"
     9  	"testing"
    10  
    11  	rbacv1 "k8s.io/api/rbac/v1"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    15  	"github.com/verrazzano/verrazzano/tools/vz/pkg/constants"
    16  	"github.com/verrazzano/verrazzano/tools/vz/pkg/helpers"
    17  	appv1 "k8s.io/api/apps/v1"
    18  	corev1 "k8s.io/api/core/v1"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/types"
    21  	k8scheme "k8s.io/client-go/kubernetes/scheme"
    22  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    23  )
    24  
    25  const (
    26  	objects  = "./testdata/objects"
    27  	testdata = "./testdata"
    28  )
    29  
    30  func TestApplyD(t *testing.T) {
    31  	var tests = []struct {
    32  		name    string
    33  		dir     string
    34  		count   int
    35  		isError bool
    36  	}{
    37  		{
    38  			"should apply YAML files",
    39  			objects,
    40  			3,
    41  			false,
    42  		},
    43  		{
    44  			"should fail to apply non-existent directories",
    45  			"blahblah",
    46  			0,
    47  			true,
    48  		},
    49  	}
    50  
    51  	for _, tt := range tests {
    52  		t.Run(tt.name, func(t *testing.T) {
    53  			c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
    54  			y := k8sutil.NewYAMLApplier(c, "")
    55  			err := y.ApplyD(tt.dir)
    56  			if tt.isError {
    57  				assert.Error(t, err)
    58  			} else {
    59  				assert.NoError(t, err)
    60  				assert.Equal(t, tt.count, len(y.Objects()))
    61  			}
    62  		})
    63  	}
    64  }
    65  
    66  func TestApplyF(t *testing.T) {
    67  	var tests = []struct {
    68  		name                                 string
    69  		file                                 string
    70  		count                                int
    71  		isError                              bool
    72  		expectedLastAppliedConfigAnnotations []string
    73  	}{
    74  		{
    75  			"should apply file",
    76  			objects + "/service.yaml",
    77  			1,
    78  			false,
    79  			[]string{"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"my-service\",\"namespace\":\"test\"},\"spec\":{\"ports\":[{\"port\":80,\"protocol\":\"TCP\",\"targetPort\":9376}],\"selector\":{\"app\":\"MyApp\"}}}\n"},
    80  		},
    81  		{
    82  			"should apply file with two objects",
    83  			testdata + "/two_objects.yaml",
    84  			2,
    85  			false,
    86  			[]string{"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"service1\",\"namespace\":\"test\"},\"spec\":{\"ports\":[{\"port\":80,\"protocol\":\"TCP\",\"targetPort\":9376}],\"selector\":{\"app\":\"MyApp\"}}}\n",
    87  				"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"service2\",\"namespace\":\"test\"},\"spec\":{\"ports\":[{\"port\":80,\"protocol\":\"TCP\",\"targetPort\":9376}],\"selector\":{\"app\":\"MyApp\"}}}\n"},
    88  		},
    89  		{
    90  			"should fail to apply files that are not YAML",
    91  			"blahblah",
    92  			0,
    93  			true,
    94  			nil,
    95  		},
    96  		{
    97  			"should fail when file is not YAML",
    98  			objects + "/not-yaml.txt",
    99  			0,
   100  			true,
   101  			nil,
   102  		},
   103  	}
   104  
   105  	for _, tt := range tests {
   106  		t.Run(tt.name, func(t *testing.T) {
   107  			c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
   108  			y := k8sutil.NewYAMLApplier(c, "test")
   109  			err := y.ApplyF(tt.file)
   110  			if tt.isError {
   111  				assert.Error(t, err)
   112  			} else {
   113  				assert.NoError(t, err)
   114  			}
   115  			assert.Equal(t, tt.count, len(y.Objects()))
   116  
   117  			for i, actualObj := range y.Objects() {
   118  				actual := actualObj.GetAnnotations()[corev1.LastAppliedConfigAnnotation]
   119  				expected := tt.expectedLastAppliedConfigAnnotations[i]
   120  				if diff := cmp.Diff(actual, expected); diff != "" {
   121  					t.Errorf("expected %v\n, got %v instead", expected, actual)
   122  					t.Logf("Difference: %s", diff)
   123  				}
   124  			}
   125  		})
   126  	}
   127  }
   128  
   129  // TestApplyFNonSpec
   130  // GIVEN a object that contains top level fields outside of spec
   131  //
   132  //	WHEN I call apply with changes non-spec fields
   133  //	THEN the resulting object contains the updates
   134  func TestApplyFNonSpec(t *testing.T) {
   135  	sa := &corev1.ServiceAccount{
   136  		ObjectMeta: metav1.ObjectMeta{
   137  			Name:      constants.VerrazzanoPlatformOperator,
   138  			Namespace: constants.VerrazzanoInstall,
   139  		},
   140  		Secrets: []corev1.ObjectReference{
   141  			{
   142  				Name: "verrazzano-platform-operator-token",
   143  			},
   144  		},
   145  	}
   146  	c := fake.NewClientBuilder().WithScheme(helpers.NewScheme()).WithObjects(sa).Build()
   147  	y := k8sutil.NewYAMLApplier(c, "")
   148  	err := y.ApplyF(testdata + "/sa_add_imagepullsecrets.yaml")
   149  	assert.NoError(t, err)
   150  
   151  	// Verify the resulting SA
   152  	saUpdated := &corev1.ServiceAccount{}
   153  	err = c.Get(context.TODO(), types.NamespacedName{Name: constants.VerrazzanoPlatformOperator, Namespace: constants.VerrazzanoInstall}, saUpdated)
   154  	assert.NoError(t, err)
   155  
   156  	assert.NotEmpty(t, saUpdated.ImagePullSecrets)
   157  	assert.Equal(t, 1, len(saUpdated.ImagePullSecrets))
   158  	assert.Equal(t, "verrazzano-container-registry", saUpdated.ImagePullSecrets[0].Name)
   159  
   160  	assert.Empty(t, saUpdated.Secrets)
   161  	assert.Equal(t, 0, len(saUpdated.Secrets))
   162  }
   163  
   164  // TestApplyFMerge
   165  // GIVEN a object that contains spec field
   166  //
   167  //	WHEN I call apply with additions to the spec field
   168  //	THEN the resulting object contains the merged updates
   169  func TestApplyFMerge(t *testing.T) {
   170  	deadlineSeconds := int32(5)
   171  	deployment := &appv1.Deployment{
   172  		ObjectMeta: metav1.ObjectMeta{
   173  			Name:      constants.VerrazzanoPlatformOperator,
   174  			Namespace: constants.VerrazzanoInstall,
   175  		},
   176  		Spec: appv1.DeploymentSpec{
   177  			MinReadySeconds:         5,
   178  			ProgressDeadlineSeconds: &deadlineSeconds,
   179  		},
   180  	}
   181  	c := fake.NewClientBuilder().WithScheme(helpers.NewScheme()).WithObjects(deployment).Build()
   182  	y := k8sutil.NewYAMLApplier(c, "")
   183  	err := y.ApplyF(testdata + "/deployment_merge.yaml")
   184  	assert.NoError(t, err)
   185  
   186  	// Verify the resulting Deployment
   187  	depUpdated := &appv1.Deployment{}
   188  	err = c.Get(context.TODO(), types.NamespacedName{Name: constants.VerrazzanoPlatformOperator, Namespace: constants.VerrazzanoInstall}, depUpdated)
   189  	assert.NoError(t, err)
   190  
   191  	assert.Equal(t, int32(5), depUpdated.Spec.MinReadySeconds)
   192  	assert.Equal(t, int32(5), *depUpdated.Spec.Replicas)
   193  	assert.Equal(t, int32(10), *depUpdated.Spec.ProgressDeadlineSeconds)
   194  }
   195  
   196  // TestApplyFClusterRole
   197  // GIVEN a ClusterRole object
   198  //
   199  //	WHEN I call apply with additions
   200  //	THEN the resulting object contains the merged updates
   201  func TestApplyFClusterRole(t *testing.T) {
   202  	deadlineSeconds := int32(5)
   203  	deployment := &appv1.Deployment{
   204  		ObjectMeta: metav1.ObjectMeta{
   205  			Name:      constants.VerrazzanoPlatformOperator,
   206  			Namespace: constants.VerrazzanoInstall,
   207  		},
   208  		Spec: appv1.DeploymentSpec{
   209  			MinReadySeconds:         5,
   210  			ProgressDeadlineSeconds: &deadlineSeconds,
   211  		},
   212  	}
   213  	c := fake.NewClientBuilder().WithScheme(helpers.NewScheme()).WithObjects(deployment).Build()
   214  	y := k8sutil.NewYAMLApplier(c, "")
   215  	err := y.ApplyF(testdata + "/clusterrole_create.yaml")
   216  	assert.NoError(t, err)
   217  
   218  	// Verify the ClusterRole that was created
   219  	clusterRole := &rbacv1.ClusterRole{}
   220  	err = c.Get(context.TODO(), types.NamespacedName{Name: "verrazzano-managed-cluster"}, clusterRole)
   221  	assert.NoError(t, err)
   222  
   223  	assert.Equal(t, 1, len(clusterRole.Rules))
   224  	rule := clusterRole.Rules[0]
   225  	assert.Equal(t, "", rule.APIGroups[0])
   226  	assert.Equal(t, "secrets", rule.Resources[0])
   227  	assert.Equal(t, 3, len(rule.Verbs))
   228  
   229  	// Update the ClusterRole
   230  	err = y.ApplyF(testdata + "/clusterrole_update.yaml")
   231  	assert.NoError(t, err)
   232  
   233  	// Verify the ClusterRole that was updated
   234  	clusterRoleUpdated := &rbacv1.ClusterRole{}
   235  	err = c.Get(context.TODO(), types.NamespacedName{Name: "verrazzano-managed-cluster"}, clusterRoleUpdated)
   236  	assert.NoError(t, err)
   237  	rule = clusterRoleUpdated.Rules[0]
   238  	assert.Equal(t, 4, len(rule.Verbs))
   239  
   240  	// Verify all the expected verbs are there
   241  	foundCount := 0
   242  	for _, verb := range rule.Verbs {
   243  		switch verb {
   244  		case "get":
   245  			foundCount++
   246  		case "list":
   247  			foundCount++
   248  		case "watch":
   249  			foundCount++
   250  		case "update":
   251  			foundCount++
   252  		}
   253  	}
   254  	assert.Equal(t, 4, foundCount)
   255  }
   256  
   257  func TestApplyFT(t *testing.T) {
   258  	var tests = []struct {
   259  		name                                 string
   260  		file                                 string
   261  		args                                 map[string]interface{}
   262  		count                                int
   263  		isError                              bool
   264  		expectedLastAppliedConfigAnnotations []string
   265  	}{
   266  		{
   267  			"should apply a template file",
   268  			testdata + "/templated_service.yaml",
   269  			map[string]interface{}{"namespace": "default"},
   270  			1,
   271  			false,
   272  			[]string{"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"tmpl-service\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"port\":80,\"protocol\":\"TCP\",\"targetPort\":9376}],\"selector\":{\"app\":\"MyApp\"}}}\n"},
   273  		},
   274  		{
   275  			"should fail to apply when template is incomplete",
   276  			testdata + "/templated_service.yaml",
   277  			map[string]interface{}{},
   278  			0,
   279  			true,
   280  			nil,
   281  		},
   282  	}
   283  
   284  	for _, tt := range tests {
   285  		t.Run(tt.name, func(t *testing.T) {
   286  			c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
   287  			y := k8sutil.NewYAMLApplier(c, "")
   288  			err := y.ApplyFT(tt.file, tt.args)
   289  			if tt.isError {
   290  				assert.Error(t, err)
   291  			} else {
   292  				assert.NoError(t, err)
   293  			}
   294  			assert.Equal(t, tt.count, len(y.Objects()))
   295  
   296  			for i, actualObj := range y.Objects() {
   297  				actual := actualObj.GetAnnotations()[corev1.LastAppliedConfigAnnotation]
   298  				assert.NotEmpty(t, actual)
   299  				expected := tt.expectedLastAppliedConfigAnnotations[i]
   300  				if diff := cmp.Diff(actual, expected); diff != "" {
   301  					t.Errorf("expected %v\n, got %v instead", expected, actual)
   302  					t.Logf("Difference: %s", diff)
   303  				}
   304  			}
   305  		})
   306  	}
   307  }
   308  
   309  // TestApplyDT tests the ApplyDT function.
   310  func TestApplyDT(t *testing.T) {
   311  	var tests = []struct {
   312  		name    string
   313  		dir     string
   314  		args    map[string]interface{}
   315  		count   int
   316  		isError bool
   317  	}{
   318  		// GIVEN a directory of template YAML files
   319  		// WHEN the ApplyDT function is called with substitution key/value pairs
   320  		// THEN the call succeeds and the resources are applied to the cluster
   321  		{
   322  			"should apply all template files in directory",
   323  			testdata,
   324  			map[string]interface{}{"namespace": "default"},
   325  			7,
   326  			false,
   327  		},
   328  		// GIVEN a directory of template YAML files
   329  		// WHEN the ApplyDT function is called with no substitution key/value pairs
   330  		// THEN the call fails
   331  		{
   332  			"should fail to apply when one or more templates are incomplete",
   333  			testdata,
   334  			map[string]interface{}{},
   335  			4,
   336  			true,
   337  		},
   338  	}
   339  
   340  	for _, tt := range tests {
   341  		t.Run(tt.name, func(t *testing.T) {
   342  			c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
   343  			y := k8sutil.NewYAMLApplier(c, "")
   344  			err := y.ApplyDT(tt.dir, tt.args)
   345  			if tt.isError {
   346  				assert.Error(t, err)
   347  			} else {
   348  				assert.NoError(t, err)
   349  			}
   350  			assert.Equal(t, tt.count, len(y.Objects()))
   351  		})
   352  	}
   353  }
   354  
   355  func TestDeleteF(t *testing.T) {
   356  	var tests = []struct {
   357  		name    string
   358  		file    string
   359  		isError bool
   360  	}{
   361  		{
   362  			"should delete valid file",
   363  			testdata + "/two_objects.yaml",
   364  			false,
   365  		},
   366  		{
   367  			"should fail to delete invalid file",
   368  			"blahblah",
   369  			true,
   370  		},
   371  	}
   372  
   373  	for _, tt := range tests {
   374  		t.Run(tt.name, func(t *testing.T) {
   375  			c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
   376  			y := k8sutil.NewYAMLApplier(c, "")
   377  			err := y.DeleteF(tt.file)
   378  			if tt.isError {
   379  				assert.Error(t, err)
   380  			} else {
   381  				assert.NoError(t, err)
   382  			}
   383  		})
   384  	}
   385  }
   386  
   387  func TestDeleteFD(t *testing.T) {
   388  	var tests = []struct {
   389  		name    string
   390  		file    string
   391  		args    map[string]interface{}
   392  		isError bool
   393  	}{
   394  		{
   395  			"should apply a template file",
   396  			testdata + "/templated_service.yaml",
   397  			map[string]interface{}{"namespace": "default"},
   398  			false,
   399  		},
   400  		{
   401  			"should fail to apply when template is incomplete",
   402  			testdata + "/templated_service.yaml",
   403  			map[string]interface{}{},
   404  			true,
   405  		},
   406  	}
   407  
   408  	for _, tt := range tests {
   409  		t.Run(tt.name, func(t *testing.T) {
   410  			c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
   411  			y := k8sutil.NewYAMLApplier(c, "")
   412  			err := y.DeleteFT(tt.file, tt.args)
   413  			if tt.isError {
   414  				assert.Error(t, err)
   415  			} else {
   416  				assert.NoError(t, err)
   417  			}
   418  		})
   419  	}
   420  }
   421  
   422  // TestDeleteFTDefaultConfig tests deleteFT with rest client from the default config
   423  // GIVEN a filepath and args
   424  //
   425  //	WHEN TestDeleteFTDefaultConfig is called
   426  //	THEN it fails to get the default restclient
   427  //func TestDeleteFTDefaultConfig(t *testing.T) {
   428  //	var tests = []struct {
   429  //		name    string
   430  //		file    string
   431  //		args    map[string]interface{}
   432  //		isError bool
   433  //	}{
   434  //		{
   435  //			"should fail to delete a template file",
   436  //			testdata + "/templated_service.yaml",
   437  //			map[string]interface{}{"namespace": "default"},
   438  //			true,
   439  //		},
   440  //	}
   441  //
   442  //	for _, tt := range tests {
   443  //		t.Run(tt.name, func(t *testing.T) {
   444  //			c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
   445  //			y := k8sutil.NewYAMLApplier(c, "")
   446  //			err := y.DeleteFTDefaultConfig(tt.file, tt.args)
   447  //			if tt.isError {
   448  //				assert.Error(t, err)
   449  //			} else {
   450  //				assert.NoError(t, err)
   451  //			}
   452  //		})
   453  //	}
   454  //}
   455  
   456  func TestDeleteAll(t *testing.T) {
   457  	c := fake.NewClientBuilder().WithScheme(k8scheme.Scheme).Build()
   458  	y := k8sutil.NewYAMLApplier(c, "")
   459  	err := y.ApplyD(objects)
   460  	assert.NoError(t, err)
   461  	assert.Equal(t, 3, len(y.Objects()))
   462  	err = y.DeleteAll()
   463  	assert.NoError(t, err)
   464  	assert.Equal(t, 0, len(y.Objects()))
   465  }