1
16
17 package set
18
19 import (
20 "fmt"
21
22 "github.com/spf13/cobra"
23 "k8s.io/klog/v2"
24
25 v1 "k8s.io/api/core/v1"
26 "k8s.io/apimachinery/pkg/api/meta"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/types"
30 "k8s.io/apimachinery/pkg/util/validation"
31 "k8s.io/cli-runtime/pkg/genericclioptions"
32 "k8s.io/cli-runtime/pkg/genericiooptions"
33 "k8s.io/cli-runtime/pkg/printers"
34 "k8s.io/cli-runtime/pkg/resource"
35 cmdutil "k8s.io/kubectl/pkg/cmd/util"
36 "k8s.io/kubectl/pkg/scheme"
37 "k8s.io/kubectl/pkg/util/i18n"
38 "k8s.io/kubectl/pkg/util/templates"
39 )
40
41
42
43 type SetSelectorOptions struct {
44
45 ResourceBuilderFlags *genericclioptions.ResourceBuilderFlags
46 PrintFlags *genericclioptions.PrintFlags
47 RecordFlags *genericclioptions.RecordFlags
48 dryRunStrategy cmdutil.DryRunStrategy
49 fieldManager string
50
51
52 resources []string
53 selector *metav1.LabelSelector
54 resourceVersion string
55
56
57 WriteToServer bool
58 PrintObj printers.ResourcePrinterFunc
59 Recorder genericclioptions.Recorder
60 ResourceFinder genericclioptions.ResourceFinder
61
62
63 genericiooptions.IOStreams
64 }
65
66 var (
67 selectorLong = templates.LongDesc(i18n.T(`
68 Set the selector on a resource. Note that the new selector will overwrite the old selector if the resource had one prior to the invocation
69 of 'set selector'.
70
71 A selector must begin with a letter or number, and may contain letters, numbers, hyphens, dots, and underscores, up to %[1]d characters.
72 If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.
73 Note: currently selectors can only be set on Service objects.`))
74 selectorExample = templates.Examples(`
75 # Set the labels and selector before creating a deployment/service pair
76 kubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run=client | kubectl set selector --local -f - 'environment=qa' -o yaml | kubectl create -f -
77 kubectl create deployment my-dep -o yaml --dry-run=client | kubectl label --local -f - environment=qa -o yaml | kubectl create -f -`)
78 )
79
80
81 func NewSelectorOptions(streams genericiooptions.IOStreams) *SetSelectorOptions {
82 return &SetSelectorOptions{
83 ResourceBuilderFlags: genericclioptions.NewResourceBuilderFlags().
84 WithScheme(scheme.Scheme).
85 WithAll(false).
86 WithLocal(false).
87 WithLatest(),
88 PrintFlags: genericclioptions.NewPrintFlags("selector updated").WithTypeSetter(scheme.Scheme),
89 RecordFlags: genericclioptions.NewRecordFlags(),
90
91 Recorder: genericclioptions.NoopRecorder{},
92
93 IOStreams: streams,
94 }
95 }
96
97
98 func NewCmdSelector(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
99 o := NewSelectorOptions(streams)
100
101 cmd := &cobra.Command{
102 Use: "selector (-f FILENAME | TYPE NAME) EXPRESSIONS [--resource-version=version]",
103 DisableFlagsInUseLine: true,
104 Short: i18n.T("Set the selector on a resource"),
105 Long: fmt.Sprintf(selectorLong, validation.LabelValueMaxLength),
106 Example: selectorExample,
107 Run: func(cmd *cobra.Command, args []string) {
108 cmdutil.CheckErr(o.Complete(f, cmd, args))
109 cmdutil.CheckErr(o.Validate())
110 cmdutil.CheckErr(o.RunSelector())
111 },
112 }
113
114 o.ResourceBuilderFlags.AddFlags(cmd.Flags())
115 o.PrintFlags.AddFlags(cmd)
116 o.RecordFlags.AddFlags(cmd)
117 cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-set")
118
119 cmd.Flags().StringVarP(&o.resourceVersion, "resource-version", "", o.resourceVersion, "If non-empty, the selectors update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.")
120 cmdutil.AddDryRunFlag(cmd)
121
122 return cmd
123 }
124
125
126 func (o *SetSelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
127 var err error
128
129 o.RecordFlags.Complete(cmd)
130 o.Recorder, err = o.RecordFlags.ToRecorder()
131 if err != nil {
132 return err
133 }
134
135 o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
136 if err != nil {
137 return err
138 }
139
140 o.resources, o.selector, err = getResourcesAndSelector(args)
141 if err != nil {
142 return err
143 }
144
145 o.ResourceFinder = o.ResourceBuilderFlags.ToBuilder(f, o.resources)
146 o.WriteToServer = !(*o.ResourceBuilderFlags.Local || o.dryRunStrategy == cmdutil.DryRunClient)
147
148 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
149 printer, err := o.PrintFlags.ToPrinter()
150 if err != nil {
151 return err
152 }
153 o.PrintObj = printer.PrintObj
154
155 return err
156 }
157
158
159 func (o *SetSelectorOptions) Validate() error {
160 if o.selector == nil {
161 return fmt.Errorf("one selector is required")
162 }
163 return nil
164 }
165
166
167 func (o *SetSelectorOptions) RunSelector() error {
168 r := o.ResourceFinder.Do()
169
170 return r.Visit(func(info *resource.Info, err error) error {
171 if err != nil {
172 return err
173 }
174 patch := &Patch{Info: info}
175
176 if len(o.resourceVersion) != 0 {
177
178 accessor, err := meta.Accessor(info.Object)
179 if err != nil {
180 return err
181 }
182 accessor.SetResourceVersion("")
183 }
184
185 CalculatePatch(patch, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
186
187 if len(o.resourceVersion) != 0 {
188 accessor, err := meta.Accessor(info.Object)
189 if err != nil {
190 return nil, err
191 }
192 accessor.SetResourceVersion(o.resourceVersion)
193 }
194
195 selectErr := updateSelectorForObject(info.Object, *o.selector)
196 if selectErr != nil {
197 return nil, selectErr
198 }
199
200
201 if err := o.Recorder.Record(patch.Info.Object); err != nil {
202 klog.V(4).Infof("error recording current command: %v", err)
203 }
204
205 return runtime.Encode(scheme.DefaultJSONEncoder(), info.Object)
206 })
207
208 if patch.Err != nil {
209 return patch.Err
210 }
211 if !o.WriteToServer {
212 return o.PrintObj(info.Object, o.Out)
213 }
214
215 actual, err := resource.
216 NewHelper(info.Client, info.Mapping).
217 DryRun(o.dryRunStrategy == cmdutil.DryRunServer).
218 WithFieldManager(o.fieldManager).
219 Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
220 if err != nil {
221 return err
222 }
223
224 return o.PrintObj(actual, o.Out)
225 })
226 }
227
228 func updateSelectorForObject(obj runtime.Object, selector metav1.LabelSelector) error {
229 copyOldSelector := func() (map[string]string, error) {
230 if len(selector.MatchExpressions) > 0 {
231 return nil, fmt.Errorf("match expression %v not supported on this object", selector.MatchExpressions)
232 }
233 dst := make(map[string]string)
234 for label, value := range selector.MatchLabels {
235 dst[label] = value
236 }
237 return dst, nil
238 }
239 var err error
240 switch t := obj.(type) {
241 case *v1.Service:
242 t.Spec.Selector, err = copyOldSelector()
243 default:
244 err = fmt.Errorf("setting a selector is only supported for Services")
245 }
246 return err
247 }
248
249
250 func getResourcesAndSelector(args []string) (resources []string, selector *metav1.LabelSelector, err error) {
251 if len(args) == 0 {
252 return []string{}, nil, nil
253 }
254 resources = args[:len(args)-1]
255 selector, err = metav1.ParseToLabelSelector(args[len(args)-1])
256 return resources, selector, err
257 }
258
View as plain text