podmanv2 volumes

add volume commands: create, inspect, ls, prune, and rm

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-03-21 14:29:30 -05:00
parent 0c084d9719
commit ae614920bf
21 changed files with 716 additions and 208 deletions

View File

@ -0,0 +1,74 @@
package volumes
import (
"encoding/json"
"fmt"
"html/template"
"os"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
var (
volumeInspectDescription = `Display detailed information on one or more volumes.
Use a Go template to change the format from JSON.`
inspectCommand = &cobra.Command{
Use: "inspect [flags] VOLUME [VOLUME...]",
Short: "Display detailed information on one or more volumes",
Long: volumeInspectDescription,
RunE: inspect,
Example: `podman volume inspect myvol
podman volume inspect --all
podman volume inspect --format "{{.Driver}} {{.Scope}}" myvol`,
}
)
var (
inspectOpts = entities.VolumeInspectOptions{}
inspectFormat string
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: inspectCommand,
Parent: volumeCmd,
})
flags := inspectCommand.Flags()
flags.BoolVarP(&inspectOpts.All, "all", "a", false, "Inspect all volumes")
flags.StringVarP(&inspectFormat, "format", "f", "json", "Format volume output using Go template")
}
func inspect(cmd *cobra.Command, args []string) error {
if (inspectOpts.All && len(args) > 0) || (!inspectOpts.All && len(args) < 1) {
return errors.New("provide one or more volume names or use --all")
}
responses, err := registry.ContainerEngine().VolumeInspect(context.Background(), args, inspectOpts)
if err != nil {
return err
}
switch inspectFormat {
case "", formats.JSONString:
jsonOut, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return errors.Wrapf(err, "error marshalling inspect JSON")
}
fmt.Println(string(jsonOut))
default:
tmpl, err := template.New("volumeInspect").Parse(inspectFormat)
if err != nil {
return err
}
if err := tmpl.Execute(os.Stdout, responses); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,98 @@
package volumes
import (
"context"
"html/template"
"io"
"os"
"strings"
"text/tabwriter"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
volumeLsDescription = `
podman volume ls
List all available volumes. The output of the volumes can be filtered
and the output format can be changed to JSON or a user specified Go template.`
lsCommand = &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Args: cobra.NoArgs,
Short: "List volumes",
Long: volumeLsDescription,
RunE: list,
}
)
var (
// Temporary struct to hold cli values.
cliOpts = struct {
Filter []string
Format string
Quiet bool
}{}
lsOpts = entities.VolumeListOptions{}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: lsCommand,
Parent: volumeCmd,
})
flags := lsCommand.Flags()
flags.StringSliceVarP(&cliOpts.Filter, "filter", "f", []string{}, "Filter volume output")
flags.StringVar(&cliOpts.Format, "format", "{{.Driver}}\t{{.Name}}\n", "Format volume output using Go template")
flags.BoolVarP(&cliOpts.Quiet, "quiet", "q", false, "Print volume output in quiet mode")
}
func list(cmd *cobra.Command, args []string) error {
var w io.Writer = os.Stdout
if cliOpts.Quiet && cmd.Flag("format").Changed {
return errors.New("quiet and format flags cannot be used together")
}
for _, f := range cliOpts.Filter {
filterSplit := strings.Split(f, "=")
if len(filterSplit) < 2 {
return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
}
lsOpts.Filter[filterSplit[0]] = append(lsOpts.Filter[filterSplit[0]], filterSplit[1:]...)
}
responses, err := registry.ContainerEngine().VolumeList(context.Background(), lsOpts)
if err != nil {
return err
}
// "\t" from the command line is not being recognized as a tab
// replacing the string "\t" to a tab character if the user passes in "\t"
cliOpts.Format = strings.Replace(cliOpts.Format, `\t`, "\t", -1)
if cliOpts.Quiet {
cliOpts.Format = "{{.Name}}\n"
}
headers := "DRIVER\tVOLUME NAME\n"
row := cliOpts.Format
if !strings.HasSuffix(cliOpts.Format, "\n") {
row += "\n"
}
format := "{{range . }}" + row + "{{end}}"
if !cliOpts.Quiet && !cmd.Flag("format").Changed {
w = tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0)
format = headers + format
}
tmpl, err := template.New("listVolume").Parse(format)
if err != nil {
return err
}
if err := tmpl.Execute(w, responses); err != nil {
return err
}
if flusher, ok := w.(interface{ Flush() error }); ok {
return flusher.Flush()
}
return nil
}

