github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/k8s/image.go (about) 1 package k8s 2 3 import ( 4 "fmt" 5 6 "github.com/distribution/reference" 7 "github.com/pkg/errors" 8 v1 "k8s.io/api/core/v1" 9 "k8s.io/apimachinery/pkg/runtime" 10 11 "github.com/tilt-dev/tilt/internal/container" 12 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 13 ) 14 15 // Iterate through the fields of a k8s entity and 16 // replace the image pull policy on all images. 17 func InjectImagePullPolicy(entity K8sEntity, policy v1.PullPolicy) (K8sEntity, error) { 18 entity = entity.DeepCopy() 19 containers, err := extractContainers(&entity) 20 if err != nil { 21 return K8sEntity{}, err 22 } 23 24 for _, container := range containers { 25 container.ImagePullPolicy = policy 26 } 27 return entity, nil 28 } 29 30 // Iterate through the fields of a k8s entity and 31 // replace a image name with its digest. 32 // 33 // policy: The pull policy to set on the replaced image. 34 // 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, locators []ImageLocator, 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 for _, locator := range locators { 78 entity, r, err = locator.Inject(entity, selector, injectRef, policy) 79 if err != nil { 80 return K8sEntity{}, false, err 81 } 82 if r { 83 replaced = true 84 } 85 } 86 87 return entity, replaced, nil 88 } 89 90 func injectImageDigestInContainers(entity K8sEntity, selector container.RefSelector, injectRef reference.Named, policy v1.PullPolicy) (K8sEntity, bool, error) { 91 containers, err := extractContainers(&entity) 92 if err != nil { 93 return K8sEntity{}, false, err 94 } 95 96 replaced := false 97 for _, c := range containers { 98 existingRef, err := container.ParseNamed(c.Image) 99 if err != nil { 100 return K8sEntity{}, false, err 101 } 102 103 if selector.Matches(existingRef) { 104 c.Image = container.FamiliarString(injectRef) 105 c.ImagePullPolicy = policy 106 replaced = true 107 } 108 } 109 110 return entity, replaced, nil 111 } 112 113 func injectImageDigestInEnvVars(entity K8sEntity, selector container.RefSelector, injectRef reference.Named) (K8sEntity, bool, error) { 114 envVars, err := extractEnvVars(&entity) 115 if err != nil { 116 return K8sEntity{}, false, err 117 } 118 119 replaced := false 120 for _, envVar := range envVars { 121 existingRef, err := container.ParseNamed(envVar.Value) 122 if err != nil || existingRef == nil { 123 continue 124 } 125 126 if selector.Matches(existingRef) { 127 envVar.Value = container.FamiliarString(injectRef) 128 replaced = true 129 } 130 } 131 132 return entity, replaced, nil 133 } 134 135 func InjectCommandAndArgs(entity K8sEntity, ref reference.Named, 136 cmd *v1alpha1.ImageMapOverrideCommand, args *v1alpha1.ImageMapOverrideArgs) (K8sEntity, error) { 137 entity = entity.DeepCopy() 138 139 selector := container.NewRefSelector(ref) 140 e, injected, err := injectCommandInContainers(entity, selector, cmd, args) 141 if err != nil { 142 return e, err 143 } 144 if !injected { 145 // NOTE(maia): currently we only support injecting commands into containers (i.e. the 146 // k8s yaml `container` block). This means we don't support injecting commands into CRDs. 147 return e, fmt.Errorf("could not inject command %v into entity: %s. No container found matching ref: %s. "+ 148 "Note: command overrides only supported on containers with images, not on CRDs", 149 cmd.Command, entity.Name(), container.FamiliarString(ref)) 150 } 151 152 return e, nil 153 } 154 155 func injectCommandInContainers(entity K8sEntity, selector container.RefSelector, 156 cmd *v1alpha1.ImageMapOverrideCommand, args *v1alpha1.ImageMapOverrideArgs) (K8sEntity, bool, error) { 157 var injected bool 158 containers, err := extractContainers(&entity) 159 if err != nil { 160 return K8sEntity{}, injected, err 161 } 162 163 for _, c := range containers { 164 existingRef, err := container.ParseNamed(c.Image) 165 if err != nil { 166 return K8sEntity{}, injected, err 167 } 168 169 if selector.Matches(existingRef) { 170 // The override rules of entrypoint and Command and Args are surprisingly complex! 171 // See this github thread: 172 // https://github.com/tilt-dev/tilt/issues/2918 173 if cmd != nil { 174 c.Command = cmd.Command 175 } 176 177 if args != nil { 178 c.Args = args.Args 179 } 180 181 injected = true 182 } 183 } 184 return entity, injected, nil 185 } 186 187 // HasImage indicates whether the given entity is tagged with the given image. 188 func (e K8sEntity) HasImage(image container.RefSelector, locators []ImageLocator, inEnvVars bool) (bool, error) { 189 var envVarImages []container.RefSelector 190 if inEnvVars { 191 envVarImages = []container.RefSelector{image} 192 } 193 images, err := e.FindImages(locators, envVarImages) 194 if err != nil { 195 return false, errors.Wrap(err, "HasImage") 196 } 197 198 for _, existingRef := range images { 199 if image.Matches(existingRef) { 200 return true, nil 201 } 202 } 203 204 return false, nil 205 } 206 207 func (e K8sEntity) FindImages(locators []ImageLocator, envVarImages []container.RefSelector) ([]reference.Named, error) { 208 var result []reference.Named 209 210 // Look for images in instances of Container 211 containers, err := extractContainers(&e) 212 if err != nil { 213 return nil, err 214 } 215 for _, c := range containers { 216 ref, err := container.ParseNamed(c.Image) 217 if err != nil { 218 return nil, errors.Wrapf(err, "parsing %s", c.Image) 219 } 220 221 result = append(result, ref) 222 } 223 224 var obj interface{} 225 if u, ok := e.Obj.(runtime.Unstructured); ok { 226 obj = u.UnstructuredContent() 227 } else { 228 obj = e.Obj 229 } 230 231 for _, locator := range locators { 232 refs, err := locator.Extract(e) 233 if err != nil { 234 return nil, err 235 } 236 237 result = append(result, refs...) 238 } 239 240 envVars, err := extractEnvVars(&obj) 241 if err != nil { 242 return nil, err 243 } 244 245 for _, envVar := range envVars { 246 existingRef, err := container.ParseNamed(envVar.Value) 247 if err != nil || existingRef == nil { 248 continue 249 } 250 for _, img := range envVarImages { 251 if img.Matches(existingRef) { 252 result = append(result, existingRef) 253 } 254 } 255 } 256 257 return result, nil 258 } 259 260 func PodContainsRef(pod v1.PodSpec, selector container.RefSelector) (bool, error) { 261 cRef, err := FindImageRefMatching(pod, selector) 262 if err != nil { 263 return false, err 264 } 265 266 return cRef != nil, nil 267 } 268 269 func FindImageRefMatching(pod v1.PodSpec, selector container.RefSelector) (reference.Named, error) { 270 for _, c := range pod.Containers { 271 cRef, err := container.ParseNamed(c.Image) 272 if err != nil { 273 return nil, errors.Wrap(err, "FindImageRefMatching") 274 } 275 276 if selector.Matches(cRef) { 277 return cRef, nil 278 } 279 } 280 return nil, nil 281 } 282 283 func FindImageNamedTaggedMatching(pod v1.PodSpec, selector container.RefSelector) (reference.NamedTagged, error) { 284 cRef, err := FindImageRefMatching(pod, selector) 285 if err != nil { 286 return nil, err 287 } 288 289 cTagged, ok := cRef.(reference.NamedTagged) 290 if !ok { 291 return nil, nil 292 } 293 294 return cTagged, nil 295 }