Skip to content

Commit

Permalink
Plugable secret backend
Browse files Browse the repository at this point in the history
This commit extends SwarmKit secret management with pluggable secret
backends support.
Following previous commits:
1. moby/swarmkit@eebac27
2. moby/moby@08f7cf0

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
```

Signed-off-by: Liron Levin <[email protected]>
  • Loading branch information
Liron Levin committed Sep 3, 2017
1 parent cc11a13 commit bc8f58d
Show file tree
Hide file tree
Showing 20 changed files with 133 additions and 51 deletions.
26 changes: 21 additions & 5 deletions cli/command/formatter/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -61,6 +62,7 @@ func newSecretContext() *secretContext {
sCtx.header = map[string]string{
"ID": secretIDHeader,
"Name": nameHeader,
"Driver": driverHeader,
"CreatedAt": secretCreatedHeader,
"UpdatedAt": secretUpdatedHeader,
"Labels": labelsHeader,
Expand Down Expand Up @@ -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"
}
Expand Down Expand Up @@ -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)
}
Expand Down
6 changes: 3 additions & 3 deletions cli/command/formatter/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions cli/command/formatter/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
49 changes: 36 additions & 13 deletions cli/command/secret/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

type createOptions struct {
name string
driver string
file string
labels opts.ListOpts
}
Expand All @@ -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
}
Expand All @@ -46,28 +51,26 @@ 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,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
},
Data: secretData,
}
if options.driver != "" {
spec.Driver = &swarm.Driver{
Name: options.driver,
}
}

r, err := client.SecretCreate(ctx, spec)
if err != nil {
Expand All @@ -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
}
38 changes: 33 additions & 5 deletions cli/command/secret/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)},
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions cli/command/secret/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func TestSecretInspectPretty(t *testing.T) {
}),
SecretID("secretID"),
SecretName("secretName"),
SecretDriver("driver"),
SecretCreatedAt(time.Time{}),
SecretUpdatedAt(time.Time{}),
), []byte{}, nil
Expand Down
1 change: 1 addition & 0 deletions cli/command/secret/ls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions cli/command/secret/testdata/secret-list-with-filter.golden
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions cli/command/secret/testdata/secret-list.golden
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion cli/command/service/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion cli/command/service/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
2 changes: 1 addition & 1 deletion cli/command/service/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions cli/command/service/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion cli/command/stack/deploy_bundlefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions cli/command/stack/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
Labels: map[string]string{"com.docker.stack.image": "foobar:1.2.3"},
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: swarm.ContainerSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: "foobar:1.2.3@sha256:deadbeef",
},
},
Expand Down Expand Up @@ -86,7 +86,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
spec := map[string]swarm.ServiceSpec{
"myservice": {
TaskTemplate: swarm.TaskSpec{
ContainerSpec: swarm.ContainerSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: testcase.image,
},
},
Expand Down
2 changes: 1 addition & 1 deletion cli/compose/convert/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions internal/test/builders/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion internal/test/builders/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}
}

Expand Down
Loading

0 comments on commit bc8f58d

Please sign in to comment.