View File

@ -0,0 +1,74 @@
package volumes
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
volumePruneDescription = `Volumes that are not currently owned by a container will be removed.
The command prompts for confirmation which can be overridden with the --force flag.
Note all data will be destroyed.`
pruneCommand = &cobra.Command{
Use: "prune",
Args: cobra.NoArgs,
Short: "Remove all unused volumes",
Long: volumePruneDescription,
RunE: prune,
}
)
var (
pruneOptions entities.VolumePruneOptions
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: pruneCommand,
Parent: volumeCmd,
})
flags := pruneCommand.Flags()
flags.BoolVarP(&pruneOptions.Force, "force", "f", false, "Do not prompt for confirmation")
}
func prune(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
// Prompt for confirmation if --force is not set
if !pruneOptions.Force {
reader := bufio.NewReader(os.Stdin)
fmt.Println("WARNING! This will remove all volumes not used by at least one container.")
fmt.Print("Are you sure you want to continue? [y/N] ")
answer, err := reader.ReadString('\n')
if err != nil {
return errors.Wrapf(err, "error reading input")
}
if strings.ToLower(answer)[0] != 'y' {
return nil
}
}
responses, err := registry.ContainerEngine().VolumePrune(context.Background(), pruneOptions)
if err != nil {
return err
}
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1,64 @@
package volumes
import (
"context"
"fmt"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
volumeRmDescription = `Remove one or more existing volumes.
By default only volumes that are not being used by any containers will be removed. To remove the volumes anyways, use the --force flag.`
rmCommand = &cobra.Command{
Use: "rm [flags] VOLUME [VOLUME...]",
Aliases: []string{"remove"},
Short: "Remove one or more volumes",
Long: volumeRmDescription,
RunE: rm,
Example: `podman volume rm myvol1 myvol2
podman volume rm --all
podman volume rm --force myvol`,
}
)
var (
rmOptions = entities.VolumeRmOptions{}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: rmCommand,
Parent: volumeCmd,
})
flags := rmCommand.Flags()
flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all volumes")
flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Remove a volume by force, even if it is being used by a container")
}
func rm(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
if (len(args) > 0 && rmOptions.All) || (len(args) < 1 && !rmOptions.All) {
return errors.New("choose either one or more volumes or all")
}
responses, err := registry.ContainerEngine().VolumeRm(context.Background(), args, rmOptions)
if err != nil {
return err
}
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

1
go.mod
View File

@ -59,6 +59,7 @@ require (
github.com/varlink/go v0.0.0-20190502142041-0f1d566d194b
github.com/vishvananda/netlink v1.1.0
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2
gopkg.in/yaml.v2 v2.2.8

View File

@ -5,6 +5,7 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
)
@ -35,7 +36,6 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool) error
return nil
}
}
return r.removeVolume(ctx, v, force)
}
@ -130,26 +130,24 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
}
// PruneVolumes removes unused volumes from the system
func (r *Runtime) PruneVolumes(ctx context.Context) ([]string, []error) {
func (r *Runtime) PruneVolumes(ctx context.Context) ([]*entities.VolumePruneReport, error) {
var (
prunedIDs []string
pruneErrors []error
reports []*entities.VolumePruneReport
)
vols, err := r.GetAllVolumes()
if err != nil {
pruneErrors = append(pruneErrors, err)
return nil, pruneErrors
return nil, err
}
for _, vol := range vols {
if err := r.RemoveVolume(ctx, vol, false); err != nil {
if errors.Cause(err) != define.ErrVolumeBeingUsed && errors.Cause(err) != define.ErrVolumeRemoved {
pruneErrors = append(pruneErrors, err)
reports = append(reports, &entities.VolumePruneReport{Id: vol.Name(), Err: err})
}
continue
}
vol.newVolumeEvent(events.Prune)
prunedIDs = append(prunedIDs, vol.Name())
reports = append(reports, &entities.VolumePruneReport{Id: vol.Name()})
}
return prunedIDs, pruneErrors
return reports, nil
}

View File

@ -347,7 +347,23 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti
// PruneVolumes is a wrapper function for libpod PruneVolumes
func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) {
return r.Runtime.PruneVolumes(ctx)
var (
vids []string
errs []error
)
reports, err := r.Runtime.PruneVolumes(ctx)
if err != nil {
errs = append(errs, err)
return vids, errs
}
for _, r := range reports {
if r.Err == nil {
vids = append(vids, r.Id)
} else {
errs = append(errs, r.Err)
}
}
return vids, errs
}
// SaveImage is a wrapper function for saving an image to the local filesystem

