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

Commit 6a9e04b

Browse files
authored
Merge pull request #675 from rumpl/fix-unknown-app-install
Better app install error reporting
2 parents dc8bf73 + 459b24c commit 6a9e04b

File tree

12 files changed

+72
-88
lines changed

12 files changed

+72
-88
lines changed

README.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,9 @@ Removing network hello_default
263263

264264
## Installation
265265

266-
**Note**: Docker app is a _command line_ plugin (not be confused with docker _engine_ plugins), extending the `docker` command with `app` sub-commands.
267-
It requires [Docker CLI](https://download.docker.com) 19.03.0 or later with experimental features enabled.
268-
Either set environment variable `DOCKER_CLI_EXPERIMENTAL=enabled`
266+
**Note**: Docker app is a _command line_ plugin (not be confused with docker _engine_ plugins), extending the `docker` command with `app` sub-commands.
267+
It requires [Docker CLI](https://download.docker.com) 19.03.0 or later with experimental features enabled.
268+
Either set environment variable `DOCKER_CLI_EXPERIMENTAL=enabled`
269269
or update your [docker CLI configuration](https://docs.docker.com/engine/reference/commandline/cli/#experimental-features).
270270

271271
**Note**: Docker-app can't be installed using the `docker plugin install` command (yet)
@@ -336,10 +336,7 @@ $ docker app inspect myhubuser/myimage
336336

337337
The first time a command is executed against a given image name the bundle is
338338
pulled from the registry and put in the local bundle store. You can pre-populate
339-
this store by running `docker app pull myhubuser/myimage:latest`. All commands
340-
manipulating a package also accept a `--pull` flag to force pulling the bundle
341-
from the registry, even if it is present in the local store. This can be useful
342-
when you are repeatedly pushing a bundle on the same tag.
339+
this store by running `docker app pull myhubuser/myimage:latest`.
343340

344341
### Multi-arch applications
345342

e2e/pushpull_test.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func TestPushInstall(t *testing.T) {
144144
cmd.Command = dockerCli.Command("app", "push", "--tag", ref, filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
145145
icmd.RunCmd(cmd).Assert(t, icmd.Success)
146146

147-
cmd.Command = dockerCli.Command("app", "install", ref, "--pull", "--name", t.Name())
147+
cmd.Command = dockerCli.Command("app", "install", ref, "--name", t.Name())
148148
icmd.RunCmd(cmd).Assert(t, icmd.Success)
149149
cmd.Command = dockerCli.Command("service", "ls")
150150
assert.Check(t, cmp.Contains(icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(), ref))
@@ -164,7 +164,7 @@ func TestPushPullInstall(t *testing.T) {
164164
// stop the registry
165165
info.stopRegistry()
166166

167-
// install without --pull should succeed (rely on local store)
167+
// install from local store
168168
cmd.Command = dockerCli.Command("app", "install", ref+tag, "--name", t.Name())
169169
icmd.RunCmd(cmd).Assert(t, icmd.Success)
170170
cmd.Command = dockerCli.Command("service", "ls")
@@ -177,9 +177,15 @@ func TestPushPullInstall(t *testing.T) {
177177
fmt.Sprintf(`%s\s+push-pull \(1.1.0-beta1\)\s+install\s+success\s+.+second[s]?\sago\s+.+second[s]?\sago\s+%s`, t.Name(), ref+tag),
178178
})
179179

180-
// install with --pull should fail (registry is stopped)
181-
cmd.Command = dockerCli.Command("app", "install", "--pull", ref, "--name", t.Name()+"2")
182-
assert.Check(t, cmp.Contains(icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: 1}).Combined(), "failed to resolve bundle manifest"))
180+
// install should fail (registry is stopped)
181+
cmd.Command = dockerCli.Command("app", "install", "unknown")
182+
//nolint: lll
183+
expected := `Unable to find application image "unknown:latest" locally
184+
Unable to find application "unknown": failed to resolve bundle manifest "docker.io/library/unknown:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed`
185+
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
186+
ExitCode: 1,
187+
Err: expected,
188+
})
183189
})
184190
}
185191

