github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/engine/buildcontrol/docker_compose_build_and_deployer_test.go (about) 1 package buildcontrol 2 3 import ( 4 "archive/tar" 5 "context" 6 "fmt" 7 "os" 8 "testing" 9 10 "github.com/jonboulle/clockwork" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" 15 16 "github.com/tilt-dev/wmclient/pkg/dirs" 17 18 "github.com/tilt-dev/tilt/internal/container" 19 "github.com/tilt-dev/tilt/internal/controllers/fake" 20 "github.com/tilt-dev/tilt/internal/docker" 21 "github.com/tilt-dev/tilt/internal/dockercompose" 22 "github.com/tilt-dev/tilt/internal/store" 23 "github.com/tilt-dev/tilt/internal/testutils" 24 "github.com/tilt-dev/tilt/internal/testutils/manifestbuilder" 25 "github.com/tilt-dev/tilt/internal/testutils/tempdir" 26 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 27 "github.com/tilt-dev/tilt/pkg/model" 28 ) 29 30 func TestDockerComposeTargetBuilt(t *testing.T) { 31 f := newDCBDFixture(t) 32 33 expectedContainerID := "fake-container-id" 34 f.dcCli.ContainerIDDefault = container.ID(expectedContainerID) 35 36 manifest := manifestbuilder.New(f, "fe").WithDockerCompose().Build() 37 dcTarg := manifest.DockerComposeTarget() 38 39 res, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{}) 40 if err != nil { 41 t.Fatal(err) 42 } 43 upCalls := f.dcCli.UpCalls() 44 if assert.Len(t, upCalls, 1, "expect one call to `docker-compose up`") { 45 call := upCalls[0] 46 assert.Equal(t, dcTarg.Spec, call.Spec) 47 assert.Equal(t, "fe", call.Spec.Service) 48 assert.True(t, call.ShouldBuild) 49 } 50 51 dRes := res[dcTarg.ID()].(store.DockerComposeBuildResult) 52 assert.Equal(t, expectedContainerID, dRes.Status.ContainerID) 53 } 54 55 func TestTiltBuildsImage(t *testing.T) { 56 f := newDCBDFixture(t) 57 58 iTarget := NewSanchoDockerBuildImageTarget(f) 59 manifest := manifestbuilder.New(f, "fe"). 60 WithDockerCompose(). 61 WithImageTarget(iTarget). 62 Build() 63 dcTarg := manifest.DockerComposeTarget() 64 65 res, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{}) 66 if err != nil { 67 t.Fatal(err) 68 } 69 70 assert.Equal(t, 1, f.dCli.BuildCount, "expect one docker build") 71 72 expectedTag := fmt.Sprintf("%s:%s", iTarget.ImageMapSpec.Selector, docker.TagLatest) 73 assert.Equal(t, expectedTag, f.dCli.TagTarget) 74 75 upCalls := f.dcCli.UpCalls() 76 if assert.Len(t, upCalls, 1, "expect one call to `docker-compose up`") { 77 call := upCalls[0] 78 assert.Equal(t, dcTarg.Spec, call.Spec) 79 assert.Equal(t, "fe", call.Spec.Service) 80 assert.False(t, call.ShouldBuild, "should call `up` without `--build` b/c Tilt is doing the building") 81 } 82 83 assert.Len(t, res, 2, "expect two results (one for each spec)") 84 } 85 86 func TestTiltBuildsImageWithTag(t *testing.T) { 87 f := newDCBDFixture(t) 88 89 refWithTag := "gcr.io/foo:bar" 90 iTarget := model.MustNewImageTarget(container.MustParseSelector(refWithTag)). 91 WithBuildDetails(model.DockerBuild{DockerImageSpec: v1alpha1.DockerImageSpec{Context: "-"}}) 92 manifest := manifestbuilder.New(f, "fe"). 93 WithDockerCompose(). 94 WithImageTarget(iTarget). 95 Build() 96 97 _, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{}) 98 if err != nil { 99 t.Fatal(err) 100 } 101 102 assert.Equal(t, refWithTag, f.dCli.TagTarget) 103 } 104 105 func TestDCBADRejectsAllSpecsIfOneUnsupported(t *testing.T) { 106 f := newDCBDFixture(t) 107 108 specs := []model.TargetSpec{model.DockerComposeTarget{}, model.ImageTarget{}, model.K8sTarget{}} 109 110 plan, err := f.dcbad.extract(specs) 111 assert.Empty(t, plan) 112 assert.EqualError(t, err, "DockerComposeBuildAndDeployer does not support target type model.K8sTarget") 113 } 114 115 func TestMultiStageDockerCompose(t *testing.T) { 116 f := newDCBDFixture(t) 117 118 manifest := NewSanchoDockerBuildMultiStageManifest(f). 119 WithDeployTarget(defaultDockerComposeTarget(f, "sancho")) 120 121 stateSet := store.BuildStateSet{} 122 _, err := f.BuildAndDeploy(BuildTargets(manifest), stateSet) 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 assert.Equal(t, 2, f.dCli.BuildCount) 128 assert.Equal(t, 0, f.dCli.PushCount) 129 130 expected := testutils.ExpectedFile{ 131 Path: "Dockerfile", 132 Contents: ` 133 FROM sancho-base:latest 134 ADD . . 135 RUN go install github.com/tilt-dev/sancho 136 ENTRYPOINT /go/bin/sancho 137 `, 138 } 139 testutils.AssertFileInTar(t, tar.NewReader(f.dCli.BuildContext), expected) 140 } 141 142 func TestMultiStageDockerComposeWithOnlyOneDirtyImage(t *testing.T) { 143 f := newDCBDFixture(t) 144 145 manifest := NewSanchoDockerBuildMultiStageManifest(f). 146 WithDeployTarget(defaultDockerComposeTarget(f, "sancho")) 147 148 iTargetID := manifest.ImageTargets[0].ID() 149 result := store.NewImageBuildResultSingleRef(iTargetID, container.MustParseNamedTagged("sancho-base:tilt-prebuilt")) 150 state := store.NewBuildState(result, nil, nil) 151 stateSet := store.BuildStateSet{iTargetID: state} 152 _, err := f.BuildAndDeploy(BuildTargets(manifest), stateSet) 153 if err != nil { 154 t.Fatal(err) 155 } 156 157 assert.Equal(t, 1, f.dCli.BuildCount) 158 assert.Equal(t, 0, f.dCli.PushCount) 159 160 expected := testutils.ExpectedFile{ 161 Path: "Dockerfile", 162 Contents: ` 163 FROM sancho-base:tilt-prebuilt 164 ADD . . 165 RUN go install github.com/tilt-dev/sancho 166 ENTRYPOINT /go/bin/sancho 167 `, 168 } 169 testutils.AssertFileInTar(t, tar.NewReader(f.dCli.BuildContext), expected) 170 } 171 172 func TestForceUpdateDC(t *testing.T) { 173 f := newDCBDFixture(t) 174 175 m := manifestbuilder.New(f, "fe").WithDockerCompose().Build() 176 iTargetID1 := m.ImageTargets[0].ID() 177 stateSet := store.BuildStateSet{ 178 iTargetID1: store.BuildState{FullBuildTriggered: true}, 179 } 180 181 _, err := f.BuildAndDeploy(BuildTargets(m), stateSet) 182 require.NoError(t, err) 183 184 // A force rebuild should delete the old resources. 185 assert.Equal(t, 1, len(f.dcCli.RmCalls())) 186 } 187 188 type dcbdFixture struct { 189 *tempdir.TempDirFixture 190 ctx context.Context 191 dcCli *dockercompose.FakeDCClient 192 dCli *docker.FakeClient 193 dcbad *DockerComposeBuildAndDeployer 194 st *store.TestingStore 195 ctrlClient ctrlclient.Client 196 } 197 198 func newDCBDFixture(t *testing.T) *dcbdFixture { 199 ctx, _, _ := testutils.CtxAndAnalyticsForTest() 200 201 f := tempdir.NewTempDirFixture(t) 202 203 // empty dirs for build contexts 204 _ = os.Mkdir(f.JoinPath("sancho"), 0777) 205 _ = os.Mkdir(f.JoinPath("sancho-base"), 0777) 206 207 dir := dirs.NewTiltDevDirAt(f.Path()) 208 dcCli := dockercompose.NewFakeDockerComposeClient(t, ctx) 209 dCli := docker.NewFakeClient() 210 cdc := fake.NewFakeTiltClient() 211 st := store.NewTestingStore() 212 213 // Make the fake ImageExists always return true, which is the behavior we want 214 // when testing the BuildAndDeployers. 215 dCli.ImageAlwaysExists = true 216 217 clock := clockwork.NewFakeClock() 218 dcbad, err := ProvideDockerComposeBuildAndDeployer(ctx, dcCli, dCli, cdc, st, clock, dir) 219 if err != nil { 220 t.Fatal(err) 221 } 222 return &dcbdFixture{ 223 TempDirFixture: f, 224 ctx: ctx, 225 dcCli: dcCli, 226 dCli: dCli, 227 dcbad: dcbad, 228 st: st, 229 ctrlClient: cdc, 230 } 231 } 232 233 func (f *dcbdFixture) upsertSpec(obj ctrlclient.Object) { 234 fake.UpsertSpec(f.ctx, f.T(), f.ctrlClient, obj) 235 } 236 237 func (f *dcbdFixture) updateStatus(obj ctrlclient.Object) { 238 fake.UpdateStatus(f.ctx, f.T(), f.ctrlClient, obj) 239 } 240 241 func (f *dcbdFixture) BuildAndDeploy(specs []model.TargetSpec, stateSet store.BuildStateSet) (store.BuildResultSet, error) { 242 243 cluster := &v1alpha1.Cluster{ 244 Spec: v1alpha1.ClusterSpec{ 245 Connection: &v1alpha1.ClusterConnection{ 246 Docker: &v1alpha1.DockerClusterConnection{}, 247 }, 248 }, 249 } 250 251 for _, spec := range specs { 252 iTarget, ok := spec.(model.ImageTarget) 253 if !ok || iTarget.IsLiveUpdateOnly { 254 continue 255 } 256 257 im := v1alpha1.ImageMap{ 258 ObjectMeta: metav1.ObjectMeta{Name: iTarget.ID().Name.String()}, 259 Spec: iTarget.ImageMapSpec, 260 } 261 f.upsertSpec(&im) 262 263 state := stateSet[iTarget.ID()] 264 state.Cluster = cluster 265 stateSet[iTarget.ID()] = state 266 267 imageBuildResult, ok := state.LastResult.(store.ImageBuildResult) 268 if ok { 269 im.Status = imageBuildResult.ImageMapStatus 270 } 271 f.updateStatus(&im) 272 } 273 return f.dcbad.BuildAndDeploy(f.ctx, f.st, specs, stateSet) 274 } 275 276 func defaultDockerComposeTarget(f Fixture, name string) model.DockerComposeTarget { 277 return model.DockerComposeTarget{ 278 Name: model.TargetName(name), 279 Spec: v1alpha1.DockerComposeServiceSpec{ 280 Service: name, 281 }, 282 } 283 }