...

Source file src/k8s.io/kubectl/pkg/cmd/label/label_test.go

Documentation: k8s.io/kubectl/pkg/cmd/label

     1  /*
     2  Copyright 2014 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 label
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/util/json"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	"k8s.io/cli-runtime/pkg/resource"
    35  	"k8s.io/client-go/rest/fake"
    36  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    37  	"k8s.io/kubectl/pkg/scheme"
    38  )
    39  
    40  func TestValidateLabels(t *testing.T) {
    41  	tests := []struct {
    42  		meta      *metav1.ObjectMeta
    43  		labels    map[string]string
    44  		expectErr bool
    45  		test      string
    46  	}{
    47  		{
    48  			meta: &metav1.ObjectMeta{
    49  				Labels: map[string]string{
    50  					"a": "b",
    51  					"c": "d",
    52  				},
    53  			},
    54  			labels: map[string]string{
    55  				"a": "c",
    56  				"d": "b",
    57  			},
    58  			test:      "one shared",
    59  			expectErr: true,
    60  		},
    61  		{
    62  			meta: &metav1.ObjectMeta{
    63  				Labels: map[string]string{
    64  					"a": "b",
    65  					"c": "d",
    66  				},
    67  			},
    68  			labels: map[string]string{
    69  				"b": "d",
    70  				"c": "a",
    71  			},
    72  			test:      "second shared",
    73  			expectErr: true,
    74  		},
    75  		{
    76  			meta: &metav1.ObjectMeta{
    77  				Labels: map[string]string{
    78  					"a": "b",
    79  					"c": "d",
    80  				},
    81  			},
    82  			labels: map[string]string{
    83  				"b": "a",
    84  				"d": "c",
    85  			},
    86  			test: "no overlap",
    87  		},
    88  		{
    89  			meta: &metav1.ObjectMeta{},
    90  			labels: map[string]string{
    91  				"b": "a",
    92  				"d": "c",
    93  			},
    94  			test: "no labels",
    95  		},
    96  	}
    97  	for _, test := range tests {
    98  		err := validateNoOverwrites(test.meta, test.labels)
    99  		if test.expectErr && err == nil {
   100  			t.Errorf("%s: unexpected non-error", test.test)
   101  		}
   102  		if !test.expectErr && err != nil {
   103  			t.Errorf("%s: unexpected error: %v", test.test, err)
   104  		}
   105  	}
   106  }
   107  
   108  func TestParseLabels(t *testing.T) {
   109  	tests := []struct {
   110  		labels         []string
   111  		expected       map[string]string
   112  		expectedRemove []string
   113  		expectErr      bool
   114  	}{
   115  		{
   116  			labels:   []string{"a=b", "c=d"},
   117  			expected: map[string]string{"a": "b", "c": "d"},
   118  		},
   119  		{
   120  			labels:   []string{},
   121  			expected: map[string]string{},
   122  		},
   123  		{
   124  			labels:         []string{"a=b", "c=d", "e-"},
   125  			expected:       map[string]string{"a": "b", "c": "d"},
   126  			expectedRemove: []string{"e"},
   127  		},
   128  		{
   129  			labels:    []string{"ab", "c=d"},
   130  			expectErr: true,
   131  		},
   132  		{
   133  			labels:    []string{"a=b", "c=d", "a-"},
   134  			expectErr: true,
   135  		},
   136  		{
   137  			labels:   []string{"a="},
   138  			expected: map[string]string{"a": ""},
   139  		},
   140  		{
   141  			labels:    []string{"a=%^$"},
   142  			expectErr: true,
   143  		},
   144  	}
   145  	for _, test := range tests {
   146  		labels, remove, err := parseLabels(test.labels)
   147  		if test.expectErr && err == nil {
   148  			t.Errorf("unexpected non-error: %v", test)
   149  		}
   150  		if !test.expectErr && err != nil {
   151  			t.Errorf("unexpected error: %v %v", err, test)
   152  		}
   153  		if !reflect.DeepEqual(labels, test.expected) {
   154  			t.Errorf("expected: %v, got %v", test.expected, labels)
   155  		}
   156  		if !reflect.DeepEqual(remove, test.expectedRemove) {
   157  			t.Errorf("expected: %v, got %v", test.expectedRemove, remove)
   158  		}
   159  	}
   160  }
   161  
   162  func TestLabelFunc(t *testing.T) {
   163  	tests := []struct {
   164  		obj       runtime.Object
   165  		overwrite bool
   166  		version   string
   167  		labels    map[string]string
   168  		remove    []string
   169  		expected  runtime.Object
   170  		expectErr string
   171  	}{
   172  		{
   173  			obj: &v1.Pod{
   174  				ObjectMeta: metav1.ObjectMeta{
   175  					Labels: map[string]string{"a": "b"},
   176  				},
   177  			},
   178  			labels: map[string]string{"a": "b"},
   179  			expected: &v1.Pod{
   180  				ObjectMeta: metav1.ObjectMeta{
   181  					Labels: map[string]string{"a": "b"},
   182  				},
   183  			},
   184  		},
   185  		{
   186  			obj: &v1.Pod{
   187  				ObjectMeta: metav1.ObjectMeta{
   188  					Labels: map[string]string{"a": "b"},
   189  				},
   190  			},
   191  			labels:    map[string]string{"a": "c"},
   192  			expectErr: "'a' already has a value (b), and --overwrite is false",
   193  		},
   194  		{
   195  			obj: &v1.Pod{
   196  				ObjectMeta: metav1.ObjectMeta{
   197  					Labels: map[string]string{"a": "b"},
   198  				},
   199  			},
   200  			labels:    map[string]string{"a": "c"},
   201  			overwrite: true,
   202  			expected: &v1.Pod{
   203  				ObjectMeta: metav1.ObjectMeta{
   204  					Labels: map[string]string{"a": "c"},
   205  				},
   206  			},
   207  		},
   208  		{
   209  			obj: &v1.Pod{
   210  				ObjectMeta: metav1.ObjectMeta{
   211  					Labels: map[string]string{"a": "b"},
   212  				},
   213  			},
   214  			labels: map[string]string{"c": "d"},
   215  			expected: &v1.Pod{
   216  				ObjectMeta: metav1.ObjectMeta{
   217  					Labels: map[string]string{"a": "b", "c": "d"},
   218  				},
   219  			},
   220  		},
   221  		{
   222  			obj: &v1.Pod{
   223  				ObjectMeta: metav1.ObjectMeta{
   224  					Labels: map[string]string{"a": "b"},
   225  				},
   226  			},
   227  			labels:  map[string]string{"c": "d"},
   228  			version: "2",
   229  			expected: &v1.Pod{
   230  				ObjectMeta: metav1.ObjectMeta{
   231  					Labels:          map[string]string{"a": "b", "c": "d"},
   232  					ResourceVersion: "2",
   233  				},
   234  			},
   235  		},
   236  		{
   237  			obj: &v1.Pod{
   238  				ObjectMeta: metav1.ObjectMeta{
   239  					Labels: map[string]string{"a": "b"},
   240  				},
   241  			},
   242  			labels: map[string]string{},
   243  			remove: []string{"a"},
   244  			expected: &v1.Pod{
   245  				ObjectMeta: metav1.ObjectMeta{
   246  					Labels: map[string]string{},
   247  				},
   248  			},
   249  		},
   250  		{
   251  			obj: &v1.Pod{
   252  				ObjectMeta: metav1.ObjectMeta{
   253  					Labels: map[string]string{"a": "b", "c": "d"},
   254  				},
   255  			},
   256  			labels: map[string]string{"e": "f"},
   257  			remove: []string{"a"},
   258  			expected: &v1.Pod{
   259  				ObjectMeta: metav1.ObjectMeta{
   260  					Labels: map[string]string{
   261  						"c": "d",
   262  						"e": "f",
   263  					},
   264  				},
   265  			},
   266  		},
   267  		{
   268  			obj: &v1.Pod{
   269  				ObjectMeta: metav1.ObjectMeta{},
   270  			},
   271  			labels: map[string]string{"a": "b"},
   272  			expected: &v1.Pod{
   273  				ObjectMeta: metav1.ObjectMeta{
   274  					Labels: map[string]string{"a": "b"},
   275  				},
   276  			},
   277  		},
   278  	}
   279  	for _, test := range tests {
   280  		err := labelFunc(test.obj, test.overwrite, test.version, test.labels, test.remove)
   281  		if test.expectErr != "" {
   282  			if err == nil {
   283  				t.Errorf("unexpected non-error: %v", test)
   284  			}
   285  			if err.Error() != test.expectErr {
   286  				t.Errorf("error expected: %v, got: %v", test.expectErr, err.Error())
   287  			}
   288  			continue
   289  		}
   290  		if test.expectErr == "" && err != nil {
   291  			t.Errorf("unexpected error: %v %v", err, test)
   292  		}
   293  		if !reflect.DeepEqual(test.obj, test.expected) {
   294  			t.Errorf("expected: %v, got %v", test.expected, test.obj)
   295  		}
   296  	}
   297  }
   298  
   299  func TestLabelErrors(t *testing.T) {
   300  	testCases := map[string]struct {
   301  		args  []string
   302  		errFn func(error) bool
   303  	}{
   304  		"no args": {
   305  			args:  []string{},
   306  			errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
   307  		},
   308  		"not enough labels": {
   309  			args:  []string{"pods"},
   310  			errFn: func(err error) bool { return strings.Contains(err.Error(), "at least one label update is required") },
   311  		},
   312  		"wrong labels": {
   313  			args:  []string{"pods", "-"},
   314  			errFn: func(err error) bool { return strings.Contains(err.Error(), "at least one label update is required") },
   315  		},
   316  		"wrong labels 2": {
   317  			args:  []string{"pods", "=bar"},
   318  			errFn: func(err error) bool { return strings.Contains(err.Error(), "at least one label update is required") },
   319  		},
   320  		"no resources": {
   321  			args:  []string{"pods-"},
   322  			errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
   323  		},
   324  		"no resources 2": {
   325  			args:  []string{"pods=bar"},
   326  			errFn: func(err error) bool { return strings.Contains(err.Error(), "one or more resources must be specified") },
   327  		},
   328  		"resources but no selectors": {
   329  			args: []string{"pods", "app=bar"},
   330  			errFn: func(err error) bool {
   331  				return strings.Contains(err.Error(), "resource(s) were provided, but no name was specified")
   332  			},
   333  		},
   334  		"multiple resources but no selectors": {
   335  			args: []string{"pods,deployments", "app=bar"},
   336  			errFn: func(err error) bool {
   337  				return strings.Contains(err.Error(), "resource(s) were provided, but no name was specified")
   338  			},
   339  		},
   340  	}
   341  
   342  	for k, testCase := range testCases {
   343  		t.Run(k, func(t *testing.T) {
   344  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   345  			defer tf.Cleanup()
   346  
   347  			tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   348  
   349  			ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
   350  			buf := bytes.NewBuffer([]byte{})
   351  			cmd := NewCmdLabel(tf, ioStreams)
   352  			cmd.SetOut(buf)
   353  			cmd.SetErr(buf)
   354  
   355  			opts := NewLabelOptions(ioStreams)
   356  			err := opts.Complete(tf, cmd, testCase.args)
   357  			if err == nil {
   358  				err = opts.Validate()
   359  			}
   360  			if err == nil {
   361  				err = opts.RunLabel()
   362  			}
   363  			if !testCase.errFn(err) {
   364  				t.Errorf("%s: unexpected error: %v", k, err)
   365  				return
   366  			}
   367  			if buf.Len() > 0 {
   368  				t.Errorf("buffer should be empty: %s", buf.String())
   369  			}
   370  		})
   371  	}
   372  }
   373  
   374  func TestLabelForResourceFromFile(t *testing.T) {
   375  	pods, _, _ := cmdtesting.TestData()
   376  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   377  	defer tf.Cleanup()
   378  
   379  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   380  
   381  	tf.UnstructuredClient = &fake.RESTClient{
   382  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   383  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   384  			switch req.Method {
   385  			case "GET":
   386  				switch req.URL.Path {
   387  				case "/namespaces/test/replicationcontrollers/cassandra":
   388  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
   389  				default:
   390  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   391  					return nil, nil
   392  				}
   393  			case "PATCH":
   394  				switch req.URL.Path {
   395  				case "/namespaces/test/replicationcontrollers/cassandra":
   396  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
   397  				default:
   398  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   399  					return nil, nil
   400  				}
   401  			default:
   402  				t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   403  				return nil, nil
   404  			}
   405  		}),
   406  	}
   407  	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   408  
   409  	ioStreams, _, buf, _ := genericiooptions.NewTestIOStreams()
   410  	cmd := NewCmdLabel(tf, ioStreams)
   411  	opts := NewLabelOptions(ioStreams)
   412  	opts.Filenames = []string{"../../../testdata/controller.yaml"}
   413  	err := opts.Complete(tf, cmd, []string{"a=b"})
   414  	if err == nil {
   415  		err = opts.Validate()
   416  	}
   417  	if err == nil {
   418  		err = opts.RunLabel()
   419  	}
   420  	if err != nil {
   421  		t.Fatalf("unexpected error: %v", err)
   422  	}
   423  	if !strings.Contains(buf.String(), "labeled") {
   424  		t.Errorf("did not set labels: %s", buf.String())
   425  	}
   426  }
   427  
   428  func TestLabelLocal(t *testing.T) {
   429  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   430  	defer tf.Cleanup()
   431  
   432  	tf.UnstructuredClient = &fake.RESTClient{
   433  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   434  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   435  			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   436  			return nil, nil
   437  		}),
   438  	}
   439  	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   440  
   441  	ioStreams, _, buf, _ := genericiooptions.NewTestIOStreams()
   442  	cmd := NewCmdLabel(tf, ioStreams)
   443  	opts := NewLabelOptions(ioStreams)
   444  	opts.Filenames = []string{"../../../testdata/controller.yaml"}
   445  	opts.local = true
   446  	err := opts.Complete(tf, cmd, []string{"a=b"})
   447  	if err == nil {
   448  		err = opts.Validate()
   449  	}
   450  	if err == nil {
   451  		err = opts.RunLabel()
   452  	}
   453  	if err != nil {
   454  		t.Fatalf("unexpected error: %v", err)
   455  	}
   456  	if !strings.Contains(buf.String(), "labeled") {
   457  		t.Errorf("did not set labels: %s", buf.String())
   458  	}
   459  }
   460  
   461  func TestLabelMultipleObjects(t *testing.T) {
   462  	pods, _, _ := cmdtesting.TestData()
   463  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   464  	defer tf.Cleanup()
   465  
   466  	codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   467  
   468  	tf.UnstructuredClient = &fake.RESTClient{
   469  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   470  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   471  			switch req.Method {
   472  			case "GET":
   473  				switch req.URL.Path {
   474  				case "/namespaces/test/pods":
   475  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, nil
   476  				default:
   477  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   478  					return nil, nil
   479  				}
   480  			case "PATCH":
   481  				switch req.URL.Path {
   482  				case "/namespaces/test/pods/foo":
   483  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[0])}, nil
   484  				case "/namespaces/test/pods/bar":
   485  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods.Items[1])}, nil
   486  				default:
   487  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   488  					return nil, nil
   489  				}
   490  			default:
   491  				t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   492  				return nil, nil
   493  			}
   494  		}),
   495  	}
   496  	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   497  
   498  	ioStreams, _, buf, _ := genericiooptions.NewTestIOStreams()
   499  	opts := NewLabelOptions(ioStreams)
   500  	opts.all = true
   501  	cmd := NewCmdLabel(tf, ioStreams)
   502  	err := opts.Complete(tf, cmd, []string{"pods", "a=b"})
   503  	if err == nil {
   504  		err = opts.Validate()
   505  	}
   506  	if err == nil {
   507  		err = opts.RunLabel()
   508  	}
   509  	if err != nil {
   510  		t.Fatalf("unexpected error: %v", err)
   511  	}
   512  	if strings.Count(buf.String(), "labeled") != len(pods.Items) {
   513  		t.Errorf("not all labels are set: %s", buf.String())
   514  	}
   515  }
   516  
   517  func TestLabelResourceVersion(t *testing.T) {
   518  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   519  	defer tf.Cleanup()
   520  
   521  	tf.UnstructuredClient = &fake.RESTClient{
   522  		GroupVersion:         schema.GroupVersion{Group: "testgroup", Version: "v1"},
   523  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   524  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   525  			switch req.Method {
   526  			case "GET":
   527  				switch req.URL.Path {
   528  				case "/namespaces/test/pods/foo":
   529  					return &http.Response{
   530  						StatusCode: http.StatusOK,
   531  						Header:     cmdtesting.DefaultHeader(),
   532  						Body: io.NopCloser(bytes.NewBufferString(
   533  							`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","resourceVersion":"10"}}`,
   534  						))}, nil
   535  				default:
   536  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   537  					return nil, nil
   538  				}
   539  			case "PATCH":
   540  				switch req.URL.Path {
   541  				case "/namespaces/test/pods/foo":
   542  					body, err := io.ReadAll(req.Body)
   543  					if err != nil {
   544  						t.Fatal(err)
   545  					}
   546  					if !bytes.Equal(body, []byte(`{"metadata":{"labels":{"a":"b"},"resourceVersion":"10"}}`)) {
   547  						t.Fatalf("expected patch with resourceVersion set, got %s", string(body))
   548  					}
   549  					return &http.Response{
   550  						StatusCode: http.StatusOK,
   551  						Header:     cmdtesting.DefaultHeader(),
   552  						Body: io.NopCloser(bytes.NewBufferString(
   553  							`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","resourceVersion":"11"}}`,
   554  						))}, nil
   555  				default:
   556  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   557  					return nil, nil
   558  				}
   559  			default:
   560  				t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   561  				return nil, nil
   562  			}
   563  		}),
   564  	}
   565  	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   566  
   567  	iostreams, _, bufOut, _ := genericiooptions.NewTestIOStreams()
   568  	cmd := NewCmdLabel(tf, iostreams)
   569  	cmd.SetOut(bufOut)
   570  	cmd.SetErr(bufOut)
   571  	options := NewLabelOptions(iostreams)
   572  	options.resourceVersion = "10"
   573  	args := []string{"pods/foo", "a=b"}
   574  	if err := options.Complete(tf, cmd, args); err != nil {
   575  		t.Fatalf("unexpected error: %v", err)
   576  	}
   577  	if err := options.Validate(); err != nil {
   578  		t.Fatalf("unexpected error: %v", err)
   579  	}
   580  	if err := options.RunLabel(); err != nil {
   581  		t.Fatalf("unexpected error: %v", err)
   582  	}
   583  }
   584  
   585  func TestRunLabelMsg(t *testing.T) {
   586  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   587  	defer tf.Cleanup()
   588  
   589  	tf.UnstructuredClient = &fake.RESTClient{
   590  		GroupVersion:         schema.GroupVersion{Group: "testgroup", Version: "v1"},
   591  		NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   592  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   593  			switch req.Method {
   594  			case "GET":
   595  				switch req.URL.Path {
   596  				case "/namespaces/test/pods/foo":
   597  					return &http.Response{
   598  						StatusCode: http.StatusOK,
   599  						Header:     cmdtesting.DefaultHeader(),
   600  						Body: io.NopCloser(bytes.NewBufferString(
   601  							`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","labels":{"existing":"abc"}}}`,
   602  						))}, nil
   603  				default:
   604  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   605  					return nil, nil
   606  				}
   607  			case "PATCH":
   608  				switch req.URL.Path {
   609  				case "/namespaces/test/pods/foo":
   610  					return &http.Response{
   611  						StatusCode: http.StatusOK,
   612  						Header:     cmdtesting.DefaultHeader(),
   613  						Body: io.NopCloser(bytes.NewBufferString(
   614  							`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","labels":{"existing":"abc"}}}`,
   615  						))}, nil
   616  				default:
   617  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   618  					return nil, nil
   619  				}
   620  			default:
   621  				t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   622  				return nil, nil
   623  			}
   624  		}),
   625  	}
   626  	tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   627  
   628  	testCases := []struct {
   629  		name          string
   630  		args          []string
   631  		overwrite     bool
   632  		dryRun        string
   633  		expectedOut   string
   634  		expectedError error
   635  	}{
   636  		{
   637  			name:        "set new label",
   638  			args:        []string{"pods/foo", "foo=bar"},
   639  			expectedOut: "pod/foo labeled\n",
   640  		},
   641  		{
   642  			name:          "attempt to set existing label without using overwrite flag",
   643  			args:          []string{"pods/foo", "existing=bar"},
   644  			expectedError: fmt.Errorf("'existing' already has a value (abc), and --overwrite is false"),
   645  		},
   646  		{
   647  			name:        "set existing label",
   648  			args:        []string{"pods/foo", "existing=bar"},
   649  			overwrite:   true,
   650  			expectedOut: "pod/foo labeled\n",
   651  		},
   652  		{
   653  			name:        "unset existing label",
   654  			args:        []string{"pods/foo", "existing-"},
   655  			expectedOut: "pod/foo unlabeled\n",
   656  		},
   657  		{
   658  			name: "unset nonexisting label",
   659  			args: []string{"pods/foo", "foo-"},
   660  			expectedOut: `label "foo" not found.
   661  pod/foo not labeled
   662  `,
   663  		},
   664  		{
   665  			name:        "set new label with server dry run",
   666  			args:        []string{"pods/foo", "foo=bar"},
   667  			dryRun:      "server",
   668  			expectedOut: "pod/foo labeled (server dry run)\n",
   669  		},
   670  		{
   671  			name:        "set new label with client dry run",
   672  			args:        []string{"pods/foo", "foo=bar"},
   673  			dryRun:      "client",
   674  			expectedOut: "pod/foo labeled (dry run)\n",
   675  		},
   676  		{
   677  			name:        "unset existing label with server dry run",
   678  			args:        []string{"pods/foo", "existing-"},
   679  			dryRun:      "server",
   680  			expectedOut: "pod/foo unlabeled (server dry run)\n",
   681  		},
   682  		{
   683  			name:        "unset existing label with client dry run",
   684  			args:        []string{"pods/foo", "existing-"},
   685  			dryRun:      "client",
   686  			expectedOut: "pod/foo unlabeled (dry run)\n",
   687  		},
   688  	}
   689  
   690  	for _, tc := range testCases {
   691  		t.Run(tc.name, func(t *testing.T) {
   692  			iostreams, _, bufOut, _ := genericiooptions.NewTestIOStreams()
   693  			cmd := NewCmdLabel(tf, iostreams)
   694  			cmd.SetOut(bufOut)
   695  			cmd.SetErr(bufOut)
   696  			if tc.dryRun != "" {
   697  				cmd.Flags().Set("dry-run", tc.dryRun)
   698  			}
   699  			options := NewLabelOptions(iostreams)
   700  			if tc.overwrite {
   701  				options.overwrite = true
   702  			}
   703  			if err := options.Complete(tf, cmd, tc.args); err != nil {
   704  				t.Fatalf("unexpected error: %v", err)
   705  			}
   706  			if err := options.Validate(); err != nil {
   707  				t.Fatalf("unexpected error: %v", err)
   708  			}
   709  
   710  			err := options.RunLabel()
   711  			if tc.expectedError == nil {
   712  				if err != nil {
   713  					t.Fatalf("unexpected error: %v", err)
   714  				}
   715  			} else {
   716  				if err == nil {
   717  					t.Fatalf("expected, but did not get, error: %s", tc.expectedError.Error())
   718  				} else if err.Error() != tc.expectedError.Error() {
   719  					t.Fatalf("wrong error\ngot: %s\nexpected: %s\n", err.Error(), tc.expectedError.Error())
   720  				}
   721  			}
   722  
   723  			if bufOut.String() != tc.expectedOut {
   724  				t.Fatalf("wrong output\ngot:\n%s\nexpected:\n%s\n", bufOut.String(), tc.expectedOut)
   725  			}
   726  		})
   727  	}
   728  }
   729  
   730  func TestLabelMsg(t *testing.T) {
   731  	tests := []struct {
   732  		obj             runtime.Object
   733  		overwrite       bool
   734  		resourceVersion string
   735  		labels          map[string]string
   736  		remove          []string
   737  		expectObj       runtime.Object
   738  		expectMsg       string
   739  		expectErr       bool
   740  	}{
   741  		{
   742  			obj: &v1.Pod{
   743  				ObjectMeta: metav1.ObjectMeta{
   744  					Labels: map[string]string{"a": "b"},
   745  				},
   746  			},
   747  			labels:    map[string]string{"a": "b"},
   748  			expectMsg: MsgNotLabeled,
   749  		},
   750  		{
   751  			obj: &v1.Pod{
   752  				ObjectMeta: metav1.ObjectMeta{},
   753  			},
   754  			labels: map[string]string{"a": "b"},
   755  			expectObj: &v1.Pod{
   756  				ObjectMeta: metav1.ObjectMeta{
   757  					Labels: map[string]string{"a": "b"},
   758  				},
   759  			},
   760  			expectMsg: MsgLabeled,
   761  		},
   762  		{
   763  			obj: &v1.Pod{
   764  				ObjectMeta: metav1.ObjectMeta{
   765  					Labels: map[string]string{"a": "b"},
   766  				},
   767  			},
   768  			labels:    map[string]string{"a": "c"},
   769  			overwrite: true,
   770  			expectObj: &v1.Pod{
   771  				ObjectMeta: metav1.ObjectMeta{
   772  					Labels: map[string]string{"a": "c"},
   773  				},
   774  			},
   775  			expectMsg: MsgLabeled,
   776  		},
   777  		{
   778  			obj: &v1.Pod{
   779  				ObjectMeta: metav1.ObjectMeta{
   780  					Labels: map[string]string{"a": "b"},
   781  				},
   782  			},
   783  			labels: map[string]string{"c": "d"},
   784  			expectObj: &v1.Pod{
   785  				ObjectMeta: metav1.ObjectMeta{
   786  					Labels: map[string]string{"a": "b", "c": "d"},
   787  				},
   788  			},
   789  			expectMsg: MsgLabeled,
   790  		},
   791  		{
   792  			obj: &v1.Pod{
   793  				ObjectMeta: metav1.ObjectMeta{
   794  					Labels: map[string]string{"a": "b"},
   795  				},
   796  			},
   797  			labels:          map[string]string{"c": "d"},
   798  			resourceVersion: "2",
   799  			expectObj: &v1.Pod{
   800  				ObjectMeta: metav1.ObjectMeta{
   801  					Labels:          map[string]string{"a": "b", "c": "d"},
   802  					ResourceVersion: "2",
   803  				},
   804  			},
   805  			expectMsg: MsgLabeled,
   806  		},
   807  		{
   808  			obj: &v1.Pod{
   809  				ObjectMeta: metav1.ObjectMeta{
   810  					Labels: map[string]string{"a": "b"},
   811  				},
   812  			},
   813  			labels: map[string]string{},
   814  			remove: []string{"a"},
   815  			expectObj: &v1.Pod{
   816  				ObjectMeta: metav1.ObjectMeta{
   817  					Labels: map[string]string{},
   818  				},
   819  			},
   820  			expectMsg: MsgUnLabeled,
   821  		},
   822  		{
   823  			obj: &v1.Pod{
   824  				ObjectMeta: metav1.ObjectMeta{
   825  					Labels: map[string]string{"a": "b", "c": "d"},
   826  				},
   827  			},
   828  			labels: map[string]string{"e": "f"},
   829  			remove: []string{"a"},
   830  			expectObj: &v1.Pod{
   831  				ObjectMeta: metav1.ObjectMeta{
   832  					Labels: map[string]string{
   833  						"c": "d",
   834  						"e": "f",
   835  					},
   836  				},
   837  			},
   838  			expectMsg: MsgLabeled,
   839  		},
   840  		{
   841  			obj: &v1.Pod{
   842  				ObjectMeta: metav1.ObjectMeta{
   843  					Labels: map[string]string{"status": "unhealthy"},
   844  				},
   845  			},
   846  			labels:    map[string]string{"status": "healthy"},
   847  			overwrite: true,
   848  			expectObj: &v1.Pod{
   849  				ObjectMeta: metav1.ObjectMeta{
   850  					Labels: map[string]string{
   851  						"status": "healthy",
   852  					},
   853  				},
   854  			},
   855  			expectMsg: MsgLabeled,
   856  		},
   857  		{
   858  			obj: &v1.Pod{
   859  				ObjectMeta: metav1.ObjectMeta{
   860  					Labels: map[string]string{"status": "unhealthy"},
   861  				},
   862  			},
   863  			labels:    map[string]string{"status": "healthy"},
   864  			overwrite: false,
   865  			expectObj: &v1.Pod{
   866  				ObjectMeta: metav1.ObjectMeta{
   867  					Labels: map[string]string{
   868  						"status": "unhealthy",
   869  					},
   870  				},
   871  			},
   872  			expectMsg: MsgNotLabeled,
   873  			expectErr: true,
   874  		},
   875  	}
   876  
   877  	for _, test := range tests {
   878  		oldData, err := json.Marshal(test.obj)
   879  		if err != nil {
   880  			t.Errorf("unexpected error: %v %v", err, test)
   881  		}
   882  
   883  		err = labelFunc(test.obj, test.overwrite, test.resourceVersion, test.labels, test.remove)
   884  		if test.expectErr && err == nil {
   885  			t.Errorf("unexpected non-error: %v", test)
   886  			continue
   887  		}
   888  		if !test.expectErr && err != nil {
   889  			t.Errorf("unexpected error: %v %v", err, test)
   890  		}
   891  
   892  		newObj, err := json.Marshal(test.obj)
   893  		if err != nil {
   894  			t.Errorf("unexpected error: %v %v", err, test)
   895  		}
   896  
   897  		dataChangeMsg := updateDataChangeMsg(oldData, newObj, test.overwrite)
   898  		if dataChangeMsg != test.expectMsg {
   899  			t.Errorf("unexpected dataChangeMsg: %v != %v, %v", dataChangeMsg, test.expectMsg, test)
   900  		}
   901  	}
   902  }
   903  

View as plain text