@@ -198,7 +204,7 @@ func TestPushInstallBundle(t *testing.T) {
198204
cmd.Command = dockerCli.Command("app", "push", "--tag", ref, "a-simple-app:1.0.0")
199205
icmd.RunCmd(cmd).Assert(t, icmd.Success)
200206

201-
cmd.Command = dockerCli.Command("app", "install", ref, "--pull", "--name", name)
207+
cmd.Command = dockerCli.Command("app", "install", ref, "--name", name)
202208
icmd.RunCmd(cmd).Assert(t, icmd.Success)
203209
cmd.Command = dockerCli.Command("service", "ls")
204210
assert.Check(t, cmp.Contains(icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(), ref))
@@ -218,7 +224,7 @@ func TestPushInstallBundle(t *testing.T) {
218224
cmd.Command = dockerCli.Command("app", "push", "--tag", ref2, ref+":latest")
219225
icmd.RunCmd(cmd).Assert(t, icmd.Success)
220226

221-
cmd.Command = dockerCli.Command("app", "install", ref2, "--pull", "--name", name)
227+
cmd.Command = dockerCli.Command("app", "install", ref2, "--name", name)
222228
icmd.RunCmd(cmd).Assert(t, icmd.Success)
223229
cmd.Command = dockerCli.Command("service", "ls")
224230
assert.Check(t, cmp.Contains(icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(), ref2))
@@ -245,7 +251,7 @@ func TestPushInstallBundle(t *testing.T) {
245251
// remove the bundle from the bundle store to be sure it won't be used instead of registry
246252
cleanupIsolatedStore()
247253
// install from the registry
248-
cmd.Command = dockerCli.Command("app", "install", ref2, "--pull", "--name", name)
254+
cmd.Command = dockerCli.Command("app", "install", ref2, "--name", name)
249255
icmd.RunCmd(cmd).Assert(t, icmd.Success)
250256
cmd.Command = dockerCli.Command("service", "ls")
251257
assert.Check(t, cmp.Contains(icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(), ref))

internal/commands/cnab.go

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ import (
1717
"github.com/deislabs/cnab-go/driver"
1818
dockerDriver "github.com/deislabs/cnab-go/driver/docker"
1919
"github.com/docker/app/internal"
20+
"github.com/docker/app/internal/log"
2021
"github.com/docker/app/internal/packager"
2122
appstore "github.com/docker/app/internal/store"
2223
"github.com/docker/cli/cli/command"
2324
"github.com/docker/cli/cli/config"
2425
"github.com/docker/cli/cli/context/docker"
2526
"github.com/docker/cli/cli/context/store"
2627
contextstore "github.com/docker/cli/cli/context/store"
28+
"github.com/docker/cnab-to-oci/remotes"
2729
"github.com/docker/distribution/reference"
2830
"github.com/docker/docker/api/types"
2931
"github.com/docker/docker/api/types/container"
@@ -255,7 +257,7 @@ func loadBundleFromFile(filename string) (*bundle.Bundle, error) {
255257
//resolveBundle looks for a CNAB bundle which can be in a Docker App Package format or
256258
// a bundle stored locally or in the bundle store. It returns a built or found bundle,
257259
// a reference to the bundle if it is found in the bundlestore, and an error.
258-
func resolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string, pullRef bool) (*bundle.Bundle, string, error) {
260+
func resolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string) (*bundle.Bundle, string, error) {
259261
// resolution logic:
260262
// - if there is a docker-app package in working directory, or an http:// / https:// prefix, use packager.Extract result
261263
// - the name has a .json or .cnab extension and refers to an existing file or web resource: load the bundle
@@ -264,41 +266,58 @@ func resolveBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name
264266
name, kind := getAppNameKind(name)
265267
switch kind {
266268
case nameKindFile:
267-
if pullRef {
268-
return nil, "", errors.Errorf("%s: cannot pull when referencing a file based app", name)
269-
}
270269
if strings.HasSuffix(name, internal.AppExtension) {
271270
return extractAndLoadAppBasedBundle(dockerCli, name)
272271
}
273272
bndl, err := loadBundleFromFile(name)
274273
return bndl, "", err
275274
case nameKindDir, nameKindEmpty:
276-
if pullRef {
277-
if kind == nameKindDir {
278-
return nil, "", errors.Errorf("%s: cannot pull when referencing a directory based app", name)
279-
}
280-
return nil, "", errors.Errorf("cannot pull when referencing a directory based app")
281-
}
282275
return extractAndLoadAppBasedBundle(dockerCli, name)
283276
case nameKindReference:
284-
bndl, tagRef, err := getLocalBundle(dockerCli, bundleStore, name, pullRef)
277+
bndl, tagRef, err := getBundle(dockerCli, bundleStore, name)
278+
if err != nil {
279+
return nil, "", err
280+
}
285281
return bndl, tagRef.String(), err
286282
}
287283
return nil, "", fmt.Errorf("could not resolve bundle %q", name)
288284
}
289285

290-
func getLocalBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string, pullRef bool) (*bundle.Bundle, reference.Named, error) {
286+
func getBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, name string) (*bundle.Bundle, reference.Named, error) {
291287
ref, err := reference.ParseNormalizedNamed(name)
292288
if err != nil {
293289
return nil, nil, errors.Wrap(err, name)
294290
}
295291
tagRef := reference.TagNameOnly(ref)
292+
293+
bndl, err := bundleStore.Read(tagRef)
294+
if err != nil {
295+
fmt.Fprintf(dockerCli.Err(), "Unable to find application image %q locally\n", reference.FamiliarString(tagRef))
296+
297+
bndl, err = pullBundle(dockerCli, bundleStore, tagRef)
298+
if err != nil {
299+
return nil, nil, err
300+
}
301+
return bndl, tagRef, nil
302+
}
303+
304+
return bndl, tagRef, nil
305+
}
306+
307+
func pullBundle(dockerCli command.Cli, bundleStore appstore.BundleStore, tagRef reference.Named) (*bundle.Bundle, error) {
296308
insecureRegistries, err := insecureRegistriesFromEngine(dockerCli)
297309
if err != nil {
298-
return nil, tagRef, fmt.Errorf("could not retrieve insecure registries: %v", err)
310+
return nil, fmt.Errorf("could not retrieve insecure registries: %v", err)
311+
}
312+
313+
bndl, err := remotes.Pull(log.WithLogContext(context.Background()), reference.TagNameOnly(tagRef), remotes.CreateResolver(dockerCli.ConfigFile(), insecureRegistries...))
314+
if err != nil {
315+
return nil, err
316+
}
317+
if err := bundleStore.Store(tagRef, bndl); err != nil {
318+
return nil, err
299319
}
300-
bndl, err := bundleStore.LookupOrPullBundle(tagRef, pullRef, dockerCli.ConfigFile(), insecureRegistries)
301-
return bndl, tagRef, err
320+
return bndl, nil
302321
}
303322

304323
func requiredClaimBindMount(c claim.Claim, targetContextName string, dockerCli command.Cli) (bindMount, error) {
@@ -354,8 +373,7 @@ func isDockerHostLocal(host string) bool {
354373
return host == "" || strings.HasPrefix(host, "unix://") || strings.HasPrefix(host, "npipe://")
355374
}
356375

357-
func prepareCustomAction(actionName string, dockerCli command.Cli, appname string, stdout io.Writer,
358-
pullOpts pullOptions, paramsOpts parametersOptions) (*action.RunCustom, *appstore.Installation, *bytes.Buffer, error) {
376+
func prepareCustomAction(actionName string, dockerCli command.Cli, appname string, stdout io.Writer, paramsOpts parametersOptions) (*action.RunCustom, *appstore.Installation, *bytes.Buffer, error) {
359377
s, err := appstore.NewApplicationStore(config.Dir())
360378
if err != nil {
361379
return nil, nil, nil, err
@@ -364,7 +382,7 @@ func prepareCustomAction(actionName string, dockerCli command.Cli, appname strin
364382
if err != nil {
365383
return nil, nil, nil, err
366384
}
367-
bundle, ref, err := resolveBundle(dockerCli, bundleStore, appname, pullOpts.pull)
385+
bundle, ref, err := resolveBundle(dockerCli, bundleStore, appname)
368386
if err != nil {
369387
return nil, nil, nil, err
370388
}

internal/commands/image/tag_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"gotest.tools/assert"
88

99
"github.com/deislabs/cnab-go/bundle"
10-
"github.com/docker/cli/cli/config/configfile"
1110
"github.com/docker/distribution/reference"
1211
)
1312

@@ -44,10 +43,6 @@ func (b *bundleStoreStub) Remove(ref reference.Named) error {
4443
return nil
4544
}
4645

47-
func (b *bundleStoreStub) LookupOrPullBundle(ref reference.Named, pullRef bool, config *configfile.ConfigFile, insecureRegistries []string) (*bundle.Bundle, error) {
48-
return nil, nil
49-
}
50-
5146
var mockedBundleStore = &bundleStoreStub{}
5247

5348
func TestInvalidSourceReference(t *testing.T) {

internal/commands/inspect.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func runInspect(dockerCli command.Cli, appname string, opts inspectOptions) erro
4343
if err != nil {
4444
return err
4545
}
46-
bndl, ref, err := getLocalBundle(dockerCli, bundleStore, appname, false)
46+
bndl, ref, err := getBundle(dockerCli, bundleStore, appname)
4747

4848
if err != nil {
4949
return err

internal/commands/install.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import (
1010
"github.com/docker/cli/cli"
1111
"github.com/docker/cli/cli/command"
1212
"github.com/docker/docker/pkg/namesgenerator"
13+
"github.com/pkg/errors"
1314
"github.com/sirupsen/logrus"
1415
"github.com/spf13/cobra"
1516
)
1617

1718
type installOptions struct {
1819
parametersOptions
1920
credentialOptions
20-
pullOptions
2121
orchestrator string
2222
kubeNamespace string
2323
stackName string
@@ -59,7 +59,6 @@ func installCmd(dockerCli command.Cli) *cobra.Command {
5959
}
6060
opts.parametersOptions.addFlags(cmd.Flags())
6161
opts.credentialOptions.addFlags(cmd.Flags())
62-
opts.pullOptions.addFlags(cmd.Flags())
6362
cmd.Flags().StringVar(&opts.orchestrator, "orchestrator", "", "Orchestrator to install on (swarm, kubernetes)")
6463
cmd.Flags().StringVar(&opts.kubeNamespace, "namespace", "default", "Kubernetes namespace to install into")
6564
cmd.Flags().StringVar(&opts.stackName, "name", "", "Assign a name to the installation")
@@ -68,7 +67,6 @@ func installCmd(dockerCli command.Cli) *cobra.Command {
6867
}
6968

7069
func runInstall(dockerCli command.Cli, appname string, opts installOptions) error {
71-
defer muteDockerCli(dockerCli)()
7270
opts.SetDefaultTargetContext(dockerCli)
7371

7472
bind, err := requiredBindMount(opts.targetContext, opts.orchestrator, dockerCli.ContextStore())
@@ -80,9 +78,9 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
8078
return err
8179
}
8280

83-
bndl, ref, err := resolveBundle(dockerCli, bundleStore, appname, opts.pull)
81+
bndl, ref, err := resolveBundle(dockerCli, bundleStore, appname)
8482
if err != nil {
85-
return err
83+
return errors.Wrapf(err, "Unable to find application %q", appname)
8684
}
8785
if err := bndl.Validate(); err != nil {
8886
return err
@@ -131,7 +129,10 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
131129
inst := &action.Install{
132130
Driver: driverImpl,
133131
}
134-
err = inst.Run(&installation.Claim, creds, os.Stdout)
132+
{
133+
defer muteDockerCli(dockerCli)()
134+
err = inst.Run(&installation.Claim, creds, os.Stdout)
135+
}
135136
// Even if the installation failed, the installation is persisted with its failure status,
136137
// so any installation needs a clean uninstallation.
137138
err2 := installationStore.Store(installation)

internal/commands/pull.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/docker/cli/cli"
99
"github.com/docker/cli/cli/command"
1010
"github.com/docker/cli/cli/config"
11+
"github.com/docker/distribution/reference"
1112
"github.com/pkg/errors"
1213
"github.com/spf13/cobra"
1314
)
@@ -34,8 +35,13 @@ func runPull(dockerCli command.Cli, name string) error {
3435
if err != nil {
3536
return err
3637
}
38+
ref, err := reference.ParseNormalizedNamed(name)
39+
if err != nil {
40+
return errors.Wrap(err, name)
41+
}
42+
tagRef := reference.TagNameOnly(ref)
3743

38-
bndl, ref, err := getLocalBundle(dockerCli, bundleStore, name, true)
44+
bndl, err := pullBundle(dockerCli, bundleStore, tagRef)
3945
if err != nil {
4046
return errors.Wrap(err, name)
4147
}

internal/commands/push.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func resolveReferenceAndBundle(dockerCli command.Cli, name string) (*bundle.Bund
102102
return nil, "", err
103103
}
104104

105-
bndl, ref, err := resolveBundle(dockerCli, bundleStore, name, false)
105+
bndl, ref, err := resolveBundle(dockerCli, bundleStore, name)
106106
if err != nil {
107107
return nil, "", err
108108
}

internal/commands/render.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313

1414
type renderOptions struct {
1515
parametersOptions
16-
pullOptions
17-
1816
formatDriver string
1917
renderOutput string
2018
}
@@ -32,7 +30,6 @@ func renderCmd(dockerCli command.Cli) *cobra.Command {
3230
},
3331
}
3432
opts.parametersOptions.addFlags(cmd.Flags())
35-
opts.pullOptions.addFlags(cmd.Flags())
3633
cmd.Flags().StringVarP(&opts.renderOutput, "output", "o", "-", "Output file")
3734
cmd.Flags().StringVar(&opts.formatDriver, "formatter", "yaml", "Configure the output format (yaml|json)")
3835

@@ -52,7 +49,7 @@ func runRender(dockerCli command.Cli, appname string, opts renderOptions) error
5249
w = f
5350
}
5451

55-
action, installation, errBuf, err := prepareCustomAction(internal.ActionRenderName, dockerCli, appname, w, opts.pullOptions, opts.parametersOptions)
52+
action, installation, errBuf, err := prepareCustomAction(internal.ActionRenderName, dockerCli, appname, w, opts.parametersOptions)
5653
if err != nil {
5754
return err
5855
}

internal/commands/root.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,6 @@ func (o *credentialOptions) CredentialSetOpts(dockerCli command.Cli, credentialS
149149
}
150150
}
151151

152-
type pullOptions struct {
153-
pull bool
154-
}
155-
156-
func (o *pullOptions) addFlags(flags *pflag.FlagSet) {
157-
flags.BoolVar(&o.pull, "pull", false, "Pull the bundle")
158-
}
159-
160152
// insecureRegistriesFromEngine reads the registry configuration from the daemon and returns
161153
// a list of all insecure ones.
162154
func insecureRegistriesFromEngine(dockerCli command.Cli) ([]string, error) {

0 commit comments

Comments
 (0)