mirror of https://github.com/docker/cli.git
Add --pretty option to "secret inspect" and "config inspect"
This adds a pretty template for both inspect subcommands. For configs, it's particularly useful because it's a way to expose the config payload in the CLI in a non-base64-encoded way. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
a2225276af
commit
62567078ff
|
@ -1,9 +1,12 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
@ -11,6 +14,7 @@ import (
|
|||
type inspectOptions struct {
|
||||
names []string
|
||||
format string
|
||||
pretty bool
|
||||
}
|
||||
|
||||
func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
@ -26,6 +30,7 @@ func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
|
|||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
|
||||
cmd.Flags().BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -33,9 +38,29 @@ func runConfigInspect(dockerCli command.Cli, opts inspectOptions) error {
|
|||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
if opts.pretty {
|
||||
opts.format = "pretty"
|
||||
}
|
||||
|
||||
getRef := func(id string) (interface{}, []byte, error) {
|
||||
return client.ConfigInspectWithRaw(ctx, id)
|
||||
}
|
||||
f := opts.format
|
||||
|
||||
// check if the user is trying to apply a template to the pretty format, which
|
||||
// is not supported
|
||||
if strings.HasPrefix(f, "pretty") && f != "pretty" {
|
||||
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
|
||||
}
|
||||
|
||||
configCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewConfigFormat(f, false),
|
||||
}
|
||||
|
||||
if err := formatter.ConfigInspectWrite(configCtx, opts.names, getRef); err != nil {
|
||||
return cli.StatusError{StatusCode: 1, Status: err.Error()}
|
||||
}
|
||||
return nil
|
||||
|
||||
return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getRef)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
|
@ -148,3 +149,39 @@ func TestConfigInspectWithFormat(t *testing.T) {
|
|||
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigInspectPretty(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
configInspectFunc func(string) (swarm.Config, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
configInspectFunc: func(id string) (swarm.Config, []byte, error) {
|
||||
return *Config(
|
||||
ConfigLabels(map[string]string{
|
||||
"lbl1": "value1",
|
||||
}),
|
||||
ConfigID("configID"),
|
||||
ConfigName("configName"),
|
||||
ConfigCreatedAt(time.Time{}),
|
||||
ConfigUpdatedAt(time.Time{}),
|
||||
ConfigData([]byte("payload here")),
|
||||
), []byte{}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newConfigInspectCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
configInspectFunc: tc.configInspectFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"configID"})
|
||||
cmd.Flags().Set("pretty", "true")
|
||||
assert.NoError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("config-inspect-pretty.%s.golden", tc.name))
|
||||
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
ID: configID
|
||||
Name: configName
|
||||
Labels:
|
||||
- lbl1=value1
|
||||
Created at: 0001-01-01 00:00:00+0000 utc
|
||||
Updated at: 0001-01-01 00:00:00+0000 utc
|
||||
Data:
|
||||
payload here
|
|
@ -5,6 +5,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
@ -14,11 +16,24 @@ const (
|
|||
configIDHeader = "ID"
|
||||
configCreatedHeader = "CREATED"
|
||||
configUpdatedHeader = "UPDATED"
|
||||
configInspectPrettyTemplate Format = `ID: {{.ID}}
|
||||
Name: {{.Name}}
|
||||
{{- if .Labels }}
|
||||
Labels:
|
||||
{{- range $k, $v := .Labels }}
|
||||
- {{ $k }}{{if $v }}={{ $v }}{{ end }}
|
||||
{{- end }}{{ end }}
|
||||
Created at: {{.CreatedAt}}
|
||||
Updated at: {{.UpdatedAt}}
|
||||
Data:
|
||||
{{.Data}}`
|
||||
)
|
||||
|
||||
// NewConfigFormat returns a Format for rendering using a config Context
|
||||
func NewConfigFormat(source string, quiet bool) Format {
|
||||
switch source {
|
||||
case PrettyFormatKey:
|
||||
return configInspectPrettyTemplate
|
||||
case TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
|
@ -98,3 +113,59 @@ func (c *configContext) Label(name string) string {
|
|||
}
|
||||
return c.c.Spec.Annotations.Labels[name]
|
||||
}
|
||||
|
||||
// ConfigInspectWrite renders the context for a list of configs
|
||||
func ConfigInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
if ctx.Format != configInspectPrettyTemplate {
|
||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
||||
}
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
for _, ref := range refs {
|
||||
configI, _, err := getRef(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config, ok := configI.(swarm.Config)
|
||||
if !ok {
|
||||
return fmt.Errorf("got wrong object to inspect :%v", ok)
|
||||
}
|
||||
if err := format(&configInspectContext{Config: config}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(&configInspectContext{}, render)
|
||||
}
|
||||
|
||||
type configInspectContext struct {
|
||||
swarm.Config
|
||||
subContext
|
||||
}
|
||||
|
||||
func (ctx *configInspectContext) ID() string {
|
||||
return ctx.Config.ID
|
||||
}
|
||||
|
||||
func (ctx *configInspectContext) Name() string {
|
||||
return ctx.Config.Spec.Name
|
||||
}
|
||||
|
||||
func (ctx *configInspectContext) Labels() map[string]string {
|
||||
return ctx.Config.Spec.Labels
|
||||
}
|
||||
|
||||
func (ctx *configInspectContext) CreatedAt() string {
|
||||
return command.PrettyPrint(ctx.Config.CreatedAt)
|
||||
}
|
||||
|
||||
func (ctx *configInspectContext) UpdatedAt() string {
|
||||
return command.PrettyPrint(ctx.Config.UpdatedAt)
|
||||
}
|
||||
|
||||
func (ctx *configInspectContext) Data() string {
|
||||
if ctx.Config.Spec.Data == nil {
|
||||
return ""
|
||||
}
|
||||
return string(ctx.Config.Spec.Data)
|
||||
}
|
||||
|
|
|
@ -69,8 +69,7 @@ TLS Info:
|
|||
{{.TLSInfoTrustRoot}}
|
||||
Issuer Subject: {{.TLSInfoCertIssuerSubject}}
|
||||
Issuer Public Key: {{.TLSInfoCertIssuerPublicKey}}
|
||||
{{- end}}
|
||||
`
|
||||
{{- end}}`
|
||||
nodeIDHeader = "ID"
|
||||
selfHeader = ""
|
||||
hostnameHeader = "HOSTNAME"
|
||||
|
@ -177,7 +176,7 @@ func (c *nodeContext) TLSStatus() string {
|
|||
return "Needs Rotation"
|
||||
}
|
||||
|
||||
// NodeInspectWrite renders the context for a list of services
|
||||
// NodeInspectWrite renders the context for a list of nodes
|
||||
func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
if ctx.Format != nodeInspectPrettyTemplate {
|
||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
||||
|
|
|
@ -341,7 +341,6 @@ data
|
|||
|
||||
Issuer Subject: c3ViamVjdA==
|
||||
Issuer Public Key: cHViS2V5
|
||||
|
||||
`
|
||||
assert.Equal(t, expected, out.String())
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
@ -14,11 +16,22 @@ const (
|
|||
secretIDHeader = "ID"
|
||||
secretCreatedHeader = "CREATED"
|
||||
secretUpdatedHeader = "UPDATED"
|
||||
secretInspectPrettyTemplate Format = `ID: {{.ID}}
|
||||
Name: {{.Name}}
|
||||
{{- if .Labels }}
|
||||
Labels:
|
||||
{{- range $k, $v := .Labels }}
|
||||
- {{ $k }}{{if $v }}={{ $v }}{{ end }}
|
||||
{{- end }}{{ end }}
|
||||
Created at: {{.CreatedAt}}
|
||||
Updated at: {{.UpdatedAt}}`
|
||||
)
|
||||
|
||||
// NewSecretFormat returns a Format for rendering using a secret Context
|
||||
func NewSecretFormat(source string, quiet bool) Format {
|
||||
switch source {
|
||||
case PrettyFormatKey:
|
||||
return secretInspectPrettyTemplate
|
||||
case TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
|
@ -98,3 +111,52 @@ func (c *secretContext) Label(name string) string {
|
|||
}
|
||||
return c.s.Spec.Annotations.Labels[name]
|
||||
}
|
||||
|
||||
// SecretInspectWrite renders the context for a list of secrets
|
||||
func SecretInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
if ctx.Format != secretInspectPrettyTemplate {
|
||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
||||
}
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
for _, ref := range refs {
|
||||
secretI, _, err := getRef(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secret, ok := secretI.(swarm.Secret)
|
||||
if !ok {
|
||||
return fmt.Errorf("got wrong object to inspect :%v", ok)
|
||||
}
|
||||
if err := format(&secretInspectContext{Secret: secret}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(&secretInspectContext{}, render)
|
||||
}
|
||||
|
||||
type secretInspectContext struct {
|
||||
swarm.Secret
|
||||
subContext
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) ID() string {
|
||||
return ctx.Secret.ID
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) Name() string {
|
||||
return ctx.Secret.Spec.Name
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) Labels() map[string]string {
|
||||
return ctx.Secret.Spec.Labels
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) CreatedAt() string {
|
||||
return command.PrettyPrint(ctx.Secret.CreatedAt)
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) UpdatedAt() string {
|
||||
return command.PrettyPrint(ctx.Secret.UpdatedAt)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
@ -11,6 +14,7 @@ import (
|
|||
type inspectOptions struct {
|
||||
names []string
|
||||
format string
|
||||
pretty bool
|
||||
}
|
||||
|
||||
func newSecretInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
@ -26,6 +30,7 @@ func newSecretInspectCommand(dockerCli command.Cli) *cobra.Command {
|
|||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
|
||||
cmd.Flags().BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -33,9 +38,28 @@ func runSecretInspect(dockerCli command.Cli, opts inspectOptions) error {
|
|||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
if opts.pretty {
|
||||
opts.format = "pretty"
|
||||
}
|
||||
|
||||
getRef := func(id string) (interface{}, []byte, error) {
|
||||
return client.SecretInspectWithRaw(ctx, id)
|
||||
}
|
||||
f := opts.format
|
||||
|
||||
return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getRef)
|
||||
// check if the user is trying to apply a template to the pretty format, which
|
||||
// is not supported
|
||||
if strings.HasPrefix(f, "pretty") && f != "pretty" {
|
||||
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
|
||||
}
|
||||
|
||||
secretCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewSecretFormat(f, false),
|
||||
}
|
||||
|
||||
if err := formatter.SecretInspectWrite(secretCtx, opts.names, getRef); err != nil {
|
||||
return cli.StatusError{StatusCode: 1, Status: err.Error()}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
|
@ -148,3 +149,38 @@ func TestSecretInspectWithFormat(t *testing.T) {
|
|||
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretInspectPretty(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
secretInspectFunc func(string) (swarm.Secret, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
secretInspectFunc: func(id string) (swarm.Secret, []byte, error) {
|
||||
return *Secret(
|
||||
SecretLabels(map[string]string{
|
||||
"lbl1": "value1",
|
||||
}),
|
||||
SecretID("secretID"),
|
||||
SecretName("secretName"),
|
||||
SecretCreatedAt(time.Time{}),
|
||||
SecretUpdatedAt(time.Time{}),
|
||||
), []byte{}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := newSecretInspectCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
secretInspectFunc: tc.secretInspectFunc,
|
||||
}, buf))
|
||||
cmd.SetArgs([]string{"secretID"})
|
||||
cmd.Flags().Set("pretty", "true")
|
||||
assert.NoError(t, cmd.Execute())
|
||||
actual := buf.String()
|
||||
expected := golden.Get(t, []byte(actual), fmt.Sprintf("secret-inspect-pretty.%s.golden", tc.name))
|
||||
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
ID: secretID
|
||||
Name: secretName
|
||||
Labels:
|
||||
- lbl1=value1
|
||||
Created at: 0001-01-01 00:00:00+0000 utc
|
||||
Updated at: 0001-01-01 00:00:00+0000 utc
|
|
@ -59,3 +59,10 @@ func ConfigUpdatedAt(t time.Time) func(*swarm.Config) {
|
|||
config.UpdatedAt = t
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigData sets the config payload.
|
||||
func ConfigData(data []byte) func(*swarm.Config) {
|
||||
return func(config *swarm.Config) {
|
||||
config.Spec.Data = data
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue