Merge pull request #4958 from jwhonce/wip/filters

[CI:DOCS] Add query parameter converters for complex types
This commit is contained in:
OpenShift Merge Robot
2020-01-24 06:56:55 -08:00
committed by GitHub
8 changed files with 104 additions and 49 deletions

View File

@ -0,0 +1,50 @@
package handlers
import (
"encoding/json"
"reflect"
"time"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
// NewAPIDecoder returns a configured schema.Decoder
func NewAPIDecoder() *schema.Decoder {
d := schema.NewDecoder()
d.IgnoreUnknownKeys(true)
d.RegisterConverter(map[string][]string{}, convertUrlValuesString)
d.RegisterConverter(time.Time{}, convertTimeString)
return d
}
// On client:
// v := map[string][]string{
// "dangling": {"true"},
// }
//
// payload, err := jsoniter.MarshalToString(v)
// if err != nil {
// panic(err)
// }
// payload = url.QueryEscape(payload)
func convertUrlValuesString(query string) reflect.Value {
f := map[string][]string{}
err := json.Unmarshal([]byte(query), &f)
if err != nil {
logrus.Infof("convertUrlValuesString: Failed to Unmarshal %s: %s", query, err.Error())
}
return reflect.ValueOf(f)
}
func convertTimeString(query string) reflect.Value {
t, err := time.Parse(time.RFC3339, query)
if err != nil {
logrus.Infof("convertTimeString: Failed to Unmarshal %s: %s", query, err.Error())
return reflect.ValueOf(time.Time{})
}
return reflect.ValueOf(t)
}

View File

@ -1,9 +1,10 @@
package handlers package handlers
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"time"
"github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -11,30 +12,24 @@ import (
func GetEvents(w http.ResponseWriter, r *http.Request) { func GetEvents(w http.ResponseWriter, r *http.Request) {
query := struct { query := struct {
Since string `json:"since"` Since time.Time `schema:"since"`
Until string `json:"until"` Until time.Time `schema:"until"`
Filters string `json:"filters"` Filters map[string][]string `schema:"filters"`
}{} }{}
if err := decodeQuery(r, &query); err != nil { if err := decodeQuery(r, &query); err != nil {
utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
} }
var filters = map[string][]string{} var libpodFilters = []string{}
if found := hasVar(r, "filters"); found { if _, found := r.URL.Query()["filters"]; found {
if err := json.Unmarshal([]byte(query.Filters), &filters); err != nil { for k, v := range query.Filters {
utils.BadRequest(w, "filters", query.Filters, err) libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
return
} }
} }
var libpodFilters = make([]string, len(filters))
for k, v := range filters {
libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
}
libpodEvents, err := getRuntime(r).GetEvents(libpodFilters) libpodEvents, err := getRuntime(r).GetEvents(libpodFilters)
if err != nil { if err != nil {
utils.BadRequest(w, "filters", query.Filters, err) utils.BadRequest(w, "filters", strings.Join(r.URL.Query()["filters"], ", "), err)
return return
} }

View File

@ -62,14 +62,14 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
// 200 no error // 200 no error
// 500 internal // 500 internal
var ( var (
dangling bool = true dangling = true
err error err error
) )
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime) runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct { query := struct {
filters map[string]string Filters map[string][]string `schema:"filters"`
}{ }{
// This is where you can override the golang default value for one of fields // This is where you can override the golang default value for one of fields
} }
@ -79,26 +79,25 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
return return
} }
// FIXME This is likely wrong due to it not being a map[string][]string
// until ts is not supported on podman prune // until ts is not supported on podman prune
if len(query.filters["until"]) > 0 { if v, found := query.Filters["until"]; found {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "until is not supported yet")) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "until=%s is not supported yet", v))
return return
} }
// labels are not supported on podman prune // labels are not supported on podman prune
if len(query.filters["label"]) > 0 { if _, found := query.Filters["since"]; found {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "labelis not supported yet")) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "labelis not supported yet"))
return return
} }
if len(query.filters["dangling"]) > 0 { if v, found := query.Filters["dangling"]; found {
dangling, err = strconv.ParseBool(query.filters["dangling"]) dangling, err = strconv.ParseBool(v[0])
if err != nil { if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter")) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter"))
return return
} }
} }
idr := []types.ImageDeleteResponseItem{} idr := []types.ImageDeleteResponseItem{}
// //
// This code needs to be migrated to libpod to work correctly. I could not // This code needs to be migrated to libpod to work correctly. I could not

View File

@ -15,10 +15,11 @@ func getVar(r *http.Request, k string) string {
return mux.Vars(r)[k] return mux.Vars(r)[k]
} }
func hasVar(r *http.Request, k string) bool { // func hasVar(r *http.Request, k string) bool {
_, found := mux.Vars(r)[k] // _, found := mux.Vars(r)[k]
return found // return found
} // }
func getName(r *http.Request) string { func getName(r *http.Request) string {
return getVar(r, "name") return getVar(r, "name")
} }

View File

