Skip to content
This repository has been archived by the owner on Jun 13, 2021. It is now read-only.

Commit

Permalink
Add --build-arg build parameters to docker app build command
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Lours <[email protected]>
  • Loading branch information
glours committed Oct 17, 2019
1 parent 8f1a594 commit d946b87
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 7 deletions.
44 changes: 44 additions & 0 deletions e2e/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,50 @@ func TestBuildWithoutTag(t *testing.T) {
})
}

func TestBuildWithArgs(t *testing.T) {
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
cmd := info.configuredCmd

testDir := path.Join("testdata", "build")
cmd.Command = dockerCli.Command("app", "build", path.Join(testDir, "single"), "--build-arg", "REPLACE_BY_BUILD_ARG=replaced")
icmd.RunCmd(cmd).Assert(t, icmd.Success)

cfg := getDockerConfigDir(t, cmd)

f := path.Join(cfg, "app", "bundles", "_ids")
infos, err := ioutil.ReadDir(f)
assert.NilError(t, err)
assert.Equal(t, len(infos), 1)
id := infos[0].Name()

f = path.Join(cfg, "app", "bundles", "_ids", id, "bundle.json")
data, err := ioutil.ReadFile(f)
assert.NilError(t, err)
var bndl bundle.Bundle
err = json.Unmarshal(data, &bndl)
assert.NilError(t, err)

cmd.Command = dockerCli.Command("inspect", bndl.Images["worker"].Digest)
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
ExitCode: 0,
Out: `"com.docker.labelled.arg": "replaced"`,
})
})
}

func TestBuildWithArgsDefinedTwice(t *testing.T) {
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
cmd := info.configuredCmd

testDir := path.Join("testdata", "build")
cmd.Command = dockerCli.Command("app", "build", path.Join(testDir, "single"), "--build-arg", "REPLACE_BY_BUILD_ARG=replaced", "--build-arg", "REPLACE_BY_BUILD_ARG=replaced_twice")
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
ExitCode: 1,
Err: `'--build-arg REPLACE_BY_BUILD_ARG' is defined twice`,
})
})
}

func getDockerConfigDir(t *testing.T, cmd icmd.Cmd) string {
var cfg string
for _, s := range cmd.Env {
Expand Down
3 changes: 3 additions & 0 deletions e2e/testdata/build/single.dockerapp/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ services:
worker:
build:
context: ./worker
args:
- REPLACE_BY_BUILD_ARG=original
- STATIC_ARG=static
dockerfile: Dockerfile.worker
db:
image: postgres:9.3
7 changes: 6 additions & 1 deletion e2e/testdata/build/worker/Dockerfile.worker
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
FROM scratch
FROM scratch
ARG REPLACE_BY_BUILD_ARG
ARG STATIC_ARG
LABEL com.docker.labelled.arg=$REPLACE_BY_BUILD_ARG
LABEL com.docker.labelled.optional=$STATIC_ARG

19 changes: 19 additions & 0 deletions internal/commands/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type buildOptions struct {
progress string
pull bool
tag string
args []string
}

func Cmd(dockerCli command.Cli) *cobra.Command {
Expand All @@ -57,6 +58,7 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&opts.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
flags.StringVarP(&opts.tag, "tag", "t", "", "Application image and optionally a tag in the 'image:tag' format")
flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image")
flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables")

return cmd
}
Expand All @@ -67,6 +69,11 @@ func runBuild(dockerCli command.Cli, application string, opt buildOptions) (refe
return nil, err
}

err = checkBuildArgsUniqueness(opt.args)
if err != nil {
return nil, err
}

var ref reference.Reference
ref, err = packager.GetNamedTagged(opt.tag)
if err != nil {
Expand Down Expand Up @@ -211,3 +218,15 @@ func debugSolveResponses(resp map[string]*client.SolveResponse) {
}
}
}

func checkBuildArgsUniqueness(args []string) error {
set := make(map[string]bool)
for _, value := range args {
key := strings.Split(value, "=")[0]
if _, ok := set[key]; ok {
return fmt.Errorf("'--build-arg %s' is defined twice", key)
}
set[key] = true
}
return nil
}
2 changes: 1 addition & 1 deletion internal/commands/build/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func parseCompose(app *types.App, options buildOptions) (map[string]build.Option
return nil, err
}

services, err := load(parsed)
services, err := load(parsed, options.args)
if err != nil {
return nil, fmt.Errorf("Failed to parse compose file: %s", err)
}
Expand Down
43 changes: 38 additions & 5 deletions internal/commands/build/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package build
import (
"fmt"
"reflect"
"strings"

"github.com/docker/cli/cli/compose/loader"
compose "github.com/docker/cli/cli/compose/types"
Expand All @@ -24,7 +25,7 @@ type ImageBuildConfig struct {
Args compose.MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
}

func load(dict map[string]interface{}) ([]ServiceConfig, error) {
func load(dict map[string]interface{}, buildArgs []string) ([]ServiceConfig, error) {
section, ok := dict["services"]
if !ok {
return nil, fmt.Errorf("compose file doesn't declare any service")
Expand All @@ -33,14 +34,14 @@ func load(dict map[string]interface{}) ([]ServiceConfig, error) {
if !ok {
return nil, fmt.Errorf("Invalid compose file: 'services' should be a map")
}
return loadServices(services)
return loadServices(services, buildArgs)
}

func loadServices(servicesDict map[string]interface{}) ([]ServiceConfig, error) {
func loadServices(servicesDict map[string]interface{}, buildArgs []string) ([]ServiceConfig, error) {
var services []ServiceConfig

for name, serviceDef := range servicesDict {
serviceConfig, err := loadService(name, serviceDef.(map[string]interface{}))
serviceConfig, err := loadService(name, serviceDef.(map[string]interface{}), buildArgs)
if err != nil {
return nil, err
}
Expand All @@ -49,14 +50,19 @@ func loadServices(servicesDict map[string]interface{}) ([]ServiceConfig, error)
return services, nil
}

func loadService(name string, serviceDict map[string]interface{}) (*ServiceConfig, error) {
func loadService(name string, serviceDict map[string]interface{}, buildArgs []string) (*ServiceConfig, error) {
serviceConfig := &ServiceConfig{Name: name}
args := transformBuildArgsArray(buildArgs, "=", true)

if err := loader.Transform(serviceDict, serviceConfig, loader.Transformer{
TypeOf: reflect.TypeOf(ImageBuildConfig{}),
Func: transformBuildConfig,
}); err != nil {
return nil, err
}
if serviceConfig.Build != nil {
serviceConfig.Build.mergeArgs(args)
}
return serviceConfig, nil
}

Expand All @@ -70,3 +76,30 @@ func transformBuildConfig(data interface{}) (interface{}, error) {
return data, errors.Errorf("invalid type %T for service build", value)
}
}

func transformBuildArgsArray(array []string, sep string, allowNil bool) map[string]string {
result := make(map[string]string)
for _, value := range array {
parts := strings.SplitN(value, sep, 2)
key := parts[0]
switch {
case len(parts) == 1 && !allowNil:
result[key] = ""
default:
result[key] = parts[1]
}
}
return result
}

func (m ImageBuildConfig) mergeArgs(mapToMerge map[string]string) {
for key := range m.Args {
if val, ok := mapToMerge[key]; ok {
if val == "" {
m.Args[key] = nil
} else {
m.Args[key] = &val
}
}
}
}

0 comments on commit d946b87

Please sign in to comment.