github.com/grahambrereton-form3/tilt@v0.10.18/internal/tiltfile/build_index.go (about)

     1  package tiltfile
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/docker/distribution/reference"
     8  	"github.com/schollz/closestmatch"
     9  
    10  	"github.com/windmilleng/tilt/pkg/model"
    11  )
    12  
    13  // An index of all the images that we know how to build.
    14  type buildIndex struct {
    15  	// keep a slice so that the order of iteration is deterministic
    16  	images []*dockerImage
    17  
    18  	imagesByName     map[string]*dockerImage
    19  	imagesBySelector map[string]*dockerImage
    20  	byTargetID       map[model.TargetID]*dockerImage
    21  
    22  	consumedImageNames   []string
    23  	consumedImageNameMap map[string]bool
    24  }
    25  
    26  func newBuildIndex() *buildIndex {
    27  	return &buildIndex{
    28  		imagesBySelector:     make(map[string]*dockerImage),
    29  		imagesByName:         make(map[string]*dockerImage),
    30  		byTargetID:           make(map[model.TargetID]*dockerImage),
    31  		consumedImageNameMap: make(map[string]bool),
    32  	}
    33  }
    34  
    35  func (idx *buildIndex) addImage(img *dockerImage) error {
    36  	selector := img.configurationRef
    37  	name := selector.RefName()
    38  	_, hasExisting := idx.imagesBySelector[selector.String()]
    39  	if hasExisting {
    40  		return fmt.Errorf("Image for ref %q has already been defined", reference.FamiliarString(selector))
    41  	}
    42  
    43  	idx.imagesBySelector[selector.String()] = img
    44  
    45  	_, hasExistingName := idx.imagesByName[name]
    46  	if hasExistingName {
    47  		// If the two selectors have the same name but different refs, they must
    48  		// have different tags. Make all the selectors "exact", so that they
    49  		// only match the exact tag.
    50  		img.configurationRef = img.configurationRef.WithExactMatch()
    51  
    52  		for _, image := range idx.images {
    53  			if image.configurationRef.RefName() == name {
    54  				image.configurationRef = image.configurationRef.WithExactMatch()
    55  			}
    56  		}
    57  	}
    58  
    59  	idx.imagesByName[name] = img
    60  	idx.byTargetID[img.ID()] = img
    61  	idx.images = append(idx.images, img)
    62  	return nil
    63  }
    64  
    65  func (idx *buildIndex) findBuilderByID(id model.TargetID) *dockerImage {
    66  	return idx.byTargetID[id]
    67  }
    68  
    69  // Many things can consume image builds:
    70  // - k8s yaml
    71  // - docker-compose yaml
    72  // - the from command in other images
    73  // Check to see if we have a build target for that image,
    74  // and mark that build target as consumed by an larger target.
    75  func (idx *buildIndex) findBuilderForConsumedImage(ref reference.Named) *dockerImage {
    76  	name := reference.FamiliarName(ref)
    77  	if !idx.consumedImageNameMap[name] {
    78  		idx.consumedImageNameMap[name] = true
    79  		idx.consumedImageNames = append(idx.consumedImageNames, name)
    80  	}
    81  
    82  	for _, image := range idx.images {
    83  		if image.configurationRef.Matches(ref) {
    84  			image.matched = true
    85  			return image
    86  		}
    87  	}
    88  	return nil
    89  }
    90  
    91  func (idx *buildIndex) assertAllMatched() error {
    92  	for _, image := range idx.images {
    93  		if !image.matched {
    94  			bagSizes := []int{2, 3, 4}
    95  			cm := closestmatch.New(idx.consumedImageNames, bagSizes)
    96  			matchLines := []string{}
    97  			for i, match := range cm.ClosestN(image.configurationRef.RefFamiliarName(), 3) {
    98  				// If there are no matches, the closestmatch library sometimes returns
    99  				// an empty string
   100  				if match == "" {
   101  					break
   102  				}
   103  				if i == 0 {
   104  					matchLines = append(matchLines, "Did you mean…\n")
   105  				}
   106  				matchLines = append(matchLines, fmt.Sprintf("    - %s\n", match))
   107  			}
   108  
   109  			return fmt.Errorf("Image not used in any deploy config:\n    ✕ %v\n%sSkipping this image build",
   110  				reference.FamiliarString(image.configurationRef), strings.Join(matchLines, ""))
   111  		}
   112  	}
   113  	return nil
   114  }