Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 8d6c935

Browse files
Merge pull request #697 from glours/add_build_arg_option
Add build parameters to docker app build command
2 parents c622dcf + 1ee6dd3 commit 8d6c935

File tree

6 files changed

+110
-7
lines changed

6 files changed

+110
-7
lines changed

e2e/build_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,51 @@ func TestBuildWithoutTag(t *testing.T) {
6868
})
6969
}
7070

71+
func TestBuildWithArgs(t *testing.T) {
72+
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
73+
cmd := info.configuredCmd
74+
75+
testDir := path.Join("testdata", "build")
76+
cmd.Command = dockerCli.Command("app", "build", "-f", path.Join(testDir, "single.dockerapp"), testDir, "--build-arg", "REPLACE_BY_BUILD_ARG=replaced")
77+
icmd.RunCmd(cmd).Assert(t, icmd.Success)
78+
79+
cfg := getDockerConfigDir(t, cmd)
80+
81+
f := path.Join(cfg, "app", "bundles", "_ids")
82+
infos, err := ioutil.ReadDir(f)
83+
assert.NilError(t, err)
84+
assert.Equal(t, len(infos), 1)
85+
id := infos[0].Name()
86+
87+
f = path.Join(cfg, "app", "bundles", "_ids", id, "bundle.json")
88+
data, err := ioutil.ReadFile(f)
89+
assert.NilError(t, err)
90+
var bndl bundle.Bundle
91+
err = json.Unmarshal(data, &bndl)
92+
assert.NilError(t, err)
93+
94+
cmd.Command = dockerCli.Command("inspect", bndl.Images["worker"].Digest)
95+
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
96+
ExitCode: 0,
97+
Out: `"com.docker.labelled.arg": "replaced"`,
98+
})
99+
})
100+
}
101+
102+
func TestBuildWithArgsDefinedTwice(t *testing.T) {
103+
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
104+
cmd := info.configuredCmd
105+
106+
testDir := path.Join("testdata", "build")
107+
cmd.Command = dockerCli.Command("app", "build", "-f", path.Join(testDir, "single.dockerapp"), testDir,
108+
"--build-arg", "REPLACE_BY_BUILD_ARG=replaced", "--build-arg", "REPLACE_BY_BUILD_ARG=replaced_twice")
109+
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
110+
ExitCode: 1,
111+
Err: `'--build-arg REPLACE_BY_BUILD_ARG' is defined twice`,
112+
})
113+
})
114+
}
115+
71116
func getDockerConfigDir(t *testing.T, cmd icmd.Cmd) string {
72117
var cfg string
73118
for _, s := range cmd.Env {

e2e/testdata/build/single.dockerapp/docker-compose.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ services:
77
worker:
88
build:
99
context: ./worker
10+
args:
11+
- REPLACE_BY_BUILD_ARG=original
12+
- STATIC_ARG=static
1013
dockerfile: Dockerfile.worker
1114
db:
1215
image: postgres:9.3
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
FROM scratch
1+
FROM scratch
2+
ARG REPLACE_BY_BUILD_ARG
3+
ARG STATIC_ARG
4+
LABEL com.docker.labelled.arg=$REPLACE_BY_BUILD_ARG
5+
LABEL com.docker.labelled.optional=$STATIC_ARG
6+

internal/commands/build/build.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type buildOptions struct {
3737
pull bool
3838
tag string
3939
folder string
40+
args []string
4041
}
4142

4243
func Cmd(dockerCli command.Cli) *cobra.Command {
@@ -61,6 +62,7 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
6162
flags.StringVarP(&opts.tag, "tag", "t", "", "Application image and optionally a tag in the 'image:tag' format")
6263
flags.StringVarP(&opts.folder, "folder", "f", "", "Docker app folder containing application definition")
6364
flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image")
65+
flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables")
6466

6567
return cmd
6668
}
@@ -71,6 +73,10 @@ func runBuild(dockerCli command.Cli, contextPath string, opt buildOptions) (refe
7173
return nil, err
7274
}
7375

76+
if err = checkBuildArgsUniqueness(opt.args); err != nil {
77+
return nil, err
78+
}
79+
7480
var ref reference.Reference
7581
ref, err = packager.GetNamedTagged(opt.tag)
7682
if err != nil {
@@ -242,3 +248,15 @@ func debugSolveResponses(resp map[string]*client.SolveResponse) {
242248
}
243249
}
244250
}
251+
252+
func checkBuildArgsUniqueness(args []string) error {
253+
set := make(map[string]bool)
254+
for _, value := range args {
255+
key := strings.Split(value, "=")[0]
256+
if _, ok := set[key]; ok {
257+
return fmt.Errorf("'--build-arg %s' is defined twice", key)
258+
}
259+
set[key] = true
260+
}
261+
return nil
262+
}

internal/commands/build/compose.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func parseCompose(app *types.App, contextPath string, options buildOptions) (map
1818
return nil, err
1919
}
2020

21-
services, err := load(parsed)
21+
services, err := load(parsed, options.args)
2222
if err != nil {
2323
return nil, fmt.Errorf("Failed to parse compose file: %s", err)
2424
}

internal/commands/build/types.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package build
33
import (
44
"fmt"
55
"reflect"
6+
"strings"
67

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

27-
func load(dict map[string]interface{}) ([]ServiceConfig, error) {
28+
func load(dict map[string]interface{}, buildArgs []string) ([]ServiceConfig, error) {
2829
section, ok := dict["services"]
2930
if !ok {
3031
return nil, fmt.Errorf("compose file doesn't declare any service")
@@ -33,14 +34,14 @@ func load(dict map[string]interface{}) ([]ServiceConfig, error) {
3334
if !ok {
3435
return nil, fmt.Errorf("Invalid compose file: 'services' should be a map")
3536
}
36-
return loadServices(services)
37+
return loadServices(services, buildArgs)
3738
}
3839

39-
func loadServices(servicesDict map[string]interface{}) ([]ServiceConfig, error) {
40+
func loadServices(servicesDict map[string]interface{}, buildArgs []string) ([]ServiceConfig, error) {
4041
var services []ServiceConfig
4142

4243
for name, serviceDef := range servicesDict {
43-
serviceConfig, err := loadService(name, serviceDef.(map[string]interface{}))
44+
serviceConfig, err := loadService(name, serviceDef.(map[string]interface{}), buildArgs)
4445
if err != nil {
4546
return nil, err
4647
}
@@ -49,14 +50,19 @@ func loadServices(servicesDict map[string]interface{}) ([]ServiceConfig, error)
4950
return services, nil
5051
}
5152

52-
func loadService(name string, serviceDict map[string]interface{}) (*ServiceConfig, error) {
53+
func loadService(name string, serviceDict map[string]interface{}, buildArgs []string) (*ServiceConfig, error) {
5354
serviceConfig := &ServiceConfig{Name: name}
55+
args := buildArgsToMap(buildArgs)
56+
5457
if err := loader.Transform(serviceDict, serviceConfig, loader.Transformer{
5558
TypeOf: reflect.TypeOf(ImageBuildConfig{}),
5659
Func: transformBuildConfig,
5760
}); err != nil {
5861
return nil, err
5962
}
63+
if serviceConfig.Build != nil {
64+
serviceConfig.Build.mergeArgs(args)
65+
}
6066
return serviceConfig, nil
6167
}
6268

@@ -70,3 +76,29 @@ func transformBuildConfig(data interface{}) (interface{}, error) {
7076
return data, errors.Errorf("invalid type %T for service build", value)
7177
}
7278
}
79+
80+
func buildArgsToMap(array []string) map[string]string {
81+
result := make(map[string]string)
82+
for _, value := range array {
83+
parts := strings.SplitN(value, "=", 2)
84+
key := parts[0]
85+
if len(parts) == 1 {
86+
result[key] = ""
87+
} else {
88+
result[key] = parts[1]
89+
}
90+
}
91+
return result
92+
}
93+
94+
func (m ImageBuildConfig) mergeArgs(mapToMerge map[string]string) {
95+
for key := range m.Args {
96+
if val, ok := mapToMerge[key]; ok {
97+
if val == "" {
98+
m.Args[key] = nil
99+
} else {
100+
m.Args[key] = &val
101+
}
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)