mirror of
https://github.com/containers/podman.git
synced 2026-07-09 23:05:00 -04:00
Add a podman volume rename command, REST API endpoint, and bindings for renaming volumes. The rename updates both the VolumeConfig and VolumeState tables in a single transaction and moves the volume directory on disk, rolling back if the transaction fails. Renaming an anonymous volume converts it to a named volume. Volumes that are in use, mounted, or backed by a volume plugin or the image driver cannot be renamed. Fixes: #28189 Signed-off-by: MayorFaj <mayorfaj@gmail.com>
217 lines
5.4 KiB
Go
217 lines
5.4 KiB
Go
//go:build !remote && (linux || freebsd)
|
|
|
|
package libpod
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"go.podman.io/podman/v6/libpod/define"
|
|
"go.podman.io/podman/v6/libpod/events"
|
|
"go.podman.io/podman/v6/pkg/domain/entities/reports"
|
|
)
|
|
|
|
// Contains the public Runtime API for volumes
|
|
|
|
// A VolumeCreateOption is a functional option which alters the Volume created by
|
|
// NewVolume
|
|
type VolumeCreateOption func(*Volume) error
|
|
|
|
// VolumeFilter is a function to determine whether a volume is included in command
|
|
// output. Volumes to be outputted are tested using the function. a true return will
|
|
// include the volume, a false return will exclude it.
|
|
type VolumeFilter func(*Volume) bool
|
|
|
|
// RemoveVolume removes a volumes
|
|
func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool, timeout *uint) error {
|
|
if !r.valid {
|
|
return define.ErrRuntimeStopped
|
|
}
|
|
|
|
return r.removeVolume(ctx, v, force, timeout, false)
|
|
}
|
|
|
|
// GetVolume retrieves a volume given its full name.
|
|
func (r *Runtime) GetVolume(name string) (*Volume, error) {
|
|
if !r.valid {
|
|
return nil, define.ErrRuntimeStopped
|
|
}
|
|
|
|
vol, err := r.state.Volume(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return vol, nil
|
|
}
|
|
|
|
// LookupVolume retrieves a volume by unambiguous partial name.
|
|
func (r *Runtime) LookupVolume(name string) (*Volume, error) {
|
|
if !r.valid {
|
|
return nil, define.ErrRuntimeStopped
|
|
}
|
|
|
|
vol, err := r.state.LookupVolume(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return vol, nil
|
|
}
|
|
|
|
// HasVolume checks to see if a volume with the given name exists
|
|
func (r *Runtime) HasVolume(name string) (bool, error) {
|
|
if !r.valid {
|
|
return false, define.ErrRuntimeStopped
|
|
}
|
|
|
|
return r.state.HasVolume(name)
|
|
}
|
|
|
|
// Volumes retrieves all volumes
|
|
// Filters can be provided which will determine which volumes are included in the
|
|
// output. If multiple filters are used, a volume will be returned if
|
|
// all of the filters are matched
|
|
func (r *Runtime) Volumes(filters ...VolumeFilter) ([]*Volume, error) {
|
|
if !r.valid {
|
|
return nil, define.ErrRuntimeStopped
|
|
}
|
|
|
|
vols, err := r.state.AllVolumes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(filters) == 0 {
|
|
return vols, nil
|
|
}
|
|
|
|
volsFiltered := make([]*Volume, 0, len(vols))
|
|
for _, vol := range vols {
|
|
include := true
|
|
for _, filter := range filters {
|
|
include = include && filter(vol)
|
|
}
|
|
|
|
if include {
|
|
volsFiltered = append(volsFiltered, vol)
|
|
}
|
|
}
|
|
|
|
return volsFiltered, nil
|
|
}
|
|
|
|
// GetAllVolumes retrieves all the volumes
|
|
func (r *Runtime) GetAllVolumes() ([]*Volume, error) {
|
|
if !r.valid {
|
|
return nil, define.ErrRuntimeStopped
|
|
}
|
|
|
|
return r.state.AllVolumes()
|
|
}
|
|
|
|
// RenameVolume renames the given volume to a new name.
|
|
// The volume must not be in use by any containers, and must use the local
|
|
// driver.
|
|
func (r *Runtime) RenameVolume(_ context.Context, vol *Volume, newName string) (*Volume, error) {
|
|
if !r.valid {
|
|
return nil, define.ErrRuntimeStopped
|
|
}
|
|
|
|
vol.lock.Lock()
|
|
defer vol.lock.Unlock()
|
|
|
|
if err := vol.update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if newName == "" || !define.NameRegex.MatchString(newName) {
|
|
return nil, define.RegexError
|
|
}
|
|
|
|
if vol.Name() == newName {
|
|
return vol, nil
|
|
}
|
|
|
|
// Only local-driver volumes can be renamed
|
|
driver := vol.Driver()
|
|
if driver != "" && driver != define.VolumeDriverLocal {
|
|
return nil, fmt.Errorf("renaming volume %s: rename is not supported for volumes using driver %q: %w", vol.Name(), driver, define.ErrInvalidArg)
|
|
}
|
|
|
|
// Refuse rename if the volume is currently mounted
|
|
if vol.state.MountCount > 0 {
|
|
return nil, fmt.Errorf("renaming volume %s: volume is currently mounted: %w", vol.Name(), define.ErrVolumeBeingUsed)
|
|
}
|
|
|
|
// Refuse rename if the volume is in use by any container
|
|
ctrs, err := r.state.VolumeInUse(vol)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("checking if volume %s is in use: %w", vol.Name(), err)
|
|
}
|
|
if len(ctrs) > 0 {
|
|
return nil, fmt.Errorf("volume %s is being used by the following container(s): %s: %w", vol.Name(), strings.Join(ctrs, ", "), define.ErrVolumeBeingUsed)
|
|
}
|
|
|
|
oldName := vol.config.Name
|
|
config := *vol.config
|
|
config.Name = newName
|
|
config.MountPoint = r.volumeDataPath(newName)
|
|
config.IsAnon = false
|
|
|
|
if err := r.state.RenameVolume(vol, &config); err != nil {
|
|
return nil, fmt.Errorf("renaming volume %s: %w", oldName, err)
|
|
}
|
|
vol.config = &config
|
|
|
|
vol.newVolumeEvent(events.Rename)
|
|
return vol, nil
|
|
}
|
|
|
|
// PruneVolumes removes unused volumes from the system
|
|
func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter, dryRun bool) ([]*reports.PruneReport, error) {
|
|
preports := make([]*reports.PruneReport, 0)
|
|
vols, err := r.Volumes(filterFuncs...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, vol := range vols {
|
|
dangling, err := vol.IsDangling()
|
|
if err != nil {
|
|
preports = append(preports, &reports.PruneReport{
|
|
Id: vol.Name(),
|
|
Err: err,
|
|
})
|
|
}
|
|
if !dangling {
|
|
continue
|
|
}
|
|
|
|
report := new(reports.PruneReport)
|
|
volSize, err := vol.Size()
|
|
if err != nil {
|
|
volSize = 0
|
|
}
|
|
report.Size = volSize
|
|
report.Id = vol.Name()
|
|
if !dryRun {
|
|
var timeout *uint
|
|
if err := r.RemoveVolume(ctx, vol, false, timeout); err != nil {
|
|
if !errors.Is(err, define.ErrVolumeBeingUsed) && !errors.Is(err, define.ErrVolumeRemoved) {
|
|
report.Err = err
|
|
} else {
|
|
// We didn't remove the volume for some reason
|
|
continue
|
|
}
|
|
} else {
|
|
vol.newVolumeEvent(events.Prune)
|
|
}
|
|
}
|
|
preports = append(preports, report)
|
|
}
|
|
return preports, nil
|
|
}
|