github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/k8s/locator.go (about) 1 package k8s 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "github.com/distribution/reference" 8 "github.com/pkg/errors" 9 v1 "k8s.io/api/core/v1" 10 "k8s.io/apimachinery/pkg/runtime" 11 12 "github.com/tilt-dev/tilt/internal/container" 13 "github.com/tilt-dev/tilt/internal/k8s/jsonpath" 14 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 15 ) 16 17 type ImageLocator interface { 18 // Checks whether two image locators are the same. 19 EqualsImageLocator(other interface{}) bool 20 21 // Find all the images in this entity. 22 Extract(e K8sEntity) ([]reference.Named, error) 23 24 // Matches the type of this entity. 25 MatchesType(e K8sEntity) bool 26 27 // Find all the images in this entity that match the given selector, 28 // and replace them with the injected ref. 29 // If the injected ref has a pull policy sibling, set it to the given pull policy. 30 // 31 // Returns a new entity with the injected ref. Returns a boolean indicated 32 // whether there was at least one successful injection. 33 Inject(e K8sEntity, selector container.RefSelector, injectRef reference.Named, policy v1.PullPolicy) (K8sEntity, bool, error) 34 35 ToSpec() v1alpha1.KubernetesImageLocator 36 } 37 38 const imagePullPolicyKey = "imagePullPolicy" 39 40 func LocatorMatchesOne(l ImageLocator, entities []K8sEntity) bool { 41 for _, e := range entities { 42 if l.MatchesType(e) { 43 return true 44 } 45 } 46 return false 47 } 48 49 type JSONPathImageLocator struct { 50 selector ObjectSelector 51 path JSONPath 52 } 53 54 func MustJSONPathImageLocator(selector ObjectSelector, path string) *JSONPathImageLocator { 55 locator, err := NewJSONPathImageLocator(selector, path) 56 if err != nil { 57 panic(err) 58 } 59 return locator 60 } 61 62 func NewJSONPathImageLocator(selector ObjectSelector, path string) (*JSONPathImageLocator, error) { 63 p, err := NewJSONPath(path) 64 if err != nil { 65 return nil, err 66 } 67 return &JSONPathImageLocator{ 68 selector: selector, 69 path: p, 70 }, nil 71 } 72 73 func (l *JSONPathImageLocator) ToSpec() v1alpha1.KubernetesImageLocator { 74 return v1alpha1.KubernetesImageLocator{ 75 ObjectSelector: l.selector.ToSpec(), 76 Path: l.path.String(), 77 } 78 } 79 80 func (l *JSONPathImageLocator) EqualsImageLocator(other interface{}) bool { 81 otherL, ok := other.(*JSONPathImageLocator) 82 if !ok { 83 return false 84 } 85 86 return l.path.path == otherL.path.path && 87 l.selector.EqualsSelector(otherL.selector) 88 } 89 90 func (l *JSONPathImageLocator) MatchesType(e K8sEntity) bool { 91 return l.selector.Matches(e) 92 } 93 94 func (l *JSONPathImageLocator) unpack(e K8sEntity) interface{} { 95 if u, ok := e.Obj.(runtime.Unstructured); ok { 96 return u.UnstructuredContent() 97 } 98 return e.Obj 99 } 100 101 func (l *JSONPathImageLocator) Extract(e K8sEntity) ([]reference.Named, error) { 102 if !l.selector.Matches(e) { 103 return nil, nil 104 } 105 106 // also look for images in any json paths that were specified for this entity 107 images, err := l.path.FindStrings(l.unpack(e)) 108 if err != nil { 109 return nil, err 110 } 111 112 result := make([]reference.Named, 0, len(images)) 113 for _, image := range images { 114 ref, err := container.ParseNamed(image) 115 if err != nil { 116 return nil, errors.Wrapf(err, "error parsing image '%s' at json path '%s'", image, l.path) 117 } 118 result = append(result, ref) 119 } 120 return result, nil 121 } 122 123 func (l *JSONPathImageLocator) Inject(e K8sEntity, selector container.RefSelector, injectRef reference.Named, pullPolicy v1.PullPolicy) (K8sEntity, bool, error) { 124 if !l.selector.Matches(e) { 125 return e, false, nil 126 } 127 128 modified := false 129 err := l.path.VisitStrings(l.unpack(e), func(val jsonpath.Value, str string) error { 130 ref, err := container.ParseNamed(str) 131 if err != nil { 132 return nil 133 } 134 135 if selector.Matches(ref) { 136 val.Set(reflect.ValueOf(container.FamiliarString(injectRef))) 137 modified = true 138 139 if pullPolicyVal, ok := val.Sibling(imagePullPolicyKey); ok && pullPolicyVal.CanSet() { 140 pullPolicyVal.Set(reflect.ValueOf(string(pullPolicy))) 141 } 142 } 143 return nil 144 }) 145 if err != nil { 146 return e, false, err 147 } 148 return e, modified, nil 149 } 150 151 var _ ImageLocator = &JSONPathImageLocator{} 152 153 type JSONPathImageObjectLocator struct { 154 selector ObjectSelector 155 path JSONPath 156 repoField string 157 tagField string 158 } 159 160 func MustJSONPathImageObjectLocator(selector ObjectSelector, path, repoField, tagField string) *JSONPathImageObjectLocator { 161 locator, err := NewJSONPathImageObjectLocator(selector, path, repoField, tagField) 162 if err != nil { 163 panic(err) 164 } 165 return locator 166 } 167 168 func NewJSONPathImageObjectLocator(selector ObjectSelector, path, repoField, tagField string) (*JSONPathImageObjectLocator, error) { 169 p, err := NewJSONPath(path) 170 if err != nil { 171 return nil, err 172 } 173 return &JSONPathImageObjectLocator{ 174 selector: selector, 175 path: p, 176 repoField: repoField, 177 tagField: tagField, 178 }, nil 179 } 180 181 func (l *JSONPathImageObjectLocator) EqualsImageLocator(other interface{}) bool { 182 otherL, ok := other.(*JSONPathImageObjectLocator) 183 if !ok { 184 return false 185 } 186 return l.path.path == otherL.path.path && 187 l.repoField == otherL.repoField && 188 l.tagField == otherL.tagField && 189 l.selector.EqualsSelector(otherL.selector) 190 } 191 192 func (l *JSONPathImageObjectLocator) MatchesType(e K8sEntity) bool { 193 return l.selector.Matches(e) 194 } 195 196 func (l *JSONPathImageObjectLocator) unpack(e K8sEntity) interface{} { 197 if u, ok := e.Obj.(runtime.Unstructured); ok { 198 return u.UnstructuredContent() 199 } 200 return e.Obj 201 } 202 203 func (l *JSONPathImageObjectLocator) extractImageFromMap(val jsonpath.Value) (reference.Named, error) { 204 m, ok := val.Interface().(map[string]interface{}) 205 if !ok { 206 return nil, fmt.Errorf("May only match maps (json_path=%q)\nGot Type: %s\nGot Value: %s", 207 l.path.path, val.Type(), val) 208 } 209 210 repoField, ok := m[l.repoField].(string) 211 imageString := "" 212 if ok { 213 imageString = repoField 214 } 215 216 tagField, ok := m[l.tagField].(string) 217 if ok && tagField != "" { 218 imageString = fmt.Sprintf("%s:%s", repoField, tagField) 219 } 220 221 return container.ParseNamed(imageString) 222 } 223 224 func (l *JSONPathImageObjectLocator) Extract(e K8sEntity) ([]reference.Named, error) { 225 if !l.selector.Matches(e) { 226 return nil, nil 227 } 228 229 result := make([]reference.Named, 0) 230 err := l.path.Visit(l.unpack(e), func(val jsonpath.Value) error { 231 ref, err := l.extractImageFromMap(val) 232 if err != nil { 233 return err 234 } 235 result = append(result, ref) 236 return nil 237 }) 238 if err != nil { 239 return nil, err 240 } 241 return result, nil 242 } 243 244 // pullPolicy is ignored for this injector for now, since it's less standard 245 // if it turns out there's a demand for this, we can plumb it through to image_object 246 func (l *JSONPathImageObjectLocator) Inject(e K8sEntity, selector container.RefSelector, injectRef reference.Named, _ v1.PullPolicy) (K8sEntity, bool, error) { 247 if !l.selector.Matches(e) { 248 return e, false, nil 249 } 250 251 tagged, isTagged := injectRef.(reference.Tagged) 252 253 modified := false 254 err := l.path.Visit(l.unpack(e), func(val jsonpath.Value) error { 255 ref, err := l.extractImageFromMap(val) 256 if err != nil { 257 return err 258 } 259 if selector.Matches(ref) { 260 m := val.Interface().(map[string]interface{}) 261 m[l.repoField] = reference.FamiliarName(injectRef) 262 if isTagged { 263 m[l.tagField] = tagged.Tag() 264 } 265 modified = true 266 } 267 return nil 268 }) 269 if err != nil { 270 return e, false, err 271 } 272 return e, modified, nil 273 } 274 275 func (l *JSONPathImageObjectLocator) ToSpec() v1alpha1.KubernetesImageLocator { 276 return v1alpha1.KubernetesImageLocator{ 277 ObjectSelector: l.selector.ToSpec(), 278 Path: l.path.String(), 279 Object: &v1alpha1.KubernetesImageObjectDescriptor{ 280 RepoField: l.repoField, 281 TagField: l.tagField, 282 }, 283 } 284 } 285 286 var _ ImageLocator = &JSONPathImageObjectLocator{}