...

Source file src/k8s.io/client-go/util/csaupgrade/upgrade_test.go

Documentation: k8s.io/client-go/util/csaupgrade

     1  /*
     2  Copyright 2022 The Kubernetes 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 csaupgrade_test
    18  
    19  import (
    20  	"encoding/json"
    21  	"reflect"
    22  	"testing"
    23  
    24  	jsonpatch "github.com/evanphx/json-patch"
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/stretchr/testify/require"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  	"k8s.io/apimachinery/pkg/util/yaml"
    31  	"k8s.io/client-go/util/csaupgrade"
    32  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    33  )
    34  
    35  func TestFindOwners(t *testing.T) {
    36  	testCases := []struct {
    37  		Name              string
    38  		ManagedFieldsYAML string
    39  		Operation         metav1.ManagedFieldsOperationType
    40  		Fields            *fieldpath.Set
    41  		Expectation       []string
    42  	}{
    43  		{
    44  			// Field a root field path owner
    45  			Name: "Basic",
    46  			ManagedFieldsYAML: `
    47        managedFields:
    48        - apiVersion: v1
    49          fieldsType: FieldsV1
    50          fieldsV1:
    51            f:data:
    52              .: {}
    53              f:key: {}
    54              f:legacy: {}
    55            f:metadata:
    56              f:annotations:
    57                .: {}
    58                f:kubectl.kubernetes.io/last-applied-configuration: {}
    59          manager: kubectl-client-side-apply
    60          operation: Update
    61          time: "2022-08-22T23:08:23Z"
    62        `,
    63  			Operation:   metav1.ManagedFieldsOperationUpdate,
    64  			Fields:      fieldpath.NewSet(fieldpath.MakePathOrDie("data")),
    65  			Expectation: []string{"kubectl-client-side-apply"},
    66  		},
    67  		{
    68  			// Find a fieldpath nested inside another field
    69  			Name: "Nested",
    70  			ManagedFieldsYAML: `
    71        managedFields:
    72        - apiVersion: v1
    73          fieldsType: FieldsV1
    74          fieldsV1:
    75            f:data:
    76              .: {}
    77              f:key: {}
    78              f:legacy: {}
    79            f:metadata:
    80              f:annotations:
    81                .: {}
    82                f:kubectl.kubernetes.io/last-applied-configuration: {}
    83          manager: kubectl-client-side-apply
    84          operation: Update
    85          time: "2022-08-22T23:08:23Z"
    86        `,
    87  			Operation:   metav1.ManagedFieldsOperationUpdate,
    88  			Fields:      fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")),
    89  			Expectation: []string{"kubectl-client-side-apply"},
    90  		},
    91  		{
    92  			// Search for an operaiton/fieldpath combination that is not found on both
    93  			// axes
    94  			Name: "NotFound",
    95  			ManagedFieldsYAML: `
    96        managedFields:
    97        - apiVersion: v1
    98          fieldsType: FieldsV1
    99          fieldsV1:
   100            f:data:
   101              .: {}
   102              f:key: {}
   103              f:legacy: {}
   104            f:metadata:
   105              f:annotations:
   106                .: {}
   107                f:kubectl.kubernetes.io/last-applied-configuration: {}
   108          manager: kubectl
   109          operation: Apply
   110          time: "2022-08-23T23:08:23Z"
   111        `,
   112  			Operation:   metav1.ManagedFieldsOperationUpdate,
   113  			Fields:      fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")),
   114  			Expectation: []string{},
   115  		},
   116  		{
   117  			// Test using apply operation
   118  			Name: "ApplyOperation",
   119  			ManagedFieldsYAML: `
   120        managedFields:
   121        - apiVersion: v1
   122          fieldsType: FieldsV1
   123          fieldsV1:
   124            f:data:
   125              .: {}
   126              f:key: {}
   127              f:legacy: {}
   128            f:metadata:
   129              f:annotations:
   130                .: {}
   131                f:kubectl.kubernetes.io/last-applied-configuration: {}
   132          manager: kubectl
   133          operation: Apply
   134          time: "2022-08-23T23:08:23Z"
   135        `,
   136  			Operation:   metav1.ManagedFieldsOperationApply,
   137  			Fields:      fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")),
   138  			Expectation: []string{"kubectl"},
   139  		},
   140  		{
   141  			// Of multiple field managers, match a single one
   142  			Name: "OneOfMultiple",
   143  			ManagedFieldsYAML: `
   144        managedFields:
   145        - apiVersion: v1
   146          fieldsType: FieldsV1
   147          fieldsV1:
   148            f:metadata:
   149              f:annotations:
   150                .: {}
   151                f:kubectl.kubernetes.io/last-applied-configuration: {}
   152          manager: kubectl-client-side-apply
   153          operation: Update
   154          time: "2022-08-23T23:08:23Z"
   155        - apiVersion: v1
   156          fieldsType: FieldsV1
   157          fieldsV1:
   158            f:data:
   159              .: {}
   160              f:key: {}
   161              f:legacy: {}
   162          manager: kubectl
   163          operation: Apply
   164          time: "2022-08-23T23:08:23Z"
   165        `,
   166  			Operation:   metav1.ManagedFieldsOperationUpdate,
   167  			Fields:      fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")),
   168  			Expectation: []string{"kubectl-client-side-apply"},
   169  		},
   170  		{
   171  			// have multiple field managers, and match more than one but not all of them
   172  			Name: "ManyOfMultiple",
   173  			ManagedFieldsYAML: `
   174        managedFields:
   175        - apiVersion: v1
   176          fieldsType: FieldsV1
   177          fieldsV1:
   178            f:metadata:
   179              f:annotations:
   180                .: {}
   181                f:kubectl.kubernetes.io/last-applied-configuration: {}
   182          manager: kubectl-client-side-apply
   183          operation: Update
   184          time: "2022-08-23T23:08:23Z"
   185        - apiVersion: v1
   186          fieldsType: FieldsV1
   187          fieldsV1:
   188            f:metadata:
   189              f:annotations:
   190                .: {}
   191                f:kubectl.kubernetes.io/last-applied-configuration: {}
   192            f:data:
   193              .: {}
   194              f:key: {}
   195              f:legacy: {}
   196          manager: kubectl
   197          operation: Apply
   198          time: "2022-08-23T23:08:23Z"
   199        - apiVersion: v1
   200          fieldsType: FieldsV1
   201          fieldsV1:
   202            f:metadata:
   203              f:annotations:
   204                .: {}
   205                f:kubectl.kubernetes.io/last-applied-configuration: {}
   206          manager: kubectl-client-side-apply2
   207          operation: Update
   208          time: "2022-08-23T23:08:23Z"
   209        `,
   210  			Operation:   metav1.ManagedFieldsOperationUpdate,
   211  			Fields:      fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration")),
   212  			Expectation: []string{"kubectl-client-side-apply", "kubectl-client-side-apply2"},
   213  		},
   214  		{
   215  			// Test with multiple fields to match against
   216  			Name: "BasicMultipleFields",
   217  			ManagedFieldsYAML: `
   218        managedFields:
   219        - apiVersion: v1
   220          fieldsType: FieldsV1
   221          fieldsV1:
   222            f:metadata:
   223              f:annotations:
   224                .: {}
   225                f:kubectl.kubernetes.io/last-applied-configuration: {}
   226            f:data:
   227              .: {}
   228              f:key: {}
   229              f:legacy: {}
   230          manager: kubectl-client-side-apply
   231          operation: Update
   232          time: "2022-08-23T23:08:23Z"
   233        `,
   234  			Operation: metav1.ManagedFieldsOperationUpdate,
   235  			Fields: fieldpath.NewSet(
   236  				fieldpath.MakePathOrDie("data", "key"),
   237  				fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration"),
   238  			),
   239  			Expectation: []string{"kubectl-client-side-apply"},
   240  		},
   241  		{
   242  			// Test with multiplle fields but the manager is missing one of the fields
   243  			// requested so it does not match
   244  			Name: "MissingOneField",
   245  			ManagedFieldsYAML: `
   246        managedFields:
   247        - apiVersion: v1
   248          fieldsType: FieldsV1
   249          fieldsV1:
   250            f:metadata:
   251              f:annotations:
   252                .: {}
   253                f:kubectl.kubernetes.io/last-applied-configuration: {}
   254            f:data:
   255              .: {}
   256              f:legacy: {}
   257          manager: kubectl-client-side-apply
   258          operation: Update
   259          time: "2022-08-23T23:08:23Z"
   260        `,
   261  			Operation: metav1.ManagedFieldsOperationUpdate,
   262  			Fields: fieldpath.NewSet(
   263  				fieldpath.MakePathOrDie("data", "key"),
   264  				fieldpath.MakePathOrDie("metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration"),
   265  			),
   266  			Expectation: []string{},
   267  		},
   268  	}
   269  	for _, tcase := range testCases {
   270  		t.Run(tcase.Name, func(t *testing.T) {
   271  			var entries struct {
   272  				ManagedFields []metav1.ManagedFieldsEntry `json:"managedFields"`
   273  			}
   274  			err := yaml.Unmarshal([]byte(tcase.ManagedFieldsYAML), &entries)
   275  			require.NoError(t, err)
   276  
   277  			result := csaupgrade.FindFieldsOwners(entries.ManagedFields, tcase.Operation, tcase.Fields)
   278  
   279  			// Compare owner names since they uniquely identify the selected entries
   280  			// (given that the operation is provided)
   281  			ownerNames := []string{}
   282  			for _, entry := range result {
   283  				ownerNames = append(ownerNames, entry.Manager)
   284  				require.Equal(t, tcase.Operation, entry.Operation)
   285  			}
   286  			require.ElementsMatch(t, tcase.Expectation, ownerNames)
   287  		})
   288  	}
   289  }
   290  
   291  func TestUpgradeCSA(t *testing.T) {
   292  
   293  	cases := []struct {
   294  		Name           string
   295  		CSAManagers    []string
   296  		SSAManager     string
   297  		Options        []csaupgrade.Option
   298  		OriginalObject []byte
   299  		ExpectedObject []byte
   300  	}{
   301  		{
   302  			// Case where there is a CSA entry with the given name, but no SSA entry
   303  			// is found. Expect that the CSA entry is converted to an SSA entry
   304  			// and renamed.
   305  			Name:        "csa-basic-direct-conversion",
   306  			CSAManagers: []string{"kubectl-client-side-apply"},
   307  			SSAManager:  "kubectl",
   308  			OriginalObject: []byte(`
   309  apiVersion: v1
   310  data: {}
   311  kind: ConfigMap
   312  metadata:
   313    resourceVersion: "1"
   314    annotations:
   315      kubectl.kubernetes.io/last-applied-configuration: |
   316        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   317    creationTimestamp: "2022-08-22T23:08:23Z"
   318    managedFields:
   319    - apiVersion: v1
   320      fieldsType: FieldsV1
   321      fieldsV1:
   322        f:data:
   323          .: {}
   324          f:key: {}
   325          f:legacy: {}
   326        f:metadata:
   327          f:annotations:
   328            .: {}
   329            f:kubectl.kubernetes.io/last-applied-configuration: {}
   330      manager: kubectl-client-side-apply
   331      operation: Update
   332      time: "2022-08-22T23:08:23Z"
   333    name: test
   334    namespace: default
   335  `),
   336  			ExpectedObject: []byte(`
   337  apiVersion: v1
   338  data: {}
   339  kind: ConfigMap
   340  metadata:
   341    resourceVersion: "1"
   342    annotations:
   343      kubectl.kubernetes.io/last-applied-configuration: |
   344        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   345    creationTimestamp: "2022-08-22T23:08:23Z"
   346    managedFields:
   347    - apiVersion: v1
   348      fieldsType: FieldsV1
   349      fieldsV1:
   350        f:data:
   351          .: {}
   352          f:key: {}
   353          f:legacy: {}
   354        f:metadata:
   355          f:annotations:
   356            .: {}
   357            f:kubectl.kubernetes.io/last-applied-configuration: {}
   358      manager: kubectl
   359      operation: Apply
   360      time: "2022-08-22T23:08:23Z"
   361    name: test
   362    namespace: default
   363  `),
   364  		},
   365  		{
   366  			// This is the case when kubectl --server-side is used for the first time
   367  			// Server creates duplicate managed fields entry - one for Update and another
   368  			// for Apply. Expect entries to be merged into one entry, which is unchanged
   369  			// from initial SSA.
   370  			Name:        "csa-combine-with-ssa-duplicate-keys",
   371  			CSAManagers: []string{"kubectl-client-side-apply"},
   372  			SSAManager:  "kubectl",
   373  			OriginalObject: []byte(`
   374  apiVersion: v1
   375  data: {}
   376  kind: ConfigMap
   377  metadata:
   378    resourceVersion: "1"
   379    annotations:
   380      kubectl.kubernetes.io/last-applied-configuration: |
   381        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   382    creationTimestamp: "2022-08-22T23:08:23Z"
   383    managedFields:
   384    - apiVersion: v1
   385      fieldsType: FieldsV1
   386      fieldsV1:
   387        f:data:
   388          .: {}
   389          f:key: {}
   390          f:legacy: {}
   391        f:metadata:
   392          f:annotations:
   393            .: {}
   394            f:kubectl.kubernetes.io/last-applied-configuration: {}
   395      manager: kubectl
   396      operation: Apply
   397      time: "2022-08-23T23:08:23Z"
   398    - apiVersion: v1
   399      fieldsType: FieldsV1
   400      fieldsV1:
   401        f:data:
   402          .: {}
   403          f:key: {}
   404          f:legacy: {}
   405        f:metadata:
   406          f:annotations:
   407            .: {}
   408            f:kubectl.kubernetes.io/last-applied-configuration: {}
   409      manager: kubectl-client-side-apply
   410      operation: Update
   411      time: "2022-08-22T23:08:23Z"
   412    name: test
   413    namespace: default
   414  `),
   415  			ExpectedObject: []byte(`
   416  apiVersion: v1
   417  data: {}
   418  kind: ConfigMap
   419  metadata:
   420    resourceVersion: "1"
   421    annotations:
   422      kubectl.kubernetes.io/last-applied-configuration: |
   423        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   424    creationTimestamp: "2022-08-22T23:08:23Z"
   425    managedFields:
   426    - apiVersion: v1
   427      fieldsType: FieldsV1
   428      fieldsV1:
   429        f:data:
   430          .: {}
   431          f:key: {}
   432          f:legacy: {}
   433        f:metadata:
   434          f:annotations:
   435            .: {}
   436            f:kubectl.kubernetes.io/last-applied-configuration: {}
   437      manager: kubectl
   438      operation: Apply
   439      time: "2022-08-23T23:08:23Z"
   440    name: test
   441    namespace: default
   442  `),
   443  		},
   444  		{
   445  			// This is the case when kubectl --server-side is used for the first time,
   446  			// but then a key is removed. A bug would take place where key is left in
   447  			// CSA entry but no longer present in SSA entry, so it would not be pruned.
   448  			// This shows that upgrading such an object results in correct behavior next
   449  			// time SSA applier
   450  			// Expect final object to have unioned keys from both entries
   451  			Name:        "csa-combine-with-ssa-additional-keys",
   452  			CSAManagers: []string{"kubectl-client-side-apply"},
   453  			SSAManager:  "kubectl",
   454  			OriginalObject: []byte(`
   455  apiVersion: v1
   456  data: {}
   457  kind: ConfigMap
   458  metadata:
   459    resourceVersion: "1"
   460    annotations:
   461      kubectl.kubernetes.io/last-applied-configuration: |
   462        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   463    creationTimestamp: "2022-08-22T23:08:23Z"
   464    managedFields:
   465    - apiVersion: v1
   466      fieldsType: FieldsV1
   467      fieldsV1:
   468        f:data:
   469          .: {}
   470          f:key: {}
   471        f:metadata:
   472          f:annotations:
   473            .: {}
   474            f:kubectl.kubernetes.io/last-applied-configuration: {}
   475      manager: kubectl
   476      operation: Apply
   477      time: "2022-08-23T23:08:23Z"
   478    - apiVersion: v1
   479      fieldsType: FieldsV1
   480      fieldsV1:
   481        f:data:
   482          .: {}
   483          f:key: {}
   484          f:legacy: {}
   485        f:metadata:
   486          f:annotations:
   487            .: {}
   488            f:kubectl.kubernetes.io/last-applied-configuration: {}
   489      manager: kubectl-client-side-apply
   490      operation: Update
   491      time: "2022-08-22T23:08:23Z"
   492    name: test
   493    namespace: default
   494  `),
   495  			ExpectedObject: []byte(`
   496  apiVersion: v1
   497  data: {}
   498  kind: ConfigMap
   499  metadata:
   500    resourceVersion: "1"
   501    annotations:
   502      kubectl.kubernetes.io/last-applied-configuration: |
   503        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   504    creationTimestamp: "2022-08-22T23:08:23Z"
   505    managedFields:
   506    - apiVersion: v1
   507      fieldsType: FieldsV1
   508      fieldsV1:
   509        f:data:
   510          .: {}
   511          f:key: {}
   512          f:legacy: {}
   513        f:metadata:
   514          f:annotations:
   515            .: {}
   516            f:kubectl.kubernetes.io/last-applied-configuration: {}
   517      manager: kubectl
   518      operation: Apply
   519      time: "2022-08-23T23:08:23Z"
   520    name: test
   521    namespace: default
   522  `),
   523  		},
   524  		{
   525  			// Case when there are multiple CSA versions on the object which do not
   526  			// match the version from the apply entry. Shows they are tossed away
   527  			// without being merged.
   528  			Name:        "csa-no-applicable-version",
   529  			CSAManagers: []string{"kubectl-client-side-apply"},
   530  			SSAManager:  "kubectl",
   531  			OriginalObject: []byte(`
   532  apiVersion: v1
   533  data: {}
   534  kind: ConfigMap
   535  metadata:
   536    resourceVersion: "1"
   537    annotations:
   538      kubectl.kubernetes.io/last-applied-configuration: |
   539        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   540    creationTimestamp: "2022-08-22T23:08:23Z"
   541    managedFields:
   542    - apiVersion: v5
   543      fieldsType: FieldsV1
   544      fieldsV1:
   545        f:data:
   546          .: {}
   547          f:key: {}
   548          f:legacy: {}
   549        f:metadata:
   550          f:annotations:
   551            .: {}
   552            f:kubectl.kubernetes.io/last-applied-configuration: {}
   553      manager: kubectl
   554      operation: Apply
   555      time: "2022-08-23T23:08:23Z"
   556    - apiVersion: v1
   557      fieldsType: FieldsV1
   558      fieldsV1:
   559        f:data:
   560          f:key2: {}
   561        f:metadata:
   562          f:annotations:
   563            f:hello2: {}
   564      manager: kubectl-client-side-apply
   565      operation: Update
   566      time: "2022-08-22T23:08:23Z"
   567    - apiVersion: v2
   568      fieldsType: FieldsV1
   569      fieldsV1:
   570        f:data:
   571          f:key3: {}
   572        f:metadata:
   573          f:annotations:
   574            f:hello3: {}
   575      manager: kubectl-client-side-apply
   576      operation: Update
   577      time: "2022-08-22T23:08:23Z"
   578    - apiVersion: v3
   579      fieldsType: FieldsV1
   580      fieldsV1:
   581        f:data:
   582          f:key4: {}
   583        f:metadata:
   584          f:annotations:
   585            f:hello3: {}
   586      manager: kubectl-client-side-apply
   587      operation: Update
   588      time: "2022-08-22T23:08:23Z"
   589    - apiVersion: v4
   590      fieldsType: FieldsV1
   591      fieldsV1:
   592        f:data:
   593          f:key5: {}
   594        f:metadata:
   595          f:annotations:
   596            f:hello4: {}
   597      manager: kubectl-client-side-apply
   598      operation: Update
   599      time: "2022-08-22T23:08:23Z"
   600    name: test
   601    namespace: default
   602  `),
   603  			ExpectedObject: []byte(`
   604  apiVersion: v1
   605  data: {}
   606  kind: ConfigMap
   607  metadata:
   608    resourceVersion: "1"
   609    annotations:
   610      kubectl.kubernetes.io/last-applied-configuration: |
   611        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   612    creationTimestamp: "2022-08-22T23:08:23Z"
   613    managedFields:
   614    - apiVersion: v5
   615      fieldsType: FieldsV1
   616      fieldsV1:
   617        f:data:
   618          .: {}
   619          f:key: {}
   620          f:legacy: {}
   621        f:metadata:
   622          f:annotations:
   623            .: {}
   624            f:kubectl.kubernetes.io/last-applied-configuration: {}
   625      manager: kubectl
   626      operation: Apply
   627      time: "2022-08-23T23:08:23Z"
   628    name: test
   629    namespace: default
   630  `),
   631  		},
   632  		{
   633  			// Case when there are multiple CSA versions on the object which do not
   634  			// match the version from the apply entry, and one which does.
   635  			// Shows that CSA entry with matching version is unioned into the SSA entry.
   636  			Name:        "csa-single-applicable-version",
   637  			CSAManagers: []string{"kubectl-client-side-apply"},
   638  			SSAManager:  "kubectl",
   639  			OriginalObject: []byte(`
   640  apiVersion: v1
   641  data: {}
   642  kind: ConfigMap
   643  metadata:
   644    resourceVersion: "1"
   645    annotations:
   646      kubectl.kubernetes.io/last-applied-configuration: |
   647        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   648    creationTimestamp: "2022-08-22T23:08:23Z"
   649    managedFields:
   650    - apiVersion: v5
   651      fieldsType: FieldsV1
   652      fieldsV1:
   653        f:data:
   654          .: {}
   655          f:key: {}
   656          f:legacy: {}
   657        f:metadata:
   658          f:annotations:
   659            .: {}
   660            f:kubectl.kubernetes.io/last-applied-configuration: {}
   661      manager: kubectl
   662      operation: Apply
   663      time: "2022-08-23T23:08:23Z"
   664    - apiVersion: v5
   665      fieldsType: FieldsV1
   666      fieldsV1:
   667        f:data:
   668          f:key2: {}
   669        f:metadata:
   670          f:annotations:
   671            f:hello2: {}
   672      manager: kubectl-client-side-apply
   673      operation: Update
   674      time: "2022-08-22T23:08:23Z"
   675    - apiVersion: v2
   676      fieldsType: FieldsV1
   677      fieldsV1:
   678        f:data:
   679          f:key3: {}
   680        f:metadata:
   681          f:annotations:
   682            f:hello3: {}
   683      manager: kubectl-client-side-apply
   684      operation: Update
   685      time: "2022-08-22T23:08:23Z"
   686    - apiVersion: v3
   687      fieldsType: FieldsV1
   688      fieldsV1:
   689        f:data:
   690          f:key4: {}
   691        f:metadata:
   692          f:annotations:
   693            f:hello4: {}
   694      manager: kubectl-client-side-apply
   695      operation: Update
   696      time: "2022-08-22T23:08:23Z"
   697    - apiVersion: v4
   698      fieldsType: FieldsV1
   699      fieldsV1:
   700        f:data:
   701          f:key5: {}
   702        f:metadata:
   703          f:annotations:
   704            f:hello5: {}
   705      manager: kubectl-client-side-apply
   706      operation: Update
   707      time: "2022-08-22T23:08:23Z"
   708    name: test
   709    namespace: default
   710  `),
   711  			ExpectedObject: []byte(`
   712  apiVersion: v1
   713  data: {}
   714  kind: ConfigMap
   715  metadata:
   716    resourceVersion: "1"
   717    annotations:
   718      kubectl.kubernetes.io/last-applied-configuration: |
   719        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   720    creationTimestamp: "2022-08-22T23:08:23Z"
   721    managedFields:
   722    - apiVersion: v5
   723      fieldsType: FieldsV1
   724      fieldsV1:
   725        f:data:
   726          .: {}
   727          f:key: {}
   728          f:key2: {}
   729          f:legacy: {}
   730        f:metadata:
   731          f:annotations:
   732            .: {}
   733            f:hello2: {}
   734            f:kubectl.kubernetes.io/last-applied-configuration: {}
   735      manager: kubectl
   736      operation: Apply
   737      time: "2022-08-23T23:08:23Z"
   738    name: test
   739    namespace: default
   740  `),
   741  		},
   742  		{
   743  			// Do nothing to object with nothing to migrate and no existing SSA manager
   744  			Name:        "noop",
   745  			CSAManagers: []string{"kubectl-client-side-apply"},
   746  			SSAManager:  "not-already-in-object",
   747  			OriginalObject: []byte(`
   748  apiVersion: v1
   749  data: {}
   750  kind: ConfigMap
   751  metadata:
   752    resourceVersion: "1"
   753    annotations:
   754      kubectl.kubernetes.io/last-applied-configuration: |
   755        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   756    creationTimestamp: "2022-08-22T23:08:23Z"
   757    managedFields:
   758    - apiVersion: v5
   759      fieldsType: FieldsV1
   760      fieldsV1:
   761        f:data:
   762          .: {}
   763          f:key: {}
   764          f:legacy: {}
   765        f:metadata:
   766          f:annotations:
   767            .: {}
   768            f:kubectl.kubernetes.io/last-applied-configuration: {}
   769      manager: kubectl
   770      operation: Apply
   771      time: "2022-08-23T23:08:23Z"
   772    name: test
   773    namespace: default
   774  `),
   775  			ExpectedObject: []byte(`
   776  apiVersion: v1
   777  data: {}
   778  kind: ConfigMap
   779  metadata:
   780    resourceVersion: "1"
   781    annotations:
   782      kubectl.kubernetes.io/last-applied-configuration: |
   783        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   784    creationTimestamp: "2022-08-22T23:08:23Z"
   785    managedFields:
   786    - apiVersion: v5
   787      fieldsType: FieldsV1
   788      fieldsV1:
   789        f:data:
   790          .: {}
   791          f:key: {}
   792          f:legacy: {}
   793        f:metadata:
   794          f:annotations:
   795            .: {}
   796            f:kubectl.kubernetes.io/last-applied-configuration: {}
   797      manager: kubectl
   798      operation: Apply
   799      time: "2022-08-23T23:08:23Z"
   800    name: test
   801    namespace: default
   802  `),
   803  		},
   804  		{
   805  			// Expect multiple targets to be merged into existing ssa manager
   806  			Name:        "multipleTargetsExisting",
   807  			CSAManagers: []string{"kube-scheduler", "kubectl-client-side-apply"},
   808  			SSAManager:  "kubectl",
   809  			OriginalObject: []byte(`
   810  apiVersion: v1
   811  data: {}
   812  kind: ConfigMap
   813  metadata:
   814    resourceVersion: "1"
   815    annotations:
   816      kubectl.kubernetes.io/last-applied-configuration: |
   817        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   818    creationTimestamp: "2022-08-22T23:08:23Z"
   819    managedFields:
   820    - apiVersion: v1
   821      fieldsType: FieldsV1
   822      fieldsV1:
   823        f:metadata:
   824          f:labels:
   825            f:name: {}
   826        f:spec:
   827          f:containers:
   828            k:{"name":"kubernetes-pause"}:
   829              .: {}
   830              f:image: {}
   831              f:name: {}
   832      manager: kubectl
   833      operation: Apply
   834    - apiVersion: v1
   835      fieldsType: FieldsV1
   836      fieldsV1:
   837        f:status:
   838          f:conditions:
   839            .: {}
   840            k:{"type":"PodScheduled"}:
   841              .: {}
   842              f:lastProbeTime: {}
   843              f:lastTransitionTime: {}
   844              f:message: {}
   845              f:reason: {}
   846              f:status: {}
   847              f:type: {}
   848      manager: kube-scheduler
   849      operation: Update
   850      time: "2022-11-03T23:22:40Z"
   851    - apiVersion: v1
   852      fieldsType: FieldsV1
   853      fieldsV1:
   854        f:metadata:
   855          f:annotations:
   856            .: {}
   857            f:kubectl.kubernetes.io/last-applied-configuration: {}
   858          f:labels:
   859            .: {}
   860            f:name: {}
   861        f:spec:
   862          f:containers:
   863            k:{"name":"kubernetes-pause"}:
   864              .: {}
   865              f:image: {}
   866              f:imagePullPolicy: {}
   867              f:name: {}
   868              f:resources: {}
   869              f:terminationMessagePath: {}
   870              f:terminationMessagePolicy: {}
   871          f:dnsPolicy: {}
   872          f:enableServiceLinks: {}
   873          f:restartPolicy: {}
   874          f:schedulerName: {}
   875          f:securityContext: {}
   876          f:terminationGracePeriodSeconds: {}
   877      manager: kubectl-client-side-apply
   878      operation: Update
   879      time: "2022-11-03T23:22:40Z"
   880    name: test
   881    namespace: default
   882  `),
   883  			ExpectedObject: []byte(`
   884  apiVersion: v1
   885  data: {}
   886  kind: ConfigMap
   887  metadata:
   888    resourceVersion: "1"
   889    annotations:
   890      kubectl.kubernetes.io/last-applied-configuration: |
   891        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   892    creationTimestamp: "2022-08-22T23:08:23Z"
   893    managedFields:
   894    - apiVersion: v1
   895      fieldsType: FieldsV1
   896      fieldsV1:
   897        f:status:
   898          f:conditions:
   899            .: {}
   900            k:{"type":"PodScheduled"}:
   901              .: {}
   902              f:lastProbeTime: {}
   903              f:lastTransitionTime: {}
   904              f:message: {}
   905              f:reason: {}
   906              f:status: {}
   907              f:type: {}
   908        f:metadata:
   909          f:annotations:
   910            .: {}
   911            f:kubectl.kubernetes.io/last-applied-configuration: {}
   912          f:labels:
   913            .: {}
   914            f:name: {}
   915        f:spec:
   916          f:containers:
   917            k:{"name":"kubernetes-pause"}:
   918              .: {}
   919              f:image: {}
   920              f:imagePullPolicy: {}
   921              f:name: {}
   922              f:resources: {}
   923              f:terminationMessagePath: {}
   924              f:terminationMessagePolicy: {}
   925          f:dnsPolicy: {}
   926          f:enableServiceLinks: {}
   927          f:restartPolicy: {}
   928          f:schedulerName: {}
   929          f:securityContext: {}
   930          f:terminationGracePeriodSeconds: {}
   931      manager: kubectl
   932      operation: Apply
   933    name: test
   934    namespace: default
   935  `),
   936  		},
   937  		{
   938  			// Expect multiple targets to be merged into a new ssa manager
   939  			Name:        "multipleTargetsNewInsertion",
   940  			CSAManagers: []string{"kubectl-client-side-apply", "kube-scheduler"},
   941  			SSAManager:  "newly-inserted-manager",
   942  			OriginalObject: []byte(`
   943  apiVersion: v1
   944  data: {}
   945  kind: ConfigMap
   946  metadata:
   947    resourceVersion: "1"
   948    annotations:
   949      kubectl.kubernetes.io/last-applied-configuration: |
   950        {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
   951    creationTimestamp: "2022-08-22T23:08:23Z"
   952    managedFields:
   953    - apiVersion: v1
   954      fieldsType: FieldsV1
   955      fieldsV1:
   956        f:metadata:
   957          f:labels:
   958            f:name: {}
   959        f:spec:
   960          f:containers:
   961            k:{"name":"kubernetes-pause"}:
   962              .: {}
   963              f:image: {}
   964              f:name: {}
   965      manager: kubectl
   966      operation: Apply
   967    - apiVersion: v1
   968      fieldsType: FieldsV1
   969      fieldsV1:
   970        f:status:
   971          f:conditions:
   972            .: {}
   973            k:{"type":"PodScheduled"}:
   974              .: {}
   975              f:lastProbeTime: {}
   976              f:lastTransitionTime: {}
   977              f:message: {}
   978              f:reason: {}
   979              f:status: {}
   980              f:type: {}
   981      manager: kube-scheduler
   982      operation: Update
   983      time: "2022-11-03T23:22:40Z"
   984    - apiVersion: v1
   985      fieldsType: FieldsV1
   986      fieldsV1:
   987        f:metadata:
   988          f:annotations:
   989            .: {}
   990            f:kubectl.kubernetes.io/last-applied-configuration: {}
   991          f:labels:
   992            .: {}
   993            f:name: {}
   994        f:spec:
   995          f:containers:
   996            k:{"name":"kubernetes-pause"}:
   997              .: {}
   998              f:image: {}
   999              f:imagePullPolicy: {}
  1000              f:name: {}
  1001              f:resources: {}
  1002              f:terminationMessagePath: {}
  1003              f:terminationMessagePolicy: {}
  1004          f:dnsPolicy: {}
  1005          f:enableServiceLinks: {}
  1006          f:restartPolicy: {}
  1007          f:schedulerName: {}
  1008          f:securityContext: {}
  1009          f:terminationGracePeriodSeconds: {}
  1010      manager: kubectl-client-side-apply
  1011      operation: Update
  1012      time: "2022-11-03T23:22:40Z"
  1013    name: test
  1014    namespace: default
  1015  `),
  1016  			ExpectedObject: []byte(`
  1017    apiVersion: v1
  1018    data: {}
  1019    kind: ConfigMap
  1020    metadata:
  1021      resourceVersion: "1"
  1022      annotations:
  1023        kubectl.kubernetes.io/last-applied-configuration: |
  1024          {"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
  1025      creationTimestamp: "2022-08-22T23:08:23Z"
  1026      managedFields:
  1027      - apiVersion: v1
  1028        fieldsType: FieldsV1
  1029        fieldsV1:
  1030          f:metadata:
  1031            f:labels:
  1032              f:name: {}
  1033          f:spec:
  1034            f:containers:
  1035              k:{"name":"kubernetes-pause"}:
  1036                .: {}
  1037                f:image: {}
  1038                f:name: {}
  1039        manager: kubectl
  1040        operation: Apply
  1041      - apiVersion: v1
  1042        fieldsType: FieldsV1
  1043        fieldsV1:
  1044          f:metadata:
  1045            f:annotations:
  1046              .: {}
  1047              f:kubectl.kubernetes.io/last-applied-configuration: {}
  1048            f:labels:
  1049              .: {}
  1050              f:name: {}
  1051          f:spec:
  1052            f:containers:
  1053              k:{"name":"kubernetes-pause"}:
  1054                .: {}
  1055                f:image: {}
  1056                f:imagePullPolicy: {}
  1057                f:name: {}
  1058                f:resources: {}
  1059                f:terminationMessagePath: {}
  1060                f:terminationMessagePolicy: {}
  1061            f:dnsPolicy: {}
  1062            f:enableServiceLinks: {}
  1063            f:restartPolicy: {}
  1064            f:schedulerName: {}
  1065            f:securityContext: {}
  1066            f:terminationGracePeriodSeconds: {}
  1067          f:status:
  1068            f:conditions:
  1069              .: {}
  1070              k:{"type":"PodScheduled"}:
  1071                .: {}
  1072                f:lastProbeTime: {}
  1073                f:lastTransitionTime: {}
  1074                f:message: {}
  1075                f:reason: {}
  1076                f:status: {}
  1077                f:type: {}
  1078        manager: newly-inserted-manager
  1079        operation: Apply
  1080        time: "2022-11-03T23:22:40Z"
  1081      name: test
  1082      namespace: default
  1083  `),
  1084  		},
  1085  		{
  1086  			// Expect multiple targets to be merged into a new ssa manager
  1087  			Name:        "subresource",
  1088  			CSAManagers: []string{"kube-controller-manager"},
  1089  			SSAManager:  "kube-controller-manager",
  1090  			Options:     []csaupgrade.Option{csaupgrade.Subresource("status")},
  1091  			OriginalObject: []byte(`
  1092  apiVersion: v1
  1093  kind: PersistentVolumeClaim
  1094  metadata:
  1095    annotations:
  1096      pv.kubernetes.io/bind-completed: "yes"
  1097      pv.kubernetes.io/bound-by-controller: "yes"
  1098      volume.beta.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com
  1099      volume.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com
  1100    creationTimestamp: "2024-02-24T15:24:31Z"
  1101    finalizers:
  1102    - kubernetes.io/pvc-protection
  1103    managedFields:
  1104    - apiVersion: v1
  1105      fieldsType: FieldsV1
  1106      fieldsV1:
  1107        f:spec:
  1108          f:accessModes: {}
  1109          f:resources:
  1110            f:requests:
  1111              .: {}
  1112              f:storage: {}
  1113          f:storageClassName: {}
  1114          f:volumeMode: {}
  1115      manager: Mozilla
  1116      operation: Update
  1117      time: "2024-02-24T15:24:31Z"
  1118    - apiVersion: v1
  1119      fieldsType: FieldsV1
  1120      fieldsV1:
  1121        f:metadata:
  1122          f:annotations:
  1123            .: {}
  1124            f:pv.kubernetes.io/bind-completed: {}
  1125            f:pv.kubernetes.io/bound-by-controller: {}
  1126            f:volume.beta.kubernetes.io/storage-provisioner: {}
  1127            f:volume.kubernetes.io/storage-provisioner: {}
  1128        f:spec:
  1129          f:volumeName: {}
  1130      manager: kube-controller-manager
  1131      operation: Update
  1132      time: "2024-02-24T15:24:32Z"
  1133    - apiVersion: v1
  1134      fieldsType: FieldsV1
  1135      fieldsV1:
  1136        f:status:
  1137          f:accessModes: {}
  1138          f:capacity:
  1139            .: {}
  1140            f:storage: {}
  1141          f:phase: {}
  1142      manager: kube-controller-manager
  1143      operation: Update
  1144      subresource: status
  1145      time: "2024-02-24T15:24:32Z"
  1146    name: test
  1147    namespace: default
  1148    resourceVersion: "948647140"
  1149    uid: f0692a61-0ffe-4fd5-b00f-0b95f3654fb9
  1150  spec:
  1151    accessModes:
  1152    - ReadWriteOnce
  1153    resources:
  1154      requests:
  1155        storage: 1Gi
  1156    storageClassName: ocs-storagecluster-cephfs
  1157    volumeMode: Filesystem
  1158    volumeName: pvc-f0692a61-0ffe-4fd5-b00f-0b95f3654fb9
  1159  status:
  1160    accessModes:
  1161    - ReadWriteOnce
  1162    capacity:
  1163      storage: 1Gi
  1164    phase: Bound
  1165  `),
  1166  			ExpectedObject: []byte(`
  1167  apiVersion: v1
  1168  kind: PersistentVolumeClaim
  1169  metadata:
  1170    annotations:
  1171      pv.kubernetes.io/bind-completed: "yes"
  1172      pv.kubernetes.io/bound-by-controller: "yes"
  1173      volume.beta.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com
  1174      volume.kubernetes.io/storage-provisioner: openshift-storage.cephfs.csi.ceph.com
  1175    creationTimestamp: "2024-02-24T15:24:31Z"
  1176    finalizers:
  1177    - kubernetes.io/pvc-protection
  1178    managedFields:
  1179    - apiVersion: v1
  1180      fieldsType: FieldsV1
  1181      fieldsV1:
  1182        f:spec:
  1183          f:accessModes: {}
  1184          f:resources:
  1185            f:requests:
  1186              .: {}
  1187              f:storage: {}
  1188          f:storageClassName: {}
  1189          f:volumeMode: {}
  1190      manager: Mozilla
  1191      operation: Update
  1192      time: "2024-02-24T15:24:31Z"
  1193    - apiVersion: v1
  1194      fieldsType: FieldsV1
  1195      fieldsV1:
  1196        f:metadata:
  1197          f:annotations:
  1198            .: {}
  1199            f:pv.kubernetes.io/bind-completed: {}
  1200            f:pv.kubernetes.io/bound-by-controller: {}
  1201            f:volume.beta.kubernetes.io/storage-provisioner: {}
  1202            f:volume.kubernetes.io/storage-provisioner: {}
  1203        f:spec:
  1204          f:volumeName: {}
  1205      manager: kube-controller-manager
  1206      operation: Update
  1207      time: "2024-02-24T15:24:32Z"
  1208    - apiVersion: v1
  1209      fieldsType: FieldsV1
  1210      fieldsV1:
  1211        f:status:
  1212          f:accessModes: {}
  1213          f:capacity:
  1214            .: {}
  1215            f:storage: {}
  1216          f:phase: {}
  1217      manager: kube-controller-manager
  1218      operation: Apply
  1219      subresource: status
  1220      time: "2024-02-24T15:24:32Z"
  1221    name: test
  1222    namespace: default
  1223    resourceVersion: "948647140"
  1224    uid: f0692a61-0ffe-4fd5-b00f-0b95f3654fb9
  1225  spec:
  1226    accessModes:
  1227    - ReadWriteOnce
  1228    resources:
  1229      requests:
  1230        storage: 1Gi
  1231    storageClassName: ocs-storagecluster-cephfs
  1232    volumeMode: Filesystem
  1233    volumeName: pvc-f0692a61-0ffe-4fd5-b00f-0b95f3654fb9
  1234  status:
  1235    accessModes:
  1236    - ReadWriteOnce
  1237    capacity:
  1238      storage: 1Gi
  1239    phase: Bound
  1240  `),
  1241  		},
  1242  	}
  1243  
  1244  	for _, testCase := range cases {
  1245  		t.Run(testCase.Name, func(t *testing.T) {
  1246  			initialObject := unstructured.Unstructured{}
  1247  			err := yaml.Unmarshal(testCase.OriginalObject, &initialObject.Object)
  1248  			if err != nil {
  1249  				t.Fatal(err)
  1250  			}
  1251  
  1252  			upgraded := initialObject.DeepCopy()
  1253  			err = csaupgrade.UpgradeManagedFields(
  1254  				upgraded,
  1255  				sets.New(testCase.CSAManagers...),
  1256  				testCase.SSAManager,
  1257  				testCase.Options...,
  1258  			)
  1259  
  1260  			if err != nil {
  1261  				t.Fatal(err)
  1262  			}
  1263  
  1264  			expectedObject := unstructured.Unstructured{}
  1265  			err = yaml.Unmarshal(testCase.ExpectedObject, &expectedObject.Object)
  1266  			if err != nil {
  1267  				t.Fatal(err)
  1268  			}
  1269  
  1270  			if !reflect.DeepEqual(&expectedObject, upgraded) {
  1271  				t.Fatal(cmp.Diff(&expectedObject, upgraded))
  1272  			}
  1273  
  1274  			// Show that the UpgradeManagedFieldsPatch yields a patch that does
  1275  			// nothing more and nothing less than make the object equal to output
  1276  			// of UpgradeManagedFields
  1277  
  1278  			initialCopy := initialObject.DeepCopyObject()
  1279  			patchBytes, err := csaupgrade.UpgradeManagedFieldsPatch(
  1280  				initialCopy, sets.New(testCase.CSAManagers...), testCase.SSAManager, testCase.Options...)
  1281  
  1282  			if err != nil {
  1283  				t.Fatal(err)
  1284  			} else if patchBytes != nil {
  1285  				patch, err := jsonpatch.DecodePatch(patchBytes)
  1286  				if err != nil {
  1287  					t.Fatal(err)
  1288  				}
  1289  
  1290  				initialJSON, err := json.Marshal(initialObject.Object)
  1291  				if err != nil {
  1292  					t.Fatal(err)
  1293  				}
  1294  
  1295  				patchedBytes, err := patch.Apply(initialJSON)
  1296  				if err != nil {
  1297  					t.Fatal(err)
  1298  				}
  1299  
  1300  				var patched unstructured.Unstructured
  1301  				if err := json.Unmarshal(patchedBytes, &patched.Object); err != nil {
  1302  					t.Fatal(err)
  1303  				}
  1304  
  1305  				if !reflect.DeepEqual(&patched, upgraded) {
  1306  					t.Fatalf("expected patch to produce an upgraded object: %v", cmp.Diff(patched, upgraded))
  1307  				}
  1308  			}
  1309  		})
  1310  	}
  1311  }
  1312  

View as plain text