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  }