Merge pull request #366 from twistlock/plugable_secrets_backend

Plugable secrets backend
This commit is contained in:
Sebastiaan van Stijn 2017-09-18 19:25:15 +02:00 committed by GitHub
commit 3edf97e5a9
10 changed files with 115 additions and 36 deletions

View File

@ -12,19 +12,20 @@ import (
) )
const ( const (
defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}" defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
secretIDHeader = "ID" secretIDHeader = "ID"
secretCreatedHeader = "CREATED" secretCreatedHeader = "CREATED"
secretUpdatedHeader = "UPDATED" secretUpdatedHeader = "UPDATED"
secretInspectPrettyTemplate Format = `ID: {{.ID}} secretInspectPrettyTemplate Format = `ID: {{.ID}}
Name: {{.Name}} Name: {{.Name}}
{{- if .Labels }} {{- if .Labels }}
Labels: Labels:
{{- range $k, $v := .Labels }} {{- range $k, $v := .Labels }}
- {{ $k }}{{if $v }}={{ $v }}{{ end }} - {{ $k }}{{if $v }}={{ $v }}{{ end }}
{{- end }}{{ end }} {{- end }}{{ end }}
Created at: {{.CreatedAt}} Driver: {{.Driver}}
Updated at: {{.UpdatedAt}}` Created at: {{.CreatedAt}}
Updated at: {{.UpdatedAt}}`
) )
// NewSecretFormat returns a Format for rendering using a secret Context // NewSecretFormat returns a Format for rendering using a secret Context
@ -61,6 +62,7 @@ func newSecretContext() *secretContext {
sCtx.header = map[string]string{ sCtx.header = map[string]string{
"ID": secretIDHeader, "ID": secretIDHeader,
"Name": nameHeader, "Name": nameHeader,
"Driver": driverHeader,
"CreatedAt": secretCreatedHeader, "CreatedAt": secretCreatedHeader,
"UpdatedAt": secretUpdatedHeader, "UpdatedAt": secretUpdatedHeader,
"Labels": labelsHeader, "Labels": labelsHeader,
@ -89,6 +91,13 @@ func (c *secretContext) CreatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.CreatedAt)) + " ago" 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 { func (c *secretContext) UpdatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.UpdatedAt)) + " ago" 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 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 { func (ctx *secretInspectContext) CreatedAt() string {
return command.PrettyPrint(ctx.Secret.CreatedAt) return command.PrettyPrint(ctx.Secret.CreatedAt)
} }

View File

