Change builder.dispatchers to use a dispatchRequest object
This change starts the process of splitting up the Builder into logical components. Remove builder.flags and move it to the new dispatchRequest object. Use runConfig from dispatchRequest instead of from the builder. More progress removing things from the Builder struct. Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
3beb8d9c8b
commit
e59327aaac
6 changed files with 404 additions and 514 deletions
|
@ -37,6 +37,13 @@ func NewBFlags() *BFlags {
|
|||
}
|
||||
}
|
||||
|
||||
// NewBFlagsWithArgs returns the new BFlags struct with Args set to args
|
||||
func NewBFlagsWithArgs(args []string) *BFlags {
|
||||
flags := NewBFlags()
|
||||
flags.Args = args
|
||||
return flags
|
||||
}
|
||||
|
||||
// AddBool adds a bool flag to BFlags
|
||||
// Note, any error will be generated when Parse() is called (see Parse).
|
||||
func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
||||
|
|
|
@ -52,20 +52,20 @@ type Builder struct {
|
|||
clientCtx context.Context
|
||||
|
||||
runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
|
||||
flags *BFlags
|
||||
tmpContainers map[string]struct{}
|
||||
image string // imageID
|
||||
imageContexts *imageContexts // helper for storing contexts from builds
|
||||
noBaseImage bool // A flag to track the use of `scratch` as the base image
|
||||
maintainer string
|
||||
cmdSet bool
|
||||
disableCommit bool
|
||||
cacheBusted bool
|
||||
buildArgs *buildArgs
|
||||
escapeToken rune
|
||||
imageCache builder.ImageCache
|
||||
|
||||
imageCache builder.ImageCache
|
||||
from builder.Image
|
||||
// TODO: these move to DispatchState
|
||||
escapeToken rune
|
||||
maintainer string
|
||||
cmdSet bool
|
||||
noBaseImage bool // A flag to track the use of `scratch` as the base image
|
||||
image string // imageID
|
||||
from builder.Image
|
||||
}
|
||||
|
||||
// BuildManager implements builder.Backend and is shared across all Builder objects.
|
||||
|
@ -304,6 +304,7 @@ func (b *Builder) tagImages(repoAndTags []reference.Named) error {
|
|||
}
|
||||
|
||||
// hasFromImage returns true if the builder has processed a `FROM <image>` line
|
||||
// TODO: move to DispatchState
|
||||
func (b *Builder) hasFromImage() bool {
|
||||
return b.image != "" || b.noBaseImage
|
||||
}
|
||||
|
|
|
@ -33,103 +33,104 @@ import (
|
|||
// Sets the environment variable foo to bar, also makes interpolation
|
||||
// in the dockerfile available from the next statement on via ${foo}.
|
||||
//
|
||||
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
func env(req dispatchRequest) error {
|
||||
if len(req.args) == 0 {
|
||||
return errAtLeastOneArgument("ENV")
|
||||
}
|
||||
|
||||
if len(args)%2 != 0 {
|
||||
if len(req.args)%2 != 0 {
|
||||
// should never get here, but just in case
|
||||
return errTooManyArguments("ENV")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commitMessage := bytes.NewBufferString("ENV")
|
||||
|
||||
for j := 0; j < len(args); j += 2 {
|
||||
if len(args[j]) == 0 {
|
||||
for j := 0; j < len(req.args); j += 2 {
|
||||
if len(req.args[j]) == 0 {
|
||||
return errBlankCommandNames("ENV")
|
||||
}
|
||||
name := args[j]
|
||||
value := args[j+1]
|
||||
name := req.args[j]
|
||||
value := req.args[j+1]
|
||||
newVar := name + "=" + value
|
||||
commitMessage.WriteString(" " + newVar)
|
||||
|
||||
gotOne := false
|
||||
for i, envVar := range b.runConfig.Env {
|
||||
for i, envVar := range req.runConfig.Env {
|
||||
envParts := strings.SplitN(envVar, "=", 2)
|
||||
compareFrom := envParts[0]
|
||||
if equalEnvKeys(compareFrom, name) {
|
||||
b.runConfig.Env[i] = newVar
|
||||
req.runConfig.Env[i] = newVar
|
||||
gotOne = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !gotOne {
|
||||
b.runConfig.Env = append(b.runConfig.Env, newVar)
|
||||
req.runConfig.Env = append(req.runConfig.Env, newVar)
|
||||
}
|
||||
}
|
||||
|
||||
return b.commit("", b.runConfig.Cmd, commitMessage.String())
|
||||
return req.builder.commit("", req.runConfig.Cmd, commitMessage.String())
|
||||
}
|
||||
|
||||
// MAINTAINER some text <maybe@an.email.address>
|
||||
//
|
||||
// Sets the maintainer metadata.
|
||||
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
func maintainer(req dispatchRequest) error {
|
||||
if len(req.args) != 1 {
|
||||
return errExactlyOneArgument("MAINTAINER")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.maintainer = args[0]
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
|
||||
maintainer := req.args[0]
|
||||
req.builder.maintainer = maintainer
|
||||
return req.builder.commit("", req.runConfig.Cmd, "MAINTAINER "+maintainer)
|
||||
}
|
||||
|
||||
// LABEL some json data describing the image
|
||||
//
|
||||
// Sets the Label variable foo to bar,
|
||||
//
|
||||
func label(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
func label(req dispatchRequest) error {
|
||||
if len(req.args) == 0 {
|
||||
return errAtLeastOneArgument("LABEL")
|
||||
}
|
||||
if len(args)%2 != 0 {
|
||||
if len(req.args)%2 != 0 {
|
||||
// should never get here, but just in case
|
||||
return errTooManyArguments("LABEL")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commitStr := "LABEL"
|
||||
|
||||
if b.runConfig.Labels == nil {
|
||||
b.runConfig.Labels = map[string]string{}
|
||||
if req.runConfig.Labels == nil {
|
||||
req.runConfig.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
for j := 0; j < len(args); j++ {
|
||||
// name ==> args[j]
|
||||
// value ==> args[j+1]
|
||||
for j := 0; j < len(req.args); j++ {
|
||||
// name ==> req.args[j]
|
||||
// value ==> req.args[j+1]
|
||||
|
||||
if len(args[j]) == 0 {
|
||||
if len(req.args[j]) == 0 {
|
||||
return errBlankCommandNames("LABEL")
|
||||
}
|
||||
|
||||
newVar := args[j] + "=" + args[j+1] + ""
|
||||
newVar := req.args[j] + "=" + req.args[j+1] + ""
|
||||
commitStr += " " + newVar
|
||||
|
||||
b.runConfig.Labels[args[j]] = args[j+1]
|
||||
req.runConfig.Labels[req.args[j]] = req.args[j+1]
|
||||
j++
|
||||
}
|
||||
return b.commit("", b.runConfig.Cmd, commitStr)
|
||||
return req.builder.commit("", req.runConfig.Cmd, commitStr)
|
||||
}
|
||||
|
||||
// ADD foo /path
|
||||
|
@ -137,73 +138,73 @@ func label(b *Builder, args []string, attributes map[string]bool, original strin
|
|||
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
||||
// exist here. If you do not wish to have this automatic handling, use COPY.
|
||||
//
|
||||
func add(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) < 2 {
|
||||
func add(req dispatchRequest) error {
|
||||
if len(req.args) < 2 {
|
||||
return errAtLeastTwoArguments("ADD")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.runContextCommand(args, true, true, "ADD", nil)
|
||||
return req.builder.runContextCommand(req.args, true, true, "ADD", nil)
|
||||
}
|
||||
|
||||
// COPY foo /path
|
||||
//
|
||||
// Same as 'ADD' but without the tar and remote url handling.
|
||||
//
|
||||
func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) < 2 {
|
||||
func dispatchCopy(req dispatchRequest) error {
|
||||
if len(req.args) < 2 {
|
||||
return errAtLeastTwoArguments("COPY")
|
||||
}
|
||||
|
||||
flFrom := b.flags.AddString("from", "")
|
||||
flFrom := req.flags.AddString("from", "")
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var im *imageMount
|
||||
if flFrom.IsUsed() {
|
||||
var err error
|
||||
im, err = b.imageContexts.get(flFrom.Value)
|
||||
im, err = req.builder.imageContexts.get(flFrom.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return b.runContextCommand(args, false, false, "COPY", im)
|
||||
return req.builder.runContextCommand(req.args, false, false, "COPY", im)
|
||||
}
|
||||
|
||||
// FROM imagename[:tag | @digest] [AS build-stage-name]
|
||||
//
|
||||
// from sets the base image
|
||||
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
ctxName, err := parseBuildStageName(args)
|
||||
func from(req dispatchRequest) error {
|
||||
ctxName, err := parseBuildStageName(req.args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
b.resetImageCache()
|
||||
if _, err := b.imageContexts.add(ctxName); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
image, err := b.getFromImage(args[0])
|
||||
req.builder.resetImageCache()
|
||||
if _, err := req.builder.imageContexts.add(ctxName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
image, err := req.builder.getFromImage(req.args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if image != nil {
|
||||
b.imageContexts.update(image.ImageID(), image.RunConfig())
|
||||
req.builder.imageContexts.update(image.ImageID(), image.RunConfig())
|
||||
}
|
||||
b.from = image
|
||||
req.builder.from = image
|
||||
|
||||
b.buildArgs.ResetAllowed()
|
||||
return b.processImageFrom(image)
|
||||
req.builder.buildArgs.ResetAllowed()
|
||||
return req.builder.processImageFrom(image)
|
||||
}
|
||||
|
||||
func parseBuildStageName(args []string) (string, error) {
|
||||
|
@ -261,16 +262,16 @@ func (b *Builder) getFromImage(name string) (builder.Image, error) {
|
|||
// special cases. search for 'OnBuild' in internals.go for additional special
|
||||
// cases.
|
||||
//
|
||||
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
func onbuild(req dispatchRequest) error {
|
||||
if len(req.args) == 0 {
|
||||
return errAtLeastOneArgument("ONBUILD")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
|
||||
triggerInstruction := strings.ToUpper(strings.TrimSpace(req.args[0]))
|
||||
switch triggerInstruction {
|
||||
case "ONBUILD":
|
||||
return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
||||
|
@ -278,29 +279,28 @@ func onbuild(b *Builder, args []string, attributes map[string]bool, original str
|
|||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
|
||||
}
|
||||
|
||||
original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
|
||||
|
||||
b.runConfig.OnBuild = append(b.runConfig.OnBuild, original)
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original))
|
||||
original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "")
|
||||
req.runConfig.OnBuild = append(req.runConfig.OnBuild, original)
|
||||
return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original))
|
||||
}
|
||||
|
||||
// WORKDIR /tmp
|
||||
//
|
||||
// Set the working directory for future RUN/CMD/etc statements.
|
||||
//
|
||||
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
func workdir(req dispatchRequest) error {
|
||||
if len(req.args) != 1 {
|
||||
return errExactlyOneArgument("WORKDIR")
|
||||
}
|
||||
|
||||
err := b.flags.Parse()
|
||||
err := req.flags.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This is from the Dockerfile and will not necessarily be in platform
|
||||
// specific semantics, hence ensure it is converted.
|
||||
b.runConfig.WorkingDir, err = normaliseWorkdir(b.runConfig.WorkingDir, args[0])
|
||||
req.runConfig.WorkingDir, err = normaliseWorkdir(req.runConfig.WorkingDir, req.args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -309,39 +309,40 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
|
|||
// This avoids having an unnecessary expensive mount/unmount calls
|
||||
// (on Windows in particular) during each container create.
|
||||
// Prior to 1.13, the mkdir was deferred and not executed at this step.
|
||||
if b.disableCommit {
|
||||
if req.builder.disableCommit {
|
||||
// Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo".
|
||||
// We've already updated the runConfig and that's enough.
|
||||
return nil
|
||||
}
|
||||
b.runConfig.Image = b.image
|
||||
req.runConfig.Image = req.builder.image
|
||||
|
||||
cmd := b.runConfig.Cmd
|
||||
comment := "WORKDIR " + b.runConfig.WorkingDir
|
||||
cmd := req.runConfig.Cmd
|
||||
comment := "WORKDIR " + req.runConfig.WorkingDir
|
||||
// reset the command for cache detection
|
||||
b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) "+comment))
|
||||
defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
req.runConfig.Cmd = strslice.StrSlice(append(getShell(req.runConfig), "#(nop) "+comment))
|
||||
defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
|
||||
|
||||
if hit, err := b.probeCache(); err != nil {
|
||||
if hit, err := req.builder.probeCache(); err != nil {
|
||||
return err
|
||||
} else if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
|
||||
Config: b.runConfig,
|
||||
req.runConfig.Image = req.builder.image
|
||||
container, err := req.builder.docker.ContainerCreate(types.ContainerCreateConfig{
|
||||
Config: req.runConfig,
|
||||
// Set a log config to override any default value set on the daemon
|
||||
HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
if err := b.docker.ContainerCreateWorkdir(container.ID); err != nil {
|
||||
req.builder.tmpContainers[container.ID] = struct{}{}
|
||||
if err := req.builder.docker.ContainerCreateWorkdir(container.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.commit(container.ID, cmd, comment)
|
||||
return req.builder.commit(container.ID, cmd, comment)
|
||||
}
|
||||
|
||||
// RUN some command yo
|
||||
|
@ -354,38 +355,38 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
|
|||
// RUN echo hi # cmd /S /C echo hi (Windows)
|
||||
// RUN [ "echo", "hi" ] # echo hi
|
||||
//
|
||||
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if !b.hasFromImage() {
|
||||
func run(req dispatchRequest) error {
|
||||
if !req.builder.hasFromImage() {
|
||||
return errors.New("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args = handleJSONArgs(args, attributes)
|
||||
args := handleJSONArgs(req.args, req.attributes)
|
||||
|
||||
if !attributes["json"] {
|
||||
args = append(getShell(b.runConfig), args...)
|
||||
if !req.attributes["json"] {
|
||||
args = append(getShell(req.runConfig), args...)
|
||||
}
|
||||
config := &container.Config{
|
||||
Cmd: strslice.StrSlice(args),
|
||||
Image: b.image,
|
||||
Image: req.builder.image,
|
||||
}
|
||||
|
||||
// stash the cmd
|
||||
cmd := b.runConfig.Cmd
|
||||
if len(b.runConfig.Entrypoint) == 0 && len(b.runConfig.Cmd) == 0 {
|
||||
b.runConfig.Cmd = config.Cmd
|
||||
cmd := req.runConfig.Cmd
|
||||
if len(req.runConfig.Entrypoint) == 0 && len(req.runConfig.Cmd) == 0 {
|
||||
req.runConfig.Cmd = config.Cmd
|
||||
}
|
||||
|
||||
// stash the config environment
|
||||
env := b.runConfig.Env
|
||||
env := req.runConfig.Env
|
||||
|
||||
defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
defer func(env []string) { b.runConfig.Env = env }(env)
|
||||
defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
|
||||
defer func(env []string) { req.runConfig.Env = env }(env)
|
||||
|
||||
cmdBuildEnv := b.buildArgsWithoutConfigEnv()
|
||||
cmdBuildEnv := req.builder.buildArgsWithoutConfigEnv()
|
||||
|
||||
// derive the command to use for probeCache() and to commit in this container.
|
||||
// Note that we only do this if there are any build-time env vars. Also, we
|
||||
|
@ -401,8 +402,8 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...))
|
||||
}
|
||||
|
||||
b.runConfig.Cmd = saveCmd
|
||||
hit, err := b.probeCache()
|
||||
req.runConfig.Cmd = saveCmd
|
||||
hit, err := req.builder.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -411,20 +412,20 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
}
|
||||
|
||||
// set Cmd manually, this is special case only for Dockerfiles
|
||||
b.runConfig.Cmd = config.Cmd
|
||||
req.runConfig.Cmd = config.Cmd
|
||||
// set build-time environment for 'run'.
|
||||
b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...)
|
||||
req.runConfig.Env = append(req.runConfig.Env, cmdBuildEnv...)
|
||||
// set config as already being escaped, this prevents double escaping on windows
|
||||
b.runConfig.ArgsEscaped = true
|
||||
req.runConfig.ArgsEscaped = true
|
||||
|
||||
logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd)
|
||||
logrus.Debugf("[BUILDER] Command to be executed: %v", req.runConfig.Cmd)
|
||||
|
||||
cID, err := b.create()
|
||||
cID, err := req.builder.create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.run(cID); err != nil {
|
||||
if err := req.builder.run(cID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -432,7 +433,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
// revert to original config environment and set the command string to
|
||||
// have the build-time env vars in it (if any) so that future cache look-ups
|
||||
// properly match it.
|
||||
b.runConfig.Env = env
|
||||
req.runConfig.Env = env
|
||||
|
||||
// remove builtinAllowedBuildArgs (see: builder.go) from the saveCmd
|
||||
// these args are transparent so resulting image should be the same regardless of the value
|
||||
|
@ -442,7 +443,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
copy(tmpBuildEnv, cmdBuildEnv)
|
||||
for i, env := range tmpBuildEnv {
|
||||
key := strings.SplitN(env, "=", 2)[0]
|
||||
if b.buildArgs.IsUnreferencedBuiltin(key) {
|
||||
if req.builder.buildArgs.IsUnreferencedBuiltin(key) {
|
||||
tmpBuildEnv = append(tmpBuildEnv[:i], tmpBuildEnv[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
@ -450,8 +451,8 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
tmpEnv := append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...)
|
||||
saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...))
|
||||
}
|
||||
b.runConfig.Cmd = saveCmd
|
||||
return b.commit(cID, cmd, "run")
|
||||
req.runConfig.Cmd = saveCmd
|
||||
return req.builder.commit(cID, cmd, "run")
|
||||
}
|
||||
|
||||
// CMD foo
|
||||
|
@ -459,27 +460,27 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
// Set the default command to run in the container (which may be empty).
|
||||
// Argument handling is the same as RUN.
|
||||
//
|
||||
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
func cmd(req dispatchRequest) error {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdSlice := handleJSONArgs(args, attributes)
|
||||
cmdSlice := handleJSONArgs(req.args, req.attributes)
|
||||
|
||||
if !attributes["json"] {
|
||||
cmdSlice = append(getShell(b.runConfig), cmdSlice...)
|
||||
if !req.attributes["json"] {
|
||||
cmdSlice = append(getShell(req.runConfig), cmdSlice...)
|
||||
}
|
||||
|
||||
b.runConfig.Cmd = strslice.StrSlice(cmdSlice)
|
||||
req.runConfig.Cmd = strslice.StrSlice(cmdSlice)
|
||||
// set config as already being escaped, this prevents double escaping on windows
|
||||
b.runConfig.ArgsEscaped = true
|
||||
req.runConfig.ArgsEscaped = true
|
||||
|
||||
if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
||||
if err := req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) != 0 {
|
||||
b.cmdSet = true
|
||||
if len(req.args) != 0 {
|
||||
req.builder.cmdSet = true
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -507,47 +508,47 @@ func parseOptInterval(f *Flag) (time.Duration, error) {
|
|||
// Set the default healthcheck command to run in the container (which may be empty).
|
||||
// Argument handling is the same as RUN.
|
||||
//
|
||||
func healthcheck(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
func healthcheck(req dispatchRequest) error {
|
||||
if len(req.args) == 0 {
|
||||
return errAtLeastOneArgument("HEALTHCHECK")
|
||||
}
|
||||
typ := strings.ToUpper(args[0])
|
||||
args = args[1:]
|
||||
typ := strings.ToUpper(req.args[0])
|
||||
args := req.args[1:]
|
||||
if typ == "NONE" {
|
||||
if len(args) != 0 {
|
||||
return errors.New("HEALTHCHECK NONE takes no arguments")
|
||||
}
|
||||
test := strslice.StrSlice{typ}
|
||||
b.runConfig.Healthcheck = &container.HealthConfig{
|
||||
req.runConfig.Healthcheck = &container.HealthConfig{
|
||||
Test: test,
|
||||
}
|
||||
} else {
|
||||
if b.runConfig.Healthcheck != nil {
|
||||
oldCmd := b.runConfig.Healthcheck.Test
|
||||
if req.runConfig.Healthcheck != nil {
|
||||
oldCmd := req.runConfig.Healthcheck.Test
|
||||
if len(oldCmd) > 0 && oldCmd[0] != "NONE" {
|
||||
fmt.Fprintf(b.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd)
|
||||
fmt.Fprintf(req.builder.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd)
|
||||
}
|
||||
}
|
||||
|
||||
healthcheck := container.HealthConfig{}
|
||||
|
||||
flInterval := b.flags.AddString("interval", "")
|
||||
flTimeout := b.flags.AddString("timeout", "")
|
||||
flStartPeriod := b.flags.AddString("start-period", "")
|
||||
flRetries := b.flags.AddString("retries", "")
|
||||
flInterval := req.flags.AddString("interval", "")
|
||||
flTimeout := req.flags.AddString("timeout", "")
|
||||
flStartPeriod := req.flags.AddString("start-period", "")
|
||||
flRetries := req.flags.AddString("retries", "")
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case "CMD":
|
||||
cmdSlice := handleJSONArgs(args, attributes)
|
||||
cmdSlice := handleJSONArgs(args, req.attributes)
|
||||
if len(cmdSlice) == 0 {
|
||||
return errors.New("Missing command after HEALTHCHECK CMD")
|
||||
}
|
||||
|
||||
if !attributes["json"] {
|
||||
if !req.attributes["json"] {
|
||||
typ = "CMD-SHELL"
|
||||
}
|
||||
|
||||
|
@ -587,10 +588,10 @@ func healthcheck(b *Builder, args []string, attributes map[string]bool, original
|
|||
healthcheck.Retries = 0
|
||||
}
|
||||
|
||||
b.runConfig.Healthcheck = &healthcheck
|
||||
req.runConfig.Healthcheck = &healthcheck
|
||||
}
|
||||
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", b.runConfig.Healthcheck))
|
||||
return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", req.runConfig.Healthcheck))
|
||||
}
|
||||
|
||||
// ENTRYPOINT /usr/sbin/nginx
|
||||
|
@ -598,35 +599,35 @@ func healthcheck(b *Builder, args []string, attributes map[string]bool, original
|
|||
// Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments
|
||||
// to /usr/sbin/nginx. Uses the default shell if not in JSON format.
|
||||
//
|
||||
// Handles command processing similar to CMD and RUN, only b.runConfig.Entrypoint
|
||||
// Handles command processing similar to CMD and RUN, only req.runConfig.Entrypoint
|
||||
// is initialized at NewBuilder time instead of through argument parsing.
|
||||
//
|
||||
func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
func entrypoint(req dispatchRequest) error {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsed := handleJSONArgs(args, attributes)
|
||||
parsed := handleJSONArgs(req.args, req.attributes)
|
||||
|
||||
switch {
|
||||
case attributes["json"]:
|
||||
case req.attributes["json"]:
|
||||
// ENTRYPOINT ["echo", "hi"]
|
||||
b.runConfig.Entrypoint = strslice.StrSlice(parsed)
|
||||
req.runConfig.Entrypoint = strslice.StrSlice(parsed)
|
||||
case len(parsed) == 0:
|
||||
// ENTRYPOINT []
|
||||
b.runConfig.Entrypoint = nil
|
||||
req.runConfig.Entrypoint = nil
|
||||
default:
|
||||
// ENTRYPOINT echo hi
|
||||
b.runConfig.Entrypoint = strslice.StrSlice(append(getShell(b.runConfig), parsed[0]))
|
||||
req.runConfig.Entrypoint = strslice.StrSlice(append(getShell(req.runConfig), parsed[0]))
|
||||
}
|
||||
|
||||
// when setting the entrypoint if a CMD was not explicitly set then
|
||||
// set the command to nil
|
||||
if !b.cmdSet {
|
||||
b.runConfig.Cmd = nil
|
||||
if !req.builder.cmdSet {
|
||||
req.runConfig.Cmd = nil
|
||||
}
|
||||
|
||||
if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.runConfig.Entrypoint)); err != nil {
|
||||
if err := req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", req.runConfig.Entrypoint)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -636,21 +637,21 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
|
|||
// EXPOSE 6667/tcp 7000/tcp
|
||||
//
|
||||
// Expose ports for links and port mappings. This all ends up in
|
||||
// b.runConfig.ExposedPorts for runconfig.
|
||||
// req.runConfig.ExposedPorts for runconfig.
|
||||
//
|
||||
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
portsTab := args
|
||||
func expose(req dispatchRequest) error {
|
||||
portsTab := req.args
|
||||
|
||||
if len(args) == 0 {
|
||||
if len(req.args) == 0 {
|
||||
return errAtLeastOneArgument("EXPOSE")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.runConfig.ExposedPorts == nil {
|
||||
b.runConfig.ExposedPorts = make(nat.PortSet)
|
||||
if req.runConfig.ExposedPorts == nil {
|
||||
req.runConfig.ExposedPorts = make(nat.PortSet)
|
||||
}
|
||||
|
||||
ports, _, err := nat.ParsePortSpecs(portsTab)
|
||||
|
@ -664,14 +665,14 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri
|
|||
portList := make([]string, len(ports))
|
||||
var i int
|
||||
for port := range ports {
|
||||
if _, exists := b.runConfig.ExposedPorts[port]; !exists {
|
||||
b.runConfig.ExposedPorts[port] = struct{}{}
|
||||
if _, exists := req.runConfig.ExposedPorts[port]; !exists {
|
||||
req.runConfig.ExposedPorts[port] = struct{}{}
|
||||
}
|
||||
portList[i] = string(port)
|
||||
i++
|
||||
}
|
||||
sort.Strings(portList)
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
||||
return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
||||
}
|
||||
|
||||
// USER foo
|
||||
|
@ -679,43 +680,43 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri
|
|||
// Set the user to 'foo' for future commands and when running the
|
||||
// ENTRYPOINT/CMD at container run time.
|
||||
//
|
||||
func user(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
func user(req dispatchRequest) error {
|
||||
if len(req.args) != 1 {
|
||||
return errExactlyOneArgument("USER")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.runConfig.User = args[0]
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("USER %v", args))
|
||||
req.runConfig.User = req.args[0]
|
||||
return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("USER %v", req.args))
|
||||
}
|
||||
|
||||
// VOLUME /foo
|
||||
//
|
||||
// Expose the volume /foo for use. Will also accept the JSON array form.
|
||||
//
|
||||
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
func volume(req dispatchRequest) error {
|
||||
if len(req.args) == 0 {
|
||||
return errAtLeastOneArgument("VOLUME")
|
||||
}
|
||||
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.runConfig.Volumes == nil {
|
||||
b.runConfig.Volumes = map[string]struct{}{}
|
||||
if req.runConfig.Volumes == nil {
|
||||
req.runConfig.Volumes = map[string]struct{}{}
|
||||
}
|
||||
for _, v := range args {
|
||||
for _, v := range req.args {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return errors.New("VOLUME specified can not be an empty string")
|
||||
}
|
||||
b.runConfig.Volumes[v] = struct{}{}
|
||||
req.runConfig.Volumes[v] = struct{}{}
|
||||
}
|
||||
if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
|
||||
if err := req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("VOLUME %v", req.args)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -724,19 +725,19 @@ func volume(b *Builder, args []string, attributes map[string]bool, original stri
|
|||
// STOPSIGNAL signal
|
||||
//
|
||||
// Set the signal that will be used to kill the container.
|
||||
func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
func stopSignal(req dispatchRequest) error {
|
||||
if len(req.args) != 1 {
|
||||
return errExactlyOneArgument("STOPSIGNAL")
|
||||
}
|
||||
|
||||
sig := args[0]
|
||||
sig := req.args[0]
|
||||
_, err := signal.ParseSignal(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.runConfig.StopSignal = sig
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
|
||||
req.runConfig.StopSignal = sig
|
||||
return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", req.args))
|
||||
}
|
||||
|
||||
// ARG name[=value]
|
||||
|
@ -744,8 +745,8 @@ func stopSignal(b *Builder, args []string, attributes map[string]bool, original
|
|||
// Adds the variable foo to the trusted list of variables that can be passed
|
||||
// to builder using the --build-arg flag for expansion/substitution or passing to 'run'.
|
||||
// Dockerfile author may optionally set a default value of this variable.
|
||||
func arg(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
func arg(req dispatchRequest) error {
|
||||
if len(req.args) != 1 {
|
||||
return errExactlyOneArgument("ARG")
|
||||
}
|
||||
|
||||
|
@ -755,7 +756,7 @@ func arg(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
hasDefault bool
|
||||
)
|
||||
|
||||
arg := args[0]
|
||||
arg := req.args[0]
|
||||
// 'arg' can just be a name or name-value pair. Note that this is different
|
||||
// from 'env' that handles the split of name and value at the parser level.
|
||||
// The reason for doing it differently for 'arg' is that we support just
|
||||
|
@ -779,36 +780,36 @@ func arg(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
if hasDefault {
|
||||
value = &newValue
|
||||
}
|
||||
b.buildArgs.AddArg(name, value)
|
||||
req.builder.buildArgs.AddArg(name, value)
|
||||
|
||||
// Arg before FROM doesn't add a layer
|
||||
if !b.hasFromImage() {
|
||||
b.buildArgs.AddMetaArg(name, value)
|
||||
if !req.builder.hasFromImage() {
|
||||
req.builder.buildArgs.AddMetaArg(name, value)
|
||||
return nil
|
||||
}
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
|
||||
return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
|
||||
}
|
||||
|
||||
// SHELL powershell -command
|
||||
//
|
||||
// Set the non-default shell to use.
|
||||
func shell(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if err := b.flags.Parse(); err != nil {
|
||||
func shell(req dispatchRequest) error {
|
||||
if err := req.flags.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
shellSlice := handleJSONArgs(args, attributes)
|
||||
shellSlice := handleJSONArgs(req.args, req.attributes)
|
||||
switch {
|
||||
case len(shellSlice) == 0:
|
||||
// SHELL []
|
||||
return errAtLeastOneArgument("SHELL")
|
||||
case attributes["json"]:
|
||||
case req.attributes["json"]:
|
||||
// SHELL ["powershell", "-command"]
|
||||
b.runConfig.Shell = strslice.StrSlice(shellSlice)
|
||||
req.runConfig.Shell = strslice.StrSlice(shellSlice)
|
||||
default:
|
||||
// SHELL powershell -command - not JSON
|
||||
return errNotJSON("SHELL", original)
|
||||
return errNotJSON("SHELL", req.original)
|
||||
}
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("SHELL %v", shellSlice))
|
||||
return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("SHELL %v", shellSlice))
|
||||
}
|
||||
|
||||
func errAtLeastOneArgument(command string) error {
|
||||
|
|
|
@ -3,7 +3,6 @@ package dockerfile
|
|||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
@ -12,6 +11,8 @@ import (
|
|||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
)
|
||||
|
||||
type commandWithFunction struct {
|
||||
|
@ -19,161 +20,142 @@ type commandWithFunction struct {
|
|||
function func(args []string) error
|
||||
}
|
||||
|
||||
func withArgs(f dispatcher) func([]string) error {
|
||||
return func(args []string) error {
|
||||
return f(dispatchRequest{args: args, runConfig: &container.Config{}})
|
||||
}
|
||||
}
|
||||
|
||||
func withBuilderAndArgs(builder *Builder, f dispatcher) func([]string) error {
|
||||
return func(args []string) error {
|
||||
return f(defaultDispatchReq(builder, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
|
||||
return dispatchRequest{
|
||||
builder: builder,
|
||||
args: args,
|
||||
flags: NewBFlags(),
|
||||
runConfig: &container.Config{},
|
||||
}
|
||||
}
|
||||
|
||||
func newBuilderWithMockBackend() *Builder {
|
||||
b := &Builder{
|
||||
runConfig: &container.Config{},
|
||||
options: &types.ImageBuildOptions{},
|
||||
docker: &MockBackend{},
|
||||
buildArgs: newBuildArgs(make(map[string]*string)),
|
||||
disableCommit: true,
|
||||
}
|
||||
b.imageContexts = &imageContexts{b: b}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestCommandsExactlyOneArgument(t *testing.T) {
|
||||
commands := []commandWithFunction{
|
||||
{"MAINTAINER", func(args []string) error { return maintainer(nil, args, nil, "") }},
|
||||
{"WORKDIR", func(args []string) error { return workdir(nil, args, nil, "") }},
|
||||
{"USER", func(args []string) error { return user(nil, args, nil, "") }},
|
||||
{"STOPSIGNAL", func(args []string) error { return stopSignal(nil, args, nil, "") }}}
|
||||
{"MAINTAINER", withArgs(maintainer)},
|
||||
{"WORKDIR", withArgs(workdir)},
|
||||
{"USER", withArgs(user)},
|
||||
{"STOPSIGNAL", withArgs(stopSignal)},
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
err := command.function([]string{})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Error should be present for %s command", command.name)
|
||||
}
|
||||
|
||||
expectedError := errExactlyOneArgument(command.name)
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
||||
}
|
||||
assert.EqualError(t, err, errExactlyOneArgument(command.name).Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsAtLeastOneArgument(t *testing.T) {
|
||||
commands := []commandWithFunction{
|
||||
{"ENV", func(args []string) error { return env(nil, args, nil, "") }},
|
||||
{"LABEL", func(args []string) error { return label(nil, args, nil, "") }},
|
||||
{"ONBUILD", func(args []string) error { return onbuild(nil, args, nil, "") }},
|
||||
{"HEALTHCHECK", func(args []string) error { return healthcheck(nil, args, nil, "") }},
|
||||
{"EXPOSE", func(args []string) error { return expose(nil, args, nil, "") }},
|
||||
{"VOLUME", func(args []string) error { return volume(nil, args, nil, "") }}}
|
||||
{"ENV", withArgs(env)},
|
||||
{"LABEL", withArgs(label)},
|
||||
{"ONBUILD", withArgs(onbuild)},
|
||||
{"HEALTHCHECK", withArgs(healthcheck)},
|
||||
{"EXPOSE", withArgs(expose)},
|
||||
{"VOLUME", withArgs(volume)},
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
err := command.function([]string{})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Error should be present for %s command", command.name)
|
||||
}
|
||||
|
||||
expectedError := errAtLeastOneArgument(command.name)
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
||||
}
|
||||
assert.EqualError(t, err, errAtLeastOneArgument(command.name).Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsAtLeastTwoArguments(t *testing.T) {
|
||||
commands := []commandWithFunction{
|
||||
{"ADD", func(args []string) error { return add(nil, args, nil, "") }},
|
||||
{"COPY", func(args []string) error { return dispatchCopy(nil, args, nil, "") }}}
|
||||
{"ADD", withArgs(add)},
|
||||
{"COPY", withArgs(dispatchCopy)}}
|
||||
|
||||
for _, command := range commands {
|
||||
err := command.function([]string{"arg1"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Error should be present for %s command", command.name)
|
||||
}
|
||||
|
||||
expectedError := errAtLeastTwoArguments(command.name)
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
||||
}
|
||||
assert.EqualError(t, err, errAtLeastTwoArguments(command.name).Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsTooManyArguments(t *testing.T) {
|
||||
commands := []commandWithFunction{
|
||||
{"ENV", func(args []string) error { return env(nil, args, nil, "") }},
|
||||
{"LABEL", func(args []string) error { return label(nil, args, nil, "") }}}
|
||||
{"ENV", withArgs(env)},
|
||||
{"LABEL", withArgs(label)}}
|
||||
|
||||
for _, command := range commands {
|
||||
err := command.function([]string{"arg1", "arg2", "arg3"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Error should be present for %s command", command.name)
|
||||
}
|
||||
|
||||
expectedError := errTooManyArguments(command.name)
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
||||
}
|
||||
assert.EqualError(t, err, errTooManyArguments(command.name).Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandseBlankNames(t *testing.T) {
|
||||
bflags := &BFlags{}
|
||||
config := &container.Config{}
|
||||
|
||||
b := &Builder{flags: bflags, runConfig: config, disableCommit: true}
|
||||
|
||||
func TestCommandsBlankNames(t *testing.T) {
|
||||
builder := newBuilderWithMockBackend()
|
||||
commands := []commandWithFunction{
|
||||
{"ENV", func(args []string) error { return env(b, args, nil, "") }},
|
||||
{"LABEL", func(args []string) error { return label(b, args, nil, "") }},
|
||||
{"ENV", withBuilderAndArgs(builder, env)},
|
||||
{"LABEL", withBuilderAndArgs(builder, label)},
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
err := command.function([]string{"", ""})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Error should be present for %s command", command.name)
|
||||
}
|
||||
|
||||
expectedError := errBlankCommandNames(command.name)
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Fatalf("Wrong error message for %s. Got: %s. Should be: %s", command.name, err.Error(), expectedError)
|
||||
}
|
||||
assert.EqualError(t, err, errBlankCommandNames(command.name).Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnv2Variables(t *testing.T) {
|
||||
b := newBuilderWithMockBackend()
|
||||
b.disableCommit = true
|
||||
|
||||
args := []string{"var1", "val1", "var2", "val2"}
|
||||
err := env(b, args, nil, "")
|
||||
assert.NoError(t, err)
|
||||
req := defaultDispatchReq(b, args...)
|
||||
err := env(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
fmt.Sprintf("%s=%s", args[0], args[1]),
|
||||
fmt.Sprintf("%s=%s", args[2], args[3]),
|
||||
}
|
||||
assert.Equal(t, expected, b.runConfig.Env)
|
||||
assert.Equal(t, expected, req.runConfig.Env)
|
||||
}
|
||||
|
||||
func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
|
||||
b := newBuilderWithMockBackend()
|
||||
b.disableCommit = true
|
||||
b.runConfig.Env = []string{"var1=old", "var2=fromenv"}
|
||||
|
||||
args := []string{"var1", "val1"}
|
||||
err := env(b, args, nil, "")
|
||||
assert.NoError(t, err)
|
||||
req := defaultDispatchReq(b, args...)
|
||||
req.runConfig.Env = []string{"var1=old", "var2=fromenv"}
|
||||
err := env(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
fmt.Sprintf("%s=%s", args[0], args[1]),
|
||||
"var2=fromenv",
|
||||
}
|
||||
assert.Equal(t, expected, b.runConfig.Env)
|
||||
assert.Equal(t, expected, req.runConfig.Env)
|
||||
}
|
||||
|
||||
func TestMaintainer(t *testing.T) {
|
||||
maintainerEntry := "Some Maintainer <maintainer@example.com>"
|
||||
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
|
||||
if err := maintainer(b, []string{maintainerEntry}, nil, ""); err != nil {
|
||||
t.Fatalf("Error when executing maintainer: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.maintainer != maintainerEntry {
|
||||
t.Fatalf("Maintainer in builder should be set to %s. Got: %s", maintainerEntry, b.maintainer)
|
||||
}
|
||||
b := newBuilderWithMockBackend()
|
||||
err := maintainer(defaultDispatchReq(b, maintainerEntry))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, maintainerEntry, b.maintainer)
|
||||
}
|
||||
|
||||
func TestLabel(t *testing.T) {
|
||||
|
@ -181,45 +163,25 @@ func TestLabel(t *testing.T) {
|
|||
labelValue := "value"
|
||||
|
||||
labelEntry := []string{labelName, labelValue}
|
||||
b := newBuilderWithMockBackend()
|
||||
req := defaultDispatchReq(b, labelEntry...)
|
||||
err := label(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
|
||||
if err := label(b, labelEntry, nil, ""); err != nil {
|
||||
t.Fatalf("Error when executing label: %s", err.Error())
|
||||
}
|
||||
|
||||
if val, ok := b.runConfig.Labels[labelName]; ok {
|
||||
if val != labelValue {
|
||||
t.Fatalf("Label %s should have value %s, had %s instead", labelName, labelValue, val)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Label %s should be present but it is not", labelName)
|
||||
}
|
||||
}
|
||||
|
||||
func newBuilderWithMockBackend() *Builder {
|
||||
b := &Builder{
|
||||
flags: &BFlags{},
|
||||
runConfig: &container.Config{},
|
||||
options: &types.ImageBuildOptions{},
|
||||
docker: &MockBackend{},
|
||||
buildArgs: newBuildArgs(make(map[string]*string)),
|
||||
}
|
||||
b.imageContexts = &imageContexts{b: b}
|
||||
return b
|
||||
require.Contains(t, req.runConfig.Labels, labelName)
|
||||
assert.Equal(t, req.runConfig.Labels[labelName], labelValue)
|
||||
}
|
||||
|
||||
func TestFromScratch(t *testing.T) {
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
err := from(b, []string{"scratch"}, nil, "")
|
||||
err := from(defaultDispatchReq(b, "scratch"))
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
assert.EqualError(t, err, "Windows does not support FROM scratch")
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "", b.image)
|
||||
assert.Equal(t, true, b.noBaseImage)
|
||||
}
|
||||
|
@ -234,10 +196,10 @@ func TestFromWithArg(t *testing.T) {
|
|||
b := newBuilderWithMockBackend()
|
||||
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
|
||||
|
||||
assert.NoError(t, arg(b, []string{"THETAG=" + tag}, nil, ""))
|
||||
err := from(b, []string{"alpine${THETAG}"}, nil, "")
|
||||
require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag)))
|
||||
err := from(defaultDispatchReq(b, "alpine${THETAG}"))
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, b.image)
|
||||
assert.Equal(t, expected, b.from.ImageID())
|
||||
assert.Len(t, b.buildArgs.GetAllAllowed(), 0)
|
||||
|
@ -255,8 +217,8 @@ func TestFromWithUndefinedArg(t *testing.T) {
|
|||
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
|
||||
b.options.BuildArgs = map[string]*string{"THETAG": &tag}
|
||||
|
||||
err := from(b, []string{"alpine${THETAG}"}, nil, "")
|
||||
assert.NoError(t, err)
|
||||
err := from(defaultDispatchReq(b, "alpine${THETAG}"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, b.image)
|
||||
}
|
||||
|
||||
|
@ -267,237 +229,147 @@ func TestOnbuildIllegalTriggers(t *testing.T) {
|
|||
{"FROM", "FROM isn't allowed as an ONBUILD trigger"}}
|
||||
|
||||
for _, trigger := range triggers {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
err := onbuild(b, []string{trigger.command}, nil, "")
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), trigger.expectedError) {
|
||||
t.Fatalf("Error message not correct. Should be: %s, got: %s", trigger.expectedError, err.Error())
|
||||
}
|
||||
err := onbuild(defaultDispatchReq(b, trigger.command))
|
||||
testutil.ErrorContains(t, err, trigger.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnbuild(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
err := onbuild(b, []string{"ADD", ".", "/app/src"}, nil, "ONBUILD ADD . /app/src")
|
||||
req := defaultDispatchReq(b, "ADD", ".", "/app/src")
|
||||
req.original = "ONBUILD ADD . /app/src"
|
||||
req.runConfig = &container.Config{}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
|
||||
expectedOnbuild := "ADD . /app/src"
|
||||
|
||||
if b.runConfig.OnBuild[0] != expectedOnbuild {
|
||||
t.Fatalf("Wrong ONBUILD command. Expected: %s, got: %s", expectedOnbuild, b.runConfig.OnBuild[0])
|
||||
}
|
||||
err := onbuild(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "ADD . /app/src", req.runConfig.OnBuild[0])
|
||||
}
|
||||
|
||||
func TestWorkdir(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
|
||||
b := newBuilderWithMockBackend()
|
||||
workingDir := "/app"
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
workingDir = "C:\app"
|
||||
}
|
||||
|
||||
err := workdir(b, []string{workingDir}, nil, "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.runConfig.WorkingDir != workingDir {
|
||||
t.Fatalf("WorkingDir should be set to %s, got %s", workingDir, b.runConfig.WorkingDir)
|
||||
}
|
||||
|
||||
req := defaultDispatchReq(b, workingDir)
|
||||
err := workdir(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, workingDir, req.runConfig.WorkingDir)
|
||||
}
|
||||
|
||||
func TestCmd(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
|
||||
b := newBuilderWithMockBackend()
|
||||
command := "./executable"
|
||||
|
||||
err := cmd(b, []string{command}, nil, "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
req := defaultDispatchReq(b, command)
|
||||
err := cmd(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
var expectedCommand strslice.StrSlice
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
|
||||
} else {
|
||||
expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
|
||||
}
|
||||
|
||||
if !compareStrSlice(b.runConfig.Cmd, expectedCommand) {
|
||||
t.Fatalf("Command should be set to %s, got %s", command, b.runConfig.Cmd)
|
||||
}
|
||||
|
||||
if !b.cmdSet {
|
||||
t.Fatal("Command should be marked as set")
|
||||
}
|
||||
}
|
||||
|
||||
func compareStrSlice(slice1, slice2 strslice.StrSlice) bool {
|
||||
if len(slice1) != len(slice2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range slice1 {
|
||||
if slice1[i] != slice2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
assert.Equal(t, expectedCommand, req.runConfig.Cmd)
|
||||
assert.True(t, b.cmdSet)
|
||||
}
|
||||
|
||||
func TestHealthcheckNone(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
if err := healthcheck(b, []string{"NONE"}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
req := defaultDispatchReq(b, "NONE")
|
||||
err := healthcheck(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
if b.runConfig.Healthcheck == nil {
|
||||
t.Fatal("Healthcheck should be set, got nil")
|
||||
}
|
||||
|
||||
expectedTest := strslice.StrSlice(append([]string{"NONE"}))
|
||||
|
||||
if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) {
|
||||
t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test)
|
||||
}
|
||||
require.NotNil(t, req.runConfig.Healthcheck)
|
||||
assert.Equal(t, []string{"NONE"}, req.runConfig.Healthcheck.Test)
|
||||
}
|
||||
|
||||
func TestHealthcheckCmd(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{flags: make(map[string]*Flag)}, runConfig: &container.Config{}, disableCommit: true}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
if err := healthcheck(b, []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
args := []string{"CMD", "curl", "-f", "http://localhost/", "||", "exit", "1"}
|
||||
req := defaultDispatchReq(b, args...)
|
||||
err := healthcheck(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
if b.runConfig.Healthcheck == nil {
|
||||
t.Fatal("Healthcheck should be set, got nil")
|
||||
}
|
||||
|
||||
expectedTest := strslice.StrSlice(append([]string{"CMD-SHELL"}, "curl -f http://localhost/ || exit 1"))
|
||||
|
||||
if !compareStrSlice(expectedTest, b.runConfig.Healthcheck.Test) {
|
||||
t.Fatalf("Command should be set to %s, got %s", expectedTest, b.runConfig.Healthcheck.Test)
|
||||
}
|
||||
require.NotNil(t, req.runConfig.Healthcheck)
|
||||
expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
|
||||
assert.Equal(t, expectedTest, req.runConfig.Healthcheck.Test)
|
||||
}
|
||||
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
|
||||
b := newBuilderWithMockBackend()
|
||||
entrypointCmd := "/usr/sbin/nginx"
|
||||
|
||||
if err := entrypoint(b, []string{entrypointCmd}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.runConfig.Entrypoint == nil {
|
||||
t.Fatal("Entrypoint should be set")
|
||||
}
|
||||
req := defaultDispatchReq(b, entrypointCmd)
|
||||
err := entrypoint(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, req.runConfig.Entrypoint)
|
||||
|
||||
var expectedEntrypoint strslice.StrSlice
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
|
||||
} else {
|
||||
expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
|
||||
}
|
||||
|
||||
if !compareStrSlice(expectedEntrypoint, b.runConfig.Entrypoint) {
|
||||
t.Fatalf("Entrypoint command should be set to %s, got %s", expectedEntrypoint, b.runConfig.Entrypoint)
|
||||
}
|
||||
assert.Equal(t, expectedEntrypoint, req.runConfig.Entrypoint)
|
||||
}
|
||||
|
||||
func TestExpose(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
exposedPort := "80"
|
||||
req := defaultDispatchReq(b, exposedPort)
|
||||
err := expose(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
if err := expose(b, []string{exposedPort}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.runConfig.ExposedPorts == nil {
|
||||
t.Fatal("ExposedPorts should be set")
|
||||
}
|
||||
|
||||
if len(b.runConfig.ExposedPorts) != 1 {
|
||||
t.Fatalf("ExposedPorts should contain only 1 element. Got %s", b.runConfig.ExposedPorts)
|
||||
}
|
||||
require.NotNil(t, req.runConfig.ExposedPorts)
|
||||
require.Len(t, req.runConfig.ExposedPorts, 1)
|
||||
|
||||
portsMapping, err := nat.ParsePortSpec(exposedPort)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error when parsing port spec: %s", err.Error())
|
||||
}
|
||||
|
||||
if _, ok := b.runConfig.ExposedPorts[portsMapping[0].Port]; !ok {
|
||||
t.Fatalf("Port %s should be present. Got %s", exposedPort, b.runConfig.ExposedPorts)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, req.runConfig.ExposedPorts, portsMapping[0].Port)
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
|
||||
b := newBuilderWithMockBackend()
|
||||
userCommand := "foo"
|
||||
|
||||
if err := user(b, []string{userCommand}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.runConfig.User != userCommand {
|
||||
t.Fatalf("User should be set to %s, got %s", userCommand, b.runConfig.User)
|
||||
}
|
||||
req := defaultDispatchReq(b, userCommand)
|
||||
err := user(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, userCommand, req.runConfig.User)
|
||||
}
|
||||
|
||||
func TestVolume(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
exposedVolume := "/foo"
|
||||
|
||||
if err := volume(b, []string{exposedVolume}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
req := defaultDispatchReq(b, exposedVolume)
|
||||
err := volume(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
if b.runConfig.Volumes == nil {
|
||||
t.Fatal("Volumes should be set")
|
||||
}
|
||||
|
||||
if len(b.runConfig.Volumes) != 1 {
|
||||
t.Fatalf("Volumes should contain only 1 element. Got %s", b.runConfig.Volumes)
|
||||
}
|
||||
|
||||
if _, ok := b.runConfig.Volumes[exposedVolume]; !ok {
|
||||
t.Fatalf("Volume %s should be present. Got %s", exposedVolume, b.runConfig.Volumes)
|
||||
}
|
||||
require.NotNil(t, req.runConfig.Volumes)
|
||||
assert.Len(t, req.runConfig.Volumes, 1)
|
||||
assert.Contains(t, req.runConfig.Volumes, exposedVolume)
|
||||
}
|
||||
|
||||
func TestStopSignal(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
|
||||
b := newBuilderWithMockBackend()
|
||||
signal := "SIGKILL"
|
||||
|
||||
if err := stopSignal(b, []string{signal}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.runConfig.StopSignal != signal {
|
||||
t.Fatalf("StopSignal should be set to %s, got %s", signal, b.runConfig.StopSignal)
|
||||
}
|
||||
req := defaultDispatchReq(b, signal)
|
||||
err := stopSignal(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, signal, req.runConfig.StopSignal)
|
||||
}
|
||||
|
||||
func TestArg(t *testing.T) {
|
||||
|
@ -507,33 +379,23 @@ func TestArg(t *testing.T) {
|
|||
argVal := "bar"
|
||||
argDef := fmt.Sprintf("%s=%s", argName, argVal)
|
||||
|
||||
err := arg(b, []string{argDef}, nil, "")
|
||||
assert.NoError(t, err)
|
||||
err := arg(defaultDispatchReq(b, argDef))
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := map[string]string{argName: argVal}
|
||||
allowed := b.buildArgs.GetAllAllowed()
|
||||
assert.Equal(t, expected, allowed)
|
||||
assert.Equal(t, expected, b.buildArgs.GetAllAllowed())
|
||||
}
|
||||
|
||||
func TestShell(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
shellCmd := "powershell"
|
||||
req := defaultDispatchReq(b, shellCmd)
|
||||
req.attributes = map[string]bool{"json": true}
|
||||
|
||||
attrs := make(map[string]bool)
|
||||
attrs["json"] = true
|
||||
|
||||
if err := shell(b, []string{shellCmd}, attrs, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.runConfig.Shell == nil {
|
||||
t.Fatal("Shell should be set")
|
||||
}
|
||||
err := shell(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedShell := strslice.StrSlice([]string{shellCmd})
|
||||
|
||||
if !compareStrSlice(expectedShell, b.runConfig.Shell) {
|
||||
t.Fatalf("Shell should be set to %s, got %s", expectedShell, b.runConfig.Shell)
|
||||
}
|
||||
assert.Equal(t, expectedShell, req.runConfig.Shell)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ package dockerfile
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"bytes"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/builder/dockerfile/command"
|
||||
"github.com/docker/docker/builder/dockerfile/parser"
|
||||
"github.com/docker/docker/runconfig/opts"
|
||||
|
@ -56,10 +58,32 @@ var allowWordExpansion = map[string]bool{
|
|||
command.Expose: true,
|
||||
}
|
||||
|
||||
var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error
|
||||
type dispatchRequest struct {
|
||||
builder *Builder // TODO: replace this with a smaller interface
|
||||
args []string
|
||||
attributes map[string]bool
|
||||
flags *BFlags
|
||||
original string
|
||||
runConfig *container.Config
|
||||
}
|
||||
|
||||
func newDispatchRequestFromNode(node *parser.Node, builder *Builder, args []string) dispatchRequest {
|
||||
return dispatchRequest{
|
||||
builder: builder,
|
||||
args: args,
|
||||
attributes: node.Attributes,
|
||||
original: node.Original,
|
||||
flags: NewBFlagsWithArgs(node.Flags),
|
||||
runConfig: builder.runConfig,
|
||||
}
|
||||
}
|
||||
|
||||
type dispatcher func(dispatchRequest) error
|
||||
|
||||
var evaluateTable map[string]dispatcher
|
||||
|
||||
func init() {
|
||||
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
|
||||
evaluateTable = map[string]dispatcher{
|
||||
command.Add: add,
|
||||
command.Arg: arg,
|
||||
command.Cmd: cmd,
|
||||
|
@ -95,8 +119,8 @@ func init() {
|
|||
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
|
||||
// deal with that, at least until it becomes more of a general concern with new
|
||||
// features.
|
||||
func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node) error {
|
||||
cmd := ast.Value
|
||||
func (b *Builder) dispatch(stepN int, stepTotal int, node *parser.Node) error {
|
||||
cmd := node.Value
|
||||
upperCasedCmd := strings.ToUpper(cmd)
|
||||
|
||||
// To ensure the user is given a decent error message if the platform
|
||||
|
@ -105,28 +129,25 @@ func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node) error {
|
|||
return err
|
||||
}
|
||||
|
||||
attrs := ast.Attributes
|
||||
original := ast.Original
|
||||
flags := ast.Flags
|
||||
strList := []string{}
|
||||
msg := fmt.Sprintf("Step %d/%d : %s", stepN+1, stepTotal, upperCasedCmd)
|
||||
msg := bytes.NewBufferString(fmt.Sprintf("Step %d/%d : %s", stepN+1, stepTotal, upperCasedCmd))
|
||||
|
||||
if len(ast.Flags) > 0 {
|
||||
msg += " " + strings.Join(ast.Flags, " ")
|
||||
if len(node.Flags) > 0 {
|
||||
msg.WriteString(strings.Join(node.Flags, " "))
|
||||
}
|
||||
|
||||
ast := node
|
||||
if cmd == "onbuild" {
|
||||
if ast.Next == nil {
|
||||
return errors.New("ONBUILD requires at least one argument")
|
||||
}
|
||||
ast = ast.Next.Children[0]
|
||||
strList = append(strList, ast.Value)
|
||||
msg += " " + ast.Value
|
||||
msg.WriteString(" " + ast.Value)
|
||||
|
||||
if len(ast.Flags) > 0 {
|
||||
msg += " " + strings.Join(ast.Flags, " ")
|
||||
msg.WriteString(" " + strings.Join(ast.Flags, " "))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
msgList := initMsgList(ast)
|
||||
|
@ -143,15 +164,13 @@ func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node) error {
|
|||
msgList[i] = ast.Value
|
||||
}
|
||||
|
||||
msg += " " + strings.Join(msgList, " ")
|
||||
fmt.Fprintln(b.Stdout, msg)
|
||||
msg.WriteString(" " + strings.Join(msgList, " "))
|
||||
fmt.Fprintln(b.Stdout, msg.String())
|
||||
|
||||
// XXX yes, we skip any cmds that are not valid; the parser should have
|
||||
// picked these out already.
|
||||
if f, ok := evaluateTable[cmd]; ok {
|
||||
b.flags = NewBFlags()
|
||||
b.flags.Args = flags
|
||||
return f(b, strList, attrs, original)
|
||||
return f(newDispatchRequestFromNode(node, b, strList))
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unknown instruction: %s", upperCasedCmd)
|
||||
|
|
|
@ -505,7 +505,7 @@ func (s *DockerSuite) TestBuildAddSingleFileToWorkdir(c *check.C) {
|
|||
|
||||
func (s *DockerSuite) TestBuildAddSingleFileToExistDir(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux) // Linux specific test
|
||||
buildImageSuccessfully(c, "testaddsinglefiletoexistdir", build.WithBuildContext(c,
|
||||
cli.BuildCmd(c, "testaddsinglefiletoexistdir", build.WithBuildContext(c,
|
||||
build.WithFile("Dockerfile", `FROM busybox
|
||||
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
|
||||
RUN echo 'dockerio:x:1001:' >> /etc/group
|
||||
|
@ -561,13 +561,13 @@ func (s *DockerSuite) TestBuildUsernamespaceValidateRemappedRoot(c *check.C) {
|
|||
}
|
||||
name := "testbuildusernamespacevalidateremappedroot"
|
||||
for _, tc := range testCases {
|
||||
buildImageSuccessfully(c, name, build.WithBuildContext(c,
|
||||
cli.BuildCmd(c, name, build.WithBuildContext(c,
|
||||
build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
|
||||
%s
|
||||
RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'root:root' ]`, tc)),
|
||||
build.WithFile("test_dir/test_file", "test file")))
|
||||
|
||||
dockerCmd(c, "rmi", name)
|
||||
cli.DockerCmd(c, "rmi", name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,7 +576,7 @@ func (s *DockerSuite) TestBuildAddAndCopyFileWithWhitespace(c *check.C) {
|
|||
name := "testaddfilewithwhitespace"
|
||||
|
||||
for _, command := range []string{"ADD", "COPY"} {
|
||||
buildImageSuccessfully(c, name, build.WithBuildContext(c,
|
||||
cli.BuildCmd(c, name, build.WithBuildContext(c,
|
||||
build.WithFile("Dockerfile", fmt.Sprintf(`FROM busybox
|
||||
RUN mkdir "/test dir"
|
||||
RUN mkdir "/test_dir"
|
||||
|
@ -600,7 +600,7 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, command, command, command, com
|
|||
build.WithFile("test dir/test_file6", "test6"),
|
||||
))
|
||||
|
||||
dockerCmd(c, "rmi", name)
|
||||
cli.DockerCmd(c, "rmi", name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,7 +623,7 @@ RUN find "test5" "C:/test dir/test_file5"
|
|||
RUN find "test6" "C:/test dir/test_file6"`
|
||||
|
||||
name := "testcopyfilewithwhitespace"
|
||||
buildImageSuccessfully(c, name, build.WithBuildContext(c,
|
||||
cli.BuildCmd(c, name, build.WithBuildContext(c,
|
||||
build.WithFile("Dockerfile", dockerfile),
|
||||
build.WithFile("test file1", "test1"),
|
||||
build.WithFile("test_file2", "test2"),
|
||||
|
|
Loading…
Reference in a new issue