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

Commit c622dcf

Browse files
Merge pull request #691 from ndeloof/build_more
Adjust the "docker app build" UX
2 parents 175bec6 + 52b815d commit c622dcf

File tree

8 files changed

+61
-36
lines changed

8 files changed

+61
-36
lines changed

e2e/build_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func TestBuild(t *testing.T) {
1717
cmd := info.configuredCmd
1818

1919
testDir := path.Join("testdata", "build")
20-
cmd.Command = dockerCli.Command("app", "build", path.Join(testDir, "single"), "--tag", "single:1.0.0")
20+
cmd.Command = dockerCli.Command("app", "build", "--tag", "single:1.0.0", "-f", path.Join(testDir, "single.dockerapp"), testDir)
2121
icmd.RunCmd(cmd).Assert(t, icmd.Success)
2222

2323
cfg := getDockerConfigDir(t, cmd)
@@ -42,7 +42,7 @@ func TestBuildWithoutTag(t *testing.T) {
4242
cmd := info.configuredCmd
4343

4444
testDir := path.Join("testdata", "build")
45-
cmd.Command = dockerCli.Command("app", "build", path.Join(testDir, "single"))
45+
cmd.Command = dockerCli.Command("app", "build", "-f", path.Join(testDir, "single.dockerapp"), testDir)
4646
icmd.RunCmd(cmd).Assert(t, icmd.Success)
4747

4848
cfg := getDockerConfigDir(t, cmd)

e2e/commands_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ func TestRenderFormatters(t *testing.T) {
7474
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
7575
cmd := info.configuredCmd
7676

77-
appPath := filepath.Join("testdata", "simple", "simple.dockerapp")
78-
cmd.Command = dockerCli.Command("app", "build", appPath, "--tag", "a-simple-tag")
77+
contextPath := filepath.Join("testdata", "simple")
78+
appPath := filepath.Join(contextPath, "simple.dockerapp")
79+
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-tag", contextPath)
7980
icmd.RunCmd(cmd).Assert(t, icmd.Success)
8081

8182
cmd.Command = dockerCli.Command("app", "render", "--formatter", "json", appPath)
@@ -164,7 +165,8 @@ func TestInspectApp(t *testing.T) {
164165
Err: `"docker app inspect" requires exactly 1 argument.`,
165166
})
166167

167-
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "simple-app:1.0.0")
168+
contextPath := filepath.Join("testdata", "simple")
169+
cmd.Command = dockerCli.Command("app", "build", "--tag", "simple-app:1.0.0", contextPath)
168170
cmd.Dir = ""
169171
icmd.RunCmd(cmd).Assert(t, icmd.Success)
170172

e2e/images_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import (
1212

1313
func insertBundles(t *testing.T, cmd icmd.Cmd, info dindSwarmAndRegistryInfo) {
1414
// Push an application so that we can later pull it by digest
15-
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"), "--tag", info.registryAddress+"/c-myapp")
15+
cmd.Command = dockerCli.Command("app", "build", "--tag", info.registryAddress+"/c-myapp", filepath.Join("testdata", "push-pull"))
1616
icmd.RunCmd(cmd).Assert(t, icmd.Success)
17-
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "b-simple-app")
17+
cmd.Command = dockerCli.Command("app", "build", "--tag", "b-simple-app", filepath.Join("testdata", "simple"))
1818
icmd.RunCmd(cmd).Assert(t, icmd.Success)
19-
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "a-simple-app")
19+
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-app", filepath.Join("testdata", "simple"))
2020
icmd.RunCmd(cmd).Assert(t, icmd.Success)
2121
}
2222

@@ -86,7 +86,7 @@ func TestImageTag(t *testing.T) {
8686
}
8787

8888
// given a first available image
89-
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "a-simple-app")
89+
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-app", filepath.Join("testdata", "simple"))
9090
icmd.RunCmd(cmd).Assert(t, icmd.Success)
9191

9292
singleImageExpectation := `APP IMAGE APP NAME
@@ -175,7 +175,7 @@ c-simple-app:latest simple
175175
`)
176176

