From 930d873ea567a6c1ff7e2069add65c81e2de732b Mon Sep 17 00:00:00 2001 From: cyli Date: Fri, 2 Sep 2016 14:32:28 -0700 Subject: [PATCH] Add swarmctl commands to create, inspect, list and remove secrets. Signed-off-by: cyli --- cmd/swarmctl/main.go | 2 + cmd/swarmctl/secrets/cmd.go | 21 ++++++++ cmd/swarmctl/secrets/common.go | 43 ++++++++++++++++ cmd/swarmctl/secrets/create.go | 44 +++++++++++++++++ cmd/swarmctl/secrets/inspect.go | 57 +++++++++++++++++++++ cmd/swarmctl/secrets/list.go | 88 +++++++++++++++++++++++++++++++++ cmd/swarmctl/secrets/remove.go | 38 ++++++++++++++ 7 files changed, 293 insertions(+) create mode 100644 cmd/swarmctl/secrets/cmd.go create mode 100644 cmd/swarmctl/secrets/common.go create mode 100644 cmd/swarmctl/secrets/create.go create mode 100644 cmd/swarmctl/secrets/inspect.go create mode 100644 cmd/swarmctl/secrets/list.go create mode 100644 cmd/swarmctl/secrets/remove.go diff --git a/cmd/swarmctl/main.go b/cmd/swarmctl/main.go index 1a94accc31..5210830260 100644 --- a/cmd/swarmctl/main.go +++ b/cmd/swarmctl/main.go @@ -6,6 +6,7 @@ import ( "github.com/docker/swarmkit/cmd/swarmctl/cluster" "github.com/docker/swarmkit/cmd/swarmctl/network" "github.com/docker/swarmkit/cmd/swarmctl/node" + "github.com/docker/swarmkit/cmd/swarmctl/secrets" "github.com/docker/swarmkit/cmd/swarmctl/service" "github.com/docker/swarmkit/cmd/swarmctl/task" "github.com/docker/swarmkit/version" @@ -54,5 +55,6 @@ func init() { version.Cmd, network.Cmd, cluster.Cmd, + secrets.Cmd, ) } diff --git a/cmd/swarmctl/secrets/cmd.go b/cmd/swarmctl/secrets/cmd.go new file mode 100644 index 0000000000..0911e0ec34 --- /dev/null +++ b/cmd/swarmctl/secrets/cmd.go @@ -0,0 +1,21 @@ +package secrets + +import "github.com/spf13/cobra" + +var ( + // Cmd exposes the top-level service command. + Cmd = &cobra.Command{ + Use: "secret", + Aliases: nil, + Short: "Secrets management", + } +) + +func init() { + Cmd.AddCommand( + inspectCmd, + listCmd, + createCmd, + removeCmd, + ) +} diff --git a/cmd/swarmctl/secrets/common.go b/cmd/swarmctl/secrets/common.go new file mode 100644 index 0000000000..03586d853a --- /dev/null +++ b/cmd/swarmctl/secrets/common.go @@ -0,0 +1,43 @@ +package secrets + +import ( + "fmt" + + "github.com/docker/swarmkit/api" + "golang.org/x/net/context" +) + +func getSecret(ctx context.Context, c api.ControlClient, input string) (*api.Secret, error) { + // not sure what it is, match by name or id prefix + resp, err := c.ListSecrets(ctx, + &api.ListSecretsRequest{ + Filters: &api.ListSecretsRequest_Filters{ + Names: []string{input}, + IDPrefixes: []string{input}, + }, + }, + ) + if err != nil { + return nil, err + } + + switch len(resp.Secrets) { + case 0: + return nil, fmt.Errorf("secret %s not found", input) + case 1: + return resp.Secrets[0], nil + default: + // ok, multiple matches. Prefer exact ID over exact name. If no exact matches, return an error + for _, s := range resp.Secrets { + if s.ID == input { + return s, nil + } + } + for _, s := range resp.Secrets { + if s.Spec.Annotations.Name == input { + return s, nil + } + } + return nil, fmt.Errorf("secret %s is ambiguous (%d matches found)", input, len(resp.Secrets)) + } +} diff --git a/cmd/swarmctl/secrets/create.go b/cmd/swarmctl/secrets/create.go new file mode 100644 index 0000000000..bcd3ba0e46 --- /dev/null +++ b/cmd/swarmctl/secrets/create.go @@ -0,0 +1,44 @@ +package secrets + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + + "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/cmd/swarmctl/common" + "github.com/spf13/cobra" +) + +var createCmd = &cobra.Command{ + Use: "create ", + Short: "Create a secret", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("create command takes a unique secret name as an argument, and accepts secret data via stdin") + } + + secretData, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("Error reading content from STDIN: %v", err) + } + + client, err := common.Dial(cmd) + if err != nil { + return err + } + + spec := &api.SecretSpec{ + Annotations: api.Annotations{Name: args[0]}, + Data: secretData, + } + + resp, err := client.CreateSecret(common.Context(cmd), &api.CreateSecretRequest{Spec: spec}) + if err != nil { + return err + } + fmt.Println(resp.Secret.ID) + return nil + }, +} diff --git a/cmd/swarmctl/secrets/inspect.go b/cmd/swarmctl/secrets/inspect.go new file mode 100644 index 0000000000..2e63e50b0a --- /dev/null +++ b/cmd/swarmctl/secrets/inspect.go @@ -0,0 +1,57 @@ +package secrets + +import ( + "errors" + "fmt" + "os" + "text/tabwriter" + "time" + + "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/cmd/swarmctl/common" + "github.com/spf13/cobra" +) + +func printSecretSummary(secret *api.Secret) { + w := tabwriter.NewWriter(os.Stdout, 8, 8, 8, ' ', 0) + defer w.Flush() + + common.FprintfIfNotEmpty(w, "ID\t: %s\n", secret.ID) + common.FprintfIfNotEmpty(w, "Name\t: %s\n", secret.Spec.Annotations.Name) + if len(secret.Spec.Annotations.Labels) > 0 { + fmt.Fprintln(w, "Labels\t") + for k, v := range secret.Spec.Annotations.Labels { + fmt.Fprintf(w, " %s\t: %s\n", k, v) + } + } + common.FprintfIfNotEmpty(w, "Digest\t: %s\n", secret.Digest) + common.FprintfIfNotEmpty(w, "Size\t: %d\n", secret.SecretSize) + + created := time.Unix(int64(secret.Meta.CreatedAt.Seconds), int64(secret.Meta.CreatedAt.Nanos)) + common.FprintfIfNotEmpty(w, "Created\t: %s\n", created.Format(time.RFC822)) +} + +var ( + inspectCmd = &cobra.Command{ + Use: "inspect ", + Short: "Inspect a secret", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("inspect command takes a single secret ID or name") + } + + client, err := common.Dial(cmd) + if err != nil { + return err + } + + secret, err := getSecret(common.Context(cmd), client, args[0]) + if err != nil { + return err + } + + printSecretSummary(secret) + return nil + }, + } +) diff --git a/cmd/swarmctl/secrets/list.go b/cmd/swarmctl/secrets/list.go new file mode 100644 index 0000000000..3367dc92d2 --- /dev/null +++ b/cmd/swarmctl/secrets/list.go @@ -0,0 +1,88 @@ +package secrets + +import ( + "errors" + "fmt" + "os" + "sort" + "text/tabwriter" + "time" + + "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/cmd/swarmctl/common" + "github.com/dustin/go-humanize" + "github.com/spf13/cobra" +) + +type secretSorter []*api.Secret + +func (k secretSorter) Len() int { return len(k) } +func (k secretSorter) Swap(i, j int) { k[i], k[j] = k[j], k[i] } +func (k secretSorter) Less(i, j int) bool { + iTime := time.Unix(k[i].Meta.CreatedAt.Seconds, int64(k[i].Meta.CreatedAt.Nanos)) + jTime := time.Unix(k[j].Meta.CreatedAt.Seconds, int64(k[j].Meta.CreatedAt.Nanos)) + return jTime.Before(iTime) +} + +var ( + listCmd = &cobra.Command{ + Use: "ls", + Short: "List secrets", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return errors.New("ls command takes no arguments") + } + + flags := cmd.Flags() + quiet, err := flags.GetBool("quiet") + if err != nil { + return err + } + + client, err := common.Dial(cmd) + if err != nil { + return err + } + + resp, err := client.ListSecrets(common.Context(cmd), &api.ListSecretsRequest{}) + if err != nil { + return err + } + + var output func(*api.Secret) + + if !quiet { + w := tabwriter.NewWriter(os.Stdout, 0, 4, 4, ' ', 0) + defer func() { + // Ignore flushing errors - there's nothing we can do. + _ = w.Flush() + }() + common.PrintHeader(w, "ID", "Name", "Created", "Digest", "Size") + output = func(s *api.Secret) { + created := time.Unix(int64(s.Meta.CreatedAt.Seconds), int64(s.Meta.CreatedAt.Nanos)) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", + s.ID, + s.Spec.Annotations.Name, + humanize.Time(created), + s.Digest, + s.SecretSize, + ) + } + + } else { + output = func(s *api.Secret) { fmt.Println(s.ID) } + } + + sorted := secretSorter(resp.Secrets) + sort.Sort(sorted) + for _, s := range sorted { + output(s) + } + return nil + }, + } +) + +func init() { + listCmd.Flags().BoolP("quiet", "q", false, "Only display secret names") +} diff --git a/cmd/swarmctl/secrets/remove.go b/cmd/swarmctl/secrets/remove.go new file mode 100644 index 0000000000..47cbb10720 --- /dev/null +++ b/cmd/swarmctl/secrets/remove.go @@ -0,0 +1,38 @@ +package secrets + +import ( + "errors" + "fmt" + + "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/cmd/swarmctl/common" + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "remove ", + Short: "Remove a secret", + Aliases: []string{"rm"}, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("remove command takes a single secret ID or name") + } + + client, err := common.Dial(cmd) + if err != nil { + return err + } + + secret, err := getSecret(common.Context(cmd), client, args[0]) + if err != nil { + return err + } + + _, err = client.RemoveSecret(common.Context(cmd), &api.RemoveSecretRequest{SecretID: secret.ID}) + if err != nil { + return err + } + fmt.Println(secret.ID) + return nil + }, +}