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 (
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue