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

Commit bcff419

Browse files
Installation is ok on a previously failed installation
Upgrading a failed installation is not ok Signed-off-by: Silvin Lubecki <[email protected]>
1 parent 8cf7e62 commit bcff419

File tree

4 files changed

+86
-44
lines changed

4 files changed

+86
-44
lines changed

e2e/commands_test.go

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -305,53 +305,35 @@ func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
305305
cmd, cleanup := dockerCli.createTestCmd()
306306
defer cleanup()
307307
appName := strings.Replace(t.Name(), "/", "_", 1)
308-
309308
tmpDir := fs.NewDir(t, appName)
310309
defer tmpDir.Remove()
311-
312-
cmd.Env = append(cmd.Env, "DOCKER_TARGET_CONTEXT=swarm-target-context")
313-
314310
// Running a swarm using docker in docker to install the application
315311
// and run the invocation image
316312
swarm := NewContainer("docker:18.09-dind", 2375)
317313
swarm.Start(t)
318314
defer swarm.Stop(t)
315+
initializeDockerAppEnvironment(t, &cmd, tmpDir, swarm, useBindMount)
319316

320-
// The dind doesn't have the cnab-app-base image so we save it in order to load it later
321-
icmd.RunCommand(dockerCli.path, "save", fmt.Sprintf("docker/cnab-app-base:%s", internal.Version), "--output", tmpDir.Join("cnab-app-base.tar.gz")).Assert(t, icmd.Success)
322-
323-
// We need two contexts:
324-
// - one for `docker` so that it connects to the dind swarm created before
325-
// - the target context for the invocation image to install within the swarm
326-
cmd.Command = dockerCli.Command("context", "create", "swarm-context", "--docker", fmt.Sprintf(`"host=tcp://%s"`, swarm.GetAddress(t)), "--default-stack-orchestrator", "swarm")
327-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
328-
329-
// When creating a context on a Windows host we cannot use
330-
// the unix socket but it's needed inside the invocation image.
331-
// The workaround is to create a context with an empty host.
332-
// This host will default to the unix socket inside the
333-
// invocation image
334-
host := "host="
335-
if !useBindMount {
336-
host += fmt.Sprintf("tcp://%s", swarm.GetPrivateAddress(t))
337-
}
338-
339-
cmd.Command = dockerCli.Command("context", "create", "swarm-target-context", "--docker", host, "--default-stack-orchestrator", "swarm")
340-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
341-
342-
// Initialize the swarm
343-
cmd.Env = append(cmd.Env, "DOCKER_CONTEXT=swarm-context")
344-
cmd.Command = dockerCli.Command("swarm", "init")
345-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
317+
// Install an illformed Docker Application Package
318+
cmd.Command = dockerCli.Command("app", "install", "testdata/simple/simple.dockerapp", "--set", "web_port=-1", "--name", appName)
319+
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
320+
ExitCode: 1,
321+
Err: "error decoding 'Ports': Invalid hostPort: -1",
322+
})
323+
// TODO: List the installation and check the failed status
346324

347-
// Load the needed base cnab image into the swarm docker engine
348-
cmd.Command = dockerCli.Command("load", "--input", tmpDir.Join("cnab-app-base.tar.gz"))
349-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
325+
// Upgrading a failed installation is not allowed
326+
cmd.Command = dockerCli.Command("app", "upgrade", appName)
327+
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
328+
ExitCode: 1,
329+
Err: fmt.Sprintf("Installation %q has failed and cannot be upgraded, reinstall it using 'docker app install'", appName),
330+
})
350331

