diff --git a/cli/cmd/login/login.go b/cli/cmd/login/login.go index f24ad27f1..f9f78410a 100644 --- a/cli/cmd/login/login.go +++ b/cli/cmd/login/login.go @@ -42,8 +42,8 @@ func Command() *cobra.Command { } // define flags for backward compatibility with com.docker.cli flags := cmd.Flags() - flags.StringP("username", "u", "", "Username") - flags.StringP("password", "p", "", "Password") + flags.StringP("username", "u", "", "username") + flags.StringP("password", "p", "", "password") flags.BoolP("password-stdin", "", false, "Take the password from stdin") mobyflags.AddMobyFlagsForRetrocompatibility(flags) diff --git a/cli/cmd/secrets.go b/cli/cmd/secrets.go new file mode 100644 index 000000000..44978b791 --- /dev/null +++ b/cli/cmd/secrets.go @@ -0,0 +1,165 @@ +/* + Copyright 2020 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + + "github.com/spf13/cobra" + + "github.com/docker/api/client" + "github.com/docker/api/secrets" +) + +type createSecretOptions struct { + Label string + Username string + Password string + Description string +} + +// SecretCommand manage secrets +func SecretCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "secret", + Short: "Manages secrets", + } + + cmd.AddCommand( + createSecret(), + inspectSecret(), + listSecrets(), + deleteSecret(), + ) + return cmd +} + +func createSecret() *cobra.Command { + opts := createSecretOptions{} + cmd := &cobra.Command{ + Use: "create NAME", + Short: "Creates a secret.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + c, err := client.New(cmd.Context()) + if err != nil { + return err + } + name := args[0] + secret := secrets.NewSecret(name, opts.Username, opts.Password, opts.Description) + id, err := c.SecretsService().CreateSecret(cmd.Context(), secret) + if err != nil { + return err + } + fmt.Println(id) + return nil + }, + } + + cmd.Flags().StringVarP(&opts.Username, "username", "u", "", "username") + cmd.Flags().StringVarP(&opts.Password, "password", "p", "", "password") + cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Secret description") + return cmd +} + +func inspectSecret() *cobra.Command { + cmd := &cobra.Command{ + Use: "inspect ID", + Short: "Displays secret details", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + c, err := client.New(cmd.Context()) + if err != nil { + return err + } + secret, err := c.SecretsService().InspectSecret(cmd.Context(), args[0]) + if err != nil { + return err + } + out, err := secret.ToJSON() + if err != nil { + return err + } + fmt.Println(out) + return nil + }, + } + return cmd +} + +func listSecrets() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List secrets stored for the existing account.", + RunE: func(cmd *cobra.Command, args []string) error { + c, err := client.New(cmd.Context()) + if err != nil { + return err + } + list, err := c.SecretsService().ListSecrets(cmd.Context()) + if err != nil { + return err + } + printList(os.Stdout, list) + return nil + }, + } + return cmd +} + +type deleteSecretOptions struct { + recover bool +} + +func deleteSecret() *cobra.Command { + opts := deleteSecretOptions{} + cmd := &cobra.Command{ + Use: "delete NAME", + Aliases: []string{"rm", "remove"}, + Short: "Removes a secret.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + c, err := client.New(cmd.Context()) + if err != nil { + return err + } + return c.SecretsService().DeleteSecret(cmd.Context(), args[0], opts.recover) + }, + } + cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.") + return cmd +} + +func printList(out io.Writer, secrets []secrets.Secret) { + printSection(out, func(w io.Writer) { + for _, secret := range secrets { + fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) // nolint:errcheck + } + }, "ID", "NAME", "DESCRIPTION") +} + +func printSection(out io.Writer, printer func(io.Writer), headers ...string) { + w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0) + fmt.Fprintln(w, strings.Join(headers, "\t")) // nolint:errcheck + printer(w) + w.Flush() // nolint:errcheck +} diff --git a/cli/mobycli/delegate_ecs.go b/cli/cmd/secrets_test.go similarity index 61% rename from cli/mobycli/delegate_ecs.go rename to cli/cmd/secrets_test.go index 65415e8c0..a443cc071 100644 --- a/cli/mobycli/delegate_ecs.go +++ b/cli/cmd/secrets_test.go @@ -1,5 +1,3 @@ -// +build !ecs - /* Copyright 2020 Docker, Inc. @@ -16,10 +14,26 @@ limitations under the License. */ -package mobycli +package cmd -import "github.com/docker/api/context/store" +import ( + "bytes" + "testing" -func init() { - delegatedContextTypes = append(delegatedContextTypes, store.AwsContextType, store.EcsContextType) + "gotest.tools/v3/golden" + + "github.com/docker/api/secrets" +) + +func TestPrintList(t *testing.T) { + secrets := []secrets.Secret{ + { + ID: "123", + Name: "secret123", + Description: "secret 1,2,3", + }, + } + out := &bytes.Buffer{} + printList(out, secrets) + golden.Assert(t, out.String(), "secrets-out.golden") } diff --git a/cli/cmd/testdata/secrets-out.golden b/cli/cmd/testdata/secrets-out.golden new file mode 100644 index 000000000..a902647a9 --- /dev/null +++ b/cli/cmd/testdata/secrets-out.golden @@ -0,0 +1,2 @@ +ID NAME DESCRIPTION +123 secret123 secret 1,2,3 diff --git a/cli/main.go b/cli/main.go index d5f6e11c7..26b2c2071 100644 --- a/cli/main.go +++ b/cli/main.go @@ -126,6 +126,7 @@ func main() { logout.Command(), cmd.VersionCommand(version), cmd.StopCommand(), + cmd.SecretCommand(), ) helpFunc := root.HelpFunc() diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go index 967747fab..ace7c8aec 100644 --- a/cli/mobycli/exec.go +++ b/cli/mobycli/exec.go @@ -28,7 +28,7 @@ import ( "github.com/docker/api/context/store" ) -var delegatedContextTypes = []string{store.DefaultContextType} +var delegatedContextTypes = []string{store.DefaultContextType, store.AwsContextType} // ComDockerCli name of the classic cli binary const ComDockerCli = "com.docker.cli"