1
16
17 package set
18
19 import (
20 "errors"
21 "fmt"
22
23 "github.com/spf13/cobra"
24 v1 "k8s.io/api/core/v1"
25 "k8s.io/klog/v2"
26
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/types"
29 utilerrors "k8s.io/apimachinery/pkg/util/errors"
30 "k8s.io/cli-runtime/pkg/genericclioptions"
31 "k8s.io/cli-runtime/pkg/genericiooptions"
32 "k8s.io/cli-runtime/pkg/printers"
33 "k8s.io/cli-runtime/pkg/resource"
34 "k8s.io/client-go/tools/clientcmd"
35 cmdutil "k8s.io/kubectl/pkg/cmd/util"
36 "k8s.io/kubectl/pkg/polymorphichelpers"
37 "k8s.io/kubectl/pkg/scheme"
38 "k8s.io/kubectl/pkg/util/i18n"
39 "k8s.io/kubectl/pkg/util/templates"
40 )
41
42 var (
43 serviceaccountResources = i18n.T(`replicationcontroller (rc), deployment (deploy), daemonset (ds), job, replicaset (rs), statefulset`)
44
45 serviceaccountLong = templates.LongDesc(i18n.T(`
46 Update the service account of pod template resources.
47
48 Possible resources (case insensitive) can be:
49
50 `) + serviceaccountResources)
51
52 serviceaccountExample = templates.Examples(i18n.T(`
53 # Set deployment nginx-deployment's service account to serviceaccount1
54 kubectl set serviceaccount deployment nginx-deployment serviceaccount1
55
56 # Print the result (in YAML format) of updated nginx deployment with the service account from local file, without hitting the API server
57 kubectl set sa -f nginx-deployment.yaml serviceaccount1 --local --dry-run=client -o yaml
58 `))
59 )
60
61
62 type SetServiceAccountOptions struct {
63 PrintFlags *genericclioptions.PrintFlags
64 RecordFlags *genericclioptions.RecordFlags
65
66 fileNameOptions resource.FilenameOptions
67 dryRunStrategy cmdutil.DryRunStrategy
68 shortOutput bool
69 all bool
70 output string
71 local bool
72 updatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
73 infos []*resource.Info
74 serviceAccountName string
75 fieldManager string
76
77 PrintObj printers.ResourcePrinterFunc
78 Recorder genericclioptions.Recorder
79
80 genericiooptions.IOStreams
81 }
82
83
84 func NewSetServiceAccountOptions(streams genericiooptions.IOStreams) *SetServiceAccountOptions {
85 return &SetServiceAccountOptions{
86 PrintFlags: genericclioptions.NewPrintFlags("serviceaccount updated").WithTypeSetter(scheme.Scheme),
87 RecordFlags: genericclioptions.NewRecordFlags(),
88
89 Recorder: genericclioptions.NoopRecorder{},
90
91 IOStreams: streams,
92 }
93 }
94
95
96 func NewCmdServiceAccount(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
97 o := NewSetServiceAccountOptions(streams)
98
99 cmd := &cobra.Command{
100 Use: "serviceaccount (-f FILENAME | TYPE NAME) SERVICE_ACCOUNT",
101 DisableFlagsInUseLine: true,
102 Aliases: []string{"sa"},
103 Short: i18n.T("Update the service account of a resource"),
104 Long: serviceaccountLong,
105 Example: serviceaccountExample,
106 Run: func(cmd *cobra.Command, args []string) {
107 cmdutil.CheckErr(o.Complete(f, cmd, args))
108 cmdutil.CheckErr(o.Run())
109 },
110 }
111
112 o.PrintFlags.AddFlags(cmd)
113 o.RecordFlags.AddFlags(cmd)
114
115 usage := "identifying the resource to get from a server."
116 cmdutil.AddFilenameOptionFlags(cmd, &o.fileNameOptions, usage)
117 cmd.Flags().BoolVar(&o.all, "all", o.all, "Select all resources, in the namespace of the specified resource types")
118 cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, set serviceaccount will NOT contact api-server but run locally.")
119 cmdutil.AddDryRunFlag(cmd)
120 cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-set")
121 return cmd
122 }
123
124
125 func (o *SetServiceAccountOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
126 var err error
127
128 o.RecordFlags.Complete(cmd)
129 o.Recorder, err = o.RecordFlags.ToRecorder()
130 if err != nil {
131 return err
132 }
133
134 o.shortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
135 o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
136 if err != nil {
137 return err
138 }
139 if o.local && o.dryRunStrategy == cmdutil.DryRunServer {
140 return fmt.Errorf("cannot specify --local and --dry-run=server - did you mean --dry-run=client?")
141 }
142 o.output = cmdutil.GetFlagString(cmd, "output")
143 o.updatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
144
145 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
146 printer, err := o.PrintFlags.ToPrinter()
147 if err != nil {
148 return err
149 }
150 o.PrintObj = printer.PrintObj
151
152 cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
153 if err != nil && !(o.local && clientcmd.IsEmptyConfig(err)) {
154 return err
155 }
156 if len(args) == 0 {
157 return errors.New("serviceaccount is required")
158 }
159 o.serviceAccountName = args[len(args)-1]
160 resources := args[:len(args)-1]
161 builder := f.NewBuilder().
162 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
163 LocalParam(o.local).
164 ContinueOnError().
165 NamespaceParam(cmdNamespace).DefaultNamespace().
166 FilenameParam(enforceNamespace, &o.fileNameOptions).
167 Flatten()
168 if !o.local {
169 builder.ResourceTypeOrNameArgs(o.all, resources...).
170 Latest()
171 }
172 o.infos, err = builder.Do().Infos()
173 return err
174 }
175
176
177 func (o *SetServiceAccountOptions) Run() error {
178 patchErrs := []error{}
179 patchFn := func(obj runtime.Object) ([]byte, error) {
180 _, err := o.updatePodSpecForObject(obj, func(podSpec *v1.PodSpec) error {
181 podSpec.ServiceAccountName = o.serviceAccountName
182 return nil
183 })
184 if err != nil {
185 return nil, err
186 }
187
188 if err := o.Recorder.Record(obj); err != nil {
189 klog.V(4).Infof("error recording current command: %v", err)
190 }
191
192 return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
193 }
194
195 patches := CalculatePatches(o.infos, scheme.DefaultJSONEncoder(), patchFn)
196 for _, patch := range patches {
197 info := patch.Info
198 name := info.ObjectName()
199 if patch.Err != nil {
200 patchErrs = append(patchErrs, fmt.Errorf("error: %s %v\n", name, patch.Err))
201 continue
202 }
203 if o.local || o.dryRunStrategy == cmdutil.DryRunClient {
204 if err := o.PrintObj(info.Object, o.Out); err != nil {
205 patchErrs = append(patchErrs, err)
206 }
207 continue
208 }
209 actual, err := resource.
210 NewHelper(info.Client, info.Mapping).
211 DryRun(o.dryRunStrategy == cmdutil.DryRunServer).
212 WithFieldManager(o.fieldManager).
213 Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
214 if err != nil {
215 patchErrs = append(patchErrs, fmt.Errorf("failed to patch ServiceAccountName %v", err))
216 continue
217 }
218
219 if err := o.PrintObj(actual, o.Out); err != nil {
220 patchErrs = append(patchErrs, err)
221 }
222 }
223 return utilerrors.NewAggregate(patchErrs)
224 }
225
View as plain text