...

Source file src/k8s.io/kubectl/pkg/cmd/util/editor/editoptions_test.go

Documentation: k8s.io/kubectl/pkg/cmd/util/editor

     1  /*
     2  Copyright 2018 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 editor
    18  
    19  import (
    20  	"reflect"
    21  	"strings"
    22  	"testing"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/cli-runtime/pkg/genericiooptions"
    32  	"k8s.io/cli-runtime/pkg/resource"
    33  )
    34  
    35  func TestHashOnLineBreak(t *testing.T) {
    36  	tests := []struct {
    37  		original string
    38  		expected string
    39  	}{
    40  		{
    41  			original: "",
    42  			expected: "",
    43  		},
    44  		{
    45  			original: "\n",
    46  			expected: "\n",
    47  		},
    48  		{
    49  			original: "a\na\na\n",
    50  			expected: "a\n# a\n# a\n",
    51  		},
    52  		{
    53  			original: "a\n\n\na\n\n",
    54  			expected: "a\n# \n# \n# a\n# \n",
    55  		},
    56  	}
    57  	for _, test := range tests {
    58  		r := hashOnLineBreak(test.original)
    59  		if r != test.expected {
    60  			t.Errorf("expected: %s, saw: %s", test.expected, r)
    61  		}
    62  	}
    63  }
    64  
    65  func TestManagedFieldsExtractAndRestore(t *testing.T) {
    66  	tests := map[string]struct {
    67  		object        runtime.Object
    68  		managedFields map[types.UID][]metav1.ManagedFieldsEntry
    69  	}{
    70  		"single object, empty managedFields": {
    71  			object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("12345")}},
    72  			managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
    73  				types.UID("12345"): nil,
    74  			},
    75  		},
    76  		"multiple objects, empty managedFields": {
    77  			object: &unstructured.UnstructuredList{
    78  				Object: map[string]interface{}{
    79  					"kind":       "List",
    80  					"apiVersion": "v1",
    81  					"metadata":   map[string]interface{}{},
    82  				},
    83  				Items: []unstructured.Unstructured{
    84  					{
    85  						Object: map[string]interface{}{
    86  							"apiVersion": "v1",
    87  							"kind":       "Pod",
    88  							"metadata": map[string]interface{}{
    89  								"uid": "12345",
    90  							},
    91  							"spec":   map[string]interface{}{},
    92  							"status": map[string]interface{}{},
    93  						},
    94  					},
    95  					{
    96  						Object: map[string]interface{}{
    97  							"apiVersion": "v1",
    98  							"kind":       "Pod",
    99  							"metadata": map[string]interface{}{
   100  								"uid": "98765",
   101  							},
   102  							"spec":   map[string]interface{}{},
   103  							"status": map[string]interface{}{},
   104  						},
   105  					},
   106  				},
   107  			},
   108  			managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
   109  				types.UID("12345"): nil,
   110  				types.UID("98765"): nil,
   111  			},
   112  		},
   113  		"single object, all managedFields": {
   114  			object: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{
   115  				UID: types.UID("12345"),
   116  				ManagedFields: []metav1.ManagedFieldsEntry{
   117  					{
   118  						Manager:   "test",
   119  						Operation: metav1.ManagedFieldsOperationApply,
   120  					},
   121  				},
   122  			}},
   123  			managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
   124  				types.UID("12345"): {
   125  					{
   126  						Manager:   "test",
   127  						Operation: metav1.ManagedFieldsOperationApply,
   128  					},
   129  				},
   130  			},
   131  		},
   132  		"multiple objects, all managedFields": {
   133  			object: &unstructured.UnstructuredList{
   134  				Object: map[string]interface{}{
   135  					"kind":       "List",
   136  					"apiVersion": "v1",
   137  					"metadata":   map[string]interface{}{},
   138  				},
   139  				Items: []unstructured.Unstructured{
   140  					{
   141  						Object: map[string]interface{}{
   142  							"apiVersion": "v1",
   143  							"kind":       "Pod",
   144  							"metadata": map[string]interface{}{
   145  								"uid": "12345",
   146  								"managedFields": []interface{}{
   147  									map[string]interface{}{
   148  										"manager":   "test",
   149  										"operation": "Apply",
   150  									},
   151  								},
   152  							},
   153  							"spec":   map[string]interface{}{},
   154  							"status": map[string]interface{}{},
   155  						},
   156  					},
   157  					{
   158  						Object: map[string]interface{}{
   159  							"apiVersion": "v1",
   160  							"kind":       "Pod",
   161  							"metadata": map[string]interface{}{
   162  								"uid": "98765",
   163  								"managedFields": []interface{}{
   164  									map[string]interface{}{
   165  										"manager":   "test",
   166  										"operation": "Update",
   167  									},
   168  								},
   169  							},
   170  							"spec":   map[string]interface{}{},
   171  							"status": map[string]interface{}{},
   172  						},
   173  					},
   174  				},
   175  			},
   176  			managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
   177  				types.UID("12345"): {
   178  					{
   179  						Manager:   "test",
   180  						Operation: metav1.ManagedFieldsOperationApply,
   181  					},
   182  				},
   183  				types.UID("98765"): {
   184  					{
   185  						Manager:   "test",
   186  						Operation: metav1.ManagedFieldsOperationUpdate,
   187  					},
   188  				},
   189  			},
   190  		},
   191  		"multiple objects, some managedFields": {
   192  			object: &unstructured.UnstructuredList{
   193  				Object: map[string]interface{}{
   194  					"kind":       "List",
   195  					"apiVersion": "v1",
   196  					"metadata":   map[string]interface{}{},
   197  				},
   198  				Items: []unstructured.Unstructured{
   199  					{
   200  						Object: map[string]interface{}{
   201  							"apiVersion": "v1",
   202  							"kind":       "Pod",
   203  							"metadata": map[string]interface{}{
   204  								"uid": "12345",
   205  								"managedFields": []interface{}{
   206  									map[string]interface{}{
   207  										"manager":   "test",
   208  										"operation": "Apply",
   209  									},
   210  								},
   211  							},
   212  							"spec":   map[string]interface{}{},
   213  							"status": map[string]interface{}{},
   214  						},
   215  					},
   216  					{
   217  						Object: map[string]interface{}{
   218  							"apiVersion": "v1",
   219  							"kind":       "Pod",
   220  							"metadata": map[string]interface{}{
   221  								"uid": "98765",
   222  							},
   223  							"spec":   map[string]interface{}{},
   224  							"status": map[string]interface{}{},
   225  						},
   226  					},
   227  				},
   228  			},
   229  			managedFields: map[types.UID][]metav1.ManagedFieldsEntry{
   230  				types.UID("12345"): {
   231  					{
   232  						Manager:   "test",
   233  						Operation: metav1.ManagedFieldsOperationApply,
   234  					},
   235  				},
   236  				types.UID("98765"): nil,
   237  			},
   238  		},
   239  	}
   240  
   241  	for name, test := range tests {
   242  		t.Run(name, func(t *testing.T) {
   243  			// operate on a copy, so we can compare the original and the modified object
   244  			objCopy := test.object.DeepCopyObject()
   245  			var infos []*resource.Info
   246  			o := NewEditOptions(NormalEditMode, genericiooptions.NewTestIOStreamsDiscard())
   247  			err := o.extractManagedFields(objCopy)
   248  			if err != nil {
   249  				t.Errorf("unexpected extraction error %v", err)
   250  			}
   251  			if meta.IsListType(objCopy) {
   252  				infos = []*resource.Info{}
   253  				meta.EachListItem(objCopy, func(obj runtime.Object) error {
   254  					metaObjs, _ := meta.Accessor(obj)
   255  					if metaObjs.GetManagedFields() != nil {
   256  						t.Errorf("unexpected managedFileds after extraction")
   257  					}
   258  					infos = append(infos, &resource.Info{Object: obj})
   259  					return nil
   260  				})
   261  			} else {
   262  				metaObjs, _ := meta.Accessor(objCopy)
   263  				if metaObjs.GetManagedFields() != nil {
   264  					t.Errorf("unexpected managedFileds after extraction")
   265  				}
   266  				infos = []*resource.Info{{Object: objCopy}}
   267  			}
   268  
   269  			err = o.restoreManagedFields(infos)
   270  			if err != nil {
   271  				t.Errorf("unexpected restore error %v", err)
   272  			}
   273  			if !reflect.DeepEqual(test.object, objCopy) {
   274  				t.Errorf("mismatched object after extract and restore managedFields: %#v", objCopy)
   275  			}
   276  			if test.managedFields != nil && !reflect.DeepEqual(test.managedFields, o.managedFields) {
   277  				t.Errorf("mismatched managedFields %#v vs %#v", test.managedFields, o.managedFields)
   278  			}
   279  		})
   280  	}
   281  }
   282  
   283  type testVisitor struct {
   284  	updatedInfos []*resource.Info
   285  }
   286  
   287  func (tv *testVisitor) Visit(f resource.VisitorFunc) error {
   288  	var err error
   289  	for _, ui := range tv.updatedInfos {
   290  		err = f(ui, err)
   291  	}
   292  	return err
   293  }
   294  
   295  var unregMapping = &meta.RESTMapping{
   296  	Resource: schema.GroupVersionResource{
   297  		Group:    "a",
   298  		Version:  "b",
   299  		Resource: "c",
   300  	},
   301  	GroupVersionKind: schema.GroupVersionKind{
   302  		Group:   "a",
   303  		Version: "b",
   304  		Kind:    "d",
   305  	},
   306  }
   307  
   308  func TestEditOptions_visitToPatch(t *testing.T) {
   309  
   310  	expectedErr := func(err error) bool {
   311  		return err != nil && strings.Contains(err.Error(), "At least one of apiVersion, kind and name was changed")
   312  	}
   313  
   314  	type args struct {
   315  		originalInfos []*resource.Info
   316  		patchVisitor  resource.Visitor
   317  		results       *editResults
   318  	}
   319  	tests := []struct {
   320  		name     string
   321  		args     args
   322  		checkErr func(err error) bool
   323  	}{
   324  		{
   325  			name: "name-diff",
   326  			args: args{
   327  				originalInfos: []*resource.Info{
   328  					{
   329  						Namespace: "ns",
   330  						Name:      "before",
   331  						Object: &unstructured.Unstructured{
   332  							Object: map[string]interface{}{
   333  								"apiVersion": "v1",
   334  								"kind":       "Thingy",
   335  								"metadata": map[string]interface{}{
   336  									"uid":       "12345",
   337  									"namespace": "ns",
   338  									"name":      "before",
   339  								},
   340  								"spec": map[string]interface{}{},
   341  							},
   342  						},
   343  						Mapping: unregMapping,
   344  					},
   345  				},
   346  				patchVisitor: &testVisitor{
   347  					updatedInfos: []*resource.Info{
   348  						{
   349  							Namespace: "ns",
   350  							Name:      "after",
   351  							Object: &unstructured.Unstructured{
   352  								Object: map[string]interface{}{
   353  									"apiVersion": "v1",
   354  									"kind":       "Thingy",
   355  									"metadata": map[string]interface{}{
   356  										"uid":       "12345",
   357  										"namespace": "ns",
   358  										"name":      "after",
   359  									},
   360  									"spec": map[string]interface{}{},
   361  								},
   362  							},
   363  							Mapping: unregMapping,
   364  						},
   365  					},
   366  				},
   367  				results: &editResults{},
   368  			},
   369  			checkErr: expectedErr,
   370  		},
   371  		{
   372  			name: "kind-diff",
   373  			args: args{
   374  				originalInfos: []*resource.Info{
   375  					{
   376  						Namespace: "ns",
   377  						Name:      "myname",
   378  						Object: &unstructured.Unstructured{
   379  							Object: map[string]interface{}{
   380  								"apiVersion": "v1",
   381  								"kind":       "Thingy",
   382  								"metadata": map[string]interface{}{
   383  									"uid":       "12345",
   384  									"namespace": "ns",
   385  									"name":      "myname",
   386  								},
   387  								"spec": map[string]interface{}{},
   388  							},
   389  						},
   390  						Mapping: unregMapping,
   391  					},
   392  				},
   393  				patchVisitor: &testVisitor{
   394  					updatedInfos: []*resource.Info{
   395  						{
   396  							Namespace: "ns",
   397  							Name:      "myname",
   398  							Object: &unstructured.Unstructured{
   399  								Object: map[string]interface{}{
   400  									"apiVersion": "v1",
   401  									"kind":       "OtherThingy",
   402  									"metadata": map[string]interface{}{
   403  										"uid":       "12345",
   404  										"namespace": "ns",
   405  										"name":      "myname",
   406  									},
   407  									"spec": map[string]interface{}{},
   408  								},
   409  							},
   410  							Mapping: unregMapping,
   411  						},
   412  					},
   413  				},
   414  				results: &editResults{},
   415  			},
   416  			checkErr: expectedErr,
   417  		},
   418  		{
   419  			name: "apiver-diff",
   420  			args: args{
   421  				originalInfos: []*resource.Info{
   422  					{
   423  						Namespace: "ns",
   424  						Name:      "myname",
   425  						Object: &unstructured.Unstructured{
   426  							Object: map[string]interface{}{
   427  								"apiVersion": "v1",
   428  								"kind":       "Thingy",
   429  								"metadata": map[string]interface{}{
   430  									"uid":       "12345",
   431  									"namespace": "ns",
   432  									"name":      "myname",
   433  								},
   434  								"spec": map[string]interface{}{},
   435  							},
   436  						},
   437  						Mapping: unregMapping,
   438  					},
   439  				},
   440  				patchVisitor: &testVisitor{
   441  					updatedInfos: []*resource.Info{
   442  						{
   443  							Namespace: "ns",
   444  							Name:      "myname",
   445  							Object: &unstructured.Unstructured{
   446  								Object: map[string]interface{}{
   447  									"apiVersion": "v1alpha1",
   448  									"kind":       "Thingy",
   449  									"metadata": map[string]interface{}{
   450  										"uid":       "12345",
   451  										"namespace": "ns",
   452  										"name":      "myname",
   453  									},
   454  									"spec": map[string]interface{}{},
   455  								},
   456  							},
   457  							Mapping: unregMapping,
   458  						},
   459  					},
   460  				},
   461  				results: &editResults{},
   462  			},
   463  			checkErr: expectedErr,
   464  		},
   465  	}
   466  	for _, tt := range tests {
   467  		t.Run(tt.name, func(t *testing.T) {
   468  			o := &EditOptions{}
   469  			if err := o.visitToPatch(tt.args.originalInfos, tt.args.patchVisitor, tt.args.results); !tt.checkErr(err) {
   470  				t.Errorf("EditOptions.visitToPatch() %s error = %v", tt.name, err)
   471  			}
   472  		})
   473  	}
   474  }
   475  

View as plain text