github.com/rawahars/moby@v24.0.4+incompatible/builder/dockerfile/dispatchers_test.go (about) 1 package dockerfile // import "github.com/docker/docker/builder/dockerfile" 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "runtime" 8 "strings" 9 "testing" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/backend" 13 "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/api/types/strslice" 15 "github.com/docker/docker/builder" 16 "github.com/docker/docker/image" 17 "github.com/docker/docker/oci" 18 "github.com/docker/go-connections/nat" 19 "github.com/moby/buildkit/frontend/dockerfile/instructions" 20 "github.com/moby/buildkit/frontend/dockerfile/parser" 21 "github.com/moby/buildkit/frontend/dockerfile/shell" 22 "gotest.tools/v3/assert" 23 is "gotest.tools/v3/assert/cmp" 24 ) 25 26 func newBuilderWithMockBackend(t *testing.T) *Builder { 27 t.Helper() 28 mockBackend := &MockBackend{} 29 opts := &types.ImageBuildOptions{} 30 ctx := context.Background() 31 32 imageProber, err := newImageProber(ctx, mockBackend, nil, false) 33 assert.NilError(t, err, "Could not create image prober") 34 35 b := &Builder{ 36 options: opts, 37 docker: mockBackend, 38 Stdout: new(bytes.Buffer), 39 disableCommit: true, 40 imageSources: newImageSources(builderOptions{ 41 Options: opts, 42 Backend: mockBackend, 43 }), 44 imageProber: imageProber, 45 containerManager: newContainerManager(mockBackend), 46 } 47 return b 48 } 49 50 func TestEnv2Variables(t *testing.T) { 51 b := newBuilderWithMockBackend(t) 52 sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 53 envCommand := &instructions.EnvCommand{ 54 Env: instructions.KeyValuePairs{ 55 instructions.KeyValuePair{Key: "var1", Value: "val1"}, 56 instructions.KeyValuePair{Key: "var2", Value: "val2"}, 57 }, 58 } 59 err := dispatch(context.TODO(), sb, envCommand) 60 assert.NilError(t, err) 61 62 expected := []string{ 63 "var1=val1", 64 "var2=val2", 65 } 66 assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env)) 67 } 68 69 func TestEnvValueWithExistingRunConfigEnv(t *testing.T) { 70 b := newBuilderWithMockBackend(t) 71 sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 72 sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"} 73 envCommand := &instructions.EnvCommand{ 74 Env: instructions.KeyValuePairs{ 75 instructions.KeyValuePair{Key: "var1", Value: "val1"}, 76 }, 77 } 78 err := dispatch(context.TODO(), sb, envCommand) 79 assert.NilError(t, err) 80 expected := []string{ 81 "var1=val1", 82 "var2=fromenv", 83 } 84 assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env)) 85 } 86 87 func TestMaintainer(t *testing.T) { 88 maintainerEntry := "Some Maintainer <maintainer@example.com>" 89 b := newBuilderWithMockBackend(t) 90 sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 91 cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry} 92 err := dispatch(context.TODO(), sb, cmd) 93 assert.NilError(t, err) 94 assert.Check(t, is.Equal(maintainerEntry, sb.state.maintainer)) 95 } 96 97 func TestLabel(t *testing.T) { 98 labelName := "label" 99 labelValue := "value" 100 101 b := newBuilderWithMockBackend(t) 102 sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 103 cmd := &instructions.LabelCommand{ 104 Labels: instructions.KeyValuePairs{ 105 instructions.KeyValuePair{Key: labelName, Value: labelValue}, 106 }, 107 } 108 err := dispatch(context.TODO(), sb, cmd) 109 assert.NilError(t, err) 110 111 assert.Assert(t, is.Contains(sb.state.runConfig.Labels, labelName)) 112 assert.Check(t, is.Equal(sb.state.runConfig.Labels[labelName], labelValue)) 113 } 114 115 func TestFromScratch(t *testing.T) { 116 b := newBuilderWithMockBackend(t) 117 sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 118 cmd := &instructions.Stage{ 119 BaseName: "scratch", 120 } 121 err := initializeStage(context.TODO(), sb, cmd) 122 123 if runtime.GOOS == "windows" { 124 assert.Check(t, is.Error(err, "Windows does not support FROM scratch")) 125 return 126 } 127 128 assert.NilError(t, err) 129 assert.Check(t, sb.state.hasFromImage()) 130 assert.Check(t, is.Equal("", sb.state.imageID)) 131 // TODO(thaJeztah): use github.com/moby/buildkit/util/system.DefaultPathEnv() once https://github.com/moby/buildkit/pull/3158 is resolved. 132 expected := "PATH=" + oci.DefaultPathEnv(runtime.GOOS) 133 assert.Check(t, is.DeepEqual([]string{expected}, sb.state.runConfig.Env)) 134 } 135 136 func TestFromWithArg(t *testing.T) { 137 tag, expected := ":sometag", "expectedthisid" 138 139 getImage := func(name string) (builder.Image, builder.ROLayer, error) { 140 assert.Check(t, is.Equal("alpine"+tag, name)) 141 return &mockImage{id: "expectedthisid"}, nil, nil 142 } 143 b := newBuilderWithMockBackend(t) 144 b.docker.(*MockBackend).getImageFunc = getImage 145 args := NewBuildArgs(make(map[string]*string)) 146 147 val := "sometag" 148 metaArg := instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{ 149 Key: "THETAG", 150 Value: &val, 151 }}} 152 cmd := &instructions.Stage{ 153 BaseName: "alpine:${THETAG}", 154 } 155 err := processMetaArg(metaArg, shell.NewLex('\\'), args) 156 157 sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults()) 158 assert.NilError(t, err) 159 err = initializeStage(context.TODO(), sb, cmd) 160 assert.NilError(t, err) 161 162 assert.Check(t, is.Equal(expected, sb.state.imageID)) 163 assert.Check(t, is.Equal(expected, sb.state.baseImage.ImageID())) 164 assert.Check(t, is.Len(sb.state.buildArgs.GetAllAllowed(), 0)) 165 assert.Check(t, is.Len(sb.state.buildArgs.GetAllMeta(), 1)) 166 } 167 168 func TestFromWithArgButBuildArgsNotGiven(t *testing.T) { 169 b := newBuilderWithMockBackend(t) 170 args := NewBuildArgs(make(map[string]*string)) 171 172 metaArg := instructions.ArgCommand{} 173 cmd := &instructions.Stage{ 174 BaseName: "${THETAG}", 175 } 176 err := processMetaArg(metaArg, shell.NewLex('\\'), args) 177 178 sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults()) 179 assert.NilError(t, err) 180 err = initializeStage(context.TODO(), sb, cmd) 181 assert.Error(t, err, "base name (${THETAG}) should not be blank") 182 } 183 184 func TestFromWithUndefinedArg(t *testing.T) { 185 tag, expected := "sometag", "expectedthisid" 186 187 getImage := func(name string) (builder.Image, builder.ROLayer, error) { 188 assert.Check(t, is.Equal("alpine", name)) 189 return &mockImage{id: "expectedthisid"}, nil, nil 190 } 191 b := newBuilderWithMockBackend(t) 192 b.docker.(*MockBackend).getImageFunc = getImage 193 sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 194 195 b.options.BuildArgs = map[string]*string{"THETAG": &tag} 196 197 cmd := &instructions.Stage{ 198 BaseName: "alpine${THETAG}", 199 } 200 err := initializeStage(context.TODO(), sb, cmd) 201 assert.NilError(t, err) 202 assert.Check(t, is.Equal(expected, sb.state.imageID)) 203 } 204 205 func TestFromMultiStageWithNamedStage(t *testing.T) { 206 b := newBuilderWithMockBackend(t) 207 firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"} 208 secondFrom := &instructions.Stage{BaseName: "base"} 209 previousResults := newStagesBuildResults() 210 firstSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults) 211 secondSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults) 212 err := initializeStage(context.TODO(), firstSB, firstFrom) 213 assert.NilError(t, err) 214 assert.Check(t, firstSB.state.hasFromImage()) 215 previousResults.indexed["base"] = firstSB.state.runConfig 216 previousResults.flat = append(previousResults.flat, firstSB.state.runConfig) 217 err = initializeStage(context.TODO(), secondSB, secondFrom) 218 assert.NilError(t, err) 219 assert.Check(t, secondSB.state.hasFromImage()) 220 } 221 222 func TestOnbuild(t *testing.T) { 223 b := newBuilderWithMockBackend(t) 224 sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 225 cmd := &instructions.OnbuildCommand{ 226 Expression: "ADD . /app/src", 227 } 228 err := dispatch(context.TODO(), sb, cmd) 229 assert.NilError(t, err) 230 assert.Check(t, is.Equal("ADD . /app/src", sb.state.runConfig.OnBuild[0])) 231 } 232 233 func TestWorkdir(t *testing.T) { 234 b := newBuilderWithMockBackend(t) 235 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 236 sb.state.baseImage = &mockImage{} 237 workingDir := "/app" 238 if runtime.GOOS == "windows" { 239 workingDir = "C:\\app" 240 } 241 cmd := &instructions.WorkdirCommand{ 242 Path: workingDir, 243 } 244 245 err := dispatch(context.TODO(), sb, cmd) 246 assert.NilError(t, err) 247 assert.Check(t, is.Equal(workingDir, sb.state.runConfig.WorkingDir)) 248 } 249 250 func TestCmd(t *testing.T) { 251 b := newBuilderWithMockBackend(t) 252 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 253 sb.state.baseImage = &mockImage{} 254 command := "./executable" 255 256 cmd := &instructions.CmdCommand{ 257 ShellDependantCmdLine: instructions.ShellDependantCmdLine{ 258 CmdLine: strslice.StrSlice{command}, 259 PrependShell: true, 260 }, 261 } 262 err := dispatch(context.TODO(), sb, cmd) 263 assert.NilError(t, err) 264 265 var expectedCommand strslice.StrSlice 266 if runtime.GOOS == "windows" { 267 expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command)) 268 } else { 269 expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command)) 270 } 271 272 assert.Check(t, is.DeepEqual(expectedCommand, sb.state.runConfig.Cmd)) 273 assert.Check(t, sb.state.cmdSet) 274 } 275 276 func TestHealthcheckNone(t *testing.T) { 277 b := newBuilderWithMockBackend(t) 278 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 279 cmd := &instructions.HealthCheckCommand{ 280 Health: &container.HealthConfig{ 281 Test: []string{"NONE"}, 282 }, 283 } 284 err := dispatch(context.TODO(), sb, cmd) 285 assert.NilError(t, err) 286 287 assert.Assert(t, sb.state.runConfig.Healthcheck != nil) 288 assert.Check(t, is.DeepEqual([]string{"NONE"}, sb.state.runConfig.Healthcheck.Test)) 289 } 290 291 func TestHealthcheckCmd(t *testing.T) { 292 b := newBuilderWithMockBackend(t) 293 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 294 expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"} 295 cmd := &instructions.HealthCheckCommand{ 296 Health: &container.HealthConfig{ 297 Test: expectedTest, 298 }, 299 } 300 err := dispatch(context.TODO(), sb, cmd) 301 assert.NilError(t, err) 302 303 assert.Assert(t, sb.state.runConfig.Healthcheck != nil) 304 assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test)) 305 } 306 307 func TestEntrypoint(t *testing.T) { 308 b := newBuilderWithMockBackend(t) 309 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 310 sb.state.baseImage = &mockImage{} 311 entrypointCmd := "/usr/sbin/nginx" 312 313 cmd := &instructions.EntrypointCommand{ 314 ShellDependantCmdLine: instructions.ShellDependantCmdLine{ 315 CmdLine: strslice.StrSlice{entrypointCmd}, 316 PrependShell: true, 317 }, 318 } 319 err := dispatch(context.TODO(), sb, cmd) 320 assert.NilError(t, err) 321 assert.Assert(t, sb.state.runConfig.Entrypoint != nil) 322 323 var expectedEntrypoint strslice.StrSlice 324 if runtime.GOOS == "windows" { 325 expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd)) 326 } else { 327 expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd)) 328 } 329 assert.Check(t, is.DeepEqual(expectedEntrypoint, sb.state.runConfig.Entrypoint)) 330 } 331 332 func TestExpose(t *testing.T) { 333 b := newBuilderWithMockBackend(t) 334 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 335 336 exposedPort := "80" 337 cmd := &instructions.ExposeCommand{ 338 Ports: []string{exposedPort}, 339 } 340 err := dispatch(context.TODO(), sb, cmd) 341 assert.NilError(t, err) 342 343 assert.Assert(t, sb.state.runConfig.ExposedPorts != nil) 344 assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1)) 345 346 portsMapping, err := nat.ParsePortSpec(exposedPort) 347 assert.NilError(t, err) 348 assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, portsMapping[0].Port)) 349 } 350 351 func TestUser(t *testing.T) { 352 b := newBuilderWithMockBackend(t) 353 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 354 355 cmd := &instructions.UserCommand{ 356 User: "test", 357 } 358 err := dispatch(context.TODO(), sb, cmd) 359 assert.NilError(t, err) 360 assert.Check(t, is.Equal("test", sb.state.runConfig.User)) 361 } 362 363 func TestVolume(t *testing.T) { 364 b := newBuilderWithMockBackend(t) 365 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 366 367 exposedVolume := "/foo" 368 369 cmd := &instructions.VolumeCommand{ 370 Volumes: []string{exposedVolume}, 371 } 372 err := dispatch(context.TODO(), sb, cmd) 373 assert.NilError(t, err) 374 assert.Assert(t, sb.state.runConfig.Volumes != nil) 375 assert.Check(t, is.Len(sb.state.runConfig.Volumes, 1)) 376 assert.Check(t, is.Contains(sb.state.runConfig.Volumes, exposedVolume)) 377 } 378 379 func TestStopSignal(t *testing.T) { 380 if runtime.GOOS == "windows" { 381 t.Skip("Windows does not support stopsignal") 382 return 383 } 384 b := newBuilderWithMockBackend(t) 385 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 386 sb.state.baseImage = &mockImage{} 387 signal := "SIGKILL" 388 389 cmd := &instructions.StopSignalCommand{ 390 Signal: signal, 391 } 392 err := dispatch(context.TODO(), sb, cmd) 393 assert.NilError(t, err) 394 assert.Check(t, is.Equal(signal, sb.state.runConfig.StopSignal)) 395 } 396 397 func TestArg(t *testing.T) { 398 b := newBuilderWithMockBackend(t) 399 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 400 401 argName := "foo" 402 argVal := "bar" 403 cmd := &instructions.ArgCommand{Args: []instructions.KeyValuePairOptional{{Key: argName, Value: &argVal}}} 404 err := dispatch(context.TODO(), sb, cmd) 405 assert.NilError(t, err) 406 407 expected := map[string]string{argName: argVal} 408 assert.Check(t, is.DeepEqual(expected, sb.state.buildArgs.GetAllAllowed())) 409 } 410 411 func TestShell(t *testing.T) { 412 b := newBuilderWithMockBackend(t) 413 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 414 415 shellCmd := "powershell" 416 cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}} 417 418 err := dispatch(context.TODO(), sb, cmd) 419 assert.NilError(t, err) 420 421 expectedShell := strslice.StrSlice([]string{shellCmd}) 422 assert.Check(t, is.DeepEqual(expectedShell, sb.state.runConfig.Shell)) 423 } 424 425 func TestPrependEnvOnCmd(t *testing.T) { 426 buildArgs := NewBuildArgs(nil) 427 buildArgs.AddArg("NO_PROXY", nil) 428 429 args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"} 430 cmd := []string{"foo", "bar"} 431 cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd) 432 expected := strslice.StrSlice([]string{ 433 "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"}) 434 assert.Check(t, is.DeepEqual(expected, cmdWithEnv)) 435 } 436 437 func TestRunWithBuildArgs(t *testing.T) { 438 b := newBuilderWithMockBackend(t) 439 args := NewBuildArgs(make(map[string]*string)) 440 args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO") 441 b.disableCommit = false 442 sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults()) 443 444 runConfig := &container.Config{} 445 origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"}) 446 447 var cmdWithShell strslice.StrSlice 448 if runtime.GOOS == "windows" { 449 cmdWithShell = strslice.StrSlice([]string{strings.Join(append(getShell(runConfig, runtime.GOOS), []string{"echo foo"}...), " ")}) 450 } else { 451 cmdWithShell = strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo")) 452 } 453 454 envVars := []string{"|1", "one=two"} 455 cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...)) 456 457 imageCache := &mockImageCache{ 458 getCacheFunc: func(parentID string, cfg *container.Config) (string, error) { 459 // Check the runConfig.Cmd sent to probeCache() 460 assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd)) 461 assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint)) 462 return "", nil 463 }, 464 } 465 466 mockBackend := b.docker.(*MockBackend) 467 mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache { 468 return imageCache 469 } 470 471 imageProber, err := newImageProber(context.TODO(), mockBackend, nil, false) 472 assert.NilError(t, err, "Could not create image prober") 473 b.imageProber = imageProber 474 475 mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) { 476 return &mockImage{ 477 id: "abcdef", 478 config: &container.Config{Cmd: origCmd}, 479 }, nil, nil 480 } 481 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.CreateResponse, error) { 482 // Check the runConfig.Cmd sent to create() 483 assert.Check(t, is.DeepEqual(cmdWithShell, config.Config.Cmd)) 484 assert.Check(t, is.Contains(config.Config.Env, "one=two")) 485 assert.Check(t, is.DeepEqual(strslice.StrSlice{""}, config.Config.Entrypoint)) 486 return container.CreateResponse{ID: "12345"}, nil 487 } 488 mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) { 489 // Check the runConfig.Cmd sent to commit() 490 assert.Check(t, is.DeepEqual(origCmd, cfg.Config.Cmd)) 491 assert.Check(t, is.DeepEqual(cachedCmd, cfg.ContainerConfig.Cmd)) 492 assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Config.Entrypoint)) 493 return "", nil 494 } 495 from := &instructions.Stage{BaseName: "abcdef"} 496 err = initializeStage(context.TODO(), sb, from) 497 assert.NilError(t, err) 498 sb.state.buildArgs.AddArg("one", strPtr("two")) 499 500 // This is hugely annoying. On the Windows side, it relies on the 501 // RunCommand being able to emit String() and Name() (as implemented by 502 // withNameAndCode). Unfortunately, that is internal, and no way to directly 503 // set. However, we can fortunately use ParseInstruction in the instructions 504 // package to parse a fake node which can be used as our instructions.RunCommand 505 // instead. 506 node := &parser.Node{ 507 Original: `RUN echo foo`, 508 Value: "run", 509 } 510 runint, err := instructions.ParseInstruction(node) 511 assert.NilError(t, err) 512 runinst := runint.(*instructions.RunCommand) 513 runinst.CmdLine = strslice.StrSlice{"echo foo"} 514 runinst.PrependShell = true 515 516 assert.NilError(t, dispatch(context.TODO(), sb, runinst)) 517 518 // Check that runConfig.Cmd has not been modified by run 519 assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd)) 520 } 521 522 func TestRunIgnoresHealthcheck(t *testing.T) { 523 b := newBuilderWithMockBackend(t) 524 args := NewBuildArgs(make(map[string]*string)) 525 sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults()) 526 b.disableCommit = false 527 528 origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"}) 529 530 imageCache := &mockImageCache{ 531 getCacheFunc: func(parentID string, cfg *container.Config) (string, error) { 532 return "", nil 533 }, 534 } 535 536 mockBackend := b.docker.(*MockBackend) 537 mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache { 538 return imageCache 539 } 540 imageProber, err := newImageProber(context.TODO(), mockBackend, nil, false) 541 assert.NilError(t, err, "Could not create image prober") 542 543 b.imageProber = imageProber 544 mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) { 545 return &mockImage{ 546 id: "abcdef", 547 config: &container.Config{Cmd: origCmd}, 548 }, nil, nil 549 } 550 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.CreateResponse, error) { 551 return container.CreateResponse{ID: "12345"}, nil 552 } 553 mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) { 554 return "", nil 555 } 556 from := &instructions.Stage{BaseName: "abcdef"} 557 err = initializeStage(context.TODO(), sb, from) 558 assert.NilError(t, err) 559 560 expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"} 561 healthint, err := instructions.ParseInstruction(&parser.Node{ 562 Original: `HEALTHCHECK CMD curl -f http://localhost/ || exit 1`, 563 Value: "healthcheck", 564 Next: &parser.Node{ 565 Value: "cmd", 566 Next: &parser.Node{ 567 Value: `curl -f http://localhost/ || exit 1`, 568 }, 569 }, 570 }) 571 assert.NilError(t, err) 572 cmd := healthint.(*instructions.HealthCheckCommand) 573 574 assert.NilError(t, dispatch(context.TODO(), sb, cmd)) 575 assert.Assert(t, sb.state.runConfig.Healthcheck != nil) 576 577 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.CreateResponse, error) { 578 // Check the Healthcheck is disabled. 579 assert.Check(t, is.DeepEqual([]string{"NONE"}, config.Config.Healthcheck.Test)) 580 return container.CreateResponse{ID: "123456"}, nil 581 } 582 583 sb.state.buildArgs.AddArg("one", strPtr("two")) 584 runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"}) 585 assert.NilError(t, err) 586 run := runint.(*instructions.RunCommand) 587 run.PrependShell = true 588 589 assert.NilError(t, dispatch(context.TODO(), sb, run)) 590 assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test)) 591 } 592 593 func TestDispatchUnsupportedOptions(t *testing.T) { 594 b := newBuilderWithMockBackend(t) 595 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 596 sb.state.baseImage = &mockImage{} 597 sb.state.operatingSystem = runtime.GOOS 598 599 t.Run("ADD with chmod", func(t *testing.T) { 600 cmd := &instructions.AddCommand{ 601 SourcesAndDest: instructions.SourcesAndDest{ 602 SourcePaths: []string{"."}, 603 DestPath: ".", 604 }, 605 Chmod: "0655", 606 } 607 err := dispatch(context.TODO(), sb, cmd) 608 assert.Error(t, err, "the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled") 609 }) 610 611 t.Run("COPY with chmod", func(t *testing.T) { 612 cmd := &instructions.CopyCommand{ 613 SourcesAndDest: instructions.SourcesAndDest{ 614 SourcePaths: []string{"."}, 615 DestPath: ".", 616 }, 617 Chmod: "0655", 618 } 619 err := dispatch(context.TODO(), sb, cmd) 620 assert.Error(t, err, "the --chmod option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled") 621 }) 622 623 t.Run("RUN with unsupported options", func(t *testing.T) { 624 runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"}) 625 assert.NilError(t, err) 626 cmd := runint.(*instructions.RunCommand) 627 628 // classic builder "RUN" currently doesn't support any flags, but testing 629 // both "known" flags and "bogus" flags for completeness, and in case 630 // one or more of these flags will be supported in future 631 for _, f := range []string{"mount", "network", "security", "any-flag"} { 632 cmd.FlagsUsed = []string{f} 633 err := dispatch(context.TODO(), sb, cmd) 634 assert.Error(t, err, fmt.Sprintf("the --%s option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled", f)) 635 } 636 }) 637 }