github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/builder/dockerfile/dispatchers_test.go (about) 1 package dockerfile 2 3 import ( 4 "fmt" 5 "runtime" 6 "testing" 7 8 "bytes" 9 "context" 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/builder/dockerfile/parser" 17 "github.com/docker/docker/pkg/system" 18 "github.com/docker/docker/pkg/testutil" 19 "github.com/docker/go-connections/nat" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 ) 23 24 type commandWithFunction struct { 25 name string 26 function func(args []string) error 27 } 28 29 func withArgs(f dispatcher) func([]string) error { 30 return func(args []string) error { 31 return f(dispatchRequest{args: args}) 32 } 33 } 34 35 func withBuilderAndArgs(builder *Builder, f dispatcher) func([]string) error { 36 return func(args []string) error { 37 return f(defaultDispatchReq(builder, args...)) 38 } 39 } 40 41 func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest { 42 return dispatchRequest{ 43 builder: builder, 44 args: args, 45 flags: NewBFlags(), 46 shlex: NewShellLex(parser.DefaultEscapeToken), 47 state: &dispatchState{runConfig: &container.Config{}}, 48 } 49 } 50 51 func newBuilderWithMockBackend() *Builder { 52 mockBackend := &MockBackend{} 53 ctx := context.Background() 54 b := &Builder{ 55 options: &types.ImageBuildOptions{}, 56 docker: mockBackend, 57 buildArgs: newBuildArgs(make(map[string]*string)), 58 Stdout: new(bytes.Buffer), 59 clientCtx: ctx, 60 disableCommit: true, 61 imageSources: newImageSources(ctx, builderOptions{ 62 Options: &types.ImageBuildOptions{}, 63 Backend: mockBackend, 64 }), 65 buildStages: newBuildStages(), 66 imageProber: newImageProber(mockBackend, nil, false), 67 containerManager: newContainerManager(mockBackend), 68 } 69 return b 70 } 71 72 func TestCommandsExactlyOneArgument(t *testing.T) { 73 commands := []commandWithFunction{ 74 {"MAINTAINER", withArgs(maintainer)}, 75 {"WORKDIR", withArgs(workdir)}, 76 {"USER", withArgs(user)}, 77 {"STOPSIGNAL", withArgs(stopSignal)}, 78 } 79 80 for _, command := range commands { 81 err := command.function([]string{}) 82 assert.EqualError(t, err, errExactlyOneArgument(command.name).Error()) 83 } 84 } 85 86 func TestCommandsAtLeastOneArgument(t *testing.T) { 87 commands := []commandWithFunction{ 88 {"ENV", withArgs(env)}, 89 {"LABEL", withArgs(label)}, 90 {"ONBUILD", withArgs(onbuild)}, 91 {"HEALTHCHECK", withArgs(healthcheck)}, 92 {"EXPOSE", withArgs(expose)}, 93 {"VOLUME", withArgs(volume)}, 94 } 95 96 for _, command := range commands { 97 err := command.function([]string{}) 98 assert.EqualError(t, err, errAtLeastOneArgument(command.name).Error()) 99 } 100 } 101 102 func TestCommandsAtLeastTwoArguments(t *testing.T) { 103 commands := []commandWithFunction{ 104 {"ADD", withArgs(add)}, 105 {"COPY", withArgs(dispatchCopy)}} 106 107 for _, command := range commands { 108 err := command.function([]string{"arg1"}) 109 assert.EqualError(t, err, errAtLeastTwoArguments(command.name).Error()) 110 } 111 } 112 113 func TestCommandsTooManyArguments(t *testing.T) { 114 commands := []commandWithFunction{ 115 {"ENV", withArgs(env)}, 116 {"LABEL", withArgs(label)}} 117 118 for _, command := range commands { 119 err := command.function([]string{"arg1", "arg2", "arg3"}) 120 assert.EqualError(t, err, errTooManyArguments(command.name).Error()) 121 } 122 } 123 124 func TestCommandsBlankNames(t *testing.T) { 125 builder := newBuilderWithMockBackend() 126 commands := []commandWithFunction{ 127 {"ENV", withBuilderAndArgs(builder, env)}, 128 {"LABEL", withBuilderAndArgs(builder, label)}, 129 } 130 131 for _, command := range commands { 132 err := command.function([]string{"", ""}) 133 assert.EqualError(t, err, errBlankCommandNames(command.name).Error()) 134 } 135 } 136 137 func TestEnv2Variables(t *testing.T) { 138 b := newBuilderWithMockBackend() 139 140 args := []string{"var1", "val1", "var2", "val2"} 141 req := defaultDispatchReq(b, args...) 142 err := env(req) 143 require.NoError(t, err) 144 145 expected := []string{ 146 fmt.Sprintf("%s=%s", args[0], args[1]), 147 fmt.Sprintf("%s=%s", args[2], args[3]), 148 } 149 assert.Equal(t, expected, req.state.runConfig.Env) 150 } 151 152 func TestEnvValueWithExistingRunConfigEnv(t *testing.T) { 153 b := newBuilderWithMockBackend() 154 155 args := []string{"var1", "val1"} 156 req := defaultDispatchReq(b, args...) 157 req.state.runConfig.Env = []string{"var1=old", "var2=fromenv"} 158 err := env(req) 159 require.NoError(t, err) 160 161 expected := []string{ 162 fmt.Sprintf("%s=%s", args[0], args[1]), 163 "var2=fromenv", 164 } 165 assert.Equal(t, expected, req.state.runConfig.Env) 166 } 167 168 func TestMaintainer(t *testing.T) { 169 maintainerEntry := "Some Maintainer <maintainer@example.com>" 170 171 b := newBuilderWithMockBackend() 172 req := defaultDispatchReq(b, maintainerEntry) 173 err := maintainer(req) 174 require.NoError(t, err) 175 assert.Equal(t, maintainerEntry, req.state.maintainer) 176 } 177 178 func TestLabel(t *testing.T) { 179 labelName := "label" 180 labelValue := "value" 181 182 labelEntry := []string{labelName, labelValue} 183 b := newBuilderWithMockBackend() 184 req := defaultDispatchReq(b, labelEntry...) 185 err := label(req) 186 require.NoError(t, err) 187 188 require.Contains(t, req.state.runConfig.Labels, labelName) 189 assert.Equal(t, req.state.runConfig.Labels[labelName], labelValue) 190 } 191 192 func TestFromScratch(t *testing.T) { 193 b := newBuilderWithMockBackend() 194 req := defaultDispatchReq(b, "scratch") 195 err := from(req) 196 197 if runtime.GOOS == "windows" { 198 assert.EqualError(t, err, "Windows does not support FROM scratch") 199 return 200 } 201 202 require.NoError(t, err) 203 assert.True(t, req.state.hasFromImage()) 204 assert.Equal(t, "", req.state.imageID) 205 assert.Equal(t, []string{"PATH=" + system.DefaultPathEnv}, req.state.runConfig.Env) 206 } 207 208 func TestFromWithArg(t *testing.T) { 209 tag, expected := ":sometag", "expectedthisid" 210 211 getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) { 212 assert.Equal(t, "alpine"+tag, name) 213 return &mockImage{id: "expectedthisid"}, nil, nil 214 } 215 b := newBuilderWithMockBackend() 216 b.docker.(*MockBackend).getImageFunc = getImage 217 218 require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag))) 219 req := defaultDispatchReq(b, "alpine${THETAG}") 220 err := from(req) 221 222 require.NoError(t, err) 223 assert.Equal(t, expected, req.state.imageID) 224 assert.Equal(t, expected, req.state.baseImage.ImageID()) 225 assert.Len(t, b.buildArgs.GetAllAllowed(), 0) 226 assert.Len(t, b.buildArgs.GetAllMeta(), 1) 227 } 228 229 func TestFromWithUndefinedArg(t *testing.T) { 230 tag, expected := "sometag", "expectedthisid" 231 232 getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) { 233 assert.Equal(t, "alpine", name) 234 return &mockImage{id: "expectedthisid"}, nil, nil 235 } 236 b := newBuilderWithMockBackend() 237 b.docker.(*MockBackend).getImageFunc = getImage 238 b.options.BuildArgs = map[string]*string{"THETAG": &tag} 239 240 req := defaultDispatchReq(b, "alpine${THETAG}") 241 err := from(req) 242 require.NoError(t, err) 243 assert.Equal(t, expected, req.state.imageID) 244 } 245 246 func TestFromMultiStageWithScratchNamedStage(t *testing.T) { 247 if runtime.GOOS == "windows" { 248 t.Skip("Windows does not support scratch") 249 } 250 b := newBuilderWithMockBackend() 251 req := defaultDispatchReq(b, "scratch", "AS", "base") 252 253 require.NoError(t, from(req)) 254 assert.True(t, req.state.hasFromImage()) 255 256 req.args = []string{"base"} 257 require.NoError(t, from(req)) 258 assert.True(t, req.state.hasFromImage()) 259 } 260 261 func TestOnbuildIllegalTriggers(t *testing.T) { 262 triggers := []struct{ command, expectedError string }{ 263 {"ONBUILD", "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed"}, 264 {"MAINTAINER", "MAINTAINER isn't allowed as an ONBUILD trigger"}, 265 {"FROM", "FROM isn't allowed as an ONBUILD trigger"}} 266 267 for _, trigger := range triggers { 268 b := newBuilderWithMockBackend() 269 270 err := onbuild(defaultDispatchReq(b, trigger.command)) 271 testutil.ErrorContains(t, err, trigger.expectedError) 272 } 273 } 274 275 func TestOnbuild(t *testing.T) { 276 b := newBuilderWithMockBackend() 277 278 req := defaultDispatchReq(b, "ADD", ".", "/app/src") 279 req.original = "ONBUILD ADD . /app/src" 280 req.state.runConfig = &container.Config{} 281 282 err := onbuild(req) 283 require.NoError(t, err) 284 assert.Equal(t, "ADD . /app/src", req.state.runConfig.OnBuild[0]) 285 } 286 287 func TestWorkdir(t *testing.T) { 288 b := newBuilderWithMockBackend() 289 workingDir := "/app" 290 if runtime.GOOS == "windows" { 291 workingDir = "C:\app" 292 } 293 294 req := defaultDispatchReq(b, workingDir) 295 err := workdir(req) 296 require.NoError(t, err) 297 assert.Equal(t, workingDir, req.state.runConfig.WorkingDir) 298 } 299 300 func TestCmd(t *testing.T) { 301 b := newBuilderWithMockBackend() 302 command := "./executable" 303 304 req := defaultDispatchReq(b, command) 305 err := cmd(req) 306 require.NoError(t, err) 307 308 var expectedCommand strslice.StrSlice 309 if runtime.GOOS == "windows" { 310 expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command)) 311 } else { 312 expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command)) 313 } 314 315 assert.Equal(t, expectedCommand, req.state.runConfig.Cmd) 316 assert.True(t, req.state.cmdSet) 317 } 318 319 func TestHealthcheckNone(t *testing.T) { 320 b := newBuilderWithMockBackend() 321 322 req := defaultDispatchReq(b, "NONE") 323 err := healthcheck(req) 324 require.NoError(t, err) 325 326 require.NotNil(t, req.state.runConfig.Healthcheck) 327 assert.Equal(t, []string{"NONE"}, req.state.runConfig.Healthcheck.Test) 328 } 329 330 func TestHealthcheckCmd(t *testing.T) { 331 b := newBuilderWithMockBackend() 332 333 args := []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"} 334 req := defaultDispatchReq(b, args...) 335 err := healthcheck(req) 336 require.NoError(t, err) 337 338 require.NotNil(t, req.state.runConfig.Healthcheck) 339 expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"} 340 assert.Equal(t, expectedTest, req.state.runConfig.Healthcheck.Test) 341 } 342 343 func TestEntrypoint(t *testing.T) { 344 b := newBuilderWithMockBackend() 345 entrypointCmd := "/usr/sbin/nginx" 346 347 req := defaultDispatchReq(b, entrypointCmd) 348 err := entrypoint(req) 349 require.NoError(t, err) 350 require.NotNil(t, req.state.runConfig.Entrypoint) 351 352 var expectedEntrypoint strslice.StrSlice 353 if runtime.GOOS == "windows" { 354 expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd)) 355 } else { 356 expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd)) 357 } 358 assert.Equal(t, expectedEntrypoint, req.state.runConfig.Entrypoint) 359 } 360 361 func TestExpose(t *testing.T) { 362 b := newBuilderWithMockBackend() 363 364 exposedPort := "80" 365 req := defaultDispatchReq(b, exposedPort) 366 err := expose(req) 367 require.NoError(t, err) 368 369 require.NotNil(t, req.state.runConfig.ExposedPorts) 370 require.Len(t, req.state.runConfig.ExposedPorts, 1) 371 372 portsMapping, err := nat.ParsePortSpec(exposedPort) 373 require.NoError(t, err) 374 assert.Contains(t, req.state.runConfig.ExposedPorts, portsMapping[0].Port) 375 } 376 377 func TestUser(t *testing.T) { 378 b := newBuilderWithMockBackend() 379 userCommand := "foo" 380 381 req := defaultDispatchReq(b, userCommand) 382 err := user(req) 383 require.NoError(t, err) 384 assert.Equal(t, userCommand, req.state.runConfig.User) 385 } 386 387 func TestVolume(t *testing.T) { 388 b := newBuilderWithMockBackend() 389 390 exposedVolume := "/foo" 391 392 req := defaultDispatchReq(b, exposedVolume) 393 err := volume(req) 394 require.NoError(t, err) 395 396 require.NotNil(t, req.state.runConfig.Volumes) 397 assert.Len(t, req.state.runConfig.Volumes, 1) 398 assert.Contains(t, req.state.runConfig.Volumes, exposedVolume) 399 } 400 401 func TestStopSignal(t *testing.T) { 402 b := newBuilderWithMockBackend() 403 signal := "SIGKILL" 404 405 req := defaultDispatchReq(b, signal) 406 err := stopSignal(req) 407 require.NoError(t, err) 408 assert.Equal(t, signal, req.state.runConfig.StopSignal) 409 } 410 411 func TestArg(t *testing.T) { 412 b := newBuilderWithMockBackend() 413 414 argName := "foo" 415 argVal := "bar" 416 argDef := fmt.Sprintf("%s=%s", argName, argVal) 417 418 err := arg(defaultDispatchReq(b, argDef)) 419 require.NoError(t, err) 420 421 expected := map[string]string{argName: argVal} 422 assert.Equal(t, expected, b.buildArgs.GetAllAllowed()) 423 } 424 425 func TestShell(t *testing.T) { 426 b := newBuilderWithMockBackend() 427 428 shellCmd := "powershell" 429 req := defaultDispatchReq(b, shellCmd) 430 req.attributes = map[string]bool{"json": true} 431 432 err := shell(req) 433 require.NoError(t, err) 434 435 expectedShell := strslice.StrSlice([]string{shellCmd}) 436 assert.Equal(t, expectedShell, req.state.runConfig.Shell) 437 } 438 439 func TestParseOptInterval(t *testing.T) { 440 flInterval := &Flag{ 441 name: "interval", 442 flagType: stringType, 443 Value: "50ns", 444 } 445 _, err := parseOptInterval(flInterval) 446 testutil.ErrorContains(t, err, "cannot be less than 1ms") 447 448 flInterval.Value = "1ms" 449 _, err = parseOptInterval(flInterval) 450 require.NoError(t, err) 451 } 452 453 func TestPrependEnvOnCmd(t *testing.T) { 454 buildArgs := newBuildArgs(nil) 455 buildArgs.AddArg("NO_PROXY", nil) 456 457 args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"} 458 cmd := []string{"foo", "bar"} 459 cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd) 460 expected := strslice.StrSlice([]string{ 461 "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"}) 462 assert.Equal(t, expected, cmdWithEnv) 463 } 464 465 func TestRunWithBuildArgs(t *testing.T) { 466 b := newBuilderWithMockBackend() 467 b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO") 468 b.disableCommit = false 469 470 runConfig := &container.Config{} 471 origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"}) 472 cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "echo foo")) 473 envVars := []string{"|1", "one=two"} 474 cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...)) 475 476 imageCache := &mockImageCache{ 477 getCacheFunc: func(parentID string, cfg *container.Config) (string, error) { 478 // Check the runConfig.Cmd sent to probeCache() 479 assert.Equal(t, cachedCmd, cfg.Cmd) 480 assert.Equal(t, strslice.StrSlice(nil), cfg.Entrypoint) 481 return "", nil 482 }, 483 } 484 485 mockBackend := b.docker.(*MockBackend) 486 mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache { 487 return imageCache 488 } 489 b.imageProber = newImageProber(mockBackend, nil, false) 490 mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) { 491 return &mockImage{ 492 id: "abcdef", 493 config: &container.Config{Cmd: origCmd}, 494 }, nil, nil 495 } 496 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) { 497 // Check the runConfig.Cmd sent to create() 498 assert.Equal(t, cmdWithShell, config.Config.Cmd) 499 assert.Contains(t, config.Config.Env, "one=two") 500 assert.Equal(t, strslice.StrSlice{""}, config.Config.Entrypoint) 501 return container.ContainerCreateCreatedBody{ID: "12345"}, nil 502 } 503 mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) { 504 // Check the runConfig.Cmd sent to commit() 505 assert.Equal(t, origCmd, cfg.Config.Cmd) 506 assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd) 507 assert.Equal(t, strslice.StrSlice(nil), cfg.Config.Entrypoint) 508 return "", nil 509 } 510 511 req := defaultDispatchReq(b, "abcdef") 512 require.NoError(t, from(req)) 513 b.buildArgs.AddArg("one", strPtr("two")) 514 515 req.args = []string{"echo foo"} 516 require.NoError(t, run(req)) 517 518 // Check that runConfig.Cmd has not been modified by run 519 assert.Equal(t, origCmd, req.state.runConfig.Cmd) 520 }