Skip to content

Commit

Permalink
Merge pull request #1567 from cyli/secrets-implementation
Browse files Browse the repository at this point in the history
secrets control-api implementation
  • Loading branch information
stevvooe authored Sep 28, 2016
2 parents a126286 + aebced0 commit 4cf6f72
Show file tree
Hide file tree
Showing 15 changed files with 1,078 additions and 21 deletions.
4 changes: 0 additions & 4 deletions api/control.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions api/control.proto
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ service Control {
// on the provided `CreateSecretRequest.SecretSpec`.
// - Returns `InvalidArgument` if the `CreateSecretRequest.SecretSpec` is malformed,
// or if the secret data is too long or contains invalid characters.
// - Returns `ResourceExhausted` if there are already the maximum number of allowed
// secrets in the system.
// - Returns an error if the creation fails.
rpc CreateSecret(CreateSecretRequest) returns (CreateSecretResponse) {
option (docker.protobuf.plugin.tls_authorization) = { roles: "swarm-manager" };
Expand Down
12 changes: 6 additions & 6 deletions api/objects.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions api/objects.proto
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ message Secret {
// is calculated from the data contained in `Secret.Spec.data`.
string digest = 4;

// Size represents the size (number of bytes) of the secret data, and is
// calculated from the data contained in `Secret.Spec.data`..
uint32 size = 5 [(gogoproto.customname) = "SecretSize"];
// SecretSize represents the size (number of bytes) of the secret data, and is
// calculated from the data contained in `Secret.Spec.data`.
int64 size = 5 [(gogoproto.customname) = "SecretSize"];

// Whether the secret is an internal secret (not set by a user) or not.
bool internal = 6;
Expand Down
2 changes: 2 additions & 0 deletions cmd/swarmctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/secret"
"github.com/docker/swarmkit/cmd/swarmctl/service"
"github.com/docker/swarmkit/cmd/swarmctl/task"
"github.com/docker/swarmkit/version"
Expand Down Expand Up @@ -54,5 +55,6 @@ func init() {
version.Cmd,
network.Cmd,
cluster.Cmd,
secret.Cmd,
)
}
21 changes: 21 additions & 0 deletions cmd/swarmctl/secret/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package secret

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,
)
}
43 changes: 43 additions & 0 deletions cmd/swarmctl/secret/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package secret

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))
}
}
44 changes: 44 additions & 0 deletions cmd/swarmctl/secret/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package secret

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 <secret name>",
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
},
}
56 changes: 56 additions & 0 deletions cmd/swarmctl/secret/inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package secret

import (
"errors"
"fmt"
"os"
"text/tabwriter"

"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/cmd/swarmctl/common"
"github.com/docker/swarmkit/protobuf/ptypes"
"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)

common.FprintfIfNotEmpty(w, "Created\t: %s\n", ptypes.TimestampString(secret.Meta.CreatedAt))
}

var (
inspectCmd = &cobra.Command{
Use: "inspect <secret ID or name>",
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
},
}
)
97 changes: 97 additions & 0 deletions cmd/swarmctl/secret/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package secret

import (
"errors"
"fmt"
"os"
"sort"
"text/tabwriter"

"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/cmd/swarmctl/common"
"github.com/docker/swarmkit/protobuf/ptypes"
"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, err := ptypes.Timestamp(k[i].Meta.CreatedAt)
if err != nil {
panic(err)
}
jTime, err := ptypes.Timestamp(k[j].Meta.CreatedAt)
if err != nil {
panic(err)
}
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, err := ptypes.Timestamp(s.Meta.CreatedAt)
if err != nil {
panic(err)
}
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")
}
38 changes: 38 additions & 0 deletions cmd/swarmctl/secret/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package secret

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 <secret ID or name>",
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
},
}
Loading

0 comments on commit 4cf6f72

Please sign in to comment.