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

     1  package tiltfile
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/distribution/reference"
     8  	"github.com/schollz/closestmatch"
     9  
    10  	"github.com/tilt-dev/tilt/internal/container"
    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  	byImageMapName   map[string]*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  		byImageMapName:       make(map[string]*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", container.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.byImageMapName[img.ImageMapName()] = img
    61  	idx.images = append(idx.images, img)
    62  	return nil
    63  }
    64  
    65  func (idx *buildIndex) findBuilderByImageMapName(im string) *dockerImage {
    66  	return idx.byImageMapName[im]
    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) unmatchedImages() []*dockerImage {
    92  	unmatchedImages := make([]*dockerImage, 0)
    93  	for _, image := range idx.images {
    94  		if !image.matched {
    95  			unmatchedImages = append(unmatchedImages, image)
    96  		}
    97  	}
    98  	return unmatchedImages
    99  }
   100  
   101  func (idx *buildIndex) unmatchedImageWarning(image *dockerImage, configType string) error {
   102  
   103  	bagSizes := []int{2, 3, 4}
   104  	cm := closestmatch.New(idx.consumedImageNames, bagSizes)
   105  	matchLines := []string{}
   106  	for i, match := range cm.ClosestN(image.configurationRef.RefFamiliarName(), 3) {
   107  		// If there are no matches, the closestmatch library sometimes returns
   108  		// an empty string
   109  		if match == "" {
   110  			break
   111  		}
   112  		if i == 0 {
   113  			matchLines = append(matchLines, "Did you mean…\n")
   114  		}
   115  		matchLines = append(matchLines, fmt.Sprintf("    - %s\n", match))
   116  	}
   117  
   118  	return fmt.Errorf("Image not used in any %s config:\n    ✕ %v\n%sSkipping this image build\n"+
   119  		"If this is deliberate, suppress this warning with: update_settings(suppress_unused_image_warnings=[%q])",
   120  		configType,
   121  		container.FamiliarString(image.configurationRef),
   122  		strings.Join(matchLines, ""),
   123  		container.FamiliarString(image.configurationRef))
   124  }