github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/builder/dockerfile/dispatchers_test.go (about) 1 package dockerfile // import "github.com/Prakhar-Agarwal-byte/moby/builder/dockerfile" 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "runtime" 8 "strings" 9 "testing" 10 11 "github.com/Prakhar-Agarwal-byte/moby/api/types" 12 "github.com/Prakhar-Agarwal-byte/moby/api/types/backend" 13 "github.com/Prakhar-Agarwal-byte/moby/api/types/container" 14 "github.com/Prakhar-Agarwal-byte/moby/api/types/strslice" 15 "github.com/Prakhar-Agarwal-byte/moby/builder" 16 "github.com/Prakhar-Agarwal-byte/moby/image" 17 "github.com/Prakhar-Agarwal-byte/moby/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 }) 435 assert.Check(t, is.DeepEqual(expected, cmdWithEnv)) 436 } 437 438 func TestRunWithBuildArgs(t *testing.T) { 439 b := newBuilderWithMockBackend(t) 440 args := NewBuildArgs(make(map[string]*string)) 441 args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO") 442 b.disableCommit = false 443 sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults()) 444 445 runConfig := &container.Config{} 446 origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"}) 447 448 var cmdWithShell strslice.StrSlice 449 if runtime.GOOS == "windows" { 450 cmdWithShell = strslice.StrSlice([]string{strings.Join(append(getShell(runConfig, runtime.GOOS), []string{"echo foo"}...), " ")}) 451 } else { 452 cmdWithShell = strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo")) 453 } 454 455 envVars := []string{"|1", "one=two"} 456 cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...)) 457 458 imageCache := &mockImageCache{ 459 getCacheFunc: func(parentID string, cfg *container.Config) (string, error) { 460 // Check the runConfig.Cmd sent to probeCache() 461 assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd)) 462 assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint)) 463 return "", nil 464 }, 465 } 466 467 mockBackend := b.docker.(*MockBackend) 468 mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache { 469 return imageCache 470 } 471 472 imageProber, err := newImageProber(context.TODO(), mockBackend, nil, false) 473 assert.NilError(t, err, "Could not create image prober") 474 b.imageProber = imageProber 475 476 mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) { 477 return &mockImage{ 478 id: "abcdef", 479 config: &container.Config{Cmd: origCmd}, 480 }, nil, nil 481 } 482 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.CreateResponse, error) { 483 // Check the runConfig.Cmd sent to create() 484 assert.Check(t, is.DeepEqual(cmdWithShell, config.Config.Cmd)) 485 assert.Check(t, is.Contains(config.Config.Env, "one=two")) 486 assert.Check(t, is.DeepEqual(strslice.StrSlice{""}, config.Config.Entrypoint)) 487 return container.CreateResponse{ID: "12345"}, nil 488 } 489 mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) { 490 // Check the runConfig.Cmd sent to commit() 491 assert.Check(t, is.DeepEqual(origCmd, cfg.Config.Cmd)) 492 assert.Check(t, is.DeepEqual(cachedCmd, cfg.ContainerConfig.Cmd)) 493 assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Config.Entrypoint)) 494 return "", nil 495 } 496 from := &instructions.Stage{BaseName: "abcdef"} 497 err = initializeStage(context.TODO(), sb, from) 498 assert.NilError(t, err) 499 sb.state.buildArgs.AddArg("one", strPtr("two")) 500 501 // This is hugely annoying. On the Windows side, it relies on the 502 // RunCommand being able to emit String() and Name() (as implemented by 503 // withNameAndCode). Unfortunately, that is internal, and no way to directly 504 // set. However, we can fortunately use ParseInstruction in the instructions 505 // package to parse a fake node which can be used as our instructions.RunCommand 506 // instead. 507 node := &parser.Node{ 508 Original: `RUN echo foo`, 509 Value: "run", 510 } 511 runint, err := instructions.ParseInstruction(node) 512 assert.NilError(t, err) 513 runinst := runint.(*instructions.RunCommand) 514 runinst.CmdLine = strslice.StrSlice{"echo foo"} 515 runinst.PrependShell = true 516 517 assert.NilError(t, dispatch(context.TODO(), sb, runinst)) 518 519 // Check that runConfig.Cmd has not been modified by run 520 assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd)) 521 } 522 523 func TestRunIgnoresHealthcheck(t *testing.T) { 524 b := newBuilderWithMockBackend(t) 525 args := NewBuildArgs(make(map[string]*string)) 526 sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults()) 527 b.disableCommit = false 528 529 origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"}) 530 531 imageCache := &mockImageCache{ 532 getCacheFunc: func(parentID string, cfg *container.Config) (string, error) { 533 return "", nil 534 }, 535 } 536 537 mockBackend := b.docker.(*MockBackend) 538 mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache { 539 return imageCache 540 } 541 imageProber, err := newImageProber(context.TODO(), mockBackend, nil, false) 542 assert.NilError(t, err, "Could not create image prober") 543 544 b.imageProber = imageProber 545 mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) { 546 return &mockImage{ 547 id: "abcdef", 548 config: &container.Config{Cmd: origCmd}, 549 }, nil, nil 550 } 551 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.CreateResponse, error) { 552 return container.CreateResponse{ID: "12345"}, nil 553 } 554 mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) { 555 return "", nil 556 } 557 from := &instructions.Stage{BaseName: "abcdef"} 558 err = initializeStage(context.TODO(), sb, from) 559 assert.NilError(t, err) 560 561 expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"} 562 healthint, err := instructions.ParseInstruction(&parser.Node{ 563 Original: `HEALTHCHECK CMD curl -f http://localhost/ || exit 1`, 564 Value: "healthcheck", 565 Next: &parser.Node{ 566 Value: "cmd", 567 Next: &parser.Node{ 568 Value: `curl -f http://localhost/ || exit 1`, 569 }, 570 }, 571 }) 572 assert.NilError(t, err) 573 cmd := healthint.(*instructions.HealthCheckCommand) 574 575 assert.NilError(t, dispatch(context.TODO(), sb, cmd)) 576 assert.Assert(t, sb.state.runConfig.Healthcheck != nil) 577 578 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.CreateResponse, error) { 579 // Check the Healthcheck is disabled. 580 assert.Check(t, is.DeepEqual([]string{"NONE"}, config.Config.Healthcheck.Test)) 581 return container.CreateResponse{ID: "123456"}, nil 582 } 583 584 sb.state.buildArgs.AddArg("one", strPtr("two")) 585 runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"}) 586 assert.NilError(t, err) 587 run := runint.(*instructions.RunCommand) 588 run.PrependShell = true 589 590 assert.NilError(t, dispatch(context.TODO(), sb, run)) 591 assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test)) 592 } 593 594 func TestDispatchUnsupportedOptions(t *testing.T) { 595 b := newBuilderWithMockBackend(t) 596 sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults()) 597 sb.state.baseImage = &mockImage{} 598 sb.state.operatingSystem = runtime.GOOS 599 600 t.Run("ADD with chmod", func(t *testing.T) { 601 cmd := &instructions.AddCommand{ 602 SourcesAndDest: instructions.SourcesAndDest{ 603 SourcePaths: []string{"."}, 604 DestPath: ".", 605 }, 606 Chmod: "0655", 607 } 608 err := dispatch(context.TODO(), sb, cmd) 609 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") 610 }) 611 612 t.Run("COPY with chmod", func(t *testing.T) { 613 cmd := &instructions.CopyCommand{ 614 SourcesAndDest: instructions.SourcesAndDest{ 615 SourcePaths: []string{"."}, 616 DestPath: ".", 617 }, 618 Chmod: "0655", 619 } 620 err := dispatch(context.TODO(), sb, cmd) 621 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") 622 }) 623 624 t.Run("RUN with unsupported options", func(t *testing.T) { 625 runint, err := instructions.ParseInstruction(&parser.Node{Original: `RUN echo foo`, Value: "run"}) 626 assert.NilError(t, err) 627 cmd := runint.(*instructions.RunCommand) 628 629 // classic builder "RUN" currently doesn't support any flags, but testing 630 // both "known" flags and "bogus" flags for completeness, and in case 631 // one or more of these flags will be supported in future 632 for _, f := range []string{"mount", "network", "security", "any-flag"} { 633 cmd.FlagsUsed = []string{f} 634 err := dispatch(context.TODO(), sb, cmd) 635 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)) 636 } 637 }) 638 }