Add query parameter converters for complex types

* Add converter for URL query parameters of type map[string][]string
* Add converter for URL query parameters of type time.Time
* Added function to allocate and configure schema.Decoder for API use
* Updated API handlers to leverage new converters, and correct handler
  code for filter type

An encoding example for a client using filters:

  v := map[string][]string{
      "dangling": {"true"},
  }
  payload, err := jsoniter.MarshalToString(v)
  if err != nil {
    panic(err)
  }
  payload = "?filters=" + url.QueryEscape(payload)

Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
Jhon Honce
2020-01-23 16:13:47 -07:00
parent 8beeb067aa
commit 9634e7eef7
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
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/pkg/errors"
@ -11,30 +12,24 @@ import (
func GetEvents(w http.ResponseWriter, r *http.Request) {
query := struct {
Since string `json:"since"`
Until string `json:"until"`
Filters string `json:"filters"`
Since time.Time `schema:"since"`
Until time.Time `schema:"until"`
Filters map[string][]string `schema:"filters"`
}{}
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()))
}
var filters = map[string][]string{}
if found := hasVar(r, "filters"); found {
if err := json.Unmarshal([]byte(query.Filters), &filters); err != nil {
utils.BadRequest(w, "filters", query.Filters, err)
return
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]))
}
}
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)
if err != nil {
utils.BadRequest(w, "filters", query.Filters, err)
utils.BadRequest(w, "filters", strings.Join(r.URL.Query()["filters"], ", "), err)
return
}

View File

@ -62,14 +62,14 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
// 200 no error
// 500 internal
var (
dangling bool = true
dangling = true
err error
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
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
}
@ -79,26 +79,25 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
return
}
// FIXME This is likely wrong due to it not being a map[string][]string
// until ts is not supported on podman prune
if len(query.filters["until"]) > 0 {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "until is not supported yet"))
if v, found := query.Filters["until"]; found {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "until=%s is not supported yet", v))
return
}
// 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"))
return
}
if len(query.filters["dangling"]) > 0 {
dangling, err = strconv.ParseBool(query.filters["dangling"])
if v, found := query.Filters["dangling"]; found {
dangling, err = strconv.ParseBool(v[0])
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter"))
return
}
}
idr := []types.ImageDeleteResponseItem{}
//
// 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]
}
func hasVar(r *http.Request, k string) bool {
_, found := mux.Vars(r)[k]
return found
}
// func hasVar(r *http.Request, k string) bool {
// _, found := mux.Vars(r)[k]
// return found
// }
func getName(r *http.Request) string {
return getVar(r, "name")
}

View File

@ -1,6 +1,7 @@
package libpod
import (
"fmt"
"io/ioutil"
"net/http"
"os"
@ -42,12 +43,12 @@ func ImageExists(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
//name := mux.Vars(r)["name"]
//_, layerInfoMap, _, err := s.Runtime.Tree(name)
//if err != nil {
// name := mux.Vars(r)["name"]
// _, layerInfoMap, _, err := s.Runtime.Tree(name)
// if err != nil {
// Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name))
// return
//}
// }
// 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.
handlers.UnsupportedHandler(w, r)
@ -95,8 +96,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
All bool `schema:"all"`
Filters []string `schema:"filters"`
All bool `schema:"all"`
Filters map[string][]string `schema:"filters"`
}{
// 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()))
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 {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return

View File

@ -108,7 +108,7 @@ func Pods(w http.ResponseWriter, r *http.Request) {
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
filters []string `schema:"filters"`
Filters map[string][]string `schema:"filters"`
}{
// 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()))
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)
return
}
pods, err := runtime.GetAllPods()
if err != nil {
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)
)
query := struct {
timeout int `schema:"t"`
Timeout int `schema:"t"`
}{
// override any golang type defaults
}
@ -207,8 +209,8 @@ func PodStop(w http.ResponseWriter, r *http.Request) {
return
}
if query.timeout > 0 {
_, stopError = pod.StopWithTimeout(r.Context(), false, query.timeout)
if query.Timeout > 0 {
_, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout)
} else {
_, 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)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
//all bool # all is currently unused
filters []string
//digests bool # digests is currently unused
// all bool # all is currently unused
Filters map[string][]string `schema:"filters"`
// digests bool # digests is currently unused
}{
// This is where you can override the golang default value for one of fields
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
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", ""))
}
return runtime.ImageRuntime().GetImagesWithFilters(filters)

View File

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