diff options
Diffstat (limited to 'libpod')
44 files changed, 341 insertions, 477 deletions
diff --git a/libpod/boltdb_state_linux.go b/libpod/boltdb_state_linux.go index e39b151f7..65efd5703 100644 --- a/libpod/boltdb_state_linux.go +++ b/libpod/boltdb_state_linux.go @@ -28,7 +28,7 @@ func replaceNetNS(netNSPath string, ctr *Container, newState *ContainerState) er newState.NetNS = ns } else { if ctr.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { - return errors.Wrapf(err, "error joning network namespace of container %s", ctr.ID()) + return errors.Wrapf(err, "error joining network namespace of container %s", ctr.ID()) } logrus.Errorf("error joining network namespace for container %s: %v", ctr.ID(), err) diff --git a/libpod/container_api.go b/libpod/container_api.go index 1b33f16b4..c3e1a23d2 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -571,7 +571,7 @@ func (c *Container) Cleanup(ctx context.Context) error { // Batch starts a batch operation on the given container // All commands in the passed function will execute under the same lock and -// without syncronyzing state after each operation +// without synchronizing state after each operation // This will result in substantial performance benefits when running numerous // commands on the same container // Note that the container passed into the Batch function cannot be removed diff --git a/libpod/container_config.go b/libpod/container_config.go index c95be9b55..93ac8807d 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -151,7 +151,7 @@ type ContainerRootFSConfig struct { // ContainerSecurityConfig is an embedded sub-config providing security configuration // to the container. type ContainerSecurityConfig struct { - // Pirivileged is whether the container is privileged. Privileged + // Privileged is whether the container is privileged. Privileged // containers have lessened security and increased access to the system. // Note that this does NOT directly correspond to Podman's --privileged // flag - most of the work of that flag is done in creating the OCI spec diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 2ce3e8e68..51474471b 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -8,6 +8,7 @@ import ( "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/driver" "github.com/containers/podman/v2/pkg/util" + units "github.com/docker/go-units" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/validate" @@ -124,8 +125,6 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) HostnamePath: hostnamePath, HostsPath: hostsPath, StaticDir: config.StaticDir, - LogPath: config.LogPath, - LogTag: config.LogTag, OCIRuntime: config.OCIRuntime, ConmonPidFile: config.ConmonPidFile, Name: config.Name, @@ -354,6 +353,10 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named logConfig := new(define.InspectLogConfig) logConfig.Type = c.config.LogDriver + logConfig.Path = c.config.LogPath + logConfig.Size = units.HumanSize(float64(c.config.LogSize)) + logConfig.Tag = c.config.LogTag + hostConfig.LogConfig = logConfig restartPolicy := new(define.InspectRestartPolicy) diff --git a/libpod/container_internal.go b/libpod/container_internal.go index c751d775d..540230c26 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -884,9 +884,9 @@ func (c *Container) startDependencies(ctx context.Context) error { // getAllDependencies is a precursor to starting dependencies. // To start a container with all of its dependencies, we need to recursively find all dependencies // a container has, as well as each of those containers' dependencies, and so on -// To do so, keep track of containers already visisted (so there aren't redundant state lookups), +// To do so, keep track of containers already visited (so there aren't redundant state lookups), // and recursively search until we have reached the leafs of every dependency node. -// Since we need to start all dependencies for our original container to successfully start, we propegate any errors +// Since we need to start all dependencies for our original container to successfully start, we propagate any errors // in looking up dependencies. // Note: this function is currently meant as a robust solution to a narrow problem: start an infra-container when // a container in the pod is run. It has not been tested for performance past one level, so expansion of recursive start diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 1bf044f9d..05b149e03 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -424,11 +424,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } if c.config.User != "" { - if rootless.IsRootless() { - if err := util.CheckRootlessUIDRange(execUser.Uid); err != nil { - return nil, err - } - } // User and Group must go together g.SetProcessUID(uint32(execUser.Uid)) g.SetProcessGID(uint32(execUser.Gid)) @@ -1664,7 +1659,7 @@ func (c *Container) getHosts() string { // generateGroupEntry generates an entry or entries into /etc/group as // required by container configuration. -// Generatlly speaking, we will make an entry under two circumstances: +// Generally speaking, we will make an entry under two circumstances: // 1. The container is started as a specific user:group, and that group is both // numeric, and does not already exist in /etc/group. // 2. It is requested that Libpod add the group that launched Podman to @@ -1942,7 +1937,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) { needGroup = false } - // Next, check if we already made the files. If we didn, don't need to + // Next, check if we already made the files. If we didn't, don't need to // do anything more. if needPasswd { passwdPath := filepath.Join(c.config.StaticDir, "passwd") diff --git a/libpod/container_log.go b/libpod/container_log.go index 03cb09052..e58503bd3 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -23,7 +23,7 @@ func (r *Runtime) Log(ctx context.Context, containers []*Container, options *log return nil } -// ReadLog reads a containers log based on the input options and returns loglines over a channel. +// ReadLog reads a containers log based on the input options and returns log lines over a channel. func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { switch c.LogDriver() { case define.NoLogging: diff --git a/libpod/container_top_unsupported.go b/libpod/container_top_unsupported.go index f911837d2..866fe106f 100644 --- a/libpod/container_top_unsupported.go +++ b/libpod/container_top_unsupported.go @@ -14,7 +14,7 @@ func (c *Container) Top(descriptors []string) ([]string, error) { // the container. The output data can be controlled via the `descriptors` // argument which expects format descriptors and supports all AIXformat // descriptors of ps (1) plus some additional ones to for instance inspect the -// set of effective capabilities. Eeach element in the returned string slice +// set of effective capabilities. Each element in the returned string slice // is a tab-separated string. // // For more details, please refer to github.com/containers/psgo. diff --git a/libpod/container_validate.go b/libpod/container_validate.go index fa809436e..57bb929dd 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -88,7 +88,7 @@ func (c *Container) validate() error { return errors.Wrapf(define.ErrInvalidArg, "cannot add to /etc/hosts if using image's /etc/hosts") } - // Check named volume, overlay volume and image volume destination conflits + // Check named volume, overlay volume and image volume destination conflist destinations := make(map[string]bool) for _, vol := range c.config.NamedVolumes { // Don't check if they already exist. diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 775965477..c61f7c159 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -82,10 +82,15 @@ type InspectRestartPolicy struct { } // InspectLogConfig holds information about a container's configured log driver -// and is presently unused. It is retained for Docker compatibility. type InspectLogConfig struct { Type string `json:"Type"` Config map[string]string `json:"Config"` //idk type, TODO + // Path specifies a path to the log file + Path string `json:"Path"` + // Tag specifies a custom log tag for the container + Tag string `json:"Tag"` + // Size specifies a maximum size of the container log + Size string `json:"Size"` } // InspectBlkioWeightDevice holds information about the relative weight @@ -152,7 +157,7 @@ type InspectMount struct { // "volume" and "bind". Type string `json:"Type"` // The name of the volume. Empty for bind mounts. - Name string `json:"Name,omptempty"` + Name string `json:"Name,omitempty"` // The source directory for the volume. Source string `json:"Source"` // The destination directory for the volume. Specified as a path within @@ -547,7 +552,7 @@ type InspectBasicNetworkConfig struct { // GlobalIPv6PrefixLen is the length of the subnet mask of this network. GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen"` // SecondaryIPv6Addresses is a list of extra IPv6 Addresses that the - // container has been assigned in this networ. + // container has been assigned in this network. SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty"` // MacAddress is the MAC address for the interface in this network. MacAddress string `json:"MacAddress"` @@ -620,8 +625,6 @@ type InspectContainerData struct { StaticDir string `json:"StaticDir"` 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"` diff --git a/libpod/define/info.go b/libpod/define/info.go index f0e05801c..00146da48 100644 --- a/libpod/define/info.go +++ b/libpod/define/info.go @@ -12,6 +12,15 @@ type Info struct { } //HostInfo describes the libpod host +type SecurityInfo struct { + AppArmorEnabled bool `json:"apparmorEnabled"` + DefaultCapabilities string `json:"capabilities"` + Rootless bool `json:"rootless"` + SECCOMPEnabled bool `json:"seccompEnabled"` + SELinuxEnabled bool `json:"selinuxEnabled"` +} + +//HostInfo describes the libpod host type HostInfo struct { Arch string `json:"arch"` BuildahVersion string `json:"buildahVersion"` @@ -29,8 +38,8 @@ type HostInfo struct { OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` OS string `json:"os"` RemoteSocket *RemoteSocket `json:"remoteSocket,omitempty"` - Rootless bool `json:"rootless"` RuntimeInfo map[string]interface{} `json:"runtimeInfo,omitempty"` + Security SecurityInfo `json:"security"` Slirp4NetNS SlirpInfo `json:"slirp4netns,omitempty"` SwapFree int64 `json:"swapFree"` SwapTotal int64 `json:"swapTotal"` diff --git a/libpod/events/events.go b/libpod/events/events.go index 4e7267af3..aa0401b62 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -216,8 +216,5 @@ func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { reopen = false } stream := options.Stream - if len(options.Until) > 0 { - stream = false - } return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true}) } diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index 9a514e302..8b7e448b1 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/containers/podman/v2/pkg/util" "github.com/coreos/go-systemd/v22/journal" "github.com/coreos/go-systemd/v22/sdjournal" "github.com/pkg/errors" @@ -72,11 +73,22 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { if err != nil { return errors.Wrapf(err, "failed to generate event options") } + var untilTime time.Time + if len(options.Until) > 0 { + untilTime, err = util.ParseInputTime(options.Until) + if err != nil { + return err + } + } j, err := sdjournal.NewJournal() if err != nil { return err } - + defer func() { + if err := j.Close(); err != nil { + logrus.Errorf("Unable to close journal :%v", err) + } + }() // match only podman journal entries podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} if err := j.AddMatch(podmanJournal.String()); err != nil { @@ -104,7 +116,6 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { if err != nil { return errors.Wrap(err, "failed to get journal cursor") } - for { select { case <-ctx.Done(): @@ -122,10 +133,14 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { return errors.Wrap(err, "failed to get journal cursor") } if prevCursor == newCursor { - if len(options.Until) > 0 || !options.Stream { + if !options.Stream || (len(options.Until) > 0 && time.Now().After(untilTime)) { break } - _ = j.Wait(sdjournal.IndefiniteWait) + t := sdjournal.IndefiniteWait + if len(options.Until) > 0 { + t = time.Until(untilTime) + } + _ = j.Wait(t) continue } prevCursor = newCursor diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index 57e38b815..05ae3ce52 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "os" + "time" + "github.com/containers/podman/v2/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" ) @@ -51,6 +53,16 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { if err != nil { return err } + if len(options.Until) > 0 { + untilTime, err := util.ParseInputTime(options.Until) + if err != nil { + return err + } + go func() { + time.Sleep(time.Until(untilTime)) + t.Stop() + }() + } funcDone := make(chan bool) copy := true go func() { diff --git a/libpod/filters/containers.go b/libpod/filters/containers.go deleted file mode 100644 index 2520c4f30..000000000 --- a/libpod/filters/containers.go +++ /dev/null @@ -1,208 +0,0 @@ -package lpfilters - -import ( - "strconv" - "strings" - "time" - - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/pkg/timetype" - "github.com/containers/podman/v2/pkg/util" - "github.com/pkg/errors" -) - -// GenerateContainerFilterFuncs return ContainerFilter functions based of filter. -func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpod.Runtime) (func(container *libpod.Container) bool, error) { - switch filter { - case "id": - // we only have to match one ID - return func(c *libpod.Container) bool { - return util.StringMatchRegexSlice(c.ID(), filterValues) - }, nil - case "label": - // we have to match that all given labels exits on that container - return func(c *libpod.Container) bool { - labels := c.Labels() - for _, filterValue := range filterValues { - matched := false - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - for labelKey, labelValue := range labels { - if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { - matched = true - break - } - } - if !matched { - return false - } - } - return true - }, nil - case "name": - // we only have to match one name - return func(c *libpod.Container) bool { - return util.StringMatchRegexSlice(c.Name(), filterValues) - }, nil - case "exited": - var exitCodes []int32 - for _, exitCode := range filterValues { - ec, err := strconv.ParseInt(exitCode, 10, 32) - if err != nil { - return nil, errors.Wrapf(err, "exited code out of range %q", ec) - } - exitCodes = append(exitCodes, int32(ec)) - } - return func(c *libpod.Container) bool { - ec, exited, err := c.ExitCode() - if err == nil && exited { - for _, exitCode := range exitCodes { - if ec == exitCode { - return true - } - } - } - return false - }, nil - case "status": - for _, filterValue := range filterValues { - if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "exited", "unknown"}) { - return nil, errors.Errorf("%s is not a valid status", filterValue) - } - } - return func(c *libpod.Container) bool { - status, err := c.State() - if err != nil { - return false - } - state := status.String() - if status == define.ContainerStateConfigured { - state = "created" - } else if status == define.ContainerStateStopped { - state = "exited" - } - for _, filterValue := range filterValues { - if filterValue == "stopped" { - filterValue = "exited" - } - if state == filterValue { - return true - } - } - return false - }, nil - case "ancestor": - // This needs to refine to match docker - // - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant. - return func(c *libpod.Container) bool { - for _, filterValue := range filterValues { - containerConfig := c.Config() - if strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) { - return true - } - } - return false - }, nil - case "before": - var createTime time.Time - for _, filterValue := range filterValues { - ctr, err := r.LookupContainer(filterValue) - if err != nil { - return nil, err - } - containerConfig := ctr.Config() - if createTime.IsZero() || createTime.After(containerConfig.CreatedTime) { - createTime = containerConfig.CreatedTime - } - } - return func(c *libpod.Container) bool { - cc := c.Config() - return createTime.After(cc.CreatedTime) - }, nil - case "since": - var createTime time.Time - for _, filterValue := range filterValues { - ctr, err := r.LookupContainer(filterValue) - if err != nil { - return nil, err - } - containerConfig := ctr.Config() - if createTime.IsZero() || createTime.After(containerConfig.CreatedTime) { - createTime = containerConfig.CreatedTime - } - } - return func(c *libpod.Container) bool { - cc := c.Config() - return createTime.Before(cc.CreatedTime) - }, nil - case "volume": - //- volume=(<volume-name>|<mount-point-destination>) - return func(c *libpod.Container) bool { - containerConfig := c.Config() - var dest string - for _, filterValue := range filterValues { - arr := strings.SplitN(filterValue, ":", 2) - source := arr[0] - if len(arr) == 2 { - dest = arr[1] - } - for _, mount := range containerConfig.Spec.Mounts { - if dest != "" && (mount.Source == source && mount.Destination == dest) { - return true - } - if dest == "" && mount.Source == source { - return true - } - } - for _, vname := range containerConfig.NamedVolumes { - if dest != "" && (vname.Name == source && vname.Dest == dest) { - return true - } - if dest == "" && vname.Name == source { - return true - } - } - } - return false - }, nil - case "health": - return func(c *libpod.Container) bool { - hcStatus, err := c.HealthCheckStatus() - if err != nil { - return false - } - for _, filterValue := range filterValues { - if hcStatus == filterValue { - return true - } - } - return false - }, nil - case "until": - if len(filterValues) != 1 { - return nil, errors.Errorf("specify exactly one timestamp for %s", filter) - } - ts, err := timetype.GetTimestamp(filterValues[0], time.Now()) - if err != nil { - return nil, err - } - seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) - if err != nil { - return nil, err - } - until := time.Unix(seconds, nanoseconds) - return func(c *libpod.Container) bool { - if !until.IsZero() && c.CreatedTime().After((until)) { - return true - } - return false - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} diff --git a/libpod/filters/pods.go b/libpod/filters/pods.go deleted file mode 100644 index 17b3f3ca9..000000000 --- a/libpod/filters/pods.go +++ /dev/null @@ -1,139 +0,0 @@ -package lpfilters - -import ( - "strconv" - "strings" - - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/pkg/util" - "github.com/pkg/errors" -) - -// GeneratePodFilterFunc takes a filter and filtervalue (key, value) -// and generates a libpod function that can be used to filter -// pods -func GeneratePodFilterFunc(filter string, filterValues []string) ( - func(pod *libpod.Pod) bool, error) { - switch filter { - case "ctr-ids": - return func(p *libpod.Pod) bool { - ctrIds, err := p.AllContainersByID() - if err != nil { - return false - } - for _, id := range ctrIds { - return util.StringMatchRegexSlice(id, filterValues) - } - return false - }, nil - case "ctr-names": - return func(p *libpod.Pod) bool { - ctrs, err := p.AllContainers() - if err != nil { - return false - } - for _, ctr := range ctrs { - return util.StringMatchRegexSlice(ctr.Name(), filterValues) - } - return false - }, nil - case "ctr-number": - return func(p *libpod.Pod) bool { - ctrIds, err := p.AllContainersByID() - if err != nil { - return false - } - for _, filterValue := range filterValues { - fVint, err2 := strconv.Atoi(filterValue) - if err2 != nil { - return false - } - if len(ctrIds) == fVint { - return true - } - } - return false - }, nil - case "ctr-status": - for _, filterValue := range filterValues { - if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "exited", "unknown"}) { - return nil, errors.Errorf("%s is not a valid status", filterValue) - } - } - return func(p *libpod.Pod) bool { - ctrStatuses, err := p.Status() - if err != nil { - return false - } - for _, ctrStatus := range ctrStatuses { - state := ctrStatus.String() - if ctrStatus == define.ContainerStateConfigured { - state = "created" - } else if ctrStatus == define.ContainerStateStopped { - state = "exited" - } - for _, filterValue := range filterValues { - if filterValue == "stopped" { - filterValue = "exited" - } - if state == filterValue { - return true - } - } - } - return false - }, nil - case "id": - return func(p *libpod.Pod) bool { - return util.StringMatchRegexSlice(p.ID(), filterValues) - }, nil - case "name": - return func(p *libpod.Pod) bool { - return util.StringMatchRegexSlice(p.Name(), filterValues) - }, nil - case "status": - for _, filterValue := range filterValues { - if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created", "degraded"}) { - return nil, errors.Errorf("%s is not a valid pod status", filterValue) - } - } - return func(p *libpod.Pod) bool { - status, err := p.GetPodStatus() - if err != nil { - return false - } - for _, filterValue := range filterValues { - if strings.ToLower(status) == filterValue { - return true - } - } - return false - }, nil - case "label": - return func(p *libpod.Pod) bool { - labels := p.Labels() - for _, filterValue := range filterValues { - matched := false - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - for labelKey, labelValue := range labels { - if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { - matched = true - break - } - } - if !matched { - return false - } - } - return true - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} diff --git a/libpod/image/df.go b/libpod/image/df.go index 84cf7af9e..231d28df4 100644 --- a/libpod/image/df.go +++ b/libpod/image/df.go @@ -51,7 +51,7 @@ func (ir *Runtime) DiskUsage(ctx context.Context, images []*Image) ([]DiskUsageS return stats, nil } -// diskUsageForImage returns the disk-usage statistics for the spcified image. +// diskUsageForImage returns the disk-usage statistics for the specified image. func diskUsageForImage(ctx context.Context, image *Image, tree *layerTree) (*DiskUsageStat, error) { stat := DiskUsageStat{ ID: image.ID(), diff --git a/libpod/image/image.go b/libpod/image/image.go index 5c3f3b9e4..a9082b2c6 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -497,7 +497,7 @@ func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, erro return inputName, repoImage, nil } - return "", nil, errors.Wrapf(ErrNoSuchImage, err.Error()) + return "", nil, err } // ID returns the image ID as a string diff --git a/libpod/image/parts.go b/libpod/image/parts.go index d6c98783b..08421320c 100644 --- a/libpod/image/parts.go +++ b/libpod/image/parts.go @@ -50,7 +50,7 @@ func decompose(input string) (imageParts, error) { // suspiciousRefNameTagValuesForSearch returns a "tag" value used in a previous implementation. // This exists only to preserve existing behavior in heuristic code; it’s dubious that that behavior is correct, -// gespecially for the tag value. +// especially for the tag value. func (ip *imageParts) suspiciousRefNameTagValuesForSearch() (string, string, string) { registry := reference.Domain(ip.unnormalizedRef) imageName := reference.Path(ip.unnormalizedRef) diff --git a/libpod/image/prune.go b/libpod/image/prune.go index b38265a7e..3c06a89c2 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -6,6 +6,7 @@ import ( "time" "github.com/containers/podman/v2/libpod/events" + "github.com/containers/podman/v2/pkg/domain/entities/reports" "github.com/containers/podman/v2/pkg/timetype" "github.com/containers/storage" "github.com/pkg/errors" @@ -110,7 +111,8 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I // PruneImages prunes dangling and optionally all unused images from the local // image store -func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) { +func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]*reports.PruneReport, error) { + preports := make([]*reports.PruneReport, 0) filterFuncs := make([]ImageFilter, 0, len(filter)) for _, f := range filter { filterSplit := strings.SplitN(f, "=", 2) @@ -125,7 +127,6 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ( filterFuncs = append(filterFuncs, generatedFunc) } - pruned := []string{} prev := 0 for { toPrune, err := ir.GetPruneImages(ctx, all, filterFuncs) @@ -143,6 +144,13 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ( if err != nil { return nil, err } + nameOrID := img.ID() + s, err := img.Size(ctx) + imgSize := *s + if err != nil { + logrus.Warnf("Failed to collect image size for: %s, %s", nameOrID, err) + imgSize = 0 + } if err := img.Remove(ctx, false); err != nil { if errors.Cause(err) == storage.ErrImageUsedByContainer { logrus.Warnf("Failed to prune image %s as it is in use: %v.\nA container associated with containers/storage (e.g., Buildah, CRI-O, etc.) maybe associated with this image.\nUsing the rmi command with the --force option will remove the container and image, but may cause failures for other dependent systems.", img.ID(), err) @@ -151,13 +159,18 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ( return nil, errors.Wrap(err, "failed to prune image") } defer img.newImageEvent(events.Prune) - nameOrID := img.ID() + if len(repotags) > 0 { nameOrID = repotags[0] } - pruned = append(pruned, nameOrID) + + preports = append(preports, &reports.PruneReport{ + Id: nameOrID, + Err: nil, + Size: uint64(imgSize), + }) } } - return pruned, nil + return preports, nil } diff --git a/libpod/image/search.go b/libpod/image/search.go index b9acf4a20..6020fbca9 100644 --- a/libpod/image/search.go +++ b/libpod/image/search.go @@ -26,7 +26,7 @@ const ( type SearchResult struct { // Index is the image index (e.g., "docker.io" or "quay.io") Index string - // Name is the canoncical name of the image (e.g., "docker.io/library/alpine"). + // Name is the canonical name of the image (e.g., "docker.io/library/alpine"). Name string // Description of the image. Description string diff --git a/libpod/image/utils.go b/libpod/image/utils.go index 7429a7f10..5e7fed5c6 100644 --- a/libpod/image/utils.go +++ b/libpod/image/utils.go @@ -20,7 +20,11 @@ import ( // a match on name:tag func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, error) { _, searchName, searchSuspiciousTagValueForSearch := search.suspiciousRefNameTagValuesForSearch() - var results []*storage.Image + type Candidate struct { + name string + image *Image + } + var candidates []Candidate for _, image := range images { for _, name := range image.Names() { d, err := decompose(name) @@ -29,23 +33,53 @@ func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, er continue } _, dName, dSuspiciousTagValueForSearch := d.suspiciousRefNameTagValuesForSearch() - if dName == searchName && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch { - results = append(results, image.image) + if dSuspiciousTagValueForSearch != searchSuspiciousTagValueForSearch { continue } - // account for registry:/somedir/image - if strings.HasSuffix(dName, "/"+searchName) && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch { - results = append(results, image.image) - continue + if dName == searchName || strings.HasSuffix(dName, "/"+searchName) { + candidates = append(candidates, Candidate{ + name: name, + image: image, + }) } } } - if len(results) == 0 { - return &storage.Image{}, errors.Errorf("unable to find a name and tag match for %s in repotags", searchName) - } else if len(results) > 1 { - return &storage.Image{}, errors.Wrapf(define.ErrMultipleImages, searchName) + if len(candidates) == 0 { + + return nil, errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", searchName) + } + + // If more then one candidate and the candidates all have same name + // and only one is read/write return it. + // Othewise return error with the list of candidates + if len(candidates) > 1 { + var ( + rwImage *Image + rwImageCnt int + ) + names := make(map[string]bool) + for _, c := range candidates { + names[c.name] = true + if !c.image.IsReadOnly() { + rwImageCnt++ + rwImage = c.image + } + } + // If only one name used and have read/write image return it + if len(names) == 1 && rwImageCnt == 1 { + return rwImage.image, nil + } + keys := []string{} + for k := range names { + keys = append(keys, k) + } + if rwImageCnt > 1 { + return nil, errors.Wrapf(define.ErrMultipleImages, "found multiple read/write images %s", strings.Join(keys, ",")) + } else { + return nil, errors.Wrapf(define.ErrMultipleImages, "found multiple read/only images %s", strings.Join(keys, ",")) + } } - return results[0], nil + return candidates[0].image.image, nil } // getCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters, inheriting some from sc. diff --git a/libpod/info.go b/libpod/info.go index 2f64a107e..1b3550abd 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -13,6 +13,8 @@ import ( "time" "github.com/containers/buildah" + "github.com/containers/common/pkg/apparmor" + "github.com/containers/common/pkg/seccomp" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/linkmode" "github.com/containers/podman/v2/pkg/cgroups" @@ -20,6 +22,7 @@ import ( "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/system" + "github.com/opencontainers/selinux/go-selinux" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -98,10 +101,16 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { MemFree: mi.MemFree, MemTotal: mi.MemTotal, OS: runtime.GOOS, - Rootless: rootless.IsRootless(), - Slirp4NetNS: define.SlirpInfo{}, - SwapFree: mi.SwapFree, - SwapTotal: mi.SwapTotal, + Security: define.SecurityInfo{ + AppArmorEnabled: apparmor.IsEnabled(), + DefaultCapabilities: strings.Join(r.config.Containers.DefaultCapabilities, ","), + Rootless: rootless.IsRootless(), + SECCOMPEnabled: seccomp.IsEnabled(), + SELinuxEnabled: selinux.GetEnabled(), + }, + Slirp4NetNS: define.SlirpInfo{}, + SwapFree: mi.SwapFree, + SwapTotal: mi.SwapTotal, } // CGroups version diff --git a/libpod/kube.go b/libpod/kube.go index bf041112a..753c58099 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -403,7 +403,7 @@ func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) { // libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume, error) { - // TjDO when named volumes are supported in play kube, also parse named volumes here + // TODO when named volumes are supported in play kube, also parse named volumes here _, mounts := c.sortUserVolumes(c.config.Spec) vms := make([]v1.VolumeMount, 0, len(mounts)) vos := make([]v1.Volume, 0, len(mounts)) @@ -524,7 +524,7 @@ func capAddDrop(caps *specs.LinuxCapabilities) (*v1.Capabilities, error) { defaultCaps = append(defaultCaps, g.Config.Process.Capabilities.Inheritable...) defaultCaps = append(defaultCaps, g.Config.Process.Capabilities.Permitted...) - // Combine all the container's capabilities into a slic + // Combine all the container's capabilities into a slice containerCaps := append(caps.Ambient, caps.Bounding...) containerCaps = append(containerCaps, caps.Effective...) containerCaps = append(containerCaps, caps.Inheritable...) diff --git a/libpod/logs/log.go b/libpod/logs/log.go index a9554088b..2637c8524 100644 --- a/libpod/logs/log.go +++ b/libpod/logs/log.go @@ -73,7 +73,7 @@ func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error Whence: whence, } - t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger}) + t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger, ReOpen: options.Follow}) return t, logTail, err } @@ -137,7 +137,7 @@ func getTailLog(path string, tail int) ([]*LogLine, error) { nllCounter++ } } - // if we have enough loglines, we can hangup + // if we have enough log lines, we can hangup if nllCounter >= tail { break } @@ -161,7 +161,7 @@ func getTailLog(path string, tail int) ([]*LogLine, error) { return tailLog, nil } -// String converts a logline to a string for output given whether a detail +// String converts a log line to a string for output given whether a detail // bool is specified. func (l *LogLine) String(options *LogOptions) string { var out string @@ -210,3 +210,19 @@ func NewLogLine(line string) (*LogLine, error) { func (l *LogLine) Partial() bool { return l.ParseLogType == PartialLogType } + +func (l *LogLine) Write(stdout io.Writer, stderr io.Writer, logOpts *LogOptions) { + switch l.Device { + case "stdout": + if stdout != nil { + fmt.Fprintln(stdout, l.String(logOpts)) + } + case "stderr": + if stderr != nil { + fmt.Fprintln(stderr, l.String(logOpts)) + } + default: + // Warn the user if the device type does not match. Most likely the file is corrupted. + logrus.Warnf("unknown Device type '%s' in log file from Container %s", l.Device, l.CID) + } +} diff --git a/libpod/network/create_test.go b/libpod/network/create_test.go index 16188e497..0b828e635 100644 --- a/libpod/network/create_test.go +++ b/libpod/network/create_test.go @@ -61,7 +61,7 @@ func Test_validateBridgeOptions(t *testing.T) { isIPv6: true, }, { - name: "IPv6 subnet, range and gateway without IPv6 option (PODMAN SUPPORTS IT UNLIKE DOCKEr)", + name: "IPv6 subnet, range and gateway without IPv6 option (PODMAN SUPPORTS IT UNLIKE DOCKER)", subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, gateway: net.ParseIP("2001:DB8::2"), diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index d61b96ecb..bf7d03501 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -60,7 +60,7 @@ func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, mtu in return &hostLocalBridge } -// NewIPAMHostLocalConf creates a new IPAMHostLocal configfuration +// NewIPAMHostLocalConf creates a new IPAMHostLocal configuration func NewIPAMHostLocalConf(routes []IPAMRoute, ipamRanges [][]IPAMLocalHostRangeConf) (IPAMHostLocalConf, error) { ipamConf := IPAMHostLocalConf{ PluginType: "host-local", @@ -95,6 +95,10 @@ func NewIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw net.IP) ([] } if gw != nil { hostRange.Gateway = gw.String() + } else { + // Add first ip in subnet as gateway. It is not required + // by cni but should be included because of network inspect. + hostRange.Gateway = CalcGatewayIP(subnet).String() } ranges = append(ranges, hostRange) return ranges, nil diff --git a/libpod/network/netconflist_test.go b/libpod/network/netconflist_test.go index 6bf1a9777..5ff733f0f 100644 --- a/libpod/network/netconflist_test.go +++ b/libpod/network/netconflist_test.go @@ -51,7 +51,8 @@ func TestNewIPAMLocalHostRange(t *testing.T) { subnet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, want: []IPAMLocalHostRangeConf{ { - Subnet: "192.168.0.0/24", + Subnet: "192.168.0.0/24", + Gateway: "192.168.0.1", }, }, }, @@ -74,7 +75,8 @@ func TestNewIPAMLocalHostRange(t *testing.T) { subnet: &net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, want: []IPAMLocalHostRangeConf{ { - Subnet: "2001:db8::/48", + Subnet: "2001:db8::/48", + Gateway: "2001:db8::1", }, }, }, diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index bf27989bf..be6867399 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -247,6 +247,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { func (r *Runtime) setupSlirp4netns(ctr *Container) error { path := r.config.Engine.NetworkCmdPath slirpOptions := r.config.Engine.NetworkCmdOptions + noPivotRoot := r.config.Engine.NoPivotRoot if path == "" { var err error path, err = exec.LookPath("slirp4netns") @@ -351,7 +352,7 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { if slirpFeatures.HasMTU { cmdArgs = append(cmdArgs, "--mtu", "65520") } - if slirpFeatures.HasEnableSandbox { + if !noPivotRoot && slirpFeatures.HasEnableSandbox { cmdArgs = append(cmdArgs, "--enable-sandbox") } if slirpFeatures.HasEnableSeccomp { @@ -424,7 +425,7 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { } // workaround for https://github.com/rootless-containers/slirp4netns/pull/153 - if slirpFeatures.HasEnableSandbox { + if !noPivotRoot && slirpFeatures.HasEnableSandbox { cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNS cmd.SysProcAttr.Unshareflags = syscall.CLONE_NEWNS } @@ -1155,7 +1156,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro return c.save() } -// ConnnectNetwork connects a container to a given network +// ConnectNetwork connects a container to a given network func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) error { networks, err := c.networksByNameIndex() if err != nil { diff --git a/libpod/oci.go b/libpod/oci.go index 924c32510..157c42c38 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -56,7 +56,7 @@ type OCIRuntime interface { // a header prepended as follows: 1-byte STREAM (0, 1, 2 for STDIN, // STDOUT, STDERR), 3 null (0x00) bytes, 4-byte big endian length. // If a cancel channel is provided, it can be used to asynchronously - // termninate the attach session. Detach keys, if given, will also cause + // terminate the attach session. Detach keys, if given, will also cause // the attach session to be terminated if provided via the STDIN // channel. If they are not provided, the default detach keys will be // used instead. Detach keys of "" will disable detaching via keyboard. diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index 149ee813b..fbc95510e 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -83,7 +83,7 @@ func (c *Container) attach(streams *define.AttachStreams, keys string, resize <- // Attach to the given container's exec session // attachFd and startFd must be open file descriptors // attachFd must be the output side of the fd. attachFd is used for two things: -// conmon will first send a nonse value across the pipe indicating it has set up its side of the console socket +// conmon will first send a nonce value across the pipe indicating it has set up its side of the console socket // this ensures attachToExec gets all of the output of the called process // conmon will then send the exit code of the exec process, or an error in the exec session // startFd must be the input side of the fd. diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go index f8e7020f7..4546acefb 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -387,7 +387,7 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex finalEnv = append(finalEnv, fmt.Sprintf("%s=%s", k, v)) } - processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID) + processFile, err := prepareProcessExec(c, options, finalEnv, sessionID) if err != nil { return nil, nil, err } diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 307b9bc54..e7cb5a802 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -47,7 +47,7 @@ import ( const ( // This is Conmon's STDIO_BUF_SIZE. I don't believe we have access to it - // directly from the Go cose, so const it here + // directly from the Go code, so const it here bufferSize = conmonConfig.BufSize ) @@ -69,6 +69,7 @@ type ConmonOCIRuntime struct { supportsKVM bool supportsNoCgroups bool sdNotify bool + enableKeyring bool } // Make a new Conmon-based OCI runtime with the given options. @@ -107,6 +108,7 @@ func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtime runtime.noPivot = runtimeCfg.Engine.NoPivotRoot runtime.reservePorts = runtimeCfg.Engine.EnablePortReservation runtime.sdNotify = runtimeCfg.Engine.SDNotify + runtime.enableKeyring = runtimeCfg.Containers.EnableKeyring // TODO: probe OCI runtime for feature and enable automatically if // available. @@ -191,6 +193,11 @@ func hasCurrentUserMapped(ctr *Container) bool { // CreateContainer creates a container. func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { + // always make the run dir accessible to the current user so that the PID files can be read without + // being in the rootless user namespace. + if err := makeAccessible(ctr.state.RunDir, 0, 0); err != nil { + return err + } if !hasCurrentUserMapped(ctr) { for _, i := range []string{ctr.state.RunDir, ctr.runtime.config.Engine.TmpDir, ctr.config.StaticDir, ctr.state.Mountpoint, ctr.runtime.config.Engine.VolumePath} { if err := makeAccessible(i, ctr.RootUID(), ctr.RootGID()); err != nil { @@ -1021,6 +1028,9 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co args = append(args, "-i") } + if !r.enableKeyring { + args = append(args, "--no-new-keyring") + } if ctr.config.ConmonPidFile != "" { args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile) } @@ -1180,26 +1190,36 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co // prepareProcessExec returns the path of the process.json used in runc exec -p // caller is responsible to close the returned *os.File if needed. -func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, sessionID string) (*os.File, error) { +func prepareProcessExec(c *Container, options *ExecOptions, env []string, sessionID string) (*os.File, error) { f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-") if err != nil { return nil, err } - pspec := c.config.Spec.Process + pspec := new(spec.Process) + if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil { + return nil, err + } pspec.SelinuxLabel = c.config.ProcessLabel - pspec.Args = cmd + pspec.Args = options.Cmd + for _, cap := range options.CapAdd { + pspec.Capabilities.Bounding = append(pspec.Capabilities.Bounding, cap) + pspec.Capabilities.Effective = append(pspec.Capabilities.Effective, cap) + pspec.Capabilities.Inheritable = append(pspec.Capabilities.Inheritable, cap) + pspec.Capabilities.Permitted = append(pspec.Capabilities.Permitted, cap) + pspec.Capabilities.Ambient = append(pspec.Capabilities.Ambient, cap) + } // We need to default this to false else it will inherit terminal as true // from the container. pspec.Terminal = false - if tty { + if options.Terminal { pspec.Terminal = true } if len(env) > 0 { pspec.Env = append(pspec.Env, env...) } - if cwd != "" { - pspec.Cwd = cwd + if options.Cwd != "" { + pspec.Cwd = options.Cwd } @@ -1207,6 +1227,7 @@ func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, se var sgids []uint32 // if the user is empty, we should inherit the user that the container is currently running with + user := options.User if user == "" { user = c.config.User addGroups = c.config.Groups @@ -1413,7 +1434,7 @@ func startCommandGivenSelinux(cmd *exec.Cmd) error { } // moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup -// it then signals for conmon to start by sending nonse data down the start fd +// it then signals for conmon to start by sending nonce data down the start fd func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File) error { mustCreateCgroup := true @@ -1433,6 +1454,14 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec } if mustCreateCgroup { + // Usually rootless users are not allowed to configure cgroupfs. + // There are cases though, where it is allowed, e.g. if the cgroup + // is manually configured and chowned). Avoid detecting all + // such cases and simply use a lower log level. + logLevel := logrus.WarnLevel + if rootless.IsRootless() { + logLevel = logrus.InfoLevel + } // TODO: This should be a switch - we are not guaranteed that // there are only 2 valid cgroup managers cgroupParent := ctr.CgroupParent() @@ -1447,17 +1476,17 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { - logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) + logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to systemd sandbox cgroup: %v", err) } } else { cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) if err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to cgroupfs sandbox cgroup: %v", err) } else if err := control.AddPid(cmd.Process.Pid); err != nil { // we need to remove this defer and delete the cgroup once conmon exits // maybe need a conmon monitor? - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to cgroupfs sandbox cgroup: %v", err) } } } @@ -1564,7 +1593,7 @@ func readConmonPipeData(pipe *os.File, ociLog string) (int, error) { return data, nil } -// writeConmonPipeData writes nonse data to a pipe +// writeConmonPipeData writes nonce data to a pipe func writeConmonPipeData(pipe *os.File) error { someData := []byte{0} _, err := pipe.Write(someData) diff --git a/libpod/options.go b/libpod/options.go index bd12c0c34..8100eee62 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -502,6 +502,7 @@ func WithEventsLogger(logger string) RuntimeOption { } rt.config.Engine.EventsLogger = logger + rt.config.Engine.EventsLogFilePath = filepath.Join(rt.config.Engine.TmpDir, "events", "events.log") return nil } @@ -750,7 +751,7 @@ func WithStopTimeout(timeout uint) CtrCreateOption { } } -// WithIDMappings sets the idmappsings for the container +// WithIDMappings sets the idmappings for the container func WithIDMappings(idmappings storage.IDMappingOptions) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { @@ -1592,7 +1593,7 @@ func WithVolumeOptions(options map[string]string) VolumeCreateOption { volume.config.Options = make(map[string]string) for key, value := range options { switch key { - case "type", "device", "o": + case "type", "device", "o", "UID", "GID": volume.config.Options[key] = value default: return errors.Wrapf(define.ErrInvalidArg, "unrecognized volume option %q is not supported with local driver", key) diff --git a/libpod/pod_top_linux.go b/libpod/pod_top_linux.go index 0e42c62df..07e1a0d80 100644 --- a/libpod/pod_top_linux.go +++ b/libpod/pod_top_linux.go @@ -15,7 +15,7 @@ import ( // the pod. The output data can be controlled via the `descriptors` // argument which expects format descriptors and supports all AIXformat // descriptors of ps (1) plus some additional ones to for instance inspect the -// set of effective capabilities. Eeach element in the returned string slice +// set of effective capabilities. Each element in the returned string slice // is a tab-separated string. // // For more details, please refer to github.com/containers/psgo. diff --git a/libpod/reset.go b/libpod/reset.go index 6d2842723..24efeed40 100644 --- a/libpod/reset.go +++ b/libpod/reset.go @@ -77,18 +77,35 @@ func (r *Runtime) Reset(ctx context.Context) error { } } + xdgRuntimeDir := filepath.Clean(os.Getenv("XDG_RUNTIME_DIR")) _, prevError := r.store.Shutdown(true) - if err := os.RemoveAll(r.store.GraphRoot()); err != nil { + graphRoot := filepath.Clean(r.store.GraphRoot()) + if graphRoot == xdgRuntimeDir { if prevError != nil { logrus.Error(prevError) } - prevError = err + prevError = errors.Errorf("failed to remove runtime graph root dir %s, since it is the same as XDG_RUNTIME_DIR", graphRoot) + } else { + if err := os.RemoveAll(graphRoot); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } } - if err := os.RemoveAll(r.store.RunRoot()); err != nil { + runRoot := filepath.Clean(r.store.RunRoot()) + if runRoot == xdgRuntimeDir { if prevError != nil { logrus.Error(prevError) } - prevError = err + prevError = errors.Errorf("failed to remove runtime root dir %s, since it is the same as XDG_RUNTIME_DIR", runRoot) + } else { + if err := os.RemoveAll(runRoot); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } } runtimeDir, err := util.GetRuntimeDir() if err != nil { @@ -98,13 +115,19 @@ func (r *Runtime) Reset(ctx context.Context) error { if tempDir == runtimeDir { tempDir = filepath.Join(tempDir, "containers") } - if err := os.RemoveAll(tempDir); err != nil { + if filepath.Clean(tempDir) == xdgRuntimeDir { if prevError != nil { logrus.Error(prevError) } - prevError = err + prevError = errors.Errorf("failed to remove runtime tmpdir %s, since it is the same as XDG_RUNTIME_DIR", tempDir) + } else { + if err := os.RemoveAll(tempDir); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } } - if storageConfPath, err := storage.DefaultConfigFile(rootless.IsRootless()); err == nil { if _, err = os.Stat(storageConfPath); err == nil { fmt.Printf("A storage.conf file exists at %s\n", storageConfPath) diff --git a/libpod/rootless_cni_linux.go b/libpod/rootless_cni_linux.go index 2c2977f9f..ce8a87759 100644 --- a/libpod/rootless_cni_linux.go +++ b/libpod/rootless_cni_linux.go @@ -100,7 +100,7 @@ func DeallocRootlessCNI(ctx context.Context, c *Container) error { } var errs *multierror.Error for _, nw := range networks { - err := rootlessCNIInfraCallDelloc(infra, c.ID(), nw) + err := rootlessCNIInfraCallDealloc(infra, c.ID(), nw) if err != nil { errs = multierror.Append(errs, err) } @@ -154,7 +154,7 @@ func rootlessCNIInfraCallAlloc(infra *Container, id, nw, k8sPodName string) (*cn return &cniRes, nil } -func rootlessCNIInfraCallDelloc(infra *Container, id, nw string) error { +func rootlessCNIInfraCallDealloc(infra *Container, id, nw string) error { logrus.Debugf("rootless CNI: dealloc %q, %q", id, nw) _, err := rootlessCNIInfraExec(infra, "dealloc", id, nw) return err diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 14b537ca2..f22e48746 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -14,6 +14,7 @@ import ( "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/cgroups" + "github.com/containers/podman/v2/pkg/domain/entities/reports" "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" @@ -884,9 +885,8 @@ func (r *Runtime) GetExecSessionContainer(id string) (*Container, error) { // PruneContainers removes stopped and exited containers from localstorage. A set of optional filters // can be provided to be more granular. -func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int64, map[string]error, error) { - pruneErrors := make(map[string]error) - prunedContainers := make(map[string]int64) +func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) ([]*reports.PruneReport, error) { + preports := make([]*reports.PruneReport, 0) // We add getting the exited and stopped containers via a filter containerStateFilter := func(c *Container) bool { if c.PodID() != "" { @@ -906,23 +906,28 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int filterFuncs = append(filterFuncs, containerStateFilter) delContainers, err := r.GetContainers(filterFuncs...) if err != nil { - return nil, nil, err + return nil, err } for _, c := range delContainers { - ctr := c - size, err := ctr.RWSize() + report := new(reports.PruneReport) + report.Id = c.ID() + report.Err = nil + report.Size = 0 + size, err := c.RWSize() if err != nil { - pruneErrors[ctr.ID()] = err + report.Err = err + preports = append(preports, report) continue } - err = r.RemoveContainer(context.Background(), ctr, false, false) + err = r.RemoveContainer(context.Background(), c, false, false) if err != nil { - pruneErrors[ctr.ID()] = err + report.Err = err } else { - prunedContainers[ctr.ID()] = size + report.Size = (uint64)(size) } + preports = append(preports, report) } - return prunedContainers, pruneErrors, nil + return preports, nil } // MountStorageContainer mounts the storage container's root filesystem diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index a2d9a875e..965333f77 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -230,7 +230,7 @@ func (r *Runtime) Import(ctx context.Context, source, reference, signaturePolicy return newImage.ID(), nil } -// donwloadFromURL downloads an image in the format "https:/example.com/myimage.tar" +// downloadFromURL downloads an image in the format "https:/example.com/myimage.tar" // and temporarily saves in it $TMPDIR/importxyz, which is deleted after the image is imported func downloadFromURL(source string) (string, error) { fmt.Printf("Downloading from %q\n", source) diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go index 055a243c0..9d985f905 100644 --- a/libpod/runtime_volume.go +++ b/libpod/runtime_volume.go @@ -5,6 +5,7 @@ import ( "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/events" + "github.com/containers/podman/v2/pkg/domain/entities/reports" "github.com/pkg/errors" ) @@ -133,22 +134,32 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) { } // PruneVolumes removes unused volumes from the system -func (r *Runtime) PruneVolumes(ctx context.Context) (map[string]error, error) { - reports := make(map[string]error) - vols, err := r.GetAllVolumes() +func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) ([]*reports.PruneReport, error) { + preports := make([]*reports.PruneReport, 0) + vols, err := r.Volumes(filterFuncs...) if err != nil { return nil, err } for _, vol := range vols { + report := new(reports.PruneReport) + volSize, err := vol.Size() + if err != nil { + volSize = 0 + } + report.Size = volSize + report.Id = vol.Name() if err := r.RemoveVolume(ctx, vol, false); err != nil { if errors.Cause(err) != define.ErrVolumeBeingUsed && errors.Cause(err) != define.ErrVolumeRemoved { - reports[vol.Name()] = err + report.Err = err + } else { + // We didn't remove the volume for some reason + continue } - continue + } else { + vol.newVolumeEvent(events.Prune) } - vol.newVolumeEvent(events.Prune) - reports[vol.Name()] = nil + preports = append(preports, report) } - return reports, nil + return preports, nil } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index e1877b17d..9bf0fd108 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -58,7 +58,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) // Validate options for key := range volume.config.Options { switch key { - case "device", "o", "type": + case "device", "o", "type", "UID", "GID": // Do nothing, valid keys default: return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key) diff --git a/libpod/state_test.go b/libpod/state_test.go index da28f3d3f..0709071ec 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -882,7 +882,7 @@ func TestRemoveContainer(t *testing.T) { }) } -func TestRemoveNonexistantContainerFails(t *testing.T) { +func TestRemoveNonexistentContainerFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { testCtr, err := getTestCtr1(manager) assert.NoError(t, err) @@ -1513,7 +1513,7 @@ func TestGetNotExistPodWithPods(t *testing.T) { err = state.AddPod(testPod2) assert.NoError(t, err) - _, err = state.Pod("notexist") + _, err = state.Pod("nonexistent") assert.Error(t, err) }) } @@ -1748,7 +1748,7 @@ func TestHasPodEmptyIDErrors(t *testing.T) { func TestHasPodNoSuchPod(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - exist, err := state.HasPod("notexist") + exist, err := state.HasPod("nonexistent") assert.NoError(t, err) assert.False(t, exist) }) diff --git a/libpod/util.go b/libpod/util.go index c26039c50..bf9bf2542 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -142,29 +142,44 @@ func JSONDeepCopy(from, to interface{}) error { return json.Unmarshal(tmp, to) } -func dpkgVersion(path string) string { +func queryPackageVersion(cmdArg ...string) string { output := unknownPackage - cmd := exec.Command("/usr/bin/dpkg", "-S", path) - if outp, err := cmd.Output(); err == nil { - output = string(outp) + if 1 < len(cmdArg) { + cmd := exec.Command(cmdArg[0], cmdArg[1:]...) + if outp, err := cmd.Output(); err == nil { + output = string(outp) + } } return strings.Trim(output, "\n") } +func equeryVersion(path string) string { + return queryPackageVersion("/usr/bin/equery", "b", path) +} + +func pacmanVersion(path string) string { + return queryPackageVersion("/usr/bin/pacman", "-Qo", path) +} + +func dpkgVersion(path string) string { + return queryPackageVersion("/usr/bin/dpkg", "-S", path) +} + func rpmVersion(path string) string { - output := unknownPackage - cmd := exec.Command("/usr/bin/rpm", "-q", "-f", path) - if outp, err := cmd.Output(); err == nil { - output = string(outp) - } - return strings.Trim(output, "\n") + return queryPackageVersion("/usr/bin/rpm", "-q", "-f", path) } func packageVersion(program string) string { if out := rpmVersion(program); out != unknownPackage { return out } - return dpkgVersion(program) + if out := dpkgVersion(program); out != unknownPackage { + return out + } + if out := pacmanVersion(program); out != unknownPackage { + return out + } + return equeryVersion(program) } func programVersion(mountProgram string) (string, error) { @@ -272,7 +287,7 @@ func writeHijackHeader(r *http.Request, conn io.Writer) { fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") } else { - // Upraded + // Upgraded fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n", proto) diff --git a/libpod/volume.go b/libpod/volume.go index 0535bf4db..ed08d375f 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -1,6 +1,8 @@ package libpod import ( + "os" + "path/filepath" "time" "github.com/containers/podman/v2/libpod/define" @@ -79,6 +81,18 @@ func (v *Volume) Name() string { return v.config.Name } +// Returns the size on disk of volume +func (v *Volume) Size() (uint64, error) { + var size uint64 + err := filepath.Walk(v.config.MountPoint, func(path string, info os.FileInfo, err error) error { + if err == nil && !info.IsDir() { + size += (uint64)(info.Size()) + } + return err + }) + return size, err +} + // Driver retrieves the volume's driver. func (v *Volume) Driver() string { return v.config.Driver |