mirror of
https://github.com/containers/podman
synced 2024-10-19 00:34:18 +00:00
rm: add containers eviction with rm --force
Add ability to evict a container when it becomes unusable. This may happen when the host setup changes after a container creation, making it impossible for that container to be used or removed. Evicting a container is done using the `rm --force` command. Signed-off-by: Marco Vedovati <mvedovati@suse.com>
This commit is contained in:
parent
83b2348313
commit
dacbc5beb2
22
API.md
22
API.md
|
@ -41,6 +41,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
|
|||
|
||||
[func Diff(name: string) DiffInfo](#Diff)
|
||||
|
||||
[func EvictContainer(name: string, removeVolumes: bool) string](#EvictContainer)
|
||||
|
||||
[func ExecContainer(opts: ExecOpts) ](#ExecContainer)
|
||||
|
||||
[func ExportContainer(name: string, path: string) string](#ExportContainer)
|
||||
|
@ -445,6 +447,22 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteUnusedImages
|
|||
|
||||
method Diff(name: [string](https://godoc.org/builtin#string)) [DiffInfo](#DiffInfo)</div>
|
||||
Diff returns a diff between libpod objects
|
||||
### <a name="EvictContainer"></a>func EvictContainer
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
method EvictContainer(name: [string](https://godoc.org/builtin#string), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
|
||||
EvictContainer requires the name or ID of a container as well as a boolean that
|
||||
indicates to remove builtin volumes. Upon successful eviction of the container,
|
||||
its ID is returned. If the container cannot be found by name or ID,
|
||||
a [ContainerNotFound](#ContainerNotFound) error will be returned.
|
||||
See also [RemoveContainer](RemoveContainer).
|
||||
#### Example
|
||||
~~~
|
||||
$ varlink call -m unix:/run/podman/io.podman/io.podman.EvictContainer '{"name": "62f4fd98cb57"}'
|
||||
{
|
||||
"container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20"
|
||||
}
|
||||
~~~
|
||||
### <a name="ExecContainer"></a>func ExecContainer
|
||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
|
@ -953,10 +971,12 @@ ReceiveFile allows the host to send a remote client a file
|
|||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||
|
||||
method RemoveContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
|
||||
RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean
|
||||
RemoveContainer requires the name or ID of a container as well as a boolean that
|
||||
indicates whether a container should be forcefully removed (e.g., by stopping it), and a boolean
|
||||
indicating whether to remove builtin volumes. Upon successful removal of the
|
||||
container, its ID is returned. If the
|
||||
container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned.
|
||||
See also [EvictContainer](EvictContainer).
|
||||
#### Example
|
||||
~~~
|
||||
$ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveContainer '{"name": "62f4fd98cb57"}'
|
||||
|
|
|
@ -13,7 +13,7 @@ var (
|
|||
rmCommand cliconfig.RmValues
|
||||
rmDescription = fmt.Sprintf(`Removes one or more containers from the host. The container name or ID can be used.
|
||||
|
||||
Command does not remove images. Running containers will not be removed without the -f option.`)
|
||||
Command does not remove images. Running or unusable containers will not be removed without the -f option.`)
|
||||
_rmCommand = &cobra.Command{
|
||||
Use: "rm [flags] CONTAINER [CONTAINER...]",
|
||||
Short: "Remove one or more containers",
|
||||
|
@ -29,7 +29,8 @@ var (
|
|||
},
|
||||
Example: `podman rm imageID
|
||||
podman rm mywebserver myflaskserver 860a4b23
|
||||
podman rm --force --all`,
|
||||
podman rm --force --all
|
||||
podman rm -f c684f0d469f2`,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -39,7 +40,7 @@ func init() {
|
|||
rmCommand.SetUsageTemplate(UsageTemplate())
|
||||
flags := rmCommand.Flags()
|
||||
flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers")
|
||||
flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false")
|
||||
flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false")
|
||||
flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
||||
flags.BoolVar(&rmCommand.Storage, "storage", false, "Remove container from storage library")
|
||||
flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container")
|
||||
|
|
|
@ -727,10 +727,12 @@ method GetAttachSockets(name: string) -> (sockets: Sockets)
|
|||
# or name, a [ContainerNotFound](#ContainerNotFound) error is returned.
|
||||
method WaitContainer(name: string, interval: int) -> (exitcode: int)
|
||||
|
||||
# RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean
|
||||
# RemoveContainer requires the name or ID of a container as well as a boolean that
|
||||
# indicates whether a container should be forcefully removed (e.g., by stopping it), and a boolean
|
||||
# indicating whether to remove builtin volumes. Upon successful removal of the
|
||||
# container, its ID is returned. If the
|
||||
# container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned.
|
||||
# See also [EvictContainer](EvictContainer).
|
||||
# #### Example
|
||||
# ~~~
|
||||
# $ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveContainer '{"name": "62f4fd98cb57"}'
|
||||
|
@ -740,6 +742,20 @@ method WaitContainer(name: string, interval: int) -> (exitcode: int)
|
|||
# ~~~
|
||||
method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (container: string)
|
||||
|
||||
# EvictContainer requires the name or ID of a container as well as a boolean that
|
||||
# indicates to remove builtin volumes. Upon successful eviction of the container,
|
||||
# its ID is returned. If the container cannot be found by name or ID,
|
||||
# a [ContainerNotFound](#ContainerNotFound) error will be returned.
|
||||
# See also [RemoveContainer](RemoveContainer).
|
||||
# #### Example
|
||||
# ~~~
|
||||
# $ varlink call -m unix:/run/podman/io.podman/io.podman.EvictContainer '{"name": "62f4fd98cb57"}'
|
||||
# {
|
||||
# "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20"
|
||||
# }
|
||||
# ~~~
|
||||
method EvictContainer(name: string, removeVolumes: bool) -> (container: string)
|
||||
|
||||
# DeleteStoppedContainers will delete all containers that are not running. It will return a list the deleted
|
||||
# container IDs. See also [RemoveContainer](RemoveContainer).
|
||||
# #### Example
|
||||
|
|
|
@ -9,7 +9,8 @@ podman\-rm - Remove one or more containers
|
|||
**podman container rm** [*options*] *container*
|
||||
|
||||
## DESCRIPTION
|
||||
**podman rm** will remove one or more containers from the host. The container name or ID can be used. This does not remove images. Running containers will not be removed without the `-f` option
|
||||
**podman rm** will remove one or more containers from the host. The container name or ID can be used. This does not remove images.
|
||||
Running or unusable containers will not be removed without the `-f` option.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
|
@ -19,9 +20,11 @@ Remove all containers. Can be used in conjunction with -f as well.
|
|||
|
||||
**--force**, **-f**
|
||||
|
||||
Force the removal of running and paused containers. Forcing a containers removal also
|
||||
Force the removal of running and paused containers. Forcing a container removal also
|
||||
removes containers from container storage even if the container is not known to podman.
|
||||
Containers could have been created by a different container engine.
|
||||
In addition, forcing can be used to remove unusable containers, e.g. containers
|
||||
whose OCI runtime has become unavailable.
|
||||
|
||||
**--latest**, **-l**
|
||||
|
||||
|
|
|
@ -407,6 +407,60 @@ func (s *BoltState) Container(id string) (*Container, error) {
|
|||
return ctr, nil
|
||||
}
|
||||
|
||||
// LookupContainerID retrieves a container ID from the state by full or unique
|
||||
// partial ID or name
|
||||
func (s *BoltState) LookupContainerID(idOrName string) (string, error) {
|
||||
if idOrName == "" {
|
||||
return "", define.ErrEmptyID
|
||||
}
|
||||
|
||||
if !s.valid {
|
||||
return "", define.ErrDBClosed
|
||||
}
|
||||
|
||||
db, err := s.getDBCon()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer s.deferredCloseDBCon(db)
|
||||
|
||||
var id []byte
|
||||
err = db.View(func(tx *bolt.Tx) error {
|
||||
ctrBucket, err := getCtrBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namesBucket, err := getNamesBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nsBucket, err := getNSBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullID, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket, nsBucket)
|
||||
// Check if it is in our namespace
|
||||
if s.namespaceBytes != nil {
|
||||
ns := nsBucket.Get(fullID)
|
||||
if !bytes.Equal(ns, s.namespaceBytes) {
|
||||
return errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
|
||||
}
|
||||
}
|
||||
id = fullID
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
retID := string(id)
|
||||
return retID, nil
|
||||
}
|
||||
|
||||
// LookupContainer retrieves a container from the state by full or unique
|
||||
// partial ID or name
|
||||
func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
|
||||
|
@ -444,67 +498,9 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// First, check if the ID given was the actual container ID
|
||||
var id []byte
|
||||
ctrExists := ctrBucket.Bucket([]byte(idOrName))
|
||||
if ctrExists != nil {
|
||||
// A full container ID was given.
|
||||
// It might not be in our namespace, but
|
||||
// getContainerFromDB() will handle that case.
|
||||
id = []byte(idOrName)
|
||||
return s.getContainerFromDB(id, ctr, ctrBucket)
|
||||
}
|
||||
|
||||
// Next, check if the full name was given
|
||||
isPod := false
|
||||
fullID := namesBucket.Get([]byte(idOrName))
|
||||
if fullID != nil {
|
||||
// The name exists and maps to an ID.
|
||||
// However, we are not yet certain the ID is a
|
||||
// container.
|
||||
ctrExists = ctrBucket.Bucket(fullID)
|
||||
if ctrExists != nil {
|
||||
// A container bucket matching the full ID was
|
||||
// found.
|
||||
return s.getContainerFromDB(fullID, ctr, ctrBucket)
|
||||
}
|
||||
// Don't error if we have a name match but it's not a
|
||||
// container - there's a chance we have a container with
|
||||
// an ID starting with those characters.
|
||||
// However, so we can return a good error, note whether
|
||||
// this is a pod.
|
||||
isPod = true
|
||||
}
|
||||
|
||||
// We were not given a full container ID or name.
|
||||
// Search for partial ID matches.
|
||||
exists := false
|
||||
err = ctrBucket.ForEach(func(checkID, checkName []byte) error {
|
||||
// If the container isn't in our namespace, we
|
||||
// can't match it
|
||||
if s.namespaceBytes != nil {
|
||||
ns := nsBucket.Get(checkID)
|
||||
if !bytes.Equal(ns, s.namespaceBytes) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(string(checkID), idOrName) {
|
||||
if exists {
|
||||
return errors.Wrapf(define.ErrCtrExists, "more than one result for container ID %s", idOrName)
|
||||
}
|
||||
id = checkID
|
||||
exists = true
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
id, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket, nsBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
if isPod {
|
||||
return errors.Wrapf(define.ErrNoSuchCtr, "%s is a pod, not a container", idOrName)
|
||||
}
|
||||
return errors.Wrapf(define.ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
|
||||
}
|
||||
|
||||
return s.getContainerFromDB(id, ctr, ctrBucket)
|
||||
|
@ -860,6 +856,39 @@ func (s *BoltState) AllContainers() ([]*Container, error) {
|
|||
return ctrs, nil
|
||||
}
|
||||
|
||||
// GetContainerConfig returns a container config from the database by full ID
|
||||
func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) {
|
||||
if len(id) == 0 {
|
||||
return nil, define.ErrEmptyID
|
||||
}
|
||||
|
||||
if !s.valid {
|
||||
return nil, define.ErrDBClosed
|
||||
}
|
||||
|
||||
config := new(ContainerConfig)
|
||||
|
||||
db, err := s.getDBCon()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.deferredCloseDBCon(db)
|
||||
|
||||
err = db.View(func(tx *bolt.Tx) error {
|
||||
ctrBucket, err := getCtrBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.getContainerConfigFromDB([]byte(id), config, ctrBucket)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// RewriteContainerConfig rewrites a container's configuration.
|
||||
// WARNING: This function is DANGEROUS. Do not use without reading the full
|
||||
// comment on this function in state.go.
|
||||
|
|
|
@ -347,7 +347,7 @@ func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|||
return bkt, nil
|
||||
}
|
||||
|
||||
func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error {
|
||||
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
|
||||
ctrBkt := ctrsBkt.Bucket(id)
|
||||
if ctrBkt == nil {
|
||||
return errors.Wrapf(define.ErrNoSuchCtr, "container %s not found in DB", string(id))
|
||||
|
@ -365,10 +365,18 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.
|
|||
return errors.Wrapf(define.ErrInternal, "container %s missing config key in DB", string(id))
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(configBytes, ctr.config); err != nil {
|
||||
if err := json.Unmarshal(configBytes, config); err != nil {
|
||||
return errors.Wrapf(err, "error unmarshalling container %s config", string(id))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error {
|
||||
if err := s.getContainerConfigFromDB(id, ctr.config, ctrsBkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the lock
|
||||
lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID)
|
||||
if err != nil {
|
||||
|
@ -388,7 +396,7 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.
|
|||
|
||||
ociRuntime, ok := s.runtime.ociRuntimes[runtimeName]
|
||||
if !ok {
|
||||
return errors.Wrapf(define.ErrInternal, "container %s was created with OCI runtime %s, but that runtime is not available in the current configuration", ctr.ID(), ctr.config.OCIRuntime)
|
||||
return errors.Wrapf(define.ErrOCIRuntimeUnavailable, "cannot find OCI runtime %q for container %s", ctr.config.OCIRuntime, ctr.ID())
|
||||
}
|
||||
ctr.ociRuntime = ociRuntime
|
||||
}
|
||||
|
@ -862,3 +870,72 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookupContainerID retrieves a container ID from the state by full or unique
|
||||
// partial ID or name.
|
||||
// NOTE: the retrieved container ID namespace may not match the state namespace.
|
||||
func (s *BoltState) lookupContainerID(idOrName string, ctrBucket, namesBucket, nsBucket *bolt.Bucket) ([]byte, error) {
|
||||
// First, check if the ID given was the actual container ID
|
||||
ctrExists := ctrBucket.Bucket([]byte(idOrName))
|
||||
if ctrExists != nil {
|
||||
// A full container ID was given.
|
||||
// It might not be in our namespace, but this will be handled
|
||||
// the callers.
|
||||
return []byte(idOrName), nil
|
||||
}
|
||||
|
||||
// Next, check if the full name was given
|
||||
isPod := false
|
||||
fullID := namesBucket.Get([]byte(idOrName))
|
||||
if fullID != nil {
|
||||
// The name exists and maps to an ID.
|
||||
// However, we are not yet certain the ID is a
|
||||
// container.
|
||||
ctrExists = ctrBucket.Bucket(fullID)
|
||||
if ctrExists != nil {
|
||||
// A container bucket matching the full ID was
|
||||
// found.
|
||||
return fullID, nil
|
||||
}
|
||||
// Don't error if we have a name match but it's not a
|
||||
// container - there's a chance we have a container with
|
||||
// an ID starting with those characters.
|
||||
// However, so we can return a good error, note whether
|
||||
// this is a pod.
|
||||
isPod = true
|
||||
}
|
||||
|
||||
var id []byte
|
||||
// We were not given a full container ID or name.
|
||||
// Search for partial ID matches.
|
||||
exists := false
|
||||
err := ctrBucket.ForEach(func(checkID, checkName []byte) error {
|
||||
// If the container isn't in our namespace, we
|
||||
// can't match it
|
||||
if s.namespaceBytes != nil {
|
||||
ns := nsBucket.Get(checkID)
|
||||
if !bytes.Equal(ns, s.namespaceBytes) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(string(checkID), idOrName) {
|
||||
if exists {
|
||||
return errors.Wrapf(define.ErrCtrExists, "more than one result for container ID %s", idOrName)
|
||||
}
|
||||
id = checkID
|
||||
exists = true
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !exists {
|
||||
if isPod {
|
||||
return nil, errors.Wrapf(define.ErrNoSuchCtr, "%s is a pod, not a container", idOrName)
|
||||
}
|
||||
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
|
|
@ -112,6 +112,10 @@ var (
|
|||
// that was not found
|
||||
ErrOCIRuntimeNotFound = errors.New("OCI runtime command not found error")
|
||||
|
||||
// ErrOCIRuntimeUnavailable indicates that the OCI runtime associated to a container
|
||||
// could not be found in the configuration
|
||||
ErrOCIRuntimeUnavailable = errors.New("OCI runtime not available in the current configuration")
|
||||
|
||||
// ErrConmonOutdated indicates the version of conmon found (whether via the configuration or $PATH)
|
||||
// is out of date for the current podman version
|
||||
ErrConmonOutdated = errors.New("outdated conmon version")
|
||||
|
|
|
@ -115,15 +115,16 @@ func (s *InMemoryState) Container(id string) (*Container, error) {
|
|||
return ctr, nil
|
||||
}
|
||||
|
||||
// LookupContainer retrieves a container by full ID, unique partial ID, or name
|
||||
func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
|
||||
// lookupID retrieves a container or pod ID by full ID, unique partial ID, or
|
||||
// name
|
||||
func (s *InMemoryState) lookupID(idOrName string) (string, error) {
|
||||
var (
|
||||
nameIndex *registrar.Registrar
|
||||
idIndex *truncindex.TruncIndex
|
||||
)
|
||||
|
||||
if idOrName == "" {
|
||||
return nil, define.ErrEmptyID
|
||||
return "", define.ErrEmptyID
|
||||
}
|
||||
|
||||
if s.namespace != "" {
|
||||
|
@ -131,7 +132,7 @@ func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
|
|||
if !ok {
|
||||
// We have no containers in the namespace
|
||||
// Return false
|
||||
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
|
||||
return "", define.ErrNoSuchCtr
|
||||
}
|
||||
nameIndex = nsIndex.nameIndex
|
||||
idIndex = nsIndex.idIndex
|
||||
|
@ -147,15 +148,55 @@ func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
|
|||
fullID, err = idIndex.Get(idOrName)
|
||||
if err != nil {
|
||||
if err == truncindex.ErrNotExist {
|
||||
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
|
||||
return "", define.ErrNoSuchCtr
|
||||
}
|
||||
return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
|
||||
return "", errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
|
||||
return "", errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
|
||||
}
|
||||
}
|
||||
|
||||
return fullID, nil
|
||||
}
|
||||
|
||||
// LookupContainerID retrieves a container ID by full ID, unique partial ID, or
|
||||
// name
|
||||
func (s *InMemoryState) LookupContainerID(idOrName string) (string, error) {
|
||||
fullID, err := s.lookupID(idOrName)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
_, ok := s.containers[fullID]
|
||||
if !ok {
|
||||
// It's a pod, not a container
|
||||
return "", errors.Wrapf(define.ErrNoSuchCtr, "name or ID %s is a pod, not a container", idOrName)
|
||||
}
|
||||
|
||||
case define.ErrNoSuchCtr:
|
||||
return "", errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
|
||||
|
||||
default:
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fullID, nil
|
||||
}
|
||||
|
||||
// LookupContainer retrieves a container by full ID, unique partial ID, or name
|
||||
func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
|
||||
fullID, err := s.lookupID(idOrName)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
|
||||
case define.ErrNoSuchCtr:
|
||||
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
|
||||
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctr, ok := s.containers[fullID]
|
||||
if !ok {
|
||||
// It's a pod, not a container
|
||||
|
@ -385,6 +426,16 @@ func (s *InMemoryState) AllContainers() ([]*Container, error) {
|
|||
return ctrs, nil
|
||||
}
|
||||
|
||||
// GetContainerConfig returns a container config from the database by full ID
|
||||
func (s *InMemoryState) GetContainerConfig(id string) (*ContainerConfig, error) {
|
||||
ctr, err := s.LookupContainer(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctr.Config(), nil
|
||||
}
|
||||
|
||||
// RewriteContainerConfig rewrites a container's configuration.
|
||||
// This function is DANGEROUS, even with an in-memory state.
|
||||
// Please read the full comment on it in state.go before using it.
|
||||
|
@ -623,49 +674,22 @@ func (s *InMemoryState) Pod(id string) (*Pod, error) {
|
|||
// LookupPod retrieves a pod from the state from a full or unique partial ID or
|
||||
// a full name
|
||||
func (s *InMemoryState) LookupPod(idOrName string) (*Pod, error) {
|
||||
var (
|
||||
nameIndex *registrar.Registrar
|
||||
idIndex *truncindex.TruncIndex
|
||||
)
|
||||
fullID, err := s.lookupID(idOrName)
|
||||
|
||||
if idOrName == "" {
|
||||
return nil, define.ErrEmptyID
|
||||
}
|
||||
switch err {
|
||||
case nil:
|
||||
|
||||
if s.namespace != "" {
|
||||
nsIndex, ok := s.namespaceIndexes[s.namespace]
|
||||
if !ok {
|
||||
// We have no containers in the namespace
|
||||
// Return false
|
||||
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
|
||||
}
|
||||
nameIndex = nsIndex.nameIndex
|
||||
idIndex = nsIndex.idIndex
|
||||
} else {
|
||||
nameIndex = s.nameIndex
|
||||
idIndex = s.idIndex
|
||||
}
|
||||
case define.ErrNoSuchCtr, define.ErrNoSuchPod:
|
||||
return nil, errors.Wrapf(define.ErrNoSuchPod, "no pod found with name or ID %s", idOrName)
|
||||
|
||||
fullID, err := nameIndex.Get(idOrName)
|
||||
if err != nil {
|
||||
if err == registrar.ErrNameNotReserved {
|
||||
// What was passed is not a name, assume it's an ID
|
||||
fullID, err = idIndex.Get(idOrName)
|
||||
if err != nil {
|
||||
if err == truncindex.ErrNotExist {
|
||||
return nil, errors.Wrapf(define.ErrNoSuchPod, "no pod found with name or ID %s", idOrName)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
|
||||
}
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pod, ok := s.pods[fullID]
|
||||
if !ok {
|
||||
// It's a container not a pod
|
||||
return nil, errors.Wrapf(define.ErrNoSuchPod, "id or name %s is a container not a pod", idOrName)
|
||||
return nil, errors.Wrapf(define.ErrNoSuchPod, "id or name %s is a container, not a pod", idOrName)
|
||||
}
|
||||
|
||||
return pod, nil
|
||||
|
|
|
@ -60,6 +60,12 @@ func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error {
|
|||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
return r.removeStorageContainer(idOrName, force)
|
||||
}
|
||||
|
||||
// Internal function to remove the container storage without
|
||||
// locking the runtime.
|
||||
func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
|
||||
targetID, err := r.store.Lookup(idOrName)
|
||||
if err != nil {
|
||||
if err == storage.ErrLayerUnknown {
|
||||
|
|
|
@ -550,6 +550,122 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
|
|||
return cleanupErr
|
||||
}
|
||||
|
||||
// EvictContainer removes the given container partial or full ID or name, and
|
||||
// returns the full ID of the evicted container and any error encountered.
|
||||
// It should be used to remove a container when obtaining a Container struct
|
||||
// pointer has failed.
|
||||
// Running container will not be stopped.
|
||||
// If removeVolume is specified, named volumes used by the container will
|
||||
// be removed also if and only if the container is the sole user.
|
||||
func (r *Runtime) EvictContainer(ctx context.Context, idOrName string, removeVolume bool) (string, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
return r.evictContainer(ctx, idOrName, removeVolume)
|
||||
}
|
||||
|
||||
// evictContainer is the internal function to handle container eviction based
|
||||
// on its partial or full ID or name.
|
||||
// It returns the full ID of the evicted container and any error encountered.
|
||||
// This does not lock the runtime nor the container.
|
||||
// removePod is used only when removing pods. It instructs Podman to ignore
|
||||
// infra container protections, and *not* remove from the database (as pod
|
||||
// remove will handle that).
|
||||
func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVolume bool) (string, error) {
|
||||
var err error
|
||||
|
||||
if !r.valid {
|
||||
return "", define.ErrRuntimeStopped
|
||||
}
|
||||
id, err := r.state.LookupContainerID(idOrName)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "Failed to find container %q in state", idOrName)
|
||||
}
|
||||
|
||||
// Error out if the container does not exist in libpod
|
||||
exists, err := r.state.HasContainer(id)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
if !exists {
|
||||
return id, errors.Wrapf(err, "Failed to find container ID %q for eviction", id)
|
||||
}
|
||||
|
||||
// Re-create a container struct for removal purposes
|
||||
c := new(Container)
|
||||
c.config, err = r.state.GetContainerConfig(id)
|
||||
if err != nil {
|
||||
return id, errors.Wrapf(err, "failed to retrieve config for ctr ID %q", id)
|
||||
}
|
||||
c.state = new(ContainerState)
|
||||
|
||||
// We need to lock the pod before we lock the container.
|
||||
// To avoid races around removing a container and the pod it is in.
|
||||
// Don't need to do this in pod removal case - we're evicting the entire
|
||||
// pod.
|
||||
var pod *Pod
|
||||
if c.config.Pod != "" {
|
||||
pod, err = r.state.Pod(c.config.Pod)
|
||||
if err != nil {
|
||||
return id, errors.Wrapf(err, "container %s is in pod %s, but pod cannot be retrieved", c.ID(), pod.ID())
|
||||
}
|
||||
|
||||
// Lock the pod while we're removing container
|
||||
pod.lock.Lock()
|
||||
defer pod.lock.Unlock()
|
||||
if err := pod.updatePod(); err != nil {
|
||||
return id, err
|
||||
}
|
||||
|
||||
infraID := pod.state.InfraContainerID
|
||||
if c.ID() == infraID {
|
||||
return id, errors.Errorf("container %s is the infra container of pod %s and cannot be removed without removing the pod", c.ID(), pod.ID())
|
||||
}
|
||||
}
|
||||
|
||||
var cleanupErr error
|
||||
// Remove the container from the state
|
||||
if c.config.Pod != "" {
|
||||
// If we're removing the pod, the container will be evicted
|
||||
// from the state elsewhere
|
||||
if err := r.state.RemoveContainerFromPod(pod, c); err != nil {
|
||||
cleanupErr = err
|
||||
}
|
||||
} else {
|
||||
if err := r.state.RemoveContainer(c); err != nil {
|
||||
cleanupErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// Unmount container mount points
|
||||
for _, mount := range c.config.Mounts {
|
||||
Unmount(mount)
|
||||
}
|
||||
|
||||
// Remove container from c/storage
|
||||
if err := r.removeStorageContainer(id, true); err != nil {
|
||||
if cleanupErr == nil {
|
||||
cleanupErr = err
|
||||
}
|
||||
}
|
||||
|
||||
if !removeVolume {
|
||||
return id, cleanupErr
|
||||
}
|
||||
|
||||
for _, v := range c.config.NamedVolumes {
|
||||
if volume, err := r.state.Volume(v.Name); err == nil {
|
||||
if !volume.IsCtrSpecific() {
|
||||
continue
|
||||
}
|
||||
if err := r.removeVolume(ctx, volume, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
|
||||
logrus.Errorf("cleanup volume (%s): %v", v, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return id, cleanupErr
|
||||
}
|
||||
|
||||
// GetContainer retrieves a container by its ID
|
||||
func (r *Runtime) GetContainer(id string) (*Container, error) {
|
||||
r.lock.RLock()
|
||||
|
|
|
@ -58,6 +58,9 @@ type State interface {
|
|||
// If the container is not in the set namespace, an error will be
|
||||
// returned.
|
||||
Container(id string) (*Container, error)
|
||||
// Return a container ID from the database by full or partial ID or full
|
||||
// name.
|
||||
LookupContainerID(idOrName string) (string, error)
|
||||
// Return a container from the database by full or partial ID or full
|
||||
// name.
|
||||
// Containers not in the set namespace will be ignored.
|
||||
|
@ -98,6 +101,9 @@ type State interface {
|
|||
// returned.
|
||||
AllContainers() ([]*Container, error)
|
||||
|
||||
// Return a container config from the database by full ID
|
||||
GetContainerConfig(id string) (*ContainerConfig, error)
|
||||
|
||||
// PLEASE READ FULL DESCRIPTION BEFORE USING.
|
||||
// Rewrite a container's configuration.
|
||||
// This function breaks libpod's normal prohibition on a read-only
|
||||
|
|
|
@ -452,6 +452,9 @@ func TestLookupContainerWithEmptyIDFails(t *testing.T) {
|
|||
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
|
||||
_, err := state.LookupContainer("")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = state.LookupContainerID("")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -459,6 +462,9 @@ func TestLookupNonexistentContainerFails(t *testing.T) {
|
|||
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
|
||||
_, err := state.LookupContainer("does not exist")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = state.LookupContainerID("does not exist")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -472,8 +478,11 @@ func TestLookupContainerByFullID(t *testing.T) {
|
|||
|
||||
retrievedCtr, err := state.LookupContainer(testCtr.ID())
|
||||
assert.NoError(t, err)
|
||||
|
||||
testContainersEqual(t, retrievedCtr, testCtr, true)
|
||||
|
||||
retrievedID, err := state.LookupContainerID(testCtr.ID())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrievedID, testCtr.ID())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -487,8 +496,11 @@ func TestLookupContainerByUniquePartialID(t *testing.T) {
|
|||
|
||||
retrievedCtr, err := state.LookupContainer(testCtr.ID()[0:8])
|
||||
assert.NoError(t, err)
|
||||
|
||||
testContainersEqual(t, retrievedCtr, testCtr, true)
|
||||
|
||||
retrievedID, err := state.LookupContainerID(testCtr.ID()[0:8])
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrievedID, testCtr.ID())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -507,6 +519,9 @@ func TestLookupContainerByNonUniquePartialIDFails(t *testing.T) {
|
|||
|
||||
_, err = state.LookupContainer(testCtr1.ID()[0:8])
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = state.LookupContainerID(testCtr1.ID()[0:8])
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -520,8 +535,11 @@ func TestLookupContainerByName(t *testing.T) {
|
|||
|
||||
retrievedCtr, err := state.LookupContainer(testCtr.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
testContainersEqual(t, retrievedCtr, testCtr, true)
|
||||
|
||||
retrievedID, err := state.LookupContainerID(testCtr.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, retrievedID, testCtr.ID())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -535,6 +553,9 @@ func TestLookupCtrByPodNameFails(t *testing.T) {
|
|||
|
||||
_, err = state.LookupContainer(testPod.Name())
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = state.LookupContainerID(testPod.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -548,6 +569,9 @@ func TestLookupCtrByPodIDFails(t *testing.T) {
|
|||
|
||||
_, err = state.LookupContainer(testPod.ID())
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = state.LookupContainerID(testPod.ID())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -565,8 +589,11 @@ func TestLookupCtrInSameNamespaceSucceeds(t *testing.T) {
|
|||
|
||||
ctr, err := state.LookupContainer(testCtr.ID())
|
||||
assert.NoError(t, err)
|
||||
|
||||
testContainersEqual(t, ctr, testCtr, true)
|
||||
|
||||
ctrID, err := state.LookupContainerID(testCtr.ID())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ctrID, testCtr.ID())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -584,6 +611,9 @@ func TestLookupCtrInDifferentNamespaceFails(t *testing.T) {
|
|||
|
||||
_, err = state.LookupContainer(testCtr.ID())
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = state.LookupContainerID(testCtr.ID())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -606,8 +636,11 @@ func TestLookupContainerMatchInDifferentNamespaceSucceeds(t *testing.T) {
|
|||
|
||||
ctr, err := state.LookupContainer("000")
|
||||
assert.NoError(t, err)
|
||||
|
||||
testContainersEqual(t, ctr, testCtr2, true)
|
||||
|
||||
ctrID, err := state.LookupContainerID("000")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ctrID, testCtr2.ID())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3599,3 +3632,30 @@ func TestSaveAndUpdatePodSameNamespace(t *testing.T) {
|
|||
testPodsEqual(t, testPod, statePod, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetContainerConfigSucceeds(t *testing.T) {
|
||||
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
|
||||
testCtr, err := getTestCtr1(manager)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = state.AddContainer(testCtr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ctrCfg, err := state.GetContainerConfig(testCtr.ID())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ctrCfg, testCtr.Config())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetContainerConfigEmptyIDFails(t *testing.T) {
|
||||
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
|
||||
_, err := state.GetContainerConfig("")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
func TestGetContainerConfigNonExistentIDFails(t *testing.T) {
|
||||
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
|
||||
_, err := state.GetContainerConfig("does not exist")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package libpod
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/pkg/cgroups"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// systemdSliceFromPath makes a new systemd slice under the given parent with
|
||||
|
@ -107,3 +109,14 @@ func LabelVolumePath(path string, shared bool) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmount umounts a target directory
|
||||
func Unmount(mount string) {
|
||||
if err := unix.Unmount(mount, unix.MNT_DETACH); err != nil {
|
||||
if err != syscall.EINVAL {
|
||||
logrus.Warnf("failed to unmount %s : %v", mount, err)
|
||||
} else {
|
||||
logrus.Debugf("failed to unmount %s : %v", mount, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,3 +28,7 @@ func assembleSystemdCgroupName(baseSlice, newSlice string) (string, error) {
|
|||
func LabelVolumePath(path string, shared bool) error {
|
||||
return define.ErrNotImplemented
|
||||
}
|
||||
|
||||
func Unmount(mount string) error {
|
||||
return define.ErrNotImplemented
|
||||
}
|
||||
|
|
|
@ -205,7 +205,22 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
|
|||
|
||||
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
|
||||
if err != nil {
|
||||
return ok, failures, err
|
||||
// Failed to get containers. If force is specified, get the containers ID
|
||||
// and evict them
|
||||
if !cli.Force {
|
||||
return ok, failures, err
|
||||
}
|
||||
|
||||
for _, ctr := range cli.InputArgs {
|
||||
logrus.Debugf("Evicting container %q", ctr)
|
||||
id, err := r.EvictContainer(ctx, ctr, cli.Volumes)
|
||||
if err != nil {
|
||||
failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id)
|
||||
continue
|
||||
}
|
||||
ok = append(ok, id)
|
||||
}
|
||||
return ok, failures, nil
|
||||
}
|
||||
|
||||
pool := shared.NewPool("rm", maxWorkers, len(ctrs))
|
||||
|
|
|
@ -321,16 +321,31 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa
|
|||
|
||||
// RemoveContainer removes container(s) based on varlink inputs.
|
||||
func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) {
|
||||
ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs)
|
||||
if err != nil {
|
||||
return nil, nil, TranslateError(err)
|
||||
}
|
||||
|
||||
var (
|
||||
ok = []string{}
|
||||
failures = map[string]error{}
|
||||
)
|
||||
|
||||
ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs)
|
||||
if err != nil {
|
||||
// Failed to get containers. If force is specified, get the containers ID
|
||||
// and evict them
|
||||
if !cli.Force {
|
||||
return nil, nil, TranslateError(err)
|
||||
}
|
||||
|
||||
for _, ctr := range cli.InputArgs {
|
||||
logrus.Debugf("Evicting container %q", ctr)
|
||||
id, err := iopodman.EvictContainer().Call(r.Conn, ctr, cli.Volumes)
|
||||
if err != nil {
|
||||
failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id)
|
||||
continue
|
||||
}
|
||||
ok = append(ok, string(id))
|
||||
}
|
||||
return ok, failures, nil
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
_, err := iopodman.RemoveContainer().Call(r.Conn, id, cli.Force, cli.Volumes)
|
||||
if err != nil {
|
||||
|
|
|
@ -508,7 +508,16 @@ func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, forc
|
|||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
return call.ReplyRemoveContainer(ctr.ID())
|
||||
}
|
||||
|
||||
// EvictContainer ...
|
||||
func (i *LibpodAPI) EvictContainer(call iopodman.VarlinkCall, name string, removeVolumes bool) error {
|
||||
ctx := getContext()
|
||||
id, err := i.Runtime.EvictContainer(ctx, name, removeVolumes)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
return call.ReplyEvictContainer(id)
|
||||
}
|
||||
|
||||
// DeleteStoppedContainers ...
|
||||
|
|
Loading…
Reference in a new issue