podman/libpod/runtime_cstorage.go
Matthew Heon fc6dcd12b3 Add support for 'image' volume driver
We added the concept of image volumes in 2.2.0, to support
inspecting an image from within a container. However, this is a
strictly read-only mount, with no modification allowed.

By contrast, the new `image` volume driver creates a c/storage
container as its underlying storage, so we have a read/write
layer. This, in and of itself, is not especially interesting, but
what it will enable in the future is. If we add a new command to
allow these image volumes to be committed, we can now distribute
volumes - and changes to them - via a standard OCI image registry
(which is rather new and quite exciting).

Future work in this area:
- Add support for `podman volume push` (commit volume changes and
  push resulting image to OCI registry).
- Add support for `podman volume pull` (currently, we require
  that the image a volume is created from be already pulled; it
  would be simpler if we had a dedicated command that did the
  pull and made a volume from it)
- Add support for scratch images (make an empty image on demand
  to use as the base of the volume)
- Add UOR support to `podman volume push` and
  `podman volume pull` to enable both with non-image volume
  drivers

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2022-09-22 13:07:40 -04:00

134 lines
4.1 KiB
Go

package libpod
import (
"errors"
"fmt"
"time"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/storage"
"github.com/sirupsen/logrus"
)
// StorageContainer represents a container present in c/storage but not in
// libpod.
type StorageContainer struct {
ID string
Names []string
Image string
CreateTime time.Time
PresentInLibpod bool
}
// ListStorageContainers lists all containers visible to c/storage.
func (r *Runtime) ListStorageContainers() ([]*StorageContainer, error) {
finalCtrs := []*StorageContainer{}
ctrs, err := r.store.Containers()
if err != nil {
return nil, err
}
for _, ctr := range ctrs {
storageCtr := new(StorageContainer)
storageCtr.ID = ctr.ID
storageCtr.Names = ctr.Names
storageCtr.Image = ctr.ImageID
storageCtr.CreateTime = ctr.Created
// Look up if container is in state
hasCtr, err := r.state.HasContainer(ctr.ID)
if err != nil {
return nil, fmt.Errorf("looking up container %s in state: %w", ctr.ID, err)
}
storageCtr.PresentInLibpod = hasCtr
finalCtrs = append(finalCtrs, storageCtr)
}
return finalCtrs, nil
}
func (r *Runtime) StorageContainer(idOrName string) (*storage.Container, error) {
return r.store.Container(idOrName)
}
// RemoveStorageContainer removes a container from c/storage.
// The container WILL NOT be removed if it exists in libpod.
// Accepts ID or full name of container.
// If force is set, the container will be unmounted first to ensure removal.
func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error {
targetID, err := r.store.Lookup(idOrName)
if err != nil {
if errors.Is(err, storage.ErrLayerUnknown) {
return fmt.Errorf("no container with ID or name %q found: %w", idOrName, define.ErrNoSuchCtr)
}
return fmt.Errorf("looking up container %q: %w", idOrName, err)
}
// Lookup returns an ID but it's not guaranteed to be a container ID.
// So we can still error here.
ctr, err := r.store.Container(targetID)
if err != nil {
if errors.Is(err, storage.ErrContainerUnknown) {
return fmt.Errorf("%q does not refer to a container: %w", idOrName, define.ErrNoSuchCtr)
}
return fmt.Errorf("retrieving container %q: %w", idOrName, err)
}
// Error out if the container exists in libpod
exists, err := r.state.HasContainer(ctr.ID)
if err != nil {
return err
}
if exists {
return fmt.Errorf("refusing to remove %q as it exists in libpod as container %s: %w", idOrName, ctr.ID, define.ErrCtrExists)
}
// Error out if this is an image-backed volume
allVols, err := r.state.AllVolumes()
if err != nil {
return err
}
for _, vol := range allVols {
if vol.config.Driver == define.VolumeDriverImage && vol.config.StorageID == ctr.ID {
return fmt.Errorf("refusing to remove %q as it exists in libpod as an image-backed volume %s: %w", idOrName, vol.Name(), define.ErrCtrExists)
}
}
if !force {
timesMounted, err := r.store.Mounted(ctr.ID)
if err != nil {
if errors.Is(err, storage.ErrContainerUnknown) {
// Container was removed from under us.
// It's gone, so don't bother erroring.
logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
logrus.Warnf("Checking if container %q is mounted, attempting to delete: %v", idOrName, err)
}
if timesMounted > 0 {
return fmt.Errorf("container %q is mounted and cannot be removed without using force: %w", idOrName, define.ErrCtrStateInvalid)
}
} else if _, err := r.store.Unmount(ctr.ID, true); err != nil {
if errors.Is(err, storage.ErrContainerUnknown) {
// Container again gone, no error
logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
logrus.Warnf("Unmounting container %q while attempting to delete storage: %v", idOrName, err)
}
if err := r.store.DeleteContainer(ctr.ID); err != nil {
if errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown) {
// Container again gone, no error
logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
return fmt.Errorf("removing storage for container %q: %w", idOrName, err)
}
return nil
}