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 }