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 c622dcf commit df545da
Show file tree
Hide file tree
Showing 6 changed files with 110 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", "-f", path.Join(testDir, "single.dockerapp"), testDir, "--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", "-f", path.Join(testDir, "single.dockerapp"), testDir, "--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

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

func Cmd(dockerCli command.Cli) *cobra.Command {
Expand All @@ -61,6 +62,7 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
flags.StringVarP(&opts.tag, "tag", "t", "", "Application image and optionally a tag in the 'image:tag' format")
flags.StringVarP(&opts.folder, "folder", "f", "", "Docker app folder containing application definition")
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 @@ -71,6 +73,10 @@ func runBuild(dockerCli command.Cli, contextPath string, opt buildOptions) (refe
return nil, err
}

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

var ref reference.Reference
ref, err = packager.GetNamedTagged(opt.tag)
if err != nil {
Expand Down Expand Up @@ -242,3 +248,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, contextPath string, options buildOptions) (map
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 := buildArgsToMap(buildArgs)

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 buildArgsToMap(array []string) map[string]string {
result := make(map[string]string)
for _, value := range array {
parts := strings.SplitN(value, "=", 2)
key := parts[0]
switch {
case len(parts) == 1:
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 df545da

Please sign in to comment.