202 lines
6.7 KiB
Go
202 lines
6.7 KiB
Go
package volume // import "github.com/docker/docker/api/server/router/volume"
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/docker/docker/api/server/httputils"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/versions"
|
|
"github.com/docker/docker/api/types/volume"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/volume/service/opts"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
// clusterVolumesVersion defines the API version that swarm cluster volume
|
|
// functionality was introduced. avoids the use of magic numbers.
|
|
clusterVolumesVersion = "1.42"
|
|
)
|
|
|
|
func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
filters, err := filters.FromJSON(r.Form.Get("filters"))
|
|
if err != nil {
|
|
return errors.Wrap(err, "error reading volume filters")
|
|
}
|
|
volumes, warnings, err := v.backend.List(ctx, filters)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
version := httputils.VersionFromContext(ctx)
|
|
if versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
|
|
clusterVolumes, swarmErr := v.cluster.GetVolumes(volume.ListOptions{Filters: filters})
|
|
if swarmErr != nil {
|
|
// if there is a swarm error, we may not want to error out right
|
|
// away. the local list probably worked. instead, let's do what we
|
|
// do if there's a bad driver while trying to list: add the error
|
|
// to the warnings. don't do this if swarm is not initialized.
|
|
warnings = append(warnings, swarmErr.Error())
|
|
}
|
|
// add the cluster volumes to the return
|
|
volumes = append(volumes, clusterVolumes...)
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, &volume.ListResponse{Volumes: volumes, Warnings: warnings})
|
|
}
|
|
|
|
func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
version := httputils.VersionFromContext(ctx)
|
|
|
|
// re: volume name duplication
|
|
//
|
|
// we prefer to get volumes locally before attempting to get them from the
|
|
// cluster. Local volumes can only be looked up by name, but cluster
|
|
// volumes can also be looked up by ID.
|
|
vol, err := v.backend.Get(ctx, vars["name"], opts.WithGetResolveStatus)
|
|
|
|
// if the volume is not found in the regular volume backend, and the client
|
|
// is using an API version greater than 1.42 (when cluster volumes were
|
|
// introduced), then check if Swarm has the volume.
|
|
if errdefs.IsNotFound(err) && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
|
|
swarmVol, err := v.cluster.GetVolume(vars["name"])
|
|
// if swarm returns an error and that error indicates that swarm is not
|
|
// initialized, return original NotFound error. Otherwise, we'd return
|
|
// a weird swarm unavailable error on non-swarm engines.
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vol = &swarmVol
|
|
} else if err != nil {
|
|
// otherwise, if this isn't NotFound, or this isn't a high enough version,
|
|
// just return the error by itself.
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, vol)
|
|
}
|
|
|
|
func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
var req volume.CreateOptions
|
|
if err := httputils.ReadJSON(r, &req); err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
vol *volume.Volume
|
|
err error
|
|
version = httputils.VersionFromContext(ctx)
|
|
)
|
|
|
|
// if the ClusterVolumeSpec is filled in, then this is a cluster volume
|
|
// and is created through the swarm cluster volume backend.
|
|
//
|
|
// re: volume name duplication
|
|
//
|
|
// As it happens, there is no good way to prevent duplication of a volume
|
|
// name between local and cluster volumes. This is because Swarm volumes
|
|
// can be created from any manager node, bypassing most of the protections
|
|
// we could put into the engine side.
|
|
//
|
|
// Instead, we will allow creating a volume with a duplicate name, which
|
|
// should not break anything.
|
|
if req.ClusterVolumeSpec != nil && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) {
|
|
logrus.Debug("using cluster volume")
|
|
vol, err = v.cluster.CreateVolume(req)
|
|
} else {
|
|
logrus.Debug("using regular volume")
|
|
vol, err = v.backend.Create(ctx, req.Name, req.Driver, opts.WithCreateOptions(req.DriverOpts), opts.WithCreateLabels(req.Labels))
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return httputils.WriteJSON(w, http.StatusCreated, vol)
|
|
}
|
|
|
|
func (v *volumeRouter) putVolumesUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if !v.cluster.IsManager() {
|
|
return errdefs.Unavailable(errors.New("volume update only valid for cluster volumes, but swarm is unavailable"))
|
|
}
|
|
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
rawVersion := r.URL.Query().Get("version")
|
|
version, err := strconv.ParseUint(rawVersion, 10, 64)
|
|
if err != nil {
|
|
err = fmt.Errorf("invalid swarm object version '%s': %v", rawVersion, err)
|
|
return errdefs.InvalidParameter(err)
|
|
}
|
|
|
|
var req volume.UpdateOptions
|
|
if err := httputils.ReadJSON(r, &req); err != nil {
|
|
return err
|
|
}
|
|
|
|
return v.cluster.UpdateVolume(vars["name"], version, req)
|
|
}
|
|
|
|
func (v *volumeRouter) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
force := httputils.BoolValue(r, "force")
|
|
|
|
version := httputils.VersionFromContext(ctx)
|
|
|
|
err := v.backend.Remove(ctx, vars["name"], opts.WithPurgeOnError(force))
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
|
|
err := v.cluster.RemoveVolume(vars["name"], force)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return nil
|
|
}
|
|
|
|
func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// API version 1.42 changes behavior where prune should only prune anonymous volumes.
|
|
// To keep older API behavior working, we need to add this filter option to consider all (local) volumes for pruning, not just anonymous ones.
|
|
if versions.LessThan(httputils.VersionFromContext(ctx), "1.42") {
|
|
pruneFilters.Add("all", "true")
|
|
}
|
|
|
|
pruneReport, err := v.backend.Prune(ctx, pruneFilters)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return httputils.WriteJSON(w, http.StatusOK, pruneReport)
|
|
}
|