177177
// given a new application
178-
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"), "--tag", "push-pull")
178+
cmd.Command = dockerCli.Command("app", "build", "--tag", "push-pull", filepath.Join("testdata", "push-pull"))
179179
icmd.RunCmd(cmd).Assert(t, icmd.Success)
180180
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
181181
a-simple-app:0.1 simple

e2e/pushpull_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func TestPushInstallBundle(t *testing.T) {
196196
ref := info.registryAddress + "/test/push-bundle"
197197

198198
// render the app to a bundle, we use the app from the push pull test above.
199-
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"), "--tag", "a-simple-app:1.0.0")
199+
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-app:1.0.0", filepath.Join("testdata", "push-pull"))
200200
icmd.RunCmd(cmd).Assert(t, icmd.Success)
201201

202202
// push it and install to check it is available
@@ -244,7 +244,7 @@ func TestPushInstallBundle(t *testing.T) {
244244
cmdIsolatedStore.Env = append(cmdIsolatedStore.Env, "DOCKER_CONTEXT=swarm-context")
245245

246246
// bundle the app again but this time with a tag to store it into the bundle store
247-
cmdIsolatedStore.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"), "--tag", ref2)
247+
cmdIsolatedStore.Command = dockerCli.Command("app", "build", "--tag", ref2, filepath.Join("testdata", "push-pull"))
248248
icmd.RunCmd(cmdIsolatedStore).Assert(t, icmd.Success)
249249
// Push the app without tagging it explicitly
250250
cmdIsolatedStore.Command = dockerCli.Command("app", "push", ref2)

internal/commands/build/build.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"io/ioutil"
910
"os"
11+
"path/filepath"
1012
"strconv"
1113
"strings"
1214

@@ -34,14 +36,15 @@ type buildOptions struct {
3436
progress string
3537
pull bool
3638
tag string
39+
folder string
3740
}
3841

3942
func Cmd(dockerCli command.Cli) *cobra.Command {
4043
var opts buildOptions
4144
cmd := &cobra.Command{
42-
Use: "build [APP_NAME] [OPTIONS]",
45+
Use: "build [OPTIONS] [CONTEXT_PATH]",
4346
Short: "Build service images for the application",
44-
Example: `$ docker app build myapp.dockerapp --tag my/app:1.0.0`,
47+
Example: `$ docker app build --tag my/app:1.0.0 .`,
4548
Args: cli.ExactArgs(1),
4649
RunE: func(cmd *cobra.Command, args []string) error {
4750
ref, err := runBuild(dockerCli, args[0], opts)
@@ -56,12 +59,13 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
5659
flags.BoolVar(&opts.noCache, "no-cache", false, "Do not use cache when building the image")
5760
flags.StringVar(&opts.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
5861
flags.StringVarP(&opts.tag, "tag", "t", "", "Application image and optionally a tag in the 'image:tag' format")
62+
flags.StringVarP(&opts.folder, "folder", "f", "", "Docker app folder containing application definition")
5963
flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image")
6064

6165
return cmd
6266
}
6367

64-
func runBuild(dockerCli command.Cli, application string, opt buildOptions) (reference.Reference, error) {
68+
func runBuild(dockerCli command.Cli, contextPath string, opt buildOptions) (reference.Reference, error) {
6569
err := checkMinimalEngineVersion(dockerCli)
6670
if err != nil {
6771
return nil, err
@@ -73,13 +77,18 @@ func runBuild(dockerCli command.Cli, application string, opt buildOptions) (refe
7377
return nil, err
7478
}
7579

80+
application, err := getAppFolder(opt, contextPath)
81+
if err != nil {
82+
return nil, err
83+
}
84+
7685
app, err := packager.Extract(application)
7786
if err != nil {
7887
return nil, err
7988
}
8089
defer app.Cleanup()
8190

82-
buildopts, err := parseCompose(app, opt)
91+
buildopts, err := parseCompose(app, contextPath, opt)
8392
if err != nil {
8493
return nil, err
8594
}
@@ -126,6 +135,28 @@ func runBuild(dockerCli command.Cli, application string, opt buildOptions) (refe
126135
return packager.PersistInBundleStore(ref, bundle)
127136
}
128137

138+
func getAppFolder(opt buildOptions, contextPath string) (string, error) {
139+
application := opt.folder
140+
if application == "" {
141+
files, err := ioutil.ReadDir(contextPath)
142+
if err != nil {
143+
return "", err
144+
}
145+
for _, f := range files {
146+
if strings.HasSuffix(f.Name(), ".dockerapp") {
147+
if application != "" {
148+
return "", fmt.Errorf("%s contains multiple *.dockerapp folders, use -f option to select the one to build", contextPath)
149+
}
150+
application = filepath.Join(contextPath, f.Name())
151+
if !f.IsDir() {
152+
return "", fmt.Errorf("%s isn't a directory", f.Name())
153+
}
154+
}
155+
}
156+
}
157+
return application, nil
158+
}
159+
129160
func checkMinimalEngineVersion(dockerCli command.Cli) error {
130161
info, err := dockerCli.Client().Info(appcontext.Context())
131162
if err != nil {

internal/commands/build/compose.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
// parseCompose do parse app compose file and extract buildx Options
1414
// We don't rely on bake's ReadTargets + TargetsToBuildOpt here as we have to skip environment variable interpolation
15-
func parseCompose(app *types.App, options buildOptions) (map[string]build.Options, error) {
15+
func parseCompose(app *types.App, contextPath string, options buildOptions) (map[string]build.Options, error) {
1616
parsed, err := loader.ParseYAML(app.Composes()[0])
1717
if err != nil {
1818
return nil, err
@@ -28,23 +28,18 @@ func parseCompose(app *types.App, options buildOptions) (map[string]build.Option
2828
if service.Build == nil {
2929
continue
3030
}
31-
3231
var tags []string
33-
if service.Image != nil && *service.Image != "" {
34-
tags = []string{*service.Image}
32+
if service.Image != nil {
33+
tags = append(tags, *service.Image)
3534
}
3635

37-
// FIXME docker app init should update relative paths
38-
// compose file has been copied to x.dockerapp, so the relative path to build context get broken
39-
contextPath := path.Join(app.Path, "..", service.Build.Context)
4036
if service.Build.Dockerfile == "" {
4137
service.Build.Dockerfile = "Dockerfile"
4238
}
43-
dockerfile := path.Join(contextPath, service.Build.Dockerfile)
4439
opts[service.Name] = build.Options{
4540
Inputs: build.Inputs{
46-
ContextPath: contextPath,
47-
DockerfilePath: dockerfile,
41+
ContextPath: path.Join(contextPath, service.Build.Context),
42+
DockerfilePath: path.Join(contextPath, service.Build.Context, service.Build.Dockerfile),
4843
},
4944
BuildArgs: flatten(service.Build.Args),
5045
NoCache: options.noCache,

internal/commands/build/compose_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,9 @@ func Test_parseCompose(t *testing.T) {
4949
}
5050
for _, tt := range tests {
5151
t.Run(tt.name, func(t *testing.T) {
52-
5352
app, err := packager.Extract("testdata/" + tt.name)
5453
assert.NilError(t, err)
55-
56-
got, err := parseCompose(app, buildOptions{})
54+
got, err := parseCompose(app, "testdata", buildOptions{})
5755
assert.NilError(t, err)
5856
_, ok := got["dontwant"]
5957
assert.Assert(t, !ok, "parseCompose() should have excluded 'dontwant' service")

internal/packager/extract.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@ import (
1414
)
1515

1616
// findApp looks for an app in CWD or subdirs
17-
func findApp() (string, error) {
18-
cwd, err := os.Getwd()
19-
if err != nil {
20-
return "", errors.Wrap(err, "cannot resolve current working directory")
21-
}
17+
func findApp(cwd string) (string, error) {
2218
if strings.HasSuffix(cwd, internal.AppExtension) {
2319
return cwd, nil
2420
}
@@ -47,8 +43,11 @@ func findApp() (string, error) {
4743
// If nothing is found, it looks for an image and loads it
4844
func Extract(name string, ops ...func(*types.App) error) (*types.App, error) {
4945
if name == "" {
50-
var err error
51-
if name, err = findApp(); err != nil {
46+
cwd, err := os.Getwd()
47+
if err != nil {
48+
return nil, errors.Wrap(err, "cannot resolve current working directory")
49+
}
50+
if name, err = findApp(cwd); err != nil {
5251
return nil, err
5352
}
5453
}

0 commit comments

Comments
 (0)