Files
podman/libpod/runtime_volume.go
MayorFaj d01b7ae534 libpod: add volume rename support
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>
2026-06-11 21:56:15 +01:00

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
}