github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/container/reference.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path"
     7  
     8  	"github.com/distribution/reference"
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    12  )
    13  
    14  // RefSet describes the references for a given image:
    15  //  1. ConfigurationRef: ref as specified in the Tiltfile
    16  //  2. LocalRef(): ref as used outside of the cluster (for Docker etc.)
    17  //  3. ClusterRef(): ref as used inside the cluster (in k8s YAML etc.). Often equivalent to
    18  //     LocalRef, but in some cases they diverge: e.g. when using a local registry with KIND,
    19  //     the image localhost:1234/my-image (localRef) is referenced in the YAML as
    20  //     http://registry/my-image (clusterRef).
    21  type RefSet struct {
    22  	// Ref as specified in Tiltfile; used to match a DockerBuild with
    23  	// corresponding k8s YAML. May contain tags, etc. (Also used as
    24  	// user-facing name for this image.)
    25  	ConfigurationRef RefSelector
    26  
    27  	// (Optional) registry to prepend to ConfigurationRef to yield ref to use in update and deploy
    28  	registry *v1alpha1.RegistryHosting
    29  }
    30  
    31  func NewRefSet(confRef RefSelector, reg *v1alpha1.RegistryHosting) (RefSet, error) {
    32  	r := RefSet{
    33  		ConfigurationRef: confRef,
    34  		registry:         reg,
    35  	}
    36  	return r, r.Validate()
    37  }
    38  
    39  func MustSimpleRefSet(ref RefSelector) RefSet {
    40  	r := RefSet{
    41  		ConfigurationRef: ref,
    42  	}
    43  	if err := r.Validate(); err != nil {
    44  		panic(err)
    45  	}
    46  	return r
    47  }
    48  
    49  func RefSetFromImageMap(spec v1alpha1.ImageMapSpec, cluster *v1alpha1.Cluster) (RefSet, error) {
    50  	selector, err := SelectorFromImageMap(spec)
    51  	if err != nil {
    52  		return RefSet{}, fmt.Errorf("validating image: %v", err)
    53  	}
    54  
    55  	reg, err := RegistryFromCluster(cluster)
    56  	if err != nil {
    57  		return RefSet{}, fmt.Errorf("determining registry: %v", err)
    58  	}
    59  
    60  	refs, err := NewRefSet(selector, reg)
    61  	if err != nil {
    62  		return RefSet{}, fmt.Errorf("applying image %s to registry %s: %v", spec.Selector, reg, err)
    63  	}
    64  	return refs, nil
    65  }
    66  
    67  func (rs RefSet) WithoutRegistry() RefSet {
    68  	return MustSimpleRefSet(rs.ConfigurationRef)
    69  }
    70  
    71  func (rs RefSet) Registry() *v1alpha1.RegistryHosting {
    72  	if rs.registry == nil {
    73  		return nil
    74  	}
    75  	return rs.registry.DeepCopy()
    76  }
    77  
    78  func (rs RefSet) MustWithRegistry(reg *v1alpha1.RegistryHosting) RefSet {
    79  	rs.registry = reg
    80  	err := rs.Validate()
    81  	if err != nil {
    82  		panic(err)
    83  	}
    84  	return rs
    85  }
    86  
    87  func (rs RefSet) Validate() error {
    88  	if rs.registry != nil {
    89  		err := rs.registry.Validate(context.TODO())
    90  		if err != nil {
    91  			return errors.Wrapf(err.ToAggregate(), "validating new RefSet with configuration ref %q", rs.ConfigurationRef)
    92  		}
    93  	}
    94  	_, err := ReplaceRegistryForLocalRef(rs.ConfigurationRef, rs.registry)
    95  	if err != nil {
    96  		return errors.Wrapf(err, "validating new RefSet with configuration ref %q", rs.ConfigurationRef)
    97  	}
    98  
    99  	_, err = ReplaceRegistryForContainerRuntimeRef(rs.ConfigurationRef, rs.registry)
   100  	if err != nil {
   101  		return errors.Wrapf(err, "validating new RefSet with configuration ref %q", rs.ConfigurationRef)
   102  	}
   103  
   104  	return nil
   105  }
   106  
   107  // LocalRef returns the ref by which this image is referenced from outside the cluster
   108  // (e.g. by `docker build`, `docker push`, etc.)
   109  func (rs RefSet) LocalRef() reference.Named {
   110  	if IsEmptyRegistry(rs.registry) {
   111  		return rs.ConfigurationRef.AsNamedOnly()
   112  	}
   113  	ref, err := ReplaceRegistryForLocalRef(rs.ConfigurationRef, rs.registry)
   114  	if err != nil {
   115  		// Validation should have caught this before now :-/
   116  		panic(fmt.Sprintf("ERROR deriving LocalRef: %v", err))
   117  	}
   118  
   119  	return ref
   120  }
   121  
   122  // ClusterRef returns the ref by which this image will be pulled by
   123  // the container runtime in the cluster.
   124  //
   125  // For example, the registry host (that the user/Tilt *push* to) might be
   126  // something like `localhost:1234/foo`, referring to an exposed port from the
   127  // registry Docker container. However, when the container runtime (itself
   128  // generally running within a Docker container), won't see it on localhost,
   129  // and will instead use a reference like `registry:5000/foo`.
   130  //
   131  // If HostFromContainerRuntime is not set on the registry for the RefSet, the
   132  // Host will be used instead. This is common in cases where both the user and
   133  // the container runtime refer to the registry in the same way.
   134  //
   135  // Note that this is specific to the container runtime, which might have its
   136  // own config for the host. The local registry specification allows an
   137  // additional "ClusterFromClusterNetwork" value, which describes a generic way
   138  // for access from within the cluster network (e.g. via cluster provided DNS).
   139  // Within Tilt, this value is NOT used for business logic, so sometimes "cluster
   140  // ref" is used to refer to the container runtime ref. The API types, however,
   141  // include both values and are labeled accurately.
   142  //
   143  // TODO(milas): Rename to ContainerRuntimeRef()
   144  func (rs RefSet) ClusterRef() reference.Named {
   145  	if IsEmptyRegistry(rs.registry) {
   146  		return rs.LocalRef()
   147  	}
   148  	ref, err := ReplaceRegistryForContainerRuntimeRef(rs.ConfigurationRef, rs.registry)
   149  	if err != nil {
   150  		// Validation should have caught this before now :-/
   151  		panic(fmt.Sprintf("ERROR deriving ClusterRef: %v", err))
   152  	}
   153  	return ref
   154  }
   155  
   156  // AddTagSuffix tags the references for build/deploy.
   157  //
   158  // In most cases, we will use the tag given as-is.
   159  //
   160  // If we're in the mode where we're pushing to a single image name (for ECR), we'll
   161  // tag it with [escaped-original-name]-[suffix].
   162  func (rs RefSet) AddTagSuffix(suffix string) (TaggedRefs, error) {
   163  	tag := suffix
   164  	if rs.registry != nil && rs.registry.SingleName != "" {
   165  		tag = fmt.Sprintf("%s-%s", escapeName(path.Base(rs.ConfigurationRef.RefFamiliarName())), tag)
   166  	}
   167  
   168  	localTagged, err := reference.WithTag(rs.LocalRef(), tag)
   169  	if err != nil {
   170  		return TaggedRefs{}, errors.Wrapf(err, "tagging localRef %s as %s", rs.LocalRef().String(), tag)
   171  	}
   172  
   173  	// TODO(maia): maybe TaggedRef should behave like RefSet, where clusterRef is optional
   174  	//   and if not set, the accessor returns LocalRef instead
   175  	clusterTagged, err := reference.WithTag(rs.ClusterRef(), tag)
   176  	if err != nil {
   177  		return TaggedRefs{}, errors.Wrapf(err, "tagging clusterRef %s as %s", rs.ClusterRef().String(), tag)
   178  	}
   179  	return TaggedRefs{
   180  		LocalRef:   localTagged,
   181  		ClusterRef: clusterTagged,
   182  	}, nil
   183  }
   184  
   185  // TaggedRefs yielded by an image build
   186  type TaggedRefs struct {
   187  	// LocalRef is the image name + tag as referenced from outside cluster
   188  	// (e.g. by the user or Tilt when pushing images).
   189  	LocalRef reference.NamedTagged
   190  	// ClusterRef is the image name + tag as referenced from the
   191  	// container runtime on the cluster.
   192  	//
   193  	// TODO(milas): Rename to ContainerRuntimeRef
   194  	ClusterRef reference.NamedTagged
   195  }