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

Commit

Permalink
Status command now shows installation informations, even if installat…
Browse files Browse the repository at this point in the history
…ion failed or if the

com.docker.app.status command is not available in the bundle.

Also added a support to the io.cnab.status well known action, as detailed in the CNAB
specifications.

Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
  • Loading branch information
silvin-lubecki committed Apr 26, 2019
1 parent 8a0547e commit 2ed726a
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 9 deletions.
18 changes: 13 additions & 5 deletions e2e/cnab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,23 @@ func TestCallCustomStatusAction(t *testing.T) {
cnab string
}{
{
name: "validCustomStatusAction",
name: "validCustomDockerStatusAction",
exitCode: 0,
expectedOutput: "Status action",
cnab: "cnab-with-status",
expectedOutput: "com.docker.app.status",
cnab: "cnab-with-docker-status",
},
{
name: "validCustomStandardStatusAction",
exitCode: 0,
expectedOutput: "io.cnab.status",
cnab: "cnab-with-standard-status",
},
// A CNAB bundle without standard or docker status action still can output
// some informations about the installation.
{
name: "missingCustomStatusAction",
exitCode: 1,
expectedOutput: "status failed: action not defined for bundle",
exitCode: 0,
expectedOutput: "Name: missingCustomStatusAction",
cnab: "cnab-without-status",
},
}
Expand Down
24 changes: 24 additions & 0 deletions e2e/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,30 @@ func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
cmd.Command = dockerCli.Command("app", "status", appName)
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
[]string{
`INSTALLATION
------------
Name: TestDockerAppLifecycle_.*
Created: .*
Modified: .*
Revision: .*
Last Action: install
Result: SUCCESS
Orchestrator: swarm
APPLICATION
-----------
Name: simple
Version: 1.1.0-beta1
Reference:.*
PARAMETERS
----------
api_host: example.com
static_subdir: data/static
web_port: 8082
STATUS
------`,
fmt.Sprintf("[[:alnum:]]+ %s_db replicated [0-1]/1 postgres:9.3", appName),
fmt.Sprintf(`[[:alnum:]]+ %s_web replicated [0-1]/1 nginx:latest \*:8082->80/tcp`, appName),
fmt.Sprintf("[[:alnum:]]+ %s_api replicated [0-1]/1 python:3.6", appName),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "cnab-with-status",
"name": "cnab-with-docker-status",
"version": "0.1.0",
"invocationImages": [
{
"imageType": "docker",
"image": "e2e/cnab-with-status:v0.1.0"
"image": "e2e/cnab-with-docker-status:v0.1.0"
}
],
"actions": {
Expand Down
15 changes: 15 additions & 0 deletions e2e/testdata/cnab-with-standard-status/bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "cnab-with-standard-status",
"version": "0.1.0",
"invocationImages": [
{
"imageType": "docker",
"image": "e2e/cnab-with-standard-status:v0.1.0"
}
],
"actions": {
"io.cnab.status": {
"modifies": false
}
}
}
23 changes: 23 additions & 0 deletions e2e/testdata/cnab-with-standard-status/cnab/app/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh

action=$CNAB_ACTION
name=$CNAB_INSTALLATION_NAME

case $action in
install)
echo "Install action"
;;
uninstall)
echo "uninstall action"
;;
upgrade)
echo "Upgrade action"
;;
io.cnab.status)
echo "Status action"
;;
*)
echo "No action for $action"
;;
esac
echo "Action $action complete for $name"
7 changes: 7 additions & 0 deletions e2e/testdata/cnab-with-standard-status/cnab/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ARG ALPINE_VERSION=3.9.2

FROM alpine:${ALPINE_VERSION}

COPY cnab/app/run /cnab/app/run

