mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #15182 from mapuri/build-arg
Support for passing build-time variables in build context
This commit is contained in:
commit
1ffff4c8e2
18 changed files with 1101 additions and 57 deletions
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,6 +65,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
||||||
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
||||||
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
||||||
|
flBuildArg := opts.NewListOpts(opts.ValidateEnv)
|
||||||
|
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
|
||||||
|
|
||||||
ulimits := make(map[string]*ulimit.Ulimit)
|
ulimits := make(map[string]*ulimit.Ulimit)
|
||||||
flUlimits := opts.NewUlimitOpt(&ulimits)
|
flUlimits := opts.NewUlimitOpt(&ulimits)
|
||||||
|
@ -257,6 +260,14 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
}
|
}
|
||||||
v.Set("ulimits", string(ulimitsJSON))
|
v.Set("ulimits", string(ulimitsJSON))
|
||||||
|
|
||||||
|
// collect all the build-time environment variables for the container
|
||||||
|
buildArgs := runconfig.ConvertKVStringsToMap(flBuildArg.GetAll())
|
||||||
|
buildArgsJSON, err := json.Marshal(buildArgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set("buildargs", string(buildArgsJSON))
|
||||||
|
|
||||||
headers := http.Header(make(map[string][]string))
|
headers := http.Header(make(map[string][]string))
|
||||||
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -323,6 +323,15 @@ func (s *Server) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||||
buildConfig.Ulimits = buildUlimits
|
buildConfig.Ulimits = buildUlimits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var buildArgs = map[string]string{}
|
||||||
|
buildArgsJSON := r.FormValue("buildargs")
|
||||||
|
if buildArgsJSON != "" {
|
||||||
|
if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildConfig.BuildArgs = buildArgs
|
||||||
|
|
||||||
// Job cancellation. Note: not all job types support this.
|
// Job cancellation. Note: not all job types support this.
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||||
finished := make(chan struct{})
|
finished := make(chan struct{})
|
||||||
|
|
|
@ -18,6 +18,7 @@ const (
|
||||||
Volume = "volume"
|
Volume = "volume"
|
||||||
User = "user"
|
User = "user"
|
||||||
StopSignal = "stopsignal"
|
StopSignal = "stopsignal"
|
||||||
|
Arg = "arg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commands is list of all Dockerfile commands
|
// Commands is list of all Dockerfile commands
|
||||||
|
@ -37,4 +38,5 @@ var Commands = map[string]struct{}{
|
||||||
Volume: {},
|
Volume: {},
|
||||||
User: {},
|
User: {},
|
||||||
StopSignal: {},
|
StopSignal: {},
|
||||||
|
Arg: {},
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,15 +327,59 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stash the cmd
|
||||||
cmd := b.Config.Cmd
|
cmd := b.Config.Cmd
|
||||||
// set Cmd manually, this is special case only for Dockerfiles
|
|
||||||
b.Config.Cmd = config.Cmd
|
|
||||||
runconfig.Merge(b.Config, config)
|
runconfig.Merge(b.Config, config)
|
||||||
|
// stash the config environment
|
||||||
|
env := b.Config.Env
|
||||||
|
|
||||||
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
|
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
|
||||||
|
defer func(env []string) { b.Config.Env = env }(env)
|
||||||
|
|
||||||
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
|
// derive the net build-time environment for this run. We let config
|
||||||
|
// environment override the build time environment.
|
||||||
|
// This means that we take the b.buildArgs list of env vars and remove
|
||||||
|
// any of those variables that are defined as part of the container. In other
|
||||||
|
// words, anything in b.Config.Env. What's left is the list of build-time env
|
||||||
|
// vars that we need to add to each RUN command - note the list could be empty.
|
||||||
|
//
|
||||||
|
// We don't persist the build time environment with container's config
|
||||||
|
// environment, but just sort and prepend it to the command string at time
|
||||||
|
// of commit.
|
||||||
|
// This helps with tracing back the image's actual environment at the time
|
||||||
|
// of RUN, without leaking it to the final image. It also aids cache
|
||||||
|
// lookup for same image built with same build time environment.
|
||||||
|
cmdBuildEnv := []string{}
|
||||||
|
configEnv := runconfig.ConvertKVStringsToMap(b.Config.Env)
|
||||||
|
for key, val := range b.buildArgs {
|
||||||
|
if !b.isBuildArgAllowed(key) {
|
||||||
|
// skip build-args that are not in allowed list, meaning they have
|
||||||
|
// not been defined by an "ARG" Dockerfile command yet.
|
||||||
|
// This is an error condition but only if there is no "ARG" in the entire
|
||||||
|
// Dockerfile, so we'll generate any necessary errors after we parsed
|
||||||
|
// the entire file (see 'leftoverArgs' processing in evaluator.go )
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := configEnv[key]; !ok {
|
||||||
|
cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// use the special argument "|#" at the start of the args array. This will
|
||||||
|
// avoid conflicts with any RUN command since commands can not
|
||||||
|
// start with | (vertical bar). The "#" (number of build envs) is there to
|
||||||
|
// help ensure proper cache matches. We don't want a RUN command
|
||||||
|
// that starts with "foo=abc" to be considered part of a build-time env var.
|
||||||
|
saveCmd := config.Cmd
|
||||||
|
if len(cmdBuildEnv) > 0 {
|
||||||
|
sort.Strings(cmdBuildEnv)
|
||||||
|
tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...)
|
||||||
|
saveCmd = stringutils.NewStrSlice(append(tmpEnv, saveCmd.Slice()...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Config.Cmd = saveCmd
|
||||||
hit, err := b.probeCache()
|
hit, err := b.probeCache()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -344,6 +388,13 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set Cmd manually, this is special case only for Dockerfiles
|
||||||
|
b.Config.Cmd = config.Cmd
|
||||||
|
// set build-time environment for 'run'.
|
||||||
|
b.Config.Env = append(b.Config.Env, cmdBuildEnv...)
|
||||||
|
|
||||||
|
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
|
||||||
|
|
||||||
c, err := b.create()
|
c, err := b.create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -358,6 +409,12 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.Config.Env = env
|
||||||
|
b.Config.Cmd = saveCmd
|
||||||
if err := b.commit(c.ID, cmd, "run"); err != nil {
|
if err := b.commit(c.ID, cmd, "run"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -557,3 +614,47 @@ func stopSignal(b *builder, args []string, attributes map[string]bool, original
|
||||||
b.Config.StopSignal = sig
|
b.Config.StopSignal = sig
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
|
return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ARG name[=value]
|
||||||
|
//
|
||||||
|
// Adds the variable foo to the trusted list of variables that can be passed
|
||||||
|
// to builder using the --build-arg flag for expansion/subsitution 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 {
|
||||||
|
return fmt.Errorf("ARG requires exactly one argument definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
hasDefault bool
|
||||||
|
)
|
||||||
|
|
||||||
|
arg := 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
|
||||||
|
// defining an arg and not assign it a value (while 'env' always expects a
|
||||||
|
// name-value pair). If possible, it will be good to harmonize the two.
|
||||||
|
if strings.Contains(arg, "=") {
|
||||||
|
parts := strings.SplitN(arg, "=", 2)
|
||||||
|
name = parts[0]
|
||||||
|
value = parts[1]
|
||||||
|
hasDefault = true
|
||||||
|
} else {
|
||||||
|
name = arg
|
||||||
|
hasDefault = false
|
||||||
|
}
|
||||||
|
// add the arg to allowed list of build-time args from this step on.
|
||||||
|
b.allowedBuildArgs[name] = true
|
||||||
|
|
||||||
|
// If there is a default value associated with this arg then add it to the
|
||||||
|
// b.buildArgs if one is not already passed to the builder. The args passed
|
||||||
|
// to builder override the defaut value of 'arg'.
|
||||||
|
if _, ok := b.buildArgs[name]; !ok && hasDefault {
|
||||||
|
b.buildArgs[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.commit("", b.Config.Cmd, fmt.Sprintf("ARG %s", arg))
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ var replaceEnvAllowed = map[string]struct{}{
|
||||||
command.Volume: {},
|
command.Volume: {},
|
||||||
command.User: {},
|
command.User: {},
|
||||||
command.StopSignal: {},
|
command.StopSignal: {},
|
||||||
|
command.Arg: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
|
var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
|
||||||
|
@ -75,6 +76,7 @@ func init() {
|
||||||
command.Volume: volume,
|
command.Volume: volume,
|
||||||
command.User: user,
|
command.User: user,
|
||||||
command.StopSignal: stopSignal,
|
command.StopSignal: stopSignal,
|
||||||
|
command.Arg: arg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +113,9 @@ type builder struct {
|
||||||
|
|
||||||
Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
|
Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
|
||||||
|
|
||||||
|
buildArgs map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'.
|
||||||
|
allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
|
||||||
|
|
||||||
// both of these are controlled by the Remove and ForceRemove options in BuildOpts
|
// both of these are controlled by the Remove and ForceRemove options in BuildOpts
|
||||||
TmpContainers map[string]struct{} // a map of containers used for removes
|
TmpContainers map[string]struct{} // a map of containers used for removes
|
||||||
|
|
||||||
|
@ -194,6 +199,18 @@ func (b *builder) Run(context io.Reader) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if there are any leftover build-args that were passed but not
|
||||||
|
// consumed during build. Return an error, if there are any.
|
||||||
|
leftoverArgs := []string{}
|
||||||
|
for arg := range b.buildArgs {
|
||||||
|
if !b.isBuildArgAllowed(arg) {
|
||||||
|
leftoverArgs = append(leftoverArgs, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(leftoverArgs) > 0 {
|
||||||
|
return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
|
||||||
|
}
|
||||||
|
|
||||||
if b.image == "" {
|
if b.image == "" {
|
||||||
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
|
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
|
||||||
}
|
}
|
||||||
|
@ -268,6 +285,18 @@ func (b *builder) readDockerfile() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// determine if build arg is part of built-in args or user
|
||||||
|
// defined args in Dockerfile at any point in time.
|
||||||
|
func (b *builder) isBuildArgAllowed(arg string) bool {
|
||||||
|
if _, ok := BuiltinAllowedBuildArgs[arg]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := b.allowedBuildArgs[arg]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// This method is the entrypoint to all statement handling routines.
|
// This method is the entrypoint to all statement handling routines.
|
||||||
//
|
//
|
||||||
// Almost all nodes will have this structure:
|
// Almost all nodes will have this structure:
|
||||||
|
@ -330,13 +359,34 @@ func (b *builder) dispatch(stepN int, ast *parser.Node) error {
|
||||||
msgList := make([]string, n)
|
msgList := make([]string, n)
|
||||||
|
|
||||||
var i int
|
var i int
|
||||||
|
// Append the build-time args to config-environment.
|
||||||
|
// This allows builder config to override the variables, making the behavior similar to
|
||||||
|
// a shell script i.e. `ENV foo bar` overrides value of `foo` passed in build
|
||||||
|
// context. But `ENV foo $foo` will use the value from build context if one
|
||||||
|
// isn't already been defined by a previous ENV primitive.
|
||||||
|
// Note, we get this behavior because we know that ProcessWord() will
|
||||||
|
// stop on the first occurrence of a variable name and not notice
|
||||||
|
// a subsequent one. So, putting the buildArgs list after the Config.Env
|
||||||
|
// list, in 'envs', is safe.
|
||||||
|
envs := b.Config.Env
|
||||||
|
for key, val := range b.buildArgs {
|
||||||
|
if !b.isBuildArgAllowed(key) {
|
||||||
|
// skip build-args that are not in allowed list, meaning they have
|
||||||
|
// not been defined by an "ARG" Dockerfile command yet.
|
||||||
|
// This is an error condition but only if there is no "ARG" in the entire
|
||||||
|
// Dockerfile, so we'll generate any necessary errors after we parsed
|
||||||
|
// the entire file (see 'leftoverArgs' processing in evaluator.go )
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
envs = append(envs, fmt.Sprintf("%s=%s", key, val))
|
||||||
|
}
|
||||||
for ast.Next != nil {
|
for ast.Next != nil {
|
||||||
ast = ast.Next
|
ast = ast.Next
|
||||||
var str string
|
var str string
|
||||||
str = ast.Value
|
str = ast.Value
|
||||||
if _, ok := replaceEnvAllowed[cmd]; ok {
|
if _, ok := replaceEnvAllowed[cmd]; ok {
|
||||||
var err error
|
var err error
|
||||||
str, err = ProcessWord(ast.Value, b.Config.Env)
|
str, err = ProcessWord(ast.Value, envs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,18 @@ var validCommitCommands = map[string]bool{
|
||||||
"workdir": true,
|
"workdir": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuiltinAllowedBuildArgs is list of built-in allowed build args
|
||||||
|
var BuiltinAllowedBuildArgs = map[string]bool{
|
||||||
|
"HTTP_PROXY": true,
|
||||||
|
"http_proxy": true,
|
||||||
|
"HTTPS_PROXY": true,
|
||||||
|
"https_proxy": true,
|
||||||
|
"FTP_PROXY": true,
|
||||||
|
"ftp_proxy": true,
|
||||||
|
"NO_PROXY": true,
|
||||||
|
"no_proxy": true,
|
||||||
|
}
|
||||||
|
|
||||||
// Config contains all configs for a build job
|
// Config contains all configs for a build job
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DockerfileName string
|
DockerfileName string
|
||||||
|
@ -66,6 +78,7 @@ type Config struct {
|
||||||
CgroupParent string
|
CgroupParent string
|
||||||
Ulimits []*ulimit.Ulimit
|
Ulimits []*ulimit.Ulimit
|
||||||
AuthConfigs map[string]cliconfig.AuthConfig
|
AuthConfigs map[string]cliconfig.AuthConfig
|
||||||
|
BuildArgs map[string]string
|
||||||
|
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Context io.ReadCloser
|
Context io.ReadCloser
|
||||||
|
@ -191,26 +204,28 @@ func Build(d *daemon.Daemon, buildConfig *Config) error {
|
||||||
Writer: buildConfig.Stdout,
|
Writer: buildConfig.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
},
|
},
|
||||||
Verbose: !buildConfig.SuppressOutput,
|
Verbose: !buildConfig.SuppressOutput,
|
||||||
UtilizeCache: !buildConfig.NoCache,
|
UtilizeCache: !buildConfig.NoCache,
|
||||||
Remove: buildConfig.Remove,
|
Remove: buildConfig.Remove,
|
||||||
ForceRemove: buildConfig.ForceRemove,
|
ForceRemove: buildConfig.ForceRemove,
|
||||||
Pull: buildConfig.Pull,
|
Pull: buildConfig.Pull,
|
||||||
OutOld: buildConfig.Stdout,
|
OutOld: buildConfig.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
AuthConfigs: buildConfig.AuthConfigs,
|
AuthConfigs: buildConfig.AuthConfigs,
|
||||||
dockerfileName: buildConfig.DockerfileName,
|
dockerfileName: buildConfig.DockerfileName,
|
||||||
cpuShares: buildConfig.CPUShares,
|
cpuShares: buildConfig.CPUShares,
|
||||||
cpuPeriod: buildConfig.CPUPeriod,
|
cpuPeriod: buildConfig.CPUPeriod,
|
||||||
cpuQuota: buildConfig.CPUQuota,
|
cpuQuota: buildConfig.CPUQuota,
|
||||||
cpuSetCpus: buildConfig.CPUSetCpus,
|
cpuSetCpus: buildConfig.CPUSetCpus,
|
||||||
cpuSetMems: buildConfig.CPUSetMems,
|
cpuSetMems: buildConfig.CPUSetMems,
|
||||||
cgroupParent: buildConfig.CgroupParent,
|
cgroupParent: buildConfig.CgroupParent,
|
||||||
memory: buildConfig.Memory,
|
memory: buildConfig.Memory,
|
||||||
memorySwap: buildConfig.MemorySwap,
|
memorySwap: buildConfig.MemorySwap,
|
||||||
ulimits: buildConfig.Ulimits,
|
ulimits: buildConfig.Ulimits,
|
||||||
cancelled: buildConfig.WaitCancelled(),
|
cancelled: buildConfig.WaitCancelled(),
|
||||||
id: stringid.GenerateRandomID(),
|
id: stringid.GenerateRandomID(),
|
||||||
|
buildArgs: buildConfig.BuildArgs,
|
||||||
|
allowedBuildArgs: make(map[string]bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
|
@ -42,15 +42,10 @@ func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
||||||
return &Node{Children: []*Node{child}}, nil, nil
|
return &Node{Children: []*Node{child}}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse environment like statements. Note that this does *not* handle
|
// helper to parse words (i.e space delimited or quoted strings) in a statement.
|
||||||
// variable interpolation, which will be handled in the evaluator.
|
// The quotes are preserved as part of this function and they are stripped later
|
||||||
func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
// as part of processWords().
|
||||||
// This is kind of tricky because we need to support the old
|
func parseWords(rest string) []string {
|
||||||
// variant: KEY name value
|
|
||||||
// as well as the new one: KEY name=value ...
|
|
||||||
// The trigger to know which one is being used will be whether we hit
|
|
||||||
// a space or = first. space ==> old, "=" ==> new
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
inSpaces = iota // looking for start of a word
|
inSpaces = iota // looking for start of a word
|
||||||
inWord
|
inWord
|
||||||
|
@ -89,15 +84,6 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
||||||
phase = inSpaces
|
phase = inSpaces
|
||||||
if blankOK || len(word) > 0 {
|
if blankOK || len(word) > 0 {
|
||||||
words = append(words, word)
|
words = append(words, word)
|
||||||
|
|
||||||
// Look for = and if not there assume
|
|
||||||
// we're doing the old stuff and
|
|
||||||
// just read the rest of the line
|
|
||||||
if !strings.Contains(word, "=") {
|
|
||||||
word = strings.TrimSpace(rest[pos:])
|
|
||||||
words = append(words, word)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
word = ""
|
word = ""
|
||||||
blankOK = false
|
blankOK = false
|
||||||
|
@ -141,13 +127,26 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return words
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse environment like statements. Note that this does *not* handle
|
||||||
|
// variable interpolation, which will be handled in the evaluator.
|
||||||
|
func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
||||||
|
// This is kind of tricky because we need to support the old
|
||||||
|
// variant: KEY name value
|
||||||
|
// as well as the new one: KEY name=value ...
|
||||||
|
// The trigger to know which one is being used will be whether we hit
|
||||||
|
// a space or = first. space ==> old, "=" ==> new
|
||||||
|
|
||||||
|
words := parseWords(rest)
|
||||||
if len(words) == 0 {
|
if len(words) == 0 {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old format (KEY name value)
|
|
||||||
var rootnode *Node
|
var rootnode *Node
|
||||||
|
|
||||||
|
// Old format (KEY name value)
|
||||||
if !strings.Contains(words[0], "=") {
|
if !strings.Contains(words[0], "=") {
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
rootnode = node
|
rootnode = node
|
||||||
|
@ -195,6 +194,38 @@ func parseLabel(rest string) (*Node, map[string]bool, error) {
|
||||||
return parseNameVal(rest, "LABEL")
|
return parseNameVal(rest, "LABEL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parses a statement containing one or more keyword definition(s) and/or
|
||||||
|
// value assignments, like `name1 name2= name3="" name4=value`.
|
||||||
|
// Note that this is a stricter format than the old format of assignment,
|
||||||
|
// allowed by parseNameVal(), in a way that this only allows assignment of the
|
||||||
|
// form `keyword=[<value>]` like `name2=`, `name3=""`, and `name4=value` above.
|
||||||
|
// In addition, a keyword definition alone is of the form `keyword` like `name1`
|
||||||
|
// above. And the assignments `name2=` and `name3=""` are equivalent and
|
||||||
|
// assign an empty value to the respective keywords.
|
||||||
|
func parseNameOrNameVal(rest string) (*Node, map[string]bool, error) {
|
||||||
|
words := parseWords(rest)
|
||||||
|
if len(words) == 0 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rootnode *Node
|
||||||
|
prevNode *Node
|
||||||
|
)
|
||||||
|
for i, word := range words {
|
||||||
|
node := &Node{}
|
||||||
|
node.Value = word
|
||||||
|
if i == 0 {
|
||||||
|
rootnode = node
|
||||||
|
} else {
|
||||||
|
prevNode.Next = node
|
||||||
|
}
|
||||||
|
prevNode = node
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootnode, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parses a whitespace-delimited set of arguments. The result is effectively a
|
// parses a whitespace-delimited set of arguments. The result is effectively a
|
||||||
// linked list of string arguments.
|
// linked list of string arguments.
|
||||||
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
||||||
|
|
|
@ -62,6 +62,7 @@ func init() {
|
||||||
command.Expose: parseStringsWhitespaceDelimited,
|
command.Expose: parseStringsWhitespaceDelimited,
|
||||||
command.Volume: parseMaybeJSONToList,
|
command.Volume: parseMaybeJSONToList,
|
||||||
command.StopSignal: parseString,
|
command.StopSignal: parseString,
|
||||||
|
command.Arg: parseNameOrNameVal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,3 +73,40 @@ func TestTestData(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseWords(t *testing.T) {
|
||||||
|
tests := []map[string][]string{
|
||||||
|
{
|
||||||
|
"input": {"foo"},
|
||||||
|
"expect": {"foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": {"foo bar"},
|
||||||
|
"expect": {"foo", "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": {"foo=bar"},
|
||||||
|
"expect": {"foo=bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": {"foo bar 'abc xyz'"},
|
||||||
|
"expect": {"foo", "bar", "'abc xyz'"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": {`foo bar "abc xyz"`},
|
||||||
|
"expect": {"foo", "bar", `"abc xyz"`},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
words := parseWords(test["input"][0])
|
||||||
|
if len(words) != len(test["expect"]) {
|
||||||
|
t.Fatalf("length check failed. input: %v, expect: %v, output: %v", test["input"][0], test["expect"], words)
|
||||||
|
}
|
||||||
|
for i, word := range words {
|
||||||
|
if word != test["expect"][i] {
|
||||||
|
t.Fatalf("word check failed for word: %q. input: %v, expect: %v, output: %v", word, test["input"][0], test["expect"], words)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ This section lists each version from latest to oldest. Each listing includes a
|
||||||
* `GET /containers/(id)/stats` will return networking information respectively for each interface.
|
* `GET /containers/(id)/stats` will return networking information respectively for each interface.
|
||||||
* The `hostConfig` option now accepts the field `DnsOptions`, which specifies a
|
* The `hostConfig` option now accepts the field `DnsOptions`, which specifies a
|
||||||
list of DNS options to be used in the container.
|
list of DNS options to be used in the container.
|
||||||
|
* `POST /build` now optionally takes a serialized map of build-time variables.
|
||||||
|
|
||||||
### v1.20 API changes
|
### v1.20 API changes
|
||||||
|
|
||||||
|
|
|
@ -1367,6 +1367,11 @@ Query Parameters:
|
||||||
- **memswap** - Total memory (memory + swap), `-1` to disable swap.
|
- **memswap** - Total memory (memory + swap), `-1` to disable swap.
|
||||||
- **cpushares** - CPU shares (relative weight).
|
- **cpushares** - CPU shares (relative weight).
|
||||||
- **cpusetcpus** - CPUs in which to allow execution (e.g., `0-3`, `0,1`).
|
- **cpusetcpus** - CPUs in which to allow execution (e.g., `0-3`, `0,1`).
|
||||||
|
- **buildargs** – JSON map of string pairs for build-time variables. Users pass
|
||||||
|
these values at build-time. Docker uses the `buildargs` as the environment
|
||||||
|
context for command(s) run via the Dockerfile's `RUN` instruction or for
|
||||||
|
variable expansion in other Dockerfile instructions. This is not meant for
|
||||||
|
passing secret values. [Read more about the buildargs instruction](/reference/builder/#arg)
|
||||||
|
|
||||||
Request Headers:
|
Request Headers:
|
||||||
|
|
||||||
|
|
|
@ -966,6 +966,128 @@ For example:
|
||||||
The output of the final `pwd` command in this `Dockerfile` would be
|
The output of the final `pwd` command in this `Dockerfile` would be
|
||||||
`/path/$DIRNAME`
|
`/path/$DIRNAME`
|
||||||
|
|
||||||
|
## ARG
|
||||||
|
|
||||||
|
ARG <name>[=<default value>]
|
||||||
|
|
||||||
|
The `ARG` instruction defines a variable that users can pass at build-time to
|
||||||
|
the builder with the `docker build` command using the `--build-arg
|
||||||
|
<varname>=<value>` flag. If a user specifies a build argument that was not
|
||||||
|
defined in the Dockerfile, the build outputs an error.
|
||||||
|
|
||||||
|
```
|
||||||
|
One or more build-args were not consumed, failing build.
|
||||||
|
```
|
||||||
|
|
||||||
|
The Dockerfile author can define a single variable by specifying `ARG` once or many
|
||||||
|
variables by specifying `ARG` more than once. For example, a valid Dockerfile:
|
||||||
|
|
||||||
|
```
|
||||||
|
FROM busybox
|
||||||
|
ARG user1
|
||||||
|
ARG buildno
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
A Dockerfile author may optionally specify a default value for an `ARG` instruction:
|
||||||
|
|
||||||
|
```
|
||||||
|
FROM busybox
|
||||||
|
ARG user1=someuser
|
||||||
|
ARG buildno=1
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
If an `ARG` value has a default and if there is no value passed at build-time, the
|
||||||
|
builder uses the default.
|
||||||
|
|
||||||
|
An `ARG` variable definition comes into effect from the line on which it is
|
||||||
|
defined in the `Dockerfile` not from the argument's use on the command-line or
|
||||||
|
elsewhere. For example, consider this Dockerfile:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 FROM busybox
|
||||||
|
2 USER ${user:-some_user}
|
||||||
|
3 ARG user
|
||||||
|
4 USER $user
|
||||||
|
...
|
||||||
|
```
|
||||||
|
A user builds this file by calling:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build --build-arg user=what_user Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
The `USER` at line 2 evaluates to `some_user` as the `user` variable is defined on the
|
||||||
|
subsequent line 3. The `USER` at line 4 evaluates to `what_user` as `user` is
|
||||||
|
defined and the `what_user` value was passed on the command line. Prior to its definition by an
|
||||||
|
`ARG` instruction, any use of a variable results in an empty string.
|
||||||
|
|
||||||
|
> **Note:** It is not recommended to use build-time variables for
|
||||||
|
> passing secrets like github keys, user credentials etc.
|
||||||
|
|
||||||
|
You can use an `ARG` or an `ENV` instruction to specify variables that are
|
||||||
|
available to the `RUN` instruction. Environment variables defined using the
|
||||||
|
`ENV` instruction always override an `ARG` instruction of the same name. Consider
|
||||||
|
this Dockerfile with an `ENV` and `ARG` instruction.
|
||||||
|
|
||||||
|
```
|
||||||
|
1 FROM ubuntu
|
||||||
|
2 ARG CONT_IMG_VER
|
||||||
|
3 ENV CONT_IMG_VER v1.0.0
|
||||||
|
4 RUN echo $CONT_IMG_VER
|
||||||
|
```
|
||||||
|
Then, assume this image is built with this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build --build-arg CONT_IMG_VER=v2.0.1 Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the `RUN` instruction uses `v1.0.0` instead of the `ARG` setting
|
||||||
|
passed by the user:`v2.0.1` This behavior is similar to a shell
|
||||||
|
script where a locally scoped variable overrides the variables passed as
|
||||||
|
arguments or inherited from environment, from its point of definition.
|
||||||
|
|
||||||
|
Using the example above but a different `ENV` specification you can create more
|
||||||
|
useful interactions between `ARG` and `ENV` instructions:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 FROM ubuntu
|
||||||
|
2 ARG CONT_IMG_VER
|
||||||
|
3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
|
||||||
|
4 RUN echo $CONT_IMG_VER
|
||||||
|
```
|
||||||
|
|
||||||
|
Unlike an `ARG` instruction, `ENV` values are always persisted in the built
|
||||||
|
image. Consider a docker build without the --build-arg flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
Using this Dockerfile example, `CONT_IMG_VER` is still persisted in the image but
|
||||||
|
its value would be `v1.0.0` as it is the default set in line 3 by the `ENV` instruction.
|
||||||
|
|
||||||
|
The variable expansion technique in this example allows you to pass arguments
|
||||||
|
from the command line and persist them in the final image by leveraging the
|
||||||
|
`ENV` instruction. Variable expansion is only supported for [a limited set of
|
||||||
|
Dockerfile instructions.](#environment-replacement)
|
||||||
|
|
||||||
|
Docker has a set of predefined `ARG` variables that you can use without a
|
||||||
|
corresponding `ARG` instruction in the Dockerfile.
|
||||||
|
|
||||||
|
* `HTTP_PROXY`
|
||||||
|
* `http_proxy`
|
||||||
|
* `HTTPS_PROXY`
|
||||||
|
* `https_proxy`
|
||||||
|
* `FTP_PROXY`
|
||||||
|
* `ftp_proxy`
|
||||||
|
* `NO_PROXY`
|
||||||
|
* `no_proxy`
|
||||||
|
|
||||||
|
To use these, simply pass them on the command line using the `--build-arg
|
||||||
|
<varname>=<value>` flag.
|
||||||
|
|
||||||
## ONBUILD
|
## ONBUILD
|
||||||
|
|
||||||
ONBUILD [INSTRUCTION]
|
ONBUILD [INSTRUCTION]
|
||||||
|
|
|
@ -17,6 +17,7 @@ weight=1
|
||||||
|
|
||||||
-f, --file="" Name of the Dockerfile (Default is 'PATH/Dockerfile')
|
-f, --file="" Name of the Dockerfile (Default is 'PATH/Dockerfile')
|
||||||
--force-rm=false Always remove intermediate containers
|
--force-rm=false Always remove intermediate containers
|
||||||
|
--build-arg=[] Set build-time variables
|
||||||
--no-cache=false Do not use cache when building the image
|
--no-cache=false Do not use cache when building the image
|
||||||
--pull=false Always attempt to pull a newer version of the image
|
--pull=false Always attempt to pull a newer version of the image
|
||||||
-q, --quiet=false Suppress the verbose output generated by the containers
|
-q, --quiet=false Suppress the verbose output generated by the containers
|
||||||
|
@ -251,3 +252,22 @@ flag](/reference/run/#specifying-custom-cgroups).
|
||||||
Using the `--ulimit` option with `docker build` will cause each build step's
|
Using the `--ulimit` option with `docker build` will cause each build step's
|
||||||
container to be started using those [`--ulimit`
|
container to be started using those [`--ulimit`
|
||||||
flag values](/reference/run/#setting-ulimits-in-a-container).
|
flag values](/reference/run/#setting-ulimits-in-a-container).
|
||||||
|
|
||||||
|
You can use `ENV` instructions in a Dockerfile to define variable
|
||||||
|
values. These values persist in the built image. However, often
|
||||||
|
persistence is not what you want. Users want to specify variables differently
|
||||||
|
depending on which host they build an image on.
|
||||||
|
|
||||||
|
A good example is `http_proxy` or source versions for pulling intermediate
|
||||||
|
files. The `ARG` instruction lets Dockerfile authors define values that users
|
||||||
|
can set at build-time using the `---build-arg` flag:
|
||||||
|
|
||||||
|
$ docker build --build-arg HTTP_PROXY=http://10.20.30.2:1234 .
|
||||||
|
|
||||||
|
This flag allows you to pass the build-time variables that are
|
||||||
|
accessed like regular environment variables in the `RUN` instruction of the
|
||||||
|
Dockerfile. Also, these values don't persist in the intermediate or final images
|
||||||
|
like `ENV` values do.
|
||||||
|
|
||||||
|
For detailed information on using `ARG` and `ENV` instructions, see the
|
||||||
|
[Dockerfile reference](/reference/builder).
|
||||||
|
|
|
@ -5676,3 +5676,507 @@ func (s *DockerSuite) TestBuildStopSignal(c *check.C) {
|
||||||
c.Fatalf("Signal %s, expected SIGKILL", res)
|
c.Fatalf("Signal %s, expected SIGKILL", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArg(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
RUN echo $%s
|
||||||
|
CMD echo $%s`, envKey, envKey, envKey)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || !strings.Contains(out, envVal) {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("failed to access environment variable in output: %q expected: %q", out, envVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := "bldargCont"
|
||||||
|
if out, _ := dockerCmd(c, "run", "--name", containerName, imgName); out != "\n" {
|
||||||
|
c.Fatalf("run produced invalid output: %q, expected empty string", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgHistory(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
envDef := "bar1"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s=%s`, envKey, envDef)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || !strings.Contains(out, envVal) {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("failed to access environment variable in output: %q expected: %q", out, envVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _ := dockerCmd(c, "history", "--no-trunc", imgName)
|
||||||
|
outputTabs := strings.Split(out, "\n")[1]
|
||||||
|
if !strings.Contains(outputTabs, envDef) {
|
||||||
|
c.Fatalf("failed to find arg default in image history output: %q expected: %q", outputTabs, envDef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgCacheHit(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
RUN echo $%s`, envKey, envKey)
|
||||||
|
|
||||||
|
origImgID := ""
|
||||||
|
var err error
|
||||||
|
if origImgID, err = buildImage(imgName, dockerfile, true, args...); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgNameCache := "bldargtestcachehit"
|
||||||
|
if newImgID, err := buildImage(imgNameCache, dockerfile, true, args...); err != nil || newImgID != origImgID {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
c.Fatalf("build didn't use cache! expected image id: %q built image id: %q", origImgID, newImgID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgCacheMissExtraArg(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
extraEnvKey := "foo1"
|
||||||
|
extraEnvVal := "bar1"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
ARG %s
|
||||||
|
RUN echo $%s`, envKey, extraEnvKey, envKey)
|
||||||
|
|
||||||
|
origImgID := ""
|
||||||
|
var err error
|
||||||
|
if origImgID, err = buildImage(imgName, dockerfile, true, args...); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgNameCache := "bldargtestcachemiss"
|
||||||
|
args = append(args, "--build-arg", fmt.Sprintf("%s=%s", extraEnvKey, extraEnvVal))
|
||||||
|
if newImgID, err := buildImage(imgNameCache, dockerfile, true, args...); err != nil || newImgID == origImgID {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
c.Fatalf("build used cache, expected a miss!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgCacheMissSameArgDiffVal(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
newEnvVal := "bar1"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
RUN echo $%s`, envKey, envKey)
|
||||||
|
|
||||||
|
origImgID := ""
|
||||||
|
var err error
|
||||||
|
if origImgID, err = buildImage(imgName, dockerfile, true, args...); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgNameCache := "bldargtestcachemiss"
|
||||||
|
args = []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, newEnvVal),
|
||||||
|
}
|
||||||
|
if newImgID, err := buildImage(imgNameCache, dockerfile, true, args...); err != nil || newImgID == origImgID {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
c.Fatalf("build used cache, expected a miss!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgOverrideArgDefinedBeforeEnv(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
envValOveride := "barOverride"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
ENV %s %s
|
||||||
|
RUN echo $%s
|
||||||
|
CMD echo $%s
|
||||||
|
`, envKey, envKey, envValOveride, envKey, envKey)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || strings.Count(out, envValOveride) != 2 {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("failed to access environment variable in output: %q expected: %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := "bldargCont"
|
||||||
|
if out, _ := dockerCmd(c, "run", "--name", containerName, imgName); !strings.Contains(out, envValOveride) {
|
||||||
|
c.Fatalf("run produced invalid output: %q, expected %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgOverrideEnvDefinedBeforeArg(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
envValOveride := "barOverride"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ENV %s %s
|
||||||
|
ARG %s
|
||||||
|
RUN echo $%s
|
||||||
|
CMD echo $%s
|
||||||
|
`, envKey, envValOveride, envKey, envKey, envKey)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || strings.Count(out, envValOveride) != 2 {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("failed to access environment variable in output: %q expected: %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := "bldargCont"
|
||||||
|
if out, _ := dockerCmd(c, "run", "--name", containerName, imgName); !strings.Contains(out, envValOveride) {
|
||||||
|
c.Fatalf("run produced invalid output: %q, expected %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgExpansion(c *check.C) {
|
||||||
|
imgName := "bldvarstest"
|
||||||
|
|
||||||
|
wdVar := "WDIR"
|
||||||
|
wdVal := "/tmp/"
|
||||||
|
addVar := "AFILE"
|
||||||
|
addVal := "addFile"
|
||||||
|
copyVar := "CFILE"
|
||||||
|
copyVal := "copyFile"
|
||||||
|
envVar := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
exposeVar := "EPORT"
|
||||||
|
exposeVal := "9999"
|
||||||
|
userVar := "USER"
|
||||||
|
userVal := "testUser"
|
||||||
|
volVar := "VOL"
|
||||||
|
volVal := "/testVol/"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", wdVar, wdVal),
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", addVar, addVal),
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", copyVar, copyVal),
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envVar, envVal),
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", exposeVar, exposeVal),
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", userVar, userVal),
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", volVar, volVal),
|
||||||
|
}
|
||||||
|
ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
WORKDIR ${%s}
|
||||||
|
ARG %s
|
||||||
|
ADD ${%s} testDir/
|
||||||
|
ARG %s
|
||||||
|
COPY $%s testDir/
|
||||||
|
ARG %s
|
||||||
|
ENV %s=${%s}
|
||||||
|
ARG %s
|
||||||
|
EXPOSE $%s
|
||||||
|
ARG %s
|
||||||
|
USER $%s
|
||||||
|
ARG %s
|
||||||
|
VOLUME ${%s}`,
|
||||||
|
wdVar, wdVar, addVar, addVar, copyVar, copyVar, envVar, envVar,
|
||||||
|
envVar, exposeVar, exposeVar, userVar, userVar, volVar, volVar),
|
||||||
|
map[string]string{
|
||||||
|
addVal: "some stuff",
|
||||||
|
copyVal: "some stuff",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
if _, err := buildImageFromContext(imgName, ctx, true, args...); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resMap map[string]interface{}
|
||||||
|
var resArr []string
|
||||||
|
res := ""
|
||||||
|
res, err = inspectField(imgName, "Config.WorkingDir")
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
if res != wdVal {
|
||||||
|
c.Fatalf("Config.WorkingDir value mismatch. Expected: %s, got: %s", wdVal, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = inspectFieldAndMarshall(imgName, "Config.Env", &resArr)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, v := range resArr {
|
||||||
|
if fmt.Sprintf("%s=%s", envVar, envVal) == v {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
c.Fatalf("Config.Env value mismatch. Expected <key=value> to exist: %s=%s, got: %v",
|
||||||
|
envVar, envVal, resArr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = inspectFieldAndMarshall(imgName, "Config.ExposedPorts", &resMap)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := resMap[fmt.Sprintf("%s/tcp", exposeVal)]; !ok {
|
||||||
|
c.Fatalf("Config.ExposedPorts value mismatch. Expected exposed port: %s/tcp, got: %v", exposeVal, resMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = inspectField(imgName, "Config.User")
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
if res != userVal {
|
||||||
|
c.Fatalf("Config.User value mismatch. Expected: %s, got: %s", userVal, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = inspectFieldAndMarshall(imgName, "Config.Volumes", &resMap)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := resMap[volVal]; !ok {
|
||||||
|
c.Fatalf("Config.Volumes value mismatch. Expected volume: %s, got: %v", volVal, resMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgExpansionOverride(c *check.C) {
|
||||||
|
imgName := "bldvarstest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
envKey1 := "foo1"
|
||||||
|
envValOveride := "barOverride"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
ENV %s %s
|
||||||
|
ENV %s ${%s}
|
||||||
|
RUN echo $%s
|
||||||
|
CMD echo $%s`, envKey, envKey, envValOveride, envKey1, envKey, envKey1, envKey1)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || strings.Count(out, envValOveride) != 2 {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("failed to access environment variable in output: %q expected: %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := "bldargCont"
|
||||||
|
if out, _ := dockerCmd(c, "run", "--name", containerName, imgName); !strings.Contains(out, envValOveride) {
|
||||||
|
c.Fatalf("run produced invalid output: %q, expected %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgUntrustedDefinedAfterUse(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
RUN echo $%s
|
||||||
|
ARG %s
|
||||||
|
CMD echo $%s`, envKey, envKey, envKey)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || strings.Contains(out, envVal) {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("able to access environment variable in output: %q expected to be missing", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := "bldargCont"
|
||||||
|
if out, _ := dockerCmd(c, "run", "--name", containerName, imgName); out != "\n" {
|
||||||
|
c.Fatalf("run produced invalid output: %q, expected empty string", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgBuiltinArg(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "HTTP_PROXY"
|
||||||
|
envVal := "bar"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
RUN echo $%s
|
||||||
|
CMD echo $%s`, envKey, envKey)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || !strings.Contains(out, envVal) {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("failed to access environment variable in output: %q expected: %q", out, envVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := "bldargCont"
|
||||||
|
if out, _ := dockerCmd(c, "run", "--name", containerName, imgName); out != "\n" {
|
||||||
|
c.Fatalf("run produced invalid output: %q, expected empty string", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgDefaultOverride(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
envValOveride := "barOverride"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envValOveride),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s=%s
|
||||||
|
ENV %s $%s
|
||||||
|
RUN echo $%s
|
||||||
|
CMD echo $%s`, envKey, envVal, envKey, envKey, envKey, envKey)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || strings.Count(out, envValOveride) != 1 {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("failed to access environment variable in output: %q expected: %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerName := "bldargCont"
|
||||||
|
if out, _ := dockerCmd(c, "run", "--name", containerName, imgName); !strings.Contains(out, envValOveride) {
|
||||||
|
c.Fatalf("run produced invalid output: %q, expected %q", out, envValOveride)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgMultiArgsSameLine(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envKey1 := "foo1"
|
||||||
|
args := []string{}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s %s`, envKey, envKey1)
|
||||||
|
|
||||||
|
errStr := "ARG requires exactly one argument definition"
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err == nil {
|
||||||
|
c.Fatalf("build succeeded, expected to fail. Output: %v", out)
|
||||||
|
} else if !strings.Contains(out, errStr) {
|
||||||
|
c.Fatalf("Unexpected error. output: %q, expected error: %q", out, errStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgUnconsumedArg(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envVal := "bar"
|
||||||
|
args := []string{
|
||||||
|
"--build-arg", fmt.Sprintf("%s=%s", envKey, envVal),
|
||||||
|
}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
RUN echo $%s
|
||||||
|
CMD echo $%s`, envKey, envKey)
|
||||||
|
|
||||||
|
errStr := "One or more build-args"
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err == nil {
|
||||||
|
c.Fatalf("build succeeded, expected to fail. Output: %v", out)
|
||||||
|
} else if !strings.Contains(out, errStr) {
|
||||||
|
c.Fatalf("Unexpected error. output: %q, expected error: %q", out, errStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgQuotedValVariants(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envKey1 := "foo1"
|
||||||
|
envKey2 := "foo2"
|
||||||
|
envKey3 := "foo3"
|
||||||
|
args := []string{}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s=""
|
||||||
|
ARG %s=''
|
||||||
|
ARG %s="''"
|
||||||
|
ARG %s='""'
|
||||||
|
RUN [ "$%s" != "$%s" ]
|
||||||
|
RUN [ "$%s" != "$%s" ]
|
||||||
|
RUN [ "$%s" != "$%s" ]
|
||||||
|
RUN [ "$%s" != "$%s" ]
|
||||||
|
RUN [ "$%s" != "$%s" ]`, envKey, envKey1, envKey2, envKey3,
|
||||||
|
envKey, envKey2, envKey, envKey3, envKey1, envKey2, envKey1, envKey3,
|
||||||
|
envKey2, envKey3)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgEmptyValVariants(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
envKey1 := "foo1"
|
||||||
|
envKey2 := "foo2"
|
||||||
|
args := []string{}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s=
|
||||||
|
ARG %s=""
|
||||||
|
ARG %s=''
|
||||||
|
RUN [ "$%s" == "$%s" ]
|
||||||
|
RUN [ "$%s" == "$%s" ]
|
||||||
|
RUN [ "$%s" == "$%s" ]`, envKey, envKey1, envKey2, envKey, envKey1, envKey1, envKey2, envKey, envKey2)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestBuildBuildTimeArgDefintionWithNoEnvInjection(c *check.C) {
|
||||||
|
imgName := "bldargtest"
|
||||||
|
envKey := "foo"
|
||||||
|
args := []string{}
|
||||||
|
dockerfile := fmt.Sprintf(`FROM busybox
|
||||||
|
ARG %s
|
||||||
|
RUN env`, envKey)
|
||||||
|
|
||||||
|
if _, out, err := buildImageWithOut(imgName, dockerfile, true, args...); err != nil || strings.Count(out, envKey) != 1 {
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("build failed to complete: %q %q", out, err)
|
||||||
|
}
|
||||||
|
c.Fatalf("unexpected number of occurrences of the arg in output: %q expected: 1", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1025,11 +1025,12 @@ func getContainerState(c *check.C, id string) (int, bool, error) {
|
||||||
return exitStatus, running, nil
|
return exitStatus, running, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImageCmd(name, dockerfile string, useCache bool) *exec.Cmd {
|
func buildImageCmd(name, dockerfile string, useCache bool, buildFlags ...string) *exec.Cmd {
|
||||||
args := []string{"-D", "build", "-t", name}
|
args := []string{"-D", "build", "-t", name}
|
||||||
if !useCache {
|
if !useCache {
|
||||||
args = append(args, "--no-cache")
|
args = append(args, "--no-cache")
|
||||||
}
|
}
|
||||||
|
args = append(args, buildFlags...)
|
||||||
args = append(args, "-")
|
args = append(args, "-")
|
||||||
buildCmd := exec.Command(dockerBinary, args...)
|
buildCmd := exec.Command(dockerBinary, args...)
|
||||||
buildCmd.Stdin = strings.NewReader(dockerfile)
|
buildCmd.Stdin = strings.NewReader(dockerfile)
|
||||||
|
@ -1037,8 +1038,8 @@ func buildImageCmd(name, dockerfile string, useCache bool) *exec.Cmd {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImageWithOut(name, dockerfile string, useCache bool) (string, string, error) {
|
func buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, error) {
|
||||||
buildCmd := buildImageCmd(name, dockerfile, useCache)
|
buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...)
|
||||||
out, exitCode, err := runCommandWithOutput(buildCmd)
|
out, exitCode, err := runCommandWithOutput(buildCmd)
|
||||||
if err != nil || exitCode != 0 {
|
if err != nil || exitCode != 0 {
|
||||||
return "", out, fmt.Errorf("failed to build the image: %s", out)
|
return "", out, fmt.Errorf("failed to build the image: %s", out)
|
||||||
|
@ -1050,8 +1051,8 @@ func buildImageWithOut(name, dockerfile string, useCache bool) (string, string,
|
||||||
return id, out, nil
|
return id, out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImageWithStdoutStderr(name, dockerfile string, useCache bool) (string, string, string, error) {
|
func buildImageWithStdoutStderr(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, string, error) {
|
||||||
buildCmd := buildImageCmd(name, dockerfile, useCache)
|
buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...)
|
||||||
stdout, stderr, exitCode, err := runCommandWithStdoutStderr(buildCmd)
|
stdout, stderr, exitCode, err := runCommandWithStdoutStderr(buildCmd)
|
||||||
if err != nil || exitCode != 0 {
|
if err != nil || exitCode != 0 {
|
||||||
return "", stdout, stderr, fmt.Errorf("failed to build the image: %s", stdout)
|
return "", stdout, stderr, fmt.Errorf("failed to build the image: %s", stdout)
|
||||||
|
@ -1063,16 +1064,17 @@ func buildImageWithStdoutStderr(name, dockerfile string, useCache bool) (string,
|
||||||
return id, stdout, stderr, nil
|
return id, stdout, stderr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImage(name, dockerfile string, useCache bool) (string, error) {
|
func buildImage(name, dockerfile string, useCache bool, buildFlags ...string) (string, error) {
|
||||||
id, _, err := buildImageWithOut(name, dockerfile, useCache)
|
id, _, err := buildImageWithOut(name, dockerfile, useCache, buildFlags...)
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImageFromContext(name string, ctx *FakeContext, useCache bool) (string, error) {
|
func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, error) {
|
||||||
args := []string{"build", "-t", name}
|
args := []string{"build", "-t", name}
|
||||||
if !useCache {
|
if !useCache {
|
||||||
args = append(args, "--no-cache")
|
args = append(args, "--no-cache")
|
||||||
}
|
}
|
||||||
|
args = append(args, buildFlags...)
|
||||||
args = append(args, ".")
|
args = append(args, ".")
|
||||||
buildCmd := exec.Command(dockerBinary, args...)
|
buildCmd := exec.Command(dockerBinary, args...)
|
||||||
buildCmd.Dir = ctx.Dir
|
buildCmd.Dir = ctx.Dir
|
||||||
|
@ -1083,11 +1085,12 @@ func buildImageFromContext(name string, ctx *FakeContext, useCache bool) (string
|
||||||
return getIDByName(name)
|
return getIDByName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImageFromPath(name, path string, useCache bool) (string, error) {
|
func buildImageFromPath(name, path string, useCache bool, buildFlags ...string) (string, error) {
|
||||||
args := []string{"build", "-t", name}
|
args := []string{"build", "-t", name}
|
||||||
if !useCache {
|
if !useCache {
|
||||||
args = append(args, "--no-cache")
|
args = append(args, "--no-cache")
|
||||||
}
|
}
|
||||||
|
args = append(args, buildFlags...)
|
||||||
args = append(args, path)
|
args = append(args, path)
|
||||||
buildCmd := exec.Command(dockerBinary, args...)
|
buildCmd := exec.Command(dockerBinary, args...)
|
||||||
out, exitCode, err := runCommandWithOutput(buildCmd)
|
out, exitCode, err := runCommandWithOutput(buildCmd)
|
||||||
|
|
|
@ -317,6 +317,127 @@ A Dockerfile is similar to a Makefile.
|
||||||
|
|
||||||
In the above example, the output of the **pwd** command is **a/b/c**.
|
In the above example, the output of the **pwd** command is **a/b/c**.
|
||||||
|
|
||||||
|
**ARG**
|
||||||
|
-- ARG <name>[=<default value>]
|
||||||
|
|
||||||
|
The `ARG` instruction defines a variable that users can pass at build-time to
|
||||||
|
the builder with the `docker build` command using the `--build-arg
|
||||||
|
<varname>=<value>` flag. If a user specifies a build argument that was not
|
||||||
|
defined in the Dockerfile, the build outputs an error.
|
||||||
|
|
||||||
|
```
|
||||||
|
One or more build-args were not consumed, failing build.
|
||||||
|
```
|
||||||
|
|
||||||
|
The Dockerfile author can define a single variable by specifying `ARG` once or many
|
||||||
|
variables by specifying `ARG` more than once. For example, a valid Dockerfile:
|
||||||
|
|
||||||
|
```
|
||||||
|
FROM busybox
|
||||||
|
ARG user1
|
||||||
|
ARG buildno
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
A Dockerfile author may optionally specify a default value for an `ARG` instruction:
|
||||||
|
|
||||||
|
```
|
||||||
|
FROM busybox
|
||||||
|
ARG user1=someuser
|
||||||
|
ARG buildno=1
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
If an `ARG` value has a default and if there is no value passed at build-time, the
|
||||||
|
builder uses the default.
|
||||||
|
|
||||||
|
An `ARG` variable definition comes into effect from the line on which it is
|
||||||
|
defined in the `Dockerfile` not from the argument's use on the command-line or
|
||||||
|
elsewhere. For example, consider this Dockerfile:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 FROM busybox
|
||||||
|
2 USER ${user:-some_user}
|
||||||
|
3 ARG user
|
||||||
|
4 USER $user
|
||||||
|
...
|
||||||
|
```
|
||||||
|
A user builds this file by calling:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build --build-arg user=what_user Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
The `USER` at line 2 evaluates to `some_user` as the `user` variable is defined on the
|
||||||
|
subsequent line 3. The `USER` at line 4 evaluates to `what_user` as `user` is
|
||||||
|
defined and the `what_user` value was passed on the command line. Prior to its definition by an
|
||||||
|
`ARG` instruction, any use of a variable results in an empty string.
|
||||||
|
|
||||||
|
> **Note:** It is not recommended to use build-time variables for
|
||||||
|
> passing secrets like github keys, user credentials etc.
|
||||||
|
|
||||||
|
You can use an `ARG` or an `ENV` instruction to specify variables that are
|
||||||
|
available to the `RUN` instruction. Environment variables defined using the
|
||||||
|
`ENV` instruction always override an `ARG` instruction of the same name. Consider
|
||||||
|
this Dockerfile with an `ENV` and `ARG` instruction.
|
||||||
|
|
||||||
|
```
|
||||||
|
1 FROM ubuntu
|
||||||
|
2 ARG CONT_IMG_VER
|
||||||
|
3 ENV CONT_IMG_VER v1.0.0
|
||||||
|
4 RUN echo $CONT_IMG_VER
|
||||||
|
```
|
||||||
|
Then, assume this image is built with this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build --build-arg CONT_IMG_VER=v2.0.1 Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the `RUN` instruction uses `v1.0.0` instead of the `ARG` setting
|
||||||
|
passed by the user:`v2.0.1` This behavior is similar to a shell
|
||||||
|
script where a locally scoped variable overrides the variables passed as
|
||||||
|
arguments or inherited from environment, from its point of definition.
|
||||||
|
|
||||||
|
Using the example above but a different `ENV` specification you can create more
|
||||||
|
useful interactions between `ARG` and `ENV` instructions:
|
||||||
|
|
||||||
|
```
|
||||||
|
1 FROM ubuntu
|
||||||
|
2 ARG CONT_IMG_VER
|
||||||
|
3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
|
||||||
|
4 RUN echo $CONT_IMG_VER
|
||||||
|
```
|
||||||
|
|
||||||
|
Unlike an `ARG` instruction, `ENV` values are always persisted in the built
|
||||||
|
image. Consider a docker build without the --build-arg flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
Using this Dockerfile example, `CONT_IMG_VER` is still persisted in the image but
|
||||||
|
its value would be `v1.0.0` as it is the default set in line 3 by the `ENV` instruction.
|
||||||
|
|
||||||
|
The variable expansion technique in this example allows you to pass arguments
|
||||||
|
from the command line and persist them in the final image by leveraging the
|
||||||
|
`ENV` instruction. Variable expansion is only supported for [a limited set of
|
||||||
|
Dockerfile instructions.](#environment-replacement)
|
||||||
|
|
||||||
|
Docker has a set of predefined `ARG` variables that you can use without a
|
||||||
|
corresponding `ARG` instruction in the Dockerfile.
|
||||||
|
|
||||||
|
* `HTTP_PROXY`
|
||||||
|
* `http_proxy`
|
||||||
|
* `HTTPS_PROXY`
|
||||||
|
* `https_proxy`
|
||||||
|
* `FTP_PROXY`
|
||||||
|
* `ftp_proxy`
|
||||||
|
* `NO_PROXY`
|
||||||
|
* `no_proxy`
|
||||||
|
|
||||||
|
To use these, simply pass them on the command line using the `--build-arg
|
||||||
|
<varname>=<value>` flag.
|
||||||
|
|
||||||
**ONBUILD**
|
**ONBUILD**
|
||||||
-- `ONBUILD [INSTRUCTION]`
|
-- `ONBUILD [INSTRUCTION]`
|
||||||
The **ONBUILD** instruction adds a trigger instruction to an image. The
|
The **ONBUILD** instruction adds a trigger instruction to an image. The
|
||||||
|
|
|
@ -8,6 +8,7 @@ docker-build - Build a new image from the source code at PATH
|
||||||
**docker build**
|
**docker build**
|
||||||
[**--help**]
|
[**--help**]
|
||||||
[**-f**|**--file**[=*PATH/Dockerfile*]]
|
[**-f**|**--file**[=*PATH/Dockerfile*]]
|
||||||
|
[**--build-arg**[=*[]*]]
|
||||||
[**--force-rm**[=*false*]]
|
[**--force-rm**[=*false*]]
|
||||||
[**--no-cache**[=*false*]]
|
[**--no-cache**[=*false*]]
|
||||||
[**--pull**[=*false*]]
|
[**--pull**[=*false*]]
|
||||||
|
@ -51,6 +52,17 @@ cloned locally and then sent as the context.
|
||||||
the remote context. In all cases, the file must be within the build context.
|
the remote context. In all cases, the file must be within the build context.
|
||||||
The default is *Dockerfile*.
|
The default is *Dockerfile*.
|
||||||
|
|
||||||
|
**--build-arg**=*variable*
|
||||||
|
name and value of a **buildarg**.
|
||||||
|
|
||||||
|
For example, if you want to pass a value for `http_proxy`, use
|
||||||
|
`--bulid-arg=http_proxy="http://some.proxy.url"`
|
||||||
|
|
||||||
|
Users pass these values at build-time. Docker uses the `buildargs` as the
|
||||||
|
environment context for command(s) run via the Dockerfile's `RUN` instruction
|
||||||
|
or for variable expansion in other Dockerfile instructions. This is not meant
|
||||||
|
for passing secret values. [Read more about the buildargs instruction](/reference/builder/#arg)
|
||||||
|
|
||||||
**--force-rm**=*true*|*false*
|
**--force-rm**=*true*|*false*
|
||||||
Always remove intermediate containers, even after unsuccessful builds. The default is *false*.
|
Always remove intermediate containers, even after unsuccessful builds. The default is *false*.
|
||||||
|
|
||||||
|
|
|
@ -324,7 +324,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||||
MacAddress: *flMacAddress,
|
MacAddress: *flMacAddress,
|
||||||
Entrypoint: entrypoint,
|
Entrypoint: entrypoint,
|
||||||
WorkingDir: *flWorkingDir,
|
WorkingDir: *flWorkingDir,
|
||||||
Labels: convertKVStringsToMap(labels),
|
Labels: ConvertKVStringsToMap(labels),
|
||||||
StopSignal: *flStopSignal,
|
StopSignal: *flStopSignal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,8 +394,8 @@ func readKVStrings(files []string, override []string) ([]string, error) {
|
||||||
return envVariables, nil
|
return envVariables, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// converts ["key=value"] to {"key":"value"}
|
// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
|
||||||
func convertKVStringsToMap(values []string) map[string]string {
|
func ConvertKVStringsToMap(values []string) map[string]string {
|
||||||
result := make(map[string]string, len(values))
|
result := make(map[string]string, len(values))
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
kv := strings.SplitN(value, "=", 2)
|
kv := strings.SplitN(value, "=", 2)
|
||||||
|
@ -410,7 +410,7 @@ func convertKVStringsToMap(values []string) map[string]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
|
func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
|
||||||
loggingOptsMap := convertKVStringsToMap(loggingOpts)
|
loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
|
||||||
if loggingDriver == "none" && len(loggingOpts) > 0 {
|
if loggingDriver == "none" && len(loggingOpts) > 0 {
|
||||||
return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
|
return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue