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

Commit 8a0547e

Browse files
Merge pull request #517 from silvin-lubecki/show-list-reference
Show application reference in the list command
2 parents be837ae + 8f246ae commit 8a0547e

19 files changed

+260
-97
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ Commands:
351351
init Initialize Docker Application definition
352352
inspect Shows metadata, parameters and a summary of the Compose file for a given application
353353
install Install an application
354+
list List the installations and their last known installation result
354355
merge Merge a directory format Docker Application definition into a single file
355356
pull Pull an application package from a registry
356357
push Push an application package to a registry

e2e/commands_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,14 @@ func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
320320
ExitCode: 1,
321321
Err: "error decoding 'Ports': Invalid hostPort: -1",
322322
})
323-
// TODO: List the installation and check the failed status
323+
324+
// List the installation and check the failed status
325+
cmd.Command = dockerCli.Command("app", "list")
326+
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
327+
[]string{
328+
`INSTALLATION\s+APPLICATION\s+LAST ACTION\s+RESULT\s+CREATED\s+MODIFIED\s+REFERENCE`,
329+
fmt.Sprintf(`%s\s+simple \(1.1.0-beta1\)\s+install\s+failure\s+.+second[s]?\s+.+second[s]?\s+`, appName),
330+
})
324331

325332
// Upgrading a failed installation is not allowed
326333
cmd.Command = dockerCli.Command("app", "upgrade", appName)
@@ -350,6 +357,14 @@ func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
350357
fmt.Sprintf("[[:alnum:]]+ %s_api replicated [0-1]/1 python:3.6", appName),
351358
})
352359

