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

Commit 137c8cd

Browse files
Merge pull request #518 from silvin-lubecki/status-display-bundle-infos
Status display bundle infos
2 parents 7b03132 + 2ed726a commit 137c8cd

File tree

10 files changed

+184
-9
lines changed

10 files changed

+184
-9
lines changed

e2e/cnab_test.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,23 @@ func TestCallCustomStatusAction(t *testing.T) {
2020
cnab string
2121
}{
2222
{
23-
name: "validCustomStatusAction",
23+
name: "validCustomDockerStatusAction",
2424
exitCode: 0,
25-
expectedOutput: "Status action",
26-
cnab: "cnab-with-status",
25+
expectedOutput: "com.docker.app.status",
26+
cnab: "cnab-with-docker-status",
2727
},
28+
{
29+
name: "validCustomStandardStatusAction",
30+
exitCode: 0,
31+
expectedOutput: "io.cnab.status",
32+
cnab: "cnab-with-standard-status",
33+
},
34+
// A CNAB bundle without standard or docker status action still can output
35+
// some informations about the installation.
2836
{
2937
name: "missingCustomStatusAction",
30-
exitCode: 1,
31-
expectedOutput: "status failed: action not defined for bundle",
38+
exitCode: 0,
39+
expectedOutput: "Name: missingCustomStatusAction",
3240
cnab: "cnab-without-status",
3341
},
3442
}

e2e/commands_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,30 @@ func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
352352
cmd.Command = dockerCli.Command("app", "status", appName)
353353
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
354354
[]string{
355+
`INSTALLATION
356+
------------
357+
Name: TestDockerAppLifecycle_.*
358+
Created: .*
359+
Modified: .*
360+
Revision: .*
361+
Last Action: install
362+
Result: SUCCESS
363+
Orchestrator: swarm
364+
365+
APPLICATION
366+
-----------
367+
Name: simple
368+
Version: 1.1.0-beta1
369+
Reference:.*
370+
371+
PARAMETERS
372+
----------
373+
api_host: example.com
374+
static_subdir: data/static
375+
web_port: 8082
376+
377+
STATUS
378+
------`,
355379
fmt.Sprintf("[[:alnum:]]+ %s_db replicated [0-1]/1 postgres:9.3", appName),
356380
fmt.Sprintf(`[[:alnum:]]+ %s_web replicated [0-1]/1 nginx:latest \*:8082->80/tcp`, appName),
357381
fmt.Sprintf("[[:alnum:]]+ %s_api replicated [0-1]/1 python:3.6", appName),

e2e/testdata/cnab-with-status/bundle.json renamed to e2e/testdata/cnab-with-docker-status/bundle.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"name": "cnab-with-status",
2+
"name": "cnab-with-docker-status",
33
"version": "0.1.0",
44
"invocationImages": [
55
{
66
"imageType": "docker",
7-
"image": "e2e/cnab-with-status:v0.1.0"
7+
"image": "e2e/cnab-with-docker-status:v0.1.0"
88
}
99
],
1010
"actions": {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "cnab-with-standard-status",
3+
"version": "0.1.0",
4+
"invocationImages": [
5+
{
6+
"imageType": "docker",
7+
"image": "e2e/cnab-with-standard-status:v0.1.0"
8+
}
9+
],
10+
"actions": {
11+
"io.cnab.status": {
12+
"modifies": false
13+
}
14+
}
15+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/sh
2+
3+
action=$CNAB_ACTION
4+
name=$CNAB_INSTALLATION_NAME
5+
6+
case $action in
7+
install)
8+
echo "Install action"
9+
;;
10+
uninstall)
11+
echo "uninstall action"
12+
;;
13+
upgrade)
14+
echo "Upgrade action"
15+
;;
16+
io.cnab.status)
17+
echo "Status action"
18+
;;
19+
*)
20+
echo "No action for $action"
21+
;;
22+
esac
23+
echo "Action $action complete for $name"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ARG ALPINE_VERSION=3.9.2
2+
3+
FROM alpine:${ALPINE_VERSION}
4+
5+
COPY cnab/app/run /cnab/app/run
6+
7+
CMD /cnab/app/run

