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  }