View File

@ -3,16 +3,15 @@ package libpod
import (
"encoding/json"
"net/http"
"strings"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/filters"
"github.com/gorilla/schema"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
func CreateVolume(w http.ResponseWriter, r *http.Request) {
@ -65,14 +64,14 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
return
}
volResponse := entities.VolumeConfigResponse{
Name: config.Name,
Labels: config.Labels,
Driver: config.Driver,
MountPoint: config.MountPoint,
CreatedTime: config.CreatedTime,
Options: config.Options,
UID: config.UID,
GID: config.GID,
Name: config.Name,
Driver: config.Driver,
Mountpoint: config.MountPoint,
CreatedAt: config.CreatedTime,
Labels: config.Labels,
Options: config.Options,
UID: config.UID,
GID: config.GID,
}
utils.WriteResponse(w, http.StatusOK, volResponse)
}
@ -85,21 +84,27 @@ func InspectVolume(w http.ResponseWriter, r *http.Request) {
vol, err := runtime.GetVolume(name)
if err != nil {
utils.VolumeNotFound(w, name, err)
return
}
inspect, err := vol.Inspect()
if err != nil {
utils.InternalServerError(w, err)
volResponse := entities.VolumeConfigResponse{
Name: vol.Name(),
Driver: vol.Driver(),
Mountpoint: vol.MountPoint(),
CreatedAt: vol.CreatedTime(),
Labels: vol.Labels(),
Scope: vol.Scope(),
Options: vol.Options(),
UID: vol.UID(),
GID: vol.GID(),
}
utils.WriteResponse(w, http.StatusOK, inspect)
utils.WriteResponse(w, http.StatusOK, volResponse)
}
func ListVolumes(w http.ResponseWriter, r *http.Request) {
var (
decoder = r.Context().Value("decoder").(*schema.Decoder)
err error
runtime = r.Context().Value("runtime").(*libpod.Runtime)
volumeConfigs []*libpod.VolumeConfig
volumeFilters []libpod.VolumeFilter
volumeConfigs []*entities.VolumeListReport
)
query := struct {
Filters map[string][]string `schema:"filters"`
@ -113,25 +118,30 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) {
return
}
if len(query.Filters) > 0 {
volumeFilters, err = generateVolumeFilters(query.Filters)
if err != nil {
utils.InternalServerError(w, err)
return
}
volumeFilters, err := filters.GenerateVolumeFilters(query.Filters)
if err != nil {
utils.InternalServerError(w, err)
return
}
vols, err := runtime.Volumes(volumeFilters...)
if err != nil {
utils.InternalServerError(w, err)
return
}
for _, v := range vols {
config, err := v.Config()
if err != nil {
utils.InternalServerError(w, err)
return
config := entities.VolumeConfigResponse{
Name: v.Name(),
Driver: v.Driver(),
Mountpoint: v.MountPoint(),
CreatedAt: v.CreatedTime(),
Labels: v.Labels(),
Scope: v.Scope(),
Options: v.Options(),
UID: v.UID(),
GID: v.GID(),
}
volumeConfigs = append(volumeConfigs, config)
volumeConfigs = append(volumeConfigs, &entities.VolumeListReport{VolumeConfigResponse: config})
}
utils.WriteResponse(w, http.StatusOK, volumeConfigs)
}
@ -140,14 +150,10 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
)
pruned, errs := runtime.PruneVolumes(r.Context())
if errs != nil {
if len(errs) > 1 {
for _, err := range errs {
log.Infof("Request Failed(%s): %s", http.StatusText(http.StatusInternalServerError), err.Error())
}
}
utils.InternalServerError(w, errs[len(errs)-1])
pruned, err := runtime.PruneVolumes(r.Context())
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, pruned)
}
@ -184,65 +190,3 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
func generateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, error) {
var vf []libpod.VolumeFilter
for filter, v := range filters {
for _, val := range v {
switch filter {
case "name":
nameVal := val
vf = append(vf, func(v *libpod.Volume) bool {
return nameVal == v.Name()
})
case "driver":
driverVal := val
vf = append(vf, func(v *libpod.Volume) bool {
return v.Driver() == driverVal
})
case "scope":
scopeVal := val
vf = append(vf, func(v *libpod.Volume) bool {
return v.Scope() == scopeVal
})
case "label":
filterArray := strings.SplitN(val, "=", 2)
filterKey := filterArray[0]
var filterVal string
if len(filterArray) > 1 {
filterVal = filterArray[1]
} else {
filterVal = ""
}
vf = append(vf, func(v *libpod.Volume) bool {
for labelKey, labelValue := range v.Labels() {
if labelKey == filterKey && ("" == filterVal || labelValue == filterVal) {
return true
}
}
return false
})
case "opt":
filterArray := strings.SplitN(val, "=", 2)
filterKey := filterArray[0]
var filterVal string
if len(filterArray) > 1 {
filterVal = filterArray[1]
} else {
filterVal = ""
}
vf = append(vf, func(v *libpod.Volume) bool {
for labelKey, labelValue := range v.Options() {
if labelKey == filterKey && ("" == filterVal || labelValue == filterVal) {
return true
}
}
return false
})
default:
return nil, errors.Errorf("%q is in an invalid volume filter", filter)
}
}
}
return vf, nil
}

