github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/api/server/router/volume/volume_routes.go (about)

     1  package volume // import "github.com/docker/docker/api/server/router/volume"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  
     9  	"github.com/docker/docker/api/server/httputils"
    10  	"github.com/docker/docker/api/types/filters"
    11  	"github.com/docker/docker/api/types/versions"
    12  	"github.com/docker/docker/api/types/volume"
    13  	"github.com/docker/docker/errdefs"
    14  	"github.com/docker/docker/volume/service/opts"
    15  	"github.com/pkg/errors"
    16  	"github.com/sirupsen/logrus"
    17  )
    18  
    19  const (
    20  	// clusterVolumesVersion defines the API version that swarm cluster volume
    21  	// functionality was introduced. avoids the use of magic numbers.
    22  	clusterVolumesVersion = "1.42"
    23  )
    24  
    25  func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    26  	if err := httputils.ParseForm(r); err != nil {
    27  		return err
    28  	}
    29  
    30  	filters, err := filters.FromJSON(r.Form.Get("filters"))
    31  	if err != nil {
    32  		return errors.Wrap(err, "error reading volume filters")
    33  	}
    34  	volumes, warnings, err := v.backend.List(ctx, filters)
    35  	if err != nil {
    36  		return err
    37  	}
    38  
    39  	version := httputils.VersionFromContext(ctx)
    40  	if versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
    41  		clusterVolumes, swarmErr := v.cluster.GetVolumes(volume.ListOptions{Filters: filters})
    42  		if swarmErr != nil {
    43  			// if there is a swarm error, we may not want to error out right
    44  			// away. the local list probably worked. instead, let's do what we
    45  			// do if there's a bad driver while trying to list: add the error
    46  			// to the warnings. don't do this if swarm is not initialized.
    47  			warnings = append(warnings, swarmErr.Error())
    48  		}
    49  		// add the cluster volumes to the return
    50  		volumes = append(volumes, clusterVolumes...)
    51  	}
    52  
    53  	return httputils.WriteJSON(w, http.StatusOK, &volume.ListResponse{Volumes: volumes, Warnings: warnings})
    54  }
    55  
    56  func (v *volumeRouter) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    57  	if err := httputils.ParseForm(r); err != nil {
    58  		return err
    59  	}
    60  	version := httputils.VersionFromContext(ctx)
    61  
    62  	// re: volume name duplication
    63  	//
    64  	// we prefer to get volumes locally before attempting to get them from the
    65  	// cluster. Local volumes can only be looked up by name, but cluster
    66  	// volumes can also be looked up by ID.
    67  	vol, err := v.backend.Get(ctx, vars["name"], opts.WithGetResolveStatus)
    68  
    69  	// if the volume is not found in the regular volume backend, and the client
    70  	// is using an API version greater than 1.42 (when cluster volumes were
    71  	// introduced), then check if Swarm has the volume.
    72  	if errdefs.IsNotFound(err) && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
    73  		swarmVol, err := v.cluster.GetVolume(vars["name"])
    74  		// if swarm returns an error and that error indicates that swarm is not
    75  		// initialized, return original NotFound error. Otherwise, we'd return
    76  		// a weird swarm unavailable error on non-swarm engines.
    77  		if err != nil {
    78  			return err
    79  		}
    80  		vol = &swarmVol
    81  	} else if err != nil {
    82  		// otherwise, if this isn't NotFound, or this isn't a high enough version,
    83  		// just return the error by itself.
    84  		return err
    85  	}
    86  
    87  	return httputils.WriteJSON(w, http.StatusOK, vol)
    88  }
    89  
    90  func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    91  	if err := httputils.ParseForm(r); err != nil {
    92  		return err
    93  	}
    94  
    95  	var req volume.CreateOptions
    96  	if err := httputils.ReadJSON(r, &req); err != nil {
    97  		return err
    98  	}
    99  
   100  	var (
   101  		vol     *volume.Volume
   102  		err     error
   103  		version = httputils.VersionFromContext(ctx)
   104  	)
   105  
   106  	// if the ClusterVolumeSpec is filled in, then this is a cluster volume
   107  	// and is created through the swarm cluster volume backend.
   108  	//
   109  	// re: volume name duplication
   110  	//
   111  	// As it happens, there is no good way to prevent duplication of a volume
   112  	// name between local and cluster volumes. This is because Swarm volumes
   113  	// can be created from any manager node, bypassing most of the protections
   114  	// we could put into the engine side.
   115  	//
   116  	// Instead, we will allow creating a volume with a duplicate name, which
   117  	// should not break anything.
   118  	if req.ClusterVolumeSpec != nil && versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) {
   119  		logrus.Debug("using cluster volume")
   120  		vol, err = v.cluster.CreateVolume(req)
   121  	} else {
   122  		logrus.Debug("using regular volume")
   123  		vol, err = v.backend.Create(ctx, req.Name, req.Driver, opts.WithCreateOptions(req.DriverOpts), opts.WithCreateLabels(req.Labels))
   124  	}
   125  
   126  	if err != nil {
   127  		return err
   128  	}
   129  	return httputils.WriteJSON(w, http.StatusCreated, vol)
   130  }
   131  
   132  func (v *volumeRouter) putVolumesUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   133  	if !v.cluster.IsManager() {
   134  		return errdefs.Unavailable(errors.New("volume update only valid for cluster volumes, but swarm is unavailable"))
   135  	}
   136  
   137  	if err := httputils.ParseForm(r); err != nil {
   138  		return err
   139  	}
   140  
   141  	rawVersion := r.URL.Query().Get("version")
   142  	version, err := strconv.ParseUint(rawVersion, 10, 64)
   143  	if err != nil {
   144  		err = fmt.Errorf("invalid swarm object version '%s': %v", rawVersion, err)
   145  		return errdefs.InvalidParameter(err)
   146  	}
   147  
   148  	var req volume.UpdateOptions
   149  	if err := httputils.ReadJSON(r, &req); err != nil {
   150  		return err
   151  	}
   152  
   153  	return v.cluster.UpdateVolume(vars["name"], version, req)
   154  }
   155  
   156  func (v *volumeRouter) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   157  	if err := httputils.ParseForm(r); err != nil {
   158  		return err
   159  	}
   160  	force := httputils.BoolValue(r, "force")
   161  
   162  	version := httputils.VersionFromContext(ctx)
   163  
   164  	err := v.backend.Remove(ctx, vars["name"], opts.WithPurgeOnError(force))
   165  	// when a removal is forced, if the volume does not exist, no error will be
   166  	// returned. this means that to ensure forcing works on swarm volumes as
   167  	// well, we should always also force remove against the cluster.
   168  	if err != nil || force {
   169  		if versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() {
   170  			if errdefs.IsNotFound(err) || force {
   171  				err := v.cluster.RemoveVolume(vars["name"], force)
   172  				if err != nil {
   173  					return err
   174  				}
   175  			}
   176  		} else {
   177  			return err
   178  		}
   179  	}
   180  
   181  	w.WriteHeader(http.StatusNoContent)
   182  	return nil
   183  }
   184  
   185  func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   186  	if err := httputils.ParseForm(r); err != nil {
   187  		return err
   188  	}
   189  
   190  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	// API version 1.42 changes behavior where prune should only prune anonymous volumes.
   196  	// To keep older API behavior working, we need to add this filter option to consider all (local) volumes for pruning, not just anonymous ones.
   197  	if versions.LessThan(httputils.VersionFromContext(ctx), "1.42") {
   198  		pruneFilters.Add("all", "true")
   199  	}
   200  
   201  	pruneReport, err := v.backend.Prune(ctx, pruneFilters)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   206  }