set_image.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*
  2. Copyright 2016 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. "github.com/spf13/cobra"
  17. "k8s.io/klog"
  18. "k8s.io/api/core/v1"
  19. "k8s.io/apimachinery/pkg/runtime"
  20. "k8s.io/apimachinery/pkg/types"
  21. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  22. "k8s.io/cli-runtime/pkg/genericclioptions"
  23. "k8s.io/cli-runtime/pkg/printers"
  24. "k8s.io/cli-runtime/pkg/resource"
  25. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  26. "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
  27. "k8s.io/kubernetes/pkg/kubectl/scheme"
  28. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  29. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  30. )
  31. // ImageOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
  32. // referencing the cmd.Flags()
  33. type SetImageOptions struct {
  34. resource.FilenameOptions
  35. PrintFlags *genericclioptions.PrintFlags
  36. RecordFlags *genericclioptions.RecordFlags
  37. Infos []*resource.Info
  38. Selector string
  39. DryRun bool
  40. All bool
  41. Output string
  42. Local bool
  43. ResolveImage ImageResolver
  44. PrintObj printers.ResourcePrinterFunc
  45. Recorder genericclioptions.Recorder
  46. UpdatePodSpecForObject polymorphichelpers.UpdatePodSpecForObjectFunc
  47. Resources []string
  48. ContainerImages map[string]string
  49. genericclioptions.IOStreams
  50. }
  51. var (
  52. imageResources = `
  53. pod (po), replicationcontroller (rc), deployment (deploy), daemonset (ds), replicaset (rs)`
  54. imageLong = templates.LongDesc(`
  55. Update existing container image(s) of resources.
  56. Possible resources include (case insensitive):
  57. ` + imageResources)
  58. imageExample = templates.Examples(`
  59. # Set a deployment's nginx container image to 'nginx:1.9.1', and its busybox container image to 'busybox'.
  60. kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1
  61. # Update all deployments' and rc's nginx container's image to 'nginx:1.9.1'
  62. kubectl set image deployments,rc nginx=nginx:1.9.1 --all
  63. # Update image of all containers of daemonset abc to 'nginx:1.9.1'
  64. kubectl set image daemonset abc *=nginx:1.9.1
  65. # Print result (in yaml format) of updating nginx container image from local file, without hitting the server
  66. kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml`)
  67. )
  68. // NewImageOptions returns an initialized SetImageOptions instance
  69. func NewImageOptions(streams genericclioptions.IOStreams) *SetImageOptions {
  70. return &SetImageOptions{
  71. PrintFlags: genericclioptions.NewPrintFlags("image updated").WithTypeSetter(scheme.Scheme),
  72. RecordFlags: genericclioptions.NewRecordFlags(),
  73. Recorder: genericclioptions.NoopRecorder{},
  74. IOStreams: streams,
  75. }
  76. }
  77. // NewCmdImage returns an initialized Command instance for the 'set image' sub command
  78. func NewCmdImage(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
  79. o := NewImageOptions(streams)
  80. cmd := &cobra.Command{
  81. Use: "image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N",
  82. DisableFlagsInUseLine: true,
  83. Short: i18n.T("Update image of a pod template"),
  84. Long: imageLong,
  85. Example: imageExample,
  86. Run: func(cmd *cobra.Command, args []string) {
  87. cmdutil.CheckErr(o.Complete(f, cmd, args))
  88. cmdutil.CheckErr(o.Validate())
  89. cmdutil.CheckErr(o.Run())
  90. },
  91. }
  92. o.PrintFlags.AddFlags(cmd)
  93. o.RecordFlags.AddFlags(cmd)
  94. usage := "identifying the resource to get from a server."
  95. cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
  96. cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources, including uninitialized ones, in the namespace of the specified resource types")
  97. cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, not including uninitialized ones, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
  98. cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, set image will NOT contact api-server but run locally.")
  99. cmdutil.AddDryRunFlag(cmd)
  100. cmdutil.AddIncludeUninitializedFlag(cmd)
  101. return cmd
  102. }
  103. // Complete completes all required options
  104. func (o *SetImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
  105. var err error
  106. o.RecordFlags.Complete(cmd)
  107. o.Recorder, err = o.RecordFlags.ToRecorder()
  108. if err != nil {
  109. return err
  110. }
  111. o.UpdatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn
  112. o.DryRun = cmdutil.GetDryRunFlag(cmd)
  113. o.Output = cmdutil.GetFlagString(cmd, "output")
  114. o.ResolveImage = resolveImageFunc
  115. if o.DryRun {
  116. o.PrintFlags.Complete("%s (dry run)")
  117. }
  118. printer, err := o.PrintFlags.ToPrinter()
  119. if err != nil {
  120. return err
  121. }
  122. o.PrintObj = printer.PrintObj
  123. cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
  124. if err != nil {
  125. return err
  126. }
  127. o.Resources, o.ContainerImages, err = getResourcesAndImages(args)
  128. if err != nil {
  129. return err
  130. }
  131. builder := f.NewBuilder().
  132. WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
  133. LocalParam(o.Local).
  134. ContinueOnError().
  135. NamespaceParam(cmdNamespace).DefaultNamespace().
  136. FilenameParam(enforceNamespace, &o.FilenameOptions).
  137. Flatten()
  138. if !o.Local {
  139. builder.LabelSelectorParam(o.Selector).
  140. ResourceTypeOrNameArgs(o.All, o.Resources...).
  141. Latest()
  142. } else {
  143. // if a --local flag was provided, and a resource was specified in the form
  144. // <resource>/<name>, fail immediately as --local cannot query the api server
  145. // for the specified resource.
  146. if len(o.Resources) > 0 {
  147. return resource.LocalResourceError
  148. }
  149. }
  150. o.Infos, err = builder.Do().Infos()
  151. if err != nil {
  152. return err
  153. }
  154. return nil
  155. }
  156. // Validate makes sure provided values in SetImageOptions are valid
  157. func (o *SetImageOptions) Validate() error {
  158. errors := []error{}
  159. if o.All && len(o.Selector) > 0 {
  160. errors = append(errors, fmt.Errorf("cannot set --all and --selector at the same time"))
  161. }
  162. if len(o.Resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
  163. errors = append(errors, fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>"))
  164. }
  165. if len(o.ContainerImages) < 1 {
  166. errors = append(errors, fmt.Errorf("at least one image update is required"))
  167. } else if len(o.ContainerImages) > 1 && hasWildcardKey(o.ContainerImages) {
  168. errors = append(errors, fmt.Errorf("all containers are already specified by *, but saw more than one container_name=container_image pairs"))
  169. }
  170. return utilerrors.NewAggregate(errors)
  171. }
  172. // Run performs the execution of 'set image' sub command
  173. func (o *SetImageOptions) Run() error {
  174. allErrs := []error{}
  175. patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
  176. _, err := o.UpdatePodSpecForObject(obj, func(spec *v1.PodSpec) error {
  177. for name, image := range o.ContainerImages {
  178. resolvedImageName, err := o.ResolveImage(image)
  179. if err != nil {
  180. allErrs = append(allErrs, fmt.Errorf("error: unable to resolve image %q for container %q: %v", image, name, err))
  181. if name == "*" {
  182. break
  183. }
  184. continue
  185. }
  186. initContainerFound := setImage(spec.InitContainers, name, resolvedImageName)
  187. containerFound := setImage(spec.Containers, name, resolvedImageName)
  188. if !containerFound && !initContainerFound {
  189. allErrs = append(allErrs, fmt.Errorf("error: unable to find container named %q", name))
  190. }
  191. }
  192. return nil
  193. })
  194. if err != nil {
  195. return nil, err
  196. }
  197. // record this change (for rollout history)
  198. if err := o.Recorder.Record(obj); err != nil {
  199. klog.V(4).Infof("error recording current command: %v", err)
  200. }
  201. return runtime.Encode(scheme.DefaultJSONEncoder(), obj)
  202. })
  203. for _, patch := range patches {
  204. info := patch.Info
  205. if patch.Err != nil {
  206. name := info.ObjectName()
  207. allErrs = append(allErrs, fmt.Errorf("error: %s %v\n", name, patch.Err))
  208. continue
  209. }
  210. // no changes
  211. if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
  212. continue
  213. }
  214. if o.Local || o.DryRun {
  215. if err := o.PrintObj(info.Object, o.Out); err != nil {
  216. allErrs = append(allErrs, err)
  217. }
  218. continue
  219. }
  220. // patch the change
  221. actual, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch, nil)
  222. if err != nil {
  223. allErrs = append(allErrs, fmt.Errorf("failed to patch image update to pod template: %v", err))
  224. continue
  225. }
  226. if err := o.PrintObj(actual, o.Out); err != nil {
  227. allErrs = append(allErrs, err)
  228. }
  229. }
  230. return utilerrors.NewAggregate(allErrs)
  231. }
  232. func setImage(containers []v1.Container, containerName string, image string) bool {
  233. containerFound := false
  234. // Find the container to update, and update its image
  235. for i, c := range containers {
  236. if c.Name == containerName || containerName == "*" {
  237. containerFound = true
  238. containers[i].Image = image
  239. }
  240. }
  241. return containerFound
  242. }
  243. // getResourcesAndImages retrieves resources and container name:images pair from given args
  244. func getResourcesAndImages(args []string) (resources []string, containerImages map[string]string, err error) {
  245. pairType := "image"
  246. resources, imageArgs, err := cmdutil.GetResourcesAndPairs(args, pairType)
  247. if err != nil {
  248. return
  249. }
  250. containerImages, _, err = cmdutil.ParsePairs(imageArgs, pairType, false)
  251. return
  252. }
  253. func hasWildcardKey(containerImages map[string]string) bool {
  254. _, ok := containerImages["*"]
  255. return ok
  256. }
  257. // ImageResolver is a func that receives an image name, and
  258. // resolves it to an appropriate / compatible image name.
  259. // Adds flexibility for future image resolving methods.
  260. type ImageResolver func(in string) (string, error)
  261. // implements ImageResolver
  262. func resolveImageFunc(in string) (string, error) {
  263. return in, nil
  264. }