github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/compat/volumes.go (about) 1 package compat 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "net/http" 7 "net/url" 8 "time" 9 10 "github.com/hanks177/podman/v4/libpod" 11 "github.com/hanks177/podman/v4/libpod/define" 12 "github.com/hanks177/podman/v4/pkg/api/handlers" 13 "github.com/hanks177/podman/v4/pkg/api/handlers/utils" 14 api "github.com/hanks177/podman/v4/pkg/api/types" 15 "github.com/hanks177/podman/v4/pkg/domain/filters" 16 "github.com/hanks177/podman/v4/pkg/domain/infra/abi/parse" 17 "github.com/hanks177/podman/v4/pkg/util" 18 docker_api_types "github.com/docker/docker/api/types" 19 docker_api_types_volume "github.com/docker/docker/api/types/volume" 20 "github.com/gorilla/schema" 21 "github.com/pkg/errors" 22 ) 23 24 func ListVolumes(w http.ResponseWriter, r *http.Request) { 25 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 26 27 filtersMap, err := util.PrepareFilters(r) 28 if err != nil { 29 utils.Error(w, http.StatusInternalServerError, 30 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 31 return 32 } 33 34 // Reject any libpod specific filters since `GenerateVolumeFilters()` will 35 // happily parse them for us. 36 for filter := range *filtersMap { 37 if filter == "opts" { 38 utils.Error(w, http.StatusInternalServerError, 39 errors.Errorf("unsupported libpod filters passed to docker endpoint")) 40 return 41 } 42 } 43 volumeFilters, err := filters.GenerateVolumeFilters(*filtersMap) 44 if err != nil { 45 utils.InternalServerError(w, err) 46 return 47 } 48 49 vols, err := runtime.Volumes(volumeFilters...) 50 if err != nil { 51 utils.InternalServerError(w, err) 52 return 53 } 54 volumeConfigs := make([]*docker_api_types.Volume, 0, len(vols)) 55 for _, v := range vols { 56 mp, err := v.MountPoint() 57 if err != nil { 58 utils.InternalServerError(w, err) 59 return 60 } 61 config := docker_api_types.Volume{ 62 Name: v.Name(), 63 Driver: v.Driver(), 64 Mountpoint: mp, 65 CreatedAt: v.CreatedTime().Format(time.RFC3339), 66 Labels: v.Labels(), 67 Scope: v.Scope(), 68 Options: v.Options(), 69 } 70 volumeConfigs = append(volumeConfigs, &config) 71 } 72 response := docker_api_types_volume.VolumeListOKBody{ 73 Volumes: volumeConfigs, 74 Warnings: []string{}, 75 } 76 utils.WriteResponse(w, http.StatusOK, response) 77 } 78 79 func CreateVolume(w http.ResponseWriter, r *http.Request) { 80 var ( 81 volumeOptions []libpod.VolumeCreateOption 82 runtime = r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 83 decoder = r.Context().Value(api.DecoderKey).(*schema.Decoder) 84 ) 85 /* No query string data*/ 86 query := struct{}{} 87 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 88 utils.Error(w, http.StatusInternalServerError, 89 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 90 return 91 } 92 // decode params from body 93 input := docker_api_types_volume.VolumeCreateBody{} 94 if err := json.NewDecoder(r.Body).Decode(&input); err != nil { 95 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 96 return 97 } 98 99 var ( 100 existingVolume *libpod.Volume 101 err error 102 ) 103 if len(input.Name) != 0 { 104 // See if the volume exists already 105 existingVolume, err = runtime.GetVolume(input.Name) 106 if err != nil && errors.Cause(err) != define.ErrNoSuchVolume { 107 utils.InternalServerError(w, err) 108 return 109 } 110 } 111 112 // if using the compat layer and the volume already exists, we 113 // must return a 201 with the same information as create 114 if existingVolume != nil && !utils.IsLibpodRequest(r) { 115 mp, err := existingVolume.MountPoint() 116 if err != nil { 117 utils.InternalServerError(w, err) 118 return 119 } 120 response := docker_api_types.Volume{ 121 CreatedAt: existingVolume.CreatedTime().Format(time.RFC3339), 122 Driver: existingVolume.Driver(), 123 Labels: existingVolume.Labels(), 124 Mountpoint: mp, 125 Name: existingVolume.Name(), 126 Options: existingVolume.Options(), 127 Scope: existingVolume.Scope(), 128 } 129 utils.WriteResponse(w, http.StatusCreated, response) 130 return 131 } 132 133 if len(input.Name) > 0 { 134 volumeOptions = append(volumeOptions, libpod.WithVolumeName(input.Name)) 135 } 136 if len(input.Driver) > 0 { 137 volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(input.Driver)) 138 } 139 if len(input.Labels) > 0 { 140 volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Labels)) 141 } 142 if len(input.DriverOpts) > 0 { 143 parsedOptions, err := parse.VolumeOptions(input.DriverOpts) 144 if err != nil { 145 utils.InternalServerError(w, err) 146 return 147 } 148 volumeOptions = append(volumeOptions, parsedOptions...) 149 } 150 vol, err := runtime.NewVolume(r.Context(), volumeOptions...) 151 if err != nil { 152 utils.InternalServerError(w, err) 153 return 154 } 155 config, err := vol.Config() 156 if err != nil { 157 utils.InternalServerError(w, err) 158 return 159 } 160 mp, err := vol.MountPoint() 161 if err != nil { 162 utils.InternalServerError(w, err) 163 return 164 } 165 volResponse := docker_api_types.Volume{ 166 Name: config.Name, 167 Driver: config.Driver, 168 Mountpoint: mp, 169 CreatedAt: config.CreatedTime.Format(time.RFC3339), 170 Labels: config.Labels, 171 Options: config.Options, 172 Scope: "local", 173 // ^^ We don't have volume scoping so we'll just claim it's "local" 174 // like we do in the `libpod.Volume.Scope()` method 175 // 176 // TODO: We don't include the volume `Status` or `UsageData`, but both 177 // are nullable in the Docker engine API spec so that's fine for now 178 } 179 utils.WriteResponse(w, http.StatusCreated, volResponse) 180 } 181 182 func InspectVolume(w http.ResponseWriter, r *http.Request) { 183 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 184 name := utils.GetName(r) 185 vol, err := runtime.GetVolume(name) 186 if err != nil { 187 utils.VolumeNotFound(w, name, err) 188 return 189 } 190 mp, err := vol.MountPoint() 191 if err != nil { 192 utils.InternalServerError(w, err) 193 return 194 } 195 volResponse := docker_api_types.Volume{ 196 Name: vol.Name(), 197 Driver: vol.Driver(), 198 Mountpoint: mp, 199 CreatedAt: vol.CreatedTime().Format(time.RFC3339), 200 Labels: vol.Labels(), 201 Options: vol.Options(), 202 Scope: vol.Scope(), 203 // TODO: As above, we don't return `Status` or `UsageData` yet 204 } 205 utils.WriteResponse(w, http.StatusOK, volResponse) 206 } 207 208 func RemoveVolume(w http.ResponseWriter, r *http.Request) { 209 var ( 210 runtime = r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 211 decoder = r.Context().Value(api.DecoderKey).(*schema.Decoder) 212 ) 213 query := struct { 214 Force bool `schema:"force"` 215 Timeout *uint `schema:"timeout"` 216 }{ 217 // override any golang type defaults 218 } 219 220 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 221 utils.Error(w, http.StatusInternalServerError, 222 errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 223 return 224 } 225 226 /* The implications for `force` differ between Docker and us, so we can't 227 * simply pass the `force` parameter to `runtime.RemoveVolume()`. 228 * Specifically, Docker's behavior seems to be that `force` means "do not 229 * error on missing volume"; ours means "remove any not-running containers 230 * using the volume at the same time". 231 * 232 * With this in mind, we only consider the `force` query parameter when we 233 * hunt for specified volume by name, using it to selectively return a 204 234 * or blow up depending on `force` being truthy or falsey/unset 235 * respectively. 236 */ 237 name := utils.GetName(r) 238 vol, err := runtime.LookupVolume(name) 239 if err == nil { 240 // As above, we do not pass `force` from the query parameters here 241 if err := runtime.RemoveVolume(r.Context(), vol, false, query.Timeout); err != nil { 242 if errors.Cause(err) == define.ErrVolumeBeingUsed { 243 utils.Error(w, http.StatusConflict, err) 244 } else { 245 utils.InternalServerError(w, err) 246 } 247 } else { 248 // Success 249 utils.WriteResponse(w, http.StatusNoContent, nil) 250 } 251 } else { 252 if !query.Force { 253 utils.VolumeNotFound(w, name, err) 254 } else { 255 // Volume does not exist and `force` is truthy - this emulates what 256 // Docker would do when told to `force` removal of a nonexistent 257 // volume 258 utils.WriteResponse(w, http.StatusNoContent, nil) 259 } 260 } 261 } 262 263 func PruneVolumes(w http.ResponseWriter, r *http.Request) { 264 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 265 filterMap, err := util.PrepareFilters(r) 266 if err != nil { 267 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 268 return 269 } 270 271 f := (url.Values)(*filterMap) 272 filterFuncs, err := filters.GeneratePruneVolumeFilters(f) 273 if err != nil { 274 utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse filters for %s", f.Encode())) 275 return 276 } 277 278 pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs) 279 if err != nil { 280 utils.InternalServerError(w, err) 281 return 282 } 283 284 var errorMsg bytes.Buffer 285 var reclaimedSpace uint64 286 prunedIds := make([]string, 0, len(pruned)) 287 for _, v := range pruned { 288 if v.Err != nil { 289 errorMsg.WriteString(v.Err.Error()) 290 errorMsg.WriteString("; ") 291 continue 292 } 293 prunedIds = append(prunedIds, v.Id) 294 reclaimedSpace += v.Size 295 } 296 if errorMsg.Len() > 0 { 297 utils.InternalServerError(w, errors.New(errorMsg.String())) 298 return 299 } 300 301 payload := handlers.VolumesPruneReport{ 302 VolumesPruneReport: docker_api_types.VolumesPruneReport{ 303 VolumesDeleted: prunedIds, 304 SpaceReclaimed: reclaimedSpace, 305 }, 306 } 307 utils.WriteResponse(w, http.StatusOK, payload) 308 }