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 }