summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/config/config.go58
-rw-r--r--libpod/config/default.go7
-rw-r--r--libpod/container.go14
-rw-r--r--libpod/container_api.go4
-rw-r--r--libpod/container_commit.go17
-rw-r--r--libpod/container_inspect.go42
-rw-r--r--libpod/container_internal.go37
-rw-r--r--libpod/container_internal_linux.go98
-rw-r--r--libpod/diff.go44
-rw-r--r--libpod/image/config.go8
-rw-r--r--libpod/image/image.go10
-rw-r--r--libpod/image/prune.go10
-rw-r--r--libpod/image/pull.go2
-rw-r--r--libpod/networking_linux.go206
-rw-r--r--libpod/oci_conmon_linux.go57
-rw-r--r--libpod/options.go33
-rw-r--r--libpod/pod_api.go2
-rw-r--r--libpod/runtime.go26
-rw-r--r--libpod/runtime_ctr.go6
-rw-r--r--libpod/runtime_img.go46
-rw-r--r--libpod/runtime_pod_linux.go30
21 files changed, 484 insertions, 273 deletions
diff --git a/libpod/config/config.go b/libpod/config/config.go
index 0e867a50e..13c128688 100644
--- a/libpod/config/config.go
+++ b/libpod/config/config.go
@@ -72,7 +72,7 @@ const (
// SetOptions contains a subset of options in a Config. It's used to indicate if
// a given option has either been set by the user or by a parsed libpod
// configuration file. If not, the corresponding option might be overwritten by
-// values from the database. This behavior guarantess backwards compat with
+// values from the database. This behavior guarantees backwards compat with
// older version of libpod and Podman.
type SetOptions struct {
// StorageConfigRunRootSet indicates if the RunRoot has been explicitly set
@@ -119,7 +119,7 @@ type Config struct {
// SetOptions contains a subset of config options. It's used to indicate if
// a given option has either been set by the user or by a parsed libpod
// configuration file. If not, the corresponding option might be
- // overwritten by values from the database. This behavior guarantess
+ // overwritten by values from the database. This behavior guarantees
// backwards compat with older version of libpod and Podman.
SetOptions
@@ -448,41 +448,50 @@ func NewConfig(userConfigPath string) (*Config, error) {
if err != nil {
return nil, errors.Wrapf(err, "error reading user config %q", userConfigPath)
}
- if err := cgroupV2Check(userConfigPath, config); err != nil {
- return nil, errors.Wrapf(err, "error rewriting configuration file %s", userConfigPath)
- }
}
// Now, check if the user can access system configs and merge them if needed.
- if configs, err := systemConfigs(); err != nil {
+ configs, err := systemConfigs()
+ if err != nil {
return nil, errors.Wrapf(err, "error finding config on system")
- } else {
- for _, path := range configs {
- systemConfig, err := readConfigFromFile(path)
- if err != nil {
- return nil, errors.Wrapf(err, "error reading system config %q", path)
- }
- // Merge the it into the config. Any unset field in config will be
- // over-written by the systemConfig.
- if err := config.mergeConfig(systemConfig); err != nil {
- return nil, errors.Wrapf(err, "error merging system config")
+ }
+
+ migrated := false
+ for _, path := range configs {
+ systemConfig, err := readConfigFromFile(path)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error reading system config %q", path)
+ }
+ // Handle CGroups v2 configuration migration.
+ // Migrate only the first config, and do it before
+ // merging.
+ if !migrated {
+ if err := cgroupV2Check(path, systemConfig); err != nil {
+ return nil, errors.Wrapf(err, "error rewriting configuration file %s", userConfigPath)
}
- logrus.Debugf("Merged system config %q: %v", path, config)
+ migrated = true
+ }
+ // Merge the it into the config. Any unset field in config will be
+ // over-written by the systemConfig.
+ if err := config.mergeConfig(systemConfig); err != nil {
+ return nil, errors.Wrapf(err, "error merging system config")
}
+ logrus.Debugf("Merged system config %q: %v", path, config)
}
// Finally, create a default config from memory and forcefully merge it into
// the config. This way we try to make sure that all fields are properly set
// and that user AND system config can partially set.
- if defaultConfig, err := defaultConfigFromMemory(); err != nil {
+ defaultConfig, err := defaultConfigFromMemory()
+ if err != nil {
return nil, errors.Wrapf(err, "error generating default config from memory")
- } else {
- // Check if we need to switch to cgroupfs and logger=file on rootless.
- defaultConfig.checkCgroupsAndLogger()
+ }
- if err := config.mergeConfig(defaultConfig); err != nil {
- return nil, errors.Wrapf(err, "error merging default config from memory")
- }
+ // Check if we need to switch to cgroupfs and logger=file on rootless.
+ defaultConfig.checkCgroupsAndLogger()
+
+ if err := config.mergeConfig(defaultConfig); err != nil {
+ return nil, errors.Wrapf(err, "error merging default config from memory")
}
// Relative paths can cause nasty bugs, because core paths we use could
@@ -564,6 +573,7 @@ func (c *Config) checkCgroupsAndLogger() {
// TODO Once runc has support for cgroups, this function should be removed.
func cgroupV2Check(configPath string, tmpConfig *Config) error {
if !tmpConfig.CgroupCheck && rootless.IsRootless() {
+ logrus.Debugf("Rewriting %s for CGroup v2 upgrade", configPath)
cgroupsV2, err := cgroups.IsCgroup2UnifiedMode()
if err != nil {
return err
diff --git a/libpod/config/default.go b/libpod/config/default.go
index 5decaeab7..c4a4efdaf 100644
--- a/libpod/config/default.go
+++ b/libpod/config/default.go
@@ -26,11 +26,12 @@ const (
// config is different for root and rootless. It also parses the storage.conf.
func defaultConfigFromMemory() (*Config, error) {
c := new(Config)
- if tmp, err := defaultTmpDir(); err != nil {
+ tmp, err := defaultTmpDir()
+ if err != nil {
return nil, err
- } else {
- c.TmpDir = tmp
}
+ c.TmpDir = tmp
+
c.EventsLogFilePath = filepath.Join(c.TmpDir, "events", "events.log")
storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID())
diff --git a/libpod/container.go b/libpod/container.go
index dcec3ee50..a046dcafc 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -135,6 +135,9 @@ type Container struct {
rootlessSlirpSyncR *os.File
rootlessSlirpSyncW *os.File
+ rootlessPortSyncR *os.File
+ rootlessPortSyncW *os.File
+
// A restored container should have the same IP address as before
// being checkpointed. If requestedIP is set it will be used instead
// of config.StaticIP.
@@ -232,6 +235,10 @@ type ContainerConfig struct {
// ID of this container's lock
LockID uint32 `json:"lockID"`
+ // CreateCommand is the full command plus arguments of the process the
+ // container has been created with.
+ CreateCommand []string `json:"CreateCommand,omitempty"`
+
// TODO consider breaking these subsections up into smaller structs
// UID/GID mappings used by the storage
@@ -372,6 +379,8 @@ type ContainerConfig struct {
CgroupParent string `json:"cgroupParent"`
// LogPath log location
LogPath string `json:"logPath"`
+ // LogTag is the tag used for logging
+ LogTag string `json:"logTag"`
// LogDriver driver for logs
LogDriver string `json:"logDriver"`
// File containing the conmon PID
@@ -719,6 +728,11 @@ func (c *Container) LogPath() string {
return c.config.LogPath
}
+// LogTag returns the tag to the container's log file
+func (c *Container) LogTag() string {
+ return c.config.LogTag
+}
+
// RestartPolicy returns the container's restart policy.
func (c *Container) RestartPolicy() string {
return c.config.RestartPolicy
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 5168dbc68..e36623529 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -183,7 +183,7 @@ func (c *Container) StopWithTimeout(timeout uint) error {
return errors.Wrapf(define.ErrCtrStateInvalid, "can only stop created or running containers. %s is in state %s", c.ID(), c.state.State.String())
}
- return c.stop(timeout, false)
+ return c.stop(timeout)
}
// Kill sends a signal to a container
@@ -715,7 +715,7 @@ func (c *Container) Refresh(ctx context.Context) error {
// Next, if the container is running, stop it
if c.state.State == define.ContainerStateRunning {
- if err := c.stop(c.config.StopTimeout, false); err != nil {
+ if err := c.stop(c.config.StopTimeout); err != nil {
return err
}
}
diff --git a/libpod/container_commit.go b/libpod/container_commit.go
index a0ba57f4f..ccc23621e 100644
--- a/libpod/container_commit.go
+++ b/libpod/container_commit.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/buildah"
"github.com/containers/buildah/util"
is "github.com/containers/image/v5/storage"
+ "github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
@@ -32,6 +33,10 @@ type ContainerCommitOptions struct {
// Commit commits the changes between a container and its image, creating a new
// image
func (c *Container) Commit(ctx context.Context, destImage string, options ContainerCommitOptions) (*image.Image, error) {
+ var (
+ imageRef types.ImageReference
+ )
+
if c.config.Rootfs != "" {
return nil, errors.Errorf("cannot commit a container that uses an exploded rootfs")
}
@@ -71,7 +76,6 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai
if err != nil {
return nil, err
}
-
if options.Author != "" {
importBuilder.SetMaintainer(options.Author)
}
@@ -191,12 +195,11 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai
if err != nil {
return nil, errors.Wrapf(err, "error resolving name %q", destImage)
}
- if len(candidates) == 0 {
- return nil, errors.Errorf("error parsing target image name %q", destImage)
- }
- imageRef, err := is.Transport.ParseStoreReference(c.runtime.store, candidates[0])
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing target image name %q", destImage)
+ if len(candidates) > 0 {
+ imageRef, err = is.Transport.ParseStoreReference(c.runtime.store, candidates[0])
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing target image name %q", destImage)
+ }
}
id, _, _, err := importBuilder.Commit(ctx, imageRef, commitOptions)
if err != nil {
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index 66aca23ed..3f4ab394f 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -107,6 +107,7 @@ type InspectContainerData struct {
OCIConfigPath string `json:"OCIConfigPath,omitempty"`
OCIRuntime string `json:"OCIRuntime,omitempty"`
LogPath string `json:"LogPath"`
+ LogTag string `json:"LogTag"`
ConmonPidFile string `json:"ConmonPidFile"`
Name string `json:"Name"`
RestartCount int32 `json:"RestartCount"`
@@ -118,7 +119,7 @@ type InspectContainerData struct {
BoundingCaps []string `json:"BoundingCaps"`
ExecIDs []string `json:"ExecIDs"`
GraphDriver *driver.Data `json:"GraphDriver"`
- SizeRw int64 `json:"SizeRw,omitempty"`
+ SizeRw *int64 `json:"SizeRw,omitempty"`
SizeRootFs int64 `json:"SizeRootFs,omitempty"`
Mounts []InspectMount `json:"Mounts"`
Dependencies []string `json:"Dependencies"`
@@ -174,6 +175,9 @@ type InspectContainerConfig struct {
StopSignal uint `json:"StopSignal"`
// Configured healthcheck for the container
Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"`
+ // CreateCommand is the full command plus arguments of the process the
+ // container has been created with.
+ CreateCommand []string `json:"CreateCommand,omitempty"`
}
// InspectContainerHostConfig holds information used when the container was
@@ -626,17 +630,9 @@ type InspectNetworkSettings struct {
MacAddress string `json:"MacAddress"`
}
-// Inspect a container for low-level information
-func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
- if !c.batched {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- if err := c.syncContainer(); err != nil {
- return nil, err
- }
- }
-
+// inspectLocked inspects a container for low-level information.
+// The caller must held c.lock.
+func (c *Container) inspectLocked(size bool) (*InspectContainerData, error) {
storeCtr, err := c.runtime.store.Container(c.ID())
if err != nil {
return nil, errors.Wrapf(err, "error getting container from store %q", c.ID())
@@ -652,6 +648,20 @@ func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
return c.getContainerInspectData(size, driverData)
}
+// Inspect a container for low-level information
+func (c *Container) Inspect(size bool) (*InspectContainerData, error) {
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if err := c.syncContainer(); err != nil {
+ return nil, err
+ }
+ }
+
+ return c.inspectLocked(size)
+}
+
func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) (*InspectContainerData, error) {
config := c.config
runtimeInfo := c.state
@@ -729,6 +739,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data)
HostsPath: hostsPath,
StaticDir: config.StaticDir,
LogPath: config.LogPath,
+ LogTag: config.LogTag,
OCIRuntime: config.OCIRuntime,
ConmonPidFile: config.ConmonPidFile,
Name: config.Name,
@@ -806,12 +817,13 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data)
if err != nil {
logrus.Errorf("error getting rootfs size %q: %v", config.ID, err)
}
+ data.SizeRootFs = rootFsSize
+
rwSize, err := c.rwSize()
if err != nil {
logrus.Errorf("error getting rw size %q: %v", config.ID, err)
}
- data.SizeRootFs = rootFsSize
- data.SizeRw = rwSize
+ data.SizeRw = &rwSize
}
return data, nil
}
@@ -947,6 +959,8 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) (*InspectCon
// leak.
ctrConfig.Healthcheck = c.config.HealthCheckConfig
+ ctrConfig.CreateCommand = c.config.CreateCommand
+
return ctrConfig, nil
}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 37801162a..46c83149a 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -84,7 +84,7 @@ func (c *Container) rootFsSize() (int64, error) {
return size + layerSize, err
}
-// rwSize Gets the size of the mutable top layer of the container.
+// rwSize gets the size of the mutable top layer of the container.
func (c *Container) rwSize() (int64, error) {
if c.config.Rootfs != "" {
var size int64
@@ -103,14 +103,16 @@ func (c *Container) rwSize() (int64, error) {
return 0, err
}
- // Get the size of the top layer by calculating the size of the diff
- // between the layer and its parent. The top layer of a container is
- // the only RW layer, all others are immutable
- layer, err := c.runtime.store.Layer(container.LayerID)
+ // The top layer of a container is
+ // the only readable/writeable layer, all others are immutable.
+ rwLayer, err := c.runtime.store.Layer(container.LayerID)
if err != nil {
return 0, err
}
- return c.runtime.store.DiffSize(layer.Parent, layer.ID)
+
+ // Get the size of the top layer by calculating the size of the diff
+ // between the layer and its parent.
+ return c.runtime.store.DiffSize(rwLayer.Parent, rwLayer.ID)
}
// bundlePath returns the path to the container's root filesystem - where the OCI spec will be
@@ -1129,9 +1131,14 @@ func (c *Container) start() error {
}
// Internal, non-locking function to stop container
-func (c *Container) stop(timeout uint, all bool) error {
+func (c *Container) stop(timeout uint) error {
logrus.Debugf("Stopping ctr %s (timeout %d)", c.ID(), timeout)
+ // If the container is running in a PID Namespace, then killing the
+ // primary pid is enough to kill the container. If it is not running in
+ // a pid namespace then the OCI Runtime needs to kill ALL processes in
+ // the containers cgroup in order to make sure the container is stopped.
+ all := !c.hasNamespace(spec.PIDNamespace)
// We can't use --all if CGroups aren't present.
// Rootless containers with CGroups v1 and NoCgroups are both cases
// where this can happen.
@@ -1188,6 +1195,7 @@ func (c *Container) pause() error {
}
if err := c.ociRuntime.PauseContainer(c); err != nil {
+ // TODO when using docker-py there is some sort of race/incompatibility here
return err
}
@@ -1205,6 +1213,7 @@ func (c *Container) unpause() error {
}
if err := c.ociRuntime.UnpauseContainer(c); err != nil {
+ // TODO when using docker-py there is some sort of race/incompatibility here
return err
}
@@ -1225,7 +1234,7 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e
if c.state.State == define.ContainerStateRunning {
conmonPID := c.state.ConmonPID
- if err := c.stop(timeout, false); err != nil {
+ if err := c.stop(timeout); err != nil {
return err
}
// Old versions of conmon have a bug where they create the exit file before
@@ -1895,3 +1904,15 @@ func (c *Container) reapExecSessions() error {
}
return lastErr
}
+
+func (c *Container) hasNamespace(namespace spec.LinuxNamespaceType) bool {
+ if c.config.Spec == nil || c.config.Spec.Linux == nil {
+ return false
+ }
+ for _, n := range c.config.Spec.Linux.Namespaces {
+ if n.Type == namespace {
+ return true
+ }
+ }
+ return false
+}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 1b0570998..6ec06943f 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -593,22 +593,68 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error)
// Get root file-system changes included in the checkpoint archive
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
+ deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files")
if !ignoreRootfs {
- rootfsDiffFile, err := os.Create(rootfsDiffPath)
- if err != nil {
- return errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath)
- }
- tarStream, err := c.runtime.GetDiffTarStream("", c.ID())
+ // To correctly track deleted files, let's go through the output of 'podman diff'
+ tarFiles, err := c.runtime.GetDiff("", c.ID())
if err != nil {
return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
}
- _, err = io.Copy(rootfsDiffFile, tarStream)
- if err != nil {
- return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
+ var rootfsIncludeFiles []string
+ var deletedFiles []string
+
+ for _, file := range tarFiles {
+ if file.Kind == archive.ChangeAdd {
+ rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
+ continue
+ }
+ if file.Kind == archive.ChangeDelete {
+ deletedFiles = append(deletedFiles, file.Path)
+ continue
+ }
+ fileName, err := os.Stat(file.Path)
+ if err != nil {
+ continue
+ }
+ if !fileName.IsDir() && file.Kind == archive.ChangeModify {
+ rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
+ continue
+ }
+ }
+
+ if len(rootfsIncludeFiles) > 0 {
+ rootfsTar, err := archive.TarWithOptions(c.state.Mountpoint, &archive.TarOptions{
+ Compression: archive.Uncompressed,
+ IncludeSourceDir: true,
+ IncludeFiles: rootfsIncludeFiles,
+ })
+ if err != nil {
+ return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
+ }
+ rootfsDiffFile, err := os.Create(rootfsDiffPath)
+ if err != nil {
+ return errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath)
+ }
+ defer rootfsDiffFile.Close()
+ _, err = io.Copy(rootfsDiffFile, rootfsTar)
+ if err != nil {
+ return err
+ }
+
+ includeFiles = append(includeFiles, "rootfs-diff.tar")
+ }
+
+ if len(deletedFiles) > 0 {
+ formatJSON, err := json.MarshalIndent(deletedFiles, "", " ")
+ if err != nil {
+ return errors.Wrapf(err, "error creating delete files list file %q", deleteFilesList)
+ }
+ if err := ioutil.WriteFile(deleteFilesList, formatJSON, 0600); err != nil {
+ return errors.Wrapf(err, "error creating delete files list file %q", deleteFilesList)
+ }
+
+ includeFiles = append(includeFiles, "deleted.files")
}
- tarStream.Close()
- rootfsDiffFile.Close()
- includeFiles = append(includeFiles, "rootfs-diff.tar")
}
input, err := archive.TarWithOptions(c.bundlePath(), &archive.TarOptions{
@@ -637,6 +683,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error)
}
os.Remove(rootfsDiffPath)
+ os.Remove(deleteFilesList)
return nil
}
@@ -941,10 +988,35 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if err != nil {
return errors.Wrapf(err, "Failed to open root file-system diff file %s", rootfsDiffPath)
}
+ defer rootfsDiffFile.Close()
if err := c.runtime.ApplyDiffTarStream(c.ID(), rootfsDiffFile); err != nil {
return errors.Wrapf(err, "Failed to apply root file-system diff file %s", rootfsDiffPath)
}
- rootfsDiffFile.Close()
+ }
+ deletedFilesPath := filepath.Join(c.bundlePath(), "deleted.files")
+ if _, err := os.Stat(deletedFilesPath); err == nil {
+ deletedFilesFile, err := os.Open(deletedFilesPath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to open deleted files file %s", deletedFilesPath)
+ }
+ defer deletedFilesFile.Close()
+
+ var deletedFiles []string
+ deletedFilesJSON, err := ioutil.ReadAll(deletedFilesFile)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to read deleted files file %s", deletedFilesPath)
+ }
+ if err := json.Unmarshal(deletedFilesJSON, &deletedFiles); err != nil {
+ return errors.Wrapf(err, "Failed to read deleted files file %s", deletedFilesPath)
+ }
+ for _, deleteFile := range deletedFiles {
+ // Using RemoveAll as deletedFiles, which is generated from 'podman diff'
+ // lists completely deleted directories as a single entry: 'D /root'.
+ err = os.RemoveAll(filepath.Join(c.state.Mountpoint, deleteFile))
+ if err != nil {
+ return errors.Wrapf(err, "Failed to delete file %s from container %s during restore", deletedFilesPath, c.ID())
+ }
+ }
}
}
@@ -965,7 +1037,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if err != nil {
logrus.Debugf("Non-fatal: removal of checkpoint directory (%s) failed: %v", c.CheckpointPath(), err)
}
- cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status", "rootfs-diff.tar"}
+ cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status", "rootfs-diff.tar", "deleted.files"}
for _, del := range cleanup {
file := filepath.Join(c.bundlePath(), del)
err = os.Remove(file)
diff --git a/libpod/diff.go b/libpod/diff.go
index 925bda927..baa4d6ad7 100644
--- a/libpod/diff.go
+++ b/libpod/diff.go
@@ -1,7 +1,6 @@
package libpod
import (
- "archive/tar"
"io"
"github.com/containers/libpod/libpod/layers"
@@ -47,49 +46,6 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
return rchanges, err
}
-// skipFileInTarAchive is an archive.TarModifierFunc function
-// which tells archive.ReplaceFileTarWrapper to skip files
-// from the tarstream
-func skipFileInTarAchive(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
- return nil, nil, nil
-}
-
-// GetDiffTarStream returns the differences between the two images, layers, or containers.
-// It is the same functionality as GetDiff() except that it returns a tarstream
-func (r *Runtime) GetDiffTarStream(from, to string) (io.ReadCloser, error) {
- toLayer, err := r.getLayerID(to)
- if err != nil {
- return nil, err
- }
- fromLayer := ""
- if from != "" {
- fromLayer, err = r.getLayerID(from)
- if err != nil {
- return nil, err
- }
- }
- rc, err := r.store.Diff(fromLayer, toLayer, nil)
- if err != nil {
- return nil, err
- }
-
- // Skip files in the tar archive which are listed
- // in containerMounts map. Just as in the GetDiff()
- // function from above
- filterMap := make(map[string]archive.TarModifierFunc)
- for key := range containerMounts {
- filterMap[key[1:]] = skipFileInTarAchive
- // In the tarstream directories always include a trailing '/'.
- // For simplicity this duplicates every entry from
- // containerMounts with a trailing '/', as containerMounts
- // does not use trailing '/' for directories.
- filterMap[key[1:]+"/"] = skipFileInTarAchive
- }
-
- filteredTarStream := archive.ReplaceFileTarWrapper(rc, filterMap)
- return filteredTarStream, nil
-}
-
// ApplyDiffTarStream applies the changes stored in 'diff' to the layer 'to'
func (r *Runtime) ApplyDiffTarStream(to string, diff io.Reader) error {
toLayer, err := r.getLayerID(to)
diff --git a/libpod/image/config.go b/libpod/image/config.go
new file mode 100644
index 000000000..bb84175a3
--- /dev/null
+++ b/libpod/image/config.go
@@ -0,0 +1,8 @@
+package image
+
+// ImageDeleteResponse is the response for removing an image from storage and containers
+// what was untagged vs actually removed
+type ImageDeleteResponse struct { //nolint
+ Untagged []string `json:"untagged"`
+ Deleted string `json:"deleted"`
+}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 108e0c5b9..84da21ce5 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -781,6 +781,7 @@ type History struct {
CreatedBy string `json:"createdBy"`
Size int64 `json:"size"`
Comment string `json:"comment"`
+ Tags []string `json:"tags"`
}
// History gets the history of an image and the IDs of images that are part of
@@ -840,14 +841,17 @@ func (i *Image) History(ctx context.Context) ([]*History, error) {
delete(topLayerMap, layer.ID)
}
}
-
- allHistory = append(allHistory, &History{
+ h := History{
ID: id,
Created: oci.History[x].Created,
CreatedBy: oci.History[x].CreatedBy,
Size: size,
Comment: oci.History[x].Comment,
- })
+ }
+ if layer != nil {
+ h.Tags = layer.Names
+ }
+ allHistory = append(allHistory, &h)
if layer != nil && layer.Parent != "" && !oci.History[x].EmptyLayer {
layer, err = i.imageruntime.store.Layer(layer.Parent)
diff --git a/libpod/image/prune.go b/libpod/image/prune.go
index f5be8ed50..3afff22af 100644
--- a/libpod/image/prune.go
+++ b/libpod/image/prune.go
@@ -116,6 +116,10 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
return nil, errors.Wrap(err, "unable to get images to prune")
}
for _, p := range pruneImages {
+ repotags, err := p.RepoTags()
+ if err != nil {
+ return nil, err
+ }
if err := p.Remove(ctx, true); err != nil {
if errors.Cause(err) == storage.ErrImageUsedByContainer {
logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err)
@@ -124,7 +128,11 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
return nil, errors.Wrap(err, "failed to prune image")
}
defer p.newImageEvent(events.Prune)
- prunedCids = append(prunedCids, p.ID())
+ nameOrID := p.ID()
+ if len(repotags) > 0 {
+ nameOrID = repotags[0]
+ }
+ prunedCids = append(prunedCids, nameOrID)
}
return prunedCids, nil
}
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index 326a23f4c..76294ba06 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -407,5 +407,5 @@ func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullR
return nil
}
}
- return errors.Errorf("%s has no label %s", imageInfo.image, label)
+ return errors.Errorf("%s has no label %s in %q", imageInfo.image, label, remoteInspect.Labels)
}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index a68338dbb..89dac2b5d 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -3,8 +3,10 @@
package libpod
import (
+ "bytes"
"crypto/rand"
"fmt"
+ "io"
"io/ioutil"
"net"
"os"
@@ -20,6 +22,7 @@ import (
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/netns"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/rootlessport"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -151,19 +154,6 @@ func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result,
return ctrNS, networkStatus, err
}
-type slirp4netnsCmdArg struct {
- Proto string `json:"proto,omitempty"`
- HostAddr string `json:"host_addr"`
- HostPort int32 `json:"host_port"`
- GuestAddr string `json:"guest_addr"`
- GuestPort int32 `json:"guest_port"`
-}
-
-type slirp4netnsCmd struct {
- Execute string `json:"execute"`
- Args slirp4netnsCmdArg `json:"arguments"`
-}
-
func checkSlirpFlags(path string) (bool, bool, bool, error) {
cmd := exec.Command(path, "--help")
out, err := cmd.CombinedOutput()
@@ -194,13 +184,9 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
defer errorhandling.CloseQuiet(syncW)
havePortMapping := len(ctr.Config().PortMappings) > 0
- apiSocket := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID))
logPath := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID))
cmdArgs := []string{}
- if havePortMapping {
- cmdArgs = append(cmdArgs, "--api-socket", apiSocket)
- }
dhp, mtu, sandbox, err := checkSlirpFlags(path)
if err != nil {
return errors.Wrapf(err, "error checking slirp4netns binary %s: %q", path, err)
@@ -221,15 +207,19 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
// -e, --exit-fd=FD specify the FD for terminating slirp4netns
// -r, --ready-fd=FD specify the FD to write to when the initialization steps are finished
cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4")
+ netnsPath := ""
if !ctr.config.PostConfigureNetNS {
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
if err != nil {
return errors.Wrapf(err, "failed to create rootless network sync pipe")
}
- cmdArgs = append(cmdArgs, "--netns-type=path", ctr.state.NetNS.Path(), "tap0")
+ netnsPath = ctr.state.NetNS.Path()
+ cmdArgs = append(cmdArgs, "--netns-type=path", netnsPath, "tap0")
} else {
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR)
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW)
+ netnsPath = fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
+ // we don't use --netns-path here (unavailable for slirp4netns < v0.4)
cmdArgs = append(cmdArgs, fmt.Sprintf("%d", ctr.state.PID), "tap0")
}
@@ -269,11 +259,27 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
}
}()
+ if err := waitForSync(syncR, cmd, logFile, 1*time.Second); err != nil {
+ return err
+ }
+
+ if havePortMapping {
+ return r.setupRootlessPortMapping(ctr, netnsPath)
+ }
+ return nil
+}
+
+func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error {
+ prog := filepath.Base(cmd.Path)
+ if len(cmd.Args) > 0 {
+ prog = cmd.Args[0]
+ }
b := make([]byte, 16)
for {
- if err := syncR.SetDeadline(time.Now().Add(1 * time.Second)); err != nil {
- return errors.Wrapf(err, "error setting slirp4netns pipe timeout")
+ if err := syncR.SetDeadline(time.Now().Add(timeout)); err != nil {
+ return errors.Wrapf(err, "error setting %s pipe timeout", prog)
}
+ // FIXME: return err as soon as proc exits, without waiting for timeout
if _, err := syncR.Read(b); err == nil {
break
} else {
@@ -282,7 +288,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
var status syscall.WaitStatus
pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
if err != nil {
- return errors.Wrapf(err, "failed to read slirp4netns process status")
+ return errors.Wrapf(err, "failed to read %s process status", prog)
}
if pid != cmd.Process.Pid {
continue
@@ -294,100 +300,87 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
}
logContent, err := ioutil.ReadAll(logFile)
if err != nil {
- return errors.Wrapf(err, "slirp4netns failed")
+ return errors.Wrapf(err, "%s failed", prog)
}
- return errors.Errorf("slirp4netns failed: %q", logContent)
+ return errors.Errorf("%s failed: %q", prog, logContent)
}
if status.Signaled() {
- return errors.New("slirp4netns killed by signal")
+ return errors.Errorf("%s killed by signal", prog)
}
continue
}
- return errors.Wrapf(err, "failed to read from slirp4netns sync pipe")
+ return errors.Wrapf(err, "failed to read from %s sync pipe", prog)
}
}
+ return nil
+}
- if havePortMapping {
- const pidWaitTimeout = 60 * time.Second
- chWait := make(chan error)
- go func() {
- interval := 25 * time.Millisecond
- for i := time.Duration(0); i < pidWaitTimeout; i += interval {
- // Check if the process is still running.
- var status syscall.WaitStatus
- pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
- if err != nil {
- break
- }
- if pid != cmd.Process.Pid {
- continue
- }
- if status.Exited() || status.Signaled() {
- chWait <- fmt.Errorf("slirp4netns exited with status %d", status.ExitStatus())
- }
- time.Sleep(interval)
- }
- }()
- defer close(chWait)
+func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (err error) {
+ syncR, syncW, err := os.Pipe()
+ if err != nil {
+ return errors.Wrapf(err, "failed to open pipe")
+ }
+ defer errorhandling.CloseQuiet(syncR)
+ defer errorhandling.CloseQuiet(syncW)
- // wait that API socket file appears before trying to use it.
- if _, err := WaitForFile(apiSocket, chWait, pidWaitTimeout); err != nil {
- return errors.Wrapf(err, "waiting for slirp4nets to create the api socket file %s", apiSocket)
- }
+ logPath := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("rootlessport-%s.log", ctr.config.ID))
+ logFile, err := os.Create(logPath)
+ if err != nil {
+ return errors.Wrapf(err, "failed to open rootlessport log file %s", logPath)
+ }
+ defer logFile.Close()
+ // Unlink immediately the file so we won't need to worry about cleaning it up later.
+ // It is still accessible through the open fd logFile.
+ if err := os.Remove(logPath); err != nil {
+ return errors.Wrapf(err, "delete file %s", logPath)
+ }
- // for each port we want to add we need to open a connection to the slirp4netns control socket
- // and send the add_hostfwd command.
- for _, i := range ctr.config.PortMappings {
- conn, err := net.Dial("unix", apiSocket)
- if err != nil {
- return errors.Wrapf(err, "cannot open connection to %s", apiSocket)
- }
- defer func() {
- if err := conn.Close(); err != nil {
- logrus.Errorf("unable to close connection: %q", err)
- }
- }()
- hostIP := i.HostIP
- if hostIP == "" {
- hostIP = "0.0.0.0"
- }
- cmd := slirp4netnsCmd{
- Execute: "add_hostfwd",
- Args: slirp4netnsCmdArg{
- Proto: i.Protocol,
- HostAddr: hostIP,
- HostPort: i.HostPort,
- GuestPort: i.ContainerPort,
- },
- }
- // create the JSON payload and send it. Mark the end of request shutting down writes
- // to the socket, as requested by slirp4netns.
- data, err := json.Marshal(&cmd)
- if err != nil {
- return errors.Wrapf(err, "cannot marshal JSON for slirp4netns")
- }
- if _, err := conn.Write([]byte(fmt.Sprintf("%s\n", data))); err != nil {
- return errors.Wrapf(err, "cannot write to control socket %s", apiSocket)
- }
- if err := conn.(*net.UnixConn).CloseWrite(); err != nil {
- return errors.Wrapf(err, "cannot shutdown the socket %s", apiSocket)
- }
- buf := make([]byte, 2048)
- readLength, err := conn.Read(buf)
- if err != nil {
- return errors.Wrapf(err, "cannot read from control socket %s", apiSocket)
- }
- // if there is no 'error' key in the received JSON data, then the operation was
- // successful.
- var y map[string]interface{}
- if err := json.Unmarshal(buf[0:readLength], &y); err != nil {
- return errors.Wrapf(err, "error parsing error status from slirp4netns")
- }
- if e, found := y["error"]; found {
- return errors.Errorf("error from slirp4netns while setting up port redirection: %v", e)
- }
+ ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe()
+ if err != nil {
+ return errors.Wrapf(err, "failed to create rootless port sync pipe")
+ }
+ cfg := rootlessport.Config{
+ Mappings: ctr.config.PortMappings,
+ NetNSPath: netnsPath,
+ ExitFD: 3,
+ ReadyFD: 4,
+ TmpDir: ctr.runtime.config.TmpDir,
+ }
+ cfgJSON, err := json.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+ cfgR := bytes.NewReader(cfgJSON)
+ var stdout bytes.Buffer
+ cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid()))
+ cmd.Args = []string{rootlessport.ReexecKey}
+ // Leak one end of the pipe in rootlessport process, the other will be sent to conmon
+ cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncR, syncW)
+ cmd.Stdin = cfgR
+ // stdout is for human-readable error, stderr is for debug log
+ cmd.Stdout = &stdout
+ cmd.Stderr = io.MultiWriter(logFile, &logrusDebugWriter{"rootlessport: "})
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setpgid: true,
+ }
+ if err := cmd.Start(); err != nil {
+ return errors.Wrapf(err, "failed to start rootlessport process")
+ }
+ defer func() {
+ if err := cmd.Process.Release(); err != nil {
+ logrus.Errorf("unable to release rootlessport process: %q", err)
}
+ }()
+ if err := waitForSync(syncR, cmd, logFile, 3*time.Second); err != nil {
+ stdoutStr := stdout.String()
+ if stdoutStr != "" {
+ // err contains full debug log and too verbose, so return stdoutStr
+ logrus.Debug(err)
+ return errors.Errorf("failed to expose ports via rootlessport: %q", stdoutStr)
+ }
+ return err
}
+ logrus.Debug("rootlessport is ready")
return nil
}
@@ -587,3 +580,12 @@ func (c *Container) getContainerNetworkInfo(data *InspectContainerData) *Inspect
}
return data
}
+
+type logrusDebugWriter struct {
+ prefix string
+}
+
+func (w *logrusDebugWriter) Write(p []byte) (int, error) {
+ logrus.Debugf("%s%s", w.prefix, string(p))
+ return len(p), nil
+}
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index 37aa71cbb..3b0b7bc7b 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"syscall"
+ "text/template"
"time"
"github.com/containers/libpod/libpod/config"
@@ -532,7 +533,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
ociLog = c.execOCILog(sessionID)
}
- args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog)
+ args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, "")
if options.PreserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", options.PreserveFDs))...)
@@ -546,6 +547,10 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
args = append(args, "-t")
}
+ if options.Streams.AttachInput {
+ args = append(args, "-i")
+ }
+
// Append container ID and command
args = append(args, "-e")
// TODO make this optional when we can detach
@@ -558,9 +563,8 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
execCmd := exec.Command(r.conmonPath, args...)
if options.Streams != nil {
- if options.Streams.AttachInput {
- execCmd.Stdin = options.Streams.InputStream
- }
+ // Don't add the InputStream to the execCmd. Instead, the data should be passed
+ // through CopyDetachable
if options.Streams.AttachOutput {
execCmd.Stdout = options.Streams.OutputStream
}
@@ -709,7 +713,7 @@ func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (b
return true, nil
}
-// ExecCleanupContainer cleans up files created when a command is run via
+// ExecContainerCleanup cleans up files created when a command is run via
// ExecContainer. This includes the attach socket for the exec session.
func (r *ConmonOCIRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error {
// Clean up the sockets dir. Issue #3962
@@ -887,6 +891,27 @@ func waitPidStop(pid int, timeout time.Duration) error {
}
}
+func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
+ logTag := ctr.LogTag()
+ if logTag == "" {
+ return "", nil
+ }
+ data, err := ctr.inspectLocked(false)
+ if err != nil {
+ return "", nil
+ }
+ tmpl, err := template.New("container").Parse(logTag)
+ if err != nil {
+ return "", errors.Wrapf(err, "template parsing error %s", logTag)
+ }
+ var b bytes.Buffer
+ err = tmpl.Execute(&b, data)
+ if err != nil {
+ return "", err
+ }
+ return b.String(), nil
+}
+
// createOCIContainer generates this container's main conmon instance and prepares it for starting
func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) {
var stderrBuf bytes.Buffer
@@ -913,7 +938,13 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON {
ociLog = filepath.Join(ctr.state.RunDir, "oci-log")
}
- args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog)
+
+ logTag, err := r.getLogTag(ctr)
+ if err != nil {
+ return err
+ }
+
+ args := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), filepath.Join(ctr.state.RunDir, "pidfile"), ctr.LogPath(), r.exitsDir, ociLog, logTag)
if ctr.config.Spec.Process.Terminal {
args = append(args, "-t")
@@ -1000,6 +1031,15 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
}
// Leak one end in conmon, the other one will be leaked into slirp4netns
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW)
+
+ if ctr.rootlessPortSyncR != nil {
+ defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR)
+ }
+ if ctr.rootlessPortSyncW != nil {
+ defer errorhandling.CloseQuiet(ctr.rootlessPortSyncW)
+ // Leak one end in conmon, the other one will be leaked into rootlessport
+ cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncW)
+ }
}
err = startCommandGivenSelinux(cmd)
@@ -1138,7 +1178,7 @@ func (r *ConmonOCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*o
}
// sharedConmonArgs takes common arguments for exec and create/restore and formats them for the conmon CLI
-func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath string) []string {
+func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath, logTag string) []string {
// set the conmon API version to be able to use the correct sync struct keys
args := []string{"--api-version", "1"}
if r.cgroupManager == define.SystemdCgroupsManager && !ctr.config.NoCgroups {
@@ -1185,6 +1225,9 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
if ociLogPath != "" {
args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath))
}
+ if logTag != "" {
+ args = append(args, "--log-tag", logTag)
+ }
if ctr.config.NoCgroups {
logrus.Debugf("Running with no CGroups")
args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled")
diff --git a/libpod/options.go b/libpod/options.go
index a9b775dc3..1d6863e7b 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -20,7 +20,9 @@ import (
)
var (
- NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
+ // NameRegex is a regular expression to validate container/pod names.
+ NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
+ // RegexError is thrown in presence of an invalid container/pod name.
RegexError = errors.Wrapf(define.ErrInvalidArg, "names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*")
)
@@ -1057,6 +1059,23 @@ func WithLogPath(path string) CtrCreateOption {
}
}
+// WithLogTag sets the tag to the log file.
+func WithLogTag(tag string) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+ if tag == "" {
+ return errors.Wrapf(define.ErrInvalidArg, "log tag must be set")
+ }
+
+ ctr.config.LogTag = tag
+
+ return nil
+ }
+
+}
+
// WithNoCgroups disables the creation of CGroups for the new container.
func WithNoCgroups() CtrCreateOption {
return func(ctr *Container) error {
@@ -1413,6 +1432,18 @@ func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption
}
}
+// WithCreateCommand adds the full command plus arguments of the current
+// process to the container config.
+func WithCreateCommand() CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+ ctr.config.CreateCommand = os.Args
+ return nil
+ }
+}
+
// Volume Creation Options
// WithVolumeName sets the name of the volume.
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index b27257004..cb04f7411 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -123,7 +123,7 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m
if timeout > -1 {
stopTimeout = uint(timeout)
}
- if err := ctr.stop(stopTimeout, false); err != nil {
+ if err := ctr.stop(stopTimeout); err != nil {
ctr.lock.Unlock()
ctrErrors[ctr.ID()] = err
continue
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 3873079ce..b4cbde28e 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -213,11 +213,11 @@ func getLockManager(runtime *Runtime) (lock.Manager, error) {
// Sets up containers/storage, state store, OCI runtime
func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
// Find a working conmon binary
- if cPath, err := runtime.config.FindConmon(); err != nil {
+ cPath, err := runtime.config.FindConmon()
+ if err != nil {
return err
- } else {
- runtime.conmonPath = cPath
}
+ runtime.conmonPath = cPath
// Make the static files directory if it does not exist
if err := os.MkdirAll(runtime.config.StaticDir, 0700); err != nil {
@@ -691,24 +691,22 @@ func (r *Runtime) Info() ([]define.InfoData, error) {
}
info = append(info, define.InfoData{Type: "store", Data: storeInfo})
- reg, err := sysreg.GetRegistries()
- if err != nil {
- return nil, errors.Wrapf(err, "error getting registries")
- }
registries := make(map[string]interface{})
- registries["search"] = reg
-
- ireg, err := sysreg.GetInsecureRegistries()
+ data, err := sysreg.GetRegistriesData()
if err != nil {
return nil, errors.Wrapf(err, "error getting registries")
}
- registries["insecure"] = ireg
-
- breg, err := sysreg.GetBlockedRegistries()
+ for _, reg := range data {
+ registries[reg.Prefix] = reg
+ }
+ regs, err := sysreg.GetRegistries()
if err != nil {
return nil, errors.Wrapf(err, "error getting registries")
}
- registries["blocked"] = breg
+ if len(regs) > 0 {
+ registries["search"] = regs
+ }
+
info = append(info, define.InfoData{Type: "registries", Data: registries})
return info, nil
}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index ae401013c..51efc5996 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -463,7 +463,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
// Check that the container's in a good state to be removed
if c.state.State == define.ContainerStateRunning {
- if err := c.stop(c.StopTimeout(), true); err != nil {
+ if err := c.stop(c.StopTimeout()); err != nil {
return errors.Wrapf(err, "cannot remove container %s as it could not be stopped", c.ID())
}
}
@@ -573,7 +573,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
if !volume.IsCtrSpecific() {
continue
}
- if err := runtime.removeVolume(ctx, volume, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
+ if err := runtime.removeVolume(ctx, volume, false); err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
logrus.Errorf("cleanup volume (%s): %v", v, err)
}
}
@@ -768,7 +768,7 @@ func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*Container, error
return nil, define.ErrRuntimeStopped
}
- ctrs, err := r.state.AllContainers()
+ ctrs, err := r.GetAllContainers()
if err != nil {
return nil, err
}
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index abd8b581d..bae1c1ed8 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -10,6 +10,7 @@ import (
"os"
"github.com/containers/buildah/imagebuildah"
+ "github.com/containers/image/v5/docker/reference"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/util"
@@ -27,19 +28,19 @@ import (
// RemoveImage deletes an image from local storage
// Images being used by running containers can only be removed if force=true
-func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) (string, error) {
- var returnMessage string
+func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) (*image.ImageDeleteResponse, error) {
+ response := image.ImageDeleteResponse{}
r.lock.Lock()
defer r.lock.Unlock()
if !r.valid {
- return "", define.ErrRuntimeStopped
+ return nil, define.ErrRuntimeStopped
}
// Get all containers, filter to only those using the image, and remove those containers
ctrs, err := r.state.AllContainers()
if err != nil {
- return "", err
+ return nil, err
}
imageCtrs := []*Container{}
for _, ctr := range ctrs {
@@ -51,17 +52,17 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
if force {
for _, ctr := range imageCtrs {
if err := r.removeContainer(ctx, ctr, true, false, false); err != nil {
- return "", errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID())
+ return nil, errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID())
}
}
} else {
- return "", fmt.Errorf("could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs))
+ return nil, fmt.Errorf("could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs))
}
}
hasChildren, err := img.IsParent(ctx)
if err != nil {
- return "", err
+ return nil, err
}
if (len(img.Names()) > 1 && !img.InputIsID()) || hasChildren {
@@ -70,19 +71,20 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
// to and untag it.
repoName, err := img.MatchRepoTag(img.InputName)
if hasChildren && errors.Cause(err) == image.ErrRepoTagNotFound {
- return "", errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", img.ID())
+ return nil, errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", img.ID())
}
if err != nil {
- return "", err
+ return nil, err
}
if err := img.UntagImage(repoName); err != nil {
- return "", err
+ return nil, err
}
- return fmt.Sprintf("Untagged: %s", repoName), nil
+ response.Untagged = append(response.Untagged, repoName)
+ return &response, nil
} else if len(img.Names()) > 1 && img.InputIsID() && !force {
// If the user requests to delete an image by ID and the image has multiple
// reponames and no force is applied, we error out.
- return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
+ return nil, fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
}
err = img.Remove(ctx, force)
if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer {
@@ -94,11 +96,9 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
err = errStorage
}
}
- for _, name := range img.Names() {
- returnMessage = returnMessage + fmt.Sprintf("Untagged: %s\n", name)
- }
- returnMessage = returnMessage + fmt.Sprintf("Deleted: %s", img.ID())
- return returnMessage, err
+ response.Untagged = append(response.Untagged, img.Names()...)
+ response.Deleted = img.ID()
+ return &response, err
}
// Remove containers that are in storage rather than Podman.
@@ -146,9 +146,9 @@ func removeStorageContainers(ctrIDs []string, store storage.Store) error {
}
// Build adds the runtime to the imagebuildah call
-func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, dockerfiles ...string) error {
- _, _, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...)
- return err
+func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, dockerfiles ...string) (string, reference.Canonical, error) {
+ id, ref, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...)
+ return id, ref, err
}
// Import is called as an intermediary to the image library Import
@@ -193,7 +193,7 @@ func (r *Runtime) Import(ctx context.Context, source string, reference string, c
}
// if it's stdin, buffer it, too
if source == "-" {
- file, err := downloadFromFile(os.Stdin)
+ file, err := DownloadFromFile(os.Stdin)
if err != nil {
return "", err
}
@@ -233,9 +233,9 @@ func downloadFromURL(source string) (string, error) {
return outFile.Name(), nil
}
-// donwloadFromFile reads all of the content from the reader and temporarily
+// DownloadFromFile reads all of the content from the reader and temporarily
// saves in it /var/tmp/importxyz, which is deleted after the image is imported
-func downloadFromFile(reader *os.File) (string, error) {
+func DownloadFromFile(reader *os.File) (string, error) {
outFile, err := ioutil.TempFile("/var/tmp", "import")
if err != nil {
return "", errors.Wrap(err, "error creating file")
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 704aaf9d0..563d9728a 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -225,11 +225,20 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
}
}
+ ctrNamedVolumes := make(map[string]*ContainerNamedVolume)
+
// Second loop - all containers are good, so we should be clear to
// remove.
for _, ctr := range ctrs {
- // Remove the container
- if err := r.removeContainer(ctx, ctr, force, true, true); err != nil {
+ // Remove the container.
+ // Do NOT remove named volumes. Instead, we're going to build a
+ // list of them to be removed at the end, once the containers
+ // have been removed by RemovePodContainers.
+ for _, vol := range ctr.config.NamedVolumes {
+ ctrNamedVolumes[vol.Name] = vol
+ }
+
+ if err := r.removeContainer(ctx, ctr, force, false, true); err != nil {
if removalErr != nil {
removalErr = err
} else {
@@ -246,6 +255,23 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
return err
}
+ for volName := range ctrNamedVolumes {
+ volume, err := r.state.Volume(volName)
+ if err != nil && errors.Cause(err) != define.ErrNoSuchVolume {
+ logrus.Errorf("Error retrieving volume %s: %v", volName, err)
+ continue
+ }
+ if !volume.IsCtrSpecific() {
+ continue
+ }
+ if err := r.removeVolume(ctx, volume, false); err != nil {
+ if errors.Cause(err) == define.ErrNoSuchVolume || errors.Cause(err) == define.ErrVolumeRemoved {
+ continue
+ }
+ logrus.Errorf("Error removing volume %s: %v", volName, err)
+ }
+ }
+
// Remove pod cgroup, if present
if p.state.CgroupPath != "" {
logrus.Debugf("Removing pod cgroup %s", p.state.CgroupPath)