mirror of https://github.com/docker/cli.git
Merge pull request #366 from twistlock/plugable_secrets_backend
Plugable secrets backend
This commit is contained in:
commit
3edf97e5a9
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -154,6 +154,7 @@ func TestSecretInspectPretty(t *testing.T) {
|
|||
}),
|
||||
SecretID("secretID"),
|
||||
SecretName("secretName"),
|
||||
SecretDriver("driver"),
|
||||
SecretCreatedAt(time.Time{}),
|
||||
SecretUpdatedAt(time.Time{}),
|
||||
), []byte{}, nil
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue