github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/docker_compose_build_and_deployer.go (about)

     1  package engine
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/docker/distribution/reference"
     8  	"github.com/opentracing/opentracing-go"
     9  
    10  	"github.com/windmilleng/tilt/internal/build"
    11  	"github.com/windmilleng/tilt/internal/container"
    12  	"github.com/windmilleng/tilt/internal/docker"
    13  	"github.com/windmilleng/tilt/internal/dockercompose"
    14  	"github.com/windmilleng/tilt/internal/store"
    15  	"github.com/windmilleng/tilt/pkg/logger"
    16  	"github.com/windmilleng/tilt/pkg/model"
    17  )
    18  
    19  type DockerComposeBuildAndDeployer struct {
    20  	dcc   dockercompose.DockerComposeClient
    21  	dc    docker.Client
    22  	icb   *imageAndCacheBuilder
    23  	clock build.Clock
    24  }
    25  
    26  var _ BuildAndDeployer = &DockerComposeBuildAndDeployer{}
    27  
    28  func NewDockerComposeBuildAndDeployer(dcc dockercompose.DockerComposeClient, dc docker.Client,
    29  	icb *imageAndCacheBuilder, c build.Clock) *DockerComposeBuildAndDeployer {
    30  	return &DockerComposeBuildAndDeployer{
    31  		dcc:   dcc,
    32  		dc:    dc,
    33  		icb:   icb,
    34  		clock: c,
    35  	}
    36  }
    37  
    38  // Extract the targets we can apply -- DCBaD supports ImageTargets and DockerComposeTargets.
    39  func (bd *DockerComposeBuildAndDeployer) extract(specs []model.TargetSpec) ([]model.ImageTarget, []model.DockerComposeTarget) {
    40  	var iTargets []model.ImageTarget
    41  	var dcTargets []model.DockerComposeTarget
    42  
    43  	for _, s := range specs {
    44  		switch s := s.(type) {
    45  		case model.ImageTarget:
    46  			iTargets = append(iTargets, s)
    47  		case model.DockerComposeTarget:
    48  			dcTargets = append(dcTargets, s)
    49  		default:
    50  			// unrecognized target
    51  			return nil, nil
    52  		}
    53  	}
    54  	return iTargets, dcTargets
    55  }
    56  
    57  func (bd *DockerComposeBuildAndDeployer) BuildAndDeploy(ctx context.Context, st store.RStore, specs []model.TargetSpec, currentState store.BuildStateSet) (store.BuildResultSet, error) {
    58  	iTargets, dcTargets := bd.extract(specs)
    59  	if len(dcTargets) != 1 {
    60  		return store.BuildResultSet{}, SilentRedirectToNextBuilderf(
    61  			"DockerComposeBuildAndDeployer requires exactly one dcTarget (got %d)", len(dcTargets))
    62  	}
    63  	dcTarget := dcTargets[0]
    64  
    65  	span, ctx := opentracing.StartSpanFromContext(ctx, "DockerComposeBuildAndDeployer-BuildAndDeploy")
    66  	span.SetTag("target", dcTargets[0].Name)
    67  	defer span.Finish()
    68  
    69  	q, err := NewImageTargetQueue(ctx, iTargets, currentState, bd.icb.ib.ImageExists)
    70  	if err != nil {
    71  		return store.BuildResultSet{}, err
    72  	}
    73  
    74  	numStages := q.CountDirty()
    75  	haveImage := len(iTargets) > 0
    76  
    77  	ps := build.NewPipelineState(ctx, numStages, bd.clock)
    78  	defer func() { ps.End(ctx, err) }()
    79  
    80  	iTargetMap := model.ImageTargetsByID(iTargets)
    81  	err = q.RunBuilds(func(target model.TargetSpec, state store.BuildState, depResults []store.BuildResult) (store.BuildResult, error) {
    82  		iTarget, ok := target.(model.ImageTarget)
    83  		if !ok {
    84  			return nil, fmt.Errorf("Not an image target: %T", target)
    85  		}
    86  
    87  		iTarget, err := injectImageDependencies(iTarget, iTargetMap, depResults)
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  
    92  		expectedRef := iTarget.ConfigurationRef
    93  
    94  		// NOTE(maia): we assume that this func takes one DC target and up to one image target
    95  		// corresponding to that service. If this func ever supports specs for more than one
    96  		// service at once, we'll have to match up image build results to DC target by ref.
    97  		ref, err := bd.icb.Build(ctx, iTarget, currentState[iTarget.ID()], ps)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  
   102  		ref, err = bd.tagWithExpected(ctx, ref, expectedRef)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  
   107  		return store.NewImageBuildResult(iTarget.ID(), ref), nil
   108  	})
   109  
   110  	if err != nil {
   111  		return store.BuildResultSet{}, err
   112  	}
   113  
   114  	stdout := logger.Get(ctx).Writer(logger.InfoLvl)
   115  	stderr := logger.Get(ctx).Writer(logger.InfoLvl)
   116  	err = bd.dcc.Up(ctx, dcTarget.ConfigPaths, dcTarget.Name, !haveImage, stdout, stderr)
   117  	if err != nil {
   118  		return store.BuildResultSet{}, err
   119  	}
   120  
   121  	// NOTE(dmiller): right now we only need this the first time. In the future
   122  	// it might be worth it to move this somewhere else
   123  	cid, err := bd.dcc.ContainerID(ctx, dcTarget.ConfigPaths, dcTarget.Name)
   124  	if err != nil {
   125  		return store.BuildResultSet{}, err
   126  	}
   127  
   128  	results := q.results
   129  	results[dcTarget.ID()] = store.NewDockerComposeDeployResult(dcTarget.ID(), cid)
   130  	return results, nil
   131  }
   132  
   133  func (bd *DockerComposeBuildAndDeployer) tagWithExpected(ctx context.Context, ref reference.NamedTagged,
   134  	expected container.RefSelector) (reference.NamedTagged, error) {
   135  	var tagAs reference.NamedTagged
   136  	expectedNt, err := container.ParseNamedTagged(expected.String())
   137  	if err == nil {
   138  		// expected ref already includes a tag, so just tag the image as that
   139  		tagAs = expectedNt
   140  	} else {
   141  		// expected ref is just a name, so tag it as `latest` b/c that's what Docker Compose wants
   142  		tagAs, err = reference.WithTag(ref, docker.TagLatest)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  	}
   147  
   148  	err = bd.dc.ImageTag(ctx, ref.String(), tagAs.String())
   149  	return tagAs, err
   150  }