CMD /cnab/app/run
2 changes: 1 addition & 1 deletion internal/commands/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
// so any installation needs a clean uninstallation.
err2 := installationStore.Store(installation)
if err != nil {
return fmt.Errorf("Installation failed: %s", errBuf)
return fmt.Errorf("Installation failed: %s\n%s", errBuf, err)
}
if err2 != nil {
return err2
Expand Down
100 changes: 99 additions & 1 deletion internal/commands/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@ package commands

import (
"fmt"
"io"
"os"
"sort"
"strings"
"text/tabwriter"
"time"

"github.com/deislabs/duffle/pkg/action"
"github.com/deislabs/duffle/pkg/credentials"
"github.com/docker/app/internal"
"github.com/docker/app/internal/store"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)

var (
knownStatusActions = []string{
internal.ActionStatusName,
// TODO: Extract this constant to the cnab-go library
"io.cnab.status",
}
)

func statusCmd(dockerCli command.Cli) *cobra.Command {
var opts credentialOptions

Expand Down Expand Up @@ -42,6 +58,14 @@ func runStatus(dockerCli command.Cli, installationName string, opts credentialOp
if err != nil {
return err
}
displayInstallationStatus(os.Stdout, installation)

// Check if the bundle knows the docker app status action, if not just exit without error.
statusAction := resolveStatusAction(installation)
if statusAction == "" {
return nil
}

bind, err := requiredClaimBindMount(installation.Claim, opts.targetContext, dockerCli)
if err != nil {
return err
Expand All @@ -62,12 +86,86 @@ func runStatus(dockerCli command.Cli, installationName string, opts credentialOp
if err := credentials.Validate(creds, installation.Bundle.Credentials); err != nil {
return err
}
printHeader(os.Stdout, "STATUS")
status := &action.RunCustom{
Action: internal.ActionStatusName,
Action: statusAction,
Driver: driverImpl,
}
if err := status.Run(&installation.Claim, creds, dockerCli.Out()); err != nil {
return fmt.Errorf("status failed: %s\n%s", err, errBuf)
}
return nil
}

func displayInstallationStatus(w io.Writer, installation *store.Installation) {
printHeader(w, "INSTALLATION")
tab := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
printValue(tab, "Name", installation.Name)
printValue(tab, "Created", units.HumanDuration(time.Since(installation.Created)))
printValue(tab, "Modified", units.HumanDuration(time.Since(installation.Modified)))
printValue(tab, "Revision", installation.Revision)
printValue(tab, "Last Action", installation.Result.Action)
printValue(tab, "Result", strings.ToUpper(installation.Result.Status))
if o, ok := installation.Parameters[internal.ParameterOrchestratorName]; ok {
orchestrator := fmt.Sprintf("%v", o)
if orchestrator == "" {
orchestrator = string(command.OrchestratorSwarm)
}
printValue(tab, "Orchestrator", orchestrator)
if kubeNamespace, ok := installation.Parameters[internal.ParameterKubernetesNamespaceName]; ok && orchestrator == string(command.OrchestratorKubernetes) {
printValue(tab, "Kubernetes namespace", fmt.Sprintf("%v", kubeNamespace))
}
}

tab.Flush()
fmt.Fprintln(w)

printHeader(w, "APPLICATION")
tab = tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
printValue(tab, "Name", installation.Bundle.Name)
printValue(tab, "Version", installation.Bundle.Version)
printValue(tab, "Reference", installation.Reference)
tab.Flush()
fmt.Fprintln(w)

if len(installation.Parameters) > 0 {
printHeader(w, "PARAMETERS")
tab = tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
params := sortParameters(installation)
for _, param := range params {
if !strings.HasPrefix(param, internal.Namespace) {
// TODO: Trim long []byte parameters, maybe add type too (string, int...)
printValue(tab, param, fmt.Sprintf("%v", installation.Parameters[param]))
}
}
tab.Flush()
fmt.Fprintln(w)
}
}

func sortParameters(installation *store.Installation) []string {
var params []string
for name := range installation.Parameters {
params = append(params, name)
}
sort.Strings(params)
return params
}

func printHeader(w io.Writer, header string) {
fmt.Fprintln(w, header)
fmt.Fprintln(w, strings.Repeat("-", len(header)))
}

func printValue(w io.Writer, key, value string) {
fmt.Fprintf(w, "%s:\t%s\n", key, value)
}

func resolveStatusAction(installation *store.Installation) string {
for _, name := range knownStatusActions {
if _, ok := installation.Bundle.Actions[name]; ok {
return name
}
}
return ""
}

0 comments on commit 2ed726a

Please sign in to comment.