...

Source file src/sigs.k8s.io/cli-utils/pkg/object/mutation/annotation_test.go

Documentation: sigs.k8s.io/cli-utils/pkg/object/mutation

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  //
     4  
     5  package mutation
     6  
     7  import (
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    12  	ktestutil "sigs.k8s.io/cli-utils/pkg/kstatus/polling/testutil"
    13  )
    14  
    15  var configmap1y = `
    16  apiVersion: v1
    17  kind: ConfigMap
    18  metadata:
    19    name: map1-name
    20    namespace: map-namespace
    21    annotations:
    22      config.kubernetes.io/apply-time-mutation: |
    23        - sourcePath: $.status.number
    24          sourceRef:
    25            group: resourcemanager.cnrm.cloud.google.com
    26            kind: Project
    27            name: example-name
    28            namespace: example-namespace
    29          targetPath: $.spec.member
    30          token: ${project-number}
    31  data: {}
    32  `
    33  
    34  var m1 = ApplyTimeMutation{
    35  	{
    36  		SourceRef: ResourceReference{
    37  			Group:     "resourcemanager.cnrm.cloud.google.com",
    38  			Kind:      "Project",
    39  			Name:      "example-name",
    40  			Namespace: "example-namespace",
    41  		},
    42  		SourcePath: "$.status.number",
    43  		TargetPath: "$.spec.member",
    44  		Token:      "${project-number}",
    45  	},
    46  }
    47  
    48  var configmap2y = `
    49  apiVersion: v1
    50  kind: ConfigMap
    51  metadata:
    52    name: map1-name
    53    namespace: map-namespace
    54    annotations:
    55      config.kubernetes.io/apply-time-mutation: |
    56        - sourcePath: .status.field
    57          sourceRef:
    58            kind: ConfigMap
    59            name: example-name
    60          targetPath: .spec.field
    61  data: {}
    62  `
    63  
    64  var m2 = ApplyTimeMutation{
    65  	{
    66  		SourceRef: ResourceReference{
    67  			Group: "",
    68  			Kind:  "ConfigMap",
    69  			Name:  "example-name",
    70  		},
    71  		SourcePath: ".status.field",
    72  		TargetPath: ".spec.field",
    73  	},
    74  }
    75  
    76  // inline json, no spaces or linebreaks
    77  var u1j = &unstructured.Unstructured{
    78  	Object: map[string]interface{}{
    79  		"apiVersion": "v1",
    80  		"kind":       "ConfigMap",
    81  		"metadata": map[string]interface{}{
    82  			"name":      "unused",
    83  			"namespace": "unused",
    84  			"annotations": map[string]interface{}{
    85  				Annotation: `[` +
    86  					`{` +
    87  					`"sourceRef":{` +
    88  					`"group":"resourcemanager.cnrm.cloud.google.com",` +
    89  					`"kind":"Project",` +
    90  					`"name":"example-name",` +
    91  					`"namespace":"example-namespace"` +
    92  					`},` +
    93  					`"sourcePath": "$.status.number",` +
    94  					`"targetPath": "$.spec.member",` +
    95  					`"token": "${project-number}"` +
    96  					`},` +
    97  					`]`,
    98  			},
    99  		},
   100  	},
   101  }
   102  
   103  // yaml w/ multiple subs
   104  var configmap3y = `
   105  apiVersion: v1
   106  kind: ConfigMap
   107  metadata:
   108    name: map1-name
   109    namespace: map-namespace
   110    annotations:
   111      config.kubernetes.io/apply-time-mutation: |
   112        - sourcePath: $.status.number
   113          sourceRef:
   114            group: resourcemanager.cnrm.cloud.google.com
   115            kind: Project
   116            name: example-name
   117            namespace: example-namespace
   118          targetPath: $.spec.member
   119          token: ${project-number}
   120        - sourcePath: .status.field
   121          sourceRef:
   122            kind: ConfigMap
   123            name: example-name
   124          targetPath: .spec.field
   125  data: {}
   126  `
   127  
   128  // json w/ multiple subs
   129  var configmap4y = `
   130  apiVersion: v1
   131  kind: ConfigMap
   132  metadata:
   133    name: map1-name
   134    namespace: map-namespace
   135    annotations:
   136      config.kubernetes.io/apply-time-mutation: |
   137        [
   138          {
   139            "sourceRef": {
   140              "group": "resourcemanager.cnrm.cloud.google.com",
   141              "kind": "Project",
   142              "name": "example-name",
   143              "namespace": "example-namespace"
   144            },
   145            "sourcePath": "$.status.number",
   146            "targetPath": "$.spec.member",
   147            "token": "${project-number}"
   148          },
   149          {
   150            "sourceRef": {
   151              "kind": "ConfigMap",
   152              "name": "example-name"
   153            },
   154            "sourcePath": ".status.field",
   155            "targetPath": ".spec.field"
   156          }
   157        ]
   158  data: {}
   159  `
   160  
   161  var m3 = ApplyTimeMutation{
   162  	{
   163  		SourceRef: ResourceReference{
   164  			Group:     "resourcemanager.cnrm.cloud.google.com",
   165  			Kind:      "Project",
   166  			Name:      "example-name",
   167  			Namespace: "example-namespace",
   168  		},
   169  		SourcePath: "$.status.number",
   170  		TargetPath: "$.spec.member",
   171  		Token:      "${project-number}",
   172  	},
   173  	{
   174  		SourceRef: ResourceReference{
   175  			Group: "",
   176  			Kind:  "ConfigMap",
   177  			Name:  "example-name",
   178  		},
   179  		SourcePath: ".status.field",
   180  		TargetPath: ".spec.field",
   181  	},
   182  }
   183  
   184  var noAnnotationsYAML = `
   185  apiVersion: v1
   186  kind: ConfigMap
   187  metadata:
   188    name: map1-name
   189    namespace: map-namespace
   190  data: {}
   191  `
   192  
   193  var invalidAnnotationsYAML = `
   194  apiVersion: v1
   195  kind: ConfigMap
   196  metadata:
   197    name: map1-name
   198    namespace: map-namespace
   199    annotations:
   200      config.kubernetes.io/apply-time-mutation: this string is not a substitution
   201  data: {}
   202  `
   203  
   204  func TestReadAnnotation(t *testing.T) {
   205  	configmap1 := ktestutil.YamlToUnstructured(t, configmap1y)
   206  	configmap2 := ktestutil.YamlToUnstructured(t, configmap2y)
   207  	configmap3 := ktestutil.YamlToUnstructured(t, configmap3y)
   208  	configmap4 := ktestutil.YamlToUnstructured(t, configmap4y)
   209  	noAnnotations := ktestutil.YamlToUnstructured(t, noAnnotationsYAML)
   210  	invalidAnnotations := ktestutil.YamlToUnstructured(t, invalidAnnotationsYAML)
   211  
   212  	testCases := map[string]struct {
   213  		obj      *unstructured.Unstructured
   214  		expected ApplyTimeMutation
   215  		isError  bool
   216  	}{
   217  		"nil object is not found": {
   218  			obj:      nil,
   219  			expected: ApplyTimeMutation{},
   220  		},
   221  		"Object with no annotations returns not found": {
   222  			obj:      noAnnotations,
   223  			expected: ApplyTimeMutation{},
   224  		},
   225  		"Unparseable depends on annotation returns not found": {
   226  			obj:      invalidAnnotations,
   227  			expected: ApplyTimeMutation{},
   228  			isError:  true,
   229  		},
   230  		"Namespace-scoped object apply-time-mutation annotation yaml": {
   231  			obj:      configmap1,
   232  			expected: m1,
   233  		},
   234  		"Namespace-scoped object apply-time-mutation annotation json": {
   235  			obj:      u1j,
   236  			expected: m1,
   237  		},
   238  		"Cluster-scoped object apply-time-mutation annotation yaml": {
   239  			obj:      configmap2,
   240  			expected: m2,
   241  		},
   242  		"Multiple objects specified in annotation yaml": {
   243  			obj:      configmap3,
   244  			expected: m3,
   245  		},
   246  		"Multiple objects specified in annotation json": {
   247  			obj:      configmap4,
   248  			expected: m3,
   249  		},
   250  	}
   251  
   252  	for tn, tc := range testCases {
   253  		t.Run(tn, func(t *testing.T) {
   254  			actual, err := ReadAnnotation(tc.obj)
   255  			if tc.isError {
   256  				if err == nil {
   257  					t.Fatalf("expected error not received")
   258  				}
   259  			} else {
   260  				if err != nil {
   261  					t.Fatalf("unexpected error received: %s", err)
   262  				}
   263  				if !actual.Equal(tc.expected) {
   264  					t.Errorf("\nexpected:\t%#v\nreceived:\t%#v", tc.expected, actual)
   265  				}
   266  			}
   267  		})
   268  	}
   269  }
   270  
   271  func TestWriteAnnotation(t *testing.T) {
   272  	configmap1 := ktestutil.YamlToUnstructured(t, configmap1y)
   273  	configmap2 := ktestutil.YamlToUnstructured(t, configmap2y)
   274  	configmap3 := ktestutil.YamlToUnstructured(t, configmap3y)
   275  
   276  	testCases := map[string]struct {
   277  		obj      *unstructured.Unstructured
   278  		mutation ApplyTimeMutation
   279  		expected *string
   280  		isError  bool
   281  	}{
   282  		"nil object": {
   283  			obj:      nil,
   284  			mutation: ApplyTimeMutation{},
   285  			expected: nil,
   286  			isError:  true,
   287  		},
   288  		"empty mutation": {
   289  			obj:      &unstructured.Unstructured{},
   290  			mutation: ApplyTimeMutation{},
   291  			expected: nil,
   292  			isError:  true,
   293  		},
   294  		"Namespace-scoped object": {
   295  			obj:      &unstructured.Unstructured{},
   296  			mutation: m1,
   297  			expected: getApplyTimeMutation(configmap1),
   298  		},
   299  		"Cluster-scoped object": {
   300  			obj:      &unstructured.Unstructured{},
   301  			mutation: m2,
   302  			expected: getApplyTimeMutation(configmap2),
   303  		},
   304  		"Multiple objects": {
   305  			obj:      &unstructured.Unstructured{},
   306  			mutation: m3,
   307  			expected: getApplyTimeMutation(configmap3),
   308  		},
   309  	}
   310  
   311  	for tn, tc := range testCases {
   312  		t.Run(tn, func(t *testing.T) {
   313  			err := WriteAnnotation(tc.obj, tc.mutation)
   314  			if tc.isError {
   315  				if err == nil {
   316  					t.Fatalf("expected error not received")
   317  				}
   318  			} else {
   319  				if err != nil {
   320  					t.Fatalf("unexpected error received: %s", err)
   321  				}
   322  				received := getApplyTimeMutation(tc.obj)
   323  
   324  				if received != tc.expected && (received == nil || tc.expected == nil) {
   325  					t.Errorf("\nexpected:\t%#v\nreceived:\t%#v", tc.expected, received)
   326  				}
   327  
   328  				require.Equal(t, *tc.expected, *received, "unexpected mutation string")
   329  			}
   330  		})
   331  	}
   332  }
   333  
   334  func getApplyTimeMutation(obj *unstructured.Unstructured) *string {
   335  	value, found := obj.GetAnnotations()[Annotation]
   336  	if !found {
   337  		return nil
   338  	}
   339  	return &value
   340  }
   341  

View as plain text