1/* 2Copyright 2019 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package set 18 19import ( 20 "errors" 21 "regexp" 22 "sort" 23 "strings" 24 25 "github.com/spf13/cobra" 26 "sigs.k8s.io/kustomize/pkg/commands/kustfile" 27 "sigs.k8s.io/kustomize/pkg/fs" 28 "sigs.k8s.io/kustomize/pkg/image" 29) 30 31type setImageOptions struct { 32 imageMap map[string]image.Image 33} 34 35var pattern = regexp.MustCompile("^(.*):([a-zA-Z0-9._-]*)$") 36 37// errors 38 39var ( 40 errImageNoArgs = errors.New("no image specified") 41 errImageInvalidArgs = errors.New(`invalid format of image, use one of the following options: 42- <image>=<newimage>:<newtag> 43- <image>=<newimage>@<newtag> 44- <image>=<newimage> 45- <image>:<newtag> 46- <image>@<digest>`) 47) 48 49const separator = "=" 50 51// newCmdSetImage sets the new names, tags or digests for images in the kustomization. 52func newCmdSetImage(fsys fs.FileSystem) *cobra.Command { 53 var o setImageOptions 54 55 cmd := &cobra.Command{ 56 Use: "image", 57 Short: `Sets images and their new names, new tags or digests in the kustomization file`, 58 Example: ` 59The command 60 set image postgres=eu.gcr.io/my-project/postgres:latest my-app=my-registry/my-app@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 61will add 62 63image: 64- name: postgres 65 newName: eu.gcr.io/my-project/postgres 66 newTag: latest 67- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 68 name: my-app 69 newName: my-registry/my-app 70 71to the kustomization file if it doesn't exist, 72and overwrite the previous ones if the image name exists. 73 74The command 75 set image node:8.15.0 mysql=mariadb alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 76will add 77 78image: 79- name: node 80 newTag: 8.15.0 81- name: mysql 82 newName: mariadb 83- digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 84 name: alpine 85 86to the kustomization file if it doesn't exist, 87and overwrite the previous ones if the image name exists. 88`, 89 RunE: func(cmd *cobra.Command, args []string) error { 90 err := o.Validate(args) 91 if err != nil { 92 return err 93 } 94 return o.RunSetImage(fsys) 95 }, 96 } 97 return cmd 98} 99 100type overwrite struct { 101 name string 102 digest string 103 tag string 104} 105 106// Validate validates setImage command. 107func (o *setImageOptions) Validate(args []string) error { 108 if len(args) == 0 { 109 return errImageNoArgs 110 } 111 112 o.imageMap = make(map[string]image.Image) 113 114 for _, arg := range args { 115 116 img, err := parse(arg) 117 if err != nil { 118 return err 119 } 120 o.imageMap[img.Name] = img 121 } 122 return nil 123} 124 125// RunSetImage runs setImage command. 126func (o *setImageOptions) RunSetImage(fSys fs.FileSystem) error { 127 mf, err := kustfile.NewKustomizationFile(fSys) 128 if err != nil { 129 return err 130 } 131 m, err := mf.Read() 132 if err != nil { 133 return err 134 } 135 136 // append only new images from ksutomize file 137 for _, im := range m.Images { 138 if _, ok := o.imageMap[im.Name]; ok { 139 continue 140 } 141 142 o.imageMap[im.Name] = im 143 } 144 145 var images []image.Image 146 for _, v := range o.imageMap { 147 images = append(images, v) 148 } 149 150 sort.Slice(images, func(i, j int) bool { 151 return images[i].Name < images[j].Name 152 }) 153 154 m.Images = images 155 return mf.Write(m) 156} 157 158func parse(arg string) (image.Image, error) { 159 160 // matches if there is an image name to overwrite 161 // <image>=<new-image><:|@><new-tag> 162 if s := strings.Split(arg, separator); len(s) == 2 { 163 p, err := parseOverwrite(s[1]) 164 return image.Image{ 165 Name: s[0], 166 NewName: p.name, 167 NewTag: p.tag, 168 Digest: p.digest, 169 }, err 170 } 171 172 // matches only for <tag|digest> overwrites 173 // <image><:|@><new-tag> 174 p, err := parseOverwrite(arg) 175 return image.Image{ 176 Name: p.name, 177 NewTag: p.tag, 178 Digest: p.digest, 179 }, err 180} 181 182// parseOverwrite parses the overwrite parameters 183// from the given arg into a struct 184func parseOverwrite(arg string) (overwrite, error) { 185 // match <image>@<digest> 186 if d := strings.Split(arg, "@"); len(d) > 1 { 187 return overwrite{ 188 name: d[0], 189 digest: d[1], 190 }, nil 191 } 192 193 // match <image>:<tag> 194 if t := pattern.FindStringSubmatch(arg); len(t) == 3 { 195 return overwrite{ 196 name: t[1], 197 tag: t[2], 198 }, nil 199 } 200 return overwrite{}, errImageInvalidArgs 201} 202