diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/config/config.go | 58 | ||||
-rw-r--r-- | libpod/config/default.go | 7 | ||||
-rw-r--r-- | libpod/container.go | 14 | ||||
-rw-r--r-- | libpod/container_api.go | 4 | ||||
-rw-r--r-- | libpod/container_commit.go | 17 | ||||
-rw-r--r-- | libpod/container_inspect.go | 42 | ||||
-rw-r--r-- | libpod/container_internal.go | 37 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 98 | ||||
-rw-r--r-- | libpod/diff.go | 44 | ||||
-rw-r--r-- | libpod/image/config.go | 8 | ||||
-rw-r--r-- | libpod/image/image.go | 10 | ||||
-rw-r--r-- | libpod/image/prune.go | 10 | ||||
-rw-r--r-- | libpod/image/pull.go | 2 | ||||
-rw-r--r-- | libpod/networking_linux.go | 206 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 57 | ||||
-rw-r--r-- | libpod/options.go | 33 | ||||
-rw-r--r-- | libpod/pod_api.go | 2 | ||||
-rw-r--r-- | libpod/runtime.go | 26 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 6 | ||||
-rw-r--r-- | libpod/runtime_img.go | 46 | ||||
-rw-r--r-- | libpod/runtime_pod_linux.go | 30 |
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) |