diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container.go | 4 | ||||
-rw-r--r-- | libpod/container_api.go | 6 | ||||
-rw-r--r-- | libpod/container_inspect.go | 180 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 2 | ||||
-rw-r--r-- | libpod/errors.go | 3 | ||||
-rw-r--r-- | libpod/oci.go | 12 | ||||
-rw-r--r-- | libpod/oci_linux.go | 20 | ||||
-rw-r--r-- | libpod/options.go | 2 | ||||
-rw-r--r-- | libpod/pod.go | 6 | ||||
-rw-r--r-- | libpod/runtime.go | 32 | ||||
-rw-r--r-- | libpod/runtime_cstorage.go | 118 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 15 | ||||
-rw-r--r-- | libpod/volume.go | 1 |
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"` |