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