...

Source file src/k8s.io/kubectl/pkg/cmd/set/set_serviceaccount_test.go

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

     1  /*
     2  Copyright 2017 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 set
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	appsv1beta1 "k8s.io/api/apps/v1beta1"
    28  	appsv1beta2 "k8s.io/api/apps/v1beta2"
    29  	batchv1 "k8s.io/api/batch/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/cli-runtime/pkg/genericclioptions"
    36  	"k8s.io/cli-runtime/pkg/genericiooptions"
    37  	"k8s.io/cli-runtime/pkg/resource"
    38  	restclient "k8s.io/client-go/rest"
    39  	"k8s.io/client-go/rest/fake"
    40  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    41  	"k8s.io/kubectl/pkg/scheme"
    42  )
    43  
    44  const (
    45  	serviceAccount                 = "serviceaccount1"
    46  	serviceAccountMissingErrString = "serviceaccount is required"
    47  	resourceMissingErrString       = `You must provide one or more resources by argument or filename.
    48  Example resource specifications include:
    49     '-f rsrc.yaml'
    50     '--filename=rsrc.json'
    51     '<resource> <name>'
    52     '<resource>'`
    53  )
    54  
    55  func TestSetServiceAccountLocal(t *testing.T) {
    56  	inputs := []struct {
    57  		yaml     string
    58  		apiGroup string
    59  	}{
    60  		{yaml: "../../../testdata/set/replication.yaml", apiGroup: ""},
    61  		{yaml: "../../../testdata/set/daemon.yaml", apiGroup: "extensions"},
    62  		{yaml: "../../../testdata/set/redis-slave.yaml", apiGroup: "extensions"},
    63  		{yaml: "../../../testdata/set/job.yaml", apiGroup: "batch"},
    64  		{yaml: "../../../testdata/set/deployment.yaml", apiGroup: "extensions"},
    65  	}
    66  
    67  	for i, input := range inputs {
    68  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
    69  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
    70  			defer tf.Cleanup()
    71  
    72  			tf.Client = &fake.RESTClient{
    73  				GroupVersion: schema.GroupVersion{Version: "v1"},
    74  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
    75  					t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
    76  					return nil, nil
    77  				}),
    78  			}
    79  
    80  			outputFormat := "yaml"
    81  
    82  			streams, _, buf, _ := genericiooptions.NewTestIOStreams()
    83  			cmd := NewCmdServiceAccount(tf, streams)
    84  			cmd.Flags().Set("output", outputFormat)
    85  			cmd.Flags().Set("local", "true")
    86  			saConfig := SetServiceAccountOptions{
    87  				PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
    88  				fileNameOptions: resource.FilenameOptions{
    89  					Filenames: []string{input.yaml}},
    90  				local:     true,
    91  				IOStreams: streams,
    92  			}
    93  			err := saConfig.Complete(tf, cmd, []string{serviceAccount})
    94  			assert.NoError(t, err)
    95  			err = saConfig.Run()
    96  			assert.NoError(t, err)
    97  			assert.Contains(t, buf.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml))
    98  		})
    99  	}
   100  }
   101  
   102  func TestSetServiceAccountMultiLocal(t *testing.T) {
   103  	tf := cmdtesting.NewTestFactory().WithNamespace("test")
   104  	defer tf.Cleanup()
   105  
   106  	tf.Client = &fake.RESTClient{
   107  		GroupVersion:         schema.GroupVersion{Version: ""},
   108  		NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   109  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   110  			t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   111  			return nil, nil
   112  		}),
   113  	}
   114  	tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Version: ""}}}
   115  
   116  	outputFormat := "name"
   117  
   118  	streams, _, buf, _ := genericiooptions.NewTestIOStreams()
   119  	cmd := NewCmdServiceAccount(tf, streams)
   120  	cmd.Flags().Set("output", outputFormat)
   121  	cmd.Flags().Set("local", "true")
   122  	opts := SetServiceAccountOptions{
   123  		PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
   124  		fileNameOptions: resource.FilenameOptions{
   125  			Filenames: []string{"../../../testdata/set/multi-resource-yaml.yaml"}},
   126  		local:     true,
   127  		IOStreams: streams,
   128  	}
   129  
   130  	err := opts.Complete(tf, cmd, []string{serviceAccount})
   131  	if err == nil {
   132  		err = opts.Run()
   133  	}
   134  	if err != nil {
   135  		t.Fatalf("unexpected error: %v", err)
   136  	}
   137  	expectedOut := "replicationcontroller/first-rc\nreplicationcontroller/second-rc\n"
   138  	if buf.String() != expectedOut {
   139  		t.Errorf("expected out:\n%s\nbut got:\n%s", expectedOut, buf.String())
   140  	}
   141  }
   142  
   143  func TestSetServiceAccountRemote(t *testing.T) {
   144  	inputs := []struct {
   145  		object       runtime.Object
   146  		groupVersion schema.GroupVersion
   147  		path         string
   148  		args         []string
   149  	}{
   150  		{
   151  			object: &extensionsv1beta1.ReplicaSet{
   152  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   153  			},
   154  			groupVersion: extensionsv1beta1.SchemeGroupVersion,
   155  			path:         "/namespaces/test/replicasets/nginx",
   156  			args:         []string{"replicaset", "nginx", serviceAccount},
   157  		},
   158  		{
   159  			object: &appsv1beta2.ReplicaSet{
   160  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   161  				Spec: appsv1beta2.ReplicaSetSpec{
   162  					Template: corev1.PodTemplateSpec{
   163  						Spec: corev1.PodSpec{
   164  							Containers: []corev1.Container{
   165  								{
   166  									Name:  "nginx",
   167  									Image: "nginx",
   168  								},
   169  							},
   170  						},
   171  					},
   172  				},
   173  			},
   174  			groupVersion: appsv1beta2.SchemeGroupVersion,
   175  			path:         "/namespaces/test/replicasets/nginx",
   176  			args:         []string{"replicaset", "nginx", serviceAccount},
   177  		},
   178  		{
   179  			object: &appsv1.ReplicaSet{
   180  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   181  				Spec: appsv1.ReplicaSetSpec{
   182  					Template: corev1.PodTemplateSpec{
   183  						Spec: corev1.PodSpec{
   184  							Containers: []corev1.Container{
   185  								{
   186  									Name:  "nginx",
   187  									Image: "nginx",
   188  								},
   189  							},
   190  						},
   191  					},
   192  				},
   193  			},
   194  			groupVersion: appsv1.SchemeGroupVersion,
   195  			path:         "/namespaces/test/replicasets/nginx",
   196  			args:         []string{"replicaset", "nginx", serviceAccount},
   197  		},
   198  		{
   199  			object: &extensionsv1beta1.DaemonSet{
   200  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   201  			},
   202  			groupVersion: extensionsv1beta1.SchemeGroupVersion,
   203  			path:         "/namespaces/test/daemonsets/nginx",
   204  			args:         []string{"daemonset", "nginx", serviceAccount},
   205  		},
   206  		{
   207  			object: &appsv1beta2.DaemonSet{
   208  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   209  			},
   210  			groupVersion: appsv1beta2.SchemeGroupVersion,
   211  			path:         "/namespaces/test/daemonsets/nginx",
   212  			args:         []string{"daemonset", "nginx", serviceAccount},
   213  		},
   214  		{
   215  			object: &appsv1.DaemonSet{
   216  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   217  			},
   218  			groupVersion: appsv1.SchemeGroupVersion,
   219  			path:         "/namespaces/test/daemonsets/nginx",
   220  			args:         []string{"daemonset", "nginx", serviceAccount},
   221  		},
   222  		{
   223  			object: &extensionsv1beta1.Deployment{
   224  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   225  			},
   226  			groupVersion: extensionsv1beta1.SchemeGroupVersion,
   227  			path:         "/namespaces/test/deployments/nginx",
   228  			args:         []string{"deployment", "nginx", serviceAccount},
   229  		},
   230  		{
   231  			object: &appsv1beta1.Deployment{
   232  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   233  			},
   234  			groupVersion: appsv1beta1.SchemeGroupVersion,
   235  			path:         "/namespaces/test/deployments/nginx",
   236  			args:         []string{"deployment", "nginx", serviceAccount},
   237  		},
   238  		{
   239  			object: &appsv1beta2.Deployment{
   240  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   241  			},
   242  			groupVersion: appsv1beta2.SchemeGroupVersion,
   243  			path:         "/namespaces/test/deployments/nginx",
   244  			args:         []string{"deployment", "nginx", serviceAccount},
   245  		},
   246  		{
   247  			object: &appsv1.Deployment{
   248  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   249  				Spec: appsv1.DeploymentSpec{
   250  					Template: corev1.PodTemplateSpec{
   251  						Spec: corev1.PodSpec{
   252  							Containers: []corev1.Container{
   253  								{
   254  									Name:  "nginx",
   255  									Image: "nginx",
   256  								},
   257  							},
   258  						},
   259  					},
   260  				},
   261  			},
   262  			groupVersion: appsv1.SchemeGroupVersion,
   263  			path:         "/namespaces/test/deployments/nginx",
   264  			args:         []string{"deployment", "nginx", serviceAccount},
   265  		},
   266  		{
   267  			object: &appsv1beta1.StatefulSet{
   268  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   269  			},
   270  			groupVersion: appsv1beta1.SchemeGroupVersion,
   271  			path:         "/namespaces/test/statefulsets/nginx",
   272  			args:         []string{"statefulset", "nginx", serviceAccount},
   273  		},
   274  		{
   275  			object: &appsv1beta2.StatefulSet{
   276  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   277  			},
   278  			groupVersion: appsv1beta2.SchemeGroupVersion,
   279  			path:         "/namespaces/test/statefulsets/nginx",
   280  			args:         []string{"statefulset", "nginx", serviceAccount},
   281  		},
   282  		{
   283  			object: &appsv1.StatefulSet{
   284  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   285  				Spec: appsv1.StatefulSetSpec{
   286  					Template: corev1.PodTemplateSpec{
   287  						Spec: corev1.PodSpec{
   288  							Containers: []corev1.Container{
   289  								{
   290  									Name:  "nginx",
   291  									Image: "nginx",
   292  								},
   293  							},
   294  						},
   295  					},
   296  				},
   297  			},
   298  			groupVersion: appsv1.SchemeGroupVersion,
   299  			path:         "/namespaces/test/statefulsets/nginx",
   300  			args:         []string{"statefulset", "nginx", serviceAccount},
   301  		},
   302  		{
   303  			object: &batchv1.Job{
   304  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   305  			},
   306  			groupVersion: batchv1.SchemeGroupVersion,
   307  			path:         "/namespaces/test/jobs/nginx",
   308  			args:         []string{"job", "nginx", serviceAccount},
   309  		},
   310  		{
   311  			object: &corev1.ReplicationController{
   312  				ObjectMeta: metav1.ObjectMeta{Name: "nginx"},
   313  			},
   314  			groupVersion: corev1.SchemeGroupVersion,
   315  			path:         "/namespaces/test/replicationcontrollers/nginx",
   316  			args:         []string{"replicationcontroller", "nginx", serviceAccount},
   317  		},
   318  	}
   319  	for i, input := range inputs {
   320  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   321  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   322  			defer tf.Cleanup()
   323  
   324  			tf.Client = &fake.RESTClient{
   325  				GroupVersion:         input.groupVersion,
   326  				NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
   327  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   328  					switch p, m := req.URL.Path, req.Method; {
   329  					case p == input.path && m == http.MethodGet:
   330  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
   331  					case p == input.path && m == http.MethodPatch:
   332  						stream, err := req.GetBody()
   333  						if err != nil {
   334  							return nil, err
   335  						}
   336  						bytes, err := io.ReadAll(stream)
   337  						if err != nil {
   338  							return nil, err
   339  						}
   340  						assert.Contains(t, string(bytes), `"serviceAccountName":`+`"`+serviceAccount+`"`, fmt.Sprintf("serviceaccount not updated for %#v", input.object))
   341  						return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
   342  					default:
   343  						t.Errorf("%s: unexpected request: %s %#v\n%#v", "serviceaccount", req.Method, req.URL, req)
   344  						return nil, fmt.Errorf("unexpected request")
   345  					}
   346  				}),
   347  			}
   348  
   349  			outputFormat := "yaml"
   350  
   351  			streams := genericiooptions.NewTestIOStreamsDiscard()
   352  			cmd := NewCmdServiceAccount(tf, streams)
   353  			cmd.Flags().Set("output", outputFormat)
   354  			saConfig := SetServiceAccountOptions{
   355  				PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
   356  
   357  				local:     false,
   358  				IOStreams: streams,
   359  			}
   360  			err := saConfig.Complete(tf, cmd, input.args)
   361  			assert.NoError(t, err)
   362  			err = saConfig.Run()
   363  			assert.NoError(t, err)
   364  		})
   365  	}
   366  }
   367  
   368  func TestServiceAccountValidation(t *testing.T) {
   369  	inputs := []struct {
   370  		name        string
   371  		args        []string
   372  		errorString string
   373  	}{
   374  		{name: "test service account missing", args: []string{}, errorString: serviceAccountMissingErrString},
   375  		{name: "test service account resource missing", args: []string{serviceAccount}, errorString: resourceMissingErrString},
   376  	}
   377  	for _, input := range inputs {
   378  		t.Run(input.name, func(t *testing.T) {
   379  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   380  			defer tf.Cleanup()
   381  
   382  			tf.Client = &fake.RESTClient{
   383  				GroupVersion: schema.GroupVersion{Version: "v1"},
   384  				Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   385  					t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
   386  					return nil, nil
   387  				}),
   388  			}
   389  
   390  			outputFormat := ""
   391  
   392  			streams := genericiooptions.NewTestIOStreamsDiscard()
   393  			cmd := NewCmdServiceAccount(tf, streams)
   394  
   395  			saConfig := &SetServiceAccountOptions{
   396  				PrintFlags: genericclioptions.NewPrintFlags("").WithDefaultOutput(outputFormat).WithTypeSetter(scheme.Scheme),
   397  				IOStreams:  streams,
   398  			}
   399  			err := saConfig.Complete(tf, cmd, input.args)
   400  			assert.EqualError(t, err, input.errorString)
   401  		})
   402  	}
   403  }
   404  
   405  func objBody(obj runtime.Object) io.ReadCloser {
   406  	return cmdtesting.BytesBody([]byte(runtime.EncodeOrDie(scheme.DefaultJSONEncoder(), obj)))
   407  }
   408  

View as plain text