Change:
<ins> /* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package set import ( "bytes" "fmt" "io" "io/ioutil" "net/http" "path" "testing" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/printers" ) const serviceAccount = "serviceaccount1" const serviceAccountMissingErrString = "serviceaccount is required" const resourceMissingErrString = `You must provide one or more resources by argument or filename. Example resource specifications include: '-f rsrc.yaml' '--filename=rsrc.json' '<resource> <name>' '<resource>'` func TestServiceAccountLocal(t *testing.T) { inputs := []struct { yaml     string apiGroup string }{ {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replication.yaml", apiGroup: api.GroupName}, {yaml: "../../../../test/fixtures/doc-yaml/admin/daemon.yaml", apiGroup: extensions.GroupName}, {yaml: "../../../../test/fixtures/doc-yaml/user-guide/replicaset/redis-slave.yaml", apiGroup: extensions.GroupName}, {yaml: "../../../../test/fixtures/doc-yaml/user-guide/job.yaml", apiGroup: batch.GroupName}, {yaml: "../../../../test/fixtures/doc-yaml/user-guide/deployment.yaml", apiGroup: extensions.GroupName}, {yaml: "../../../../examples/storage/minio/minio-distributed-statefulset.yaml", apiGroup: apps.GroupName}, } f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ APIRegistry: api.Registry, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#vn%#v", req.Method, req.URL, req) return nil, nil }), } tf.Namespace = "test" out := new(bytes.Buffer) cmd := NewCmdServiceAccount(f, out, out) cmd.SetOutput(out) cmd.Flags().Set("output", "yaml") cmd.Flags().Set("local", "true") for _, input := range inputs { testapi.Default = testapi.Groups[input.apiGroup] tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) saConfig := serviceAccountConfig{fileNameOptions: resource.FilenameOptions{ Filenames: []string{input.yaml}}, out:   out, local: true} err := saConfig.Complete(f, cmd, []string{serviceAccount}) assert.NoError(t, err) err = saConfig.Run() assert.NoError(t, err) assert.Contains(t, out.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml)) } } func TestServiceAccountRemote(t *testing.T) { inputs := []struct { object              runtime.Object apiPrefix, apiGroup string args                []string }{ { object: &extensions.ReplicaSet{ TypeMeta:   metav1.TypeMeta{Kind: "ReplicaSet", APIVersion: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, apiPrefix: "/apis", apiGroup: extensions.GroupName, args: []string{"replicaset", "nginx", serviceAccount}, }, { object: &extensions.DaemonSet{ TypeMeta:   metav1.TypeMeta{Kind: "DaemonSet", APIVersion: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, apiPrefix: "/apis", apiGroup: extensions.GroupName, args: []string{"daemonset", "nginx", serviceAccount}, }, { object: &api.ReplicationController{ TypeMeta:   metav1.TypeMeta{Kind: "ReplicationController", APIVersion: api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, apiPrefix: "/api", apiGroup: api.GroupName, args: []string{"replicationcontroller", "nginx", serviceAccount}}, { object: &extensions.Deployment{ TypeMeta:   metav1.TypeMeta{Kind: "Deployment", APIVersion: api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, apiPrefix: "/apis", apiGroup: extensions.GroupName, args: []string{"deployment", "nginx", serviceAccount}, }, { object: &batch.Job{ TypeMeta:   metav1.TypeMeta{Kind: "Job", APIVersion: api.Registry.GroupOrDie(batch.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, apiPrefix: "/apis", apiGroup: batch.GroupName, args: []string{"job", "nginx", serviceAccount}, }, { object: &apps.StatefulSet{ TypeMeta:   metav1.TypeMeta{Kind: "StatefulSet", APIVersion: api.Registry.GroupOrDie(apps.GroupName).GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{Name: "nginx"}, }, apiPrefix: "/apis", apiGroup: apps.GroupName, args: []string{"statefulset", "nginx", serviceAccount}, }, } for _, input := range inputs { groupVersion := api.Registry.GroupOrDie(input.apiGroup).GroupVersion testapi.Default = testapi.Groups[input.apiGroup] f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = printers.NewVersionedPrinter(&printers.YAMLPrinter{}, testapi.Default.Converter(), *testapi.Default.GroupVersion()) tf.Namespace = "test" tf.CategoryExpander = resource.LegacyCategoryExpander tf.Client = &fake.RESTClient{ APIRegistry:          api.Registry, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { resourcePath := testapi.Default.ResourcePath(input.args[0]+"s", tf.Namespace, input.args[1]) switch p, m := req.URL.Path, req.Method; { case p == resourcePath && m == http.MethodGet: return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil case p == resourcePath && m == http.MethodPatch: stream, err := req.GetBody() if err != nil { return nil, err } bytes, err := ioutil.ReadAll(stream) if err != nil { return nil, err } assert.Contains(t, string(bytes), `"serviceAccountName":`+`"`+serviceAccount+`"`, fmt.Sprintf("serviceaccount not updated for %#v", input.object)) return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, input.object)}, nil default: t.Errorf("%s: unexpected request: %s %#vn%#v", "serviceaccount", req.Method, req.URL, req) return nil, fmt.Errorf("unexpected request") } }), VersionedAPIPath: path.Join(input.apiPrefix, groupVersion.String()), GroupName:        input.apiGroup, } out := new(bytes.Buffer) cmd := NewCmdServiceAccount(f, out, out) cmd.SetOutput(out) cmd.Flags().Set("output", "yaml") saConfig := serviceAccountConfig{ out:   out, local: false} err := saConfig.Complete(f, cmd, input.args) assert.NoError(t, err) err = saConfig.Run() assert.NoError(t, err) } } func TestServiceAccountValidation(t *testing.T) { inputs := []struct { args        []string errorString string }{ {args: []string{}, errorString: serviceAccountMissingErrString}, {args: []string{serviceAccount}, errorString: resourceMissingErrString}, } for _, input := range inputs { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Client = &fake.RESTClient{ APIRegistry: api.Registry, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#vn%#v", req.Method, req.URL, req) return nil, nil }), } tf.Namespace = "test" out := bytes.NewBuffer([]byte{}) cmd := NewCmdServiceAccount(f, out, out) cmd.SetOutput(out) saConfig := &serviceAccountConfig{} err := saConfig.Complete(f, cmd, input.args) assert.EqualError(t, err, input.errorString) } } func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { return bytesBody([]byte(runtime.EncodeOrDie(codec, obj))) } func defaultHeader() http.Header { header := http.Header{} header.Set("Content-Type", runtime.ContentTypeJSON) return header } func bytesBody(bodyBytes []byte) io.ReadCloser { return ioutil.NopCloser(bytes.NewReader(bodyBytes)) } </ins>

Discussion:
Is this a BUG REPORT or FEATURE REQUEST? (choose one): FEATURE REQUEST Kubernetes version (use ): HEAD Now that RBAC is going to GA, using ServiceAccounts is getting more and more important. Still a lot of users run their apps/addons/whatever in the SA. In order for the admin to be able to change ServiceAccount easily after an application has been installed (which supposedly is crashlooping due to insufficient privs), a command could exist. I know I can do this: but for new users to Kubernetes, it's non-obvious. Proposed command: Tracking issue: WDYT? This would be a good way for a new contributor to get aquainted with the code by contributing this feature.
Is anyone working on this? If not I would like to take this up.
Nobody is working on this. I would be super happy if you could contribute this feature. Be aware of that there isn't consensus around this proposal yet (I proposed it yesterday, saturday, so people haven't had time to catch up on it) However, I do think that this is in line with , so feel free to start working on this if you want to ;) Also, don't hesitate to ask for help if you don't know how to get started and we'll help out! Thanks!
I'm in favor of this command. ?
Thanks, I will start working on this.
Is the SA on a Pod mutable or immutable? What about DaemonSet and StatefulSet? Do those have SA as well? Is there a way to do this so that it dynamically supports anything with a PodTemplate? Maybe this should be done as a subresource that can be dynamically discovered by kubectl using the discovery service.
Maybe helpful:
Pod is immutable. All the other objects (with the exception of stateful set) are mutable.
That pattern is the same for all the things that set aspects of a pod spec (except image, which is also mutable in pods)
Statefulsets are going to loosen their validation soon I expect.
This sounds good to me. just go ahead, I will help review this. Thanks ~
The outout of this command kubectl get rc/nginx-controller -o=yaml , has service acccount at the following path '' , and the corresponding pod has service account at 'spec.serviceAccountName'.

Both refer to the 'serviceAccountName' field of PodSpec struct but can be made to be different by patching the ReplicationController resource. How does ReplicationController and Pod use serviceAccountName differently? Shouldn't the ReplicationControllerSpec have a serviceAccountName field?
ptal, I think you are more familiar with ReplicationController implementation than me.
ReplicationController contains a pod spec object, which contains a serviceAccountName.
Hi , how to go about writing e2e test cases for this. My development is mostly complete.
I wouldn't mind if you posted the PR now and then we'd see what e2e test makes sense to have. In order to move forward...
what are the next steps?