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 }