internal/commands/install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
136136
// so any installation needs a clean uninstallation.
137137
err2 := installationStore.Store(installation)
138138
if err != nil {
139-
return fmt.Errorf("Installation failed: %s", errBuf)
139+
return fmt.Errorf("Installation failed: %s\n%s", errBuf, err)
140140
}
141141
if err2 != nil {
142142
return err2

internal/commands/status.go

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,31 @@ package commands
22

33
import (
44
"fmt"
5+
"io"
6+
"os"
7+
"sort"
8+
"strings"
9+
"text/tabwriter"
10+
"time"
511

612
"github.com/deislabs/duffle/pkg/action"
713
"github.com/deislabs/duffle/pkg/credentials"
814
"github.com/docker/app/internal"
15+
"github.com/docker/app/internal/store"
916
"github.com/docker/cli/cli"
1017
"github.com/docker/cli/cli/command"
18+
units "github.com/docker/go-units"
1119
"github.com/spf13/cobra"
1220
)
1321

22+
var (
23+
knownStatusActions = []string{
24+
internal.ActionStatusName,
25+
// TODO: Extract this constant to the cnab-go library
26+
"io.cnab.status",
27+
}
28+
)
29+
1430
func statusCmd(dockerCli command.Cli) *cobra.Command {
1531
var opts credentialOptions
1632

@@ -42,6 +58,14 @@ func runStatus(dockerCli command.Cli, installationName string, opts credentialOp
4258
if err != nil {
4359
return err
4460
}
61+
displayInstallationStatus(os.Stdout, installation)
62+
63+
// Check if the bundle knows the docker app status action, if not just exit without error.
64+
statusAction := resolveStatusAction(installation)
65+
if statusAction == "" {
66+
return nil
67+
}
68+
4569
bind, err := requiredClaimBindMount(installation.Claim, opts.targetContext, dockerCli)
4670
if err != nil {
4771
return err
@@ -62,12 +86,86 @@ func runStatus(dockerCli command.Cli, installationName string, opts credentialOp
6286
if err := credentials.Validate(creds, installation.Bundle.Credentials); err != nil {
6387
return err
6488
}
89+
printHeader(os.Stdout, "STATUS")
6590
status := &action.RunCustom{
66-
Action: internal.ActionStatusName,
91+
Action: statusAction,
6792
Driver: driverImpl,
6893
}
6994
if err := status.Run(&installation.Claim, creds, dockerCli.Out()); err != nil {
7095
return fmt.Errorf("status failed: %s\n%s", err, errBuf)
7196
}
7297
return nil
7398
}
99+
100+
func displayInstallationStatus(w io.Writer, installation *store.Installation) {
101+
printHeader(w, "INSTALLATION")
102+
tab := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
103+
printValue(tab, "Name", installation.Name)
104+
printValue(tab, "Created", units.HumanDuration(time.Since(installation.Created)))
105+
printValue(tab, "Modified", units.HumanDuration(time.Since(installation.Modified)))
106+
printValue(tab, "Revision", installation.Revision)
107+
printValue(tab, "Last Action", installation.Result.Action)
108+
printValue(tab, "Result", strings.ToUpper(installation.Result.Status))
109+
if o, ok := installation.Parameters[internal.ParameterOrchestratorName]; ok {
110+
orchestrator := fmt.Sprintf("%v", o)
111+
if orchestrator == "" {
112+
orchestrator = string(command.OrchestratorSwarm)
113+
}
114+
printValue(tab, "Orchestrator", orchestrator)
115+
if kubeNamespace, ok := installation.Parameters[internal.ParameterKubernetesNamespaceName]; ok && orchestrator == string(command.OrchestratorKubernetes) {
116+
printValue(tab, "Kubernetes namespace", fmt.Sprintf("%v", kubeNamespace))
117+
}
118+
}
119+
120+
tab.Flush()
121+
fmt.Fprintln(w)
122+
123+
printHeader(w, "APPLICATION")
124+
tab = tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
125+
printValue(tab, "Name", installation.Bundle.Name)
126+
printValue(tab, "Version", installation.Bundle.Version)
127+
printValue(tab, "Reference", installation.Reference)
128+
tab.Flush()
129+
fmt.Fprintln(w)
130+
131+
if len(installation.Parameters) > 0 {
132+
printHeader(w, "PARAMETERS")
133+
tab = tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
134+
params := sortParameters(installation)
135+
for _, param := range params {
136+
if !strings.HasPrefix(param, internal.Namespace) {
137+
// TODO: Trim long []byte parameters, maybe add type too (string, int...)
138+
printValue(tab, param, fmt.Sprintf("%v", installation.Parameters[param]))
139+
}
140+
}
141+
tab.Flush()
142+
fmt.Fprintln(w)
143+
}
144+
}
145+
146+
func sortParameters(installation *store.Installation) []string {
147+
var params []string
148+
for name := range installation.Parameters {
149+
params = append(params, name)
150+
}
151+
sort.Strings(params)
152+
return params
153+
}
154+
155+
func printHeader(w io.Writer, header string) {
156+
fmt.Fprintln(w, header)
157+
fmt.Fprintln(w, strings.Repeat("-", len(header)))
158+
}
159+
160+
func printValue(w io.Writer, key, value string) {
161+
fmt.Fprintf(w, "%s:\t%s\n", key, value)
162+
}
163+
164+
func resolveStatusAction(installation *store.Installation) string {
165+
for _, name := range knownStatusActions {
166+
if _, ok := installation.Bundle.Actions[name]; ok {
167+
return name
168+
}
169+
}
170+
return ""
171+
}

0 commit comments

Comments
 (0)