diff --git a/components/engine/api/swagger.yaml b/components/engine/api/swagger.yaml index da8ac9158c..746d815356 100644 --- a/components/engine/api/swagger.yaml +++ b/components/engine/api/swagger.yaml @@ -7984,6 +7984,9 @@ paths: description: | A JSON encoded value of the filters (a `map[string][]string`) to process on the secrets list. Available filters: + - `id=` + - `label= or label==value` + - `name=` - `names=` tags: ["Secret"] /secrets/create: diff --git a/components/engine/cli/command/secret/ls.go b/components/engine/cli/command/secret/ls.go index 211ebceb5f..1d60ff7c4d 100644 --- a/components/engine/cli/command/secret/ls.go +++ b/components/engine/cli/command/secret/ls.go @@ -5,6 +5,7 @@ import ( "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/formatter" + "github.com/docker/docker/opts" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -12,10 +13,11 @@ import ( type listOptions struct { quiet bool format string + filter opts.FilterOpt } func newSecretListCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := listOptions{} + opts := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -30,6 +32,7 @@ func newSecretListCommand(dockerCli *command.DockerCli) *cobra.Command { flags := cmd.Flags() flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") flags.StringVarP(&opts.format, "format", "", "", "Pretty-print secrets using a Go template") + flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") return cmd } @@ -38,7 +41,7 @@ func runSecretList(dockerCli *command.DockerCli, opts listOptions) error { client := dockerCli.Client() ctx := context.Background() - secrets, err := client.SecretList(ctx, types.SecretListOptions{}) + secrets, err := client.SecretList(ctx, types.SecretListOptions{Filters: opts.filter.Value()}) if err != nil { return err } diff --git a/components/engine/cli/command/service/parse.go b/components/engine/cli/command/service/parse.go index f86bebe87c..acee08761f 100644 --- a/components/engine/cli/command/service/parse.go +++ b/components/engine/cli/command/service/parse.go @@ -26,7 +26,7 @@ func ParseSecrets(client client.SecretAPIClient, requestedSecrets []*swarmtypes. args := filters.NewArgs() for _, s := range secretRefs { - args.Add("names", s.SecretName) + args.Add("name", s.SecretName) } secrets, err := client.SecretList(ctx, types.SecretListOptions{ diff --git a/components/engine/daemon/cluster/filters_test.go b/components/engine/daemon/cluster/filters_test.go new file mode 100644 index 0000000000..8f5fa83164 --- /dev/null +++ b/components/engine/daemon/cluster/filters_test.go @@ -0,0 +1,57 @@ +package cluster + +import ( + "testing" + + "github.com/docker/docker/api/types/filters" +) + +func TestNewListSecretsFilters(t *testing.T) { + validNameFilter := filters.NewArgs() + validNameFilter.Add("name", "test_name") + + validIDFilter := filters.NewArgs() + validIDFilter.Add("id", "7c9009d6720f6de3b492f5") + + validLabelFilter := filters.NewArgs() + validLabelFilter.Add("label", "type=test") + validLabelFilter.Add("label", "storage=ssd") + validLabelFilter.Add("label", "memory") + + validNamesFilter := filters.NewArgs() + validNamesFilter.Add("names", "test_name") + + validAllFilter := filters.NewArgs() + validAllFilter.Add("name", "nodeName") + validAllFilter.Add("id", "7c9009d6720f6de3b492f5") + validAllFilter.Add("label", "type=test") + validAllFilter.Add("label", "memory") + validAllFilter.Add("names", "test_name") + + validFilters := []filters.Args{ + validNameFilter, + validIDFilter, + validLabelFilter, + validNamesFilter, + validAllFilter, + } + + invalidTypeFilter := filters.NewArgs() + invalidTypeFilter.Add("nonexist", "aaaa") + + invalidFilters := []filters.Args{ + invalidTypeFilter, + } + + for _, filter := range validFilters { + if _, err := newListSecretsFilters(filter); err != nil { + t.Fatalf("Should get no error, got %v", err) + } + } + + for _, filter := range invalidFilters { + if _, err := newListSecretsFilters(filter); err == nil { + t.Fatalf("Should get an error for filter %s, while got nil", filter) + } + } +} diff --git a/components/engine/docs/reference/commandline/secret_ls.md b/components/engine/docs/reference/commandline/secret_ls.md index 743aecffb7..9b60227b8e 100644 --- a/components/engine/docs/reference/commandline/secret_ls.md +++ b/components/engine/docs/reference/commandline/secret_ls.md @@ -24,8 +24,10 @@ Aliases: ls, list Options: - -q, --quiet Only display IDs - -format string Pretty-print secrets using a Go template + -f, --filter filter Filter output based on conditions provided + --format string Pretty-print secrets using a Go template + --help Print usage + -q, --quiet Only display IDs ``` ## Description @@ -39,8 +41,70 @@ For detailed information about using secrets, refer to [manage sensitive data wi ```bash $ docker secret ls -ID NAME CREATED UPDATED -eo7jnzguqgtpdah3cm5srfb97 my_secret 11 minutes ago 11 minutes ago +ID NAME CREATED UPDATED +6697bflskwj1998km1gnnjr38 q5s5570vtvnimefos1fyeo2u2 6 weeks ago 6 weeks ago +9u9hk4br2ej0wgngkga6rp4hq my_secret 5 weeks ago 5 weeks ago +mem02h8n73mybpgqjf0kfi1n0 test_secret 3 seconds ago 3 seconds ago +``` + +### Filtering + +The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more +than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`) + +The currently supported filters are: + +* [id](secret_ls.md#id) (secret's ID) +* [label](secret_ls.md#label) (`label=` or `label==`) +* [name](secret_ls.md#name) (secret's name) + +#### id + +The `id` filter matches all or prefix of a secret's id. + +```bash +$ docker secret ls -f "id=6697bflskwj1998km1gnnjr38" + +ID NAME CREATED UPDATED +6697bflskwj1998km1gnnjr38 q5s5570vtvnimefos1fyeo2u2 6 weeks ago 6 weeks ago +``` + +#### label + +The `label` filter matches secrets based on the presence of a `label` alone or +a `label` and a value. + +The following filter matches all secrets with a `project` label regardless of +its value: + +```bash +$ docker secret ls --filter label=project + +ID NAME CREATED UPDATED +mem02h8n73mybpgqjf0kfi1n0 test_secret About an hour ago About an hour ago +``` + +The following filter matches only services with the `project` label with the +`project-a` value. + +```bash +$ docker service ls --filter label=project=test + +ID NAME CREATED UPDATED +mem02h8n73mybpgqjf0kfi1n0 test_secret About an hour ago About an hour ago +``` + +#### name + +The `name` filter matches on all or prefix of a secret's name. + +The following filter matches secret with a name containing a prefix of `test`. + +```bash +$ docker secret ls --filter name=test_secret + +ID NAME CREATED UPDATED +mem02h8n73mybpgqjf0kfi1n0 test_secret About an hour ago About an hour ago ``` ### Format the output diff --git a/components/engine/integration-cli/docker_cli_secret_ls_test.go b/components/engine/integration-cli/docker_cli_secret_ls_test.go new file mode 100644 index 0000000000..f3201f7d7d --- /dev/null +++ b/components/engine/integration-cli/docker_cli_secret_ls_test.go @@ -0,0 +1,125 @@ +// +build !windows + +package main + +import ( + "strings" + + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/integration-cli/checker" + "github.com/go-check/check" +) + +func (s *DockerSwarmSuite) TestSecretList(c *check.C) { + d := s.AddDaemon(c, true, true) + + testName0 := "test0" + testName1 := "test1" + + // create secret test0 + id0 := d.CreateSecret(c, swarm.SecretSpec{ + Annotations: swarm.Annotations{ + Name: testName0, + Labels: map[string]string{"type": "test"}, + }, + Data: []byte("TESTINGDATA0"), + }) + c.Assert(id0, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id0)) + + secret := d.GetSecret(c, id0) + c.Assert(secret.Spec.Name, checker.Equals, testName0) + + // create secret test1 + id1 := d.CreateSecret(c, swarm.SecretSpec{ + Annotations: swarm.Annotations{ + Name: testName1, + Labels: map[string]string{"type": "production"}, + }, + Data: []byte("TESTINGDATA1"), + }) + c.Assert(id1, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id1)) + + secret = d.GetSecret(c, id1) + c.Assert(secret.Spec.Name, checker.Equals, testName1) + + // test by command `docker secret ls` + out, err := d.Cmd("secret", "ls") + c.Assert(err, checker.IsNil, check.Commentf(out)) + c.Assert(strings.TrimSpace(out), checker.Contains, testName0) + c.Assert(strings.TrimSpace(out), checker.Contains, testName1) + + // test filter by name `docker secret ls --filter name=xxx` + args := []string{ + "secret", + "ls", + "--filter", + "name=test0", + } + out, err = d.Cmd(args...) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + c.Assert(strings.TrimSpace(out), checker.Contains, testName0) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName1) + + // test filter by id `docker secret ls --filter id=xxx` + args = []string{ + "secret", + "ls", + "--filter", + "id=" + id1, + } + out, err = d.Cmd(args...) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName0) + c.Assert(strings.TrimSpace(out), checker.Contains, testName1) + + // test filter by label `docker secret ls --filter label=xxx` + args = []string{ + "secret", + "ls", + "--filter", + "label=type", + } + out, err = d.Cmd(args...) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + c.Assert(strings.TrimSpace(out), checker.Contains, testName0) + c.Assert(strings.TrimSpace(out), checker.Contains, testName1) + + args = []string{ + "secret", + "ls", + "--filter", + "label=type=test", + } + out, err = d.Cmd(args...) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + c.Assert(strings.TrimSpace(out), checker.Contains, testName0) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName1) + + args = []string{ + "secret", + "ls", + "--filter", + "label=type=production", + } + out, err = d.Cmd(args...) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), testName0) + c.Assert(strings.TrimSpace(out), checker.Contains, testName1) + + // test invalid filter `docker secret ls --filter noexisttype=xxx` + args = []string{ + "secret", + "ls", + "--filter", + "noexisttype=test0", + } + out, err = d.Cmd(args...) + c.Assert(err, checker.NotNil, check.Commentf(out)) + + c.Assert(strings.TrimSpace(out), checker.Contains, "Error response from daemon: Invalid filter 'noexisttype'") +}