@ -1,6 +1,7 @@
package libpod package libpod
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
@ -42,12 +43,12 @@ func ImageExists(w http.ResponseWriter, r *http.Request) {
func ImageTree(w http.ResponseWriter, r *http.Request) { func ImageTree(w http.ResponseWriter, r *http.Request) {
// tree is a bit of a mess ... logic is in adapter and therefore not callable from here. needs rework // tree is a bit of a mess ... logic is in adapter and therefore not callable from here. needs rework
//name := mux.Vars(r)["name"] // name := mux.Vars(r)["name"]
//_, layerInfoMap, _, err := s.Runtime.Tree(name) // _, layerInfoMap, _, err := s.Runtime.Tree(name)
//if err != nil { // if err != nil {
// Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name)) // Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name))
// return // return
//} // }
// it is not clear to me how to deal with this given all the processing of the image // it is not clear to me how to deal with this given all the processing of the image
// is in main. we need to discuss how that really should be and return something useful. // is in main. we need to discuss how that really should be and return something useful.
handlers.UnsupportedHandler(w, r) handlers.UnsupportedHandler(w, r)
@ -95,8 +96,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime) runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct { query := struct {
All bool `schema:"all"` All bool `schema:"all"`
Filters []string `schema:"filters"` Filters map[string][]string `schema:"filters"`
}{ }{
// override any golang type defaults // override any golang type defaults
} }
@ -106,7 +107,14 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return return
} }
cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, query.Filters)
var libpodFilters = []string{}
if _, found := r.URL.Query()["filters"]; found {
for k, v := range query.Filters {
libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
}
}
cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters)
if err != nil { if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return return

View File

@ -108,7 +108,7 @@ func Pods(w http.ResponseWriter, r *http.Request) {
) )
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct { query := struct {
filters []string `schema:"filters"` Filters map[string][]string `schema:"filters"`
}{ }{
// override any golang type defaults // override any golang type defaults
} }
@ -117,10 +117,12 @@ func Pods(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return return
} }
if len(query.filters) > 0 {
if _, found := r.URL.Query()["filters"]; found {
utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented) utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented)
return return
} }
pods, err := runtime.GetAllPods() pods, err := runtime.GetAllPods()
if err != nil { if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
@ -164,7 +166,7 @@ func PodStop(w http.ResponseWriter, r *http.Request) {
decoder = r.Context().Value("decoder").(*schema.Decoder) decoder = r.Context().Value("decoder").(*schema.Decoder)
) )
query := struct { query := struct {
timeout int `schema:"t"` Timeout int `schema:"t"`
}{ }{
// override any golang type defaults // override any golang type defaults
} }
@ -207,8 +209,8 @@ func PodStop(w http.ResponseWriter, r *http.Request) {
return return
} }
if query.timeout > 0 { if query.Timeout > 0 {
_, stopError = pod.StopWithTimeout(r.Context(), false, query.timeout) _, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout)
} else { } else {
_, stopError = pod.Stop(r.Context(), false) _, stopError = pod.Stop(r.Context(), false)
} }

View File

@ -15,17 +15,18 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) {
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime) runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct { query := struct {
//all bool # all is currently unused // all bool # all is currently unused
filters []string Filters map[string][]string `schema:"filters"`
//digests bool # digests is currently unused // digests bool # digests is currently unused
}{ }{
// This is where you can override the golang default value for one of fields // This is where you can override the golang default value for one of fields
} }
if err := decoder.Decode(&query, r.URL.Query()); err != nil { if err := decoder.Decode(&query, r.URL.Query()); err != nil {
return nil, err return nil, err
} }
filters := query.filters
if len(filters) < 1 { var filters = []string{}
if _, found := r.URL.Query()["filters"]; found {
filters = append(filters, fmt.Sprintf("reference=%s", "")) filters = append(filters, fmt.Sprintf("reference=%s", ""))
} }
return runtime.ImageRuntime().GetImagesWithFilters(filters) return runtime.ImageRuntime().GetImagesWithFilters(filters)

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/coreos/go-systemd/activation" "github.com/coreos/go-systemd/activation"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/schema" "github.com/gorilla/schema"
@ -71,7 +72,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
ReadTimeout: 20 * time.Second, ReadTimeout: 20 * time.Second,
WriteTimeout: 2 * time.Minute, WriteTimeout: 2 * time.Minute,
}, },
Decoder: schema.NewDecoder(), Decoder: handlers.NewAPIDecoder(),
Context: nil, Context: nil,
Runtime: runtime, Runtime: runtime,
Listener: *listener, Listener: *listener,
@ -85,6 +86,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
}) })
ctx, cancelFn := context.WithCancel(context.Background()) ctx, cancelFn := context.WithCancel(context.Background())
server.CancelFunc = cancelFn
// TODO: Use ConnContext when ported to go 1.13 // TODO: Use ConnContext when ported to go 1.13
ctx = context.WithValue(ctx, "decoder", server.Decoder) ctx = context.WithValue(ctx, "decoder", server.Decoder)
@ -92,9 +94,6 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
ctx = context.WithValue(ctx, "shutdownFunc", server.Shutdown) ctx = context.WithValue(ctx, "shutdownFunc", server.Shutdown)
server.Context = ctx server.Context = ctx
server.CancelFunc = cancelFn
server.Decoder.IgnoreUnknownKeys(true)
router.NotFoundHandler = http.HandlerFunc( router.NotFoundHandler = http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
// We can track user errors... // We can track user errors...