351-
// Install a Docker Application Package
332+
// Install a Docker Application Package with an existing failed installation is fine
352333
cmd.Command = dockerCli.Command("app", "install", "testdata/simple/simple.dockerapp", "--name", appName)
353334
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
354335
[]string{
336+
fmt.Sprintf("WARNING: installing over previously failed installation %q", appName),
355337
fmt.Sprintf("Creating network %s_back", appName),
356338
fmt.Sprintf("Creating network %s_front", appName),
357339
fmt.Sprintf("Creating service %s_db", appName),
@@ -368,6 +350,13 @@ func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
368350
fmt.Sprintf("[[:alnum:]]+ %s_api replicated [0-1]/1 python:3.6", appName),
369351
})
370352

353+
// Installing again the same application is forbidden
354+
cmd.Command = dockerCli.Command("app", "install", "testdata/simple/simple.dockerapp", "--name", appName)
355+
icmd.RunCmd(cmd).Assert(t, icmd.Expected{
356+
ExitCode: 1,
357+
Err: fmt.Sprintf("Installation %q already exists, use 'docker app upgrade' instead", appName),
358+
})
359+
371360
// Upgrade the application, changing the port
372361
cmd.Command = dockerCli.Command("app", "upgrade", appName, "--set", "web_port=8081")
373362
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
@@ -393,6 +382,41 @@ func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
393382
})
394383
}
395384

