github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/builder/dockerfile/dispatchers_test.go (about) 1 package dockerfile 2 3 import ( 4 "bytes" 5 "context" 6 "runtime" 7 "testing" 8 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/api/types/backend" 11 "github.com/docker/docker/api/types/container" 12 "github.com/docker/docker/api/types/strslice" 13 "github.com/docker/docker/builder" 14 "github.com/docker/docker/builder/dockerfile/instructions" 15 "github.com/docker/docker/pkg/system" 16 "github.com/docker/go-connections/nat" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func newBuilderWithMockBackend() *Builder { 22 mockBackend := &MockBackend{} 23 ctx := context.Background() 24 b := &Builder{ 25 options: &types.ImageBuildOptions{Platform: runtime.GOOS}, 26 docker: mockBackend, 27 Stdout: new(bytes.Buffer), 28 clientCtx: ctx, 29 disableCommit: true, 30 imageSources: newImageSources(ctx, builderOptions{ 31 Options: &types.ImageBuildOptions{Platform: runtime.GOOS}, 32 Backend: mockBackend, 33 }), 34 imageProber: newImageProber(mockBackend, nil, runtime.GOOS, false), 35 containerManager: newContainerManager(mockBackend), 36 } 37 return b 38 } 39 40 func TestEnv2Variables(t *testing.T) { 41 b := newBuilderWithMockBackend() 42 sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 43 envCommand := &instructions.EnvCommand{ 44 Env: instructions.KeyValuePairs{ 45 instructions.KeyValuePair{Key: "var1", Value: "val1"}, 46 instructions.KeyValuePair{Key: "var2", Value: "val2"}, 47 }, 48 } 49 err := dispatch(sb, envCommand) 50 require.NoError(t, err) 51 52 expected := []string{ 53 "var1=val1", 54 "var2=val2", 55 } 56 assert.Equal(t, expected, sb.state.runConfig.Env) 57 } 58 59 func TestEnvValueWithExistingRunConfigEnv(t *testing.T) { 60 b := newBuilderWithMockBackend() 61 sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 62 sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"} 63 envCommand := &instructions.EnvCommand{ 64 Env: instructions.KeyValuePairs{ 65 instructions.KeyValuePair{Key: "var1", Value: "val1"}, 66 }, 67 } 68 err := dispatch(sb, envCommand) 69 require.NoError(t, err) 70 expected := []string{ 71 "var1=val1", 72 "var2=fromenv", 73 } 74 assert.Equal(t, expected, sb.state.runConfig.Env) 75 } 76 77 func TestMaintainer(t *testing.T) { 78 maintainerEntry := "Some Maintainer <maintainer@example.com>" 79 b := newBuilderWithMockBackend() 80 sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 81 cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry} 82 err := dispatch(sb, cmd) 83 require.NoError(t, err) 84 assert.Equal(t, maintainerEntry, sb.state.maintainer) 85 } 86 87 func TestLabel(t *testing.T) { 88 labelName := "label" 89 labelValue := "value" 90 91 b := newBuilderWithMockBackend() 92 sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 93 cmd := &instructions.LabelCommand{ 94 Labels: instructions.KeyValuePairs{ 95 instructions.KeyValuePair{Key: labelName, Value: labelValue}, 96 }, 97 } 98 err := dispatch(sb, cmd) 99 require.NoError(t, err) 100 101 require.Contains(t, sb.state.runConfig.Labels, labelName) 102 assert.Equal(t, sb.state.runConfig.Labels[labelName], labelValue) 103 } 104 105 func TestFromScratch(t *testing.T) { 106 b := newBuilderWithMockBackend() 107 sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 108 cmd := &instructions.Stage{ 109 BaseName: "scratch", 110 } 111 err := initializeStage(sb, cmd) 112 113 if runtime.GOOS == "windows" && !system.LCOWSupported() { 114 assert.EqualError(t, err, "Windows does not support FROM scratch") 115 return 116 } 117 118 require.NoError(t, err) 119 assert.True(t, sb.state.hasFromImage()) 120 assert.Equal(t, "", sb.state.imageID) 121 expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS) 122 assert.Equal(t, []string{expected}, sb.state.runConfig.Env) 123 } 124 125 func TestFromWithArg(t *testing.T) { 126 tag, expected := ":sometag", "expectedthisid" 127 128 getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) { 129 assert.Equal(t, "alpine"+tag, name) 130 return &mockImage{id: "expectedthisid"}, nil, nil 131 } 132 b := newBuilderWithMockBackend() 133 b.docker.(*MockBackend).getImageFunc = getImage 134 args := newBuildArgs(make(map[string]*string)) 135 136 val := "sometag" 137 metaArg := instructions.ArgCommand{ 138 Key: "THETAG", 139 Value: &val, 140 } 141 cmd := &instructions.Stage{ 142 BaseName: "alpine:${THETAG}", 143 } 144 err := processMetaArg(metaArg, NewShellLex('\\'), args) 145 146 sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults()) 147 require.NoError(t, err) 148 err = initializeStage(sb, cmd) 149 require.NoError(t, err) 150 151 assert.Equal(t, expected, sb.state.imageID) 152 assert.Equal(t, expected, sb.state.baseImage.ImageID()) 153 assert.Len(t, sb.state.buildArgs.GetAllAllowed(), 0) 154 assert.Len(t, sb.state.buildArgs.GetAllMeta(), 1) 155 } 156 157 func TestFromWithUndefinedArg(t *testing.T) { 158 tag, expected := "sometag", "expectedthisid" 159 160 getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) { 161 assert.Equal(t, "alpine", name) 162 return &mockImage{id: "expectedthisid"}, nil, nil 163 } 164 b := newBuilderWithMockBackend() 165 b.docker.(*MockBackend).getImageFunc = getImage 166 sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 167 168 b.options.BuildArgs = map[string]*string{"THETAG": &tag} 169 170 cmd := &instructions.Stage{ 171 BaseName: "alpine${THETAG}", 172 } 173 err := initializeStage(sb, cmd) 174 require.NoError(t, err) 175 assert.Equal(t, expected, sb.state.imageID) 176 } 177 178 func TestFromMultiStageWithNamedStage(t *testing.T) { 179 b := newBuilderWithMockBackend() 180 firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"} 181 secondFrom := &instructions.Stage{BaseName: "base"} 182 previousResults := newStagesBuildResults() 183 firstSB := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), previousResults) 184 secondSB := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), previousResults) 185 err := initializeStage(firstSB, firstFrom) 186 require.NoError(t, err) 187 assert.True(t, firstSB.state.hasFromImage()) 188 previousResults.indexed["base"] = firstSB.state.runConfig 189 previousResults.flat = append(previousResults.flat, firstSB.state.runConfig) 190 err = initializeStage(secondSB, secondFrom) 191 require.NoError(t, err) 192 assert.True(t, secondSB.state.hasFromImage()) 193 } 194 195 func TestOnbuild(t *testing.T) { 196 b := newBuilderWithMockBackend() 197 sb := newDispatchRequest(b, '\\', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 198 cmd := &instructions.OnbuildCommand{ 199 Expression: "ADD . /app/src", 200 } 201 err := dispatch(sb, cmd) 202 require.NoError(t, err) 203 assert.Equal(t, "ADD . /app/src", sb.state.runConfig.OnBuild[0]) 204 } 205 206 func TestWorkdir(t *testing.T) { 207 b := newBuilderWithMockBackend() 208 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 209 workingDir := "/app" 210 if runtime.GOOS == "windows" { 211 workingDir = "C:\\app" 212 } 213 cmd := &instructions.WorkdirCommand{ 214 Path: workingDir, 215 } 216 217 err := dispatch(sb, cmd) 218 require.NoError(t, err) 219 assert.Equal(t, workingDir, sb.state.runConfig.WorkingDir) 220 } 221 222 func TestCmd(t *testing.T) { 223 b := newBuilderWithMockBackend() 224 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 225 command := "./executable" 226 227 cmd := &instructions.CmdCommand{ 228 ShellDependantCmdLine: instructions.ShellDependantCmdLine{ 229 CmdLine: strslice.StrSlice{command}, 230 PrependShell: true, 231 }, 232 } 233 err := dispatch(sb, cmd) 234 require.NoError(t, err) 235 236 var expectedCommand strslice.StrSlice 237 if runtime.GOOS == "windows" { 238 expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command)) 239 } else { 240 expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command)) 241 } 242 243 assert.Equal(t, expectedCommand, sb.state.runConfig.Cmd) 244 assert.True(t, sb.state.cmdSet) 245 } 246 247 func TestHealthcheckNone(t *testing.T) { 248 b := newBuilderWithMockBackend() 249 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 250 cmd := &instructions.HealthCheckCommand{ 251 Health: &container.HealthConfig{ 252 Test: []string{"NONE"}, 253 }, 254 } 255 err := dispatch(sb, cmd) 256 require.NoError(t, err) 257 258 require.NotNil(t, sb.state.runConfig.Healthcheck) 259 assert.Equal(t, []string{"NONE"}, sb.state.runConfig.Healthcheck.Test) 260 } 261 262 func TestHealthcheckCmd(t *testing.T) { 263 264 b := newBuilderWithMockBackend() 265 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 266 expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"} 267 cmd := &instructions.HealthCheckCommand{ 268 Health: &container.HealthConfig{ 269 Test: expectedTest, 270 }, 271 } 272 err := dispatch(sb, cmd) 273 require.NoError(t, err) 274 275 require.NotNil(t, sb.state.runConfig.Healthcheck) 276 assert.Equal(t, expectedTest, sb.state.runConfig.Healthcheck.Test) 277 } 278 279 func TestEntrypoint(t *testing.T) { 280 b := newBuilderWithMockBackend() 281 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 282 entrypointCmd := "/usr/sbin/nginx" 283 284 cmd := &instructions.EntrypointCommand{ 285 ShellDependantCmdLine: instructions.ShellDependantCmdLine{ 286 CmdLine: strslice.StrSlice{entrypointCmd}, 287 PrependShell: true, 288 }, 289 } 290 err := dispatch(sb, cmd) 291 require.NoError(t, err) 292 require.NotNil(t, sb.state.runConfig.Entrypoint) 293 294 var expectedEntrypoint strslice.StrSlice 295 if runtime.GOOS == "windows" { 296 expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd)) 297 } else { 298 expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd)) 299 } 300 assert.Equal(t, expectedEntrypoint, sb.state.runConfig.Entrypoint) 301 } 302 303 func TestExpose(t *testing.T) { 304 b := newBuilderWithMockBackend() 305 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 306 307 exposedPort := "80" 308 cmd := &instructions.ExposeCommand{ 309 Ports: []string{exposedPort}, 310 } 311 err := dispatch(sb, cmd) 312 require.NoError(t, err) 313 314 require.NotNil(t, sb.state.runConfig.ExposedPorts) 315 require.Len(t, sb.state.runConfig.ExposedPorts, 1) 316 317 portsMapping, err := nat.ParsePortSpec(exposedPort) 318 require.NoError(t, err) 319 assert.Contains(t, sb.state.runConfig.ExposedPorts, portsMapping[0].Port) 320 } 321 322 func TestUser(t *testing.T) { 323 b := newBuilderWithMockBackend() 324 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 325 326 cmd := &instructions.UserCommand{ 327 User: "test", 328 } 329 err := dispatch(sb, cmd) 330 require.NoError(t, err) 331 assert.Equal(t, "test", sb.state.runConfig.User) 332 } 333 334 func TestVolume(t *testing.T) { 335 b := newBuilderWithMockBackend() 336 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 337 338 exposedVolume := "/foo" 339 340 cmd := &instructions.VolumeCommand{ 341 Volumes: []string{exposedVolume}, 342 } 343 err := dispatch(sb, cmd) 344 require.NoError(t, err) 345 require.NotNil(t, sb.state.runConfig.Volumes) 346 assert.Len(t, sb.state.runConfig.Volumes, 1) 347 assert.Contains(t, sb.state.runConfig.Volumes, exposedVolume) 348 } 349 350 func TestStopSignal(t *testing.T) { 351 if runtime.GOOS == "windows" { 352 t.Skip("Windows does not support stopsignal") 353 return 354 } 355 b := newBuilderWithMockBackend() 356 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 357 signal := "SIGKILL" 358 359 cmd := &instructions.StopSignalCommand{ 360 Signal: signal, 361 } 362 err := dispatch(sb, cmd) 363 require.NoError(t, err) 364 assert.Equal(t, signal, sb.state.runConfig.StopSignal) 365 } 366 367 func TestArg(t *testing.T) { 368 b := newBuilderWithMockBackend() 369 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 370 371 argName := "foo" 372 argVal := "bar" 373 cmd := &instructions.ArgCommand{Key: argName, Value: &argVal} 374 err := dispatch(sb, cmd) 375 require.NoError(t, err) 376 377 expected := map[string]string{argName: argVal} 378 assert.Equal(t, expected, sb.state.buildArgs.GetAllAllowed()) 379 } 380 381 func TestShell(t *testing.T) { 382 b := newBuilderWithMockBackend() 383 sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) 384 385 shellCmd := "powershell" 386 cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}} 387 388 err := dispatch(sb, cmd) 389 require.NoError(t, err) 390 391 expectedShell := strslice.StrSlice([]string{shellCmd}) 392 assert.Equal(t, expectedShell, sb.state.runConfig.Shell) 393 } 394 395 func TestPrependEnvOnCmd(t *testing.T) { 396 buildArgs := newBuildArgs(nil) 397 buildArgs.AddArg("NO_PROXY", nil) 398 399 args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"} 400 cmd := []string{"foo", "bar"} 401 cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd) 402 expected := strslice.StrSlice([]string{ 403 "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"}) 404 assert.Equal(t, expected, cmdWithEnv) 405 } 406 407 func TestRunWithBuildArgs(t *testing.T) { 408 b := newBuilderWithMockBackend() 409 args := newBuildArgs(make(map[string]*string)) 410 args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO") 411 b.disableCommit = false 412 sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults()) 413 414 runConfig := &container.Config{} 415 origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"}) 416 cmdWithShell := strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo")) 417 envVars := []string{"|1", "one=two"} 418 cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...)) 419 420 imageCache := &mockImageCache{ 421 getCacheFunc: func(parentID string, cfg *container.Config) (string, error) { 422 // Check the runConfig.Cmd sent to probeCache() 423 assert.Equal(t, cachedCmd, cfg.Cmd) 424 assert.Equal(t, strslice.StrSlice(nil), cfg.Entrypoint) 425 return "", nil 426 }, 427 } 428 429 mockBackend := b.docker.(*MockBackend) 430 mockBackend.makeImageCacheFunc = func(_ []string, _ string) builder.ImageCache { 431 return imageCache 432 } 433 b.imageProber = newImageProber(mockBackend, nil, runtime.GOOS, false) 434 mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) { 435 return &mockImage{ 436 id: "abcdef", 437 config: &container.Config{Cmd: origCmd}, 438 }, nil, nil 439 } 440 mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) { 441 // Check the runConfig.Cmd sent to create() 442 assert.Equal(t, cmdWithShell, config.Config.Cmd) 443 assert.Contains(t, config.Config.Env, "one=two") 444 assert.Equal(t, strslice.StrSlice{""}, config.Config.Entrypoint) 445 return container.ContainerCreateCreatedBody{ID: "12345"}, nil 446 } 447 mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) { 448 // Check the runConfig.Cmd sent to commit() 449 assert.Equal(t, origCmd, cfg.Config.Cmd) 450 assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd) 451 assert.Equal(t, strslice.StrSlice(nil), cfg.Config.Entrypoint) 452 return "", nil 453 } 454 from := &instructions.Stage{BaseName: "abcdef"} 455 err := initializeStage(sb, from) 456 require.NoError(t, err) 457 sb.state.buildArgs.AddArg("one", strPtr("two")) 458 run := &instructions.RunCommand{ 459 ShellDependantCmdLine: instructions.ShellDependantCmdLine{ 460 CmdLine: strslice.StrSlice{"echo foo"}, 461 PrependShell: true, 462 }, 463 } 464 require.NoError(t, dispatch(sb, run)) 465 466 // Check that runConfig.Cmd has not been modified by run 467 assert.Equal(t, origCmd, sb.state.runConfig.Cmd) 468 }