...

Source file src/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager_test.go

Documentation: k8s.io/apimachinery/pkg/util/managedfields/internal

     1  /*
     2  Copyright 2020 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 internal_test
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"testing"
    23  
    24  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/util/managedfields/internal"
    30  	"k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest"
    31  	yamlutil "k8s.io/apimachinery/pkg/util/yaml"
    32  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    33  	"sigs.k8s.io/structured-merge-diff/v4/merge"
    34  	"sigs.k8s.io/yaml"
    35  )
    36  
    37  type testArgs struct {
    38  	lastApplied       []byte
    39  	original          []byte
    40  	applied           []byte
    41  	fieldManager      string
    42  	expectConflictSet *fieldpath.Set
    43  }
    44  
    45  // TestApplyUsingLastAppliedAnnotation tests that applying to an object
    46  // created with the client-side apply last-applied annotation
    47  // will not give conflicts
    48  func TestApplyUsingLastAppliedAnnotation(t *testing.T) {
    49  	f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment"))
    50  
    51  	tests := []testArgs{
    52  		{
    53  			fieldManager: "kubectl",
    54  			lastApplied: []byte(`
    55  apiVersion: apps/v1
    56  kind: Deployment
    57  metadata:
    58    name: my-deployment
    59  spec:
    60    replicas: 3
    61    selector:
    62      matchLabels:
    63        app: my-app
    64    template:
    65      metadata:
    66        labels:
    67          app: my-app
    68      spec:
    69        containers:
    70        - name: my-c
    71          image: my-image-v1
    72        - name: my-c2
    73          image: my-image2
    74  `),
    75  			original: []byte(`
    76  apiVersion: apps/v1
    77  kind: Deployment
    78  metadata:
    79    name: my-deployment
    80    labels:
    81      app: my-app # missing from last-applied
    82  spec:
    83    replicas: 100 # does not match last-applied
    84    selector:
    85      matchLabels:
    86        app: my-app
    87    template:
    88      metadata:
    89        labels:
    90          app: my-app
    91      spec:
    92        containers:
    93        - name: my-c
    94          image: my-image-v2 # does no match last-applied
    95        # note that second container in last-applied is missing
    96  `),
    97  			applied: []byte(`
    98  # test conflicts due to fields not allowed by last-applied
    99  
   100  apiVersion: apps/v1
   101  kind: Deployment
   102  metadata:
   103    name: my-deployment
   104    labels:
   105      app: my-new-label # NOT allowed: update label
   106  spec:
   107    replicas: 333 # NOT allowed: update replicas
   108    selector:
   109      matchLabels:
   110        app: my-new-label # allowed: update label
   111    template:
   112      metadata:
   113        labels:
   114          app: my-new-label # allowed: update-label
   115      spec:
   116        containers:
   117        - name: my-c
   118          image: my-image-new # NOT allowed: update image
   119  `),
   120  			expectConflictSet: fieldpath.NewSet(
   121  				fieldpath.MakePathOrDie("metadata", "labels", "app"),
   122  				fieldpath.MakePathOrDie("spec", "replicas"),
   123  				fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"),
   124  			),
   125  		},
   126  		{
   127  			fieldManager: "kubectl",
   128  			lastApplied: []byte(`
   129  apiVersion: apps/v1
   130  kind: Deployment
   131  metadata:
   132    name: my-deployment
   133    labels:
   134      app: my-app
   135  spec:
   136    replicas: 3
   137    selector:
   138      matchLabels:
   139        app: my-app
   140    template:
   141      metadata:
   142        labels:
   143          app: my-app
   144      spec:
   145        containers:
   146        - name: my-c
   147          image: my-image
   148  `),
   149  			original: []byte(`
   150  apiVersion: apps/v1
   151  kind: Deployment
   152  metadata:
   153    name: my-deployment
   154    labels:
   155      app: my-app
   156  spec:
   157    replicas: 100 # does not match last applied
   158    selector:
   159      matchLabels:
   160        app: my-app
   161    template:
   162      metadata:
   163        labels:
   164          app: my-app
   165      spec:
   166        containers:
   167        - name: my-c
   168          image: my-image
   169  `),
   170  			applied: []byte(`
   171  apiVersion: apps/v1
   172  kind: Deployment
   173  metadata:
   174    name: my-deployment
   175    labels:
   176      app: my-new-label
   177  spec:
   178    replicas: 3 # expect conflict
   179    template:
   180      metadata:
   181        labels:
   182          app: my-app
   183      spec:
   184        containers:
   185        - name: my-c
   186          image: my-image
   187  `),
   188  			expectConflictSet: fieldpath.NewSet(
   189  				fieldpath.MakePathOrDie("spec", "replicas"),
   190  			),
   191  		},
   192  		{
   193  			fieldManager: "kubectl",
   194  			original: []byte(`
   195  apiVersion: apps/v1
   196  kind: Deployment
   197  metadata:
   198    name: my-deployment
   199    labels:
   200      app: my-app
   201  spec:
   202    replicas: 100
   203    selector:
   204      matchLabels:
   205        app: my-app
   206    template:
   207      metadata:
   208        labels:
   209          app: my-app
   210      spec:
   211        containers:
   212        - name: my-c
   213          image: my-image
   214  `),
   215  			applied: []byte(`
   216  # applied object matches original
   217  
   218  apiVersion: apps/v1
   219  kind: Deployment
   220  metadata:
   221    name: my-deployment
   222    labels:
   223      app: my-app
   224  spec:
   225    replicas: 100
   226    selector:
   227      matchLabels:
   228        app: my-app
   229    template:
   230      metadata:
   231        labels:
   232          app: my-app
   233      spec:
   234        containers:
   235        - name: my-c
   236          image: my-image
   237  `),
   238  		},
   239  		{
   240  			fieldManager: "kubectl",
   241  			original: []byte(`
   242  apiVersion: apps/v1
   243  kind: Deployment
   244  metadata:
   245    name: my-deployment
   246    labels:
   247      app: my-app
   248  spec:
   249    replicas: 3
   250    selector:
   251      matchLabels:
   252        app: my-app
   253    template:
   254      metadata:
   255        labels:
   256          app: my-app
   257      spec:
   258        containers:
   259        - name: my-c
   260          image: my-image
   261  `),
   262  			applied: []byte(`
   263  # test allowed update with no conflicts
   264  
   265  apiVersion: apps/v1
   266  kind: Deployment
   267  metadata:
   268    name: my-deployment
   269    labels:
   270      app: my-new-label # update label
   271  spec:
   272    replicas: 333 # update replicas
   273    selector:
   274      matchLabels:
   275        app: my-new-label # update label
   276    template:
   277      metadata:
   278        labels:
   279          app: my-new-label # update-label
   280      spec:
   281        containers:
   282        - name: my-c
   283          image: my-image
   284  `),
   285  		},
   286  		{
   287  			fieldManager: "not_kubectl",
   288  			lastApplied: []byte(`
   289  # expect conflicts because field manager is NOT kubectl
   290  
   291  apiVersion: apps/v1
   292  kind: Deployment
   293  metadata:
   294    name: my-deployment
   295    labels:
   296      app: my-app
   297  spec:
   298    replicas: 3
   299    selector:
   300      matchLabels:
   301        app: my-app
   302    template:
   303      metadata:
   304        labels:
   305          app: my-app
   306      spec:
   307        containers:
   308        - name: my-c
   309          image: my-image-v1
   310  `),
   311  			original: []byte(`
   312  apiVersion: apps/v1
   313  kind: Deployment
   314  metadata:
   315    name: my-deployment
   316    labels:
   317      app: my-app
   318  spec:
   319    replicas: 100 # does not match last-applied
   320    selector:
   321      matchLabels:
   322        app: my-app
   323    template:
   324      metadata:
   325        labels:
   326          app: my-app
   327      spec:
   328        containers:
   329        - name: my-c
   330          image: my-image-v2 # does no match last-applied
   331  `),
   332  			applied: []byte(`
   333  # test conflicts due to fields not allowed by last-applied
   334  
   335  apiVersion: apps/v1
   336  kind: Deployment
   337  metadata:
   338    name: my-deployment
   339    labels:
   340      app: my-new-label # update label
   341  spec:
   342    replicas: 333 # update replicas
   343    selector:
   344      matchLabels:
   345        app: my-new-label # update label
   346    template:
   347      metadata:
   348        labels:
   349          app: my-new-label # update-label
   350      spec:
   351        containers:
   352        - name: my-c
   353          image: my-image-new # update image
   354  `),
   355  			expectConflictSet: fieldpath.NewSet(
   356  				fieldpath.MakePathOrDie("metadata", "labels", "app"),
   357  				fieldpath.MakePathOrDie("spec", "replicas"),
   358  				fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic
   359  				fieldpath.MakePathOrDie("spec", "template", "metadata", "labels", "app"),
   360  				fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"),
   361  			),
   362  		},
   363  		{
   364  			fieldManager: "kubectl",
   365  			original: []byte(`
   366  apiVersion: apps/v1
   367  kind: Deployment
   368  metadata:
   369    name: my-deployment
   370    labels:
   371      app: my-app
   372  spec:
   373    replicas: 3
   374    selector:
   375      matchLabels:
   376        app: my-app
   377    template:
   378      metadata:
   379        labels:
   380          app: my-app
   381      spec:
   382        containers:
   383        - name: my-c
   384          image: my-image
   385  `),
   386  			applied: []byte(`
   387  # test allowed update with no conflicts
   388  
   389  apiVersion: apps/v1
   390  kind: Deployment
   391  metadata:
   392    name: my-deployment
   393    labels:
   394      app: my-new-label
   395  spec:
   396    replicas: 3
   397    selector:
   398      matchLabels:
   399        app: my-app
   400    template:
   401      metadata:
   402        labels:
   403          app: my-app
   404      spec:
   405        containers:
   406        - name: my-c
   407          image: my-new-image # update image
   408  `),
   409  		},
   410  		{
   411  			fieldManager: "not_kubectl",
   412  			original: []byte(`
   413  apiVersion: apps/v1
   414  kind: Deployment
   415  metadata:
   416    name: my-deployment
   417    labels:
   418      app: my-app
   419  spec:
   420    replicas: 100
   421    selector:
   422      matchLabels:
   423        app: my-app
   424    template:
   425      metadata:
   426        labels:
   427          app: my-app
   428      spec:
   429        containers:
   430        - name: my-c
   431          image: my-image
   432  `),
   433  			applied: []byte(`
   434  
   435  # expect changes to fail because field manager is not kubectl
   436  
   437  apiVersion: apps/v1
   438  kind: Deployment
   439  metadata:
   440    name: my-deployment
   441    labels:
   442      app: my-new-label # update label
   443  spec:
   444    replicas: 3 # update replicas
   445    selector:
   446      matchLabels:
   447        app: my-app
   448    template:
   449      metadata:
   450        labels:
   451          app: my-app
   452      spec:
   453        containers:
   454        - name: my-c
   455          image: my-new-image # update image
   456  `),
   457  			expectConflictSet: fieldpath.NewSet(
   458  				fieldpath.MakePathOrDie("metadata", "labels", "app"),
   459  				fieldpath.MakePathOrDie("spec", "replicas"),
   460  				fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"),
   461  			),
   462  		},
   463  		{
   464  			fieldManager: "kubectl",
   465  			original: []byte(`
   466  apiVersion: apps/v1
   467  kind: Deployment
   468  metadata:
   469    name: my-deployment
   470  spec:
   471    replicas: 3
   472  `),
   473  			applied: []byte(`
   474  apiVersion: apps/v1
   475  kind: Deployment
   476  metadata:
   477    name: my-deployment
   478  spec:
   479    replicas: 100 # update replicas
   480  `),
   481  		},
   482  		{
   483  			fieldManager: "kubectl",
   484  			lastApplied: []byte(`
   485  apiVersion: extensions/v1beta1
   486  kind: Deployment
   487  metadata:
   488    name: my-deployment
   489  spec:
   490    replicas: 3
   491  `),
   492  			original: []byte(`
   493  apiVersion: apps/v1 # expect conflict due to apiVersion mismatch with last-applied
   494  kind: Deployment
   495  metadata:
   496    name: my-deployment
   497  spec:
   498    replicas: 3
   499  `),
   500  			applied: []byte(`
   501  apiVersion: apps/v1
   502  kind: Deployment
   503  metadata:
   504    name: my-deployment
   505  spec:
   506    replicas: 100 # update replicas
   507  `),
   508  			expectConflictSet: fieldpath.NewSet(
   509  				fieldpath.MakePathOrDie("spec", "replicas"),
   510  			),
   511  		},
   512  		{
   513  			fieldManager: "kubectl",
   514  			lastApplied: []byte(`
   515  apiVerison: foo
   516  kind: bar
   517  spec: expect conflict due to invalid object
   518  `),
   519  			original: []byte(`
   520  apiVersion: apps/v1
   521  kind: Deployment
   522  metadata:
   523    name: my-deployment
   524  spec:
   525    replicas: 3
   526  `),
   527  			applied: []byte(`
   528  apiVersion: apps/v1
   529  kind: Deployment
   530  metadata:
   531    name: my-deployment
   532  spec:
   533    replicas: 100 # update replicas
   534  `),
   535  			expectConflictSet: fieldpath.NewSet(
   536  				fieldpath.MakePathOrDie("spec", "replicas"),
   537  			),
   538  		},
   539  		{
   540  			fieldManager: "kubectl",
   541  			// last-applied is empty
   542  			lastApplied: []byte{},
   543  			original: []byte(`
   544  apiVersion: apps/v1
   545  kind: Deployment
   546  metadata:
   547    name: my-deployment
   548  spec:
   549    replicas: 3
   550  `),
   551  			applied: []byte(`
   552  apiVersion: apps/v1
   553  kind: Deployment
   554  metadata:
   555    name: my-deployment
   556  spec:
   557    replicas: 100 # update replicas
   558  `),
   559  			expectConflictSet: fieldpath.NewSet(
   560  				fieldpath.MakePathOrDie("spec", "replicas"),
   561  			),
   562  		},
   563  	}
   564  
   565  	testConflicts(t, f, tests)
   566  }
   567  
   568  func TestServiceApply(t *testing.T) {
   569  	f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Service"))
   570  
   571  	tests := []testArgs{
   572  		{
   573  			fieldManager: "kubectl",
   574  			original: []byte(`
   575  apiVersion: v1
   576  kind: Service
   577  metadata:
   578    name: test
   579  spec:
   580    ports:
   581    - name: https
   582      port: 443
   583      protocol: TCP
   584      targetPort: 8443
   585    selector:
   586      old: test
   587  `),
   588  			applied: []byte(`
   589  # All accepted while using the same field manager
   590  
   591  apiVersion: v1
   592  kind: Service
   593  metadata:
   594    name: test
   595  spec:
   596    ports:
   597    - name: https
   598      port: 443
   599      protocol: TCP
   600      targetPort: 8444
   601    selector:
   602      new: test
   603  `),
   604  		},
   605  		{
   606  			fieldManager: "kubectl",
   607  			original: []byte(`
   608  apiVersion: v1
   609  kind: Service
   610  metadata:
   611    name: test
   612  spec:
   613    ports:
   614    - name: https
   615      port: 443
   616      protocol: TCP
   617      targetPort: 8443
   618    selector:
   619      old: test
   620  `),
   621  			applied: []byte(`
   622  # Allowed to remove selectors while using the same field manager
   623  
   624  apiVersion: v1
   625  kind: Service
   626  metadata:
   627    name: test
   628  spec:
   629    ports:
   630    - name: https
   631      port: 443
   632      protocol: TCP
   633      targetPort: 8444
   634    selector: {}
   635  `),
   636  		},
   637  		{
   638  			fieldManager: "not_kubectl",
   639  			original: []byte(`
   640  apiVersion: v1
   641  kind: Service
   642  metadata:
   643    name: test
   644  spec:
   645    ports:
   646    - name: https
   647      port: 443
   648      protocol: TCP # TODO: issue - this is a defaulted field, should not be required in a new spec
   649      targetPort: 8443
   650    selector:
   651      old: test
   652  `),
   653  			applied: []byte(`
   654  # test selector update not allowed by last-applied
   655  
   656  apiVersion: v1
   657  kind: Service
   658  metadata:
   659    name: test
   660  spec:
   661    ports:
   662    - name: https
   663      port: 443
   664      protocol: TCP
   665      targetPort: 8444
   666    selector:
   667      new: test
   668  `),
   669  			expectConflictSet: fieldpath.NewSet(
   670  				fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic
   671  				fieldpath.MakePathOrDie("spec", "ports", fieldpath.KeyByFields("port", 443, "protocol", "TCP"), "targetPort"),
   672  			),
   673  		},
   674  	}
   675  
   676  	testConflicts(t, f, tests)
   677  }
   678  
   679  func TestReplicationControllerApply(t *testing.T) {
   680  	f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ReplicationController"))
   681  
   682  	tests := []testArgs{
   683  		{
   684  			fieldManager: "kubectl",
   685  			original: []byte(`
   686  apiVersion: v1
   687  kind: ReplicationController
   688  metadata:
   689    name: test
   690  spec:
   691    replicas: 0
   692    selector:
   693      old: test
   694  `),
   695  			applied: []byte(`
   696  # All accepted while using the same field manager
   697  
   698  apiVersion: v1
   699  kind: ReplicationController
   700  metadata:
   701    name: test
   702  spec:
   703    replicas: 3
   704    selector:
   705      new: test
   706  `),
   707  		},
   708  		{
   709  			fieldManager: "not_kubectl",
   710  			original: []byte(`
   711  apiVersion: v1
   712  kind: ReplicationController
   713  metadata:
   714    name: test
   715  spec:
   716    replicas: 0
   717    selector:
   718      old: test
   719  `),
   720  			applied: []byte(`
   721  # test selector update not allowed by last-applied
   722  
   723  apiVersion: v1
   724  kind: ReplicationController
   725  metadata:
   726    name: test
   727  spec:
   728    replicas: 3
   729    selector:
   730      new: test
   731  `),
   732  			expectConflictSet: fieldpath.NewSet(
   733  				fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic
   734  				fieldpath.MakePathOrDie("spec", "replicas"),
   735  			),
   736  		},
   737  	}
   738  
   739  	testConflicts(t, f, tests)
   740  }
   741  
   742  func TestPodApply(t *testing.T) {
   743  	f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"))
   744  
   745  	tests := []testArgs{
   746  		{
   747  			fieldManager: "kubectl",
   748  			original: []byte(`
   749  apiVersion: v1
   750  kind: Pod
   751  metadata:
   752    name: test
   753    namespace: test
   754  spec:
   755    containers:
   756    - args:
   757      - -v=2
   758      command:
   759      - controller
   760      image: some.registry/app:latest
   761      name: doJob
   762    nodeName: definetlyControlPlane
   763    nodeSelector:
   764      node-role.kubernetes.io/master: ""
   765  `),
   766  			applied: []byte(`
   767  # All accepted while using the same field manager
   768  
   769  apiVersion: v1
   770  kind: Pod
   771  metadata:
   772    name: test
   773    namespace: test
   774  spec:
   775    containers:
   776    - args:
   777      - -v=2
   778      command:
   779      - controller
   780      image: some.registry/app:latest
   781      name: doJob
   782    nodeSelector:
   783      node-role.kubernetes.io/worker: ""
   784  `),
   785  		},
   786  		{
   787  			fieldManager: "not_kubectl",
   788  			original: []byte(`
   789  apiVersion: v1
   790  kind: Pod
   791  metadata:
   792    name: test
   793    namespace: test
   794  spec:
   795    containers:
   796    - args:
   797      - -v=2
   798      command:
   799      - controller
   800      image: some.registry/app:latest
   801      name: doJob
   802    nodeName: definetlyControlPlane
   803    nodeSelector:
   804      node-role.kubernetes.io/master: ""
   805  `),
   806  			applied: []byte(`
   807  # test selector update not allowed by last-applied
   808  
   809  apiVersion: v1
   810  kind: Pod
   811  metadata:
   812    name: test
   813    namespace: test
   814  spec:
   815    containers:
   816    - args:
   817      - -v=2
   818      command:
   819      - controller
   820      image: some.registry/app:latest
   821      name: doJob
   822    nodeName: definetlyControlPlane
   823    nodeSelector:
   824      node-role.kubernetes.io/master: ""
   825      otherNodeType: ""
   826  `),
   827  			expectConflictSet: fieldpath.NewSet(
   828  				fieldpath.MakePathOrDie("spec", "nodeSelector"), // selector is atomic
   829  			),
   830  		},
   831  		{
   832  			fieldManager: "not_kubectl",
   833  			original: []byte(`
   834  apiVersion: v1
   835  kind: Pod
   836  metadata:
   837    name: test
   838    namespace: test
   839  spec:
   840    containers:
   841    - args:
   842      - -v=2
   843      command:
   844      - controller
   845      image: some.registry/app:latest
   846      name: doJob
   847    nodeName: definetlyControlPlane
   848    nodeSelector:
   849      node-role.kubernetes.io/master: ""
   850  `),
   851  			applied: []byte(`
   852  # purging selector not allowed for different manager
   853  
   854  apiVersion: v1
   855  kind: Pod
   856  metadata:
   857    name: test
   858    namespace: test
   859  spec:
   860    containers:
   861    - args:
   862      - -v=2
   863      command:
   864      - controller
   865      image: some.registry/app:latest
   866      name: doJob
   867    nodeName: another
   868    nodeSelector: {}
   869  `),
   870  			expectConflictSet: fieldpath.NewSet(
   871  				fieldpath.MakePathOrDie("spec", "nodeSelector"), // selector is atomic
   872  				fieldpath.MakePathOrDie("spec", "nodeName"),
   873  			),
   874  		},
   875  		{
   876  			fieldManager: "kubectl",
   877  			original: []byte(`
   878  apiVersion: v1
   879  kind: Pod
   880  metadata:
   881    name: test
   882    namespace: test
   883  spec:
   884    containers:
   885    - args:
   886      - -v=2
   887      command:
   888      - controller
   889      image: some.registry/app:latest
   890      name: doJob
   891    nodeName: definetlyControlPlane
   892    nodeSelector:
   893      node-role.kubernetes.io/master: ""
   894  `),
   895  			applied: []byte(`
   896  # same manager could purge nodeSelector
   897  
   898  apiVersion: v1
   899  kind: Pod
   900  metadata:
   901    name: test
   902    namespace: test
   903  spec:
   904    containers:
   905    - args:
   906      - -v=2
   907      command:
   908      - controller
   909      image: some.registry/app:latest
   910      name: doJob
   911    nodeName: another
   912    nodeSelector: {}
   913  `),
   914  		},
   915  	}
   916  
   917  	testConflicts(t, f, tests)
   918  }
   919  
   920  func testConflicts(t *testing.T, f managedfieldstest.TestFieldManager, tests []testArgs) {
   921  	for i, test := range tests {
   922  		t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
   923  			f.Reset()
   924  
   925  			originalObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
   926  			if err := yaml.Unmarshal(test.original, &originalObj.Object); err != nil {
   927  				t.Errorf("error decoding YAML: %v", err)
   928  			}
   929  
   930  			if test.lastApplied == nil {
   931  				test.lastApplied = test.original
   932  			}
   933  			if err := setLastAppliedFromEncoded(originalObj, test.lastApplied); err != nil {
   934  				t.Errorf("failed to set last applied: %v", err)
   935  			}
   936  
   937  			if err := f.Update(originalObj, "test_client_side_apply"); err != nil {
   938  				t.Errorf("failed to apply object: %v", err)
   939  			}
   940  
   941  			appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
   942  			if err := yaml.Unmarshal(test.applied, &appliedObj.Object); err != nil {
   943  				t.Errorf("error decoding YAML: %v", err)
   944  			}
   945  
   946  			err := f.Apply(appliedObj, test.fieldManager, false)
   947  
   948  			if test.expectConflictSet == nil {
   949  				if err != nil {
   950  					t.Errorf("expected no error but got %v", err)
   951  				}
   952  			} else {
   953  				if err == nil || !apierrors.IsConflict(err) {
   954  					t.Errorf("expected to get conflicts but got %v", err)
   955  				}
   956  
   957  				expectedConflicts := merge.Conflicts{}
   958  				test.expectConflictSet.Iterate(func(p fieldpath.Path) {
   959  					expectedConflicts = append(expectedConflicts, merge.Conflict{
   960  						Manager: fmt.Sprintf(`{"manager":"test_client_side_apply","operation":"Update","apiVersion":"%s"}`, f.APIVersion()),
   961  						Path:    p,
   962  					})
   963  				})
   964  				expectedConflictErr := internal.NewConflictError(expectedConflicts)
   965  				if !reflect.DeepEqual(expectedConflictErr, err) {
   966  					t.Errorf("expected to get\n%+v\nbut got\n%+v", expectedConflictErr, err)
   967  				}
   968  
   969  				// Yet force should resolve all conflicts
   970  				err = f.Apply(appliedObj, test.fieldManager, true)
   971  				if err != nil {
   972  					t.Errorf("unexpected error during force ownership apply: %v", err)
   973  				}
   974  
   975  			}
   976  
   977  			// Eventually resource should contain applied changes
   978  			if !apiequality.Semantic.DeepDerivative(appliedObj, f.Live()) {
   979  				t.Errorf("expected equal resource: \n%#v, got: \n%#v", appliedObj, f.Live())
   980  			}
   981  		})
   982  	}
   983  }
   984  
   985  func yamlToJSON(y []byte) (string, error) {
   986  	obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
   987  	if err := yaml.Unmarshal(y, &obj.Object); err != nil {
   988  		return "", fmt.Errorf("error decoding YAML: %v", err)
   989  	}
   990  	serialization, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
   991  	if err != nil {
   992  		return "", fmt.Errorf("error encoding object: %v", err)
   993  	}
   994  	json, err := yamlutil.ToJSON(serialization)
   995  	if err != nil {
   996  		return "", fmt.Errorf("error converting to json: %v", err)
   997  	}
   998  	return string(json), nil
   999  }
  1000  
  1001  func setLastAppliedFromEncoded(obj runtime.Object, lastApplied []byte) error {
  1002  	lastAppliedJSON, err := yamlToJSON(lastApplied)
  1003  	if err != nil {
  1004  		return err
  1005  	}
  1006  	return internal.SetLastApplied(obj, lastAppliedJSON)
  1007  }
  1008  

View as plain text