github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/image_and_cache_builder.go (about) 1 package engine 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/docker/distribution/reference" 8 9 "github.com/windmilleng/tilt/internal/build" 10 "github.com/windmilleng/tilt/internal/dockerfile" 11 "github.com/windmilleng/tilt/internal/ignore" 12 "github.com/windmilleng/tilt/internal/store" 13 "github.com/windmilleng/tilt/pkg/logger" 14 "github.com/windmilleng/tilt/pkg/model" 15 ) 16 17 type imageAndCacheBuilder struct { 18 ib build.ImageBuilder 19 cb build.CacheBuilder 20 custb build.CustomBuilder 21 updateMode UpdateMode 22 } 23 24 func NewImageAndCacheBuilder(ib build.ImageBuilder, cb build.CacheBuilder, custb build.CustomBuilder, updateMode UpdateMode) *imageAndCacheBuilder { 25 return &imageAndCacheBuilder{ 26 ib: ib, 27 cb: cb, 28 custb: custb, 29 updateMode: updateMode, 30 } 31 } 32 33 func (icb *imageAndCacheBuilder) Build(ctx context.Context, iTarget model.ImageTarget, state store.BuildState, ps *build.PipelineState) (reference.NamedTagged, error) { 34 var n reference.NamedTagged 35 36 userFacingRefName := reference.FamiliarString(iTarget.ConfigurationRef) 37 refToBuild := iTarget.DeploymentRef 38 cacheInputs := icb.createCacheInputs(iTarget) 39 cacheRef, err := icb.cb.FetchCache(ctx, cacheInputs) 40 if err != nil { 41 return nil, err 42 } 43 44 switch bd := iTarget.BuildDetails.(type) { 45 case model.DockerBuild: 46 ps.StartPipelineStep(ctx, "Building Dockerfile: [%s]", userFacingRefName) 47 defer ps.EndPipelineStep(ctx) 48 49 df := icb.dockerfile(iTarget, cacheRef) 50 ref, err := icb.ib.BuildImage(ctx, ps, refToBuild, df, bd.BuildPath, 51 ignore.CreateBuildContextFilter(iTarget), bd.BuildArgs, bd.TargetStage) 52 53 if err != nil { 54 return nil, err 55 } 56 n = ref 57 58 go icb.maybeCreateCacheFrom(ctx, cacheInputs, ref, state, iTarget, cacheRef) 59 case model.FastBuild: 60 ps.StartPipelineStep(ctx, "Building from scratch: [%s]", userFacingRefName) 61 defer ps.EndPipelineStep(ctx) 62 63 df := icb.baseDockerfile(bd, cacheRef, iTarget.CachePaths()) 64 runs := bd.Runs 65 ref, err := icb.ib.DeprecatedFastBuildImage(ctx, ps, refToBuild, df, bd.Syncs, ignore.CreateBuildContextFilter(iTarget), runs, bd.Entrypoint) 66 67 if err != nil { 68 return nil, err 69 } 70 n = ref 71 go icb.maybeCreateCacheFrom(ctx, cacheInputs, ref, state, iTarget, cacheRef) 72 case model.CustomBuild: 73 ps.StartPipelineStep(ctx, "Building Custom Build: [%s]", userFacingRefName) 74 defer ps.EndPipelineStep(ctx) 75 ref, err := icb.custb.Build(ctx, refToBuild, bd.Command, bd.Tag) 76 if err != nil { 77 return nil, err 78 } 79 n = ref 80 default: 81 // Theoretically this should never trip b/c we `validate` the manifest beforehand...? 82 // If we get here, something is very wrong. 83 return nil, fmt.Errorf("image %q has no valid buildDetails (neither DockerBuildInfo nor FastBuildInfo)", iTarget.ConfigurationRef) 84 } 85 86 return n, nil 87 } 88 89 func (icb *imageAndCacheBuilder) dockerfile(image model.ImageTarget, cacheRef reference.NamedTagged) dockerfile.Dockerfile { 90 df := dockerfile.Dockerfile(image.DockerBuildInfo().Dockerfile) 91 if cacheRef == nil { 92 return df 93 } 94 95 if len(image.CachePaths()) == 0 { 96 return df 97 } 98 99 _, restDf, ok := df.SplitIntoBaseDockerfile() 100 if !ok { 101 return df 102 } 103 104 // Replace all the lines before the ADD with a load from the Tilt cache. 105 return dockerfile.FromExisting(cacheRef). 106 WithLabel(build.CacheImage, "0"). // sadly there's no way to unset a label :sob: 107 Append(restDf) 108 } 109 110 func (icb *imageAndCacheBuilder) baseDockerfile(fbInfo model.FastBuild, 111 cacheRef build.CacheRef, cachePaths []string) dockerfile.Dockerfile { 112 df := dockerfile.Dockerfile(fbInfo.BaseDockerfile) 113 if cacheRef == nil { 114 return df 115 } 116 117 if len(cachePaths) == 0 { 118 return df 119 } 120 121 // Use the cache as the new base dockerfile. 122 return dockerfile.FromExisting(cacheRef). 123 WithLabel(build.CacheImage, "0") // sadly there's no way to unset a label :sob: 124 } 125 126 func (icb *imageAndCacheBuilder) createCacheInputs(iTarget model.ImageTarget) build.CacheInputs { 127 baseDockerfile := dockerfile.Dockerfile(iTarget.TopFastBuildInfo().BaseDockerfile) 128 if dbInfo, ok := iTarget.BuildDetails.(model.DockerBuild); ok { 129 df := dockerfile.Dockerfile(dbInfo.Dockerfile) 130 var ok bool 131 baseDockerfile, _, ok = df.SplitIntoBaseDockerfile() 132 if !ok { 133 return build.CacheInputs{} 134 } 135 } 136 137 return build.CacheInputs{ 138 Ref: iTarget.ConfigurationRef.AsNamedOnly(), 139 CachePaths: iTarget.CachePaths(), 140 BaseDockerfile: baseDockerfile, 141 } 142 } 143 144 func (icb *imageAndCacheBuilder) maybeCreateCacheFrom(ctx context.Context, cacheInputs build.CacheInputs, sourceRef reference.NamedTagged, state store.BuildState, image model.ImageTarget, oldCacheRef reference.NamedTagged) { 145 // Only create the cache the first time we build the image. 146 if state.LastSuccessfulResult != nil { 147 return 148 } 149 150 // Only create the cache if there is no existing cache 151 if oldCacheRef != nil { 152 return 153 } 154 155 var buildArgs model.DockerBuildArgs 156 if dbInfo, ok := image.BuildDetails.(model.DockerBuild); ok { 157 buildArgs = dbInfo.BuildArgs 158 } 159 160 err := icb.cb.CreateCacheFrom(ctx, cacheInputs, sourceRef, buildArgs) 161 if err != nil { 162 logger.Get(ctx).Debugf("Could not create cache: %v", err) 163 } 164 }