set_serviceaccount_test.go 13 KB

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