From d346e6e734dbaac911de0d774a5d8703e2abc83d Mon Sep 17 00:00:00 2001 From: Jakub Guzik Date: Thu, 2 Sep 2021 23:01:37 +0200 Subject: [PATCH] Add filtering functionality to http api secrets list Filtering is missing in both compat API and libpod API, while docker has filtering functinality. This commit enables filtering option using name and id in both libpod and http API. Signed-off-by: Jakub Guzik --- cmd/podman/common/completion.go | 2 +- cmd/podman/secrets/list.go | 2 +- pkg/api/handlers/compat/secrets.go | 22 ++++++--------- pkg/api/server/register_secrets.go | 16 +++++++++++ pkg/bindings/secrets/secrets.go | 6 +++- pkg/bindings/secrets/types.go | 1 + pkg/bindings/secrets/types_list_options.go | 16 +++++++++++ pkg/domain/entities/engine_container.go | 2 +- pkg/domain/entities/secrets.go | 2 +- pkg/domain/infra/abi/secrets.go | 33 +++++++++++++--------- pkg/domain/infra/tunnel/secrets.go | 5 ++-- pkg/domain/utils/secrets_filters.go | 24 ++++++++++++++++ test/apiv2/50-secrets.at | 33 ++++++++++++++++++++-- 13 files changed, 128 insertions(+), 36 deletions(-) create mode 100644 pkg/domain/utils/secrets_filters.go diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 3966606e32..e925fb4f13 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -223,7 +223,7 @@ func getSecrets(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCom cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveNoFileComp } - secrets, err := engine.SecretList(registry.GetContext()) + secrets, err := engine.SecretList(registry.GetContext(), entities.SecretListRequest{}) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/podman/secrets/list.go b/cmd/podman/secrets/list.go index e64990c6f6..f136de4abb 100644 --- a/cmd/podman/secrets/list.go +++ b/cmd/podman/secrets/list.go @@ -48,7 +48,7 @@ func init() { } func ls(cmd *cobra.Command, args []string) error { - responses, err := registry.ContainerEngine().SecretList(context.Background()) + responses, err := registry.ContainerEngine().SecretList(context.Background(), entities.SecretListRequest{}) if err != nil { return err } diff --git a/pkg/api/handlers/compat/secrets.go b/pkg/api/handlers/compat/secrets.go index 86e3887a4f..7dd17ea941 100644 --- a/pkg/api/handlers/compat/secrets.go +++ b/pkg/api/handlers/compat/secrets.go @@ -11,31 +11,25 @@ import ( "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" - "github.com/gorilla/schema" + "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" ) func ListSecrets(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) - decoder = r.Context().Value("decoder").(*schema.Decoder) ) - query := struct { - Filters map[string][]string `schema:"filters"` - }{} - - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + filtersMap, err := util.PrepareFilters(r) + if err != nil { + utils.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - if len(query.Filters) > 0 { - utils.Error(w, "filters not supported", http.StatusBadRequest, - errors.Wrapf(errors.New("bad parameter"), "filters not supported")) - return - } ic := abi.ContainerEngine{Libpod: runtime} - reports, err := ic.SecretList(r.Context()) + listOptions := entities.SecretListRequest{ + Filters: *filtersMap, + } + reports, err := ic.SecretList(r.Context(), listOptions) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/server/register_secrets.go b/pkg/api/server/register_secrets.go index ca9790e93f..129912179b 100644 --- a/pkg/api/server/register_secrets.go +++ b/pkg/api/server/register_secrets.go @@ -44,6 +44,14 @@ func (s *APIServer) registerSecretHandlers(r *mux.Router) error { // - secrets // summary: List secrets // description: Returns a list of secrets + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // JSON encoded value of the filters (a `map[string][]string`) to process on the secrets list. Currently available filters: + // - `name=[name]` Matches secrets name (accepts regex). + // - `id=[id]` Matches for full or partial ID. // produces: // - application/json // parameters: @@ -110,6 +118,14 @@ func (s *APIServer) registerSecretHandlers(r *mux.Router) error { // - secrets (compat) // summary: List secrets // description: Returns a list of secrets + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // JSON encoded value of the filters (a `map[string][]string`) to process on the secrets list. Currently available filters: + // - `name=[name]` Matches secrets name (accepts regex). + // - `id=[id]` Matches for full or partial ID. // produces: // - application/json // parameters: diff --git a/pkg/bindings/secrets/secrets.go b/pkg/bindings/secrets/secrets.go index b741d3e5c4..c439971c96 100644 --- a/pkg/bindings/secrets/secrets.go +++ b/pkg/bindings/secrets/secrets.go @@ -18,7 +18,11 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.SecretInfoRepo if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/json", nil, nil) + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/json", params, nil) if err != nil { return secrs, err } diff --git a/pkg/bindings/secrets/types.go b/pkg/bindings/secrets/types.go index a64dea1b47..01c3c248d1 100644 --- a/pkg/bindings/secrets/types.go +++ b/pkg/bindings/secrets/types.go @@ -3,6 +3,7 @@ package secrets //go:generate go run ../generator/generator.go ListOptions // ListOptions are optional options for inspecting secrets type ListOptions struct { + Filters map[string][]string } //go:generate go run ../generator/generator.go InspectOptions diff --git a/pkg/bindings/secrets/types_list_options.go b/pkg/bindings/secrets/types_list_options.go index 568e021a89..e4501dde85 100644 --- a/pkg/bindings/secrets/types_list_options.go +++ b/pkg/bindings/secrets/types_list_options.go @@ -19,3 +19,19 @@ func (o *ListOptions) Changed(fieldName string) bool { func (o *ListOptions) ToParams() (url.Values, error) { return util.ToParams(o) } + +// WithFilters +func (o *ListOptions) WithFilters(value map[string][]string) *ListOptions { + v := value + o.Filters = v + return o +} + +// GetFilters +func (o *ListOptions) GetFilters() map[string][]string { + var filters map[string][]string + if o.Filters == nil { + return filters + } + return o.Filters +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index bd011d309b..d03e2f719c 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -85,7 +85,7 @@ type ContainerEngine interface { SetupRootless(ctx context.Context, noMoveProcess bool) error SecretCreate(ctx context.Context, name string, reader io.Reader, options SecretCreateOptions) (*SecretCreateReport, error) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*SecretInfoReport, []error, error) - SecretList(ctx context.Context) ([]*SecretInfoReport, error) + SecretList(ctx context.Context, opts SecretListRequest) ([]*SecretInfoReport, error) SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error) Shutdown(ctx context.Context) SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error) diff --git a/pkg/domain/entities/secrets.go b/pkg/domain/entities/secrets.go index 56a1465b74..55b470d7b8 100644 --- a/pkg/domain/entities/secrets.go +++ b/pkg/domain/entities/secrets.go @@ -16,7 +16,7 @@ type SecretCreateOptions struct { } type SecretListRequest struct { - Filters map[string]string + Filters map[string][]string } type SecretListReport struct { diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go index 0bdb4ce605..2bf8eaae31 100644 --- a/pkg/domain/infra/abi/secrets.go +++ b/pkg/domain/infra/abi/secrets.go @@ -7,6 +7,7 @@ import ( "path/filepath" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/domain/utils" "github.com/pkg/errors" ) @@ -84,7 +85,7 @@ func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string return reports, errs, nil } -func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) { +func (ic *ContainerEngine) SecretList(ctx context.Context, opts entities.SecretListRequest) ([]*entities.SecretInfoReport, error) { manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err @@ -95,19 +96,25 @@ func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretIn } report := make([]*entities.SecretInfoReport, 0, len(secretList)) for _, secret := range secretList { - reportItem := entities.SecretInfoReport{ - ID: secret.ID, - CreatedAt: secret.CreatedAt, - UpdatedAt: secret.CreatedAt, - Spec: entities.SecretSpec{ - Name: secret.Name, - Driver: entities.SecretDriverSpec{ - Name: secret.Driver, - Options: secret.DriverOptions, - }, - }, + result, err := utils.IfPassesSecretsFilter(secret, opts.Filters) + if err != nil { + return nil, err + } + if result { + reportItem := entities.SecretInfoReport{ + ID: secret.ID, + CreatedAt: secret.CreatedAt, + UpdatedAt: secret.CreatedAt, + Spec: entities.SecretSpec{ + Name: secret.Name, + Driver: entities.SecretDriverSpec{ + Name: secret.Driver, + Options: secret.DriverOptions, + }, + }, + } + report = append(report, &reportItem) } - report = append(report, &reportItem) } return report, nil } diff --git a/pkg/domain/infra/tunnel/secrets.go b/pkg/domain/infra/tunnel/secrets.go index ecbb809319..6337c7fbe8 100644 --- a/pkg/domain/infra/tunnel/secrets.go +++ b/pkg/domain/infra/tunnel/secrets.go @@ -43,8 +43,9 @@ func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string return allInspect, errs, nil } -func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) { - secrs, _ := secrets.List(ic.ClientCtx, nil) +func (ic *ContainerEngine) SecretList(ctx context.Context, opts entities.SecretListRequest) ([]*entities.SecretInfoReport, error) { + options := new(secrets.ListOptions).WithFilters(opts.Filters) + secrs, _ := secrets.List(ic.ClientCtx, options) return secrs, nil } diff --git a/pkg/domain/utils/secrets_filters.go b/pkg/domain/utils/secrets_filters.go new file mode 100644 index 0000000000..3ff7c75307 --- /dev/null +++ b/pkg/domain/utils/secrets_filters.go @@ -0,0 +1,24 @@ +package utils + +import ( + "strings" + + "github.com/containers/common/pkg/secrets" + "github.com/containers/podman/v3/pkg/util" + "github.com/pkg/errors" +) + +func IfPassesSecretsFilter(s secrets.Secret, filters map[string][]string) (bool, error) { + result := true + for key, filterValues := range filters { + switch strings.ToLower(key) { + case "name": + result = util.StringMatchRegexSlice(s.Name, filterValues) + case "id": + result = util.StringMatchRegexSlice(s.ID, filterValues) + default: + return false, errors.Errorf("invalid filter %q", key) + } + } + return result, nil +} diff --git a/test/apiv2/50-secrets.at b/test/apiv2/50-secrets.at index 034ec080a9..ed0e8fb6bd 100644 --- a/test/apiv2/50-secrets.at +++ b/test/apiv2/50-secrets.at @@ -27,8 +27,37 @@ t GET secrets 200 \ .[0].Spec.Name=mysecret \ .[0].Version.Index=1 -# secret list unsupported filters -t GET secrets?filters='{"name":["foo1"]}' 400 +# secret list with filters +t GET secrets?filters='{"name":["mysecret"]}' 200 \ + length=1 \ + .[0].Spec.Name=mysecret \ + .[0].Version.Index=1 + +t GET secrets?filters='{"name":["mysecret2"]}' 200 \ + length=0 \ + +# secret libpod list with filters +t GET libpod/secrets/json?filters='{"name":["mysecret"]}' 200 \ + length=1 \ + .[0].Spec.Name=mysecret \ + +t GET libpod/secrets/json?filters='{"name":["mysecret2"]}' 200 \ + length=0 \ + +# secret list with unsupported filters +t GET secrets?filters='{"label":["xyz"]}' 500 + +#compat api list secrets sanity checks +t GET secrets?filters='garb1age}' 500 \ + .cause="invalid character 'g' looking for beginning of value" +t GET secrets?filters='{"label":["testl' 500 \ + .cause="unexpected end of JSON input" + +#libpod api list secrets sanity checks +t GET libpod/secrets/json?filters='garb1age}' 500 \ + .cause="invalid character 'g' looking for beginning of value" +t GET libpod/secrets/json?filters='{"label":["testl' 500 \ + .cause="unexpected end of JSON input" # secret rm t DELETE secrets/mysecret 204