github.com/grahambrereton-form3/tilt@v0.10.18/internal/k8s/image.go (about) 1 package k8s 2 3 import ( 4 "fmt" 5 6 "github.com/docker/distribution/reference" 7 "github.com/pkg/errors" 8 v1 "k8s.io/api/core/v1" 9 "k8s.io/apimachinery/pkg/runtime" 10 11 "github.com/windmilleng/tilt/pkg/model" 12 13 "github.com/windmilleng/tilt/internal/container" 14 ) 15 16 // Iterate through the fields of a k8s entity and 17 // replace the image pull policy on all images. 18 func InjectImagePullPolicy(entity K8sEntity, policy v1.PullPolicy) (K8sEntity, error) { 19 entity = entity.DeepCopy() 20 containers, err := extractContainers(&entity) 21 if err != nil { 22 return K8sEntity{}, err 23 } 24 25 for _, container := range containers { 26 container.ImagePullPolicy = policy 27 } 28 return entity, nil 29 } 30 31 // Iterate through the fields of a k8s entity and 32 // replace a image name with its digest. 33 // 34 // policy: The pull policy to set on the replaced image. 35 // When working with a local k8s cluster, we want to set this to Never, 36 // to ensure that k8s fails hard if the image is missing from docker. 37 // 38 // Returns: the new entity, whether the image was replaced, and an error. 39 func InjectImageDigest(entity K8sEntity, selector container.RefSelector, injectRef reference.Named, matchInEnvVars bool, policy v1.PullPolicy) (K8sEntity, bool, error) { 40 entity = entity.DeepCopy() 41 42 // NOTE(nick): For some reason, if you have a reference with a digest, 43 // kubernetes will never find it in the local registry and always tries to do a 44 // pull. It's not clear to me why it behaves this way. 45 // 46 // There is not a simple way to resolve this problem at this level of the 47 // API. In some cases, the digest won't matter and the name/tag will be 48 // enough. In other cases, the digest will be critical if we don't have good 49 // synchronization that the name/tag currently matches the digest. 50 // 51 // For now, we try to detect this case and push the error up to the caller. 52 _, hasDigest := injectRef.(reference.Digested) 53 if hasDigest && policy == v1.PullNever { 54 return K8sEntity{}, false, fmt.Errorf("INTERNAL TILT ERROR: Cannot set PullNever with digest") 55 } 56 57 replaced := false 58 59 entity, r, err := injectImageDigestInContainers(entity, selector, injectRef, policy) 60 if err != nil { 61 return K8sEntity{}, false, err 62 } 63 if r { 64 replaced = true 65 } 66 67 if matchInEnvVars { 68 entity, r, err = injectImageDigestInEnvVars(entity, selector, injectRef) 69 if err != nil { 70 return K8sEntity{}, false, err 71 } 72 if r { 73 replaced = true 74 } 75 } 76 77 entity, r, err = injectImageDigestInUnstructured(entity, injectRef) 78 if err != nil { 79 return K8sEntity{}, false, err 80 } 81 if r { 82 replaced = true 83 } 84 85 return entity, replaced, nil 86 } 87 88 func injectImageDigestInContainers(entity K8sEntity, selector container.RefSelector, injectRef reference.Named, policy v1.PullPolicy) (K8sEntity, bool, error) { 89 containers, err := extractContainers(&entity) 90 if err != nil { 91 return K8sEntity{}, false, err 92 } 93 94 replaced := false 95 for _, c := range containers { 96 existingRef, err := container.ParseNamed(c.Image) 97 if err != nil { 98 return K8sEntity{}, false, err 99 } 100 101 if selector.Matches(existingRef) { 102 c.Image = reference.FamiliarString(injectRef) 103 c.ImagePullPolicy = policy 104 replaced = true 105 } 106 } 107 108 return entity, replaced, nil 109 } 110 111 func injectImageDigestInEnvVars(entity K8sEntity, selector container.RefSelector, injectRef reference.Named) (K8sEntity, bool, error) { 112 envVars, err := extractEnvVars(&entity) 113 if err != nil { 114 return K8sEntity{}, false, err 115 } 116 117 replaced := false 118 for _, envVar := range envVars { 119 existingRef, err := container.ParseNamed(envVar.Value) 120 if err != nil || existingRef == nil { 121 continue 122 } 123 124 if selector.Matches(existingRef) { 125 envVar.Value = reference.FamiliarString(injectRef) 126 replaced = true 127 } 128 } 129 130 return entity, replaced, nil 131 } 132 133 func injectImageInUnstructuredInterface(ui interface{}, injectRef reference.Named) (interface{}, bool) { 134 switch x := ui.(type) { 135 case map[string]interface{}: 136 replaced := false 137 for k, v := range x { 138 newV, r := injectImageInUnstructuredInterface(v, injectRef) 139 x[k] = newV 140 if r { 141 replaced = true 142 } 143 } 144 return x, replaced 145 case []interface{}: 146 replaced := false 147 for i, v := range x { 148 newV, r := injectImageInUnstructuredInterface(v, injectRef) 149 x[i] = newV 150 if r { 151 replaced = true 152 } 153 } 154 return x, replaced 155 case string: 156 ref, err := container.ParseNamed(x) 157 if err == nil && ref.Name() == injectRef.Name() { 158 return reference.FamiliarString(injectRef), true 159 } else { 160 return x, false 161 } 162 default: 163 return ui, false 164 } 165 } 166 167 func injectImageDigestInUnstructured(entity K8sEntity, injectRef reference.Named) (K8sEntity, bool, error) { 168 u, ok := entity.Obj.(runtime.Unstructured) 169 if !ok { 170 return entity, false, nil 171 } 172 173 n, replaced := injectImageInUnstructuredInterface(u.UnstructuredContent(), injectRef) 174 175 u.SetUnstructuredContent(n.(map[string]interface{})) 176 177 entity.Obj = u 178 return entity, replaced, nil 179 } 180 181 func InjectCommand(entity K8sEntity, ref reference.Named, cmd model.Cmd) (K8sEntity, error) { 182 entity = entity.DeepCopy() 183 184 selector := container.NewRefSelector(ref) 185 e, injected, err := injectCommandInContainers(entity, selector, cmd) 186 if err != nil { 187 return e, err 188 } 189 if !injected { 190 // NOTE(maia): currently we only support injecting commands into containers (i.e. the 191 // k8s yaml `container` block). This means we don't support injecting commands into CRDs. 192 return e, fmt.Errorf("could not inject command %v into entity: %s. No container found matching ref: %s. "+ 193 "Note: command overrides only supported on containers with images, not on CRDs", 194 cmd.Argv, entity.Name(), reference.FamiliarString(ref)) 195 } 196 197 return e, nil 198 } 199 200 func injectCommandInContainers(entity K8sEntity, selector container.RefSelector, cmd model.Cmd) (K8sEntity, bool, error) { 201 var injected bool 202 containers, err := extractContainers(&entity) 203 if err != nil { 204 return K8sEntity{}, injected, err 205 } 206 207 for _, c := range containers { 208 existingRef, err := container.ParseNamed(c.Image) 209 if err != nil { 210 return K8sEntity{}, injected, err 211 } 212 213 if selector.Matches(existingRef) { 214 c.Command = cmd.Argv 215 c.Args = nil // clear the "args" param. 216 217 injected = true 218 } 219 } 220 return entity, injected, nil 221 } 222 223 // HasImage indicates whether the given entity is tagged with the given image. 224 func (e K8sEntity) HasImage(image container.RefSelector, imageJSONPaths []JSONPath, inEnvVars bool) (bool, error) { 225 var envVarImages []container.RefSelector 226 if inEnvVars { 227 envVarImages = []container.RefSelector{image} 228 } 229 images, err := e.FindImages(imageJSONPaths, envVarImages) 230 if err != nil { 231 return false, errors.Wrap(err, "HasImage") 232 } 233 234 for _, existingRef := range images { 235 if image.Matches(existingRef) { 236 return true, nil 237 } 238 } 239 240 return false, nil 241 } 242 243 func (e K8sEntity) FindImages(imageJSONPaths []JSONPath, envVarImages []container.RefSelector) ([]reference.Named, error) { 244 var result []reference.Named 245 246 // Look for images in instances of Container 247 containers, err := extractContainers(&e) 248 if err != nil { 249 return nil, err 250 } 251 for _, c := range containers { 252 ref, err := container.ParseNamed(c.Image) 253 if err != nil { 254 return nil, errors.Wrapf(err, "parsing %s", c.Image) 255 } 256 257 result = append(result, ref) 258 } 259 260 var obj interface{} 261 if u, ok := e.Obj.(runtime.Unstructured); ok { 262 obj = u.UnstructuredContent() 263 } else { 264 obj = e.Obj 265 } 266 267 // also look for images in any json paths that were specified for this entity 268 for _, path := range imageJSONPaths { 269 image, err := path.Execute(obj) 270 if err != nil { 271 return nil, errors.Wrapf(err, "error applying json path '%s'", path) 272 } 273 ref, err := container.ParseNamed(image) 274 if err != nil { 275 return nil, errors.Wrapf(err, "error parsing image '%s' at json path '%s'", image, path) 276 } 277 result = append(result, ref) 278 } 279 280 envVars, err := extractEnvVars(&obj) 281 if err != nil { 282 return nil, err 283 } 284 285 for _, envVar := range envVars { 286 existingRef, err := container.ParseNamed(envVar.Value) 287 if err != nil || existingRef == nil { 288 continue 289 } 290 for _, img := range envVarImages { 291 if img.Matches(existingRef) { 292 result = append(result, existingRef) 293 } 294 } 295 } 296 297 return result, nil 298 } 299 300 func PodContainsRef(pod v1.PodSpec, selector container.RefSelector) (bool, error) { 301 cRef, err := FindImageRefMatching(pod, selector) 302 if err != nil { 303 return false, err 304 } 305 306 return cRef != nil, nil 307 } 308 309 func FindImageRefMatching(pod v1.PodSpec, selector container.RefSelector) (reference.Named, error) { 310 for _, c := range pod.Containers { 311 cRef, err := container.ParseNamed(c.Image) 312 if err != nil { 313 return nil, errors.Wrap(err, "FindImageRefMatching") 314 } 315 316 if selector.Matches(cRef) { 317 return cRef, nil 318 } 319 } 320 return nil, nil 321 } 322 323 func FindImageNamedTaggedMatching(pod v1.PodSpec, selector container.RefSelector) (reference.NamedTagged, error) { 324 cRef, err := FindImageRefMatching(pod, selector) 325 if err != nil { 326 return nil, err 327 } 328 329 cTagged, ok := cRef.(reference.NamedTagged) 330 if !ok { 331 return nil, nil 332 } 333 334 return cTagged, nil 335 }