From e34fae3d120a936fb04b65d5ee83d60bb967b68e Mon Sep 17 00:00:00 2001 From: Liron Levin Date: Wed, 19 Jul 2017 18:32:08 +0300 Subject: [PATCH] Plugable secret backend This commit extends SwarmKit secret management with pluggable secret backends support. Following previous commits: 1. docker/swarmkit@eebac27434d34708fac993f9f5181d106c5c2fae 2. docker/docker@08f7cf05268782a0dd8e4c41a4cc65fdf78d09f2 Added driver parameter to `docker secret` command. Specifically: 1. `docker secret create [secret_name] --driver [driver_name]` 2. Displaying the driver in ``` $ docker secret ls $ docker secret inspect [secret_name] $ docker secret inspect [secret_name] -pretty ``` There is a bug in serialization of the secret data. Handled in docker/docker#34157. Signed-off-by: Liron Levin --- cli/command/formatter/secret.go | 26 ++++++++-- cli/command/formatter/secret_test.go | 6 +-- cli/command/formatter/service.go | 3 ++ cli/command/secret/create.go | 48 ++++++++++++++----- cli/command/secret/create_test.go | 6 +-- cli/command/secret/inspect_test.go | 1 + cli/command/secret/ls_test.go | 1 + .../secret-inspect-pretty.simple.golden | 1 + .../testdata/secret-list-with-filter.golden | 6 +-- .../secret/testdata/secret-list.golden | 6 +-- cli/command/service/inspect_test.go | 2 +- cli/command/service/opts.go | 2 +- cli/command/service/update.go | 2 +- cli/command/service/update_test.go | 12 ++--- cli/command/stack/deploy_bundlefile.go | 2 +- cli/compose/convert/service.go | 2 +- cli/internal/test/builders/secret.go | 9 ++++ cli/internal/test/builders/service.go | 2 +- cli/internal/test/builders/task.go | 2 +- 19 files changed, 94 insertions(+), 45 deletions(-) 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/formatter/service.go b/cli/command/formatter/service.go index 27200b8be955..d92f92b6f65a 100644 --- a/cli/command/formatter/service.go +++ b/cli/command/formatter/service.go @@ -504,6 +504,9 @@ func (c *serviceContext) Replicas() string { } func (c *serviceContext) Image() string { + if c.service.Spec.TaskTemplate.ContainerSpec == nil { + return "" + } image := c.service.Spec.TaskTemplate.ContainerSpec.Image if ref, err := reference.ParseNormalizedNamed(image); err == nil { // update image string for display, (strips any digest) diff --git a/cli/command/secret/create.go b/cli/command/secret/create.go index 20efaec752a9..8d9c4e0d6d8c 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,20 @@ 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.StringVar(&options.driver, "driver", "", "Secret driver") return cmd } @@ -46,21 +50,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 +65,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 +79,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 2c4f9f5d9cb5..90dfcf59bb20 100644 --- a/cli/command/secret/create_test.go +++ b/cli/command/secret/create_test.go @@ -25,12 +25,8 @@ func TestSecretCreateErrors(t *testing.T) { secretCreateFunc func(swarm.SecretSpec) (types.SecretCreateResponse, error) expectedError string }{ - { - args: []string{"too_few"}, - expectedError: "requires exactly 2 argument(s)", - }, {args: []string{"too", "many", "arguments"}, - expectedError: "requires exactly 2 argument(s)", + expectedError: "requires at least 1 and at most 2", }, { args: []string{"name", filepath.Join("testdata", secretDataFile)}, diff --git a/cli/command/secret/inspect_test.go b/cli/command/secret/inspect_test.go index 093624ddfed9..2e8f7f862e12 100644 --- a/cli/command/secret/inspect_test.go +++ b/cli/command/secret/inspect_test.go @@ -164,6 +164,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 fb033efea6ec..fae668e999c8 100644 --- a/cli/command/secret/ls_test.go +++ b/cli/command/secret/ls_test.go @@ -64,6 +64,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 cc14091e8bb6..efb1e34f2020 100644 --- a/cli/command/secret/testdata/secret-inspect-pretty.simple.golden +++ b/cli/command/secret/testdata/secret-inspect-pretty.simple.golden @@ -2,5 +2,6 @@ ID: secretID Name: secretName Labels: - lbl1=value1 +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..ab7044617977 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..03a553dd0ee4 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/cli/command/service/inspect_test.go b/cli/command/service/inspect_test.go index e7b34da2125e..d464197202a0 100644 --- a/cli/command/service/inspect_test.go +++ b/cli/command/service/inspect_test.go @@ -41,7 +41,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) Labels: map[string]string{"com.label": "foo"}, }, TaskTemplate: swarm.TaskSpec{ - ContainerSpec: swarm.ContainerSpec{ + ContainerSpec: &swarm.ContainerSpec{ Image: "foo/bar@sha256:this_is_a_test", }, Networks: []swarm.NetworkAttachmentConfig{ diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index f2381881d7c7..fc94c7d913a1 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -592,7 +592,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()), }, TaskTemplate: swarm.TaskSpec{ - ContainerSpec: swarm.ContainerSpec{ + ContainerSpec: &swarm.ContainerSpec{ Image: options.image, Args: options.args, Command: options.entrypoint.Value(), diff --git a/cli/command/service/update.go b/cli/command/service/update.go index dba05a1b1eef..03783d8c2c0d 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -270,7 +270,7 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags } } - cspec := &spec.TaskTemplate.ContainerSpec + cspec := spec.TaskTemplate.ContainerSpec task := &spec.TaskTemplate taskResources := func() *swarm.ResourceRequirements { diff --git a/cli/command/service/update_test.go b/cli/command/service/update_test.go index 8f49d52ab8b6..0374e7f28a38 100644 --- a/cli/command/service/update_test.go +++ b/cli/command/service/update_test.go @@ -19,8 +19,8 @@ func TestUpdateServiceArgs(t *testing.T) { flags := newUpdateCommand(nil).Flags() flags.Set("args", "the \"new args\"") - spec := &swarm.ServiceSpec{} - cspec := &spec.TaskTemplate.ContainerSpec + spec := &swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{}}} + cspec := spec.TaskTemplate.ContainerSpec cspec.Args = []string{"old", "args"} updateService(nil, nil, flags, spec) @@ -452,8 +452,8 @@ func TestUpdateSecretUpdateInPlace(t *testing.T) { } func TestUpdateReadOnly(t *testing.T) { - spec := &swarm.ServiceSpec{} - cspec := &spec.TaskTemplate.ContainerSpec + spec := &swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{}}} + cspec := spec.TaskTemplate.ContainerSpec // Update with --read-only=true, changed to true flags := newUpdateCommand(nil).Flags() @@ -474,8 +474,8 @@ func TestUpdateReadOnly(t *testing.T) { } func TestUpdateStopSignal(t *testing.T) { - spec := &swarm.ServiceSpec{} - cspec := &spec.TaskTemplate.ContainerSpec + spec := &swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{}}} + cspec := spec.TaskTemplate.ContainerSpec // Update with --stop-signal=SIGUSR1 flags := newUpdateCommand(nil).Flags() diff --git a/cli/command/stack/deploy_bundlefile.go b/cli/command/stack/deploy_bundlefile.go index 1074210e97dd..5c4d4546f0a9 100644 --- a/cli/command/stack/deploy_bundlefile.go +++ b/cli/command/stack/deploy_bundlefile.go @@ -64,7 +64,7 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions Labels: convert.AddStackLabel(namespace, service.Labels), }, TaskTemplate: swarm.TaskSpec{ - ContainerSpec: swarm.ContainerSpec{ + ContainerSpec: &swarm.ContainerSpec{ Image: service.Image, Command: service.Command, Args: service.Args, diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index 730f5e1ee5b5..2140b5d5e550 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -128,7 +128,7 @@ func Service( Labels: AddStackLabel(namespace, service.Deploy.Labels), }, TaskTemplate: swarm.TaskSpec{ - ContainerSpec: swarm.ContainerSpec{ + ContainerSpec: &swarm.ContainerSpec{ Image: service.Image, Command: service.Entrypoint, Args: service.Command, diff --git a/cli/internal/test/builders/secret.go b/cli/internal/test/builders/secret.go index 9e0f910e9373..3cd5f7872ae2 100644 --- a/cli/internal/test/builders/secret.go +++ b/cli/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) { diff --git a/cli/internal/test/builders/service.go b/cli/internal/test/builders/service.go index 71718268e17e..0970e5449bcb 100644 --- a/cli/internal/test/builders/service.go +++ b/cli/internal/test/builders/service.go @@ -56,7 +56,7 @@ func ReplicatedService(replicas uint64) func(*swarm.Service) { // ServiceImage sets the service's image func ServiceImage(image string) func(*swarm.Service) { return func(service *swarm.Service) { - service.Spec.TaskTemplate = swarm.TaskSpec{ContainerSpec: swarm.ContainerSpec{Image: image}} + service.Spec.TaskTemplate = swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: image}} } } diff --git a/cli/internal/test/builders/task.go b/cli/internal/test/builders/task.go index 479b6f14c5fb..40efc3d93a3d 100644 --- a/cli/internal/test/builders/task.go +++ b/cli/internal/test/builders/task.go @@ -140,7 +140,7 @@ func WithTaskSpec(specBuilders ...func(*swarm.TaskSpec)) func(*swarm.Task) { // Any number of taskSpec function builder can be pass to augment it. func TaskSpec(specBuilders ...func(*swarm.TaskSpec)) *swarm.TaskSpec { taskSpec := &swarm.TaskSpec{ - ContainerSpec: swarm.ContainerSpec{ + ContainerSpec: &swarm.ContainerSpec{ Image: "myimage:mytag", }, }