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 }