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

     1  package engine
     2  
     3  import (
     4  	"archive/tar"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/docker/distribution/reference"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/opencontainers/go-digest"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/windmilleng/wmclient/pkg/dirs"
    17  	v1 "k8s.io/api/apps/v1"
    18  	corev1 "k8s.io/api/core/v1"
    19  
    20  	"github.com/windmilleng/tilt/internal/container"
    21  	"github.com/windmilleng/tilt/internal/docker"
    22  	"github.com/windmilleng/tilt/internal/k8s"
    23  	"github.com/windmilleng/tilt/internal/k8s/testyaml"
    24  	"github.com/windmilleng/tilt/internal/store"
    25  	"github.com/windmilleng/tilt/internal/testutils"
    26  	"github.com/windmilleng/tilt/internal/testutils/manifestbuilder"
    27  	"github.com/windmilleng/tilt/internal/testutils/tempdir"
    28  	"github.com/windmilleng/tilt/pkg/model"
    29  )
    30  
    31  func TestDockerBuildWithCache(t *testing.T) {
    32  	f := newIBDFixture(t, k8s.EnvGKE)
    33  	defer f.TearDown()
    34  
    35  	manifest := NewSanchoDockerBuildManifestWithCache(f, []string{"/root/.cache"})
    36  	cache := "gcr.io/some-project-162817/sancho:tilt-cache-3de427a264f80719a58a9abd456487b3"
    37  	f.docker.Images[cache] = types.ImageInspect{}
    38  
    39  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	expected := expectedFile{
    45  		Path: "Dockerfile",
    46  		Contents: `FROM gcr.io/some-project-162817/sancho:tilt-cache-3de427a264f80719a58a9abd456487b3
    47  LABEL "tilt.cache"="0"
    48  ADD . .
    49  RUN go install github.com/windmilleng/sancho
    50  ENTRYPOINT /go/bin/sancho
    51  `,
    52  	}
    53  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildOptions.Context), expected)
    54  }
    55  
    56  func TestBaseDockerfileWithCache(t *testing.T) {
    57  	f := newIBDFixture(t, k8s.EnvGKE)
    58  	defer f.TearDown()
    59  
    60  	manifest := NewSanchoFastBuildManifestWithCache(f, []string{"/root/.cache"})
    61  	cache := "gcr.io/some-project-162817/sancho:tilt-cache-3de427a264f80719a58a9abd456487b3"
    62  	f.docker.Images[cache] = types.ImageInspect{}
    63  
    64  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	expected := expectedFile{
    70  		Path: "Dockerfile",
    71  		Contents: `FROM gcr.io/some-project-162817/sancho:tilt-cache-3de427a264f80719a58a9abd456487b3
    72  LABEL "tilt.cache"="0"
    73  ADD . /
    74  RUN ["go", "install", "github.com/windmilleng/sancho"]
    75  ENTRYPOINT ["/go/bin/sancho"]
    76  LABEL "tilt.buildMode"="scratch"`,
    77  	}
    78  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildOptions.Context), expected)
    79  }
    80  
    81  func TestDeployTwinImages(t *testing.T) {
    82  	f := newIBDFixture(t, k8s.EnvGKE)
    83  	defer f.TearDown()
    84  
    85  	sancho := NewSanchoFastBuildManifest(f)
    86  	manifest := sancho.WithDeployTarget(sancho.K8sTarget().AppendYAML(SanchoTwinYAML))
    87  	result, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  
    92  	id := manifest.ImageTargetAt(0).ID()
    93  	expectedImage := "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95"
    94  	image := store.ImageFromBuildResult(result[id])
    95  	assert.Equal(t, expectedImage, image.String())
    96  	assert.Equalf(t, 2, strings.Count(f.k8s.Yaml, expectedImage),
    97  		"Expected image to update twice in YAML: %s", f.k8s.Yaml)
    98  }
    99  
   100  func TestDeployPodWithMultipleImages(t *testing.T) {
   101  	f := newIBDFixture(t, k8s.EnvGKE)
   102  	defer f.TearDown()
   103  
   104  	iTarget1 := NewSanchoDockerBuildImageTarget(f)
   105  	iTarget2 := NewSanchoSidecarDockerBuildImageTarget(f)
   106  	kTarget := model.K8sTarget{Name: "sancho", YAML: testyaml.SanchoSidecarYAML}.
   107  		WithDependencyIDs([]model.TargetID{iTarget1.ID(), iTarget2.ID()})
   108  	targets := []model.TargetSpec{iTarget1, iTarget2, kTarget}
   109  
   110  	result, err := f.ibd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{})
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  
   115  	assert.Equal(t, 2, f.docker.BuildCount)
   116  
   117  	expectedSanchoRef := "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95"
   118  	image := store.ImageFromBuildResult(result[iTarget1.ID()])
   119  	assert.Equal(t, expectedSanchoRef, image.String())
   120  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSanchoRef),
   121  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   122  
   123  	expectedSidecarRef := "gcr.io/some-project-162817/sancho-sidecar:tilt-11cd0b38bc3ceb95"
   124  	image = store.ImageFromBuildResult(result[iTarget2.ID()])
   125  	assert.Equal(t, expectedSidecarRef, image.String())
   126  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSidecarRef),
   127  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   128  }
   129  
   130  func TestDeployPodWithMultipleLiveUpdateImages(t *testing.T) {
   131  	f := newIBDFixture(t, k8s.EnvGKE)
   132  	defer f.TearDown()
   133  	f.ibd.injectSynclet = true
   134  
   135  	iTarget1 := NewSanchoLiveUpdateImageTarget(f)
   136  	iTarget2 := NewSanchoSidecarLiveUpdateImageTarget(f)
   137  
   138  	kTarget := model.K8sTarget{Name: "sancho", YAML: testyaml.SanchoSidecarYAML}.
   139  		WithDependencyIDs([]model.TargetID{iTarget1.ID(), iTarget2.ID()})
   140  	targets := []model.TargetSpec{iTarget1, iTarget2, kTarget}
   141  
   142  	result, err := f.ibd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{})
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  
   147  	assert.Equal(t, 2, f.docker.BuildCount)
   148  
   149  	expectedSanchoRef := "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95"
   150  	image := store.ImageFromBuildResult(result[iTarget1.ID()])
   151  	assert.Equal(t, expectedSanchoRef, image.String())
   152  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSanchoRef),
   153  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   154  
   155  	expectedSidecarRef := "gcr.io/some-project-162817/sancho-sidecar:tilt-11cd0b38bc3ceb95"
   156  	image = store.ImageFromBuildResult(result[iTarget2.ID()])
   157  	assert.Equal(t, expectedSidecarRef, image.String())
   158  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSidecarRef),
   159  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   160  
   161  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, "gcr.io/windmill-public-containers/tilt-synclet:"),
   162  		"Expected synclet to be injected once in YAML: %s", f.k8s.Yaml)
   163  }
   164  
   165  func TestNoImageTargets(t *testing.T) {
   166  	f := newIBDFixture(t, k8s.EnvGKE)
   167  	defer f.TearDown()
   168  
   169  	targName := "some-k8s-manifest"
   170  	specs := []model.TargetSpec{
   171  		model.K8sTarget{
   172  			Name: model.TargetName(targName),
   173  			YAML: testyaml.LonelyPodYAML,
   174  		},
   175  	}
   176  
   177  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, specs, store.BuildStateSet{})
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  
   182  	assert.Equal(t, 0, f.docker.BuildCount, "expect no docker builds")
   183  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, "image: gcr.io/windmill-public-containers/lonely-pod"),
   184  		"Expected lonely-pod image to appear once in YAML: %s", f.k8s.Yaml)
   185  
   186  	expectedLabelStr := fmt.Sprintf("%s: %s", k8s.ManagedByLabel, k8s.ManagedByValue)
   187  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedLabelStr),
   188  		"Expected \"%s\" label to appear once in YAML: %s", expectedLabelStr, f.k8s.Yaml)
   189  }
   190  
   191  func TestImageIsClean(t *testing.T) {
   192  	f := newIBDFixture(t, k8s.EnvGKE)
   193  	defer f.TearDown()
   194  
   195  	manifest := NewSanchoDockerBuildManifest(f)
   196  	iTargetID1 := manifest.ImageTargets[0].ID()
   197  	result1 := store.NewImageBuildResult(iTargetID1, container.MustParseNamedTagged("sancho-base:tilt-prebuilt1"))
   198  
   199  	f.docker.ImageListCount = 1
   200  
   201  	stateSet := store.BuildStateSet{
   202  		iTargetID1: store.NewBuildState(result1, []string{}),
   203  	}
   204  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), stateSet)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  
   209  	// Expect no build or push, b/c image is clean (i.e. last build was an image build and
   210  	// no file changes since).
   211  	assert.Equal(t, 0, f.docker.BuildCount)
   212  	assert.Equal(t, 0, f.docker.PushCount)
   213  }
   214  
   215  func TestImageIsDirtyAfterContainerBuild(t *testing.T) {
   216  	f := newIBDFixture(t, k8s.EnvGKE)
   217  	defer f.TearDown()
   218  
   219  	manifest := NewSanchoDockerBuildManifest(f)
   220  	iTargetID1 := manifest.ImageTargets[0].ID()
   221  	result1 := store.NewLiveUpdateBuildResult(
   222  		iTargetID1,
   223  		container.MustParseNamedTagged("sancho-base:tilt-prebuilt1"),
   224  		[]container.ID{container.ID("12345")})
   225  
   226  	stateSet := store.BuildStateSet{
   227  		iTargetID1: store.NewBuildState(result1, []string{}),
   228  	}
   229  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), stateSet)
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  
   234  	// Expect build + push; last result has a container ID, which implies that it was an in-place
   235  	// update, so the current state of this manifest is NOT reflected in an existing image.
   236  	assert.Equal(t, 1, f.docker.BuildCount)
   237  	assert.Equal(t, 1, f.docker.PushCount)
   238  }
   239  
   240  func TestMultiStageDockerBuild(t *testing.T) {
   241  	f := newIBDFixture(t, k8s.EnvGKE)
   242  	defer f.TearDown()
   243  
   244  	manifest := NewSanchoDockerBuildMultiStageManifest(f)
   245  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  
   250  	assert.Equal(t, 2, f.docker.BuildCount)
   251  	assert.Equal(t, 1, f.docker.PushCount)
   252  	assert.Equal(t, 0, f.kp.pushCount)
   253  
   254  	expected := expectedFile{
   255  		Path: "Dockerfile",
   256  		Contents: `
   257  FROM sancho-base:tilt-11cd0b38bc3ceb95
   258  ADD . .
   259  RUN go install github.com/windmilleng/sancho
   260  ENTRYPOINT /go/bin/sancho
   261  `,
   262  	}
   263  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildOptions.Context), expected)
   264  }
   265  
   266  func TestMultiStageDockerBuildPreservesSyntaxDirective(t *testing.T) {
   267  	f := newIBDFixture(t, k8s.EnvGKE)
   268  	defer f.TearDown()
   269  
   270  	baseImage := model.NewImageTarget(SanchoBaseRef).WithBuildDetails(model.DockerBuild{
   271  		Dockerfile: `FROM golang:1.10`,
   272  		BuildPath:  f.JoinPath("sancho-base"),
   273  	})
   274  
   275  	srcImage := model.NewImageTarget(SanchoRef).WithBuildDetails(model.DockerBuild{
   276  		Dockerfile: `# syntax = docker/dockerfile:experimental
   277  
   278  FROM sancho-base
   279  ADD . .
   280  RUN go install github.com/windmilleng/sancho
   281  ENTRYPOINT /go/bin/sancho
   282  `,
   283  		BuildPath: f.JoinPath("sancho"),
   284  	}).WithDependencyIDs([]model.TargetID{baseImage.ID()})
   285  
   286  	m := manifestbuilder.New(f, "sancho").
   287  		WithK8sYAML(SanchoYAML).
   288  		WithImageTargets(baseImage, srcImage).
   289  		Build()
   290  
   291  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(m), store.BuildStateSet{})
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	assert.Equal(t, 2, f.docker.BuildCount)
   297  	assert.Equal(t, 1, f.docker.PushCount)
   298  	assert.Equal(t, 0, f.kp.pushCount)
   299  
   300  	expected := expectedFile{
   301  		Path: "Dockerfile",
   302  		Contents: `# syntax = docker/dockerfile:experimental
   303  
   304  FROM sancho-base:tilt-11cd0b38bc3ceb95
   305  ADD . .
   306  RUN go install github.com/windmilleng/sancho
   307  ENTRYPOINT /go/bin/sancho
   308  `,
   309  	}
   310  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildOptions.Context), expected)
   311  }
   312  
   313  func TestMultiStageDockerBuildWithFirstImageDirty(t *testing.T) {
   314  	f := newIBDFixture(t, k8s.EnvGKE)
   315  	defer f.TearDown()
   316  
   317  	manifest := NewSanchoDockerBuildMultiStageManifest(f)
   318  	iTargetID1 := manifest.ImageTargets[0].ID()
   319  	iTargetID2 := manifest.ImageTargets[1].ID()
   320  	result1 := store.NewImageBuildResult(iTargetID1, container.MustParseNamedTagged("sancho-base:tilt-prebuilt1"))
   321  	result2 := store.NewImageBuildResult(iTargetID2, container.MustParseNamedTagged("sancho:tilt-prebuilt2"))
   322  
   323  	newFile := f.WriteFile("sancho-base/message.txt", "message")
   324  
   325  	stateSet := store.BuildStateSet{
   326  		iTargetID1: store.NewBuildState(result1, []string{newFile}),
   327  		iTargetID2: store.NewBuildState(result2, nil),
   328  	}
   329  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), stateSet)
   330  	if err != nil {
   331  		t.Fatal(err)
   332  	}
   333  
   334  	assert.Equal(t, 2, f.docker.BuildCount)
   335  	assert.Equal(t, 1, f.docker.PushCount)
   336  
   337  	expected := expectedFile{
   338  		Path: "Dockerfile",
   339  		Contents: `
   340  FROM sancho-base:tilt-11cd0b38bc3ceb95
   341  ADD . .
   342  RUN go install github.com/windmilleng/sancho
   343  ENTRYPOINT /go/bin/sancho
   344  `,
   345  	}
   346  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildOptions.Context), expected)
   347  }
   348  
   349  func TestMultiStageDockerBuildWithSecondImageDirty(t *testing.T) {
   350  	f := newIBDFixture(t, k8s.EnvGKE)
   351  	defer f.TearDown()
   352  
   353  	manifest := NewSanchoDockerBuildMultiStageManifest(f)
   354  	iTargetID1 := manifest.ImageTargets[0].ID()
   355  	iTargetID2 := manifest.ImageTargets[1].ID()
   356  	result1 := store.NewImageBuildResult(iTargetID1, container.MustParseNamedTagged("sancho-base:tilt-prebuilt1"))
   357  	result2 := store.NewImageBuildResult(iTargetID2, container.MustParseNamedTagged("sancho:tilt-prebuilt2"))
   358  
   359  	newFile := f.WriteFile("sancho/message.txt", "message")
   360  
   361  	f.docker.ImageListCount = 1
   362  
   363  	stateSet := store.BuildStateSet{
   364  		iTargetID1: store.NewBuildState(result1, nil),
   365  		iTargetID2: store.NewBuildState(result2, []string{newFile}),
   366  	}
   367  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), stateSet)
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  
   372  	assert.Equal(t, 1, f.docker.BuildCount)
   373  
   374  	expected := expectedFile{
   375  		Path: "Dockerfile",
   376  		Contents: `
   377  FROM sancho-base:tilt-prebuilt1
   378  ADD . .
   379  RUN go install github.com/windmilleng/sancho
   380  ENTRYPOINT /go/bin/sancho
   381  `,
   382  	}
   383  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildOptions.Context), expected)
   384  }
   385  
   386  func TestKINDPush(t *testing.T) {
   387  	f := newIBDFixture(t, k8s.EnvKIND)
   388  	defer f.TearDown()
   389  
   390  	manifest := NewSanchoDockerBuildManifest(f)
   391  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   392  	if err != nil {
   393  		t.Fatal(err)
   394  	}
   395  
   396  	assert.Equal(t, 1, f.docker.BuildCount)
   397  	assert.Equal(t, 1, f.kp.pushCount)
   398  	assert.Equal(t, 0, f.docker.PushCount)
   399  }
   400  
   401  func TestCustomBuildDisablePush(t *testing.T) {
   402  	f := newIBDFixture(t, k8s.EnvKIND)
   403  	defer f.TearDown()
   404  	sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
   405  	f.docker.Images["gcr.io/some-project-162817/sancho:tilt-build"] = types.ImageInspect{ID: string(sha)}
   406  
   407  	manifest := NewSanchoCustomBuildManifestWithPushDisabled(f)
   408  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   409  	assert.NoError(t, err)
   410  
   411  	// but we also didn't try to build or push an image
   412  	assert.Equal(t, 0, f.docker.BuildCount)
   413  	assert.Equal(t, 0, f.kp.pushCount)
   414  	assert.Equal(t, 0, f.docker.PushCount)
   415  }
   416  
   417  func TestDeployUsesInjectRef(t *testing.T) {
   418  	expectedImages := []string{"foo.com/gcr.io_some-project-162817_sancho"}
   419  	tests := []struct {
   420  		name           string
   421  		manifest       func(f Fixture) model.Manifest
   422  		expectedImages []string
   423  	}{
   424  		{"docker build", func(f Fixture) model.Manifest { return NewSanchoDockerBuildManifest(f) }, expectedImages},
   425  		{"fast build", NewSanchoFastBuildManifest, expectedImages},
   426  		{"custom build", NewSanchoCustomBuildManifest, expectedImages},
   427  		{"live multi stage", NewSanchoLiveUpdateMultiStageManifest, append(expectedImages, "foo.com/sancho-base")},
   428  	}
   429  
   430  	for _, test := range tests {
   431  		t.Run(test.name, func(t *testing.T) {
   432  			f := newIBDFixture(t, k8s.EnvGKE)
   433  			defer f.TearDown()
   434  
   435  			if test.name == "custom build" {
   436  				sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
   437  				f.docker.Images["foo.com/gcr.io_some-project-162817_sancho:tilt-build-1546304461"] = types.ImageInspect{ID: string(sha)}
   438  			}
   439  
   440  			manifest := test.manifest(f)
   441  			var err error
   442  			for i := range manifest.ImageTargets {
   443  				manifest.ImageTargets[i].DeploymentRef, err = container.ReplaceRegistry("foo.com", manifest.ImageTargets[i].ConfigurationRef)
   444  				if err != nil {
   445  					t.Fatal(err)
   446  				}
   447  			}
   448  
   449  			result, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   450  			if err != nil {
   451  				t.Fatal(err)
   452  			}
   453  
   454  			var observedImages []string
   455  			for i := range manifest.ImageTargets {
   456  				id := manifest.ImageTargets[i].ID()
   457  				image := store.ImageFromBuildResult(result[id])
   458  				observedImages = append(observedImages, image.Name())
   459  			}
   460  
   461  			assert.ElementsMatch(t, test.expectedImages, observedImages)
   462  		})
   463  	}
   464  }
   465  
   466  func TestDeployInjectImageEnvVar(t *testing.T) {
   467  	f := newIBDFixture(t, k8s.EnvGKE)
   468  	defer f.TearDown()
   469  
   470  	manifest := NewSanchoManifestWithImageInEnvVar(f)
   471  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   472  	if err != nil {
   473  		t.Fatal(err)
   474  	}
   475  
   476  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   477  	if err != nil {
   478  		t.Fatal(err)
   479  	}
   480  
   481  	if !assert.Equal(t, 1, len(entities)) {
   482  		return
   483  	}
   484  
   485  	d := entities[0].Obj.(*v1.Deployment)
   486  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   487  		return
   488  	}
   489  
   490  	c := d.Spec.Template.Spec.Containers[0]
   491  	// container image always gets injected
   492  	assert.Equal(t, "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95", c.Image)
   493  	expectedEnv := []corev1.EnvVar{
   494  		// sancho2 gets injected here because it sets match_in_env_vars in docker_build
   495  		{Name: "foo", Value: "gcr.io/some-project-162817/sancho2:tilt-11cd0b38bc3ceb95"},
   496  		// sancho does not because it doesn't
   497  		{Name: "bar", Value: "gcr.io/some-project-162817/sancho"},
   498  	}
   499  	assert.Equal(t, expectedEnv, c.Env)
   500  }
   501  
   502  func TestDeployInjectsOverrideCommand(t *testing.T) {
   503  	f := newIBDFixture(t, k8s.EnvGKE)
   504  	defer f.TearDown()
   505  
   506  	cmd := model.ToShellCmd("./foo.sh bar")
   507  	manifest := NewSanchoDockerBuildManifest(f)
   508  	iTarg := manifest.ImageTargetAt(0).WithOverrideCommand(cmd)
   509  	manifest = manifest.WithImageTarget(iTarg)
   510  
   511  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   512  	if err != nil {
   513  		t.Fatal(err)
   514  	}
   515  
   516  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   517  	if err != nil {
   518  		t.Fatal(err)
   519  	}
   520  
   521  	if !assert.Equal(t, 1, len(entities)) {
   522  		return
   523  	}
   524  
   525  	d := entities[0].Obj.(*v1.Deployment)
   526  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   527  		return
   528  	}
   529  
   530  	c := d.Spec.Template.Spec.Containers[0]
   531  
   532  	// Make sure container ref injection worked as expected
   533  	assert.Equal(t, "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95", c.Image)
   534  
   535  	assert.Equal(t, cmd.Argv, c.Command)
   536  	assert.Empty(t, c.Args)
   537  }
   538  
   539  func TestDeployInjectOverrideCommandClearsOldCommandAndArgs(t *testing.T) {
   540  	f := newIBDFixture(t, k8s.EnvGKE)
   541  	defer f.TearDown()
   542  
   543  	cmd := model.ToShellCmd("./foo.sh bar")
   544  	manifest := NewSanchoDockerBuildManifestWithYaml(f, testyaml.SanchoYAMLWithCommand)
   545  	iTarg := manifest.ImageTargetAt(0).WithOverrideCommand(cmd)
   546  	manifest = manifest.WithImageTarget(iTarg)
   547  
   548  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   549  	if err != nil {
   550  		t.Fatal(err)
   551  	}
   552  
   553  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  
   558  	if !assert.Equal(t, 1, len(entities)) {
   559  		return
   560  	}
   561  
   562  	d := entities[0].Obj.(*v1.Deployment)
   563  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   564  		return
   565  	}
   566  
   567  	c := d.Spec.Template.Spec.Containers[0]
   568  	assert.Equal(t, cmd.Argv, c.Command)
   569  	assert.Empty(t, c.Args)
   570  }
   571  
   572  func TestCantInjectOverrideCommandWithoutContainer(t *testing.T) {
   573  	f := newIBDFixture(t, k8s.EnvGKE)
   574  	defer f.TearDown()
   575  
   576  	// CRD YAML: we WILL successfully inject the new image ref, but can't inject
   577  	// an override command for that image because it's not in a "container" block:
   578  	// expect an error when we try
   579  	crdYamlWithSanchoImage := strings.ReplaceAll(testyaml.CRDYAML, testyaml.CRDImage, testyaml.SanchoImage)
   580  
   581  	cmd := model.ToShellCmd("./foo.sh bar")
   582  	manifest := NewSanchoDockerBuildManifestWithYaml(f, crdYamlWithSanchoImage)
   583  	iTarg := manifest.ImageTargetAt(0).WithOverrideCommand(cmd)
   584  	manifest = manifest.WithImageTarget(iTarg)
   585  
   586  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   587  	if assert.Error(t, err) {
   588  		assert.Contains(t, err.Error(), "could not inject command")
   589  	}
   590  }
   591  
   592  func TestInjectOverrideCommandsMultipleImages(t *testing.T) {
   593  	f := newIBDFixture(t, k8s.EnvGKE)
   594  	defer f.TearDown()
   595  
   596  	cmd1 := model.ToShellCmd("./command1.sh foo")
   597  	cmd2 := model.ToShellCmd("./command2.sh bar baz")
   598  
   599  	iTarget1 := NewSanchoDockerBuildImageTarget(f).WithOverrideCommand(cmd1)
   600  	iTarget2 := NewSanchoSidecarDockerBuildImageTarget(f).WithOverrideCommand(cmd2)
   601  	kTarget := model.K8sTarget{Name: "sancho", YAML: testyaml.SanchoSidecarYAML}.
   602  		WithDependencyIDs([]model.TargetID{iTarget1.ID(), iTarget2.ID()})
   603  	targets := []model.TargetSpec{iTarget1, iTarget2, kTarget}
   604  
   605  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{})
   606  	if err != nil {
   607  		t.Fatal(err)
   608  	}
   609  
   610  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   611  	if err != nil {
   612  		t.Fatal(err)
   613  	}
   614  
   615  	if !assert.Equal(t, 1, len(entities)) {
   616  		return
   617  	}
   618  
   619  	d := entities[0].Obj.(*v1.Deployment)
   620  	if !assert.Equal(t, 2, len(d.Spec.Template.Spec.Containers)) {
   621  		return
   622  	}
   623  
   624  	sanchoContainer := d.Spec.Template.Spec.Containers[0]
   625  	sidecarContainer := d.Spec.Template.Spec.Containers[1]
   626  
   627  	// Make sure container ref injection worked as expected
   628  	assert.Equal(t, "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95", sanchoContainer.Image)
   629  	assert.Equal(t, "gcr.io/some-project-162817/sancho-sidecar:tilt-11cd0b38bc3ceb95", sidecarContainer.Image)
   630  
   631  	assert.Equal(t, cmd1.Argv, sanchoContainer.Command)
   632  	assert.Equal(t, cmd2.Argv, sidecarContainer.Command)
   633  
   634  }
   635  
   636  func TestIBDDeployUIDs(t *testing.T) {
   637  	f := newIBDFixture(t, k8s.EnvGKE)
   638  	defer f.TearDown()
   639  
   640  	manifest := NewSanchoFastBuildManifest(f)
   641  	result, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   642  	if err != nil {
   643  		t.Fatal(err)
   644  	}
   645  
   646  	assert.Equal(t, 1, len(result.DeployedUIDSet()))
   647  	assert.True(t, result.DeployedUIDSet().Contains(f.k8s.LastUpsertResult[0].UID()))
   648  }
   649  
   650  func TestDockerBuildTargetStage(t *testing.T) {
   651  	f := newIBDFixture(t, k8s.EnvGKE)
   652  	defer f.TearDown()
   653  
   654  	iTarget := NewSanchoDockerBuildImageTarget(f)
   655  	db := iTarget.BuildDetails.(model.DockerBuild)
   656  	db.TargetStage = "stage"
   657  	iTarget.BuildDetails = db
   658  
   659  	manifest := manifestbuilder.New(f, "sancho").
   660  		WithK8sYAML(testyaml.SanchoYAML).
   661  		WithImageTargets(iTarget).
   662  		Build()
   663  	_, err := f.ibd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{})
   664  	if err != nil {
   665  		t.Fatal(err)
   666  	}
   667  	assert.Equal(t, "stage", f.docker.BuildOptions.Target)
   668  }
   669  
   670  type ibdFixture struct {
   671  	*tempdir.TempDirFixture
   672  	ctx    context.Context
   673  	docker *docker.FakeClient
   674  	k8s    *k8s.FakeK8sClient
   675  	ibd    *ImageBuildAndDeployer
   676  	st     *store.TestingStore
   677  	kp     *fakeKINDPusher
   678  }
   679  
   680  func newIBDFixture(t *testing.T, env k8s.Env) *ibdFixture {
   681  	f := tempdir.NewTempDirFixture(t)
   682  	dir := dirs.NewWindmillDirAt(f.Path())
   683  	docker := docker.NewFakeClient()
   684  	ctx, _, ta := testutils.CtxAndAnalyticsForTest()
   685  	kClient := k8s.NewFakeK8sClient()
   686  	kp := &fakeKINDPusher{}
   687  	clock := fakeClock{time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC)}
   688  	ibd, err := provideImageBuildAndDeployer(ctx, docker, kClient, env, dir, clock, kp, ta)
   689  	if err != nil {
   690  		t.Fatal(err)
   691  	}
   692  	return &ibdFixture{
   693  		TempDirFixture: f,
   694  		ctx:            ctx,
   695  		docker:         docker,
   696  		k8s:            kClient,
   697  		ibd:            ibd,
   698  		st:             store.NewTestingStore(),
   699  		kp:             kp,
   700  	}
   701  }
   702  
   703  func (f *ibdFixture) TearDown() {
   704  	f.k8s.TearDown()
   705  	f.TempDirFixture.TearDown()
   706  }
   707  
   708  type fakeKINDPusher struct {
   709  	pushCount int
   710  }
   711  
   712  func (kp *fakeKINDPusher) PushToKIND(ctx context.Context, ref reference.NamedTagged, w io.Writer) error {
   713  	kp.pushCount++
   714  	return nil
   715  }