385+
func initializeDockerAppEnvironment(t *testing.T, cmd *icmd.Cmd, tmpDir *fs.Dir, swarm *Container, useBindMount bool) {
386+
cmd.Env = append(cmd.Env, "DOCKER_TARGET_CONTEXT=swarm-target-context")
387+
388+
// The dind doesn't have the cnab-app-base image so we save it in order to load it later
389+
icmd.RunCommand(dockerCli.path, "save", fmt.Sprintf("docker/cnab-app-base:%s", internal.Version), "--output", tmpDir.Join("cnab-app-base.tar.gz")).Assert(t, icmd.Success)
390+
391+
// We need two contexts:
392+
// - one for `docker` so that it connects to the dind swarm created before
393+
// - the target context for the invocation image to install within the swarm
394+
cmd.Command = dockerCli.Command("context", "create", "swarm-context", "--docker", fmt.Sprintf(`"host=tcp://%s"`, swarm.GetAddress(t)), "--default-stack-orchestrator", "swarm")
395+
icmd.RunCmd(*cmd).Assert(t, icmd.Success)
396+
397+
// When creating a context on a Windows host we cannot use
398+
// the unix socket but it's needed inside the invocation image.
399+
// The workaround is to create a context with an empty host.
400+
// This host will default to the unix socket inside the
401+
// invocation image
402+
host := "host="
403+
if !useBindMount {
404+
host += fmt.Sprintf("tcp://%s", swarm.GetPrivateAddress(t))
405+
}
406+
407+
cmd.Command = dockerCli.Command("context", "create", "swarm-target-context", "--docker", host, "--default-stack-orchestrator", "swarm")
408+
icmd.RunCmd(*cmd).Assert(t, icmd.Success)
409+
410+
// Initialize the swarm
411+
cmd.Env = append(cmd.Env, "DOCKER_CONTEXT=swarm-context")
412+
cmd.Command = dockerCli.Command("swarm", "init")
413+
icmd.RunCmd(*cmd).Assert(t, icmd.Success)
414+
415+
// Load the needed base cnab image into the swarm docker engine
416+
cmd.Command = dockerCli.Command("load", "--input", tmpDir.Join("cnab-app-base.tar.gz"))
417+
icmd.RunCmd(*cmd).Assert(t, icmd.Success)
418+
}
419+
396420
func checkContains(t *testing.T, combined string, expectedLines []string) {
397421
for _, expected := range expectedLines {
398422
exp := regexp.MustCompile(expected)

internal/commands/cnab.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,8 @@ func prepareCustomAction(actionName string, dockerCli command.Cli, appname strin
335335
}
336336
return a, c, errBuf, nil
337337
}
338+
339+
func isInstallationFailed(installation *claim.Claim) bool {
340+
return installation.Result.Action == claim.ActionInstall &&
341+
installation.Result.Status == claim.StatusFailure
342+
}

internal/commands/install.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package commands
22

33
import (
44
"fmt"
5+
"os"
56

67
"github.com/deislabs/duffle/pkg/action"
78
"github.com/deislabs/duffle/pkg/claim"
@@ -90,8 +91,15 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
9091
if installationName == "" {
9192
installationName = bndl.Name
9293
}
93-
if _, err = installationStore.Read(installationName); err == nil {
94-
return fmt.Errorf("installation %q already exists", installationName)
94+
if installation, err := installationStore.Read(installationName); err == nil {
95+
// A failed installation can be overridden, but with a warning
96+
if isInstallationFailed(&installation) {
97+
fmt.Fprintf(os.Stderr, "WARNING: installing over previously failed installation %q\n", installationName)
98+
} else {
99+
// Return an error in case of successful installation, or even failed upgrade, which means
100+
// their was already a successful installation.
101+
return fmt.Errorf("Installation %q already exists, use 'docker app upgrade' instead", installationName)
102+
}
95103
}
96104
c, err := claim.New(installationName)
97105
if err != nil {

internal/commands/upgrade.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package commands
22

33
import (
44
"fmt"
5+
"os"
56

67
"github.com/deislabs/duffle/pkg/action"
78
"github.com/deislabs/duffle/pkg/credentials"
@@ -46,46 +47,50 @@ func runUpgrade(dockerCli command.Cli, installationName string, opts upgradeOpti
4647
return err
4748
}
4849

49-
c, err := installationStore.Read(installationName)
50+
installation, err := installationStore.Read(installationName)
5051
if err != nil {
5152
return err
5253
}
5354

55+
if isInstallationFailed(&installation) {
56+
return fmt.Errorf("Installation %q has failed and cannot be upgraded, reinstall it using 'docker app install'", installationName)
57+
}
58+
5459
if opts.bundleOrDockerApp != "" {
5560
b, err := resolveBundle(dockerCli, bundleStore, opts.bundleOrDockerApp, opts.pull, opts.insecureRegistries)
5661
if err != nil {
5762
return err
5863
}
59-
c.Bundle = b
64+
installation.Bundle = b
6065
}
61-
if err := mergeBundleParameters(&c,
66+
if err := mergeBundleParameters(&installation,
6267
withFileParameters(opts.parametersFiles),
6368
withCommandLineParameters(opts.overrides),
6469
withSendRegistryAuth(opts.sendRegistryAuth),
6570
); err != nil {
6671
return err
6772
}
6873

69-
bind, err := requiredClaimBindMount(c, opts.targetContext, dockerCli)
74+
bind, err := requiredClaimBindMount(installation, opts.targetContext, dockerCli)
7075
if err != nil {
7176
return err
7277
}
7378
driverImpl, errBuf, err := prepareDriver(dockerCli, bind, nil)
7479
if err != nil {
7580
return err
7681
}
77-
creds, err := prepareCredentialSet(c.Bundle, opts.CredentialSetOpts(dockerCli, credentialStore)...)
82+
creds, err := prepareCredentialSet(installation.Bundle, opts.CredentialSetOpts(dockerCli, credentialStore)...)
7883
if err != nil {
7984
return err
8085
}
81-
if err := credentials.Validate(creds, c.Bundle.Credentials); err != nil {
86+
if err := credentials.Validate(creds, installation.Bundle.Credentials); err != nil {
8287
return err
8388
}
8489
u := &action.Upgrade{
8590
Driver: driverImpl,
8691
}
87-
err = u.Run(&c, creds, dockerCli.Out())
88-
err2 := installationStore.Store(c)
92+
err = u.Run(&installation, creds, os.Stdout)
93+
err2 := installationStore.Store(installation)
8994
if err != nil {
9095
return fmt.Errorf("upgrade failed: %s", errBuf)
9196
}

0 commit comments

Comments
 (0)