Instead of mutating and reverting, just create a copy and pass the copy
around.
Add a unit test for builder dispatcher.run
Fix two test failures
Fix image history by adding a CreatedBy to commit options. Previously the
createdBy field was being created by modifying a reference to the runConfig that
was held from when the container was created.
Fix a test that expected a trailing slash. Previously the runConfig was being
modified by container create. Now that we're creating a copy of runConfig
instead of sharing a reference the runConfig retains the trailing slash.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"time" |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/api/types" |
| 9 |
+ "github.com/docker/docker/api/types/container" |
|
| 9 | 10 |
) |
| 10 | 11 |
|
| 11 | 12 |
// ContainerAttachConfig holds the streams to use when connecting to a container to view logs. |
| ... | ... |
@@ -97,4 +98,7 @@ type ExecProcessConfig struct {
|
| 97 | 97 |
type ContainerCommitConfig struct {
|
| 98 | 98 |
types.ContainerCommitConfig |
| 99 | 99 |
Changes []string |
| 100 |
+ // TODO: ContainerConfig is only used by the dockerfile Builder, so remove it |
|
| 101 |
+ // once the Builder has been updated to use a different interface |
|
| 102 |
+ ContainerConfig *container.Config |
|
| 100 | 103 |
} |
| ... | ... |
@@ -243,6 +243,10 @@ func (b *Builder) hasFromImage() bool {
|
| 243 | 243 |
// |
| 244 | 244 |
// TODO: Remove? |
| 245 | 245 |
func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
|
| 246 |
+ if len(changes) == 0 {
|
|
| 247 |
+ return config, nil |
|
| 248 |
+ } |
|
| 249 |
+ |
|
| 246 | 250 |
b := newBuilder(context.Background(), builderOptions{})
|
| 247 | 251 |
|
| 248 | 252 |
result, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) |
| ... | ... |
@@ -315,20 +315,18 @@ func workdir(req dispatchRequest) error {
|
| 315 | 315 |
return nil |
| 316 | 316 |
} |
| 317 | 317 |
|
| 318 |
- cmd := req.runConfig.Cmd |
|
| 319 |
- comment := "WORKDIR " + req.runConfig.WorkingDir |
|
| 320 |
- // reset the command for cache detection |
|
| 321 |
- req.runConfig.Cmd = strslice.StrSlice(append(getShell(req.runConfig), "#(nop) "+comment)) |
|
| 322 |
- defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
|
|
| 318 |
+ // TODO: why is this done here. This seems to be done at random places all over |
|
| 319 |
+ // the builder |
|
| 320 |
+ req.runConfig.Image = req.builder.image |
|
| 323 | 321 |
|
| 324 |
- // TODO: this should pass a copy of runConfig |
|
| 325 |
- if hit, err := req.builder.probeCache(req.builder.image, req.runConfig); err != nil || hit {
|
|
| 322 |
+ comment := "WORKDIR " + req.runConfig.WorkingDir |
|
| 323 |
+ runConfigWithCommentCmd := copyRunConfig(req.runConfig, withCmdCommentString(comment)) |
|
| 324 |
+ if hit, err := req.builder.probeCache(req.builder.image, runConfigWithCommentCmd); err != nil || hit {
|
|
| 326 | 325 |
return err |
| 327 | 326 |
} |
| 328 | 327 |
|
| 329 |
- req.runConfig.Image = req.builder.image |
|
| 330 | 328 |
container, err := req.builder.docker.ContainerCreate(types.ContainerCreateConfig{
|
| 331 |
- Config: req.runConfig, |
|
| 329 |
+ Config: runConfigWithCommentCmd, |
|
| 332 | 330 |
// Set a log config to override any default value set on the daemon |
| 333 | 331 |
HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
|
| 334 | 332 |
}) |
| ... | ... |
@@ -340,7 +338,7 @@ func workdir(req dispatchRequest) error {
|
| 340 | 340 |
return err |
| 341 | 341 |
} |
| 342 | 342 |
|
| 343 |
- return req.builder.commitContainer(container.ID, copyRunConfig(req.runConfig, withCmd(cmd))) |
|
| 343 |
+ return req.builder.commitContainer(container.ID, runConfigWithCommentCmd) |
|
| 344 | 344 |
} |
| 345 | 345 |
|
| 346 | 346 |
// RUN some command yo |
| ... | ... |
@@ -363,79 +361,57 @@ func run(req dispatchRequest) error {
|
| 363 | 363 |
} |
| 364 | 364 |
|
| 365 | 365 |
args := handleJSONArgs(req.args, req.attributes) |
| 366 |
- |
|
| 367 | 366 |
if !req.attributes["json"] {
|
| 368 | 367 |
args = append(getShell(req.runConfig), args...) |
| 369 | 368 |
} |
| 370 |
- config := &container.Config{
|
|
| 371 |
- Cmd: strslice.StrSlice(args), |
|
| 372 |
- Image: req.builder.image, |
|
| 373 |
- } |
|
| 369 |
+ cmdFromArgs := strslice.StrSlice(args) |
|
| 370 |
+ buildArgs := req.builder.buildArgsWithoutConfigEnv() |
|
| 374 | 371 |
|
| 375 |
- // stash the cmd |
|
| 376 |
- cmd := req.runConfig.Cmd |
|
| 377 |
- if len(req.runConfig.Entrypoint) == 0 && len(req.runConfig.Cmd) == 0 {
|
|
| 378 |
- req.runConfig.Cmd = config.Cmd |
|
| 372 |
+ saveCmd := cmdFromArgs |
|
| 373 |
+ if len(buildArgs) > 0 {
|
|
| 374 |
+ saveCmd = prependEnvOnCmd(req.builder.buildArgs, buildArgs, cmdFromArgs) |
|
| 379 | 375 |
} |
| 380 | 376 |
|
| 381 |
- // stash the config environment |
|
| 382 |
- env := req.runConfig.Env |
|
| 383 |
- |
|
| 384 |
- defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
|
|
| 385 |
- defer func(env []string) { req.runConfig.Env = env }(env)
|
|
| 386 |
- |
|
| 387 |
- cmdBuildEnv := req.builder.buildArgsWithoutConfigEnv() |
|
| 388 |
- |
|
| 389 |
- // derive the command to use for probeCache() and to commit in this container. |
|
| 390 |
- // Note that we only do this if there are any build-time env vars. Also, we |
|
| 391 |
- // use the special argument "|#" at the start of the args array. This will |
|
| 392 |
- // avoid conflicts with any RUN command since commands can not |
|
| 393 |
- // start with | (vertical bar). The "#" (number of build envs) is there to |
|
| 394 |
- // help ensure proper cache matches. We don't want a RUN command |
|
| 395 |
- // that starts with "foo=abc" to be considered part of a build-time env var. |
|
| 396 |
- saveCmd := config.Cmd |
|
| 397 |
- if len(cmdBuildEnv) > 0 {
|
|
| 398 |
- saveCmd = prependEnvOnCmd(req.builder.buildArgs, cmdBuildEnv, saveCmd) |
|
| 399 |
- } |
|
| 377 |
+ // TODO: this was previously in b.create(), why is it necessary? |
|
| 378 |
+ req.runConfig.Image = req.builder.image |
|
| 400 | 379 |
|
| 401 |
- req.runConfig.Cmd = saveCmd |
|
| 402 |
- hit, err := req.builder.probeCache(req.builder.image, req.runConfig) |
|
| 380 |
+ runConfigForCacheProbe := copyRunConfig(req.runConfig, withCmd(saveCmd)) |
|
| 381 |
+ hit, err := req.builder.probeCache(req.builder.image, runConfigForCacheProbe) |
|
| 403 | 382 |
if err != nil || hit {
|
| 404 | 383 |
return err |
| 405 | 384 |
} |
| 406 | 385 |
|
| 407 |
- // set Cmd manually, this is special case only for Dockerfiles |
|
| 408 |
- req.runConfig.Cmd = config.Cmd |
|
| 409 |
- // set build-time environment for 'run'. |
|
| 410 |
- req.runConfig.Env = append(req.runConfig.Env, cmdBuildEnv...) |
|
| 411 |
- // set config as already being escaped, this prevents double escaping on windows |
|
| 412 |
- req.runConfig.ArgsEscaped = true |
|
| 386 |
+ runConfig := copyRunConfig(req.runConfig, |
|
| 387 |
+ withCmd(cmdFromArgs), |
|
| 388 |
+ withEnv(append(req.runConfig.Env, buildArgs...))) |
|
| 413 | 389 |
|
| 414 |
- logrus.Debugf("[BUILDER] Command to be executed: %v", req.runConfig.Cmd)
|
|
| 390 |
+ // set config as already being escaped, this prevents double escaping on windows |
|
| 391 |
+ runConfig.ArgsEscaped = true |
|
| 415 | 392 |
|
| 416 |
- // TODO: this was previously in b.create(), why is it necessary? |
|
| 417 |
- req.builder.runConfig.Image = req.builder.image |
|
| 393 |
+ logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd)
|
|
| 418 | 394 |
|
| 419 |
- // TODO: should pass a copy of runConfig |
|
| 420 |
- cID, err := req.builder.create(req.runConfig) |
|
| 395 |
+ cID, err := req.builder.create(runConfig) |
|
| 421 | 396 |
if err != nil {
|
| 422 | 397 |
return err |
| 423 | 398 |
} |
| 424 |
- |
|
| 425 |
- if err := req.builder.run(cID); err != nil {
|
|
| 399 |
+ if err := req.builder.run(cID, runConfig.Cmd); err != nil {
|
|
| 426 | 400 |
return err |
| 427 | 401 |
} |
| 428 | 402 |
|
| 429 |
- // FIXME: this is duplicated with the defer above in this function (i think?) |
|
| 430 |
- // revert to original config environment and set the command string to |
|
| 431 |
- // have the build-time env vars in it (if any) so that future cache look-ups |
|
| 432 |
- // properly match it. |
|
| 433 |
- req.runConfig.Env = env |
|
| 434 |
- |
|
| 435 |
- req.runConfig.Cmd = saveCmd |
|
| 436 |
- return req.builder.commitContainer(cID, copyRunConfig(req.runConfig, withCmd(cmd))) |
|
| 403 |
+ return req.builder.commitContainer(cID, runConfigForCacheProbe) |
|
| 437 | 404 |
} |
| 438 | 405 |
|
| 406 |
+// Derive the command to use for probeCache() and to commit in this container. |
|
| 407 |
+// Note that we only do this if there are any build-time env vars. Also, we |
|
| 408 |
+// use the special argument "|#" at the start of the args array. This will |
|
| 409 |
+// avoid conflicts with any RUN command since commands can not |
|
| 410 |
+// start with | (vertical bar). The "#" (number of build envs) is there to |
|
| 411 |
+// help ensure proper cache matches. We don't want a RUN command |
|
| 412 |
+// that starts with "foo=abc" to be considered part of a build-time env var. |
|
| 413 |
+// |
|
| 414 |
+// remove any unreferenced built-in args from the environment variables. |
|
| 415 |
+// These args are transparent so resulting image should be the same regardless |
|
| 416 |
+// of the value. |
|
| 439 | 417 |
func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice {
|
| 440 | 418 |
var tmpBuildEnv []string |
| 441 | 419 |
for _, env := range buildArgVars {
|
| ... | ... |
@@ -5,7 +5,10 @@ import ( |
| 5 | 5 |
"runtime" |
| 6 | 6 |
"testing" |
| 7 | 7 |
|
| 8 |
+ "bytes" |
|
| 9 |
+ "context" |
|
| 8 | 10 |
"github.com/docker/docker/api/types" |
| 11 |
+ "github.com/docker/docker/api/types/backend" |
|
| 9 | 12 |
"github.com/docker/docker/api/types/container" |
| 10 | 13 |
"github.com/docker/docker/api/types/strslice" |
| 11 | 14 |
"github.com/docker/docker/builder" |
| ... | ... |
@@ -49,6 +52,9 @@ func newBuilderWithMockBackend() *Builder {
|
| 49 | 49 |
options: &types.ImageBuildOptions{},
|
| 50 | 50 |
docker: &MockBackend{},
|
| 51 | 51 |
buildArgs: newBuildArgs(make(map[string]*string)), |
| 52 |
+ tmpContainers: make(map[string]struct{}),
|
|
| 53 |
+ Stdout: new(bytes.Buffer), |
|
| 54 |
+ clientCtx: context.Background(), |
|
| 52 | 55 |
disableCommit: true, |
| 53 | 56 |
} |
| 54 | 57 |
b.imageContexts = &imageContexts{b: b}
|
| ... | ... |
@@ -409,13 +415,71 @@ func TestParseOptInterval(t *testing.T) {
|
| 409 | 409 |
Value: "50ns", |
| 410 | 410 |
} |
| 411 | 411 |
_, err := parseOptInterval(flInterval) |
| 412 |
- if err == nil {
|
|
| 413 |
- t.Fatalf("Error should be presented for interval %s", flInterval.Value)
|
|
| 414 |
- } |
|
| 412 |
+ testutil.ErrorContains(t, err, "cannot be less than 1ms") |
|
| 415 | 413 |
|
| 416 | 414 |
flInterval.Value = "1ms" |
| 417 | 415 |
_, err = parseOptInterval(flInterval) |
| 418 |
- if err != nil {
|
|
| 419 |
- t.Fatalf("Unexpected error: %s", err.Error())
|
|
| 416 |
+ require.NoError(t, err) |
|
| 417 |
+} |
|
| 418 |
+ |
|
| 419 |
+func TestPrependEnvOnCmd(t *testing.T) {
|
|
| 420 |
+ buildArgs := newBuildArgs(nil) |
|
| 421 |
+ buildArgs.AddArg("NO_PROXY", nil)
|
|
| 422 |
+ |
|
| 423 |
+ args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
|
|
| 424 |
+ cmd := []string{"foo", "bar"}
|
|
| 425 |
+ cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd) |
|
| 426 |
+ expected := strslice.StrSlice([]string{
|
|
| 427 |
+ "|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"}) |
|
| 428 |
+ assert.Equal(t, expected, cmdWithEnv) |
|
| 429 |
+} |
|
| 430 |
+ |
|
| 431 |
+func TestRunWithBuildArgs(t *testing.T) {
|
|
| 432 |
+ b := newBuilderWithMockBackend() |
|
| 433 |
+ b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
|
|
| 434 |
+ b.disableCommit = false |
|
| 435 |
+ |
|
| 436 |
+ origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
|
|
| 437 |
+ cmdWithShell := strslice.StrSlice(append(getShell(b.runConfig), "echo foo")) |
|
| 438 |
+ envVars := []string{"|1", "one=two"}
|
|
| 439 |
+ cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...)) |
|
| 440 |
+ |
|
| 441 |
+ imageCache := &mockImageCache{
|
|
| 442 |
+ getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
|
|
| 443 |
+ // Check the runConfig.Cmd sent to probeCache() |
|
| 444 |
+ assert.Equal(t, cachedCmd, cfg.Cmd) |
|
| 445 |
+ return "", nil |
|
| 446 |
+ }, |
|
| 420 | 447 |
} |
| 448 |
+ b.imageCache = imageCache |
|
| 449 |
+ |
|
| 450 |
+ mockBackend := b.docker.(*MockBackend) |
|
| 451 |
+ mockBackend.getImageOnBuildImage = &mockImage{
|
|
| 452 |
+ id: "abcdef", |
|
| 453 |
+ config: &container.Config{Cmd: origCmd},
|
|
| 454 |
+ } |
|
| 455 |
+ mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
|
|
| 456 |
+ // Check the runConfig.Cmd sent to create() |
|
| 457 |
+ assert.Equal(t, cmdWithShell, config.Config.Cmd) |
|
| 458 |
+ assert.Contains(t, config.Config.Env, "one=two") |
|
| 459 |
+ return container.ContainerCreateCreatedBody{ID: "12345"}, nil
|
|
| 460 |
+ } |
|
| 461 |
+ mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
|
|
| 462 |
+ // Check the runConfig.Cmd sent to commit() |
|
| 463 |
+ assert.Equal(t, origCmd, cfg.Config.Cmd) |
|
| 464 |
+ assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd) |
|
| 465 |
+ return "", nil |
|
| 466 |
+ } |
|
| 467 |
+ |
|
| 468 |
+ req := defaultDispatchReq(b, "abcdef") |
|
| 469 |
+ require.NoError(t, from(req)) |
|
| 470 |
+ b.buildArgs.AddArg("one", strPtr("two"))
|
|
| 471 |
+ // TODO: this can be removed with b.runConfig |
|
| 472 |
+ req.runConfig.Cmd = origCmd |
|
| 473 |
+ |
|
| 474 |
+ req.args = []string{"echo foo"}
|
|
| 475 |
+ require.NoError(t, run(req)) |
|
| 476 |
+ |
|
| 477 |
+ // Check that runConfig.Cmd has not been modified by run |
|
| 478 |
+ assert.Equal(t, origCmd, b.runConfig.Cmd) |
|
| 421 | 479 |
} |
| ... | ... |
@@ -21,7 +21,6 @@ import ( |
| 21 | 21 |
"github.com/docker/docker/api/types" |
| 22 | 22 |
"github.com/docker/docker/api/types/backend" |
| 23 | 23 |
"github.com/docker/docker/api/types/container" |
| 24 |
- "github.com/docker/docker/api/types/strslice" |
|
| 25 | 24 |
"github.com/docker/docker/builder" |
| 26 | 25 |
"github.com/docker/docker/builder/dockerfile/parser" |
| 27 | 26 |
"github.com/docker/docker/builder/remotecontext" |
| ... | ... |
@@ -56,10 +55,10 @@ func (b *Builder) commit(comment string) error {
|
| 56 | 56 |
return err |
| 57 | 57 |
} |
| 58 | 58 |
|
| 59 |
- return b.commitContainer(id, b.runConfig) |
|
| 59 |
+ return b.commitContainer(id, runConfigWithCommentCmd) |
|
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
-func (b *Builder) commitContainer(id string, runConfig *container.Config) error {
|
|
| 62 |
+func (b *Builder) commitContainer(id string, containerConfig *container.Config) error {
|
|
| 63 | 63 |
if b.disableCommit {
|
| 64 | 64 |
return nil |
| 65 | 65 |
} |
| ... | ... |
@@ -68,8 +67,9 @@ func (b *Builder) commitContainer(id string, runConfig *container.Config) error |
| 68 | 68 |
ContainerCommitConfig: types.ContainerCommitConfig{
|
| 69 | 69 |
Author: b.maintainer, |
| 70 | 70 |
Pause: true, |
| 71 |
- Config: runConfig, |
|
| 71 |
+ Config: b.runConfig, |
|
| 72 | 72 |
}, |
| 73 |
+ ContainerConfig: containerConfig, |
|
| 73 | 74 |
} |
| 74 | 75 |
|
| 75 | 76 |
// Commit the container |
| ... | ... |
@@ -81,7 +81,7 @@ func (b *Builder) commitContainer(id string, runConfig *container.Config) error |
| 81 | 81 |
// TODO: this function should return imageID and runConfig instead of setting |
| 82 | 82 |
// then on the builder |
| 83 | 83 |
b.image = imageID |
| 84 |
- b.imageContexts.update(imageID, runConfig) |
|
| 84 |
+ b.imageContexts.update(imageID, b.runConfig) |
|
| 85 | 85 |
return nil |
| 86 | 86 |
} |
| 87 | 87 |
|
| ... | ... |
@@ -100,6 +100,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD |
| 100 | 100 |
// Work in daemon-specific filepath semantics |
| 101 | 101 |
dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest |
| 102 | 102 |
|
| 103 |
+ // TODO: why is this done here. This seems to be done at random places all over |
|
| 104 |
+ // the builder |
|
| 103 | 105 |
b.runConfig.Image = b.image |
| 104 | 106 |
|
| 105 | 107 |
var infos []copyInfo |
| ... | ... |
@@ -164,18 +166,16 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD |
| 164 | 164 |
srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil)) |
| 165 | 165 |
} |
| 166 | 166 |
|
| 167 |
- cmd := b.runConfig.Cmd |
|
| 168 | 167 |
// TODO: should this have been using origPaths instead of srcHash in the comment? |
| 169 |
- b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest)))
|
|
| 170 |
- defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
|
| 171 |
- |
|
| 172 |
- // TODO: this should pass a copy of runConfig |
|
| 173 |
- if hit, err := b.probeCache(b.image, b.runConfig); err != nil || hit {
|
|
| 168 |
+ runConfigWithCommentCmd := copyRunConfig( |
|
| 169 |
+ b.runConfig, |
|
| 170 |
+ withCmdCommentString(fmt.Sprintf("%s %s in %s ", cmdName, srcHash, dest)))
|
|
| 171 |
+ if hit, err := b.probeCache(b.image, runConfigWithCommentCmd); err != nil || hit {
|
|
| 174 | 172 |
return err |
| 175 | 173 |
} |
| 176 | 174 |
|
| 177 | 175 |
container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
|
| 178 |
- Config: b.runConfig, |
|
| 176 |
+ Config: runConfigWithCommentCmd, |
|
| 179 | 177 |
// Set a log config to override any default value set on the daemon |
| 180 | 178 |
HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
|
| 181 | 179 |
}) |
| ... | ... |
@@ -196,7 +196,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD |
| 196 | 196 |
} |
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 |
- return b.commitContainer(container.ID, copyRunConfig(b.runConfig, withCmd(cmd))) |
|
| 199 |
+ return b.commitContainer(container.ID, runConfigWithCommentCmd) |
|
| 200 | 200 |
} |
| 201 | 201 |
|
| 202 | 202 |
type runConfigModifier func(*container.Config) |
| ... | ... |
@@ -215,12 +215,24 @@ func withCmd(cmd []string) runConfigModifier {
|
| 215 | 215 |
} |
| 216 | 216 |
} |
| 217 | 217 |
|
| 218 |
+// withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for |
|
| 219 |
+// why there are two almost identical versions of this. |
|
| 218 | 220 |
func withCmdComment(comment string) runConfigModifier {
|
| 219 | 221 |
return func(runConfig *container.Config) {
|
| 220 | 222 |
runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment) |
| 221 | 223 |
} |
| 222 | 224 |
} |
| 223 | 225 |
|
| 226 |
+// withCmdCommentString exists to maintain compatibility with older versions. |
|
| 227 |
+// A few instructions (workdir, copy, add) used a nop comment that is a single arg |
|
| 228 |
+// where as all the other instructions used a two arg comment string. This |
|
| 229 |
+// function implements the single arg version. |
|
| 230 |
+func withCmdCommentString(comment string) runConfigModifier {
|
|
| 231 |
+ return func(runConfig *container.Config) {
|
|
| 232 |
+ runConfig.Cmd = append(getShell(runConfig), "#(nop) "+comment) |
|
| 233 |
+ } |
|
| 234 |
+} |
|
| 235 |
+ |
|
| 224 | 236 |
func withEnv(env []string) runConfigModifier {
|
| 225 | 237 |
return func(runConfig *container.Config) {
|
| 226 | 238 |
runConfig.Env = env |
| ... | ... |
@@ -606,7 +618,7 @@ func (b *Builder) create(runConfig *container.Config) (string, error) {
|
| 606 | 606 |
|
| 607 | 607 |
var errCancelled = errors.New("build cancelled")
|
| 608 | 608 |
|
| 609 |
-func (b *Builder) run(cID string) (err error) {
|
|
| 609 |
+func (b *Builder) run(cID string, cmd []string) (err error) {
|
|
| 610 | 610 |
attached := make(chan struct{})
|
| 611 | 611 |
errCh := make(chan error) |
| 612 | 612 |
go func() {
|
| ... | ... |
@@ -660,7 +672,7 @@ func (b *Builder) run(cID string) (err error) {
|
| 660 | 660 |
} |
| 661 | 661 |
// TODO: change error type, because jsonmessage.JSONError assumes HTTP |
| 662 | 662 |
return &jsonmessage.JSONError{
|
| 663 |
- Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(b.runConfig.Cmd, " "), ret),
|
|
| 663 |
+ Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(cmd, " "), ret),
|
|
| 664 | 664 |
Code: ret, |
| 665 | 665 |
} |
| 666 | 666 |
} |
| ... | ... |
@@ -15,13 +15,19 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
// MockBackend implements the builder.Backend interface for unit testing |
| 17 | 17 |
type MockBackend struct {
|
| 18 |
- getImageOnBuildFunc func(string) (builder.Image, error) |
|
| 18 |
+ getImageOnBuildFunc func(string) (builder.Image, error) |
|
| 19 |
+ getImageOnBuildImage *mockImage |
|
| 20 |
+ containerCreateFunc func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) |
|
| 21 |
+ commitFunc func(string, *backend.ContainerCommitConfig) (string, error) |
|
| 19 | 22 |
} |
| 20 | 23 |
|
| 21 | 24 |
func (m *MockBackend) GetImageOnBuild(name string) (builder.Image, error) {
|
| 22 | 25 |
if m.getImageOnBuildFunc != nil {
|
| 23 | 26 |
return m.getImageOnBuildFunc(name) |
| 24 | 27 |
} |
| 28 |
+ if m.getImageOnBuildImage != nil {
|
|
| 29 |
+ return m.getImageOnBuildImage, nil |
|
| 30 |
+ } |
|
| 25 | 31 |
return &mockImage{id: "theid"}, nil
|
| 26 | 32 |
} |
| 27 | 33 |
|
| ... | ... |
@@ -38,6 +44,9 @@ func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout |
| 38 | 38 |
} |
| 39 | 39 |
|
| 40 | 40 |
func (m *MockBackend) ContainerCreate(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
|
| 41 |
+ if m.containerCreateFunc != nil {
|
|
| 42 |
+ return m.containerCreateFunc(config) |
|
| 43 |
+ } |
|
| 41 | 44 |
return container.ContainerCreateCreatedBody{}, nil
|
| 42 | 45 |
} |
| 43 | 46 |
|
| ... | ... |
@@ -45,7 +54,10 @@ func (m *MockBackend) ContainerRm(name string, config *types.ContainerRmConfig) |
| 45 | 45 |
return nil |
| 46 | 46 |
} |
| 47 | 47 |
|
| 48 |
-func (m *MockBackend) Commit(string, *backend.ContainerCommitConfig) (string, error) {
|
|
| 48 |
+func (m *MockBackend) Commit(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
|
|
| 49 |
+ if m.commitFunc != nil {
|
|
| 50 |
+ return m.commitFunc(cID, cfg) |
|
| 51 |
+ } |
|
| 49 | 52 |
return "", nil |
| 50 | 53 |
} |
| 51 | 54 |
|
| ... | ... |
@@ -97,3 +109,14 @@ func (i *mockImage) ImageID() string {
|
| 97 | 97 |
func (i *mockImage) RunConfig() *container.Config {
|
| 98 | 98 |
return i.config |
| 99 | 99 |
} |
| 100 |
+ |
|
| 101 |
+type mockImageCache struct {
|
|
| 102 |
+ getCacheFunc func(parentID string, cfg *container.Config) (string, error) |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 105 |
+func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config) (string, error) {
|
|
| 106 |
+ if mic.getCacheFunc != nil {
|
|
| 107 |
+ return mic.getCacheFunc(parentID, cfg) |
|
| 108 |
+ } |
|
| 109 |
+ return "", nil |
|
| 110 |
+} |
| ... | ... |
@@ -129,6 +129,11 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str |
| 129 | 129 |
return "", err |
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 |
+ containerConfig := c.ContainerConfig |
|
| 133 |
+ if containerConfig == nil {
|
|
| 134 |
+ containerConfig = container.Config |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 132 | 137 |
// It is not possible to commit a running container on Windows and on Solaris. |
| 133 | 138 |
if (runtime.GOOS == "windows" || runtime.GOOS == "solaris") && container.IsRunning() {
|
| 134 | 139 |
return "", errors.Errorf("%+v does not support commit of a running container", runtime.GOOS)
|
| ... | ... |
@@ -185,7 +190,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str |
| 185 | 185 |
h := image.History{
|
| 186 | 186 |
Author: c.Author, |
| 187 | 187 |
Created: time.Now().UTC(), |
| 188 |
- CreatedBy: strings.Join(container.Config.Cmd, " "), |
|
| 188 |
+ CreatedBy: strings.Join(containerConfig.Cmd, " "), |
|
| 189 | 189 |
Comment: c.Comment, |
| 190 | 190 |
EmptyLayer: true, |
| 191 | 191 |
} |
| ... | ... |
@@ -204,7 +209,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str |
| 204 | 204 |
Architecture: runtime.GOARCH, |
| 205 | 205 |
OS: runtime.GOOS, |
| 206 | 206 |
Container: container.ID, |
| 207 |
- ContainerConfig: *container.Config, |
|
| 207 |
+ ContainerConfig: *containerConfig, |
|
| 208 | 208 |
Author: c.Author, |
| 209 | 209 |
Created: h.Created, |
| 210 | 210 |
}, |
| ... | ... |
@@ -1,6 +1,8 @@ |
| 1 | 1 |
package cache |
| 2 | 2 |
|
| 3 |
-import "github.com/docker/docker/api/types/container" |
|
| 3 |
+import ( |
|
| 4 |
+ "github.com/docker/docker/api/types/container" |
|
| 5 |
+) |
|
| 4 | 6 |
|
| 5 | 7 |
// compare two Config struct. Do not compare the "Image" nor "Hostname" fields |
| 6 | 8 |
// If OpenStdin is set, then it differs |
| ... | ... |
@@ -345,11 +345,8 @@ ONBUILD RUN ["true"]`)) |
| 345 | 345 |
|
| 346 | 346 |
buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s`, name1))) |
| 347 | 347 |
|
| 348 |
- out, _ := dockerCmd(c, "run", name2) |
|
| 349 |
- if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) {
|
|
| 350 |
- c.Fatalf("did not get echo output from onbuild. Got: %q", out)
|
|
| 351 |
- } |
|
| 352 |
- |
|
| 348 |
+ result := cli.DockerCmd(c, "run", name2) |
|
| 349 |
+ result.Assert(c, icmd.Expected{Out: "hello world"})
|
|
| 353 | 350 |
} |
| 354 | 351 |
|
| 355 | 352 |
// FIXME(vdemeester) why we disabled cache here ? |
| ... | ... |
@@ -4376,12 +4373,8 @@ func (s *DockerSuite) TestBuildTimeArgHistoryExclusions(c *check.C) {
|
| 4376 | 4376 |
if strings.Contains(out, "https_proxy") {
|
| 4377 | 4377 |
c.Fatalf("failed to exclude proxy settings from history!")
|
| 4378 | 4378 |
} |
| 4379 |
- if !strings.Contains(out, fmt.Sprintf("%s=%s", envKey, envVal)) {
|
|
| 4380 |
- c.Fatalf("explicitly defined ARG %s is not in output", explicitProxyKey)
|
|
| 4381 |
- } |
|
| 4382 |
- if !strings.Contains(out, fmt.Sprintf("%s=%s", envKey, envVal)) {
|
|
| 4383 |
- c.Fatalf("missing build arguments from output")
|
|
| 4384 |
- } |
|
| 4379 |
+ result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", envKey, envVal)})
|
|
| 4380 |
+ result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", explicitProxyKey, explicitProxyVal)})
|
|
| 4385 | 4381 |
|
| 4386 | 4382 |
cacheID := buildImage(imgName + "-two") |
| 4387 | 4383 |
c.Assert(origID, checker.Equals, cacheID) |
| ... | ... |
@@ -4576,9 +4569,7 @@ func (s *DockerSuite) TestBuildBuildTimeArgExpansion(c *check.C) {
|
| 4576 | 4576 |
) |
| 4577 | 4577 |
|
| 4578 | 4578 |
res := inspectField(c, imgName, "Config.WorkingDir") |
| 4579 |
- if res != filepath.ToSlash(filepath.Clean(wdVal)) {
|
|
| 4580 |
- c.Fatalf("Config.WorkingDir value mismatch. Expected: %s, got: %s", filepath.ToSlash(filepath.Clean(wdVal)), res)
|
|
| 4581 |
- } |
|
| 4579 |
+ c.Check(res, check.Equals, filepath.ToSlash(wdVal)) |
|
| 4582 | 4580 |
|
| 4583 | 4581 |
var resArr []string |
| 4584 | 4582 |
inspectFieldAndUnmarshall(c, imgName, "Config.Env", &resArr) |