github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/manifest/images.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package manifest 18 19 import ( 20 "context" 21 "strconv" 22 23 apimachinery "k8s.io/apimachinery/pkg/runtime/schema" 24 25 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 26 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 27 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation" 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 30 ) 31 32 const imageField = "image" 33 34 type ResourceSelectorImages struct { 35 allowlist map[apimachinery.GroupKind]latest.ResourceFilter 36 denylist map[apimachinery.GroupKind]latest.ResourceFilter 37 } 38 39 func NewResourceSelectorImages(allowlist map[apimachinery.GroupKind]latest.ResourceFilter, denylist map[apimachinery.GroupKind]latest.ResourceFilter) *ResourceSelectorImages { 40 return &ResourceSelectorImages{ 41 allowlist: allowlist, 42 denylist: denylist, 43 } 44 } 45 46 func (rsi *ResourceSelectorImages) allowByGroupKind(gk apimachinery.GroupKind) bool { 47 if _, allowed := rsi.allowlist[gk]; allowed { 48 // TODO(aaron-prindle) see if it makes sense to make this only use the allowlist... 49 if rf, disallowed := rsi.denylist[gk]; disallowed { 50 for _, s := range rf.Labels { 51 if s == ".*" { 52 return false 53 } 54 } 55 for _, s := range rf.Image { 56 if s == ".*" { 57 return false 58 } 59 } 60 } 61 return true 62 } 63 return false 64 } 65 66 func (rsi *ResourceSelectorImages) allowByNavpath(gk apimachinery.GroupKind, navpath string, k string) (string, bool) { 67 matchedConfigConnectorImage := false 68 69 for _, w := range ConfigConnectorResourceSelector { 70 if k == imageField && w.Matches(gk.Group, gk.Kind) { 71 matchedConfigConnectorImage = true 72 break 73 } 74 } 75 76 if rf, ok := rsi.denylist[gk]; ok { 77 for _, denypath := range rf.Image { 78 if denypath == ".*" { 79 return "", false 80 } 81 if navpath == denypath { 82 return "", false 83 } 84 } 85 } 86 87 if rf, ok := rsi.allowlist[gk]; ok { 88 matchedConfigConnectorImage = false 89 90 for _, allowpath := range rf.Image { 91 if allowpath == ".*" && k == imageField { 92 return "", true 93 } 94 if navpath == allowpath { 95 return "", true 96 } 97 } 98 } 99 return "", matchedConfigConnectorImage 100 } 101 102 // GetImages gathers a map of base image names to the image with its tag 103 func (l *ManifestList) GetImages(rs ResourceSelector) ([]graph.Artifact, error) { 104 s := &imageSaver{} 105 _, err := l.Visit(s, rs) 106 return s.Images, parseImagesInManifestErr(err) 107 } 108 109 type imageSaver struct { 110 Images []graph.Artifact 111 } 112 113 func (is *imageSaver) Visit(gk apimachinery.GroupKind, navpath string, o map[string]interface{}, k string, v interface{}, rs ResourceSelector) bool { 114 if k != imageField { 115 return true 116 } 117 118 image, ok := v.(string) 119 if !ok { 120 return true 121 } 122 parsed, err := docker.ParseReference(image) 123 if err != nil { 124 log.Entry(context.TODO()).Debugf("Couldn't parse image [%s]: %s", image, err.Error()) 125 return false 126 } 127 128 is.Images = append(is.Images, graph.Artifact{ 129 Tag: image, 130 ImageName: parsed.BaseName, 131 }) 132 return false 133 } 134 135 // ReplaceImages replaces image names in a list of manifests. 136 // It doesn't replace images that are referenced by digest. 137 func (l *ManifestList) ReplaceImages(ctx context.Context, builds []graph.Artifact, rs ResourceSelector) (ManifestList, error) { 138 return l.replaceImages(ctx, builds, selectLocalManifestImages, rs) 139 } 140 141 // ReplaceRemoteManifestImages replaces all image names in a list containing remote manifests. 142 // This will even override images referenced by digest or with a different repository 143 func (l *ManifestList) ReplaceRemoteManifestImages(ctx context.Context, builds []graph.Artifact, rs ResourceSelector) (ManifestList, error) { 144 return l.replaceImages(ctx, builds, selectRemoteManifestImages, rs) 145 } 146 147 func (l *ManifestList) replaceImages(ctx context.Context, builds []graph.Artifact, selector imageSelector, rs ResourceSelector) (ManifestList, error) { 148 _, endTrace := instrumentation.StartTrace(ctx, "ReplaceImages", map[string]string{ 149 "manifestEntries": strconv.Itoa(len(*l)), 150 "numImagesReplaced": strconv.Itoa(len(builds)), 151 }) 152 defer endTrace() 153 154 replacer := newImageReplacer(builds, selector) 155 156 updated, err := l.Visit(replacer, rs) 157 if err != nil { 158 endTrace(instrumentation.TraceEndError(err)) 159 return nil, replaceImageErr(err) 160 } 161 162 replacer.Check() 163 log.Entry(ctx).Debug("manifests with tagged images:", updated.String()) 164 165 return updated, nil 166 } 167 168 type imageReplacer struct { 169 tagsByImageName map[string]string 170 found map[string]bool 171 selector imageSelector 172 } 173 174 func newImageReplacer(builds []graph.Artifact, selector imageSelector) *imageReplacer { 175 tagsByImageName := make(map[string]string) 176 for _, build := range builds { 177 imageName := docker.SanitizeImageName(build.ImageName) 178 tagsByImageName[imageName] = build.Tag 179 } 180 181 return &imageReplacer{ 182 tagsByImageName: tagsByImageName, 183 found: make(map[string]bool), 184 selector: selector, 185 } 186 } 187 188 func (r *imageReplacer) Visit(gk apimachinery.GroupKind, navpath string, o map[string]interface{}, k string, v interface{}, rs ResourceSelector) bool { 189 if _, ok := rs.allowByNavpath(gk, navpath, k); !ok { 190 return true 191 } 192 193 image, ok := v.(string) 194 if !ok { 195 return true 196 } 197 198 parsed, err := docker.ParseReference(image) 199 if err != nil { 200 log.Entry(context.TODO()).Debugf("Couldn't parse image [%s]: %s", image, err.Error()) 201 return false 202 } 203 if imageName, tag, selected := r.selector(r.tagsByImageName, parsed); selected { 204 r.found[imageName] = true 205 o[k] = tag 206 } 207 return false 208 } 209 210 func (r *imageReplacer) Check() { 211 for imageName := range r.tagsByImageName { 212 if !r.found[imageName] { 213 log.Entry(context.TODO()).Debugf("image [%s] is not used by the current deployment", imageName) 214 } 215 } 216 } 217 218 // imageSelector represents a strategy for matching the container `image` defined in a kubernetes manifest with the correct skaffold artifact. 219 type imageSelector func(tagsByImageName map[string]string, image *docker.ImageReference) (imageName, tag string, valid bool) 220 221 func selectLocalManifestImages(tagsByImageName map[string]string, image *docker.ImageReference) (string, string, bool) { 222 // Leave images referenced by digest as they are 223 if image.Digest != "" { 224 return "", "", false 225 } 226 // local manifest mentions artifact `imageName` directly, so `imageName` is parsed into `image.BaseName` 227 tag, present := tagsByImageName[image.BaseName] 228 return image.BaseName, tag, present 229 } 230 231 func selectRemoteManifestImages(tagsByImageName map[string]string, image *docker.ImageReference) (string, string, bool) { 232 // if manifest mentions `imageName` directly then `imageName` is parsed into `image.BaseName` 233 if tag, present := tagsByImageName[image.BaseName]; present { 234 return image.BaseName, tag, present 235 } 236 // if manifest mentions image with repository then `imageName` is parsed into `image.Name` 237 tag, present := tagsByImageName[image.Name] 238 return image.Name, tag, present 239 }