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

Commit ccf5775

Browse files
authored
Merge pull request #375 from silvin-lubecki/141-better-inspect
Add compose file summary to the inspect command
2 parents 66ee35d + d2e6f2a commit ccf5775

14 files changed

Lines changed: 387 additions & 59 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ Commands:
255255
fork Create a fork of an existing application to be modified
256256
helm Generate a Helm chart
257257
init Start building a Docker application
258-
inspect Shows metadata and settings for a given application
258+
inspect Shows metadata, settings and a summary of the compose file for a given application
259259
merge Merge a multi-file application into a single file
260260
push Push the application to a registry
261261
render Render the Compose file for the application

cmd/docker-app/inspect.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,37 @@ package main
33
import (
44
"github.com/docker/app/internal/inspect"
55
"github.com/docker/app/internal/packager"
6+
"github.com/docker/app/types"
67
"github.com/docker/cli/cli"
78
"github.com/docker/cli/cli/command"
9+
cliopts "github.com/docker/cli/opts"
810
"github.com/spf13/cobra"
911
)
1012

13+
var (
14+
inspectSettingsFile []string
15+
inspectEnv []string
16+
)
17+
1118
// inspectCmd represents the inspect command
1219
func inspectCmd(dockerCli command.Cli) *cobra.Command {
13-
return &cobra.Command{
14-
Use: "inspect [<app-name>]",
15-
Short: "Shows metadata and settings for a given application",
20+
cmd := &cobra.Command{
21+
Use: "inspect [<app-name>] [-s key=value...] [-f settings-file...]",
22+
Short: "Shows metadata, settings and a summary of the compose file for a given application",
1623
Args: cli.RequiresMaxArgs(1),
1724
RunE: func(cmd *cobra.Command, args []string) error {
18-
app, err := packager.Extract(firstOrEmpty(args))
25+
app, err := packager.Extract(firstOrEmpty(args),
26+
types.WithSettingsFiles(inspectSettingsFile...),
27+
)
1928
if err != nil {
2029
return err
2130
}
2231
defer app.Cleanup()
23-
return inspect.Inspect(dockerCli.Out(), app)
32+
argSettings := cliopts.ConvertKVStringsToMap(inspectEnv)
33+
return inspect.Inspect(dockerCli.Out(), app, argSettings)
2434
},
2535
}
36+
cmd.Flags().StringArrayVarP(&inspectSettingsFile, "settings-files", "f", []string{}, "Override settings files")
37+
cmd.Flags().StringArrayVarP(&inspectEnv, "set", "s", []string{}, "Override settings values")
38+
return cmd
2639
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
myapp 0.1.0
2+
23
Maintained by: bearclaw <bearclaw@bearclaw.bearclaw>
34

4-
Setting Default
5-
------- -------
5+
Service (1) Replicas Ports Image
6+
----------- -------- ----- -----
7+
test 1 alpine:latest
8+
9+
Settings (4) Value
10+
------------ -----
611
myapp.alpine_version latest
712
myapp.command1 cat
813
myapp.command2 foo
14+
myapp.command3 bar
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
hello-world 0.1.0
2+
23
Maintained by: user <user@email.com>
34

45
Hello, World!
56

6-
Setting Default
7-
------- -------
8-
port 8080
9-
text Hello, World!
7+
Service (1) Replicas Ports Image
8+
----------- -------- ----- -----
9+
hello 1 8080 hashicorp/http-echo
10+
11+
Settings (2) Value
12+
------------ -----
13+
port 8080
14+
text Hello, World!

e2e/testdata/render/envvariables/my.dockerapp/settings.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ myapp:
22
alpine_version: latest
33
command1: cat
44
command2: foo
5+
command3: bar

internal/inspect/inspect.go

Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,137 @@ import (
44
"fmt"
55
"io"
66
"sort"
7+
"strings"
78
"text/tabwriter"
89

10+
"github.com/docker/app/render"
911
"github.com/docker/app/types"
12+
"github.com/docker/app/types/settings"
13+
composetypes "github.com/docker/cli/cli/compose/types"
1014
)
1115

1216
// Inspect dumps the metadata of an app
13-
func Inspect(out io.Writer, app *types.App) error {
17+
func Inspect(out io.Writer, app *types.App, argSettings map[string]string) error {
18+
// Render the compose file
19+
config, err := render.Render(app, argSettings)
20+
if err != nil {
21+
return err
22+
}
23+
24+
// Extract all the settings
25+
settingsKeys, allSettings, err := extractSettings(app, argSettings)
26+
if err != nil {
27+
return err
28+
}
29+
30+
// Add Meta data
31+
printMetadata(out, app)
32+
33+
// Add Service section
34+
printSection(out, len(config.Services), func(w io.Writer) {
35+
for _, service := range config.Services {
36+
fmt.Fprintf(w, "%s\t%d\t%s\t%s\n", service.Name, getReplicas(service), getPorts(service.Ports), service.Image)
37+
}
38+
}, "Service", "Replicas", "Ports", "Image")
39+
40+
// Add Network section
41+
printSection(out, len(config.Networks), func(w io.Writer) {
42+
for name := range config.Networks {
43+
fmt.Fprintln(w, name)
44+
}
45+
}, "Network")
46+
47+
// Add Volume section
48+
printSection(out, len(config.Volumes), func(w io.Writer) {
49+
for name := range config.Volumes {
50+
fmt.Fprintln(w, name)
51+
}
52+
}, "Volume")
53+
54+
// Add Secret section
55+
printSection(out, len(config.Secrets), func(w io.Writer) {
56+
for name := range config.Secrets {
57+
fmt.Fprintln(w, name)
58+
}
59+
}, "Secret")
60+
61+
// Add Setting section
62+
printSection(out, len(settingsKeys), func(w io.Writer) {
63+
for _, k := range settingsKeys {
64+
fmt.Fprintf(w, "%s\t%s\n", k, allSettings[k])
65+
}
66+
}, "Setting", "Value")
67+
68+
return nil
69+
}
70+
71+
func printMetadata(out io.Writer, app *types.App) {
1472
meta := app.Metadata()
15-
// extract settings
16-
settings := app.Settings().Flatten()
73+
fmt.Fprintln(out, meta.Name, meta.Version)
74+
if maintainers := meta.Maintainers.String(); maintainers != "" {
75+
fmt.Fprintln(out)
76+
fmt.Fprintln(out, "Maintained by:", maintainers)
77+
}
78+
if meta.Description != "" {
79+
fmt.Fprintln(out)
80+
fmt.Fprintln(out, meta.Description)
81+
}
82+
}
83+
84+
func printSection(out io.Writer, len int, printer func(io.Writer), headers ...string) {
85+
if len == 0 {
86+
return
87+
}
88+
fmt.Fprintln(out)
89+
w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
90+
var plural string
91+
if len > 1 {
92+
plural = "s"
93+
}
94+
headers[0] = fmt.Sprintf("%s%s (%d)", headers[0], plural, len)
95+
printHeaders(w, headers...)
96+
printer(w)
97+
w.Flush()
98+
}
99+
100+
func printHeaders(w io.Writer, headers ...string) {
101+
fmt.Fprintln(w, strings.Join(headers, "\t"))
102+
dashes := make([]string, len(headers))
103+
for i, h := range headers {
104+
dashes[i] = strings.Repeat("-", len(h))
105+
}
106+
fmt.Fprintln(w, strings.Join(dashes, "\t"))
107+
}
108+
109+
func getReplicas(service composetypes.ServiceConfig) int {
110+
if service.Deploy.Replicas != nil {
111+
return int(*service.Deploy.Replicas)
112+
}
113+
return 1
114+
}
115+
116+
func extractSettings(app *types.App, argSettings map[string]string) ([]string, map[string]string, error) {
117+
allSettings, err := mergeAndFlattenSettings(app, argSettings)
118+
if err != nil {
119+
return nil, nil, err
120+
}
17121
// sort the keys to get consistent output
18122
var settingsKeys []string
19-
for k := range settings {
123+
for k := range allSettings {
20124
settingsKeys = append(settingsKeys, k)
21125
}
22126
sort.Slice(settingsKeys, func(i, j int) bool { return settingsKeys[i] < settingsKeys[j] })
23-
// build maintainers string
24-
maintainers := meta.Maintainers.String()
25-
fmt.Fprintf(out, "%s %s\n", meta.Name, meta.Version)
26-
if maintainers != "" {
27-
fmt.Fprintf(out, "Maintained by: %s\n", maintainers)
28-
fmt.Fprintln(out, "")
29-
}
30-
if meta.Description != "" {
31-
fmt.Fprintf(out, "%s\n", meta.Description)
32-
fmt.Fprintln(out, "")
127+
return settingsKeys, allSettings, nil
128+
}
129+
130+
func mergeAndFlattenSettings(app *types.App, argSettings map[string]string) (map[string]string, error) {
131+
sArgs, err := settings.FromFlatten(argSettings)
132+
if err != nil {
133+
return nil, err
33134
}
34-
w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
35-
fmt.Fprintln(w, "Setting\tDefault")
36-
fmt.Fprintln(w, "-------\t-------")
37-
for _, k := range settingsKeys {
38-
fmt.Fprintf(w, "%s\t%s\n", k, settings[k])
135+
s, err := settings.Merge(app.Settings(), sArgs)
136+
if err != nil {
137+
return nil, err
39138
}
40-
w.Flush()
41-
return nil
139+
return s.Flatten(), nil
42140
}

internal/inspect/inspect_test.go

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ import (
1313
)
1414

1515
const (
16-
composeYAML = `version: "3.1"
17-
18-
services:
19-
web:
20-
image: nginx`
16+
composeYAML = `version: "3.1"`
2117
)
2218

2319
func TestInspect(t *testing.T) {
@@ -50,8 +46,41 @@ maintainers:
5046
description: "this is sparta !"`),
5147
fs.WithFile(internal.SettingsFileName, ""),
5248
),
49+
fs.WithDir("overridden",
50+
fs.WithFile(internal.ComposeFileName, `
51+
version: "3.1"
52+
53+
services:
54+
web:
55+
image: nginx
56+
ports:
57+
- ${web.port}:80
58+
`),
59+
fs.WithFile(internal.MetadataFileName, `
60+
version: 0.1.0
61+
name: foo
62+
`),
63+
fs.WithFile(internal.SettingsFileName, ""),
64+
),
5365
fs.WithDir("full",
54-
fs.WithFile(internal.ComposeFileName, composeYAML),
66+
fs.WithFile(internal.ComposeFileName, `
67+
version: "3.1"
68+
69+
services:
70+
web:
71+
image: nginx:latest
72+
ports:
73+
- 8080-8100:12300-12320
74+
deploy:
75+
replicas: 2
76+
networks:
77+
my-network:
78+
volumes:
79+
my-volume:
80+
secrets:
81+
my-secret:
82+
file: ./my_secret.txt
83+
`),
5584
fs.WithFile(internal.MetadataFileName, `
5685
version: 0.1.0
5786
name: foo
@@ -66,14 +95,23 @@ text: hello`),
6695
)
6796
defer dir.Remove()
6897

69-
for _, appname := range []string{
70-
"no-maintainers", "no-description", "no-settings", "full",
98+
for _, testcase := range []struct {
99+
name string
100+
args map[string]string
101+
}{
102+
{name: "no-maintainers"},
103+
{name: "no-description"},
104+
{name: "no-settings"},
105+
{name: "overridden", args: map[string]string{"web.port": "80"}},
106+
{name: "full"},
71107
} {
72-
outBuffer := new(bytes.Buffer)
73-
app, err := types.NewAppFromDefaultFiles(dir.Join(appname))
74-
assert.NilError(t, err)
75-
err = Inspect(outBuffer, app)
76-
assert.NilError(t, err)
77-
golden.Assert(t, outBuffer.String(), fmt.Sprintf("inspect-%s.golden", appname), appname)
108+
t.Run(testcase.name, func(t *testing.T) {
109+
outBuffer := new(bytes.Buffer)
110+
app, err := types.NewAppFromDefaultFiles(dir.Join(testcase.name))
111+
assert.NilError(t, err)
112+
err = Inspect(outBuffer, app, testcase.args)
113+
assert.NilError(t, err)
114+
golden.Assert(t, outBuffer.String(), fmt.Sprintf("inspect-%s.golden", testcase.name), testcase.name)
115+
})
78116
}
79117
}

0 commit comments

Comments
 (0)