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  }