github.com/moby/docker@v26.1.3+incompatible/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/containerd/log" 10 "github.com/docker/docker/api/server/httputils" 11 "github.com/docker/docker/api/types/filters" 12 "github.com/docker/docker/api/types/versions" 13 "github.com/docker/docker/api/types/volume" 14 "github.com/docker/docker/errdefs" 15 "github.com/docker/docker/volume/service/opts" 16 "github.com/pkg/errors" 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 log.G(ctx).Debug("using cluster volume") 120 vol, err = v.cluster.CreateVolume(req) 121 } else { 122 log.G(ctx).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 // First we try deleting local volume. The volume may not be found as a 163 // local volume, but could be a cluster volume, so we ignore "not found" 164 // errors at this stage. Note that no "not found" error is produced if 165 // "force" is enabled. 166 err := v.backend.Remove(ctx, vars["name"], opts.WithPurgeOnError(force)) 167 if err != nil && !errdefs.IsNotFound(err) { 168 return err 169 } 170 171 // If no volume was found, the volume may be a cluster volume. If force 172 // is enabled, the volume backend won't return an error for non-existing 173 // volumes, so we don't know if removal succeeded (or not volume existed). 174 // In that case we always try to delete cluster volumes as well. 175 if errdefs.IsNotFound(err) || force { 176 version := httputils.VersionFromContext(ctx) 177 if versions.GreaterThanOrEqualTo(version, clusterVolumesVersion) && v.cluster.IsManager() { 178 err = v.cluster.RemoveVolume(vars["name"], force) 179 } 180 } 181 182 if err != nil { 183 return err 184 } 185 w.WriteHeader(http.StatusNoContent) 186 return nil 187 } 188 189 func (v *volumeRouter) postVolumesPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 190 if err := httputils.ParseForm(r); err != nil { 191 return err 192 } 193 194 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 195 if err != nil { 196 return err 197 } 198 199 // API version 1.42 changes behavior where prune should only prune anonymous volumes. 200 // To keep older API behavior working, we need to add this filter option to consider all (local) volumes for pruning, not just anonymous ones. 201 if versions.LessThan(httputils.VersionFromContext(ctx), "1.42") { 202 pruneFilters.Add("all", "true") 203 } 204 205 pruneReport, err := v.backend.Prune(ctx, pruneFilters) 206 if err != nil { 207 return err 208 } 209 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 210 }