View File

@ -53,8 +53,8 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
// produces:
// - application/json
// responses:
// '204':
// description: no error
// '200':
// "$ref": "#/responses/VolumePruneResponse"
// '500':
// "$ref": "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/volumes/prune"), s.APIHandler(libpod.PruneVolumes)).Methods(http.MethodPost)
@ -71,11 +71,11 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
// - application/json
// responses:
// '200':
// "$ref": "#/responses/InspectVolumeResponse"
// "$ref": "#/responses/VolumeCreateResponse"
// '404':
// "$ref": "#/responses/NoSuchVolume"
// "$ref": "#/responses/NoSuchVolume"
// '500':
// "$ref": "#/responses/InternalError"
// "$ref": "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/volumes/{name}/json"), s.APIHandler(libpod.InspectVolume)).Methods(http.MethodGet)
// swagger:operation DELETE /libpod/volumes/{name} volumes removeVolume
// ---

View File

@ -151,6 +151,13 @@ type ok struct {
}
}
// Volume prune response
// swagger:response VolumePruneResponse
type swagVolumePruneResponse struct {
// in:body
Body []entities.VolumePruneReport
}
// Volume create response
// swagger:response VolumeCreateResponse
type swagVolumeCreateResponse struct {

View File

@ -7,7 +7,6 @@ import (
"strconv"
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
jsoniter "github.com/json-iterator/go"
@ -35,9 +34,9 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions) (*entities
}
// Inspect returns low-level information about a volume.
func Inspect(ctx context.Context, nameOrID string) (*libpod.InspectVolumeData, error) {
func Inspect(ctx context.Context, nameOrID string) (*entities.VolumeConfigResponse, error) {
var (
inspect libpod.InspectVolumeData
inspect entities.VolumeConfigResponse
)
conn, err := bindings.GetClient(ctx)
if err != nil {
@ -52,9 +51,9 @@ func Inspect(ctx context.Context, nameOrID string) (*libpod.InspectVolumeData, e
// List returns the configurations for existing volumes in the form of a slice. Optionally, filters
// can be used to refine the list of volumes.
func List(ctx context.Context, filters map[string][]string) ([]*libpod.VolumeConfig, error) {
func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeListReport, error) {
var (
vols []*libpod.VolumeConfig
vols []*entities.VolumeListReport
)
conn, err := bindings.GetClient(ctx)
if err != nil {
@ -76,9 +75,9 @@ func List(ctx context.Context, filters map[string][]string) ([]*libpod.VolumeCon
}
// Prune removes unused volumes from the local filesystem.
func Prune(ctx context.Context) ([]string, error) {
func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
var (
pruned []string
pruned []*entities.VolumePruneReport
)
conn, err := bindings.GetClient(ctx)
if err != nil {
@ -86,7 +85,7 @@ func Prune(ctx context.Context) ([]string, error) {
}
response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil)
if err != nil {
return pruned, err
return nil, err
}
return pruned, response.Process(&pruned)
}

View File

@ -5,7 +5,6 @@ import (
)
type ContainerEngine interface {
ContainerPrune(ctx context.Context) (*ContainerPruneReport, error)
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error)
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
@ -14,10 +13,10 @@ type ContainerEngine interface {
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
PodDelete(ctx context.Context, opts PodPruneOptions) (*PodDeleteReport, error)
PodExists(ctx context.Context, nameOrId string) (*BoolReport, error)
PodPrune(ctx context.Context) (*PodPruneReport, error)
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
VolumeDelete(ctx context.Context, opts VolumeDeleteOptions) (*VolumeDeleteReport, error)
VolumePrune(ctx context.Context) (*VolumePruneReport, error)
VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error)
VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error)
VolumePrune(ctx context.Context, opts VolumePruneOptions) ([]*VolumePruneReport, error)
VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error)
}

View File

@ -13,13 +13,5 @@ type Report struct {
Err map[string]error
}
type ContainerDeleteOptions struct{}
type ContainerDeleteReport struct{ Report }
type ContainerPruneReport struct{ Report }
type PodDeleteReport struct{ Report }
type PodPruneOptions struct{}
type PodPruneReport struct{ Report }
type VolumeDeleteOptions struct{}
type VolumeDeleteReport struct{ Report }
type VolumePruneReport struct{ Report }

View File

@ -1,6 +1,8 @@
package entities
import "time"
import (
"time"
)
// swagger:model VolumeCreate
type VolumeCreateOptions struct {
@ -20,22 +22,71 @@ type IdOrNameResponse struct {
}
type VolumeConfigResponse struct {
// Name of the volume.
Name string `json:"name"`
Labels map[string]string `json:"labels"`
// The volume driver. Empty string or local does not activate a volume
// driver, all other volumes will.
Driver string `json:"volumeDriver"`
// The location the volume is mounted at.
MountPoint string `json:"mountPoint"`
// Time the volume was created.
CreatedTime time.Time `json:"createdAt,omitempty"`
// Options to pass to the volume driver. For the local driver, this is
// a list of mount options. For other drivers, they are passed to the
// volume driver handling the volume.
Options map[string]string `json:"volumeOptions,omitempty"`
// UID the volume will be created as.
UID int `json:"uid"`
// GID the volume will be created as.
GID int `json:"gid"`
// Name is the name of the volume.
Name string `json:"Name"`
// Driver is the driver used to create the volume.
// This will be properly implemented in a future version.
Driver string `json:"Driver"`
// Mountpoint is the path on the host where the volume is mounted.
Mountpoint string `json:"Mountpoint"`
// CreatedAt is the date and time the volume was created at. This is not
// stored for older Libpod volumes; if so, it will be omitted.
CreatedAt time.Time `json:"CreatedAt,omitempty"`
// Status is presently unused and provided only for Docker compatibility.
// In the future it will be used to return information on the volume's
// current state.
Status map[string]string `json:"Status,omitempty"`
// Labels includes the volume's configured labels, key:value pairs that
// can be passed during volume creation to provide information for third
// party tools.
Labels map[string]string `json:"Labels"`
// Scope is unused and provided solely for Docker compatibility. It is
// unconditionally set to "local".
Scope string `json:"Scope"`
// Options is a set of options that were used when creating the volume.
// It is presently not used.
Options map[string]string `json:"Options"`
// UID is the UID that the volume was created with.
UID int `json:"UID,omitempty"`
// GID is the GID that the volume was created with.
GID int `json:"GID,omitempty"`
// Anonymous indicates that the volume was created as an anonymous
// volume for a specific container, and will be be removed when any
// container using it is removed.
Anonymous bool `json:"Anonymous,omitempty"`
}
type VolumeRmOptions struct {
All bool
Force bool
}
type VolumeRmReport struct {
Err error
Id string
}
type VolumeInspectOptions struct {
All bool
}
type VolumeInspectReport struct {
*VolumeConfigResponse
}
type VolumePruneOptions struct {
Force bool
}
type VolumePruneReport struct {
Err error
Id string
}
type VolumeListOptions struct {
Filter map[string][]string
}
type VolumeListReport struct {
VolumeConfigResponse
}

View File

@ -0,0 +1,70 @@
package filters
import (
"strings"
"github.com/containers/libpod/libpod"
"github.com/pkg/errors"
)
func GenerateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, error) {
var vf []libpod.VolumeFilter
for filter, v := range filters {
for _, val := range v {
switch filter {
case "name":
nameVal := val
vf = append(vf, func(v *libpod.Volume) bool {
return nameVal == v.Name()
})
case "driver":
driverVal := val
vf = append(vf, func(v *libpod.Volume) bool {
return v.Driver() == driverVal
})
case "scope":
scopeVal := val
vf = append(vf, func(v *libpod.Volume) bool {
return v.Scope() == scopeVal
})
case "label":
filterArray := strings.SplitN(val, "=", 2)
filterKey := filterArray[0]
var filterVal string
if len(filterArray) > 1 {
filterVal = filterArray[1]
} else {
filterVal = ""
}
vf = append(vf, func(v *libpod.Volume) bool {
for labelKey, labelValue := range v.Labels() {
if labelKey == filterKey && ("" == filterVal || labelValue == filterVal) {
return true
}
}
return false
})
case "opt":
filterArray := strings.SplitN(val, "=", 2)
filterKey := filterArray[0]
var filterVal string
if len(filterArray) > 1 {
filterVal = filterArray[1]
} else {
filterVal = ""
}
vf = append(vf, func(v *libpod.Volume) bool {
for labelKey, labelValue := range v.Options() {
if labelKey == filterKey && ("" == filterVal || labelValue == filterVal) {
return true
}
}
return false
})
default:
return nil, errors.Errorf("%q is in an invalid volume filter", filter)
}
}
}
return vf, nil
}

View File

@ -239,23 +239,3 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
}
return reports, nil
}
func (ic *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) {
panic("implement me")
}
func (ic *ContainerEngine) PodDelete(ctx context.Context, opts entities.PodPruneOptions) (*entities.PodDeleteReport, error) {
panic("implement me")
}
func (ic *ContainerEngine) PodPrune(ctx context.Context) (*entities.PodPruneReport, error) {
panic("implement me")
}
func (ic *ContainerEngine) VolumeDelete(ctx context.Context, opts entities.VolumeDeleteOptions) (*entities.VolumeDeleteReport, error) {
panic("implement me")
}
func (ic *ContainerEngine) VolumePrune(ctx context.Context) (*entities.VolumePruneReport, error) {
panic("implement me")
}

View File

@ -7,7 +7,9 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/filters"
"github.com/containers/libpod/pkg/domain/infra/abi/parse"
"github.com/pkg/errors"
)
func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.VolumeCreateOptions) (*entities.IdOrNameResponse, error) {
@ -36,3 +38,109 @@ func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.Volum
}
return &entities.IdOrNameResponse{IdOrName: vol.Name()}, nil
}
func (ic *ContainerEngine) VolumeRm(ctx context.Context, namesOrIds []string, opts entities.VolumeRmOptions) ([]*entities.VolumeRmReport, error) {
var (
err error
reports []*entities.VolumeRmReport
vols []*libpod.Volume
)
if opts.All {
vols, err = ic.Libpod.Volumes()
if err != nil {
return nil, err
}
} else {
for _, id := range namesOrIds {
vol, err := ic.Libpod.LookupVolume(id)
if err != nil {
reports = append(reports, &entities.VolumeRmReport{
Err: err,
Id: id,
})
continue
}
vols = append(vols, vol)
}
}
for _, vol := range vols {
reports = append(reports, &entities.VolumeRmReport{
Err: ic.Libpod.RemoveVolume(ctx, vol, opts.Force),
Id: vol.Name(),
})
}
return reports, nil
}
func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []string, opts entities.VolumeInspectOptions) ([]*entities.VolumeInspectReport, error) {
var (
err error
reports []*entities.VolumeInspectReport
vols []*libpod.Volume
)
// Note: as with previous implementation, a single failure here
// results a return.
if opts.All {
vols, err = ic.Libpod.GetAllVolumes()
if err != nil {
return nil, err
}
} else {
for _, v := range namesOrIds {
vol, err := ic.Libpod.LookupVolume(v)
if err != nil {
return nil, errors.Wrapf(err, "error inspecting volume %s", v)
}
vols = append(vols, vol)
}
}
for _, v := range vols {
config := entities.VolumeConfigResponse{
Name: v.Name(),
Driver: v.Driver(),
Mountpoint: v.MountPoint(),
CreatedAt: v.CreatedTime(),
Labels: v.Labels(),
Scope: v.Scope(),
Options: v.Options(),
UID: v.UID(),
GID: v.GID(),
}
reports = append(reports, &entities.VolumeInspectReport{&config})
}
return reports, nil
}
func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
return ic.Libpod.PruneVolumes(ctx)
}
func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) {
var (
reports []*entities.VolumeListReport
)
volumeFilters, err := filters.GenerateVolumeFilters(opts.Filter)
if err != nil {
return nil, err
}
vols, err := ic.Libpod.Volumes(volumeFilters...)
if err != nil {
return nil, err
}
for _, v := range vols {
config := entities.VolumeConfigResponse{
Name: v.Name(),
Driver: v.Driver(),
Mountpoint: v.MountPoint(),
CreatedAt: v.CreatedTime(),
Labels: v.Labels(),
Scope: v.Scope(),
Options: v.Options(),
UID: v.UID(),
GID: v.GID(),
}
reports = append(reports, &entities.VolumeListReport{config})
}
return reports, nil
}