360+
// List the installed application
361+
cmd.Command = dockerCli.Command("app", "list")
362+
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
363+
[]string{
364+
`INSTALLATION\s+APPLICATION\s+LAST ACTION\s+RESULT\s+CREATED\s+MODIFIED\s+REFERENCE`,
365+
fmt.Sprintf(`%s\s+simple \(1.1.0-beta1\)\s+install\s+success\s+.+second[s]?\s+.+second[s]?\s+`, appName),
366+
})
367+
353368
// Installing again the same application is forbidden
354369
cmd.Command = dockerCli.Command("app", "install", "testdata/simple/simple.dockerapp", "--name", appName)
355370
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
@@ -420,6 +435,6 @@ func initializeDockerAppEnvironment(t *testing.T, cmd *icmd.Cmd, tmpDir *fs.Dir,
420435
func checkContains(t *testing.T, combined string, expectedLines []string) {
421436
for _, expected := range expectedLines {
422437
exp := regexp.MustCompile(expected)
423-
assert.Assert(t, exp.MatchString(combined), expected, combined)
438+
assert.Assert(t, exp.MatchString(combined), "expected %q != actual %q", expected, combined)
424439
}
425440
}

e2e/pushpull_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,28 @@ func TestPushPullInstall(t *testing.T) {
104104
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
105105
cmd := info.configuredCmd
106106
ref := info.registryAddress + "/test/push-pull"
107-
cmd.Command = dockerCli.Command("app", "push", "--tag", ref, "--insecure-registries="+info.registryAddress, filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
107+
tag := ":v.0.0.1"
108+
cmd.Command = dockerCli.Command("app", "push", "--tag", ref+tag, "--insecure-registries="+info.registryAddress, filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
108109
icmd.RunCmd(cmd).Assert(t, icmd.Success)
109-
cmd.Command = dockerCli.Command("app", "pull", ref, "--insecure-registries="+info.registryAddress)
110+
cmd.Command = dockerCli.Command("app", "pull", ref+tag, "--insecure-registries="+info.registryAddress)
110111
icmd.RunCmd(cmd).Assert(t, icmd.Success)
111112

112113
// stop the registry
113114
info.stopRegistry()
114115

115116
// install without --pull should succeed (rely on local store)
116-
cmd.Command = dockerCli.Command("app", "install", "--insecure-registries="+info.registryAddress, ref, "--name", t.Name())
117+
cmd.Command = dockerCli.Command("app", "install", "--insecure-registries="+info.registryAddress, ref+tag, "--name", t.Name())
117118
icmd.RunCmd(cmd).Assert(t, icmd.Success)
118119
cmd.Command = dockerCli.Command("service", "ls")
119120
assert.Check(t, cmp.Contains(icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(), ref))
120121

122+
// listing the installed application shows the pulled application reference
123+
cmd.Command = dockerCli.Command("app", "list")
124+
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
125+
[]string{
126+
fmt.Sprintf(`%s\s+push-pull \(1.1.0-beta1\)\s+install\s+success\s+.+second[s]?\s+.+second[s]?\s+%s`, t.Name(), ref+tag),
127+
})
128+
121129
// install with --pull should fail (registry is stopped)
122130
cmd.Command = dockerCli.Command("app", "install", "--pull", "--insecure-registries="+info.registryAddress, ref, "--name", t.Name()+"2")
123131
assert.Check(t, cmp.Contains(icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: 1}).Combined(), "failed to resolve bundle manifest"))

e2e/testdata/plugin-usage-experimental.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Commands:
99
init Initialize Docker Application definition
1010
inspect Shows metadata, parameters and a summary of the Compose file for a given application
1111
install Install an application
12+
list List the installations and their last known installation result
1213
merge Merge a directory format Docker Application definition into a single file
1314
pull Pull an application package from a registry
1415
push Push an application package to a registry

e2e/testdata/plugin-usage.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Commands:
99
init Initialize Docker Application definition
1010
inspect Shows metadata, parameters and a summary of the Compose file for a given application
1111
install Install an application
12+
list List the installations and their last known installation result
1213
merge Merge a directory format Docker Application definition into a single file
1314
pull Pull an application package from a registry
1415
push Push an application package to a registry

internal/commands/cnab.go

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,17 @@ func getAppNameKind(name string) (string, nameKind) {
201201
return name, nameKindFile
202202
}
203203

204-
func extractAndLoadAppBasedBundle(dockerCli command.Cli, name string) (*bundle.Bundle, error) {
204+
func extractAndLoadAppBasedBundle(dockerCli command.Cli, name string) (*bundle.Bundle, string, error) {
205205
app, err := packager.Extract(name)
206206
if err != nil {
207-
return nil, err
207+
return nil, "", err
208208
}
209209
defer app.Cleanup()
210-
return makeBundleFromApp(dockerCli, app)
210+
bndl, err := makeBundleFromApp(dockerCli, app)
211+
return bndl, "", err
211212
}
212213

213-
func resolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string, pullRef bool, insecureRegistries []string) (*bundle.Bundle, error) {
214+
func resolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string, pullRef bool, insecureRegistries []string) (*bundle.Bundle, string, error) {
214215
// resolution logic:
215216
// - if there is a docker-app package in working directory, or an http:// / https:// prefix, use packager.Extract result
216217
// - the name has a .json or .cnab extension and refers to an existing file or web resource: load the bundle
@@ -220,28 +221,31 @@ func resolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name
220221
switch kind {
221222
case nameKindFile:
222223
if pullRef {
223-
return nil, errors.Errorf("%s: cannot pull when referencing a file based app", name)
224+
return nil, "", errors.Errorf("%s: cannot pull when referencing a file based app", name)
224225
}
225226
if strings.HasSuffix(name, internal.AppExtension) {
226227
return extractAndLoadAppBasedBundle(dockerCli, name)
227228
}
228-
return loader.NewLoader().Load(name)
229+
bndl, err := loader.NewLoader().Load(name)
230+
return bndl, "", err
229231
case nameKindDir, nameKindEmpty:
230232
if pullRef {
231233
if kind == nameKindDir {
232-
return nil, errors.Errorf("%s: cannot pull when referencing a directory based app", name)
234+
return nil, "", errors.Errorf("%s: cannot pull when referencing a directory based app", name)
233235
}
234-
return nil, errors.Errorf("cannot pull when referencing a directory based app")
236+
return nil, "", errors.Errorf("cannot pull when referencing a directory based app")
235237
}
236238
return extractAndLoadAppBasedBundle(dockerCli, name)
237239
case nameKindReference:
238240
ref, err := reference.ParseNormalizedNamed(name)
239241
if err != nil {
240-
return nil, errors.Wrap(err, name)
242+
return nil, "", errors.Wrap(err, name)
241243
}
242-
return bundleStore.LookupOrPullBundle(reference.TagNameOnly(ref), pullRef, dockerCli.ConfigFile(), insecureRegistries)
244+
tagRef := reference.TagNameOnly(ref)
245+
bndl, err := bundleStore.LookupOrPullBundle(tagRef, pullRef, dockerCli.ConfigFile(), insecureRegistries)
246+
return bndl, tagRef.String(), err
243247
}
244-
return nil, fmt.Errorf("could not resolve bundle %q", name)
248+
return nil, "", fmt.Errorf("could not resolve bundle %q", name)
245249
}
246250

247251
func requiredClaimBindMount(c claim.Claim, targetContextName string, dockerCli command.Cli) (bindMount, error) {
@@ -298,7 +302,7 @@ func isDockerHostLocal(host string) bool {
298302
}
299303

300304
func prepareCustomAction(actionName string, dockerCli command.Cli, appname string, stdout io.Writer,
301-
registryOpts registryOptions, pullOpts pullOptions, paramsOpts parametersOptions) (*action.RunCustom, *claim.Claim, *bytes.Buffer, error) {
305+
registryOpts registryOptions, pullOpts pullOptions, paramsOpts parametersOptions) (*action.RunCustom, *appstore.Installation, *bytes.Buffer, error) {
302306
s, err := appstore.NewApplicationStore(config.Dir())
303307
if err != nil {
304308
return nil, nil, nil, err
@@ -307,22 +311,21 @@ func prepareCustomAction(actionName string, dockerCli command.Cli, appname strin
307311
if err != nil {
308312
return nil, nil, nil, err
309313
}
310-
311-
c, err := claim.New("custom-action")
314+
driverImpl, errBuf, err := prepareDriver(dockerCli, bindMount{}, stdout)
312315
if err != nil {
313316
return nil, nil, nil, err
314317
}
315-
driverImpl, errBuf, err := prepareDriver(dockerCli, bindMount{}, stdout)
318+
bundle, ref, err := resolveBundle(dockerCli, bundleStore, appname, pullOpts.pull, registryOpts.insecureRegistries)
316319
if err != nil {
317320
return nil, nil, nil, err
318321
}
319-
bundle, err := resolveBundle(dockerCli, bundleStore, appname, pullOpts.pull, registryOpts.insecureRegistries)
322+
installation, err := appstore.NewInstallation("custom-action", ref)
320323
if err != nil {
321324
return nil, nil, nil, err
322325
}
323-
c.Bundle = bundle
326+
installation.Bundle = bundle
324327

325-
if err := mergeBundleParameters(c,
328+
if err := mergeBundleParameters(installation,
326329
withFileParameters(paramsOpts.parametersFiles),
327330
withCommandLineParameters(paramsOpts.overrides),
328331
); err != nil {
@@ -333,10 +336,10 @@ func prepareCustomAction(actionName string, dockerCli command.Cli, appname strin
333336
Action: actionName,
334337
Driver: driverImpl,
335338
}
336-
return a, c, errBuf, nil
339+
return a, installation, errBuf, nil
337340
}
338341

339-
func isInstallationFailed(installation *claim.Claim) bool {
342+
func isInstallationFailed(installation *appstore.Installation) bool {
340343
return installation.Result.Action == claim.ActionInstall &&
341344
installation.Result.Status == claim.StatusFailure
342345
}

internal/commands/inspect.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ func inspectCmd(dockerCli command.Cli) *cobra.Command {
3434

3535
func runInspect(dockerCli command.Cli, appname string, opts inspectOptions) error {
3636
defer muteDockerCli(dockerCli)()
37-
a, c, errBuf, err := prepareCustomAction(internal.ActionInspectName, dockerCli, appname, nil, opts.registryOptions, opts.pullOptions, opts.parametersOptions)
37+
action, installation, errBuf, err := prepareCustomAction(internal.ActionInspectName, dockerCli, appname, nil, opts.registryOptions, opts.pullOptions, opts.parametersOptions)
3838
if err != nil {
3939
return err
4040
}
41-
if err := a.Run(c, nil, nil); err != nil {
41+
if err := action.Run(&installation.Claim, nil, nil); err != nil {
4242
return fmt.Errorf("inspect failed: %s", errBuf)
4343
}
4444
return nil

internal/commands/install.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"os"
66

77
"github.com/deislabs/duffle/pkg/action"
8-
"github.com/deislabs/duffle/pkg/claim"
98
"github.com/deislabs/duffle/pkg/credentials"
9+
"github.com/docker/app/internal/store"
1010
"github.com/docker/cli/cli"
1111
"github.com/docker/cli/cli/command"
1212
"github.com/spf13/cobra"
@@ -80,7 +80,7 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
8080
return err
8181
}
8282

83-
bndl, err := resolveBundle(dockerCli, bundleStore, appname, opts.pull, opts.insecureRegistries)
83+
bndl, ref, err := resolveBundle(dockerCli, bundleStore, appname, opts.pull, opts.insecureRegistries)
8484
if err != nil {
8585
return err
8686
}
@@ -93,15 +93,15 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
9393
}
9494
if installation, err := installationStore.Read(installationName); err == nil {
9595
// A failed installation can be overridden, but with a warning
96-
if isInstallationFailed(&installation) {
96+
if isInstallationFailed(installation) {
9797
fmt.Fprintf(os.Stderr, "WARNING: installing over previously failed installation %q\n", installationName)
9898
} else {
9999
// Return an error in case of successful installation, or even failed upgrade, which means
100100
// their was already a successful installation.
101101
return fmt.Errorf("Installation %q already exists, use 'docker app upgrade' instead", installationName)
102102
}
103103
}
104-
c, err := claim.New(installationName)
104+
installation, err := store.NewInstallation(installationName, ref)
105105
if err != nil {
106106
return err
107107
}
@@ -110,9 +110,9 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
110110
if err != nil {
111111
return err
112112
}
113-
c.Bundle = bndl
113+
installation.Bundle = bndl
114114

115-
if err := mergeBundleParameters(c,
115+
if err := mergeBundleParameters(installation,
116116
withFileParameters(opts.parametersFiles),
117117
withCommandLineParameters(opts.overrides),
118118
withOrchestratorParameters(opts.orchestrator, opts.kubeNamespace),
@@ -131,10 +131,10 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
131131
inst := &action.Install{
132132
Driver: driverImpl,
133133
}
134-
err = inst.Run(c, creds, os.Stdout)
134+
err = inst.Run(&installation.Claim, creds, os.Stdout)
135135
// Even if the installation failed, the installation is persisted with its failure status,
136136
// so any installation needs a clean uninstallation.
137-
err2 := installationStore.Store(*c)
137+
err2 := installationStore.Store(installation)
138138
if err != nil {
139139
return fmt.Errorf("Installation failed: %s", errBuf)
140140
}

internal/commands/list.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"strings"
7+
"text/tabwriter"
8+
"time"
9+
10+
"github.com/docker/app/internal/store"
11+
"github.com/docker/cli/cli"
12+
"github.com/docker/cli/cli/command"
13+
"github.com/docker/cli/cli/config"
14+
units "github.com/docker/go-units"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
type listOptions struct {
19+
targetContext string
20+
}
21+
22+
var (
23+
listColumns = []struct {
24+
header string
25+
value func(i *store.Installation) string
26+
}{
27+
{"INSTALLATION", func(i *store.Installation) string { return i.Name }},
28+
{"APPLICATION", func(i *store.Installation) string { return fmt.Sprintf("%s (%s)", i.Bundle.Name, i.Bundle.Version) }},
29+
{"LAST ACTION", func(i *store.Installation) string { return i.Result.Action }},
30+
{"RESULT", func(i *store.Installation) string { return i.Result.Status }},
31+
{"CREATED", func(i *store.Installation) string { return units.HumanDuration(time.Since(i.Created)) }},
32+
{"MODIFIED", func(i *store.Installation) string { return units.HumanDuration(time.Since(i.Modified)) }},
33+
{"REFERENCE", func(i *store.Installation) string { return i.Reference }},
34+
}
35+
)
36+
37+
func listCmd(dockerCli command.Cli) *cobra.Command {
38+
var opts listOptions
39+
40+
cmd := &cobra.Command{
41+
Use: "list [OPTIONS]",
42+
Short: "List the installations and their last known installation result",
43+
Aliases: []string{"ls"},
44+
Args: cli.NoArgs,
45+
RunE: func(cmd *cobra.Command, args []string) error {
46+
return runList(dockerCli, opts)
47+
},
48+
}
49+
cmd.Flags().StringVar(&opts.targetContext, "target-context", "", "List installations on this context")
50+
51+
return cmd
52+
}
53+
54+
func runList(dockerCli command.Cli, opts listOptions) error {
55+
targetContext := getTargetContext(opts.targetContext, dockerCli.CurrentContext())
56+
57+
appstore, err := store.NewApplicationStore(config.Dir())
58+
if err != nil {
59+
return err
60+
}
61+
installationStore, err := appstore.InstallationStore(targetContext)
62+
if err != nil {
63+
return err
64+
}
65+
66+
installations, err := installationStore.List()
67+
if err != nil {
68+
return err
69+
}
70+
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
71+
printHeaders(w)
72+
73+
for _, name := range installations {
74+
installation, err := installationStore.Read(name)
75+
if err != nil {
76+
return err
77+
}
78+
printValues(w, installation)
79+
}
80+
return w.Flush()
81+
}
82+
83+
func printHeaders(w io.Writer) {
84+
var headers []string
85+
for _, column := range listColumns {
86+
headers = append(headers, column.header)
87+
}
88+
fmt.Fprintln(w, strings.Join(headers, "\t"))
89+
}
90+
91+
func printValues(w io.Writer, installation *store.Installation) {
92+
var values []string
93+
for _, column := range listColumns {
94+
values = append(values, column.value(installation))
95+
}
96+
fmt.Fprintln(w, strings.Join(values, "\t"))
97+
}

0 commit comments

Comments
 (0)