...

Source file src/k8s.io/kubectl/pkg/cmd/run/run_test.go

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

     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 run
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"os"
    25  	"reflect"
    26  	"strconv"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/spf13/cobra"
    31  
    32  	corev1 "k8s.io/api/core/v1"
    33  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	"k8s.io/apimachinery/pkg/util/intstr"
    37  	"k8s.io/cli-runtime/pkg/genericclioptions"
    38  	"k8s.io/cli-runtime/pkg/genericiooptions"
    39  	restclient "k8s.io/client-go/rest"
    40  	"k8s.io/client-go/rest/fake"
    41  	"k8s.io/kubectl/pkg/cmd/delete"
    42  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    43  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    44  	"k8s.io/kubectl/pkg/scheme"
    45  	"k8s.io/kubectl/pkg/util/i18n"
    46  )
    47  
    48  func TestGetRestartPolicy(t *testing.T) {
    49  	tests := []struct {
    50  		input       string
    51  		interactive bool
    52  		expected    corev1.RestartPolicy
    53  		expectErr   bool
    54  	}{
    55  		{
    56  			input:    "",
    57  			expected: corev1.RestartPolicyAlways,
    58  		},
    59  		{
    60  			input:       "",
    61  			interactive: true,
    62  			expected:    corev1.RestartPolicyOnFailure,
    63  		},
    64  		{
    65  			input:       string(corev1.RestartPolicyAlways),
    66  			interactive: true,
    67  			expected:    corev1.RestartPolicyAlways,
    68  		},
    69  		{
    70  			input:       string(corev1.RestartPolicyNever),
    71  			interactive: true,
    72  			expected:    corev1.RestartPolicyNever,
    73  		},
    74  		{
    75  			input:    string(corev1.RestartPolicyAlways),
    76  			expected: corev1.RestartPolicyAlways,
    77  		},
    78  		{
    79  			input:    string(corev1.RestartPolicyNever),
    80  			expected: corev1.RestartPolicyNever,
    81  		},
    82  		{
    83  			input:     "foo",
    84  			expectErr: true,
    85  		},
    86  	}
    87  	for _, test := range tests {
    88  		cmd := &cobra.Command{}
    89  		cmd.Flags().String("restart", "", i18n.T("dummy restart flag)"))
    90  		cmd.Flags().Lookup("restart").Value.Set(test.input)
    91  		policy, err := getRestartPolicy(cmd, test.interactive)
    92  		if test.expectErr && err == nil {
    93  			t.Error("unexpected non-error")
    94  		}
    95  		if !test.expectErr && err != nil {
    96  			t.Errorf("unexpected error: %v", err)
    97  		}
    98  		if !test.expectErr && policy != test.expected {
    99  			t.Errorf("expected: %s, saw: %s (%s:%v)", test.expected, policy, test.input, test.interactive)
   100  		}
   101  	}
   102  }
   103  
   104  func TestGetEnv(t *testing.T) {
   105  	test := struct {
   106  		input    []string
   107  		expected []string
   108  	}{
   109  		input:    []string{"a=b", "c=d"},
   110  		expected: []string{"a=b", "c=d"},
   111  	}
   112  	cmd := &cobra.Command{}
   113  	cmd.Flags().StringSlice("env", test.input, "")
   114  
   115  	envStrings := cmdutil.GetFlagStringSlice(cmd, "env")
   116  	if len(envStrings) != 2 || !reflect.DeepEqual(envStrings, test.expected) {
   117  		t.Errorf("expected: %s, saw: %s", test.expected, envStrings)
   118  	}
   119  }
   120  
   121  func TestRunArgsFollowDashRules(t *testing.T) {
   122  	one := int32(1)
   123  	rc := &corev1.ReplicationController{
   124  		ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
   125  		Spec: corev1.ReplicationControllerSpec{
   126  			Replicas: &one,
   127  		},
   128  	}
   129  
   130  	tests := []struct {
   131  		args          []string
   132  		argsLenAtDash int
   133  		expectError   bool
   134  		name          string
   135  	}{
   136  		{
   137  			args:          []string{},
   138  			argsLenAtDash: -1,
   139  			expectError:   true,
   140  			name:          "empty",
   141  		},
   142  		{
   143  			args:          []string{"foo"},
   144  			argsLenAtDash: -1,
   145  			expectError:   false,
   146  			name:          "no cmd",
   147  		},
   148  		{
   149  			args:          []string{"foo", "sleep"},
   150  			argsLenAtDash: -1,
   151  			expectError:   false,
   152  			name:          "cmd no dash",
   153  		},
   154  		{
   155  			args:          []string{"foo", "sleep"},
   156  			argsLenAtDash: 1,
   157  			expectError:   false,
   158  			name:          "cmd has dash",
   159  		},
   160  		{
   161  			args:          []string{"foo", "sleep"},
   162  			argsLenAtDash: 0,
   163  			expectError:   true,
   164  			name:          "no name",
   165  		},
   166  	}
   167  	for _, test := range tests {
   168  		t.Run(test.name, func(t *testing.T) {
   169  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   170  			defer tf.Cleanup()
   171  
   172  			codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   173  			ns := scheme.Codecs.WithoutConversion()
   174  
   175  			tf.Client = &fake.RESTClient{
   176  				GroupVersion:         corev1.SchemeGroupVersion,
   177  				NegotiatedSerializer: ns,
   178  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   179  					if req.URL.Path == "/namespaces/test/pods" {
   180  						return &http.Response{StatusCode: http.StatusCreated, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, rc)}, nil
   181  					}
   182  					return &http.Response{
   183  						StatusCode: http.StatusOK,
   184  						Body:       io.NopCloser(bytes.NewBuffer([]byte("{}"))),
   185  					}, nil
   186  				}),
   187  			}
   188  
   189  			tf.ClientConfigVal = &restclient.Config{}
   190  
   191  			cmd := NewCmdRun(tf, genericiooptions.NewTestIOStreamsDiscard())
   192  			cmd.Flags().Set("image", "nginx")
   193  
   194  			printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme)
   195  			printer, err := printFlags.ToPrinter()
   196  			if err != nil {
   197  				t.Errorf("unexpected error: %v", err)
   198  				return
   199  			}
   200  
   201  			deleteFlags := delete.NewDeleteFlags("to use to replace the resource.")
   202  			deleteOptions, err := deleteFlags.ToOptions(nil, genericiooptions.NewTestIOStreamsDiscard())
   203  			if err != nil {
   204  				t.Errorf("unexpected error: %v", err)
   205  				return
   206  			}
   207  			opts := &RunOptions{
   208  				PrintFlags:    printFlags,
   209  				DeleteOptions: deleteOptions,
   210  
   211  				IOStreams: genericiooptions.NewTestIOStreamsDiscard(),
   212  
   213  				Image: "nginx",
   214  
   215  				PrintObj: func(obj runtime.Object) error {
   216  					return printer.PrintObj(obj, os.Stdout)
   217  				},
   218  				Recorder: genericclioptions.NoopRecorder{},
   219  
   220  				ArgsLenAtDash: test.argsLenAtDash,
   221  			}
   222  
   223  			err = opts.Run(tf, cmd, test.args)
   224  			if test.expectError && err == nil {
   225  				t.Errorf("unexpected non-error (%s)", test.name)
   226  			}
   227  			if !test.expectError && err != nil {
   228  				t.Errorf("unexpected error: %v (%s)", err, test.name)
   229  			}
   230  		})
   231  	}
   232  }
   233  
   234  func TestGenerateService(t *testing.T) {
   235  	tests := []struct {
   236  		name       string
   237  		port       string
   238  		args       []string
   239  		params     map[string]interface{}
   240  		expectErr  bool
   241  		service    corev1.Service
   242  		expectPOST bool
   243  	}{
   244  		{
   245  			name: "basic",
   246  			port: "80",
   247  			args: []string{"foo"},
   248  			params: map[string]interface{}{
   249  				"name": "foo",
   250  			},
   251  			expectErr: false,
   252  			service: corev1.Service{
   253  				TypeMeta: metav1.TypeMeta{
   254  					Kind:       "Service",
   255  					APIVersion: "v1",
   256  				},
   257  				ObjectMeta: metav1.ObjectMeta{
   258  					Name: "foo",
   259  				},
   260  				Spec: corev1.ServiceSpec{
   261  					Ports: []corev1.ServicePort{
   262  						{
   263  							Port:       80,
   264  							Protocol:   "TCP",
   265  							TargetPort: intstr.FromInt32(80),
   266  						},
   267  					},
   268  					Selector: map[string]string{
   269  						"run": "foo",
   270  					},
   271  				},
   272  			},
   273  			expectPOST: true,
   274  		},
   275  		{
   276  			name: "custom labels",
   277  			port: "80",
   278  			args: []string{"foo"},
   279  			params: map[string]interface{}{
   280  				"name":   "foo",
   281  				"labels": "app=bar",
   282  			},
   283  			expectErr: false,
   284  			service: corev1.Service{
   285  				TypeMeta: metav1.TypeMeta{
   286  					Kind:       "Service",
   287  					APIVersion: "v1",
   288  				},
   289  				ObjectMeta: metav1.ObjectMeta{
   290  					Name:   "foo",
   291  					Labels: map[string]string{"app": "bar"},
   292  				},
   293  				Spec: corev1.ServiceSpec{
   294  					Ports: []corev1.ServicePort{
   295  						{
   296  							Port:       80,
   297  							Protocol:   "TCP",
   298  							TargetPort: intstr.FromInt32(80),
   299  						},
   300  					},
   301  					Selector: map[string]string{
   302  						"app": "bar",
   303  					},
   304  				},
   305  			},
   306  			expectPOST: true,
   307  		},
   308  		{
   309  			expectErr:  true,
   310  			name:       "missing port",
   311  			expectPOST: false,
   312  		},
   313  		{
   314  			name: "dry-run",
   315  			port: "80",
   316  			args: []string{"foo"},
   317  			params: map[string]interface{}{
   318  				"name": "foo",
   319  			},
   320  			expectErr:  false,
   321  			expectPOST: false,
   322  		},
   323  	}
   324  	for _, test := range tests {
   325  		t.Run(test.name, func(t *testing.T) {
   326  			sawPOST := false
   327  			tf := cmdtesting.NewTestFactory()
   328  			defer tf.Cleanup()
   329  
   330  			codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   331  			ns := scheme.Codecs.WithoutConversion()
   332  
   333  			tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   334  			tf.Client = &fake.RESTClient{
   335  				GroupVersion:         corev1.SchemeGroupVersion,
   336  				NegotiatedSerializer: ns,
   337  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   338  					switch p, m := req.URL.Path, req.Method; {
   339  					case test.expectPOST && m == "POST" && p == "/namespaces/test/services":
   340  						sawPOST = true
   341  						body := cmdtesting.ObjBody(codec, &test.service)
   342  						data, err := io.ReadAll(req.Body)
   343  						if err != nil {
   344  							t.Fatalf("unexpected error: %v", err)
   345  						}
   346  						defer req.Body.Close()
   347  						svc := &corev1.Service{}
   348  						if err := runtime.DecodeInto(codec, data, svc); err != nil {
   349  							t.Fatalf("unexpected error: %v", err)
   350  						}
   351  						// Copy things that are defaulted by the system
   352  						test.service.Annotations = svc.Annotations
   353  
   354  						if !apiequality.Semantic.DeepEqual(&test.service, svc) {
   355  							t.Errorf("expected:\n%v\nsaw:\n%v\n", &test.service, svc)
   356  						}
   357  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
   358  					default:
   359  						t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
   360  						return nil, fmt.Errorf("unexpected request")
   361  					}
   362  				}),
   363  			}
   364  
   365  			printFlags := genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme)
   366  			printer, err := printFlags.ToPrinter()
   367  			if err != nil {
   368  				t.Errorf("unexpected error: %v", err)
   369  				return
   370  			}
   371  
   372  			ioStreams, _, buff, _ := genericiooptions.NewTestIOStreams()
   373  			deleteFlags := delete.NewDeleteFlags("to use to replace the resource.")
   374  			deleteOptions, err := deleteFlags.ToOptions(nil, genericiooptions.NewTestIOStreamsDiscard())
   375  			if err != nil {
   376  				t.Errorf("unexpected error: %v", err)
   377  				return
   378  			}
   379  			opts := &RunOptions{
   380  				PrintFlags:    printFlags,
   381  				DeleteOptions: deleteOptions,
   382  
   383  				IOStreams: ioStreams,
   384  
   385  				Port:     test.port,
   386  				Recorder: genericclioptions.NoopRecorder{},
   387  
   388  				PrintObj: func(obj runtime.Object) error {
   389  					return printer.PrintObj(obj, buff)
   390  				},
   391  
   392  				Namespace: "test",
   393  			}
   394  
   395  			cmd := &cobra.Command{}
   396  			cmd.Flags().Bool(cmdutil.ApplyAnnotationsFlag, false, "")
   397  			cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.")
   398  			addRunFlags(cmd, opts)
   399  
   400  			if !test.expectPOST {
   401  				opts.DryRunStrategy = cmdutil.DryRunClient
   402  			}
   403  
   404  			if len(test.port) > 0 {
   405  				cmd.Flags().Set("port", test.port)
   406  				test.params["port"] = test.port
   407  			}
   408  
   409  			_, err = opts.generateService(tf, cmd, test.params)
   410  			if test.expectErr {
   411  				if err == nil {
   412  					t.Error("unexpected non-error")
   413  				}
   414  				return
   415  			}
   416  			if err != nil {
   417  				t.Errorf("unexpected error: %v", err)
   418  			}
   419  			if test.expectPOST != sawPOST {
   420  				t.Errorf("expectPost: %v, sawPost: %v", test.expectPOST, sawPOST)
   421  			}
   422  		})
   423  	}
   424  }
   425  
   426  func TestRunValidations(t *testing.T) {
   427  	tests := []struct {
   428  		name        string
   429  		args        []string
   430  		flags       map[string]string
   431  		expectedErr string
   432  	}{
   433  		{
   434  			name:        "test missing name error",
   435  			expectedErr: "NAME is required",
   436  		},
   437  		{
   438  			name:        "test missing --image error",
   439  			args:        []string{"test"},
   440  			expectedErr: "--image is required",
   441  		},
   442  		{
   443  			name: "test invalid image name error",
   444  			args: []string{"test"},
   445  			flags: map[string]string{
   446  				"image": "#",
   447  			},
   448  			expectedErr: "Invalid image name",
   449  		},
   450  		{
   451  			name: "test rm errors when used on non-attached containers",
   452  			args: []string{"test"},
   453  			flags: map[string]string{
   454  				"image": "busybox",
   455  				"rm":    "true",
   456  			},
   457  			expectedErr: "rm should only be used for attached containers",
   458  		},
   459  		{
   460  			name: "test error on attached containers options",
   461  			args: []string{"test"},
   462  			flags: map[string]string{
   463  				"image":   "busybox",
   464  				"attach":  "true",
   465  				"dry-run": "client",
   466  			},
   467  			expectedErr: "can't be used with attached containers options",
   468  		},
   469  		{
   470  			name: "test error on attached containers options, with value from stdin",
   471  			args: []string{"test"},
   472  			flags: map[string]string{
   473  				"image":   "busybox",
   474  				"stdin":   "true",
   475  				"dry-run": "client",
   476  			},
   477  			expectedErr: "can't be used with attached containers options",
   478  		},
   479  		{
   480  			name: "test error on attached containers options, with value from stdin and tty",
   481  			args: []string{"test"},
   482  			flags: map[string]string{
   483  				"image":   "busybox",
   484  				"tty":     "true",
   485  				"stdin":   "true",
   486  				"dry-run": "client",
   487  			},
   488  			expectedErr: "can't be used with attached containers options",
   489  		},
   490  		{
   491  			name: "test error when tty=true and no stdin provided",
   492  			args: []string{"test"},
   493  			flags: map[string]string{
   494  				"image": "busybox",
   495  				"tty":   "true",
   496  			},
   497  			expectedErr: "stdin is required for containers with -t/--tty",
   498  		},
   499  		{
   500  			name: "test invalid override type error",
   501  			args: []string{"test"},
   502  			flags: map[string]string{
   503  				"image":         "busybox",
   504  				"overrides":     "{}",
   505  				"override-type": "foo",
   506  			},
   507  			expectedErr: "invalid override type: foo",
   508  		},
   509  	}
   510  	for _, test := range tests {
   511  		t.Run(test.name, func(t *testing.T) {
   512  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   513  			defer tf.Cleanup()
   514  
   515  			_, _, codec := cmdtesting.NewExternalScheme()
   516  			ns := scheme.Codecs.WithoutConversion()
   517  			tf.Client = &fake.RESTClient{
   518  				NegotiatedSerializer: ns,
   519  				Resp:                 &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, cmdtesting.NewInternalType("", "", ""))},
   520  			}
   521  			tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
   522  
   523  			streams, _, _, bufErr := genericiooptions.NewTestIOStreams()
   524  			cmdutil.BehaviorOnFatal(func(str string, code int) {
   525  				bufErr.Write([]byte(str))
   526  			})
   527  
   528  			cmd := NewCmdRun(tf, streams)
   529  			for flagName, flagValue := range test.flags {
   530  				cmd.Flags().Set(flagName, flagValue)
   531  			}
   532  			cmd.Run(cmd, test.args)
   533  
   534  			var err error
   535  			if bufErr.Len() > 0 {
   536  				err = fmt.Errorf("%v", bufErr.String())
   537  			}
   538  			if err != nil && len(test.expectedErr) > 0 {
   539  				if !strings.Contains(err.Error(), test.expectedErr) {
   540  					t.Errorf("unexpected error: %v", err)
   541  				}
   542  			}
   543  		})
   544  	}
   545  
   546  }
   547  
   548  func TestExpose(t *testing.T) {
   549  	tests := []struct {
   550  		name      string
   551  		podName   string
   552  		imageName string
   553  		podLabels map[string]string
   554  		port      int
   555  	}{
   556  		{
   557  			name:      "test simple expose",
   558  			podName:   "test-pod",
   559  			imageName: "test-image",
   560  			podLabels: map[string]string{"color": "red", "shape": "square"},
   561  			port:      1234,
   562  		},
   563  	}
   564  
   565  	for _, test := range tests {
   566  		t.Run(test.name, func(t *testing.T) {
   567  
   568  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   569  			defer tf.Cleanup()
   570  
   571  			codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   572  			ns := scheme.Codecs.WithoutConversion()
   573  			tf.Client = &fake.RESTClient{
   574  				NegotiatedSerializer: ns,
   575  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   576  					t.Logf("path: %v, method: %v", req.URL.Path, req.Method)
   577  					switch p, m := req.URL.Path, req.Method; {
   578  					case m == "POST" && p == "/namespaces/test/pods":
   579  						pod := &corev1.Pod{}
   580  						body := cmdtesting.ObjBody(codec, pod)
   581  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
   582  					case m == "POST" && p == "/namespaces/test/services":
   583  						data, err := io.ReadAll(req.Body)
   584  						if err != nil {
   585  							t.Fatalf("unexpected error: %v", err)
   586  						}
   587  
   588  						service := &corev1.Service{}
   589  						if err := runtime.DecodeInto(codec, data, service); err != nil {
   590  							t.Fatalf("unexpected error: %v", err)
   591  						}
   592  
   593  						if service.ObjectMeta.Name != test.podName {
   594  							t.Errorf("Invalid name on service. Expected:%v, Actual:%v", test.podName, service.ObjectMeta.Name)
   595  						}
   596  
   597  						if !reflect.DeepEqual(service.Spec.Selector, test.podLabels) {
   598  							t.Errorf("Invalid selector on service. Expected:%v, Actual:%v", test.podLabels, service.Spec.Selector)
   599  						}
   600  
   601  						if len(service.Spec.Ports) != 1 && service.Spec.Ports[0].Port != int32(test.port) {
   602  							t.Errorf("Invalid port on service: %v", service.Spec.Ports)
   603  						}
   604  
   605  						body := cmdtesting.ObjBody(codec, service)
   606  
   607  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
   608  					default:
   609  						t.Errorf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   610  						return nil, fmt.Errorf("unexpected request")
   611  					}
   612  				}),
   613  			}
   614  
   615  			streams, _, _, bufErr := genericiooptions.NewTestIOStreams()
   616  			cmdutil.BehaviorOnFatal(func(str string, code int) {
   617  				bufErr.Write([]byte(str))
   618  			})
   619  
   620  			cmd := NewCmdRun(tf, streams)
   621  			cmd.Flags().Set("image", test.imageName)
   622  			cmd.Flags().Set("expose", "true")
   623  			cmd.Flags().Set("port", strconv.Itoa(test.port))
   624  
   625  			labels := []string{}
   626  			for k, v := range test.podLabels {
   627  				labels = append(labels, fmt.Sprintf("%s=%s", k, v))
   628  			}
   629  			cmd.Flags().Set("labels", strings.Join(labels, ","))
   630  
   631  			cmd.Run(cmd, []string{test.podName})
   632  
   633  			if bufErr.Len() > 0 {
   634  				err := fmt.Errorf("%v", bufErr.String())
   635  				if err != nil {
   636  					t.Errorf("unexpected error: %v", err)
   637  				}
   638  			}
   639  		})
   640  
   641  	}
   642  }
   643  
   644  func TestRunOverride(t *testing.T) {
   645  	tests := []struct {
   646  		name           string
   647  		overrides      string
   648  		overrideType   string
   649  		expectedOutput string
   650  	}{
   651  		{
   652  			name:         "run with merge override type should replace spec",
   653  			overrides:    `{"spec":{"containers":[{"name":"test","resources":{"limits":{"cpu":"200m"}}}]}}`,
   654  			overrideType: "merge",
   655  			expectedOutput: `apiVersion: v1
   656  kind: Pod
   657  metadata:
   658    creationTimestamp: null
   659    labels:
   660      run: test
   661    name: test
   662    namespace: ns
   663  spec:
   664    containers:
   665    - name: test
   666      resources:
   667        limits:
   668          cpu: 200m
   669    dnsPolicy: ClusterFirst
   670    restartPolicy: Always
   671  status: {}
   672  `,
   673  		},
   674  		{
   675  			name:         "run with no override type specified, should perform an RFC7396 JSON Merge Patch",
   676  			overrides:    `{"spec":{"containers":[{"name":"test","resources":{"limits":{"cpu":"200m"}}}]}}`,
   677  			overrideType: "",
   678  			expectedOutput: `apiVersion: v1
   679  kind: Pod
   680  metadata:
   681    creationTimestamp: null
   682    labels:
   683      run: test
   684    name: test
   685    namespace: ns
   686  spec:
   687    containers:
   688    - name: test
   689      resources:
   690        limits:
   691          cpu: 200m
   692    dnsPolicy: ClusterFirst
   693    restartPolicy: Always
   694  status: {}
   695  `,
   696  		},
   697  		{
   698  			name:         "run with strategic override type should merge spec, preserving container image",
   699  			overrides:    `{"spec":{"containers":[{"name":"test","resources":{"limits":{"cpu":"200m"}}}]}}`,
   700  			overrideType: "strategic",
   701  			expectedOutput: `apiVersion: v1
   702  kind: Pod
   703  metadata:
   704    creationTimestamp: null
   705    labels:
   706      run: test
   707    name: test
   708    namespace: ns
   709  spec:
   710    containers:
   711    - image: busybox
   712      name: test
   713      resources:
   714        limits:
   715          cpu: 200m
   716    dnsPolicy: ClusterFirst
   717    restartPolicy: Always
   718  status: {}
   719  `,
   720  		},
   721  		{
   722  			name: "run with json override type should perform add, replace, and remove operations",
   723  			overrides: `[
   724  				{"op": "add", "path": "/metadata/labels/foo", "value": "bar"},
   725  				{"op": "replace", "path": "/spec/containers/0/resources", "value": {"limits": {"cpu": "200m"}}},
   726  				{"op": "remove", "path": "/spec/dnsPolicy"}
   727  			]`,
   728  			overrideType: "json",
   729  			expectedOutput: `apiVersion: v1
   730  kind: Pod
   731  metadata:
   732    creationTimestamp: null
   733    labels:
   734      foo: bar
   735      run: test
   736    name: test
   737    namespace: ns
   738  spec:
   739    containers:
   740    - image: busybox
   741      name: test
   742      resources:
   743        limits:
   744          cpu: 200m
   745    restartPolicy: Always
   746  status: {}
   747  `,
   748  		},
   749  	}
   750  	for _, test := range tests {
   751  		t.Run(test.name, func(t *testing.T) {
   752  			tf := cmdtesting.NewTestFactory().WithNamespace("ns")
   753  			defer tf.Cleanup()
   754  
   755  			streams, _, bufOut, _ := genericiooptions.NewTestIOStreams()
   756  
   757  			cmd := NewCmdRun(tf, streams)
   758  			cmd.Flags().Set("dry-run", "client")
   759  			cmd.Flags().Set("output", "yaml")
   760  			cmd.Flags().Set("image", "busybox")
   761  			cmd.Flags().Set("overrides", test.overrides)
   762  			cmd.Flags().Set("override-type", test.overrideType)
   763  			cmd.Run(cmd, []string{"test"})
   764  
   765  			actualOutput := bufOut.String()
   766  			if actualOutput != test.expectedOutput {
   767  				t.Errorf("unexpected output.\n\nExpected:\n%v\nActual:\n%v", test.expectedOutput, actualOutput)
   768  			}
   769  		})
   770  	}
   771  }
   772  

View as plain text