Skip to content

Commit

Permalink
Add function for looking up volumes by partial name
Browse files Browse the repository at this point in the history
This isn't included in Docker, but seems handy enough.

Use the new API for 'volume rm' and 'volume inspect'.

Fixes containers#3891

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
  • Loading branch information
mheon committed Sep 9, 2019
1 parent 290def5 commit c7a3c21
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 60 deletions.
4 changes: 2 additions & 2 deletions API.md
Expand Up @@ -177,7 +177,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in

[func VolumeCreate(options: VolumeCreateOpts) string](#VolumeCreate)

[func VolumeRemove(options: VolumeRemoveOpts) []string](#VolumeRemove)
[func VolumeRemove(options: VolumeRemoveOpts) []string, map[string]](#VolumeRemove)

[func VolumesPrune() []string, []string](#VolumesPrune)

Expand Down Expand Up @@ -1151,7 +1151,7 @@ VolumeCreate creates a volume on a remote host
### <a name="VolumeRemove"></a>func VolumeRemove
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">

method VolumeRemove(options: [VolumeRemoveOpts](#VolumeRemoveOpts)) [[]string](#[]string)</div>
method VolumeRemove(options: [VolumeRemoveOpts](#VolumeRemoveOpts)) [[]string](#[]string), [map[string]](#map[string])</div>
VolumeRemove removes a volume on a remote host
### <a name="VolumesPrune"></a>func VolumesPrune
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
Expand Down
47 changes: 47 additions & 0 deletions cmd/podman/shared/volumes_shared.go
@@ -0,0 +1,47 @@
package shared

import (
"context"

"github.com/containers/libpod/libpod"
)

// Remove given set of volumes
func SharedRemoveVolumes(ctx context.Context, runtime *libpod.Runtime, vols []string, all, force bool) ([]string, map[string]error, error) {
var (
toRemove []*libpod.Volume
success []string
failed map[string]error
)

failed = make(map[string]error)

if all {
vols, err := runtime.Volumes()
if err != nil {
return nil, nil, err
}
toRemove = vols
} else {
for _, v := range vols {
vol, err := runtime.LookupVolume(v)
if err != nil {
failed[v] = err
continue
}
toRemove = append(toRemove, vol)
}
}

// We could parallelize this, but I haven't heard anyone complain about
// performance here yet, so hold off.
for _, vol := range toRemove {
if err := runtime.RemoveVolume(ctx, vol, force); err != nil {
failed[vol.Name()] = err
continue
}
success = append(success, vol.Name())
}

return success, failed, nil
}
2 changes: 1 addition & 1 deletion cmd/podman/varlink/io.podman.varlink
Expand Up @@ -1212,7 +1212,7 @@ method ReceiveFile(path: string, delete: bool) -> (len: int)
method VolumeCreate(options: VolumeCreateOpts) -> (volumeName: string)

# VolumeRemove removes a volume on a remote host
method VolumeRemove(options: VolumeRemoveOpts) -> (volumeNames: []string)
method VolumeRemove(options: VolumeRemoveOpts) -> (successes: []string, failures: [string]string)

# GetVolumes gets slice of the volumes on a remote host
method GetVolumes(args: []string, all: bool) -> (volumes: []Volume)
Expand Down
18 changes: 3 additions & 15 deletions cmd/podman/volume_rm.go
@@ -1,8 +1,6 @@
package main

import (
"fmt"

"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
Expand Down Expand Up @@ -52,19 +50,9 @@ func volumeRmCmd(c *cliconfig.VolumeRmValues) error {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.DeferredShutdown(false)
deletedVolumeNames, err := runtime.RemoveVolumes(getContext(), c)
deletedVolumeNames, deletedVolumeErrors, err := runtime.RemoveVolumes(getContext(), c)
if err != nil {
if len(deletedVolumeNames) > 0 {
printDeleteVolumes(deletedVolumeNames)
return err
}
}
printDeleteVolumes(deletedVolumeNames)
return err
}

func printDeleteVolumes(volumes []string) {
for _, v := range volumes {
fmt.Println(v)
return err
}
return printCmdResults(deletedVolumeNames, deletedVolumeErrors)
}
1 change: 1 addition & 0 deletions docs/podman-volume-inspect.1.md
Expand Up @@ -11,6 +11,7 @@ podman\-volume\-inspect - Get detailed information on one or more volumes
Display detailed information on one or more volumes. The output can be formatted using
the **--format** flag and a Go template. To get detailed information about all the
existing volumes, use the **--all** flag.
Volumes can be queried individually by providing their full name or a unique partial name.


## OPTIONS
Expand Down
8 changes: 4 additions & 4 deletions docs/podman-volume-rm.1.md
Expand Up @@ -4,14 +4,14 @@
podman\-volume\-rm - Remove one or more volumes

## SYNOPSIS
**podman volume rm** [*options*]
**podman volume rm** [*options*] *volume* [...]

## DESCRIPTION

Removes one ore more volumes. Only volumes that are not being used will be removed.
Removes one or more volumes. Only volumes that are not being used will be removed.
If a volume is being used by a container, an error will be returned unless the **--force**
flag is being used. To remove all the volumes, use the **--all** flag.

flag is being used. To remove all volumes, use the **--all** flag.
Volumes can be removed individually by providing their full name or a unique partial name.

## OPTIONS

Expand Down
69 changes: 69 additions & 0 deletions libpod/boltdb_state.go
Expand Up @@ -1735,6 +1735,75 @@ func (s *BoltState) Volume(name string) (*Volume, error) {
return volume, nil
}

// LookupVolume locates a volume from a partial name.
func (s *BoltState) LookupVolume(name string) (*Volume, error) {
if name == "" {
return nil, define.ErrEmptyID
}

if !s.valid {
return nil, define.ErrDBClosed
}

volName := []byte(name)

volume := new(Volume)
volume.config = new(VolumeConfig)

db, err := s.getDBCon()
if err != nil {
return nil, err
}
defer s.deferredCloseDBCon(db)

err = db.View(func(tx *bolt.Tx) error {
volBkt, err := getVolBucket(tx)
if err != nil {
return err
}

allVolsBkt, err := getAllVolsBucket(tx)
if err != nil {
return err
}

// Check for exact match on name
volDB := volBkt.Bucket(volName)
if volDB != nil {
return s.getVolumeFromDB(volName, volume, volBkt)
}

// No exact match. Search all names.
foundMatch := false
err = allVolsBkt.ForEach(func(checkName, checkName2 []byte) error {
if strings.HasPrefix(string(checkName), name) {
if foundMatch {
return errors.Wrapf(define.ErrVolumeExists, "more than one result for volume name %q", name)
}
foundMatch = true
volName = make([]byte, 0, len(checkName))
copy(checkName, volName)
}
return nil
})
if err != nil {
return err
}

if !foundMatch {
return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %q found", name)
}

return s.getVolumeFromDB(volName, volume, volBkt)
})
if err != nil {
return nil, err
}

return volume, nil

}

// HasVolume returns true if the given volume exists in the state, otherwise it returns false
func (s *BoltState) HasVolume(name string) (bool, error) {
if name == "" {
Expand Down
35 changes: 35 additions & 0 deletions libpod/in_memory_state.go
Expand Up @@ -459,6 +459,41 @@ func (s *InMemoryState) Volume(name string) (*Volume, error) {
return vol, nil
}

// LookupVolume finds a volume from an unambiguous partial ID.
func (s *InMemoryState) LookupVolume(name string) (*Volume, error) {
if name == "" {
return nil, define.ErrEmptyID
}

vol, ok := s.volumes[name]
if ok {
return vol, nil
}

// Alright, we've failed to find by full name. Now comes the expensive
// part.
// Loop through all volumes and look for matches.
var (
foundMatch bool
candidate *Volume
)
for volName, vol := range s.volumes {
if strings.HasPrefix(volName, name) {
if foundMatch {
return nil, errors.Wrapf(define.ErrVolumeExists, "more than one result for volume name %q", name)
}
candidate = vol
foundMatch = true
}
}

if !foundMatch {
return nil, errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %q found", name)
}

return candidate, nil
}

// HasVolume checks if a volume with the given name is present in the state
func (s *InMemoryState) HasVolume(name string) (bool, error) {
if name == "" {
Expand Down
46 changes: 15 additions & 31 deletions libpod/runtime_volume.go
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

// Contains the public Runtime API for volumes
Expand Down Expand Up @@ -43,48 +42,33 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool) error
return r.removeVolume(ctx, v, force)
}

// RemoveVolumes removes a slice of volumes or all with a force bool
func (r *Runtime) RemoveVolumes(ctx context.Context, volumes []string, all, force bool) ([]string, error) {
var (
vols []*Volume
err error
deletedVols []string
)
if all {
vols, err = r.Volumes()
if err != nil {
return nil, errors.Wrapf(err, "unable to get all volumes")
}
} else {
for _, i := range volumes {
vol, err := r.GetVolume(i)
if err != nil {
return nil, err
}
vols = append(vols, vol)
}
// GetVolume retrieves a volume given its full name.
func (r *Runtime) GetVolume(name string) (*Volume, error) {
r.lock.RLock()
defer r.lock.RUnlock()

if !r.valid {
return nil, define.ErrRuntimeStopped
}

for _, vol := range vols {
if err := r.RemoveVolume(ctx, vol, force); err != nil {
return deletedVols, err
}
logrus.Debugf("removed volume %s", vol.Name())
deletedVols = append(deletedVols, vol.Name())
vol, err := r.state.Volume(name)
if err != nil {
return nil, err
}
return deletedVols, nil

return vol, nil
}

// GetVolume retrieves a volume given its full name.
func (r *Runtime) GetVolume(name string) (*Volume, error) {
// LookupVolume retrieves a volume by unambigious partial name.
func (r *Runtime) LookupVolume(name string) (*Volume, error) {
r.lock.RLock()
defer r.lock.RUnlock()

if !r.valid {
return nil, define.ErrRuntimeStopped
}

vol, err := r.state.Volume(name)
vol, err := r.state.LookupVolume(name)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions libpod/state.go
Expand Up @@ -190,6 +190,9 @@ type State interface {
// Volume accepts full name of volume
// If the volume doesn't exist, an error will be returned
Volume(volName string) (*Volume, error)
// LookupVolume accepts an unambiguous partial name or full name of a
// volume. Ambiguous names will result in an error.
LookupVolume(name string) (*Volume, error)
// HasVolume returns true if volName exists in the state,
// otherwise it returns false
HasVolume(volName string) (bool, error)
Expand Down
6 changes: 3 additions & 3 deletions pkg/adapter/runtime.go
Expand Up @@ -196,8 +196,8 @@ func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCrea
}

// RemoveVolumes is a wrapper to remove volumes
func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) {
return r.Runtime.RemoveVolumes(ctx, c.InputArgs, c.All, c.Force)
func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, map[string]error, error) {
return shared.SharedRemoveVolumes(ctx, r.Runtime, c.InputArgs, c.All, c.Force)
}

// Push is a wrapper to push an image to a registry
Expand All @@ -220,7 +220,7 @@ func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeIn
volumes, err = r.GetAllVolumes()
} else {
for _, v := range c.InputArgs {
vol, err := r.GetVolume(v)
vol, err := r.LookupVolume(v)
if err != nil {
return nil, err
}
Expand Down
9 changes: 7 additions & 2 deletions pkg/adapter/runtime_remote.go
Expand Up @@ -610,13 +610,18 @@ func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCrea
}

// RemoveVolumes removes volumes over a varlink connection for the remote client
func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) {
func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, map[string]error, error) {
rmOpts := iopodman.VolumeRemoveOpts{
All: c.All,
Force: c.Force,
Volumes: c.InputArgs,
}
return iopodman.VolumeRemove().Call(r.Conn, rmOpts)
success, failures, err := iopodman.VolumeRemove().Call(r.Conn, rmOpts)
stringsToErrors := make(map[string]error)
for k, v := range failures {
stringsToErrors[k] = errors.New(v)
}
return success, stringsToErrors, err
}

func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestMIMEType, authfile, digestfile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions image.SigningOptions, dockerRegistryOptions *image.DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error {
Expand Down

0 comments on commit c7a3c21

Please sign in to comment.