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

Commit

Permalink
Adjust the "docker app build" UX
Browse files Browse the repository at this point in the history
docker app build -f <path to x.dockerapp folder> <path to docker context, typically ".">

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
  • Loading branch information
ndeloof committed Oct 16, 2019
1 parent 35fdf49 commit e8c8507
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 39 deletions.
4 changes: 2 additions & 2 deletions e2e/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestBuild(t *testing.T) {
cmd := info.configuredCmd

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

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

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

cfg := getDockerConfigDir(t, cmd)
Expand Down
8 changes: 5 additions & 3 deletions e2e/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ func TestRenderFormatters(t *testing.T) {
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
cmd := info.configuredCmd

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

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

cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "simple", "simple.dockerapp"), "--tag", "simple-app:1.0.0")
contextPath := filepath.Join("testdata", "simple")
cmd.Command = dockerCli.Command("app", "build", "--tag", "simple-app:1.0.0", contextPath)
cmd.Dir = ""
icmd.RunCmd(cmd).Assert(t, icmd.Success)

Expand Down
10 changes: 5 additions & 5 deletions e2e/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (

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

Expand Down Expand Up @@ -86,7 +86,7 @@ func TestImageTag(t *testing.T) {
}

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

singleImageExpectation := `APP IMAGE APP NAME
Expand Down Expand Up @@ -175,7 +175,7 @@ c-simple-app:latest simple
`)

