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 <jakubmguzik@gmail.com>
This commit is contained in:
Jakub Guzik 2021-09-02 23:01:37 +02:00
parent 469900406a
commit d346e6e734
13 changed files with 128 additions and 36 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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:

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -16,7 +16,7 @@ type SecretCreateOptions struct {
}
type SecretListRequest struct {
Filters map[string]string
Filters map[string][]string
}
type SecretListReport struct {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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