Merge pull request #31352 from dnephin/allow-arg-in-fromt
[dockerfile] Allow ARG in FROM
This commit is contained in:
commit
4d9e32a08e
12 changed files with 453 additions and 162 deletions
124
builder/dockerfile/buildargs.go
Normal file
124
builder/dockerfile/buildargs.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package dockerfile
|
||||
|
||||
// builtinAllowedBuildArgs is list of built-in allowed build args
|
||||
// these args are considered transparent and are excluded from the image history.
|
||||
// Filtering from history is implemented in dispatchers.go
|
||||
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,
|
||||
}
|
||||
|
||||
// buildArgs manages arguments used by the builder
|
||||
type buildArgs struct {
|
||||
// args that are allowed for expansion/substitution and passing to commands in 'run'.
|
||||
allowedBuildArgs map[string]*string
|
||||
// args defined before the first `FROM` in a Dockerfile
|
||||
allowedMetaArgs map[string]*string
|
||||
// args referenced by the Dockerfile
|
||||
referencedArgs map[string]struct{}
|
||||
// args provided by the user on the command line
|
||||
argsFromOptions map[string]*string
|
||||
}
|
||||
|
||||
func newBuildArgs(argsFromOptions map[string]*string) *buildArgs {
|
||||
return &buildArgs{
|
||||
allowedBuildArgs: make(map[string]*string),
|
||||
allowedMetaArgs: make(map[string]*string),
|
||||
referencedArgs: make(map[string]struct{}),
|
||||
argsFromOptions: argsFromOptions,
|
||||
}
|
||||
}
|
||||
|
||||
// UnreferencedOptionArgs returns the list of args that were set from options but
|
||||
// were never referenced from the Dockerfile
|
||||
func (b *buildArgs) UnreferencedOptionArgs() []string {
|
||||
leftoverArgs := []string{}
|
||||
for arg := range b.argsFromOptions {
|
||||
if _, ok := b.referencedArgs[arg]; !ok {
|
||||
leftoverArgs = append(leftoverArgs, arg)
|
||||
}
|
||||
}
|
||||
return leftoverArgs
|
||||
}
|
||||
|
||||
// ResetAllowed clears the list of args that are allowed to be used by a
|
||||
// directive
|
||||
func (b *buildArgs) ResetAllowed() {
|
||||
b.allowedBuildArgs = make(map[string]*string)
|
||||
}
|
||||
|
||||
// AddMetaArg adds a new meta arg that can be used by FROM directives
|
||||
func (b *buildArgs) AddMetaArg(key string, value *string) {
|
||||
b.allowedMetaArgs[key] = value
|
||||
}
|
||||
|
||||
// AddArg adds a new arg that can be used by directives
|
||||
func (b *buildArgs) AddArg(key string, value *string) {
|
||||
b.allowedBuildArgs[key] = value
|
||||
b.referencedArgs[key] = struct{}{}
|
||||
}
|
||||
|
||||
// IsUnreferencedBuiltin checks if the key is a built-in arg, or if it has been
|
||||
// referenced by the Dockerfile. Returns true if the arg is a builtin that has
|
||||
// not been referenced in the Dockerfile.
|
||||
func (b *buildArgs) IsUnreferencedBuiltin(key string) bool {
|
||||
_, isBuiltin := builtinAllowedBuildArgs[key]
|
||||
_, isAllowed := b.allowedBuildArgs[key]
|
||||
return isBuiltin && !isAllowed
|
||||
}
|
||||
|
||||
// GetAllAllowed returns a mapping with all the allowed args
|
||||
func (b *buildArgs) GetAllAllowed() map[string]string {
|
||||
return b.getAllFromMapping(b.allowedBuildArgs)
|
||||
}
|
||||
|
||||
// GetAllMeta returns a mapping with all the meta meta args
|
||||
func (b *buildArgs) GetAllMeta() map[string]string {
|
||||
return b.getAllFromMapping(b.allowedMetaArgs)
|
||||
}
|
||||
|
||||
func (b *buildArgs) getAllFromMapping(source map[string]*string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
|
||||
keys := keysFromMaps(source, builtinAllowedBuildArgs)
|
||||
for _, key := range keys {
|
||||
v, ok := b.getBuildArg(key, source)
|
||||
if ok {
|
||||
m[key] = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (b *buildArgs) getBuildArg(key string, mapping map[string]*string) (string, bool) {
|
||||
defaultValue, exists := mapping[key]
|
||||
// Return override from options if one is defined
|
||||
if v, ok := b.argsFromOptions[key]; ok && v != nil {
|
||||
return *v, ok
|
||||
}
|
||||
|
||||
if defaultValue == nil {
|
||||
if v, ok := b.allowedMetaArgs[key]; ok && v != nil {
|
||||
return *v, ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
return *defaultValue, exists
|
||||
}
|
||||
|
||||
func keysFromMaps(source map[string]*string, builtin map[string]bool) []string {
|
||||
keys := []string{}
|
||||
for key := range source {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
for key := range builtin {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
63
builder/dockerfile/buildargs_test.go
Normal file
63
builder/dockerfile/buildargs_test.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package dockerfile
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func strPtr(source string) *string {
|
||||
return &source
|
||||
}
|
||||
|
||||
func TestGetAllAllowed(t *testing.T) {
|
||||
buildArgs := newBuildArgs(map[string]*string{
|
||||
"ArgNotUsedInDockerfile": strPtr("fromopt1"),
|
||||
"ArgOverriddenByOptions": strPtr("fromopt2"),
|
||||
"ArgNoDefaultInDockerfileFromOptions": strPtr("fromopt3"),
|
||||
"HTTP_PROXY": strPtr("theproxy"),
|
||||
})
|
||||
|
||||
buildArgs.AddMetaArg("ArgFromMeta", strPtr("frommeta1"))
|
||||
buildArgs.AddMetaArg("ArgFromMetaOverriden", strPtr("frommeta2"))
|
||||
buildArgs.AddMetaArg("ArgFromMetaNotUsed", strPtr("frommeta3"))
|
||||
|
||||
buildArgs.AddArg("ArgOverriddenByOptions", strPtr("fromdockerfile2"))
|
||||
buildArgs.AddArg("ArgWithDefaultInDockerfile", strPtr("fromdockerfile1"))
|
||||
buildArgs.AddArg("ArgNoDefaultInDockerfile", nil)
|
||||
buildArgs.AddArg("ArgNoDefaultInDockerfileFromOptions", nil)
|
||||
buildArgs.AddArg("ArgFromMeta", nil)
|
||||
buildArgs.AddArg("ArgFromMetaOverriden", strPtr("fromdockerfile3"))
|
||||
|
||||
all := buildArgs.GetAllAllowed()
|
||||
expected := map[string]string{
|
||||
"HTTP_PROXY": "theproxy",
|
||||
"ArgOverriddenByOptions": "fromopt2",
|
||||
"ArgWithDefaultInDockerfile": "fromdockerfile1",
|
||||
"ArgNoDefaultInDockerfileFromOptions": "fromopt3",
|
||||
"ArgFromMeta": "frommeta1",
|
||||
"ArgFromMetaOverriden": "fromdockerfile3",
|
||||
}
|
||||
assert.DeepEqual(t, all, expected)
|
||||
}
|
||||
|
||||
func TestGetAllMeta(t *testing.T) {
|
||||
buildArgs := newBuildArgs(map[string]*string{
|
||||
"ArgNotUsedInDockerfile": strPtr("fromopt1"),
|
||||
"ArgOverriddenByOptions": strPtr("fromopt2"),
|
||||
"ArgNoDefaultInMetaFromOptions": strPtr("fromopt3"),
|
||||
"HTTP_PROXY": strPtr("theproxy"),
|
||||
})
|
||||
|
||||
buildArgs.AddMetaArg("ArgFromMeta", strPtr("frommeta1"))
|
||||
buildArgs.AddMetaArg("ArgOverriddenByOptions", strPtr("frommeta2"))
|
||||
buildArgs.AddMetaArg("ArgNoDefaultInMetaFromOptions", nil)
|
||||
|
||||
all := buildArgs.GetAllMeta()
|
||||
expected := map[string]string{
|
||||
"HTTP_PROXY": "theproxy",
|
||||
"ArgFromMeta": "frommeta1",
|
||||
"ArgOverriddenByOptions": "fromopt2",
|
||||
"ArgNoDefaultInMetaFromOptions": "fromopt3",
|
||||
}
|
||||
assert.DeepEqual(t, all, expected)
|
||||
}
|
|
@ -36,20 +36,6 @@ var validCommitCommands = map[string]bool{
|
|||
"workdir": true,
|
||||
}
|
||||
|
||||
// BuiltinAllowedBuildArgs is list of built-in allowed build args
|
||||
// these args are considered transparent and are excluded from the image history.
|
||||
// Filtering from history is implemented in dispatchers.go
|
||||
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,
|
||||
}
|
||||
|
||||
var defaultLogConfig = container.LogConfig{Type: "none"}
|
||||
|
||||
// Builder is a Dockerfile builder
|
||||
|
@ -66,20 +52,19 @@ type Builder struct {
|
|||
clientCtx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
dockerfile *parser.Node
|
||||
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
|
||||
maintainer string
|
||||
cmdSet bool
|
||||
disableCommit bool
|
||||
cacheBusted bool
|
||||
allowedBuildArgs map[string]*string // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
|
||||
allBuildArgs map[string]struct{} // list of all build-time args found during parsing of the Dockerfile
|
||||
directive parser.Directive
|
||||
dockerfile *parser.Node
|
||||
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
|
||||
directive parser.Directive
|
||||
|
||||
// TODO: remove once docker.Commit can receive a tag
|
||||
id string
|
||||
|
@ -134,18 +119,17 @@ func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, back
|
|||
}
|
||||
ctx, cancel := context.WithCancel(clientCtx)
|
||||
b = &Builder{
|
||||
clientCtx: ctx,
|
||||
cancel: cancel,
|
||||
options: config,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
docker: backend,
|
||||
context: buildContext,
|
||||
runConfig: new(container.Config),
|
||||
tmpContainers: map[string]struct{}{},
|
||||
id: stringid.GenerateNonCryptoID(),
|
||||
allowedBuildArgs: make(map[string]*string),
|
||||
allBuildArgs: make(map[string]struct{}),
|
||||
clientCtx: ctx,
|
||||
cancel: cancel,
|
||||
options: config,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
docker: backend,
|
||||
context: buildContext,
|
||||
runConfig: new(container.Config),
|
||||
tmpContainers: map[string]struct{}{},
|
||||
id: stringid.GenerateNonCryptoID(),
|
||||
buildArgs: newBuildArgs(config.BuildArgs),
|
||||
directive: parser.Directive{
|
||||
EscapeSeen: false,
|
||||
LookingForDirectives: true,
|
||||
|
@ -316,18 +300,17 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
|
|||
// check if there are any leftover build-args that were passed but not
|
||||
// consumed during build. Print a warning, if there are any.
|
||||
func (b *Builder) warnOnUnusedBuildArgs() {
|
||||
leftoverArgs := []string{}
|
||||
for arg := range b.options.BuildArgs {
|
||||
if _, ok := b.allBuildArgs[arg]; !ok {
|
||||
leftoverArgs = append(leftoverArgs, arg)
|
||||
}
|
||||
}
|
||||
|
||||
leftoverArgs := b.buildArgs.UnreferencedOptionArgs()
|
||||
if len(leftoverArgs) > 0 {
|
||||
fmt.Fprintf(b.Stderr, "[Warning] One or more build-args %v were not consumed\n", leftoverArgs)
|
||||
}
|
||||
}
|
||||
|
||||
// hasFromImage returns true if the builder has processed a `FROM <image>` line
|
||||
func (b *Builder) hasFromImage() bool {
|
||||
return b.image != "" || b.noBaseImage
|
||||
}
|
||||
|
||||
// Cancel cancels an ongoing Dockerfile build.
|
||||
func (b *Builder) Cancel() {
|
||||
b.cancel()
|
||||
|
|
|
@ -218,7 +218,15 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
|
|||
return err
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
substituionArgs := []string{}
|
||||
for key, value := range b.buildArgs.GetAllMeta() {
|
||||
substituionArgs = append(substituionArgs, key+"="+value)
|
||||
}
|
||||
|
||||
name, err := ProcessWord(args[0], substituionArgs, b.directive.EscapeToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var image builder.Image
|
||||
|
||||
|
@ -252,8 +260,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
|
|||
}
|
||||
b.from = image
|
||||
|
||||
b.allowedBuildArgs = make(map[string]*string)
|
||||
|
||||
b.buildArgs.ResetAllowed()
|
||||
return b.processImageFrom(image)
|
||||
}
|
||||
|
||||
|
@ -360,7 +367,7 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
|
|||
// RUN [ "echo", "hi" ] # echo hi
|
||||
//
|
||||
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if b.image == "" && !b.noBaseImage {
|
||||
if !b.hasFromImage() {
|
||||
return errors.New("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
|
||||
|
@ -438,7 +445,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
// properly match it.
|
||||
b.runConfig.Env = env
|
||||
|
||||
// remove BuiltinAllowedBuildArgs (see: builder.go) from the saveCmd
|
||||
// remove builtinAllowedBuildArgs (see: builder.go) from the saveCmd
|
||||
// these args are transparent so resulting image should be the same regardless of the value
|
||||
if len(cmdBuildEnv) > 0 {
|
||||
saveCmd = config.Cmd
|
||||
|
@ -446,11 +453,8 @@ 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 _, ok := BuiltinAllowedBuildArgs[key]; ok {
|
||||
// If an built-in arg is explicitly added in the Dockerfile, don't prune it
|
||||
if _, ok := b.allowedBuildArgs[key]; !ok {
|
||||
tmpBuildEnv = append(tmpBuildEnv[:i], tmpBuildEnv[i+1:]...)
|
||||
}
|
||||
if b.buildArgs.IsUnreferencedBuiltin(key) {
|
||||
tmpBuildEnv = append(tmpBuildEnv[:i], tmpBuildEnv[i+1:]...)
|
||||
}
|
||||
}
|
||||
sort.Strings(tmpBuildEnv)
|
||||
|
@ -781,15 +785,18 @@ func arg(b *Builder, args []string, attributes map[string]bool, original string)
|
|||
name = arg
|
||||
hasDefault = false
|
||||
}
|
||||
// add the arg to allowed list of build-time args from this step on.
|
||||
b.allBuildArgs[name] = struct{}{}
|
||||
|
||||
var value *string
|
||||
if hasDefault {
|
||||
value = &newValue
|
||||
}
|
||||
b.allowedBuildArgs[name] = value
|
||||
b.buildArgs.AddArg(name, value)
|
||||
|
||||
// Arg before FROM doesn't add a layer
|
||||
if !b.hasFromImage() {
|
||||
b.buildArgs.AddMetaArg(name, value)
|
||||
return nil
|
||||
}
|
||||
return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
|
@ -189,35 +191,67 @@ func TestLabel(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFrom(t *testing.T) {
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true}
|
||||
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
|
||||
}
|
||||
|
||||
func TestFromScratch(t *testing.T) {
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
err := from(b, []string{"scratch"}, nil, "")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
if err == nil {
|
||||
t.Fatal("Error not set on Windows")
|
||||
}
|
||||
|
||||
expectedError := "Windows does not support FROM scratch"
|
||||
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("Error message not correct on Windows. Should be: %s, got: %s", expectedError, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Error when executing from: %s", err.Error())
|
||||
}
|
||||
|
||||
if b.image != "" {
|
||||
t.Fatalf("Image should be empty, got: %s", b.image)
|
||||
}
|
||||
|
||||
if b.noBaseImage != true {
|
||||
t.Fatalf("Image should not have any base image, got: %v", b.noBaseImage)
|
||||
}
|
||||
assert.Error(t, err, "Windows does not support FROM scratch")
|
||||
return
|
||||
}
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, b.image, "")
|
||||
assert.Equal(t, b.noBaseImage, true)
|
||||
}
|
||||
|
||||
func TestFromWithArg(t *testing.T) {
|
||||
tag, expected := ":sometag", "expectedthisid"
|
||||
|
||||
getImage := func(name string) (builder.Image, error) {
|
||||
assert.Equal(t, name, "alpine"+tag)
|
||||
return &mockImage{id: "expectedthisid"}, nil
|
||||
}
|
||||
b := newBuilderWithMockBackend()
|
||||
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
|
||||
|
||||
assert.NilError(t, arg(b, []string{"THETAG=" + tag}, nil, ""))
|
||||
err := from(b, []string{"alpine${THETAG}"}, nil, "")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, b.image, expected)
|
||||
assert.Equal(t, b.from.ImageID(), expected)
|
||||
assert.Equal(t, len(b.buildArgs.GetAllAllowed()), 0)
|
||||
assert.Equal(t, len(b.buildArgs.GetAllMeta()), 1)
|
||||
}
|
||||
|
||||
func TestFromWithUndefinedArg(t *testing.T) {
|
||||
tag, expected := "sometag", "expectedthisid"
|
||||
|
||||
getImage := func(name string) (builder.Image, error) {
|
||||
assert.Equal(t, name, "alpine")
|
||||
return &mockImage{id: "expectedthisid"}, nil
|
||||
}
|
||||
b := newBuilderWithMockBackend()
|
||||
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
|
||||
b.options.BuildArgs = map[string]*string{"THETAG": &tag}
|
||||
|
||||
err := from(b, []string{"alpine${THETAG}"}, nil, "")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, b.image, expected)
|
||||
}
|
||||
|
||||
func TestOnbuildIllegalTriggers(t *testing.T) {
|
||||
|
@ -461,29 +495,18 @@ func TestStopSignal(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestArg(t *testing.T) {
|
||||
// This is a bad test that tests implementation details and not at
|
||||
// any features of the builder. Replace or remove.
|
||||
buildOptions := &types.ImageBuildOptions{BuildArgs: make(map[string]*string)}
|
||||
|
||||
b := &Builder{flags: &BFlags{}, runConfig: &container.Config{}, disableCommit: true, allowedBuildArgs: make(map[string]*string), allBuildArgs: make(map[string]struct{}), options: buildOptions}
|
||||
b := newBuilderWithMockBackend()
|
||||
|
||||
argName := "foo"
|
||||
argVal := "bar"
|
||||
argDef := fmt.Sprintf("%s=%s", argName, argVal)
|
||||
|
||||
if err := arg(b, []string{argDef}, nil, ""); err != nil {
|
||||
t.Fatalf("Error should be empty, got: %s", err.Error())
|
||||
}
|
||||
err := arg(b, []string{argDef}, nil, "")
|
||||
assert.NilError(t, err)
|
||||
|
||||
value, ok := b.getBuildArg(argName)
|
||||
|
||||
if !ok {
|
||||
t.Fatalf("%s argument should be a build arg", argName)
|
||||
}
|
||||
|
||||
if value != "bar" {
|
||||
t.Fatalf("%s argument should have default value 'bar', got %s", argName, value)
|
||||
}
|
||||
expected := map[string]string{argName: argVal}
|
||||
allowed := b.buildArgs.GetAllAllowed()
|
||||
assert.DeepEqual(t, allowed, expected)
|
||||
}
|
||||
|
||||
func TestShell(t *testing.T) {
|
||||
|
|
|
@ -175,7 +175,10 @@ func (b *Builder) evaluateEnv(cmd string, str string, envs []string) ([]string,
|
|||
if allowWordExpansion[cmd] {
|
||||
processFunc = ProcessWords
|
||||
} else {
|
||||
processFunc = ProcessWord
|
||||
processFunc = func(word string, envs []string, escape rune) ([]string, error) {
|
||||
word, err := ProcessWord(word, envs, escape)
|
||||
return []string{word}, err
|
||||
}
|
||||
}
|
||||
return processFunc(str, envs, b.directive.EscapeToken)
|
||||
}
|
||||
|
@ -186,7 +189,7 @@ func (b *Builder) buildArgsWithoutConfigEnv() []string {
|
|||
envs := []string{}
|
||||
configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env)
|
||||
|
||||
for key, val := range b.getBuildArgs() {
|
||||
for key, val := range b.buildArgs.GetAllAllowed() {
|
||||
if _, ok := configEnv[key]; !ok {
|
||||
envs = append(envs, fmt.Sprintf("%s=%s", key, val))
|
||||
}
|
||||
|
|
|
@ -180,9 +180,17 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
|
|||
}
|
||||
|
||||
config := &container.Config{}
|
||||
options := &types.ImageBuildOptions{}
|
||||
options := &types.ImageBuildOptions{
|
||||
BuildArgs: make(map[string]*string),
|
||||
}
|
||||
|
||||
b := &Builder{runConfig: config, options: options, Stdout: ioutil.Discard, context: context}
|
||||
b := &Builder{
|
||||
runConfig: config,
|
||||
options: options,
|
||||
Stdout: ioutil.Discard,
|
||||
context: context,
|
||||
buildArgs: newBuildArgs(options.BuildArgs),
|
||||
}
|
||||
|
||||
err = b.dispatch(0, len(n.Children), n.Children[0])
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e
|
|||
if b.disableCommit {
|
||||
return nil
|
||||
}
|
||||
if b.image == "" && !b.noBaseImage {
|
||||
if !b.hasFromImage() {
|
||||
return errors.New("Please provide a source image with `from` prior to commit")
|
||||
}
|
||||
b.runConfig.Image = b.image
|
||||
|
@ -516,7 +516,7 @@ func (b *Builder) probeCache() (bool, error) {
|
|||
}
|
||||
|
||||
func (b *Builder) create() (string, error) {
|
||||
if b.image == "" && !b.noBaseImage {
|
||||
if !b.hasFromImage() {
|
||||
return "", errors.New("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.runConfig.Image = b.image
|
||||
|
@ -710,36 +710,3 @@ func (b *Builder) parseDockerfile() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) getBuildArg(arg string) (string, bool) {
|
||||
defaultValue, defined := b.allowedBuildArgs[arg]
|
||||
_, builtin := BuiltinAllowedBuildArgs[arg]
|
||||
if defined || builtin {
|
||||
if v, ok := b.options.BuildArgs[arg]; ok && v != nil {
|
||||
return *v, ok
|
||||
}
|
||||
}
|
||||
if defaultValue == nil {
|
||||
return "", false
|
||||
}
|
||||
return *defaultValue, defined
|
||||
}
|
||||
|
||||
func (b *Builder) getBuildArgs() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for arg := range b.options.BuildArgs {
|
||||
v, ok := b.getBuildArg(arg)
|
||||
if ok {
|
||||
m[arg] = v
|
||||
}
|
||||
}
|
||||
for arg := range b.allowedBuildArgs {
|
||||
if _, ok := m[arg]; !ok {
|
||||
v, ok := b.getBuildArg(arg)
|
||||
if ok {
|
||||
m[arg] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
99
builder/dockerfile/mockbackend_test.go
Normal file
99
builder/dockerfile/mockbackend_test.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package dockerfile
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/image"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// MockBackend implements the builder.Backend interface for unit testing
|
||||
type MockBackend struct {
|
||||
getImageOnBuildFunc func(string) (builder.Image, error)
|
||||
}
|
||||
|
||||
func (m *MockBackend) GetImageOnBuild(name string) (builder.Image, error) {
|
||||
if m.getImageOnBuildFunc != nil {
|
||||
return m.getImageOnBuildFunc(name)
|
||||
}
|
||||
return &mockImage{id: "theid"}, nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) TagImageWithReference(image.ID, reference.Named) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerCreate(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
|
||||
return container.ContainerCreateCreatedBody{}, nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerRm(name string, config *types.ContainerRmConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) Commit(string, *backend.ContainerCommitConfig) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerKill(containerID string, sig uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerStart(containerID string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerWait(containerID string, timeout time.Duration) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerUpdateCmdOnBuild(containerID string, cmd []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) ContainerCreateWorkdir(containerID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) CopyOnBuild(containerID string, destPath string, src builder.FileInfo, decompress bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) HasExperimental() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *MockBackend) SquashImage(from string, to string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockBackend) MountImage(name string) (string, func() error, error) {
|
||||
return "", func() error { return nil }, nil
|
||||
}
|
||||
|
||||
type mockImage struct {
|
||||
id string
|
||||
config *container.Config
|
||||
}
|
||||
|
||||
func (i *mockImage) ImageID() string {
|
||||
return i.id
|
||||
}
|
||||
|
||||
func (i *mockImage) RunConfig() *container.Config {
|
||||
return i.config
|
||||
}
|
|
@ -24,9 +24,9 @@ type shellWord struct {
|
|||
|
||||
// ProcessWord will use the 'env' list of environment variables,
|
||||
// and replace any env var references in 'word'.
|
||||
func ProcessWord(word string, env []string, escapeToken rune) ([]string, error) {
|
||||
func ProcessWord(word string, env []string, escapeToken rune) (string, error) {
|
||||
word, _, err := process(word, env, escapeToken)
|
||||
return []string{word}, err
|
||||
return word, err
|
||||
}
|
||||
|
||||
// ProcessWords will use the 'env' list of environment variables,
|
||||
|
|
|
@ -54,7 +54,7 @@ func TestShellParser4EnvVars(t *testing.T) {
|
|||
assert.Error(t, err, "")
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, newWord, []string{expected})
|
||||
assert.Equal(t, newWord, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4728,7 +4728,7 @@ func (s *DockerSuite) TestBuildBuildTimeArgDefintionWithNoEnvInjection(c *check.
|
|||
ARG %s
|
||||
RUN env`, envKey)
|
||||
|
||||
result := buildImage(imgName, build.WithDockerfile(dockerfile))
|
||||
result := cli.BuildCmd(c, imgName, build.WithDockerfile(dockerfile))
|
||||
result.Assert(c, icmd.Success)
|
||||
if strings.Count(result.Combined(), envKey) != 1 {
|
||||
c.Fatalf("unexpected number of occurrences of the arg in output: %q expected: 1", result.Combined())
|
||||
|
@ -4745,29 +4745,45 @@ func (s *DockerSuite) TestBuildBuildTimeArgMultipleFrom(c *check.C) {
|
|||
ARG bar=def
|
||||
RUN env > /out`
|
||||
|
||||
result := buildImage(imgName, build.WithDockerfile(dockerfile))
|
||||
result := cli.BuildCmd(c, imgName, build.WithDockerfile(dockerfile))
|
||||
result.Assert(c, icmd.Success)
|
||||
|
||||
result = icmd.RunCmd(icmd.Cmd{
|
||||
Command: []string{dockerBinary, "images", "-q", "-f", "label=multifromtest=1"},
|
||||
})
|
||||
result.Assert(c, icmd.Success)
|
||||
result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest=1")
|
||||
parentID := strings.TrimSpace(result.Stdout())
|
||||
|
||||
result = icmd.RunCmd(icmd.Cmd{
|
||||
Command: []string{dockerBinary, "run", "--rm", parentID, "cat", "/out"},
|
||||
})
|
||||
result.Assert(c, icmd.Success)
|
||||
result = cli.DockerCmd(c, "run", "--rm", parentID, "cat", "/out")
|
||||
c.Assert(result.Stdout(), checker.Contains, "foo=abc")
|
||||
|
||||
result = icmd.RunCmd(icmd.Cmd{
|
||||
Command: []string{dockerBinary, "run", "--rm", imgName, "cat", "/out"},
|
||||
})
|
||||
result.Assert(c, icmd.Success)
|
||||
result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out")
|
||||
c.Assert(result.Stdout(), checker.Not(checker.Contains), "foo")
|
||||
c.Assert(result.Stdout(), checker.Contains, "bar=def")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestBuildBuildTimeFromArgMultipleFrom(c *check.C) {
|
||||
imgName := "multifrombldargtest"
|
||||
dockerfile := `ARG tag=nosuchtag
|
||||
FROM busybox:${tag}
|
||||
LABEL multifromtest=1
|
||||
RUN env > /out
|
||||
FROM busybox:${tag}
|
||||
ARG tag
|
||||
RUN env > /out`
|
||||
|
||||
result := cli.BuildCmd(c, imgName,
|
||||
build.WithDockerfile(dockerfile),
|
||||
cli.WithFlags("--build-arg", fmt.Sprintf("tag=latest")))
|
||||
result.Assert(c, icmd.Success)
|
||||
|
||||
result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest=1")
|
||||
parentID := strings.TrimSpace(result.Stdout())
|
||||
|
||||
result = cli.DockerCmd(c, "run", "--rm", parentID, "cat", "/out")
|
||||
c.Assert(result.Stdout(), checker.Not(checker.Contains), "tag")
|
||||
|
||||
result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out")
|
||||
c.Assert(result.Stdout(), checker.Contains, "tag=latest")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestBuildBuildTimeUnusedArgMultipleFrom(c *check.C) {
|
||||
imgName := "multifromunusedarg"
|
||||
dockerfile := `FROM busybox
|
||||
|
@ -4776,16 +4792,14 @@ func (s *DockerSuite) TestBuildBuildTimeUnusedArgMultipleFrom(c *check.C) {
|
|||
ARG bar
|
||||
RUN env > /out`
|
||||
|
||||
result := buildImage(imgName, build.WithDockerfile(dockerfile), cli.WithFlags(
|
||||
"--build-arg", fmt.Sprintf("baz=abc")))
|
||||
result := cli.BuildCmd(c, imgName,
|
||||
build.WithDockerfile(dockerfile),
|
||||
cli.WithFlags("--build-arg", fmt.Sprintf("baz=abc")))
|
||||
result.Assert(c, icmd.Success)
|
||||
c.Assert(result.Combined(), checker.Contains, "[Warning]")
|
||||
c.Assert(result.Combined(), checker.Contains, "[baz] were not consumed")
|
||||
|
||||
result = icmd.RunCmd(icmd.Cmd{
|
||||
Command: []string{dockerBinary, "run", "--rm", imgName, "cat", "/out"},
|
||||
})
|
||||
result.Assert(c, icmd.Success)
|
||||
result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out")
|
||||
c.Assert(result.Stdout(), checker.Not(checker.Contains), "bar")
|
||||
c.Assert(result.Stdout(), checker.Not(checker.Contains), "baz")
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue