github.com/grahambrereton-form3/tilt@v0.10.18/internal/build/container_test.go (about) 1 //+build !skipcontainertests 2 3 // Tests that involve spinning up/interacting with actual containers 4 package build 5 6 import ( 7 "io/ioutil" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/docker/distribution/reference" 13 "github.com/stretchr/testify/assert" 14 15 "github.com/windmilleng/tilt/internal/docker" 16 "github.com/windmilleng/tilt/internal/dockerfile" 17 "github.com/windmilleng/tilt/pkg/model" 18 ) 19 20 // * * * IMAGE BUILDER * * * 21 22 func TestDockerBuildDockerfile(t *testing.T) { 23 f := newDockerBuildFixture(t) 24 defer f.teardown() 25 26 df := dockerfile.Dockerfile(` 27 FROM alpine 28 WORKDIR /src 29 ADD a.txt . 30 RUN cp a.txt b.txt 31 ADD dir/c.txt . 32 `) 33 34 f.WriteFile("a.txt", "a") 35 f.WriteFile("dir/c.txt", "c") 36 f.WriteFile("missing.txt", "missing") 37 38 ref, err := f.b.BuildImage(f.ctx, f.ps, f.getNameFromTest(), df, f.Path(), model.EmptyMatcher, model.DockerBuildArgs{}, "") 39 if err != nil { 40 t.Fatal(err) 41 } 42 43 f.assertImageHasLabels(ref, docker.BuiltByTiltLabel) 44 45 pcs := []expectedFile{ 46 expectedFile{Path: "/src/a.txt", Contents: "a"}, 47 expectedFile{Path: "/src/b.txt", Contents: "a"}, 48 expectedFile{Path: "/src/c.txt", Contents: "c"}, 49 expectedFile{Path: "/src/dir/c.txt", Missing: true}, 50 expectedFile{Path: "/src/missing.txt", Missing: true}, 51 } 52 f.assertFilesInImage(ref, pcs) 53 } 54 55 func TestDockerBuildWithBuildArgs(t *testing.T) { 56 f := newDockerBuildFixture(t) 57 defer f.teardown() 58 59 df := dockerfile.Dockerfile(`FROM alpine 60 ARG some_variable_name 61 62 ADD $some_variable_name /test.txt`) 63 64 f.WriteFile("awesome_variable", "hi im an awesome variable") 65 66 ba := model.DockerBuildArgs{ 67 "some_variable_name": "awesome_variable", 68 } 69 ref, err := f.b.BuildImage(f.ctx, f.ps, f.getNameFromTest(), df, f.Path(), model.EmptyMatcher, ba, "") 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 expected := []expectedFile{ 75 expectedFile{Path: "/test.txt", Contents: "hi im an awesome variable"}, 76 } 77 f.assertFilesInImage(ref, expected) 78 } 79 80 func TestSync(t *testing.T) { 81 f := newDockerBuildFixture(t) 82 defer f.teardown() 83 84 // write some files in to it 85 f.WriteFile("hi/hello", "hi hello") 86 f.WriteFile("sup", "my name is dan") 87 88 s := model.Sync{ 89 LocalPath: f.Path(), 90 ContainerPath: "/src", 91 } 92 93 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{s}, model.EmptyMatcher, nil, model.Cmd{}) 94 if err != nil { 95 t.Fatal(err) 96 } 97 98 pcs := []expectedFile{ 99 expectedFile{Path: "/src/hi/hello", Contents: "hi hello"}, 100 expectedFile{Path: "/src/sup", Contents: "my name is dan"}, 101 } 102 f.assertFilesInImage(ref, pcs) 103 } 104 105 func TestSyncFileToDirectory(t *testing.T) { 106 f := newDockerBuildFixture(t) 107 defer f.teardown() 108 109 f.WriteFile("sup", "my name is dan") 110 111 s := model.Sync{ 112 LocalPath: f.JoinPath("sup"), 113 ContainerPath: "/src/", 114 } 115 116 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{s}, model.EmptyMatcher, nil, model.Cmd{}) 117 if err != nil { 118 t.Fatal(err) 119 } 120 121 pcs := []expectedFile{ 122 expectedFile{Path: "/src/sup", Contents: "my name is dan"}, 123 } 124 f.assertFilesInImage(ref, pcs) 125 } 126 127 func TestMultipleSyncs(t *testing.T) { 128 f := newDockerBuildFixture(t) 129 defer f.teardown() 130 131 // write some files in to it 132 f.WriteFile("hi/hello", "hi hello") 133 f.WriteFile("bye/ciao/goodbye", "bye laterz") 134 135 s1 := model.Sync{ 136 LocalPath: f.JoinPath("hi"), 137 ContainerPath: "/hello_there", 138 } 139 s2 := model.Sync{ 140 LocalPath: f.JoinPath("bye"), 141 ContainerPath: "goodbye_there", 142 } 143 144 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{s1, s2}, model.EmptyMatcher, nil, model.Cmd{}) 145 if err != nil { 146 t.Fatal(err) 147 } 148 149 pcs := []expectedFile{ 150 expectedFile{Path: "/hello_there/hello", Contents: "hi hello"}, 151 expectedFile{Path: "/goodbye_there/ciao/goodbye", Contents: "bye laterz"}, 152 } 153 f.assertFilesInImage(ref, pcs) 154 } 155 156 func TestSyncCollisions(t *testing.T) { 157 f := newDockerBuildFixture(t) 158 defer f.teardown() 159 160 // write some files in to it 161 f.WriteFile("hi/hello", "hi hello") 162 f.WriteFile("bye/hello", "bye laterz") 163 164 // Sync-ing two files to the same place in the container -- expect the second file 165 // to take precedence (file should contain "bye laterz") 166 s1 := model.Sync{ 167 LocalPath: f.JoinPath("hi"), 168 ContainerPath: "/hello_there", 169 } 170 s2 := model.Sync{ 171 LocalPath: f.JoinPath("bye"), 172 ContainerPath: "/hello_there", 173 } 174 175 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{s1, s2}, model.EmptyMatcher, nil, model.Cmd{}) 176 if err != nil { 177 t.Fatal(err) 178 } 179 180 pcs := []expectedFile{ 181 expectedFile{Path: "/hello_there/hello", Contents: "bye laterz"}, 182 } 183 f.assertFilesInImage(ref, pcs) 184 } 185 186 func TestPush(t *testing.T) { 187 f := newDockerBuildFixture(t) 188 defer f.teardown() 189 190 f.startRegistry() 191 192 // write some files in to it 193 f.WriteFile("hi/hello", "hi hello") 194 f.WriteFile("sup", "my name is dan") 195 196 s := model.Sync{ 197 LocalPath: f.Path(), 198 ContainerPath: "/src", 199 } 200 201 name, err := reference.WithName("localhost:5005/myimage") 202 if err != nil { 203 t.Fatal(err) 204 } 205 206 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, name, simpleDockerfile, []model.Sync{s}, model.EmptyMatcher, nil, model.Cmd{}) 207 if err != nil { 208 t.Fatal(err) 209 } 210 211 namedTagged, err := f.b.PushImage(f.ctx, ref, ioutil.Discard) 212 if err != nil { 213 t.Fatal(err) 214 } 215 216 pcs := []expectedFile{ 217 expectedFile{Path: "/src/hi/hello", Contents: "hi hello"}, 218 expectedFile{Path: "/src/sup", Contents: "my name is dan"}, 219 } 220 221 f.assertFilesInImage(namedTagged, pcs) 222 } 223 224 func TestPushInvalid(t *testing.T) { 225 f := newDockerBuildFixture(t) 226 defer f.teardown() 227 228 s := model.Sync{ 229 LocalPath: f.Path(), 230 ContainerPath: "/src", 231 } 232 name, err := reference.WithName("localhost:5005/myimage") 233 if err != nil { 234 t.Fatal(err) 235 } 236 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, name, simpleDockerfile, []model.Sync{s}, model.EmptyMatcher, nil, model.Cmd{}) 237 if err != nil { 238 t.Fatal(err) 239 } 240 241 _, err = f.b.PushImage(f.ctx, ref, ioutil.Discard) 242 msg := `pushing image "localhost:5005/myimage"` 243 if err == nil || !strings.Contains(err.Error(), msg) { 244 t.Fatalf("Expected error containing %q, actual: %v", msg, err) 245 } 246 } 247 248 func TestBuildOneRun(t *testing.T) { 249 f := newDockerBuildFixture(t) 250 defer f.teardown() 251 252 runs := model.ToRuns([]model.Cmd{ 253 model.ToShellCmd("echo -n hello >> hi"), 254 }) 255 256 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{}, model.EmptyMatcher, runs, model.Cmd{}) 257 if err != nil { 258 t.Fatal(err) 259 } 260 261 expected := []expectedFile{ 262 expectedFile{Path: "hi", Contents: "hello"}, 263 } 264 f.assertFilesInImage(ref, expected) 265 } 266 267 func TestBuildMultipleRuns(t *testing.T) { 268 f := newDockerBuildFixture(t) 269 defer f.teardown() 270 271 runs := model.ToRuns([]model.Cmd{ 272 model.ToShellCmd("echo -n hello >> hi"), 273 model.ToShellCmd("echo -n sup >> hi2"), 274 }) 275 276 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{}, model.EmptyMatcher, runs, model.Cmd{}) 277 if err != nil { 278 t.Fatal(err) 279 } 280 281 expected := []expectedFile{ 282 expectedFile{Path: "hi", Contents: "hello"}, 283 expectedFile{Path: "hi2", Contents: "sup"}, 284 } 285 f.assertFilesInImage(ref, expected) 286 } 287 288 func TestBuildMultipleRunsRemoveFiles(t *testing.T) { 289 f := newDockerBuildFixture(t) 290 defer f.teardown() 291 292 runs := model.ToRuns([]model.Cmd{ 293 model.Cmd{Argv: []string{"sh", "-c", "echo -n hello >> hi"}}, 294 model.Cmd{Argv: []string{"sh", "-c", "echo -n sup >> hi2"}}, 295 model.Cmd{Argv: []string{"sh", "-c", "rm hi"}}, 296 }) 297 298 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{}, model.EmptyMatcher, runs, model.Cmd{}) 299 if err != nil { 300 t.Fatal(err) 301 } 302 303 expected := []expectedFile{ 304 expectedFile{Path: "hi2", Contents: "sup"}, 305 expectedFile{Path: "hi", Missing: true}, 306 } 307 f.assertFilesInImage(ref, expected) 308 } 309 310 func TestBuildFailingRun(t *testing.T) { 311 f := newDockerBuildFixture(t) 312 defer f.teardown() 313 314 runs := model.ToRuns([]model.Cmd{ 315 model.ToShellCmd("echo hello && exit 1"), 316 }) 317 318 _, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{}, model.EmptyMatcher, runs, model.Cmd{}) 319 if assert.NotNil(t, err) { 320 assert.Contains(t, err.Error(), "hello") 321 322 // Different versions of docker have a different error string 323 hasExitCode1 := strings.Contains(err.Error(), "exit code 1") || 324 strings.Contains(err.Error(), "returned a non-zero code: 1") || 325 strings.Contains(err.Error(), "exit code: 1") 326 if !hasExitCode1 { 327 t.Errorf("Expected failure with exit code 1, actual: %v", err) 328 } 329 } 330 } 331 332 func TestEntrypoint(t *testing.T) { 333 f := newDockerBuildFixture(t) 334 defer f.teardown() 335 336 entrypoint := model.ToShellCmd("echo -n hello >> hi") 337 d, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, nil, model.EmptyMatcher, nil, entrypoint) 338 if err != nil { 339 t.Fatal(err) 340 } 341 342 expected := []expectedFile{ 343 expectedFile{Path: "hi", Contents: "hello"}, 344 } 345 346 // Start container WITHOUT overriding entrypoint (which assertFilesInImage... does) 347 cID := f.startContainer(f.ctx, containerConfig(d)) 348 f.assertFilesInContainer(f.ctx, cID, expected) 349 } 350 351 func TestDockerfileWithEntrypointPermitted(t *testing.T) { 352 f := newDockerBuildFixture(t) 353 defer f.teardown() 354 355 df := dockerfile.Dockerfile(`FROM alpine 356 ENTRYPOINT ["sleep", "100000"]`) 357 358 _, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), df, nil, model.EmptyMatcher, nil, model.Cmd{}) 359 if err != nil { 360 t.Errorf("Unexpected error %v", err) 361 } 362 } 363 364 func TestReapOneImage(t *testing.T) { 365 f := newDockerBuildFixture(t) 366 defer f.teardown() 367 368 s := model.Sync{ 369 LocalPath: f.Path(), 370 ContainerPath: "/src", 371 } 372 373 df1 := simpleDockerfile 374 ref1, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), df1, []model.Sync{s}, model.EmptyMatcher, nil, model.Cmd{}) 375 if err != nil { 376 t.Fatal(err) 377 } 378 379 label := dockerfile.Label("tilt.reaperTest") 380 f.b.extraLabels[label] = "1" 381 df2 := simpleDockerfile.Run(model.ToShellCmd("echo hi >> hi.txt")) 382 ref2, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), df2, []model.Sync{s}, model.EmptyMatcher, nil, model.Cmd{}) 383 if err != nil { 384 t.Fatal(err) 385 } 386 387 err = f.reaper.RemoveTiltImages(f.ctx, time.Now().Add(time.Second), false, FilterByLabel(label)) 388 if err != nil { 389 t.Fatal(err) 390 } 391 392 f.assertImageExists(ref1) 393 f.assertImageNotExists(ref2) 394 } 395 396 func TestConditionalRunInRealDocker(t *testing.T) { 397 f := newDockerBuildFixture(t) 398 defer f.teardown() 399 400 f.WriteFile("a.txt", "a") 401 f.WriteFile("b.txt", "b") 402 403 s := model.Sync{ 404 LocalPath: f.Path(), 405 ContainerPath: "/src", 406 } 407 run1 := model.Run{ 408 Cmd: model.ToShellCmd("cat /src/a.txt >> /src/c.txt"), 409 Triggers: model.NewPathSet([]string{"a.txt"}, f.Path()), 410 } 411 run2 := model.Run{ 412 Cmd: model.ToShellCmd("cat /src/b.txt >> /src/d.txt"), 413 } 414 415 ref, err := f.b.DeprecatedFastBuildImage(f.ctx, f.ps, f.getNameFromTest(), simpleDockerfile, []model.Sync{s}, model.EmptyMatcher, []model.Run{run1, run2}, model.Cmd{}) 416 if err != nil { 417 t.Fatal(err) 418 } 419 420 pcs := []expectedFile{ 421 expectedFile{Path: "/src/a.txt", Contents: "a"}, 422 expectedFile{Path: "/src/b.txt", Contents: "b"}, 423 expectedFile{Path: "/src/c.txt", Contents: "a"}, 424 expectedFile{Path: "/src/d.txt", Contents: "b"}, 425 } 426 f.assertFilesInImage(ref, pcs) 427 }