diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/config/config.go | 94 | ||||
-rw-r--r-- | libpod/container_api.go | 7 | ||||
-rw-r--r-- | libpod/container_internal.go | 40 | ||||
-rw-r--r-- | libpod/image/filters.go | 176 | ||||
-rw-r--r-- | libpod/image/image.go | 13 | ||||
-rw-r--r-- | libpod/oci.go | 6 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 22 | ||||
-rw-r--r-- | libpod/oci_missing.go | 5 | ||||
-rw-r--r-- | libpod/runtime.go | 20 |
9 files changed, 342 insertions, 41 deletions
diff --git a/libpod/config/config.go b/libpod/config/config.go index f1fa70fbc..6240bccb0 100644 --- a/libpod/config/config.go +++ b/libpod/config/config.go @@ -12,6 +12,7 @@ import ( "github.com/BurntSushi/toml" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" @@ -137,11 +138,11 @@ type Config struct { // VolumePath is the default location that named volumes will be created // under. This convention is followed by the default volume driver, but // may not be by other drivers. - VolumePath string `toml:"volume_path"` + VolumePath string `toml:"volume_path,omitempty"` // ImageDefaultTransport is the default transport method used to fetch // images. - ImageDefaultTransport string `toml:"image_default_transport"` + ImageDefaultTransport string `toml:"image_default_transport,omitempty"` // SignaturePolicyPath is the path to a signature policy to use for // validating images. If left empty, the containers/image default signature @@ -149,61 +150,61 @@ type Config struct { SignaturePolicyPath string `toml:"signature_policy_path,omitempty"` // OCIRuntime is the OCI runtime to use. - OCIRuntime string `toml:"runtime"` + OCIRuntime string `toml:"runtime,omitempty"` // OCIRuntimes are the set of configured OCI runtimes (default is runc). - OCIRuntimes map[string][]string `toml:"runtimes"` + OCIRuntimes map[string][]string `toml:"runtimes,omitempty"` // RuntimeSupportsJSON is the list of the OCI runtimes that support // --format=json. - RuntimeSupportsJSON []string `toml:"runtime_supports_json"` + RuntimeSupportsJSON []string `toml:"runtime_supports_json,omitempty"` // RuntimeSupportsNoCgroups is a list of OCI runtimes that support // running containers without CGroups. - RuntimeSupportsNoCgroups []string `toml:"runtime_supports_nocgroups"` + RuntimeSupportsNoCgroups []string `toml:"runtime_supports_nocgroups,omitempty"` // RuntimePath is the path to OCI runtime binary for launching containers. // The first path pointing to a valid file will be used This is used only // when there are no OCIRuntime/OCIRuntimes defined. It is used only to be // backward compatible with older versions of Podman. - RuntimePath []string `toml:"runtime_path"` + RuntimePath []string `toml:"runtime_path,omitempty"` // ConmonPath is the path to the Conmon binary used for managing containers. // The first path pointing to a valid file will be used. - ConmonPath []string `toml:"conmon_path"` + ConmonPath []string `toml:"conmon_path,omitempty"` // ConmonEnvVars are environment variables to pass to the Conmon binary // when it is launched. - ConmonEnvVars []string `toml:"conmon_env_vars"` + ConmonEnvVars []string `toml:"conmon_env_vars,omitempty"` // CGroupManager is the CGroup Manager to use Valid values are "cgroupfs" // and "systemd". - CgroupManager string `toml:"cgroup_manager"` + CgroupManager string `toml:"cgroup_manager,omitempty"` // InitPath is the path to the container-init binary. - InitPath string `toml:"init_path"` + InitPath string `toml:"init_path,omitempty"` // StaticDir is the path to a persistent directory to store container // files. - StaticDir string `toml:"static_dir"` + StaticDir string `toml:"static_dir,omitempty"` // TmpDir is the path to a temporary directory to store per-boot container // files. Must be stored in a tmpfs. - TmpDir string `toml:"tmp_dir"` + TmpDir string `toml:"tmp_dir,omitempty"` // MaxLogSize is the maximum size of container logfiles. MaxLogSize int64 `toml:"max_log_size,omitempty"` // NoPivotRoot sets whether to set no-pivot-root in the OCI runtime. - NoPivotRoot bool `toml:"no_pivot_root"` + NoPivotRoot bool `toml:"no_pivot_root,omitempty"` // CNIConfigDir sets the directory where CNI configuration files are // stored. - CNIConfigDir string `toml:"cni_config_dir"` + CNIConfigDir string `toml:"cni_config_dir,omitempty"` // CNIPluginDir sets a number of directories where the CNI network // plugins can be located. - CNIPluginDir []string `toml:"cni_plugin_dir"` + CNIPluginDir []string `toml:"cni_plugin_dir,omitempty"` // CNIDefaultNetwork is the network name of the default CNI network // to attach pods to. @@ -213,7 +214,7 @@ type Config struct { // configuration files. When the same filename is present in in // multiple directories, the file in the directory listed last in // this slice takes precedence. - HooksDir []string `toml:"hooks_dir"` + HooksDir []string `toml:"hooks_dir,omitempty"` // DefaultMountsFile is the path to the default mounts file for testing // purposes only. @@ -229,10 +230,10 @@ type Config struct { // InfraImage is the image a pod infra container will use to manage // namespaces. - InfraImage string `toml:"infra_image"` + InfraImage string `toml:"infra_image,omitempty"` // InfraCommand is the command run to start up a pod infra container. - InfraCommand string `toml:"infra_command"` + InfraCommand string `toml:"infra_command,omitempty"` // EnablePortReservation determines whether libpod will reserve ports on the // host when they are forwarded to containers. When enabled, when ports are @@ -241,13 +242,13 @@ type Config struct { // programs on the host. However, this can cause significant memory usage if // a container has many ports forwarded to it. Disabling this can save // memory. - EnablePortReservation bool `toml:"enable_port_reservation"` + EnablePortReservation bool `toml:"enable_port_reservation,omitempty"` // EnableLabeling indicates whether libpod will support container labeling. - EnableLabeling bool `toml:"label"` + EnableLabeling bool `toml:"label,omitempty"` // NetworkCmdPath is the path to the slirp4netns binary. - NetworkCmdPath string `toml:"network_cmd_path"` + NetworkCmdPath string `toml:"network_cmd_path,omitempty"` // NumLocks is the number of locks to make available for containers and // pods. @@ -257,17 +258,21 @@ type Config struct { LockType string `toml:"lock_type,omitempty"` // EventsLogger determines where events should be logged. - EventsLogger string `toml:"events_logger"` + EventsLogger string `toml:"events_logger,omitempty"` // EventsLogFilePath is where the events log is stored. - EventsLogFilePath string `toml:"events_logfile_path"` + EventsLogFilePath string `toml:"events_logfile_path,omitempty"` //DetachKeys is the sequence of keys used to detach a container. - DetachKeys string `toml:"detach_keys"` + DetachKeys string `toml:"detach_keys,omitempty"` // SDNotify tells Libpod to allow containers to notify the host systemd of // readiness using the SD_NOTIFY mechanism. - SDNotify bool + SDNotify bool `toml:",omitempty"` + + // CgroupCheck indicates the configuration has been rewritten after an + // upgrade to Fedora 31 to change the default OCI runtime for cgroupsv2. + CgroupCheck bool `toml:"cgroup_check,omitempty"` } // DBConfig is a set of Libpod runtime configuration settings that are saved in @@ -449,11 +454,21 @@ func NewConfig(userConfigPath string) (*Config, error) { if configs, err := systemConfigs(); err != nil { return nil, errors.Wrapf(err, "error finding config on system") } else { + 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) + } + 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 { @@ -550,3 +565,30 @@ func (c *Config) checkCgroupsAndLogger() { c.EventsLogger = "file" } } + +// Since runc does not currently support cgroupV2 +// Change to default crun on first running of libpod.conf +// 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 + } + if cgroupsV2 { + path, err := exec.LookPath("crun") + if err != nil { + logrus.Warnf("Can not find crun package on the host, containers might fail to run on cgroup V2 systems without crun: %q", err) + // Can't find crun path so do nothing + return nil + } + tmpConfig.CgroupCheck = true + tmpConfig.OCIRuntime = path + if err := tmpConfig.Write(configPath); err != nil { + return err + } + } + } + return nil +} diff --git a/libpod/container_api.go b/libpod/container_api.go index 153a1d628..5168dbc68 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -594,7 +594,12 @@ func (c *Container) Cleanup(ctx context.Context) error { // If we didn't restart, we perform a normal cleanup - // Check if we have active exec sessions + // Reap exec sessions first. + if err := c.reapExecSessions(); err != nil { + return err + } + + // Check if we have active exec sessions after reaping. if len(c.state.ExecSessions) != 0 { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has active exec sessions, refusing to clean up", c.ID()) } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 1e8a8a580..37801162a 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1749,6 +1749,11 @@ func (c *Container) checkReadyForRemoval() error { return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it is %s - running or paused containers cannot be removed without force", c.ID(), c.state.State.String()) } + // Reap exec sessions + if err := c.reapExecSessions(); err != nil { + return err + } + if len(c.state.ExecSessions) != 0 { return errors.Wrapf(define.ErrCtrStateInvalid, "cannot remove container %s as it has active exec sessions", c.ID()) } @@ -1855,3 +1860,38 @@ func (c *Container) checkExitFile() error { // Read the exit file to get our stopped time and exit code. return c.handleExitFile(exitFile, info) } + +// Reap dead exec sessions +func (c *Container) reapExecSessions() error { + // Instead of saving once per iteration, use a defer to do it once at + // the end. + var lastErr error + needSave := false + for id := range c.state.ExecSessions { + alive, err := c.ociRuntime.ExecUpdateStatus(c, id) + if err != nil { + if lastErr != nil { + logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr) + } + lastErr = err + continue + } + if !alive { + // Clean up lingering files and remove the exec session + if err := c.ociRuntime.ExecContainerCleanup(c, id); err != nil { + return errors.Wrapf(err, "error cleaning up container %s exec session %s files", c.ID(), id) + } + delete(c.state.ExecSessions, id) + needSave = true + } + } + if needSave { + if err := c.save(); err != nil { + if lastErr != nil { + logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr) + } + lastErr = err + } + } + return lastErr +} diff --git a/libpod/image/filters.go b/libpod/image/filters.go new file mode 100644 index 000000000..d545f1bfc --- /dev/null +++ b/libpod/image/filters.go @@ -0,0 +1,176 @@ +package image + +import ( + "context" + "fmt" + "github.com/pkg/errors" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/containers/libpod/pkg/inspect" + "github.com/sirupsen/logrus" +) + +// ResultFilter is a mock function for image filtering +type ResultFilter func(*Image) bool + +// Filter is a function to determine whether an image is included in +// command output. Images to be outputted are tested using the function. A true +// return will include the image, a false return will exclude it. +type Filter func(*Image, *inspect.ImageData) bool + +// CreatedBeforeFilter allows you to filter on images created before +// the given time.Time +func CreatedBeforeFilter(createTime time.Time) ResultFilter { + return func(i *Image) bool { + return i.Created().Before(createTime) + } +} + +// CreatedAfterFilter allows you to filter on images created after +// the given time.Time +func CreatedAfterFilter(createTime time.Time) ResultFilter { + return func(i *Image) bool { + return i.Created().After(createTime) + } +} + +// DanglingFilter allows you to filter images for dangling images +func DanglingFilter(danglingImages bool) ResultFilter { + return func(i *Image) bool { + if danglingImages { + return i.Dangling() + } + return !i.Dangling() + } +} + +// ReadOnlyFilter allows you to filter images based on read/only and read/write +func ReadOnlyFilter(readOnly bool) ResultFilter { + return func(i *Image) bool { + if readOnly { + return i.IsReadOnly() + } + return !i.IsReadOnly() + } +} + +// LabelFilter allows you to filter by images labels key and/or value +func LabelFilter(ctx context.Context, labelfilter string) ResultFilter { + // We need to handle both label=key and label=key=value + return func(i *Image) bool { + var value string + splitFilter := strings.Split(labelfilter, "=") + key := splitFilter[0] + if len(splitFilter) > 1 { + value = splitFilter[1] + } + labels, err := i.Labels(ctx) + if err != nil { + return false + } + if len(strings.TrimSpace(labels[key])) > 0 && len(strings.TrimSpace(value)) == 0 { + return true + } + return labels[key] == value + } +} + +// ReferenceFilter allows you to filter by image name +// Replacing all '/' with '|' so that filepath.Match() can work +// '|' character is not valid in image name, so this is safe +func ReferenceFilter(ctx context.Context, referenceFilter string) ResultFilter { + filter := fmt.Sprintf("*%s*", referenceFilter) + filter = strings.Replace(filter, "/", "|", -1) + return func(i *Image) bool { + if len(referenceFilter) < 1 { + return true + } + for _, name := range i.Names() { + newName := strings.Replace(name, "/", "|", -1) + match, err := filepath.Match(filter, newName) + if err != nil { + logrus.Errorf("failed to match %s and %s, %q", name, referenceFilter, err) + } + if match { + return true + } + } + return false + } +} + +// OutputImageFilter allows you to filter by an a specific image name +func OutputImageFilter(userImage *Image) ResultFilter { + return func(i *Image) bool { + return userImage.ID() == i.ID() + } +} + +// FilterImages filters images using a set of predefined filter funcs +func FilterImages(images []*Image, filters []ResultFilter) []*Image { + var filteredImages []*Image + for _, image := range images { + include := true + for _, filter := range filters { + include = include && filter(image) + } + if include { + filteredImages = append(filteredImages, image) + } + } + return filteredImages +} + +// createFilterFuncs returns an array of filter functions based on the user inputs +// and is later used to filter images for output +func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilter, error) { + var filterFuncs []ResultFilter + ctx := context.Background() + for _, filter := range filters { + splitFilter := strings.Split(filter, "=") + if len(splitFilter) < 2 { + return nil, errors.Errorf("invalid filter syntax %s", filter) + } + switch splitFilter[0] { + case "before": + before, err := ir.NewFromLocal(splitFilter[1]) + if err != nil { + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) + } + filterFuncs = append(filterFuncs, CreatedBeforeFilter(before.Created())) + case "after": + after, err := ir.NewFromLocal(splitFilter[1]) + if err != nil { + return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) + } + filterFuncs = append(filterFuncs, CreatedAfterFilter(after.Created())) + case "readonly": + readonly, err := strconv.ParseBool(splitFilter[1]) + if err != nil { + return nil, errors.Wrapf(err, "invalid filter readonly=%s", splitFilter[1]) + } + filterFuncs = append(filterFuncs, ReadOnlyFilter(readonly)) + case "dangling": + danglingImages, err := strconv.ParseBool(splitFilter[1]) + if err != nil { + return nil, errors.Wrapf(err, "invalid filter dangling=%s", splitFilter[1]) + } + filterFuncs = append(filterFuncs, DanglingFilter(danglingImages)) + case "label": + labelFilter := strings.Join(splitFilter[1:], "=") + filterFuncs = append(filterFuncs, LabelFilter(ctx, labelFilter)) + case "reference": + referenceFilter := strings.Join(splitFilter[1:], "=") + filterFuncs = append(filterFuncs, ReferenceFilter(ctx, referenceFilter)) + default: + return nil, errors.Errorf("invalid filter %s ", splitFilter[0]) + } + } + if img != nil { + filterFuncs = append(filterFuncs, OutputImageFilter(img)) + } + return filterFuncs, nil +} diff --git a/libpod/image/image.go b/libpod/image/image.go index 129ccd376..c8583a1c5 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -216,6 +216,19 @@ func (ir *Runtime) Shutdown(force bool) error { return err } +// GetImagesWithFilters gets images with a series of filters applied +func (ir *Runtime) GetImagesWithFilters(filters []string) ([]*Image, error) { + filterFuncs, err := ir.createFilterFuncs(filters, nil) + if err != nil { + return nil, err + } + images, err := ir.GetImages() + if err != nil { + return nil, err + } + return FilterImages(images, filterFuncs), nil +} + func (i *Image) reloadImage() error { newImage, err := i.imageruntime.getImage(i.ID()) if err != nil { diff --git a/libpod/oci.go b/libpod/oci.go index 9e761788e..05a2f37db 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -23,9 +23,6 @@ type OCIRuntime interface { // CreateContainer creates the container in the OCI runtime. CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error // UpdateContainerStatus updates the status of the given container. - // It includes a switch for whether to perform a hard query of the - // runtime. If unset, the exit file (if supported by the implementation) - // will be used. UpdateContainerStatus(ctr *Container) error // StartContainer starts the given container. StartContainer(ctr *Container) error @@ -59,6 +56,9 @@ type OCIRuntime interface { // If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will // be omitted. ExecStopContainer(ctr *Container, sessionID string, timeout uint) error + // ExecUpdateStatus checks the status of a given exec session. + // Returns true if the session is still running, or false if it exited. + ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) // ExecContainerCleanup cleans up after an exec session exits. // It removes any files left by the exec session that are no longer // needed, including the attach socket. diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 026b13129..37aa71cbb 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -687,6 +687,28 @@ func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, t return nil } +// ExecUpdateStatus checks if the given exec session is still running. +func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { + session, ok := ctr.state.ExecSessions[sessionID] + if !ok { + // TODO This should probably be a separate error + return false, errors.Wrapf(define.ErrInvalidArg, "no exec session with ID %s found in container %s", sessionID, ctr.ID()) + } + + logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID) + + // Is the session dead? + // Ping the PID with signal 0 to see if it still exists. + if err := unix.Kill(session.PID, 0); err != nil { + if err == unix.ESRCH { + return false, nil + } + return false, errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, session.PID) + } + + return true, nil +} + // ExecCleanupContainer 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 { diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index d4524cd34..0faa1805b 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -120,6 +120,11 @@ func (r *MissingRuntime) ExecStopContainer(ctr *Container, sessionID string, tim return r.printError() } +// ExecUpdateStatus is not available as the runtime is missing. +func (r *MissingRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { + return false, r.printError() +} + // ExecContainerCleanup is not available as the runtime is missing func (r *MissingRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { return r.printError() diff --git a/libpod/runtime.go b/libpod/runtime.go index 3873079ce..001d850b0 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -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 } |