diff options
author | maybe-sybr <58414429+maybe-sybr@users.noreply.github.com> | 2020-06-24 11:16:59 +1000 |
---|---|---|
committer | maybe-sybr <58414429+maybe-sybr@users.noreply.github.com> | 2020-07-02 16:33:19 +1000 |
commit | cb61a2d858d7874c66bafcf6d17c17bb9d7849e8 (patch) | |
tree | 7ddb797d86b6e231333c559103291abb3743c6d6 /pkg/api/handlers | |
parent | e84695213e35c22ba085e3831cbd025cd55a4c84 (diff) | |
download | podman-cb61a2d858d7874c66bafcf6d17c17bb9d7849e8.tar.gz podman-cb61a2d858d7874c66bafcf6d17c17bb9d7849e8.tar.bz2 podman-cb61a2d858d7874c66bafcf6d17c17bb9d7849e8.zip |
APIv2: Add docker compatible volume endpoints
This change implements docker compatibile endpoint for interacting with
volumes. The code is mostly lifted from the `libpod` API handlers but
decodes and constructs data using types defined in the docker API
package.
Some notable support caveats with the current implementation:
* we don't return the nullable `Status` or `UsageData` keys when
returning volume information for inspect and create endpoints
* we don't support filters when pruning
* we return a fixed `0` for the `SpaceReclaimed` key when pruning
since we have no insight into how much space was freed from runtime
Signed-off-by: Matt Brindley <58414429+maybe-sybr@users.noreply.github.com>
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r-- | pkg/api/handlers/compat/volumes.go | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go new file mode 100644 index 000000000..3f4e6eac1 --- /dev/null +++ b/pkg/api/handlers/compat/volumes.go @@ -0,0 +1,238 @@ +package compat + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/filters" + "github.com/containers/libpod/pkg/domain/infra/abi/parse" + docker_api_types "github.com/docker/docker/api/types" + docker_api_types_volume "github.com/docker/docker/api/types/volume" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func ListVolumes(w http.ResponseWriter, r *http.Request) { + var ( + decoder = r.Context().Value("decoder").(*schema.Decoder) + runtime = r.Context().Value("runtime").(*libpod.Runtime) + ) + query := struct { + Filters map[string][]string `schema:"filters"` + }{ + // override any golang type defaults + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + // Reject any libpod specific filters since `GenerateVolumeFilters()` will + // happily parse them for us. + for filter := range query.Filters { + if filter == "opts" { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Errorf("unsupported libpod filters passed to docker endpoint")) + return + } + } + volumeFilters, err := filters.GenerateVolumeFilters(query.Filters) + if err != nil { + utils.InternalServerError(w, err) + return + } + + vols, err := runtime.Volumes(volumeFilters...) + if err != nil { + utils.InternalServerError(w, err) + return + } + volumeConfigs := make([]*docker_api_types.Volume, 0, len(vols)) + for _, v := range vols { + config := docker_api_types.Volume{ + Name: v.Name(), + Driver: v.Driver(), + Mountpoint: v.MountPoint(), + CreatedAt: v.CreatedTime().Format(time.RFC3339), + Labels: v.Labels(), + Scope: v.Scope(), + Options: v.Options(), + } + volumeConfigs = append(volumeConfigs, &config) + } + response := docker_api_types_volume.VolumeListOKBody{ + Volumes: volumeConfigs, + Warnings: []string{}, + } + utils.WriteResponse(w, http.StatusOK, response) +} + +func CreateVolume(w http.ResponseWriter, r *http.Request) { + var ( + volumeOptions []libpod.VolumeCreateOption + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + /* No query string data*/ + query := struct{}{} + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + // decode params from body + input := docker_api_types_volume.VolumeCreateBody{} + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + + if len(input.Name) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeName(input.Name)) + } + if len(input.Driver) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(input.Driver)) + } + if len(input.Labels) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Labels)) + } + if len(input.DriverOpts) > 0 { + parsedOptions, err := parse.VolumeOptions(input.DriverOpts) + if err != nil { + utils.InternalServerError(w, err) + return + } + volumeOptions = append(volumeOptions, parsedOptions...) + } + vol, err := runtime.NewVolume(r.Context(), volumeOptions...) + if err != nil { + utils.InternalServerError(w, err) + return + } + config, err := vol.Config() + if err != nil { + utils.InternalServerError(w, err) + return + } + volResponse := docker_api_types.Volume{ + Name: config.Name, + Driver: config.Driver, + Mountpoint: config.MountPoint, + CreatedAt: config.CreatedTime.Format(time.RFC3339), + Labels: config.Labels, + Options: config.Options, + Scope: "local", + // ^^ We don't have volume scoping so we'll just claim it's "local" + // like we do in the `libpod.Volume.Scope()` method + // + // TODO: We don't include the volume `Status` or `UsageData`, but both + // are nullable in the Docker engine API spec so that's fine for now + } + utils.WriteResponse(w, http.StatusCreated, volResponse) +} + +func InspectVolume(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + ) + name := utils.GetName(r) + vol, err := runtime.GetVolume(name) + if err != nil { + utils.VolumeNotFound(w, name, err) + return + } + volResponse := docker_api_types.Volume{ + Name: vol.Name(), + Driver: vol.Driver(), + Mountpoint: vol.MountPoint(), + CreatedAt: vol.CreatedTime().Format(time.RFC3339), + Labels: vol.Labels(), + Options: vol.Options(), + Scope: vol.Scope(), + // TODO: As above, we don't return `Status` or `UsageData` yet + } + utils.WriteResponse(w, http.StatusOK, volResponse) +} + +func RemoveVolume(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + Force bool `schema:"force"` + }{ + // override any golang type defaults + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + name := utils.GetName(r) + vol, err := runtime.LookupVolume(name) + if err != nil { + utils.VolumeNotFound(w, name, err) + return + } + if err := runtime.RemoveVolume(r.Context(), vol, query.Force); err != nil { + if errors.Cause(err) == define.ErrVolumeBeingUsed { + utils.Error(w, "volumes being used", http.StatusConflict, err) + return + } + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} + +func PruneVolumes(w http.ResponseWriter, r *http.Request) { + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + // For some reason the prune filters are query parameters even though this + // is a POST endpoint + query := struct { + Filters map[string][]string `schema:"filters"` + }{ + // override any golang type defaults + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + // TODO: We have no ability to pass pruning filters to `PruneVolumes()` so + // we'll explicitly reject the request if we see any + if len(query.Filters) > 0 { + utils.InternalServerError(w, errors.New("filters for pruning volumes is not implemented")) + return + } + + pruned, err := runtime.PruneVolumes(r.Context()) + if err != nil { + utils.InternalServerError(w, err) + return + } + prunedIds := make([]string, 0, len(pruned)) + for k := range pruned { + // XXX: This drops any pruning per-volume error messages on the floor + prunedIds = append(prunedIds, k) + } + pruneResponse := docker_api_types.VolumesPruneReport{ + VolumesDeleted: prunedIds, + // TODO: We don't have any insight into how much space was reclaimed + // from `PruneVolumes()` but it's not nullable + SpaceReclaimed: 0, + } + + utils.WriteResponse(w, http.StatusOK, pruneResponse) +} |