View File

@ -33,13 +33,6 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin
return responses, nil
}
func (r *ContainerEngine) ContainerDelete(ctx context.Context, opts entities.ContainerDeleteOptions) (*entities.ContainerDeleteReport, error) {
panic("implement me")
}
func (r *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) {
panic("implement me")
}
func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) {
var (
reports []*entities.PauseUnpauseReport

View File

@ -2,8 +2,6 @@ package tunnel
import (
"context"
"github.com/containers/libpod/pkg/domain/entities"
)
// Image-related runtime using an ssh-tunnel to utilize Podman service
@ -15,19 +13,3 @@ type ImageEngine struct {
type ContainerEngine struct {
ClientCxt context.Context
}
func (r *ContainerEngine) PodDelete(ctx context.Context, opts entities.PodPruneOptions) (*entities.PodDeleteReport, error) {
panic("implement me")
}
func (r *ContainerEngine) PodPrune(ctx context.Context) (*entities.PodPruneReport, error) {
panic("implement me")
}
func (r *ContainerEngine) VolumeDelete(ctx context.Context, opts entities.VolumeDeleteOptions) (*entities.VolumeDeleteReport, error) {
panic("implement me")
}
func (r *ContainerEngine) VolumePrune(ctx context.Context) (*entities.VolumePruneReport, error) {
panic("implement me")
}

View File

@ -14,3 +14,57 @@ func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.Volum
}
return &entities.IdOrNameResponse{IdOrName: response.Name}, nil
}
func (ic *ContainerEngine) VolumeRm(ctx context.Context, namesOrIds []string, opts entities.VolumeRmOptions) ([]*entities.VolumeRmReport, error) {
var (
reports []*entities.VolumeRmReport
)
if opts.All {
vols, err := volumes.List(ic.ClientCxt, nil)
if err != nil {
return nil, err
}
for _, v := range vols {
namesOrIds = append(namesOrIds, v.Name)
}
}
for _, id := range namesOrIds {
reports = append(reports, &entities.VolumeRmReport{
Err: volumes.Remove(ic.ClientCxt, id, &opts.Force),
Id: id,
})
}
return reports, nil
}
func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []string, opts entities.VolumeInspectOptions) ([]*entities.VolumeInspectReport, error) {
var (
reports []*entities.VolumeInspectReport
)
if opts.All {
vols, err := volumes.List(ic.ClientCxt, nil)
if err != nil {
return nil, err
}
for _, v := range vols {
namesOrIds = append(namesOrIds, v.Name)
}
}
for _, id := range namesOrIds {
data, err := volumes.Inspect(ic.ClientCxt, id)
if err != nil {
return nil, err
}
reports = append(reports, &entities.VolumeInspectReport{VolumeConfigResponse: data})
}
return reports, nil
}
func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) {
return volumes.Prune(ic.ClientCxt)
}
func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) {
return volumes.List(ic.ClientCxt, opts.Filter)
}

View File

@ -105,16 +105,20 @@ func (i *LibpodAPI) InspectVolume(call iopodman.VarlinkCall, name string) error
// VolumesPrune removes unused images via a varlink call
func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error {
var errs []string
prunedNames, prunedErrors := i.Runtime.PruneVolumes(getContext())
if len(prunedErrors) == 0 {
return call.ReplyVolumesPrune(prunedNames, []string{})
var (
prunedErrors []string
prunedNames []string
)
responses, err := i.Runtime.PruneVolumes(getContext())
if err != nil {
return call.ReplyVolumesPrune([]string{}, []string{err.Error()})
}
// We need to take the errors and capture their strings to go back over
// varlink
for _, e := range prunedErrors {
errs = append(errs, e.Error())
for _, i := range responses {
if i.Err == nil {
prunedNames = append(prunedNames, i.Id)
} else {
prunedErrors = append(prunedErrors, i.Err.Error())
}
}
return call.ReplyVolumesPrune(prunedNames, errs)
return call.ReplyVolumesPrune(prunedNames, prunedErrors)
}