diff --git a/cli/command/formatter/secret.go b/cli/command/formatter/secret.go index 04bf944ecd07..d025cd8f3b53 100644 --- a/cli/command/formatter/secret.go +++ b/cli/command/formatter/secret.go @@ -12,19 +12,20 @@ import ( ) const ( - defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}" + defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}" secretIDHeader = "ID" secretCreatedHeader = "CREATED" secretUpdatedHeader = "UPDATED" - secretInspectPrettyTemplate Format = `ID: {{.ID}} -Name: {{.Name}} + 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}}` +Driver: {{.Driver}} +Created at: {{.CreatedAt}} +Updated at: {{.UpdatedAt}}` ) // NewSecretFormat returns a Format for rendering using a secret Context @@ -61,6 +62,7 @@ func newSecretContext() *secretContext { sCtx.header = map[string]string{ "ID": secretIDHeader, "Name": nameHeader, + "Driver": driverHeader, "CreatedAt": secretCreatedHeader, "UpdatedAt": secretUpdatedHeader, "Labels": labelsHeader, @@ -89,6 +91,13 @@ func (c *secretContext) CreatedAt() string { return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.CreatedAt)) + " ago" } +func (c *secretContext) Driver() string { + if c.s.Spec.Driver == nil { + return "" + } + return c.s.Spec.Driver.Name +} + func (c *secretContext) UpdatedAt() string { return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.UpdatedAt)) + " ago" } @@ -153,6 +162,13 @@ func (ctx *secretInspectContext) Labels() map[string]string { return ctx.Secret.Spec.Labels } +func (ctx *secretInspectContext) Driver() string { + if ctx.Secret.Spec.Driver == nil { + return "" + } + return ctx.Secret.Spec.Driver.Name +} + func (ctx *secretInspectContext) CreatedAt() string { return command.PrettyPrint(ctx.Secret.CreatedAt) } diff --git a/cli/command/formatter/secret_test.go b/cli/command/formatter/secret_test.go index 98fe61315f49..03f6ac1f5789 100644 --- a/cli/command/formatter/secret_test.go +++ b/cli/command/formatter/secret_test.go @@ -28,9 +28,9 @@ func TestSecretContextFormatWrite(t *testing.T) { }, // Table format {Context{Format: NewSecretFormat("table", false)}, - `ID NAME CREATED UPDATED -1 passwords Less than a second ago Less than a second ago -2 id_rsa Less than a second ago Less than a second ago + `ID NAME DRIVER CREATED UPDATED +1 passwords Less than a second ago Less than a second ago +2 id_rsa Less than a second ago Less than a second ago `}, {Context{Format: NewSecretFormat("table {{.Name}}", true)}, `NAME diff --git a/cli/command/secret/create.go b/cli/command/secret/create.go index 20efaec752a9..6ade18bdfc2d 100644 --- a/cli/command/secret/create.go +++ b/cli/command/secret/create.go @@ -17,6 +17,7 @@ import ( type createOptions struct { name string + driver string file string labels opts.ListOpts } @@ -27,17 +28,21 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command { } cmd := &cobra.Command{ - Use: "create [OPTIONS] SECRET file|-", + Use: "create [OPTIONS] SECRET [file|-]", Short: "Create a secret from a file or STDIN as content", - Args: cli.ExactArgs(2), + Args: cli.RequiresRangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { options.name = args[0] - options.file = args[1] + if len(args) == 2 { + options.file = args[1] + } return runSecretCreate(dockerCli, options) }, } flags := cmd.Flags() flags.VarP(&options.labels, "label", "l", "Secret labels") + flags.StringVarP(&options.driver, "driver", "d", "", "Secret driver") + flags.SetAnnotation("driver", "version", []string{"1.31"}) return cmd } @@ -46,21 +51,14 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error { client := dockerCli.Client() ctx := context.Background() - var in io.Reader = dockerCli.In() - if options.file != "-" { - file, err := system.OpenSequential(options.file) - if err != nil { - return err - } - in = file - defer file.Close() + if options.driver != "" && options.file != "" { + return errors.Errorf("When using secret driver secret data must be empty") } - secretData, err := ioutil.ReadAll(in) + secretData, err := readSecretData(dockerCli.In(), options.file) if err != nil { return errors.Errorf("Error reading content from %q: %v", options.file, err) } - spec := swarm.SecretSpec{ Annotations: swarm.Annotations{ Name: options.name, @@ -68,6 +66,11 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error { }, Data: secretData, } + if options.driver != "" { + spec.Driver = &swarm.Driver{ + Name: options.driver, + } + } r, err := client.SecretCreate(ctx, spec) if err != nil { @@ -77,3 +80,23 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error { fmt.Fprintln(dockerCli.Out(), r.ID) return nil } + +func readSecretData(in io.ReadCloser, file string) ([]byte, error) { + // Read secret value from external driver + if file == "" { + return nil, nil + } + if file != "-" { + var err error + in, err = system.OpenSequential(file) + if err != nil { + return nil, err + } + defer in.Close() + } + data, err := ioutil.ReadAll(in) + if err != nil { + return nil, err + } + return data, nil +} diff --git a/cli/command/secret/create_test.go b/cli/command/secret/create_test.go index f9f70cfedda9..442dd7a919fd 100644 --- a/cli/command/secret/create_test.go +++ b/cli/command/secret/create_test.go @@ -24,12 +24,11 @@ func TestSecretCreateErrors(t *testing.T) { secretCreateFunc func(swarm.SecretSpec) (types.SecretCreateResponse, error) expectedError string }{ - { - args: []string{"too_few"}, - expectedError: "requires exactly 2 arguments", - }, {args: []string{"too", "many", "arguments"}, - expectedError: "requires exactly 2 arguments", + expectedError: "requires at least 1 and at most 2 arguments", + }, + {args: []string{"create", "--driver", "driver", "-"}, + expectedError: "secret data must be empty", }, { args: []string{"name", filepath.Join("testdata", secretDataFile)}, @@ -75,6 +74,35 @@ func TestSecretCreateWithName(t *testing.T) { assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String())) } +func TestSecretCreateWithDriver(t *testing.T) { + expectedDriver := &swarm.Driver{ + Name: "secret-driver", + } + name := "foo" + + cli := test.NewFakeCli(&fakeClient{ + secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) { + if spec.Name != name { + return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) + } + + if !reflect.DeepEqual(spec.Driver.Name, expectedDriver.Name) { + return types.SecretCreateResponse{}, errors.Errorf("expected driver %v, got %v", expectedDriver, spec.Labels) + } + + return types.SecretCreateResponse{ + ID: "ID-" + spec.Name, + }, nil + }, + }) + + cmd := newSecretCreateCommand(cli) + cmd.SetArgs([]string{name}) + cmd.Flags().Set("driver", expectedDriver.Name) + assert.NoError(t, cmd.Execute()) + assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String())) +} + func TestSecretCreateWithLabels(t *testing.T) { expectedLabels := map[string]string{ "lbl1": "Label-foo", diff --git a/cli/command/secret/inspect_test.go b/cli/command/secret/inspect_test.go index 188f6a7c4ca6..cb74e11ae2d8 100644 --- a/cli/command/secret/inspect_test.go +++ b/cli/command/secret/inspect_test.go @@ -154,6 +154,7 @@ func TestSecretInspectPretty(t *testing.T) { }), SecretID("secretID"), SecretName("secretName"), + SecretDriver("driver"), SecretCreatedAt(time.Time{}), SecretUpdatedAt(time.Time{}), ), []byte{}, nil diff --git a/cli/command/secret/ls_test.go b/cli/command/secret/ls_test.go index 2509e24dc379..83f6742af2cc 100644 --- a/cli/command/secret/ls_test.go +++ b/cli/command/secret/ls_test.go @@ -63,6 +63,7 @@ func TestSecretList(t *testing.T) { SecretVersion(swarm.Version{Index: 11}), SecretCreatedAt(time.Now().Add(-2*time.Hour)), SecretUpdatedAt(time.Now().Add(-1*time.Hour)), + SecretDriver("driver"), ), }, nil }, diff --git a/cli/command/secret/testdata/secret-inspect-pretty.simple.golden b/cli/command/secret/testdata/secret-inspect-pretty.simple.golden index 4e2df1fb4c50..37234eff7708 100644 --- a/cli/command/secret/testdata/secret-inspect-pretty.simple.golden +++ b/cli/command/secret/testdata/secret-inspect-pretty.simple.golden @@ -1,6 +1,7 @@ -ID: secretID -Name: secretName +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 +Driver: driver +Created at: 0001-01-01 00:00:00 +0000 utc +Updated at: 0001-01-01 00:00:00 +0000 utc diff --git a/cli/command/secret/testdata/secret-list-with-filter.golden b/cli/command/secret/testdata/secret-list-with-filter.golden index 29983de8e92e..01ea6f3ae79b 100644 --- a/cli/command/secret/testdata/secret-list-with-filter.golden +++ b/cli/command/secret/testdata/secret-list-with-filter.golden @@ -1,3 +1,3 @@ -ID NAME CREATED UPDATED -ID-foo foo 2 hours ago About an hour ago -ID-bar bar 2 hours ago About an hour ago +ID NAME DRIVER CREATED UPDATED +ID-foo foo 2 hours ago About an hour ago +ID-bar bar 2 hours ago About an hour ago diff --git a/cli/command/secret/testdata/secret-list.golden b/cli/command/secret/testdata/secret-list.golden index 29983de8e92e..d8c204043855 100644 --- a/cli/command/secret/testdata/secret-list.golden +++ b/cli/command/secret/testdata/secret-list.golden @@ -1,3 +1,3 @@ -ID NAME CREATED UPDATED -ID-foo foo 2 hours ago About an hour ago -ID-bar bar 2 hours ago About an hour ago +ID NAME DRIVER CREATED UPDATED +ID-foo foo 2 hours ago About an hour ago +ID-bar bar driver 2 hours ago About an hour ago diff --git a/internal/test/builders/secret.go b/internal/test/builders/secret.go index 9e0f910e9373..3cd5f7872ae2 100644 --- a/internal/test/builders/secret.go +++ b/internal/test/builders/secret.go @@ -32,6 +32,15 @@ func SecretName(name string) func(secret *swarm.Secret) { } } +// SecretDriver sets the secret's driver name +func SecretDriver(driver string) func(secret *swarm.Secret) { + return func(secret *swarm.Secret) { + secret.Spec.Driver = &swarm.Driver{ + Name: driver, + } + } +} + // SecretID sets the secret's ID func SecretID(ID string) func(secret *swarm.Secret) { return func(secret *swarm.Secret) {