// given a new application
cmd.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"), "--tag", "push-pull")
cmd.Command = dockerCli.Command("app", "build", "--tag", "push-pull", filepath.Join("testdata", "push-pull"))
icmd.RunCmd(cmd).Assert(t, icmd.Success)
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
a-simple-app:0.1 simple
Expand Down
4 changes: 2 additions & 2 deletions e2e/pushpull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func TestPushInstallBundle(t *testing.T) {
ref := info.registryAddress + "/test/push-bundle"

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

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

// bundle the app again but this time with a tag to store it into the bundle store
cmdIsolatedStore.Command = dockerCli.Command("app", "build", filepath.Join("testdata", "push-pull", "push-pull.dockerapp"), "--tag", ref2)
cmdIsolatedStore.Command = dockerCli.Command("app", "build", "--tag", ref2, filepath.Join("testdata", "push-pull"))
icmd.RunCmd(cmdIsolatedStore).Assert(t, icmd.Success)
// Push the app without tagging it explicitly
cmdIsolatedStore.Command = dockerCli.Command("app", "push", ref2)
Expand Down
45 changes: 39 additions & 6 deletions internal/commands/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"

Expand Down Expand Up @@ -34,14 +36,15 @@ type buildOptions struct {
progress string
pull bool
tag string
folder string
}

func Cmd(dockerCli command.Cli) *cobra.Command {
var opts buildOptions
cmd := &cobra.Command{
Use: "build [APP_NAME] [OPTIONS]",
Use: "build [OPTIONS] [CONTEXT_PATH]",
Short: "Build service images for the application",
Example: `$ docker app build myapp.dockerapp --tag my/app:1.0.0`,
Example: `$ docker app build --tag my/app:1.0.0 .`,
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ref, err := runBuild(dockerCli, args[0], opts)
Expand All @@ -56,12 +59,13 @@ func Cmd(dockerCli command.Cli) *cobra.Command {
flags.BoolVar(&opts.noCache, "no-cache", false, "Do not use cache when building the image")
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.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")

return cmd
}

func runBuild(dockerCli command.Cli, application string, opt buildOptions) (reference.Reference, error) {
func runBuild(dockerCli command.Cli, contextPath string, opt buildOptions) (reference.Reference, error) {
err := checkMinimalEngineVersion(dockerCli)
if err != nil {
return nil, err
Expand All @@ -73,18 +77,46 @@ func runBuild(dockerCli command.Cli, application string, opt buildOptions) (refe
return nil, err
}

application := opt.folder
if application == "" {
files, err := ioutil.ReadDir(contextPath)
if err != nil {
return nil, err
}
for _, f := range files {
if strings.HasSuffix(f.Name(), ".dockerapp") {
if application != "" {
return nil, fmt.Errorf("%s contains multiple *.dockerapp folders, use -f option to select the one to build", contextPath)
}
application = filepath.Join(contextPath, f.Name())
}
}
}

app, err := packager.Extract(application)
if err != nil {
return nil, err
}
defer app.Cleanup()

buildopts, err := parseCompose(app, opt)
serviceTag := ref
if serviceTag == nil {
named, err := reference.WithName(app.Metadata().Name)
if err != nil {
return nil, err
}
serviceTag, err = reference.WithTag(named, app.Metadata().Version)
if err != nil {
return nil, err
}
}

buildopts, err := parseCompose(app, contextPath, opt, serviceTag)
if err != nil {
return nil, err
}

buildopts["com.docker.app.invocation-image"], err = createInvocationImageBuildOptions(dockerCli, app)
buildopts["com.docker.app.invocation-image"], err = createInvocationImageBuildOptions(dockerCli, app, serviceTag)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -165,7 +197,7 @@ func updateBundle(dockerCli command.Cli, bundle *bundle.Bundle, resp map[string]
return nil
}

func createInvocationImageBuildOptions(dockerCli command.Cli, app *types.App) (build.Options, error) {
func createInvocationImageBuildOptions(dockerCli command.Cli, app *types.App, serviceTag reference.Reference) (build.Options, error) {
buildContext := bytes.NewBuffer(nil)
if err := packager.PackInvocationImageContext(dockerCli, app, buildContext); err != nil {
return build.Options{}, err
Expand All @@ -176,6 +208,7 @@ func createInvocationImageBuildOptions(dockerCli command.Cli, app *types.App) (b
ContextPath: "-",
},
Session: []session.Attachable{authprovider.NewDockerAuthProvider(os.Stderr)},
Tags: []string{fmt.Sprintf("%s-installer", serviceTag.String())},
}, nil
}

Expand Down
19 changes: 9 additions & 10 deletions internal/commands/build/compose.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package build

import (
"errors"
"fmt"
"github.com/docker/distribution/reference"
"path"

"github.com/docker/app/types"
Expand All @@ -12,7 +14,7 @@ import (

// parseCompose do parse app compose file and extract buildx Options
// We don't rely on bake's ReadTargets + TargetsToBuildOpt here as we have to skip environment variable interpolation
func parseCompose(app *types.App, options buildOptions) (map[string]build.Options, error) {
func parseCompose(app *types.App, contextPath string, options buildOptions, reference reference.Reference) (map[string]build.Options, error) {
parsed, err := loader.ParseYAML(app.Composes()[0])
if err != nil {
return nil, err
Expand All @@ -29,22 +31,19 @@ func parseCompose(app *types.App, options buildOptions) (map[string]build.Option
continue
}

var tags []string
if service.Image != nil && *service.Image != "" {
tags = []string{*service.Image}
if service.Name == "installer" {
return nil, errors.New("'installer' is a reserved service name, please fix your docker-compose.yml file")
}

// FIXME docker app init should update relative paths
// compose file has been copied to x.dockerapp, so the relative path to build context get broken
contextPath := path.Join(app.Path, "..", service.Build.Context)
tags := []string{fmt.Sprintf("%s-%s", reference.String(), service.Name)}

if service.Build.Dockerfile == "" {
service.Build.Dockerfile = "Dockerfile"
}
dockerfile := path.Join(contextPath, service.Build.Dockerfile)
opts[service.Name] = build.Options{
Inputs: build.Inputs{
ContextPath: contextPath,
DockerfilePath: dockerfile,
ContextPath: path.Join(contextPath, service.Build.Context),
DockerfilePath: path.Join(contextPath, service.Build.Context, service.Build.Dockerfile),
},
BuildArgs: flatten(service.Build.Args),
NoCache: options.noCache,
Expand Down
13 changes: 9 additions & 4 deletions internal/commands/build/compose_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package build

import (
"github.com/docker/distribution/reference"
"testing"

"github.com/docker/app/internal/packager"
Expand All @@ -9,6 +10,10 @@ import (
)

func Test_parseCompose(t *testing.T) {

tag, err := reference.Parse("test:1.0")
assert.NilError(t, err)

tests := []struct {
name string
service string
Expand All @@ -22,7 +27,7 @@ func Test_parseCompose(t *testing.T) {
ContextPath: "testdata/web",
DockerfilePath: "testdata/web/Dockerfile",
},
Tags: []string{"frontend"},
Tags: []string{"test:1.0-web"},
},
},
{
Expand All @@ -33,6 +38,7 @@ func Test_parseCompose(t *testing.T) {
ContextPath: "testdata/web",
DockerfilePath: "testdata/web/Dockerfile.custom",
},
Tags: []string{"test:1.0-web"},
},
},
{
Expand All @@ -44,16 +50,15 @@ func Test_parseCompose(t *testing.T) {
DockerfilePath: "testdata/web/Dockerfile",
},
BuildArgs: map[string]string{"foo": "bar"},
Tags: []string{"test:1.0-web"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

app, err := packager.Extract("testdata/" + tt.name)
assert.NilError(t, err)

got, err := parseCompose(app, buildOptions{})
got, err := parseCompose(app, "testdata", buildOptions{}, tag)
assert.NilError(t, err)
_, ok := got["dontwant"]
assert.Assert(t, !ok, "parseCompose() should have excluded 'dontwant' service")
Expand Down
13 changes: 6 additions & 7 deletions internal/packager/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ import (
)

// findApp looks for an app in CWD or subdirs
func findApp() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", errors.Wrap(err, "cannot resolve current working directory")
}
func FindApp(cwd string) (string, error) {
if strings.HasSuffix(cwd, internal.AppExtension) {
return cwd, nil
}
Expand Down Expand Up @@ -47,8 +43,11 @@ func findApp() (string, error) {
// If nothing is found, it looks for an image and loads it
func Extract(name string, ops ...func(*types.App) error) (*types.App, error) {
if name == "" {
var err error
if name, err = findApp(); err != nil {
cwd, err := os.Getwd()
if err != nil {
return nil, errors.Wrap(err, "cannot resolve current working directory")
}
if name, err = FindApp(cwd); err != nil {
return nil, err
}
}
Expand Down

0 comments on commit e8c8507

Please sign in to comment.