summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container.go4
-rw-r--r--libpod/container_api.go6
-rw-r--r--libpod/container_inspect.go180
-rw-r--r--libpod/container_internal_linux.go2
-rw-r--r--libpod/errors.go3
-rw-r--r--libpod/oci.go12
-rw-r--r--libpod/oci_linux.go20
-rw-r--r--libpod/options.go2
-rw-r--r--libpod/pod.go6
-rw-r--r--libpod/runtime.go32
-rw-r--r--libpod/runtime_cstorage.go118
-rw-r--r--libpod/runtime_ctr.go15
-rw-r--r--libpod/volume.go1
13 files changed, 335 insertions, 66 deletions
diff --git a/libpod/container.go b/libpod/container.go
index c8ab42fc3..68c4cd6b0 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -135,7 +135,6 @@ const (
// assume that their callers handled this requirement. Generally speaking, if a
// function takes the container lock and accesses any part of state, it should
// syncContainer() immediately after locking.
-// ffjson: skip
type Container struct {
config *ContainerConfig
@@ -161,7 +160,6 @@ type Container struct {
// ContainerState contains the current state of the container
// It is stored on disk in a tmpfs and recreated on reboot
-// easyjson:json
type ContainerState struct {
// The current state of the running container
State ContainerStatus `json:"state"`
@@ -222,7 +220,6 @@ type ContainerState struct {
}
// ExecSession contains information on an active exec session
-// easyjson:json
type ExecSession struct {
ID string `json:"id"`
Command []string `json:"command"`
@@ -232,7 +229,6 @@ type ExecSession struct {
// ContainerConfig contains all information that was used to create the
// container. It may not be changed once created.
// It is stored, read-only, on disk
-// easyjson:json
type ContainerConfig struct {
Spec *spec.Spec `json:"spec"`
ID string `json:"id"`
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 1894780de..0e877d04e 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -180,7 +180,7 @@ func (c *Container) StopWithTimeout(timeout uint) error {
if c.state.State == ContainerStateConfigured ||
c.state.State == ContainerStateUnknown ||
c.state.State == ContainerStatePaused {
- return errors.Wrapf(ErrCtrStateInvalid, "can only stop created, running, or stopped containers. %s in state %s", c.ID(), c.state.State.String())
+ return errors.Wrapf(ErrCtrStateInvalid, "can only stop created, running, or stopped containers. %s is in state %s", c.ID(), c.state.State.String())
}
if c.state.State == ContainerStateStopped ||
@@ -203,7 +203,7 @@ func (c *Container) Kill(signal uint) error {
}
if c.state.State != ContainerStateRunning {
- return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers")
+ return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers. %s is in state %s", c.ID(), c.state.State.String())
}
defer c.newContainerEvent(events.Kill)
@@ -241,7 +241,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
// TODO can probably relax this once we track exec sessions
if conState != ContainerStateRunning {
- return errors.Errorf("cannot exec into container that is not running")
+ return errors.Wrapf(ErrCtrStateInvalid, "cannot exec into container that is not running")
}
if privileged || c.config.Privileged {
capList = caps.GetAllCapabilities()
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index 8e34e7088..0a62ceb7c 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -1,12 +1,11 @@
package libpod
import (
- "strings"
"time"
"github.com/containers/libpod/libpod/driver"
"github.com/cri-o/ocicni/pkg/ocicni"
- specs "github.com/opencontainers/runtime-spec/specs-go"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -29,6 +28,7 @@ type InspectContainerData struct {
HostnamePath string `json:"HostnamePath"`
HostsPath string `json:"HostsPath"`
StaticDir string `json:"StaticDir"`
+ OCIConfigPath string `json:"OCIConfigPath,omitempty"`
LogPath string `json:"LogPath"`
ConmonPidFile string `json:"ConmonPidFile"`
Name string `json:"Name"`
@@ -43,7 +43,7 @@ type InspectContainerData struct {
GraphDriver *driver.Data `json:"GraphDriver"`
SizeRw int64 `json:"SizeRw,omitempty"`
SizeRootFs int64 `json:"SizeRootFs,omitempty"`
- Mounts []specs.Mount `json:"Mounts"`
+ Mounts []*InspectMount `json:"Mounts"`
Dependencies []string `json:"Dependencies"`
NetworkSettings *InspectNetworkSettings `json:"NetworkSettings"` //TODO
ExitCommand []string `json:"ExitCommand"`
@@ -51,6 +51,35 @@ type InspectContainerData struct {
IsInfra bool `json:"IsInfra"`
}
+// InspectMount provides a record of a single mount in a container. It contains
+// fields for both named and normal volumes. Only user-specified volumes will be
+// included, and tmpfs volumes are not included even if the user specified them.
+type InspectMount struct {
+ // Whether the mount is a volume or bind mount. Allowed values are
+ // "volume" and "bind".
+ Type string `json:"Type"`
+ // The name of the volume. Empty for bind mounts.
+ Name string `json:"Name,omptempty"`
+ // The source directory for the volume.
+ Src string `json:"Source"`
+ // The destination directory for the volume. Specified as a path within
+ // the container, as it would be passed into the OCI runtime.
+ Dst string `json:"Destination"`
+ // The driver used for the named volume. Empty for bind mounts.
+ Driver string `json:"Driver"`
+ // Contains SELinux :z/:Z mount options. Unclear what, if anything, else
+ // goes in here.
+ Mode string `json:"Mode"`
+ // All remaining mount options. Additional data, not present in the
+ // original output.
+ Options []string `json:"Options"`
+ // Whether the volume is read-write
+ RW bool `json:"RW"`
+ // Mount propagation for the mount. Can be empty if not specified, but
+ // is always printed - no omitempty.
+ Propagation string `json:"Propagation"`
+}
+
// InspectContainerState provides a detailed record of a container's current
// state. It is returned as part of InspectContainerData.
// As with InspectContainerData, many portions of this struct are matched to
@@ -148,34 +177,24 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data)
execIDs = append(execIDs, id)
}
- if c.state.BindMounts == nil {
- c.state.BindMounts = make(map[string]string)
- }
-
resolvPath := ""
- if getPath, ok := c.state.BindMounts["/etc/resolv.conf"]; ok {
- resolvPath = getPath
- }
-
hostsPath := ""
- if getPath, ok := c.state.BindMounts["/etc/hosts"]; ok {
- hostsPath = getPath
- }
-
hostnamePath := ""
- if getPath, ok := c.state.BindMounts["/etc/hostname"]; ok {
- hostnamePath = getPath
+ if c.state.BindMounts != nil {
+ if getPath, ok := c.state.BindMounts["/etc/resolv.conf"]; ok {
+ resolvPath = getPath
+ }
+ if getPath, ok := c.state.BindMounts["/etc/hosts"]; ok {
+ hostsPath = getPath
+ }
+ if getPath, ok := c.state.BindMounts["/etc/hostname"]; ok {
+ hostnamePath = getPath
+ }
}
- var mounts []specs.Mount
- for i, mnt := range spec.Mounts {
- mounts = append(mounts, mnt)
- // We only want to show the name of the named volume in the inspect
- // output, so split the path and get the name out of it.
- if strings.Contains(mnt.Source, c.runtime.config.VolumePath) {
- split := strings.Split(mnt.Source[len(c.runtime.config.VolumePath)+1:], "/")
- mounts[i].Source = split[0]
- }
+ mounts, err := c.getInspectMounts()
+ if err != nil {
+ return nil, err
}
data := &InspectContainerData{
@@ -242,8 +261,12 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data)
IsInfra: c.IsInfra(),
}
+ if c.state.ConfigPath != "" {
+ data.OCIConfigPath = c.state.ConfigPath
+ }
+
if c.config.HealthCheckConfig != nil {
- // This container has a healthcheck defined in it; we need to add it's state
+ // This container has a healthcheck defined in it; we need to add it's state
healthCheckState, err := c.GetHealthCheckLog()
if err != nil {
// An error here is not considered fatal; no health state will be displayed
@@ -275,3 +298,106 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data)
}
return data, nil
}
+
+// Get inspect-formatted mounts list.
+// Only includes user-specified mounts. Only includes bind mounts and named
+// volumes, not tmpfs volumes.
+func (c *Container) getInspectMounts() ([]*InspectMount, error) {
+ inspectMounts := []*InspectMount{}
+
+ // No mounts, return early
+ if len(c.config.UserVolumes) == 0 {
+ return inspectMounts, nil
+ }
+
+ // We need to parse all named volumes and mounts into maps, so we don't
+ // end up with repeated lookups for each user volume.
+ // Map destination to struct, as destination is what is stored in
+ // UserVolumes.
+ namedVolumes := make(map[string]*ContainerNamedVolume)
+ mounts := make(map[string]spec.Mount)
+ for _, namedVol := range c.config.NamedVolumes {
+ namedVolumes[namedVol.Dest] = namedVol
+ }
+ for _, mount := range c.config.Spec.Mounts {
+ mounts[mount.Destination] = mount
+ }
+
+ for _, vol := range c.config.UserVolumes {
+ // We need to look up the volumes.
+ // First: is it a named volume?
+ if volume, ok := namedVolumes[vol]; ok {
+ mountStruct := new(InspectMount)
+ mountStruct.Type = "volume"
+ mountStruct.Dst = volume.Dest
+ mountStruct.Name = volume.Name
+
+ // For src and driver, we need to look up the named
+ // volume.
+ volFromDB, err := c.runtime.state.Volume(volume.Name)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error looking up volume %s in container %s config", volume.Name, c.ID())
+ }
+ mountStruct.Driver = volFromDB.Driver()
+ mountStruct.Src = volFromDB.MountPoint()
+
+ parseMountOptionsForInspect(volume.Options, mountStruct)
+
+ inspectMounts = append(inspectMounts, mountStruct)
+ } else if mount, ok := mounts[vol]; ok {
+ // It's a mount.
+ // Is it a tmpfs? If so, discard.
+ if mount.Type == "tmpfs" {
+ continue
+ }
+
+ mountStruct := new(InspectMount)
+ mountStruct.Type = "bind"
+ mountStruct.Src = mount.Source
+ mountStruct.Dst = mount.Destination
+
+ parseMountOptionsForInspect(mount.Options, mountStruct)
+
+ inspectMounts = append(inspectMounts, mountStruct)
+ }
+ // We couldn't find a mount. Log a warning.
+ logrus.Warnf("Could not find mount at destination %q when building inspect output for container %s", vol, c.ID())
+ }
+
+ return inspectMounts, nil
+}
+
+// Parse mount options so we can populate them in the mount structure.
+// The mount passed in will be modified.
+func parseMountOptionsForInspect(options []string, mount *InspectMount) {
+ isRW := true
+ mountProp := ""
+ zZ := ""
+ otherOpts := []string{}
+
+ // Some of these may be overwritten if the user passes us garbage opts
+ // (for example, [ro,rw])
+ // We catch these on the Podman side, so not a problem there, but other
+ // users of libpod who do not properly validate mount options may see
+ // this.
+ // Not really worth dealing with on our end - garbage in, garbage out.
+ for _, opt := range options {
+ switch opt {
+ case "ro":
+ isRW = false
+ case "rw":
+ // Do nothing, silently discard
+ case "shared", "slave", "private", "rshared", "rslave", "rprivate":
+ mountProp = opt
+ case "z", "Z":
+ zZ = opt
+ default:
+ otherOpts = append(otherOpts, opt)
+ }
+ }
+
+ mount.RW = isRW
+ mount.Propagation = mountProp
+ mount.Mode = zZ
+ mount.Options = otherOpts
+}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index a88b0a6d5..55cc5089b 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1000,7 +1000,7 @@ func (c *Container) generateResolvConf() (string, error) {
nameservers := resolvconf.GetNameservers(resolv.Content)
// slirp4netns has a built in DNS server.
if c.config.NetMode.IsSlirp4netns() {
- nameservers = append(nameservers, "10.0.2.3")
+ nameservers = append([]string{"10.0.2.3"}, nameservers...)
}
if len(c.config.DNSServer) > 0 {
// We store DNS servers as net.IP, so need to convert to string
diff --git a/libpod/errors.go b/libpod/errors.go
index dd82d0796..cca0935ec 100644
--- a/libpod/errors.go
+++ b/libpod/errors.go
@@ -96,4 +96,7 @@ var (
// ErrOSNotSupported indicates the function is not available on the particular
// OS.
ErrOSNotSupported = errors.New("No support for this OS yet")
+
+ // ErrOCIRuntime indicates an error from the OCI runtime
+ ErrOCIRuntime = errors.New("OCI runtime error")
)
diff --git a/libpod/oci.go b/libpod/oci.go
index 7138108c5..dcb72fc1b 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -58,6 +58,7 @@ type OCIRuntime struct {
logSizeMax int64
noPivot bool
reservePorts bool
+ supportsJSON bool
}
// syncInfo is used to return data from monitor process to daemon
@@ -66,8 +67,16 @@ type syncInfo struct {
Message string `json:"message,omitempty"`
}
+// ociError is used to parse the OCI runtime JSON log. It is not part of the
+// OCI runtime specifications, it follows what runc does
+type ociError struct {
+ Level string `json:"level,omitempty"`
+ Time string `json:"time,omitempty"`
+ Msg string `json:"msg,omitempty"`
+}
+
// Make a new OCI runtime with provided options
-func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool) (*OCIRuntime, error) {
+func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool, supportsJSON bool) (*OCIRuntime, error) {
runtime := new(OCIRuntime)
runtime.name = oruntime.Name
runtime.path = oruntime.Paths[0]
@@ -78,6 +87,7 @@ func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []strin
runtime.logSizeMax = logSizeMax
runtime.noPivot = noPivotRoot
runtime.reservePorts = reservePorts
+ runtime.supportsJSON = supportsJSON
runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits")
runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket")
diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go
index 7c1c18052..9bbefdb06 100644
--- a/libpod/oci_linux.go
+++ b/libpod/oci_linux.go
@@ -6,6 +6,7 @@ import (
"bufio"
"bytes"
"fmt"
+ "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -208,6 +209,9 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res
defer parentPipe.Close()
defer parentStartPipe.Close()
+ ociLog := filepath.Join(ctr.state.RunDir, "oci-log")
+ logLevel := logrus.GetLevel()
+
args := []string{}
if r.cgroupManager == SystemdCgroupsManager {
args = append(args, "-s")
@@ -219,6 +223,9 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res
args = append(args, "-b", ctr.bundlePath())
args = append(args, "-p", filepath.Join(ctr.state.RunDir, "pidfile"))
args = append(args, "--exit-dir", r.exitsDir)
+ if logLevel != logrus.DebugLevel && r.supportsJSON {
+ args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLog))
+ }
if ctr.config.ConmonPidFile != "" {
args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile)
}
@@ -248,7 +255,6 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res
args = append(args, "--no-pivot")
}
- logLevel := logrus.GetLevel()
args = append(args, "--log-level", logLevel.String())
if logLevel == logrus.DebugLevel {
@@ -417,8 +423,18 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res
}
logrus.Debugf("Received container pid: %d", ss.si.Pid)
if ss.si.Pid == -1 {
+ if r.supportsJSON {
+ data, err := ioutil.ReadFile(ociLog)
+ if err == nil {
+ var ociErr ociError
+ if err := json.Unmarshal(data, &ociErr); err == nil {
+ return errors.Wrapf(ErrOCIRuntime, "%s", strings.Trim(ociErr.Msg, "\n"))
+ }
+ }
+ }
+ // If we failed to parse the JSON errors, then print the output as it is
if ss.si.Message != "" {
- return errors.Wrapf(ErrInternal, "container create failed: %s", ss.si.Message)
+ return errors.Wrapf(ErrOCIRuntime, "%s", ss.si.Message)
}
return errors.Wrapf(ErrInternal, "container create failed")
}
diff --git a/libpod/options.go b/libpod/options.go
index 20aa51981..cdac09654 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1127,6 +1127,8 @@ func WithGroups(groups []string) CtrCreateOption {
// These are not added to the container's spec, but will instead be used during
// commit to populate the volumes of the new image, and to trigger some OCI
// hooks that are only added if volume mounts are present.
+// Furthermore, they are used in the output of inspect, to filter volumes -
+// only volumes included in this list will be included in the output.
// Unless explicitly set, committed images will have no volumes.
// The given volumes slice must not be nil.
func WithUserVolumes(volumes []string) CtrCreateOption {
diff --git a/libpod/pod.go b/libpod/pod.go
index 4ce697402..c319c449f 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -18,7 +18,6 @@ import (
// assume their callers handled this requirement. Generally speaking, if a
// function takes the pod lock and accesses any part of state, it should
// updatePod() immediately after locking.
-// ffjson: skip
// Pod represents a group of containers that may share namespaces
type Pod struct {
config *PodConfig
@@ -30,7 +29,6 @@ type Pod struct {
}
// PodConfig represents a pod's static configuration
-// easyjson:json
type PodConfig struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -66,7 +64,6 @@ type PodConfig struct {
}
// podState represents a pod's state
-// easyjson:json
type podState struct {
// CgroupPath is the path to the pod's CGroup
CgroupPath string `json:"cgroupPath"`
@@ -77,7 +74,6 @@ type podState struct {
// PodInspect represents the data we want to display for
// podman pod inspect
-// easyjson:json
type PodInspect struct {
Config *PodConfig
State *PodInspectState
@@ -85,14 +81,12 @@ type PodInspect struct {
}
// PodInspectState contains inspect data on the pod's state
-// easyjson:json
type PodInspectState struct {
CgroupPath string `json:"cgroupPath"`
InfraContainerID string `json:"infraContainerID"`
}
// PodContainerInfo keeps information on a container in a pod
-// easyjson:json
type PodContainerInfo struct {
ID string `json:"id"`
State string `json:"state"`
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 098607b63..2c50fce85 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -42,11 +42,20 @@ const (
SQLiteStateStore RuntimeStateStore = iota
// BoltDBStateStore is a state backed by a BoltDB database
BoltDBStateStore RuntimeStateStore = iota
+)
+
+var (
+ // InstallPrefix is the prefix where podman will be installed.
+ // It can be overridden at build time.
+ installPrefix = "/usr/local"
+ // EtcDir is the sysconfdir where podman should look for system config files.
+ // It can be overridden at build time.
+ etcDir = "/etc"
// SeccompDefaultPath defines the default seccomp path
- SeccompDefaultPath = "/usr/share/containers/seccomp.json"
+ SeccompDefaultPath = installPrefix + "/share/containers/seccomp.json"
// SeccompOverridePath if this exists it overrides the default seccomp path
- SeccompOverridePath = "/etc/crio/seccomp.json"
+ SeccompOverridePath = etcDir + "/crio/seccomp.json"
// ConfigPath is the path to the libpod configuration file
// This file is loaded to replace the builtin default config before
@@ -54,11 +63,11 @@ const (
// If it is not present, the builtin default config is used instead
// This path can be overridden when the runtime is created by using
// NewRuntimeFromConfig() instead of NewRuntime()
- ConfigPath = "/usr/share/containers/libpod.conf"
+ ConfigPath = installPrefix + "/share/containers/libpod.conf"
// OverrideConfigPath is the path to an override for the default libpod
// configuration file. If OverrideConfigPath exists, it will be used in
// place of the configuration file pointed to by ConfigPath.
- OverrideConfigPath = "/etc/containers/libpod.conf"
+ OverrideConfigPath = etcDir + "/containers/libpod.conf"
// DefaultInfraImage to use for infra container
DefaultInfraImage = "k8s.gcr.io/pause:3.1"
@@ -151,6 +160,8 @@ type RuntimeConfig struct {
OCIRuntime string `toml:"runtime"`
// OCIRuntimes are the set of configured OCI runtimes (default is runc)
OCIRuntimes map[string][]string `toml:"runtimes"`
+ // RuntimeSupportsJSON is the list of the OCI runtimes that support --format=json
+ RuntimeSupportsJSON []string `toml:"runtime_supports_json"`
// RuntimePath is the path to OCI runtime binary for launching
// containers.
// The first path pointing to a valid file will be used
@@ -298,7 +309,7 @@ func defaultRuntimeConfig() (RuntimeConfig, error) {
TmpDir: "",
MaxLogSize: -1,
NoPivotRoot: false,
- CNIConfigDir: "/etc/cni/net.d/",
+ CNIConfigDir: etcDir + "/cni/net.d/",
CNIPluginDir: []string{"/usr/libexec/cni", "/usr/lib/cni", "/usr/local/lib/cni", "/opt/cni/bin"},
InfraCommand: DefaultInfraCommand,
InfraImage: DefaultInfraImage,
@@ -830,12 +841,21 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
}
}
+ supportsJSON := false
+ for _, r := range runtime.config.RuntimeSupportsJSON {
+ if r == runtime.config.OCIRuntime {
+ supportsJSON = true
+ break
+ }
+ }
+
// Make an OCI runtime to perform container operations
ociRuntime, err := newOCIRuntime(runtime.ociRuntimePath,
runtime.conmonPath, runtime.config.ConmonEnvVars,
runtime.config.CgroupManager, runtime.config.TmpDir,
runtime.config.MaxLogSize, runtime.config.NoPivotRoot,
- runtime.config.EnablePortReservation)
+ runtime.config.EnablePortReservation,
+ supportsJSON)
if err != nil {
return err
}
diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go
new file mode 100644
index 000000000..569f63322
--- /dev/null
+++ b/libpod/runtime_cstorage.go
@@ -0,0 +1,118 @@
+package libpod
+
+import (
+ "github.com/containers/storage"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// StorageContainer represents a container present in c/storage but not in
+// libpod.
+type StorageContainer struct {
+ ID string
+ Names []string
+ PresentInLibpod bool
+}
+
+// ListStorageContainers lists all containers visible to c/storage.
+func (r *Runtime) ListStorageContainers() ([]*StorageContainer, error) {
+ r.lock.RLock()
+ defer r.lock.RUnlock()
+
+ 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
+
+ // Look up if container is in state
+ hasCtr, err := r.state.HasContainer(ctr.ID)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error looking up container %s in state", ctr.ID)
+ }
+
+ storageCtr.PresentInLibpod = hasCtr
+
+ finalCtrs = append(finalCtrs, storageCtr)
+ }
+
+ return finalCtrs, nil
+}
+
+// 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 {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ targetID, err := r.store.Lookup(idOrName)
+ if err != nil {
+ if err == storage.ErrLayerUnknown {
+ return errors.Wrapf(ErrNoSuchCtr, "no container with ID or name %q found", idOrName)
+ }
+ return errors.Wrapf(err, "error looking up container %q", idOrName)
+ }
+
+ // 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 err == storage.ErrContainerUnknown {
+ return errors.Wrapf(ErrNoSuchCtr, "%q does not refer to a container", idOrName)
+ }
+ return errors.Wrapf(err, "error retrieving container %q", idOrName)
+ }
+
+ // Error out if the container exists in libpod
+ exists, err := r.state.HasContainer(ctr.ID)
+ if err != nil {
+ return err
+ }
+ if exists {
+ return errors.Wrapf(ErrCtrExists, "refusing to remove %q as it exists in libpod as container %s", idOrName, ctr.ID)
+ }
+
+ if !force {
+ timesMounted, err := r.store.Mounted(ctr.ID)
+ if err != nil {
+ if err == storage.ErrContainerUnknown {
+ // Container was removed from under us.
+ // It's gone, so don't bother erroring.
+ logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ return nil
+ }
+ return errors.Wrapf(err, "error looking up container %q mounts", idOrName)
+ }
+ if timesMounted > 0 {
+ return errors.Wrapf(ErrCtrStateInvalid, "container %q is mounted and cannot be removed without using force", idOrName)
+ }
+ } else {
+ if _, err := r.store.Unmount(ctr.ID, true); err != nil {
+ if err == storage.ErrContainerUnknown {
+ // Container again gone, no error
+ logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ return nil
+ }
+ return errors.Wrapf(err, "error unmounting container %q", idOrName)
+ }
+ }
+
+ if err := r.store.DeleteContainer(ctr.ID); err != nil {
+ if err == storage.ErrContainerUnknown {
+ // Container again gone, no error
+ logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ return nil
+ }
+ return errors.Wrapf(err, "error removing storage for container %q", idOrName)
+ }
+
+ return nil
+}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index cf1f5701d..0871b83a7 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -9,9 +9,7 @@ import (
"time"
"github.com/containers/libpod/libpod/events"
- "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/storage"
"github.com/containers/storage/pkg/stringid"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
@@ -614,16 +612,3 @@ func (r *Runtime) GetLatestContainer() (*Container, error) {
}
return ctrs[lastCreatedIndex], nil
}
-
-// RemoveContainersFromStorage attempt to remove containers from storage that do not exist in libpod database
-func (r *Runtime) RemoveContainersFromStorage(ctrs []string) {
- for _, i := range ctrs {
- // if the container does not exist in database, attempt to remove it from storage
- if _, err := r.LookupContainer(i); err != nil && errors.Cause(err) == image.ErrNoSuchCtr {
- r.storageService.UnmountContainerImage(i, true)
- if err := r.storageService.DeleteContainer(i); err != nil && errors.Cause(err) != storage.ErrContainerUnknown {
- logrus.Errorf("Failed to remove container %q from storage: %s", i, err)
- }
- }
- }
-}
diff --git a/libpod/volume.go b/libpod/volume.go
index 0b37d44ef..9ed2ff087 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -10,7 +10,6 @@ type Volume struct {
}
// VolumeConfig holds the volume's config information
-//easyjson:json
type VolumeConfig struct {
// Name of the volume
Name string `json:"name"`