@ -28,9 +28,9 @@ func TestSecretContextFormatWrite(t *testing.T) {
}, },
// Table format // Table format
{Context{Format: NewSecretFormat("table", false)}, {Context{Format: NewSecretFormat("table", false)},
`ID NAME CREATED UPDATED `ID NAME DRIVER CREATED UPDATED
1 passwords Less than a second ago Less than a second ago 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 2 id_rsa Less than a second ago Less than a second ago
`}, `},
{Context{Format: NewSecretFormat("table {{.Name}}", true)}, {Context{Format: NewSecretFormat("table {{.Name}}", true)},
`NAME `NAME

View File

@ -17,6 +17,7 @@ import (
type createOptions struct { type createOptions struct {
name string name string
driver string
file string file string
labels opts.ListOpts labels opts.ListOpts
} }
@ -27,17 +28,21 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
} }
cmd := &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", 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 { RunE: func(cmd *cobra.Command, args []string) error {
options.name = args[0] options.name = args[0]
options.file = args[1] if len(args) == 2 {
options.file = args[1]
}
return runSecretCreate(dockerCli, options) return runSecretCreate(dockerCli, options)
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.VarP(&options.labels, "label", "l", "Secret labels") 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 return cmd
} }
@ -46,21 +51,14 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()
var in io.Reader = dockerCli.In() if options.driver != "" && options.file != "" {
if options.file != "-" { return errors.Errorf("When using secret driver secret data must be empty")
file, err := system.OpenSequential(options.file)
if err != nil {
return err
}
in = file
defer file.Close()
} }
secretData, err := ioutil.ReadAll(in) secretData, err := readSecretData(dockerCli.In(), options.file)
if err != nil { if err != nil {
return errors.Errorf("Error reading content from %q: %v", options.file, err) return errors.Errorf("Error reading content from %q: %v", options.file, err)
} }
spec := swarm.SecretSpec{ spec := swarm.SecretSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: options.name, Name: options.name,
@ -68,6 +66,11 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error {
}, },
Data: secretData, Data: secretData,
} }
if options.driver != "" {
spec.Driver = &swarm.Driver{
Name: options.driver,
}
}
r, err := client.SecretCreate(ctx, spec) r, err := client.SecretCreate(ctx, spec)
if err != nil { if err != nil {
@ -77,3 +80,23 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error {
fmt.Fprintln(dockerCli.Out(), r.ID) fmt.Fprintln(dockerCli.Out(), r.ID)
return nil 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
}

View File

@ -24,12 +24,11 @@ func TestSecretCreateErrors(t *testing.T) {
secretCreateFunc func(swarm.SecretSpec) (types.SecretCreateResponse, error) secretCreateFunc func(swarm.SecretSpec) (types.SecretCreateResponse, error)
expectedError string expectedError string
}{ }{
{
args: []string{"too_few"},
expectedError: "requires exactly 2 arguments",
},
{args: []string{"too", "many", "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)}, 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())) 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) { func TestSecretCreateWithLabels(t *testing.T) {
expectedLabels := map[string]string{ expectedLabels := map[string]string{
"lbl1": "Label-foo", "lbl1": "Label-foo",

View File

@ -154,6 +154,7 @@ func TestSecretInspectPretty(t *testing.T) {
}), }),
SecretID("secretID"), SecretID("secretID"),
SecretName("secretName"), SecretName("secretName"),
SecretDriver("driver"),
SecretCreatedAt(time.Time{}), SecretCreatedAt(time.Time{}),
SecretUpdatedAt(time.Time{}), SecretUpdatedAt(time.Time{}),
), []byte{}, nil ), []byte{}, nil

View File

@ -63,6 +63,7 @@ func TestSecretList(t *testing.T) {
SecretVersion(swarm.Version{Index: 11}), SecretVersion(swarm.Version{Index: 11}),
SecretCreatedAt(time.Now().Add(-2*time.Hour)), SecretCreatedAt(time.Now().Add(-2*time.Hour)),
SecretUpdatedAt(time.Now().Add(-1*time.Hour)), SecretUpdatedAt(time.Now().Add(-1*time.Hour)),
SecretDriver("driver"),
), ),
}, nil }, nil
}, },

View File

@ -1,6 +1,7 @@
ID: secretID ID: secretID
Name: secretName Name: secretName
Labels: Labels:
- lbl1=value1 - lbl1=value1
Created at: 0001-01-01 00:00:00 +0000 utc Driver: driver
Updated at: 0001-01-01 00:00:00 +0000 utc Created at: 0001-01-01 00:00:00 +0000 utc
Updated at: 0001-01-01 00:00:00 +0000 utc

View File

@ -1,3 +1,3 @@
ID NAME CREATED UPDATED ID NAME DRIVER CREATED UPDATED
ID-foo foo 2 hours ago About an hour ago ID-foo foo 2 hours ago About an hour ago
ID-bar bar 2 hours ago About an hour ago ID-bar bar 2 hours ago About an hour ago

View File

@ -1,3 +1,3 @@
ID NAME CREATED UPDATED ID NAME DRIVER CREATED UPDATED
ID-foo foo 2 hours ago About an hour ago ID-foo foo 2 hours ago About an hour ago
ID-bar bar 2 hours ago About an hour ago ID-bar bar driver 2 hours ago About an hour ago

View File

@ -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 // SecretID sets the secret's ID
func SecretID(ID string) func(secret *swarm.Secret) { func SecretID(ID string) func(secret *swarm.Secret) {
return func(secret *swarm.Secret) { return func(secret *swarm.Secret) {