github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/engine/buildcontrol/image_build_and_deployer_test.go (about)

     1  package buildcontrol
     2  
     3  import (
     4  	"archive/tar"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/distribution/reference"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/jonboulle/clockwork"
    16  	"github.com/opencontainers/go-digest"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  	v1 "k8s.io/api/apps/v1"
    20  	corev1 "k8s.io/api/core/v1"
    21  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  	ktypes "k8s.io/apimachinery/pkg/types"
    23  	ctrl "sigs.k8s.io/controller-runtime"
    24  	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
    25  
    26  	"github.com/tilt-dev/clusterid"
    27  	"github.com/tilt-dev/tilt/internal/container"
    28  	"github.com/tilt-dev/tilt/internal/controllers/fake"
    29  	"github.com/tilt-dev/tilt/internal/docker"
    30  	"github.com/tilt-dev/tilt/internal/k8s"
    31  	"github.com/tilt-dev/tilt/internal/k8s/testyaml"
    32  	"github.com/tilt-dev/tilt/internal/store"
    33  	"github.com/tilt-dev/tilt/internal/store/k8sconv"
    34  	"github.com/tilt-dev/tilt/internal/testutils"
    35  	"github.com/tilt-dev/tilt/internal/testutils/bufsync"
    36  	"github.com/tilt-dev/tilt/internal/testutils/manifestbuilder"
    37  	"github.com/tilt-dev/tilt/internal/testutils/tempdir"
    38  	"github.com/tilt-dev/tilt/internal/yaml"
    39  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    40  	"github.com/tilt-dev/tilt/pkg/logger"
    41  	"github.com/tilt-dev/tilt/pkg/model"
    42  	"github.com/tilt-dev/wmclient/pkg/dirs"
    43  )
    44  
    45  func TestDeployTwinImages(t *testing.T) {
    46  	f := newIBDFixture(t, clusterid.ProductGKE)
    47  
    48  	sancho := NewSanchoDockerBuildManifest(f)
    49  	newK8sTarget := k8s.MustTarget("sancho", yaml.ConcatYAML(SanchoYAML, SanchoTwinYAML)).
    50  		WithImageDependencies(sancho.K8sTarget().ImageMaps)
    51  	manifest := sancho.WithDeployTarget(newK8sTarget)
    52  	result, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
    53  	if err != nil {
    54  		t.Fatal(err)
    55  	}
    56  
    57  	id := manifest.ImageTargetAt(0).ID()
    58  	expectedImage := "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95"
    59  	image := store.ClusterImageRefFromBuildResult(result[id])
    60  	assert.Equal(t, expectedImage, image)
    61  	assert.Equalf(t, 2, strings.Count(f.k8s.Yaml, expectedImage),
    62  		"Expected image to update twice in YAML: %s", f.k8s.Yaml)
    63  }
    64  
    65  func TestForceUpdateK8s(t *testing.T) {
    66  	f := newIBDFixture(t, clusterid.ProductGKE)
    67  
    68  	m := NewSanchoDockerBuildManifest(f)
    69  
    70  	iTargetID1 := m.ImageTargets[0].ID()
    71  	stateSet := store.BuildStateSet{
    72  		iTargetID1: store.BuildState{FullBuildTriggered: true},
    73  	}
    74  	_, err := f.BuildAndDeploy(BuildTargets(m), stateSet)
    75  	require.NoError(t, err)
    76  
    77  	// A force rebuild should delete the old resources.
    78  	assert.Equal(t, 1, strings.Count(f.k8s.DeletedYaml, "Deployment"))
    79  }
    80  
    81  func TestForceUpdateDoesNotDeleteNamespace(t *testing.T) {
    82  	f := newIBDFixture(t, clusterid.ProductGKE)
    83  
    84  	m := manifestbuilder.New(f, "sancho").
    85  		WithK8sYAML(SanchoYAML + `
    86  ---
    87  apiVersion: v1
    88  kind: Namespace
    89  metadata:
    90    name: my-namespace
    91  ---
    92  apiVersion: v1
    93  kind: ConfigMap
    94  metadata:
    95    name: my-config
    96    namespace: my-namespace
    97  `).
    98  		WithImageTarget(NewSanchoDockerBuildImageTarget(f)).
    99  		Build()
   100  
   101  	iTargetID1 := m.ImageTargets[0].ID()
   102  	stateSet := store.BuildStateSet{
   103  		iTargetID1: store.BuildState{FullBuildTriggered: true},
   104  	}
   105  	_, err := f.BuildAndDeploy(BuildTargets(m), stateSet)
   106  	require.NoError(t, err)
   107  
   108  	// A force rebuild should delete the ConfigMap but not the Namespace.
   109  	assert.Equal(t, 1, strings.Count(f.k8s.DeletedYaml, "kind: ConfigMap"))
   110  	assert.Equal(t, 0, strings.Count(f.k8s.DeletedYaml, "kind: Namespace"))
   111  }
   112  
   113  func TestDeleteShouldHappenInReverseOrder(t *testing.T) {
   114  	f := newIBDFixture(t, clusterid.ProductGKE)
   115  
   116  	m := newK8sMultiEntityManifest("sancho")
   117  
   118  	err := f.ibd.delete(f.ctx, m.K8sTarget(), nil)
   119  	require.NoError(t, err)
   120  
   121  	assert.Regexp(t, "(?s)name: sancho-deployment.*name: sancho-pvc", f.k8s.DeletedYaml) // pvc comes after deployment
   122  }
   123  
   124  func TestDeployPodWithMultipleImages(t *testing.T) {
   125  	f := newIBDFixture(t, clusterid.ProductGKE)
   126  
   127  	iTarget1 := NewSanchoDockerBuildImageTarget(f)
   128  	iTarget2 := NewSanchoSidecarDockerBuildImageTarget(f)
   129  	kTarget := k8s.MustTarget("sancho", testyaml.SanchoSidecarYAML).
   130  		WithImageDependencies([]string{iTarget1.ImageMapName(), iTarget2.ImageMapName()})
   131  	targets := []model.TargetSpec{iTarget1, iTarget2, kTarget}
   132  
   133  	result, err := f.BuildAndDeploy(targets, store.BuildStateSet{})
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  
   138  	assert.Equal(t, 2, f.docker.BuildCount)
   139  
   140  	expectedSanchoRef := "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95"
   141  	image := store.ClusterImageRefFromBuildResult(result[iTarget1.ID()])
   142  	assert.Equal(t, expectedSanchoRef, image)
   143  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSanchoRef),
   144  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   145  
   146  	expectedSidecarRef := "gcr.io/some-project-162817/sancho-sidecar:tilt-11cd0b38bc3ceb95"
   147  	image = store.ClusterImageRefFromBuildResult(result[iTarget2.ID()])
   148  	assert.Equal(t, expectedSidecarRef, image)
   149  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSidecarRef),
   150  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   151  }
   152  
   153  func TestDeployPodWithMultipleLiveUpdateImages(t *testing.T) {
   154  	f := newIBDFixture(t, clusterid.ProductGKE)
   155  
   156  	iTarget1 := NewSanchoLiveUpdateImageTarget(f)
   157  	iTarget2 := NewSanchoSidecarLiveUpdateImageTarget(f)
   158  
   159  	kTarget := k8s.MustTarget("sancho", testyaml.SanchoSidecarYAML).
   160  		WithImageDependencies([]string{iTarget1.ImageMapName(), iTarget2.ImageMapName()})
   161  	targets := []model.TargetSpec{iTarget1, iTarget2, kTarget}
   162  
   163  	result, err := f.BuildAndDeploy(targets, store.BuildStateSet{})
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  
   168  	assert.Equal(t, 2, f.docker.BuildCount)
   169  
   170  	expectedSanchoRef := "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95"
   171  	image := store.ClusterImageRefFromBuildResult(result[iTarget1.ID()])
   172  	assert.Equal(t, expectedSanchoRef, image)
   173  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSanchoRef),
   174  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   175  
   176  	expectedSidecarRef := "gcr.io/some-project-162817/sancho-sidecar:tilt-11cd0b38bc3ceb95"
   177  	image = store.ClusterImageRefFromBuildResult(result[iTarget2.ID()])
   178  	assert.Equal(t, expectedSidecarRef, image)
   179  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedSidecarRef),
   180  		"Expected image to appear once in YAML: %s", f.k8s.Yaml)
   181  }
   182  
   183  func TestNoImageTargets(t *testing.T) {
   184  	f := newIBDFixture(t, clusterid.ProductGKE)
   185  
   186  	targName := "some-k8s-manifest"
   187  	specs := []model.TargetSpec{
   188  		k8s.MustTarget(model.TargetName(targName), testyaml.LonelyPodYAML),
   189  	}
   190  
   191  	_, err := f.BuildAndDeploy(specs, store.BuildStateSet{})
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  
   196  	assert.Equal(t, 0, f.docker.BuildCount, "expect no docker builds")
   197  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, "image: gcr.io/windmill-public-containers/lonely-pod"),
   198  		"Expected lonely-pod image to appear once in YAML: %s", f.k8s.Yaml)
   199  
   200  	expectedLabelStr := fmt.Sprintf("%s: %s", k8s.ManagedByLabel, k8s.ManagedByValue)
   201  	assert.Equalf(t, 1, strings.Count(f.k8s.Yaml, expectedLabelStr),
   202  		"Expected \"%s\" label to appear once in YAML: %s", expectedLabelStr, f.k8s.Yaml)
   203  
   204  	// If we're not making updates in response to an image change, it's OK to
   205  	// leave the existing image pull policy.
   206  	assert.Contains(t, f.k8s.Yaml, "imagePullPolicy: Always")
   207  }
   208  
   209  func TestStatefulSetPodManagementPolicy(t *testing.T) {
   210  	f := newIBDFixture(t, clusterid.ProductGKE)
   211  
   212  	targName := "redis"
   213  
   214  	iTarget := NewSanchoDockerBuildImageTarget(f)
   215  	yaml := strings.Replace(
   216  		testyaml.RedisStatefulSetYAML,
   217  		`image: "docker.io/bitnami/redis:4.0.12"`,
   218  		fmt.Sprintf(`image: %q`, f.refs(iTarget).LocalRef().String()), 1)
   219  	kTarget := k8s.MustTarget(model.TargetName(targName), yaml)
   220  
   221  	_, err := f.BuildAndDeploy(
   222  		[]model.TargetSpec{kTarget}, store.BuildStateSet{})
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	assert.NoError(t, err)
   227  	assert.NotContains(t, f.k8s.Yaml, "podManagementPolicy: Parallel")
   228  
   229  	_, err = f.BuildAndDeploy(
   230  		[]model.TargetSpec{
   231  			iTarget,
   232  			kTarget.WithImageDependencies([]string{iTarget.ImageMapName()}),
   233  		},
   234  		store.BuildStateSet{})
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	assert.NoError(t, err)
   239  	assert.Contains(t, f.k8s.Yaml, "podManagementPolicy: Parallel")
   240  }
   241  
   242  func TestImageIsClean(t *testing.T) {
   243  	f := newIBDFixture(t, clusterid.ProductGKE)
   244  
   245  	manifest := NewSanchoDockerBuildManifest(f)
   246  	iTargetID1 := manifest.ImageTargets[0].ID()
   247  	result1 := store.NewImageBuildResultSingleRef(iTargetID1, container.MustParseNamedTagged("sancho-base:tilt-prebuilt1"))
   248  
   249  	stateSet := store.BuildStateSet{
   250  		iTargetID1: store.NewBuildState(result1, []string{}, nil),
   251  	}
   252  	_, err := f.BuildAndDeploy(BuildTargets(manifest), stateSet)
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  
   257  	// Expect no build or push, b/c image is clean (i.e. last build was an image build and
   258  	// no file changes since).
   259  	assert.Equal(t, 0, f.docker.BuildCount)
   260  	assert.Equal(t, 0, f.docker.PushCount)
   261  }
   262  
   263  func TestMultiStageDockerBuild(t *testing.T) {
   264  	f := newIBDFixture(t, clusterid.ProductGKE)
   265  
   266  	manifest := NewSanchoDockerBuildMultiStageManifest(f)
   267  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   268  	if err != nil {
   269  		t.Fatal(err)
   270  	}
   271  
   272  	assert.Equal(t, 2, f.docker.BuildCount)
   273  	assert.Equal(t, 1, f.docker.PushCount)
   274  	assert.Equal(t, 0, f.kl.loadCount)
   275  
   276  	expected := testutils.ExpectedFile{
   277  		Path: "Dockerfile",
   278  		Contents: `
   279  FROM sancho-base:tilt-11cd0b38bc3ceb95
   280  ADD . .
   281  RUN go install github.com/tilt-dev/sancho
   282  ENTRYPOINT /go/bin/sancho
   283  `,
   284  	}
   285  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildContext), expected)
   286  }
   287  
   288  func TestMultiStageDockerBuildPreservesSyntaxDirective(t *testing.T) {
   289  	f := newIBDFixture(t, clusterid.ProductGKE)
   290  
   291  	baseImage := model.MustNewImageTarget(SanchoBaseRef).
   292  		WithDockerImage(v1alpha1.DockerImageSpec{
   293  			DockerfileContents: `FROM golang:1.10`,
   294  			Context:            f.JoinPath("sancho-base"),
   295  		})
   296  
   297  	srcImage := model.MustNewImageTarget(SanchoRef).
   298  		WithDockerImage(v1alpha1.DockerImageSpec{
   299  			DockerfileContents: `# syntax = docker/dockerfile:experimental
   300  
   301  FROM sancho-base
   302  ADD . .
   303  RUN go install github.com/tilt-dev/sancho
   304  ENTRYPOINT /go/bin/sancho
   305  `,
   306  			Context: f.JoinPath("sancho"),
   307  		}).WithImageMapDeps([]string{baseImage.ImageMapName()})
   308  
   309  	m := manifestbuilder.New(f, "sancho").
   310  		WithK8sYAML(SanchoYAML).
   311  		WithImageTargets(baseImage, srcImage).
   312  		Build()
   313  
   314  	_, err := f.BuildAndDeploy(BuildTargets(m), store.BuildStateSet{})
   315  	if err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	assert.Equal(t, 2, f.docker.BuildCount)
   320  	assert.Equal(t, 1, f.docker.PushCount)
   321  	assert.Equal(t, 0, f.kl.loadCount)
   322  
   323  	expected := testutils.ExpectedFile{
   324  		Path: "Dockerfile",
   325  		Contents: `# syntax = docker/dockerfile:experimental
   326  
   327  FROM sancho-base:tilt-11cd0b38bc3ceb95
   328  ADD . .
   329  RUN go install github.com/tilt-dev/sancho
   330  ENTRYPOINT /go/bin/sancho
   331  `,
   332  	}
   333  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildContext), expected)
   334  }
   335  
   336  func TestMultiStageDockerBuildWithFirstImageDirty(t *testing.T) {
   337  	f := newIBDFixture(t, clusterid.ProductGKE)
   338  
   339  	manifest := NewSanchoDockerBuildMultiStageManifest(f)
   340  	iTargetID1 := manifest.ImageTargets[0].ID()
   341  	iTargetID2 := manifest.ImageTargets[1].ID()
   342  	result1 := store.NewImageBuildResultSingleRef(iTargetID1, container.MustParseNamedTagged("sancho-base:tilt-prebuilt1"))
   343  	result2 := store.NewImageBuildResultSingleRef(iTargetID2, container.MustParseNamedTagged("sancho:tilt-prebuilt2"))
   344  
   345  	newFile := f.WriteFile("sancho-base/message.txt", "message")
   346  
   347  	stateSet := store.BuildStateSet{
   348  		iTargetID1: store.NewBuildState(result1, []string{newFile}, nil),
   349  		iTargetID2: store.NewBuildState(result2, nil, nil),
   350  	}
   351  	_, err := f.BuildAndDeploy(BuildTargets(manifest), stateSet)
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  
   356  	assert.Equal(t, 2, f.docker.BuildCount)
   357  	assert.Equal(t, 1, f.docker.PushCount)
   358  
   359  	expected := testutils.ExpectedFile{
   360  		Path: "Dockerfile",
   361  		Contents: `
   362  FROM sancho-base:tilt-11cd0b38bc3ceb95
   363  ADD . .
   364  RUN go install github.com/tilt-dev/sancho
   365  ENTRYPOINT /go/bin/sancho
   366  `,
   367  	}
   368  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildContext), expected)
   369  }
   370  
   371  func TestMultiStageDockerBuildWithSecondImageDirty(t *testing.T) {
   372  	f := newIBDFixture(t, clusterid.ProductGKE)
   373  
   374  	manifest := NewSanchoDockerBuildMultiStageManifest(f)
   375  	iTargetID1 := manifest.ImageTargets[0].ID()
   376  	iTargetID2 := manifest.ImageTargets[1].ID()
   377  	result1 := store.NewImageBuildResultSingleRef(iTargetID1, container.MustParseNamedTagged("sancho-base:tilt-prebuilt1"))
   378  	result2 := store.NewImageBuildResultSingleRef(iTargetID2, container.MustParseNamedTagged("sancho:tilt-prebuilt2"))
   379  
   380  	newFile := f.WriteFile("sancho/message.txt", "message")
   381  
   382  	stateSet := store.BuildStateSet{
   383  		iTargetID1: store.NewBuildState(result1, nil, nil),
   384  		iTargetID2: store.NewBuildState(result2, []string{newFile}, nil),
   385  	}
   386  	_, err := f.BuildAndDeploy(BuildTargets(manifest), stateSet)
   387  	if err != nil {
   388  		t.Fatal(err)
   389  	}
   390  
   391  	assert.Equal(t, 1, f.docker.BuildCount)
   392  
   393  	expected := testutils.ExpectedFile{
   394  		Path: "Dockerfile",
   395  		Contents: `
   396  FROM sancho-base:tilt-prebuilt1
   397  ADD . .
   398  RUN go install github.com/tilt-dev/sancho
   399  ENTRYPOINT /go/bin/sancho
   400  `,
   401  	}
   402  	testutils.AssertFileInTar(t, tar.NewReader(f.docker.BuildContext), expected)
   403  }
   404  
   405  func TestK8sUpsertTimeout(t *testing.T) {
   406  	f := newIBDFixture(t, clusterid.ProductGKE)
   407  
   408  	timeout := 123 * time.Second
   409  
   410  	manifest := NewSanchoDockerBuildManifest(f)
   411  	k8sTarget := manifest.DeployTarget.(model.K8sTarget)
   412  	k8sTarget.Timeout = metav1.Duration{Duration: timeout}
   413  	manifest.DeployTarget = k8sTarget
   414  
   415  	_, err := f.BuildAndDeploy(BuildTargets(manifest), nil)
   416  	if err != nil {
   417  		t.Fatal(err)
   418  	}
   419  
   420  	assert.Equal(t, f.k8s.UpsertTimeout, timeout)
   421  }
   422  
   423  func TestKINDLoad(t *testing.T) {
   424  	f := newIBDFixture(t, clusterid.ProductKIND)
   425  
   426  	manifest := NewSanchoDockerBuildManifest(f)
   427  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   428  	if err != nil {
   429  		t.Fatal(err)
   430  	}
   431  
   432  	assert.Equal(t, 1, f.docker.BuildCount)
   433  	assert.Equal(t, 1, f.kl.loadCount)
   434  	assert.Equal(t, 0, f.docker.PushCount)
   435  }
   436  
   437  func TestDockerPushIfKINDAndClusterRef(t *testing.T) {
   438  	f := newIBDFixture(t, clusterid.ProductKIND)
   439  	f.cluster.Spec.DefaultRegistry = &v1alpha1.RegistryHosting{
   440  		Host:                     "localhost:1234",
   441  		HostFromContainerRuntime: "registry:1234",
   442  	}
   443  
   444  	manifest := NewSanchoDockerBuildManifest(f)
   445  	iTarg := manifest.ImageTargetAt(0)
   446  	manifest = manifest.WithImageTarget(iTarg)
   447  
   448  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  
   453  	refs := f.refs(iTarg)
   454  
   455  	assert.Equal(t, 1, f.docker.BuildCount, "Docker build count")
   456  	assert.Equal(t, 0, f.kl.loadCount, "KIND load count")
   457  	assert.Equal(t, 1, f.docker.PushCount, "Docker push count")
   458  	assert.Equal(t, refs.LocalRef().String(), container.MustParseNamed(f.docker.PushImage).Name(), "image pushed to Docker as LocalRef")
   459  
   460  	yaml := f.k8s.Yaml
   461  	assert.Contains(t, yaml, refs.ClusterRef().String(), "ClusterRef was injected into applied YAML")
   462  	assert.NotContains(t, yaml, refs.LocalRef().String(), "LocalRef was NOT injected into applied YAML")
   463  }
   464  
   465  func TestCustomBuildDisablePush(t *testing.T) {
   466  	f := newIBDFixture(t, clusterid.ProductKIND)
   467  	sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
   468  	f.docker.Images["gcr.io/some-project-162817/sancho:tilt-build"] = types.ImageInspect{ID: string(sha)}
   469  
   470  	manifest := NewSanchoCustomBuildManifestWithPushDisabled(f)
   471  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   472  	assert.NoError(t, err)
   473  
   474  	// We didn't try to build or push an image, but we did try to tag it
   475  	assert.Equal(t, 0, f.docker.BuildCount)
   476  	assert.Equal(t, 1, f.docker.TagCount)
   477  	assert.Equal(t, 0, f.kl.loadCount)
   478  	assert.Equal(t, 0, f.docker.PushCount)
   479  }
   480  
   481  func TestCustomBuildSkipsLocalDocker(t *testing.T) {
   482  	f := newIBDFixture(t, clusterid.ProductKIND)
   483  	sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
   484  	f.docker.Images["gcr.io/some-project-162817/sancho:tilt-build"] = types.ImageInspect{ID: string(sha)}
   485  
   486  	cb := model.CustomBuild{
   487  		CmdImageSpec: v1alpha1.CmdImageSpec{
   488  			Args:       model.ToHostCmd("exit 0").Argv,
   489  			OutputTag:  "tilt-build",
   490  			OutputMode: v1alpha1.CmdImageOutputRemote,
   491  		},
   492  		Deps: []string{f.JoinPath("app")},
   493  	}
   494  
   495  	manifest := manifestbuilder.New(f, "sancho").
   496  		WithK8sYAML(SanchoYAML).
   497  		WithImageTarget(model.MustNewImageTarget(SanchoRef).WithBuildDetails(cb)).
   498  		Build()
   499  
   500  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   501  	assert.NoError(t, err)
   502  
   503  	// We didn't try to build, tag, or push an image
   504  	assert.Equal(t, 0, f.docker.BuildCount)
   505  	assert.Equal(t, 0, f.docker.TagCount)
   506  	assert.Equal(t, 0, f.kl.loadCount)
   507  	assert.Equal(t, 0, f.docker.PushCount)
   508  }
   509  
   510  func TestBuildAndDeployUsesCorrectRef(t *testing.T) {
   511  	expectedImages := []string{"foo.com/gcr.io_some-project-162817_sancho"}
   512  	expectedImagesClusterRef := []string{"registry:1234/gcr.io_some-project-162817_sancho"}
   513  	tests := []struct {
   514  		name           string
   515  		manifest       func(f Fixture) model.Manifest
   516  		withClusterRef bool // if true, clusterRef != localRef, i.e. ref of the built docker image != ref injected into YAML
   517  		expectBuilt    []string
   518  		expectDeployed []string
   519  	}{
   520  		{"docker build", NewSanchoDockerBuildManifest, false, expectedImages, expectedImages},
   521  		{"docker build + distinct clusterRef", NewSanchoDockerBuildManifest, true, expectedImages, expectedImagesClusterRef},
   522  		{"custom build", NewSanchoCustomBuildManifest, false, expectedImages, expectedImages},
   523  		{"custom build + distinct clusterRef", NewSanchoCustomBuildManifest, true, expectedImages, expectedImagesClusterRef},
   524  		{"live multi stage", NewSanchoLiveUpdateMultiStageManifest, false, append(expectedImages, "foo.com/sancho-base"), expectedImages},
   525  		{"live multi stage + distinct clusterRef", NewSanchoLiveUpdateMultiStageManifest, true, append(expectedImages, "foo.com/sancho-base"), expectedImagesClusterRef},
   526  	}
   527  
   528  	for _, test := range tests {
   529  		t.Run(test.name, func(t *testing.T) {
   530  			f := newIBDFixture(t, clusterid.ProductGKE)
   531  			f.cluster.Spec.DefaultRegistry = &v1alpha1.RegistryHosting{Host: "foo.com"}
   532  			if test.withClusterRef {
   533  				f.cluster.Spec.DefaultRegistry.HostFromContainerRuntime = "registry:1234"
   534  			}
   535  
   536  			if strings.Contains(test.name, "custom build") {
   537  				sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
   538  				f.docker.Images["foo.com/gcr.io_some-project-162817_sancho:tilt-build-1546304461"] = types.ImageInspect{ID: string(sha)}
   539  			}
   540  
   541  			manifest := test.manifest(f)
   542  			result, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   543  			if err != nil {
   544  				t.Fatal(err)
   545  			}
   546  
   547  			var observedImages []string
   548  			for i := range manifest.ImageTargets {
   549  				id := manifest.ImageTargets[i].ID()
   550  				image := store.LocalImageRefFromBuildResult(result[id])
   551  				imageRef := container.MustParseNamedTagged(image)
   552  				observedImages = append(observedImages, imageRef.Name())
   553  			}
   554  
   555  			assert.ElementsMatch(t, test.expectBuilt, observedImages)
   556  
   557  			for _, expected := range test.expectDeployed {
   558  				assert.Contains(t, f.k8s.Yaml, expected)
   559  			}
   560  		})
   561  	}
   562  }
   563  
   564  func TestDeployInjectImageEnvVar(t *testing.T) {
   565  	f := newIBDFixture(t, clusterid.ProductGKE)
   566  
   567  	manifest := NewSanchoManifestWithImageInEnvVar(f)
   568  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   569  	if err != nil {
   570  		t.Fatal(err)
   571  	}
   572  
   573  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   574  	if err != nil {
   575  		t.Fatal(err)
   576  	}
   577  
   578  	if !assert.Equal(t, 1, len(entities)) {
   579  		return
   580  	}
   581  
   582  	d := entities[0].Obj.(*v1.Deployment)
   583  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   584  		return
   585  	}
   586  
   587  	c := d.Spec.Template.Spec.Containers[0]
   588  	// container image always gets injected
   589  	assert.Equal(t, "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95", c.Image)
   590  	expectedEnv := []corev1.EnvVar{
   591  		// sancho2 gets injected here because it sets match_in_env_vars in docker_build
   592  		{Name: "foo", Value: "gcr.io/some-project-162817/sancho2:tilt-11cd0b38bc3ceb95"},
   593  		// sancho does not because it doesn't
   594  		{Name: "bar", Value: "gcr.io/some-project-162817/sancho"},
   595  	}
   596  	assert.Equal(t, expectedEnv, c.Env)
   597  }
   598  
   599  func TestDeployInjectsOverrideCommand(t *testing.T) {
   600  	f := newIBDFixture(t, clusterid.ProductGKE)
   601  
   602  	cmd := model.ToUnixCmd("./foo.sh bar")
   603  	manifest := NewSanchoDockerBuildManifest(f)
   604  	iTarg := manifest.ImageTargetAt(0).WithOverrideCommand(cmd)
   605  	manifest = manifest.WithImageTarget(iTarg)
   606  
   607  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   608  	if err != nil {
   609  		t.Fatal(err)
   610  	}
   611  
   612  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   613  	if err != nil {
   614  		t.Fatal(err)
   615  	}
   616  
   617  	if !assert.Equal(t, 1, len(entities)) {
   618  		return
   619  	}
   620  
   621  	d := entities[0].Obj.(*v1.Deployment)
   622  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   623  		return
   624  	}
   625  
   626  	c := d.Spec.Template.Spec.Containers[0]
   627  
   628  	// Make sure container ref injection worked as expected
   629  	assert.Equal(t, "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95", c.Image)
   630  
   631  	assert.Equal(t, cmd.Argv, c.Command)
   632  	assert.Empty(t, c.Args)
   633  }
   634  
   635  func (f *ibdFixture) firstPodTemplateSpecHash() k8s.PodTemplateSpecHash {
   636  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   637  	if err != nil {
   638  		f.T().Fatal(err)
   639  	}
   640  
   641  	// if you want to use this from a test that applies more than one entity, it will have to change
   642  	require.Equal(f.T(), 1, len(entities), "expected only one entity. Yaml contained: %s", f.k8s.Yaml)
   643  
   644  	require.IsType(f.T(), &v1.Deployment{}, entities[0].Obj)
   645  	d := entities[0].Obj.(*v1.Deployment)
   646  	ret := k8s.PodTemplateSpecHash(d.Spec.Template.Labels[k8s.TiltPodTemplateHashLabel])
   647  	require.NotEqual(f.T(), ret, k8s.PodTemplateSpecHash(""))
   648  	return ret
   649  }
   650  
   651  func TestDeployInjectsPodTemplateSpecHash(t *testing.T) {
   652  	f := newIBDFixture(t, clusterid.ProductGKE)
   653  
   654  	manifest := NewSanchoDockerBuildManifest(f)
   655  
   656  	resultSet, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   657  	if err != nil {
   658  		t.Fatal(err)
   659  	}
   660  
   661  	hash := f.firstPodTemplateSpecHash()
   662  
   663  	require.True(t, k8sconv.ContainsHash(resultSet.ApplyFilter(), hash))
   664  }
   665  
   666  func TestDeployPodTemplateSpecHashChangesWhenImageChanges(t *testing.T) {
   667  	f := newIBDFixture(t, clusterid.ProductGKE)
   668  
   669  	manifest := NewSanchoDockerBuildManifest(f)
   670  
   671  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   672  	if err != nil {
   673  		t.Fatal(err)
   674  	}
   675  
   676  	hash1 := f.firstPodTemplateSpecHash()
   677  
   678  	// now change the image digest and build again
   679  	f.docker.BuildOutput = docker.ExampleBuildOutput2
   680  
   681  	_, err = f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   682  	if err != nil {
   683  		t.Fatal(err)
   684  	}
   685  
   686  	hash2 := f.firstPodTemplateSpecHash()
   687  
   688  	require.NotEqual(t, hash1, hash2)
   689  }
   690  
   691  func TestDeployInjectOverrideCommandClearsOldCommandButNotArgs(t *testing.T) {
   692  	f := newIBDFixture(t, clusterid.ProductGKE)
   693  
   694  	cmd := model.ToUnixCmd("./foo.sh bar")
   695  	manifest := NewSanchoDockerBuildManifestWithYaml(f, testyaml.SanchoYAMLWithCommand)
   696  	iTarg := manifest.ImageTargetAt(0).WithOverrideCommand(cmd)
   697  	manifest = manifest.WithImageTarget(iTarg)
   698  
   699  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   700  	if err != nil {
   701  		t.Fatal(err)
   702  	}
   703  
   704  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   705  	if err != nil {
   706  		t.Fatal(err)
   707  	}
   708  
   709  	if !assert.Equal(t, 1, len(entities)) {
   710  		return
   711  	}
   712  
   713  	d := entities[0].Obj.(*v1.Deployment)
   714  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   715  		return
   716  	}
   717  
   718  	c := d.Spec.Template.Spec.Containers[0]
   719  	assert.Equal(t, cmd.Argv, c.Command)
   720  	assert.Equal(t, []string{"something", "something_else"}, c.Args)
   721  }
   722  
   723  func TestDeployInjectOverrideCommandAndArgs(t *testing.T) {
   724  	f := newIBDFixture(t, clusterid.ProductGKE)
   725  
   726  	cmd := model.ToUnixCmd("./foo.sh bar")
   727  	manifest := NewSanchoDockerBuildManifestWithYaml(f, testyaml.SanchoYAMLWithCommand)
   728  	iTarg := manifest.ImageTargetAt(0).WithOverrideCommand(cmd)
   729  	iTarg.OverrideArgs = &v1alpha1.ImageMapOverrideArgs{}
   730  	manifest = manifest.WithImageTarget(iTarg)
   731  
   732  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   733  	if err != nil {
   734  		t.Fatal(err)
   735  	}
   736  
   737  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   738  	if err != nil {
   739  		t.Fatal(err)
   740  	}
   741  
   742  	if !assert.Equal(t, 1, len(entities)) {
   743  		return
   744  	}
   745  
   746  	d := entities[0].Obj.(*v1.Deployment)
   747  	if !assert.Equal(t, 1, len(d.Spec.Template.Spec.Containers)) {
   748  		return
   749  	}
   750  
   751  	c := d.Spec.Template.Spec.Containers[0]
   752  	assert.Equal(t, cmd.Argv, c.Command)
   753  	assert.Equal(t, []string(nil), c.Args)
   754  }
   755  
   756  func TestCantInjectOverrideCommandWithoutContainer(t *testing.T) {
   757  	f := newIBDFixture(t, clusterid.ProductGKE)
   758  
   759  	// CRD YAML: we WILL successfully inject the new image ref, but can't inject
   760  	// an override command for that image because it's not in a "container" block:
   761  	// expect an error when we try
   762  	crdYamlWithSanchoImage := strings.ReplaceAll(testyaml.CRDYAML, testyaml.CRDImage, testyaml.SanchoImage)
   763  
   764  	cmd := model.ToUnixCmd("./foo.sh bar")
   765  	manifest := manifestbuilder.New(f, "sancho").
   766  		WithK8sYAML(crdYamlWithSanchoImage).
   767  		WithNamedJSONPathImageLocator("projects.example.martin-helmich.de",
   768  			"{.spec.validation.openAPIV3Schema.properties.spec.properties.image}").
   769  		WithImageTarget(NewSanchoDockerBuildImageTarget(f).WithOverrideCommand(cmd)).
   770  		Build()
   771  
   772  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   773  	if assert.Error(t, err) {
   774  		assert.Contains(t, err.Error(), "could not inject command")
   775  	}
   776  }
   777  
   778  func TestInjectOverrideCommandsMultipleImages(t *testing.T) {
   779  	f := newIBDFixture(t, clusterid.ProductGKE)
   780  
   781  	cmd1 := model.ToUnixCmd("./command1.sh foo")
   782  	cmd2 := model.ToUnixCmd("./command2.sh bar baz")
   783  
   784  	iTarget1 := NewSanchoDockerBuildImageTarget(f).WithOverrideCommand(cmd1)
   785  	iTarget2 := NewSanchoSidecarDockerBuildImageTarget(f).WithOverrideCommand(cmd2)
   786  	kTarget := k8s.MustTarget("sancho", testyaml.SanchoSidecarYAML).
   787  		WithImageDependencies([]string{iTarget1.ImageMapName(), iTarget2.ImageMapName()})
   788  	targets := []model.TargetSpec{iTarget1, iTarget2, kTarget}
   789  
   790  	_, err := f.BuildAndDeploy(targets, store.BuildStateSet{})
   791  	if err != nil {
   792  		t.Fatal(err)
   793  	}
   794  
   795  	entities, err := k8s.ParseYAMLFromString(f.k8s.Yaml)
   796  	if err != nil {
   797  		t.Fatal(err)
   798  	}
   799  
   800  	if !assert.Equal(t, 1, len(entities)) {
   801  		return
   802  	}
   803  
   804  	d := entities[0].Obj.(*v1.Deployment)
   805  	if !assert.Equal(t, 2, len(d.Spec.Template.Spec.Containers)) {
   806  		return
   807  	}
   808  
   809  	sanchoContainer := d.Spec.Template.Spec.Containers[0]
   810  	sidecarContainer := d.Spec.Template.Spec.Containers[1]
   811  
   812  	// Make sure container ref injection worked as expected
   813  	assert.Equal(t, "gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95", sanchoContainer.Image)
   814  	assert.Equal(t, "gcr.io/some-project-162817/sancho-sidecar:tilt-11cd0b38bc3ceb95", sidecarContainer.Image)
   815  
   816  	assert.Equal(t, cmd1.Argv, sanchoContainer.Command)
   817  	assert.Equal(t, cmd2.Argv, sidecarContainer.Command)
   818  
   819  }
   820  
   821  func TestIBDDeployUIDs(t *testing.T) {
   822  	f := newIBDFixture(t, clusterid.ProductGKE)
   823  
   824  	manifest := NewSanchoDockerBuildManifest(f)
   825  	result, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   826  	if err != nil {
   827  		t.Fatal(err)
   828  	}
   829  
   830  	filter := result.ApplyFilter()
   831  	assert.Equal(t, 1, len(filter.DeployedRefs))
   832  	assert.True(t, k8sconv.ContainsUID(filter, f.k8s.LastUpsertResult[0].UID()))
   833  }
   834  
   835  func TestDockerBuildTargetStage(t *testing.T) {
   836  	f := newIBDFixture(t, clusterid.ProductGKE)
   837  
   838  	iTarget := NewSanchoDockerBuildImageTarget(f)
   839  	db := iTarget.BuildDetails.(model.DockerBuild)
   840  	db.DockerImageSpec.Target = "stage"
   841  	iTarget.BuildDetails = db
   842  
   843  	manifest := manifestbuilder.New(f, "sancho").
   844  		WithK8sYAML(testyaml.SanchoYAML).
   845  		WithImageTargets(iTarget).
   846  		Build()
   847  	_, err := f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   848  	if err != nil {
   849  		t.Fatal(err)
   850  	}
   851  	assert.Equal(t, "stage", f.docker.BuildOptions.Target)
   852  }
   853  
   854  func TestDockerBuildStatus(t *testing.T) {
   855  	f := newIBDFixture(t, clusterid.ProductGKE)
   856  
   857  	iTarget := NewSanchoDockerBuildImageTarget(f)
   858  	manifest := manifestbuilder.New(f, "sancho").
   859  		WithK8sYAML(testyaml.SanchoYAML).
   860  		WithImageTargets(iTarget).
   861  		Build()
   862  
   863  	iTarget = manifest.ImageTargets[0]
   864  	nn := ktypes.NamespacedName{Name: iTarget.DockerImageName}
   865  	err := f.ctrlClient.Create(f.ctx, &v1alpha1.DockerImage{
   866  		ObjectMeta: metav1.ObjectMeta{Name: nn.Name},
   867  		Spec:       iTarget.DockerBuildInfo().DockerImageSpec,
   868  	})
   869  	require.NoError(t, err)
   870  
   871  	_, err = f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   872  	require.NoError(t, err)
   873  
   874  	var di v1alpha1.DockerImage
   875  	err = f.ctrlClient.Get(f.ctx, nn, &di)
   876  	require.NoError(t, err)
   877  	require.NotNil(t, di.Status.Completed)
   878  }
   879  
   880  func TestCustomBuildStatus(t *testing.T) {
   881  	f := newIBDFixture(t, clusterid.ProductGKE)
   882  
   883  	sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
   884  	f.docker.Images["gcr.io/some-project-162817/sancho:tilt-build"] = types.ImageInspect{ID: string(sha)}
   885  
   886  	cb := model.CustomBuild{
   887  		CmdImageSpec: v1alpha1.CmdImageSpec{Args: model.ToHostCmd("exit 0").Argv, OutputTag: "tilt-build"},
   888  		Deps:         []string{f.JoinPath("app")},
   889  	}
   890  	iTarget := model.MustNewImageTarget(SanchoRef).WithBuildDetails(cb)
   891  	manifest := manifestbuilder.New(f, "sancho").
   892  		WithK8sYAML(testyaml.SanchoYAML).
   893  		WithImageTargets(iTarget).
   894  		Build()
   895  
   896  	iTarget = manifest.ImageTargets[0]
   897  	nn := ktypes.NamespacedName{Name: iTarget.CmdImageName}
   898  	err := f.ctrlClient.Create(f.ctx, &v1alpha1.CmdImage{
   899  		ObjectMeta: metav1.ObjectMeta{Name: nn.Name},
   900  		Spec:       iTarget.CustomBuildInfo().CmdImageSpec,
   901  	})
   902  	require.NoError(t, err)
   903  
   904  	_, err = f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   905  	require.NoError(t, err)
   906  
   907  	var ci v1alpha1.CmdImage
   908  	err = f.ctrlClient.Get(f.ctx, nn, &ci)
   909  	require.NoError(t, err)
   910  	require.NotNil(t, ci.Status.Completed)
   911  }
   912  
   913  func TestCustomBuildCancel(t *testing.T) {
   914  	f := newIBDFixture(t, clusterid.ProductGKE)
   915  
   916  	sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab")
   917  	f.docker.Images["gcr.io/some-project-162817/sancho:tilt-build"] = types.ImageInspect{ID: string(sha)}
   918  
   919  	cb := model.CustomBuild{
   920  		CmdImageSpec: v1alpha1.CmdImageSpec{Args: model.ToHostCmd("sleep 100").Argv, OutputTag: "tilt-build"},
   921  		Deps:         []string{f.JoinPath("app")},
   922  	}
   923  	iTarget := model.MustNewImageTarget(SanchoRef).WithBuildDetails(cb)
   924  	manifest := manifestbuilder.New(f, "sancho").
   925  		WithK8sYAML(testyaml.SanchoYAML).
   926  		WithImageTargets(iTarget).
   927  		Build()
   928  
   929  	iTarget = manifest.ImageTargets[0]
   930  	nn := ktypes.NamespacedName{Name: iTarget.CmdImageName}
   931  	err := f.ctrlClient.Create(f.ctx, &v1alpha1.CmdImage{
   932  		ObjectMeta: metav1.ObjectMeta{Name: nn.Name},
   933  		Spec:       iTarget.CustomBuildInfo().CmdImageSpec,
   934  	})
   935  	require.NoError(t, err)
   936  
   937  	originalCtx := f.ctx
   938  	ctx, cancel := context.WithCancel(f.ctx)
   939  	f.ctx = ctx
   940  	go func() {
   941  		cancel()
   942  	}()
   943  	_, err = f.BuildAndDeploy(BuildTargets(manifest), store.BuildStateSet{})
   944  	require.Error(t, err)
   945  	require.Contains(t, err.Error(), `Custom build "sleep 100" failed`)
   946  
   947  	var ci v1alpha1.CmdImage
   948  	err = f.ctrlClient.Get(originalCtx, nn, &ci)
   949  	require.NoError(t, err)
   950  	require.NotNil(t, ci.Status.Completed)
   951  }
   952  
   953  func TestTwoManifestsWithCommonImage(t *testing.T) {
   954  	f := newIBDFixture(t, clusterid.ProductGKE)
   955  
   956  	m1, m2 := NewManifestsWithCommonAncestor(f)
   957  	results1, err := f.BuildAndDeploy(BuildTargets(m1), store.BuildStateSet{})
   958  	require.NoError(t, err)
   959  	assert.Equal(t,
   960  		[]string{"image:gcr.io_common", "image:gcr.io_image-1", "k8s:image-1"},
   961  		resultKeys(results1))
   962  
   963  	stateSet := f.resultsToNextState(results1)
   964  
   965  	results2, err := f.BuildAndDeploy(BuildTargets(m2), stateSet)
   966  	require.NoError(t, err)
   967  	assert.Equal(t,
   968  		// We did not return image-common because it didn't need a rebuild.
   969  		[]string{"image:gcr.io_image-2", "k8s:image-2"},
   970  		resultKeys(results2))
   971  }
   972  
   973  func TestTwoManifestsWithCommonImagePrebuilt(t *testing.T) {
   974  	f := newIBDFixture(t, clusterid.ProductGKE)
   975  
   976  	m1, _ := NewManifestsWithCommonAncestor(f)
   977  	iTarget1 := m1.ImageTargets[0]
   978  	prebuilt1 := store.NewImageBuildResultSingleRef(iTarget1.ID(),
   979  		container.MustParseNamedTagged("gcr.io/common:tilt-prebuilt"))
   980  
   981  	stateSet := store.BuildStateSet{}
   982  	stateSet[iTarget1.ID()] = store.NewBuildState(prebuilt1, nil, nil)
   983  
   984  	results1, err := f.BuildAndDeploy(BuildTargets(m1), stateSet)
   985  	require.NoError(t, err)
   986  	assert.Equal(t,
   987  		[]string{"image:gcr.io_image-1", "k8s:image-1"},
   988  		resultKeys(results1))
   989  	assert.Contains(t, f.out.String(),
   990  		"STEP 1/4 — Loading cached images\n     - gcr.io/common:tilt-prebuilt")
   991  }
   992  
   993  func TestTwoManifestsWithTwoCommonAncestors(t *testing.T) {
   994  	f := newIBDFixture(t, clusterid.ProductGKE)
   995  
   996  	m1, m2 := NewManifestsWithTwoCommonAncestors(f)
   997  	results1, err := f.BuildAndDeploy(BuildTargets(m1), store.BuildStateSet{})
   998  	require.NoError(t, err)
   999  	assert.Equal(t,
  1000  		[]string{"image:gcr.io_base", "image:gcr.io_common", "image:gcr.io_image-1", "k8s:image-1"},
  1001  		resultKeys(results1))
  1002  
  1003  	stateSet := f.resultsToNextState(results1)
  1004  
  1005  	results2, err := f.BuildAndDeploy(BuildTargets(m2), stateSet)
  1006  	require.NoError(t, err)
  1007  	assert.Equal(t,
  1008  		// We did not return image-common because it didn't need a rebuild.
  1009  		[]string{"image:gcr.io_image-2", "k8s:image-2"},
  1010  		resultKeys(results2))
  1011  }
  1012  
  1013  func TestTwoManifestsWithSameTwoImages(t *testing.T) {
  1014  	f := newIBDFixture(t, clusterid.ProductGKE)
  1015  
  1016  	m1, m2 := NewManifestsWithSameTwoImages(f)
  1017  	results1, err := f.BuildAndDeploy(BuildTargets(m1), store.BuildStateSet{})
  1018  	require.NoError(t, err)
  1019  	assert.Equal(t,
  1020  		[]string{"image:gcr.io_common", "image:gcr.io_image-1", "k8s:dep-1"},
  1021  		resultKeys(results1))
  1022  
  1023  	stateSet := f.resultsToNextState(results1)
  1024  
  1025  	results2, err := f.BuildAndDeploy(BuildTargets(m2), stateSet)
  1026  	require.NoError(t, err)
  1027  	assert.Equal(t,
  1028  		[]string{"k8s:dep-2"},
  1029  		resultKeys(results2))
  1030  }
  1031  
  1032  func TestPlatformFromCluster(t *testing.T) {
  1033  	f := newIBDFixture(t, clusterid.ProductGKE)
  1034  	f.cluster.Status.Arch = "amd64"
  1035  
  1036  	m := NewSanchoDockerBuildManifest(f)
  1037  	iTargetID1 := m.ImageTargets[0].ID()
  1038  	stateSet := store.BuildStateSet{
  1039  		iTargetID1: store.BuildState{FullBuildTriggered: true},
  1040  	}
  1041  	_, err := f.BuildAndDeploy(BuildTargets(m), stateSet)
  1042  	require.NoError(t, err)
  1043  	assert.Equal(t, "linux/amd64", f.docker.BuildOptions.Platform)
  1044  }
  1045  
  1046  func TestDockerForMacDeploy(t *testing.T) {
  1047  	f := newIBDFixture(t, clusterid.ProductDockerDesktop)
  1048  
  1049  	manifest := NewSanchoDockerBuildManifest(f)
  1050  	targets := BuildTargets(manifest)
  1051  	_, err := f.BuildAndDeploy(targets, store.BuildStateSet{})
  1052  	if err != nil {
  1053  		t.Fatal(err)
  1054  	}
  1055  
  1056  	if f.docker.BuildCount != 1 {
  1057  		t.Errorf("Expected 1 docker build, actual: %d", f.docker.BuildCount)
  1058  	}
  1059  
  1060  	if f.docker.PushCount != 0 {
  1061  		t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount)
  1062  	}
  1063  
  1064  	expectedYaml := "image: gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95"
  1065  	if !strings.Contains(f.k8s.Yaml, expectedYaml) {
  1066  		t.Errorf("Expected yaml to contain %q. Actual:\n%s", expectedYaml, f.k8s.Yaml)
  1067  	}
  1068  }
  1069  
  1070  func resultKeys(result store.BuildResultSet) []string {
  1071  	keys := []string{}
  1072  	for id := range result {
  1073  		keys = append(keys, id.String())
  1074  	}
  1075  	sort.Strings(keys)
  1076  	return keys
  1077  }
  1078  
  1079  type ibdFixture struct {
  1080  	*tempdir.TempDirFixture
  1081  	out        *bufsync.ThreadSafeBuffer
  1082  	ctx        context.Context
  1083  	docker     *docker.FakeClient
  1084  	k8s        *k8s.FakeK8sClient
  1085  	ibd        *ImageBuildAndDeployer
  1086  	st         *store.TestingStore
  1087  	kl         *fakeKINDLoader
  1088  	ctrlClient ctrlclient.Client
  1089  	cluster    *v1alpha1.Cluster
  1090  }
  1091  
  1092  func newIBDFixture(t *testing.T, env clusterid.Product) *ibdFixture {
  1093  	f := tempdir.NewTempDirFixture(t)
  1094  
  1095  	// empty dirs for build contexts
  1096  	_ = os.Mkdir(f.JoinPath("sancho"), 0777)
  1097  	_ = os.Mkdir(f.JoinPath("sancho-base"), 0777)
  1098  
  1099  	dir := dirs.NewTiltDevDirAt(f.Path())
  1100  
  1101  	dockerClient := docker.NewFakeClient()
  1102  
  1103  	// Make the fake ImageExists always return true, which is the behavior we want
  1104  	// when testing the ImageBuildAndDeployer.
  1105  	dockerClient.ImageAlwaysExists = true
  1106  
  1107  	out := bufsync.NewThreadSafeBuffer()
  1108  	ctx, _, ta := testutils.CtxAndAnalyticsForTest()
  1109  	ctx = logger.WithLogger(ctx, logger.NewTestLogger(out))
  1110  	kClient := k8s.NewFakeK8sClient(t)
  1111  	kl := &fakeKINDLoader{}
  1112  	clock := fakeClock{time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC)}
  1113  	kubeContext := k8s.KubeContext(fmt.Sprintf("%s-me", env))
  1114  	clusterEnv := docker.ClusterEnv(docker.Env{})
  1115  	if env == clusterid.ProductDockerDesktop {
  1116  		clusterEnv.BuildToKubeContexts = []string{string(kubeContext)}
  1117  	}
  1118  	dockerClient.FakeEnv = docker.Env(clusterEnv)
  1119  
  1120  	ctrlClient := fake.NewFakeTiltClient()
  1121  	st := store.NewTestingStore()
  1122  	cclock := clockwork.NewFakeClock()
  1123  	ibd, err := ProvideImageBuildAndDeployer(ctx, dockerClient, kClient, env, kubeContext,
  1124  		clusterEnv, dir, clock, cclock, kl, ta, ctrlClient, st)
  1125  	if err != nil {
  1126  		t.Fatal(err)
  1127  	}
  1128  
  1129  	cluster := &v1alpha1.Cluster{
  1130  		Status: v1alpha1.ClusterStatus{
  1131  			Connection: &v1alpha1.ClusterConnectionStatus{
  1132  				Kubernetes: &v1alpha1.KubernetesClusterConnectionStatus{
  1133  					Product: string(env),
  1134  					Context: string(kubeContext),
  1135  				},
  1136  			},
  1137  		},
  1138  	}
  1139  	ret := &ibdFixture{
  1140  		TempDirFixture: f,
  1141  		out:            out,
  1142  		ctx:            ctx,
  1143  		docker:         dockerClient,
  1144  		k8s:            kClient,
  1145  		ibd:            ibd,
  1146  		st:             st,
  1147  		kl:             kl,
  1148  		ctrlClient:     ctrlClient,
  1149  		cluster:        cluster,
  1150  	}
  1151  
  1152  	return ret
  1153  }
  1154  
  1155  func (f *ibdFixture) upsertSpec(obj ctrlclient.Object) {
  1156  	fake.UpsertSpec(f.ctx, f.T(), f.ctrlClient, obj)
  1157  }
  1158  
  1159  func (f *ibdFixture) updateStatus(obj ctrlclient.Object) {
  1160  	fake.UpdateStatus(f.ctx, f.T(), f.ctrlClient, obj)
  1161  }
  1162  
  1163  func (f *ibdFixture) BuildAndDeploy(specs []model.TargetSpec, stateSet store.BuildStateSet) (store.BuildResultSet, error) {
  1164  	if stateSet == nil {
  1165  		stateSet = store.BuildStateSet{}
  1166  	}
  1167  	iTargets, kTargets := extractImageAndK8sTargets(specs)
  1168  	for _, iTarget := range iTargets {
  1169  		if iTarget.IsLiveUpdateOnly {
  1170  			continue
  1171  		}
  1172  
  1173  		im := v1alpha1.ImageMap{
  1174  			ObjectMeta: metav1.ObjectMeta{Name: iTarget.ID().Name.String()},
  1175  			Spec:       iTarget.ImageMapSpec,
  1176  		}
  1177  		f.upsertSpec(&im)
  1178  
  1179  		state := stateSet[iTarget.ID()]
  1180  		imageBuildResult, ok := state.LastResult.(store.ImageBuildResult)
  1181  		if ok {
  1182  			im.Status = imageBuildResult.ImageMapStatus
  1183  		}
  1184  		f.updateStatus(&im)
  1185  
  1186  		s := stateSet[iTarget.ID()]
  1187  		s.Cluster = f.cluster
  1188  		stateSet[iTarget.ID()] = s
  1189  
  1190  		// The reconcilers usually invoke their own async requeuers,
  1191  		// so do the reconciliation manually to make these tests synchronous.
  1192  		if iTarget.CmdImageName != "" {
  1193  			cmdImageSpec := iTarget.CustomBuildInfo().CmdImageSpec
  1194  			c := v1alpha1.Cmd{
  1195  				ObjectMeta: metav1.ObjectMeta{Name: iTarget.CmdImageName},
  1196  				Spec: v1alpha1.CmdSpec{
  1197  					Args: cmdImageSpec.Args,
  1198  					Dir:  cmdImageSpec.Dir,
  1199  				},
  1200  			}
  1201  			f.upsertSpec(&c)
  1202  
  1203  			defer f.ibd.cr.Reconcile(context.Background(), ctrl.Request{NamespacedName: ktypes.NamespacedName{Name: iTarget.CmdImageName}})
  1204  		}
  1205  		if iTarget.DockerImageName != "" {
  1206  			defer f.ibd.dr.Reconcile(context.Background(), ctrl.Request{NamespacedName: ktypes.NamespacedName{Name: iTarget.DockerImageName}})
  1207  		}
  1208  	}
  1209  	for _, kTarget := range kTargets {
  1210  		ka := v1alpha1.KubernetesApply{
  1211  			ObjectMeta: metav1.ObjectMeta{Name: kTarget.ID().Name.String()},
  1212  			Spec:       kTarget.KubernetesApplySpec,
  1213  		}
  1214  		f.upsertSpec(&ka)
  1215  	}
  1216  	return f.ibd.BuildAndDeploy(f.ctx, f.st, specs, stateSet)
  1217  }
  1218  
  1219  func (f *ibdFixture) resultsToNextState(results store.BuildResultSet) store.BuildStateSet {
  1220  	stateSet := store.BuildStateSet{}
  1221  	for id, result := range results {
  1222  		stateSet[id] = store.NewBuildState(result, nil, nil)
  1223  	}
  1224  	return stateSet
  1225  }
  1226  
  1227  func (f *ibdFixture) refs(iTarget model.ImageTarget) container.RefSet {
  1228  	f.T().Helper()
  1229  	refs, err := iTarget.Refs(f.cluster)
  1230  	require.NoErrorf(f.T(), err, "Determining refs for %s", iTarget.ID().String())
  1231  	return refs
  1232  }
  1233  
  1234  func newK8sMultiEntityManifest(name string) model.Manifest {
  1235  	yaml := fmt.Sprintf(`
  1236  apiVersion: v1
  1237  kind: PersistentVolumeClaim
  1238  metadata:
  1239    name: %s-pvc
  1240  spec: {}
  1241  status: {}
  1242  
  1243  ---
  1244  
  1245  apiVersion: v1
  1246  kind: Deployment
  1247  metadata:
  1248    name: %s-deployment
  1249  spec: {}
  1250  status: {}`, name, name)
  1251  	return model.Manifest{Name: model.ManifestName(name)}.WithDeployTarget(model.NewK8sTargetForTesting(yaml))
  1252  }
  1253  
  1254  type fakeKINDLoader struct {
  1255  	loadCount int
  1256  }
  1257  
  1258  func (kl *fakeKINDLoader) LoadToKIND(ctx context.Context, cluster *v1alpha1.Cluster, ref reference.NamedTagged) error {
  1259  	kl.loadCount++
  1260  	return nil
  1261  }
  1262  
  1263  type fakeClock struct {
  1264  	now time.Time
  1265  }
  1266  
  1267  func (c fakeClock) Now() time.Time { return c.now }