diff options
Diffstat (limited to 'libpod')
28 files changed, 895 insertions, 293 deletions
diff --git a/libpod/container.go b/libpod/container.go index c1deb95f9..5cd719ab6 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -34,15 +34,6 @@ const SystemdDefaultCgroupParent = "machine.slice" // manager in libpod when running as rootless const SystemdDefaultRootlessCgroupParent = "user.slice" -// JournaldLogging is the string conmon expects to specify journald logging -const JournaldLogging = "journald" - -// KubernetesLogging is the string conmon expects when specifying to use the kubernetes logging format -const KubernetesLogging = "k8s-file" - -// JSONLogging is the string conmon expects when specifying to use the json logging format -const JSONLogging = "json-file" - // DefaultWaitInterval is the default interval between container status checks // while waiting. const DefaultWaitInterval = 250 * time.Millisecond @@ -564,6 +555,11 @@ func (c *Container) MountLabel() string { return c.config.MountLabel } +// Systemd returns whether the container will be running in systemd mode +func (c *Container) Systemd() bool { + return c.config.Systemd +} + // User returns the user who the container is run as func (c *Container) User() string { return c.config.User diff --git a/libpod/container_api.go b/libpod/container_api.go index 967180437..b31079b26 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -3,14 +3,16 @@ package libpod import ( "bufio" "context" - "io" "io/ioutil" "net" "os" + "strings" + "sync" "time" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/libpod/logs" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -96,7 +98,7 @@ func (c *Container) Start(ctx context.Context, recursive bool) (err error) { // The channel will be closed automatically after the result of attach has been // sent. // If recursive is set, StartAndAttach will also start all containers this container depends on. -func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, recursive bool) (attachResChan <-chan error, err error) { +func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, recursive bool) (attachResChan <-chan error, err error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -213,29 +215,10 @@ func (c *Container) Kill(signal uint) error { return c.save() } -// AttachStreams contains streams that will be attached to the container -type AttachStreams struct { - // OutputStream will be attached to container's STDOUT - OutputStream io.WriteCloser - // ErrorStream will be attached to container's STDERR - ErrorStream io.WriteCloser - // InputStream will be attached to container's STDIN - InputStream *bufio.Reader - // AttachOutput is whether to attach to STDOUT - // If false, stdout will not be attached - AttachOutput bool - // AttachError is whether to attach to STDERR - // If false, stdout will not be attached - AttachError bool - // AttachInput is whether to attach to STDIN - // If false, stdout will not be attached - AttachInput bool -} - // Attach attaches to a container. // This function returns when the attach finishes. It does not hold the lock for // the duration of its runtime, only using it at the beginning to verify state. -func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize) error { +func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize) error { if !c.batched { c.lock.Lock() if err := c.syncContainer(); err != nil { @@ -264,15 +247,28 @@ func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan re // forwarded to the client. // This function returns when the attach finishes. It does not hold the lock for // the duration of its runtime, only using it at the beginning to verify state. -func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool) error { +// The streamLogs parameter indicates that all the container's logs until present +// will be streamed at the beginning of the attach. +// The streamAttach parameter indicates that the attach itself will be streamed +// over the socket; if this is not set, but streamLogs is, only the logs will be +// sent. +// At least one of streamAttach and streamLogs must be set. +func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, streamAttach, streamLogs bool) (deferredErr error) { + isTerminal := false + if c.config.Spec.Process != nil { + isTerminal = c.config.Spec.Process.Terminal + } + // Ensure our contract of writing errors to and closing the HTTP conn is + // honored. + defer func() { + hijackWriteErrorAndClose(deferredErr, c.ID(), isTerminal, httpCon, httpBuf) + }() + if !c.batched { c.lock.Lock() if err := c.syncContainer(); err != nil { c.lock.Unlock() - // Write any errors to the HTTP buffer before we close. - hijackWriteErrorAndClose(err, c.ID(), httpCon, httpBuf) - return err } // We are NOT holding the lock for the duration of the function. @@ -280,16 +276,80 @@ func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, stre } if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { - toReturn := errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers") - - // Write any errors to the HTTP buffer before we close. - hijackWriteErrorAndClose(toReturn, c.ID(), httpCon, httpBuf) + return errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers") + } - return toReturn + if !streamAttach && !streamLogs { + return errors.Wrapf(define.ErrInvalidArg, "must specify at least one of stream or logs") } logrus.Infof("Performing HTTP Hijack attach to container %s", c.ID()) + if streamLogs { + // Get all logs for the container + logChan := make(chan *logs.LogLine) + logOpts := new(logs.LogOptions) + logOpts.Tail = -1 + logOpts.WaitGroup = new(sync.WaitGroup) + errChan := make(chan error) + go func() { + var err error + // In non-terminal mode we need to prepend with the + // stream header. + logrus.Debugf("Writing logs for container %s to HTTP attach", c.ID()) + for logLine := range logChan { + if !isTerminal { + device := logLine.Device + var header []byte + headerLen := uint32(len(logLine.Msg)) + + switch strings.ToLower(device) { + case "stdin": + header = makeHTTPAttachHeader(0, headerLen) + case "stdout": + header = makeHTTPAttachHeader(1, headerLen) + case "stderr": + header = makeHTTPAttachHeader(2, headerLen) + default: + logrus.Errorf("Unknown device for log line: %s", device) + header = makeHTTPAttachHeader(1, headerLen) + } + _, err = httpBuf.Write(header) + if err != nil { + break + } + } + _, err = httpBuf.Write([]byte(logLine.Msg)) + if err != nil { + break + } + _, err = httpBuf.Write([]byte("\n")) + if err != nil { + break + } + err = httpBuf.Flush() + if err != nil { + break + } + } + errChan <- err + }() + go func() { + logOpts.WaitGroup.Wait() + close(logChan) + }() + if err := c.ReadLog(logOpts, logChan); err != nil { + return err + } + logrus.Debugf("Done reading logs for container %s", c.ID()) + if err := <-errChan; err != nil { + return err + } + } + if !streamAttach { + return nil + } + c.newContainerEvent(events.Attach) return c.ociRuntime.HTTPAttach(c, httpCon, httpBuf, streams, detachKeys, cancel) } diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 5469462f8..c1ce8b724 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -221,7 +221,7 @@ func (c *Container) ExecStart(sessionID string) error { // ExecStartAndAttach starts and attaches to an exec session in a container. // TODO: Should we include detach keys in the signature to allow override? // TODO: How do we handle AttachStdin/AttachStdout/AttachStderr? -func (c *Container) ExecStartAndAttach(sessionID string, streams *AttachStreams) error { +func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams) error { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -544,7 +544,7 @@ func (c *Container) ExecResize(sessionID string, newSize remotecommand.TerminalS // Exec emulates the old Libpod exec API, providing a single call to create, // run, and remove an exec session. Returns exit code and error. Exit code is // not guaranteed to be set sanely if error is not nil. -func (c *Container) Exec(config *ExecConfig, streams *AttachStreams, resize <-chan remotecommand.TerminalSize) (int, error) { +func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan remotecommand.TerminalSize) (int, error) { sessionID, err := c.ExecCreate(config) if err != nil { return -1, err diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 4e18819b8..50bd9bc25 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -19,6 +19,7 @@ import ( "github.com/containers/libpod/pkg/hooks" "github.com/containers/libpod/pkg/hooks/exec" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/mount" @@ -339,6 +340,29 @@ func (c *Container) syncContainer() error { return nil } +func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) { + if c.config.Rootfs != "" { + return + } + *dest = *from + if dest.AutoUserNs { + overrides := c.getUserOverrides() + dest.AutoUserNsOpts.PasswdFile = overrides.ContainerEtcPasswdPath + dest.AutoUserNsOpts.GroupFile = overrides.ContainerEtcGroupPath + if c.config.User != "" { + initialSize := uint32(0) + parts := strings.Split(c.config.User, ":") + for _, p := range parts { + s, err := strconv.ParseUint(p, 10, 32) + if err == nil && uint32(s) > initialSize { + initialSize = uint32(s) + } + } + dest.AutoUserNsOpts.InitialSize = initialSize + 1 + } + } +} + // Create container root filesystem for use func (c *Container) setupStorage(ctx context.Context) error { span, _ := opentracing.StartSpanFromContext(ctx, "setupStorage") @@ -398,29 +422,45 @@ func (c *Container) setupStorage(ctx context.Context) error { options.MountOpts = newOptions } - if c.config.Rootfs == "" { - options.IDMappingOptions = c.config.IDMappings - } + c.setupStorageMapping(&options.IDMappingOptions, &c.config.IDMappings) + containerInfo, err := c.runtime.storageService.CreateContainerStorage(ctx, c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, options) if err != nil { return errors.Wrapf(err, "error creating container storage") } - if len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0 { - if err := os.Chown(containerInfo.RunDir, c.RootUID(), c.RootGID()); err != nil { + c.config.IDMappings.UIDMap = containerInfo.UIDMap + c.config.IDMappings.GIDMap = containerInfo.GIDMap + + processLabel := containerInfo.ProcessLabel + switch { + case c.ociRuntime.SupportsKVM(): + processLabel, err = util.SELinuxKVMLabel(processLabel) + if err != nil { return err } - - if err := os.Chown(containerInfo.Dir, c.RootUID(), c.RootGID()); err != nil { + case c.config.Systemd: + processLabel, err = util.SELinuxInitLabel(processLabel) + if err != nil { return err } } - c.config.ProcessLabel = containerInfo.ProcessLabel + c.config.ProcessLabel = processLabel c.config.MountLabel = containerInfo.MountLabel c.config.StaticDir = containerInfo.Dir c.state.RunDir = containerInfo.RunDir + if len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0 { + if err := os.Chown(containerInfo.RunDir, c.RootUID(), c.RootGID()); err != nil { + return err + } + + if err := os.Chown(containerInfo.Dir, c.RootUID(), c.RootGID()); err != nil { + return err + } + } + // Set the default Entrypoint and Command if containerInfo.Config != nil { if c.config.Entrypoint == nil { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index a3f97f2a6..c40ad45b9 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -396,6 +396,20 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } + if c.config.IDMappings.AutoUserNs { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return nil, err + } + g.ClearLinuxUIDMappings() + for _, uidmap := range c.config.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + g.ClearLinuxGIDMappings() + for _, gidmap := range c.config.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + } + g.SetRootPath(c.state.Mountpoint) g.AddAnnotation(annotations.Created, c.config.CreatedTime.Format(time.RFC3339Nano)) g.AddAnnotation("org.opencontainers.image.stopSignal", fmt.Sprintf("%d", c.config.StopSignal)) diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index 395271b2a..2a611c2d9 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -6,6 +6,7 @@ import ( "context" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/lookup" spec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -44,3 +45,7 @@ func (c *Container) copyOwnerAndPerms(source, dest string) error { func (c *Container) getOCICgroupPath() (string, error) { return "", define.ErrNotImplemented } + +func (c *Container) getUserOverrides() *lookup.Overrides { + return nil +} diff --git a/libpod/container.log.go b/libpod/container_log.go index 514edb8c8..bfa303e84 100644 --- a/libpod/container.log.go +++ b/libpod/container_log.go @@ -3,6 +3,7 @@ package libpod import ( "os" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/logs" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -22,7 +23,7 @@ func (r *Runtime) Log(containers []*Container, options *logs.LogOptions, logChan func (c *Container) ReadLog(options *logs.LogOptions, logChannel chan *logs.LogLine) error { // TODO Skip sending logs until journald logs can be read // TODO make this not a magic string - if c.LogDriver() == JournaldLogging { + if c.LogDriver() == define.JournaldLogging { return c.readFromJournal(options, logChannel) } return c.readFromLogFile(options, logChannel) diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index 2a35a2ae9..98a69966a 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -112,7 +112,7 @@ func (c *Container) execPS(args []string) ([]string, error) { defer wErrPipe.Close() defer rErrPipe.Close() - streams := new(AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = wPipe streams.ErrorStream = wErrPipe streams.AttachOutput = true diff --git a/libpod/define/config.go b/libpod/define/config.go index 5598f97a3..17d764c65 100644 --- a/libpod/define/config.go +++ b/libpod/define/config.go @@ -1,5 +1,10 @@ package define +import ( + "bufio" + "io" +) + var ( // DefaultInfraImage to use for infra container DefaultInfraImage = "k8s.gcr.io/pause:3.2" @@ -26,3 +31,38 @@ type InfoData struct { // VolumeDriverLocal is the "local" volume driver. It is managed by libpod // itself. const VolumeDriverLocal = "local" + +const ( + OCIManifestDir = "oci-dir" + OCIArchive = "oci-archive" + V2s2ManifestDir = "docker-dir" + V2s2Archive = "docker-archive" +) + +// AttachStreams contains streams that will be attached to the container +type AttachStreams struct { + // OutputStream will be attached to container's STDOUT + OutputStream io.WriteCloser + // ErrorStream will be attached to container's STDERR + ErrorStream io.WriteCloser + // InputStream will be attached to container's STDIN + InputStream *bufio.Reader + // AttachOutput is whether to attach to STDOUT + // If false, stdout will not be attached + AttachOutput bool + // AttachError is whether to attach to STDERR + // If false, stdout will not be attached + AttachError bool + // AttachInput is whether to attach to STDIN + // If false, stdout will not be attached + AttachInput bool +} + +// JournaldLogging is the string conmon expects to specify journald logging +const JournaldLogging = "journald" + +// KubernetesLogging is the string conmon expects when specifying to use the kubernetes logging format +const KubernetesLogging = "k8s-file" + +// JSONLogging is the string conmon expects when specifying to use the json logging format +const JSONLogging = "json-file" diff --git a/libpod/define/inspect.go b/libpod/define/ctr_inspect.go index b7cd13f82..b7cd13f82 100644 --- a/libpod/define/inspect.go +++ b/libpod/define/ctr_inspect.go diff --git a/libpod/define/info.go b/libpod/define/info.go new file mode 100644 index 000000000..2516cad77 --- /dev/null +++ b/libpod/define/info.go @@ -0,0 +1,102 @@ +package define + +import "github.com/containers/storage/pkg/idtools" + +// Info is the overall struct that describes the host system +// running libpod/podman +type Info struct { + Host *HostInfo `json:"host"` + Store *StoreInfo `json:"store"` + Registries map[string]interface{} `json:"registries"` + Version Version `json:"version"` +} + +//HostInfo describes the libpod host +type HostInfo struct { + Arch string `json:"arch"` + BuildahVersion string `json:"buildahVersion"` + CGroupsVersion string `json:"cgroupVersion"` + Conmon *ConmonInfo `json:"conmon"` + CPUs int `json:"cpus"` + Distribution DistributionInfo `json:"distribution"` + EventLogger string `json:"eventLogger"` + Hostname string `json:"hostname"` + IDMappings IDMappings `json:"idMappings,omitempty"` + Kernel string `json:"kernel"` + MemFree int64 `json:"memFree"` + MemTotal int64 `json:"memTotal"` + OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` + OS string `json:"os"` + Rootless bool `json:"rootless"` + RuntimeInfo map[string]interface{} `json:"runtimeInfo,omitempty"` + Slirp4NetNS SlirpInfo `json:"slirp4netns,omitempty"` + SwapFree int64 `json:"swapFree"` + SwapTotal int64 `json:"swapTotal"` + Uptime string `json:"uptime"` +} + +// SlirpInfo describes the slirp exectuable that +// is being being used. +type SlirpInfo struct { + Executable string `json:"executable"` + Package string `json:"package"` + Version string `json:"version"` +} + +// IDMappings describe the GID and UID mappings +type IDMappings struct { + GIDMap []idtools.IDMap `json:"gidmap"` + UIDMap []idtools.IDMap `json:"uidmap"` +} + +// DistributionInfo describes the host distribution +// for libpod +type DistributionInfo struct { + Distribution string `json:"distribution"` + Version string `json:"version"` +} + +// ConmonInfo describes the conmon executable being used +type ConmonInfo struct { + Package string `json:"package"` + Path string `json:"path"` + Version string `json:"version"` +} + +// OCIRuntimeInfo describes the runtime (crun or runc) being +// used with podman +type OCIRuntimeInfo struct { + Name string `json:"name"` + Package string `json:"package"` + Path string `json:"path"` + Version string `json:"version"` +} + +// StoreInfo describes the container storage and its +// attributes +type StoreInfo struct { + ConfigFile string `json:"configFile"` + ContainerStore ContainerStore `json:"containerStore"` + GraphDriverName string `json:"graphDriverName"` + GraphOptions map[string]interface{} `json:"graphOptions"` + GraphRoot string `json:"graphRoot"` + GraphStatus map[string]string `json:"graphStatus"` + ImageStore ImageStore `json:"imageStore"` + RunRoot string `json:"runRoot"` + VolumePath string `json:"volumePath"` +} + +// ImageStore describes the image store. Right now only the number +// of images present +type ImageStore struct { + Number int `json:"number"` +} + +// ContainerStore describes the quantity of containers in the +// store by status +type ContainerStore struct { + Number int `json:"number"` + Paused int `json:"paused"` + Running int `json:"running"` + Stopped int `json:"stopped"` +} diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go new file mode 100644 index 000000000..8558c149b --- /dev/null +++ b/libpod/define/pod_inspect.go @@ -0,0 +1,97 @@ +package define + +import ( + "net" + "time" + + "github.com/cri-o/ocicni/pkg/ocicni" +) + +// InspectPodData contains detailed information on a pod's configuration and +// state. It is used as the output of Inspect on pods. +type InspectPodData struct { + // ID is the ID of the pod. + ID string `json:"Id"` + // Name is the name of the pod. + Name string + // Namespace is the Libpod namespace the pod is placed in. + Namespace string `json:"Namespace,omitempty"` + // Created is the time when the pod was created. + Created time.Time + // Hostname is the hostname that the pod will set. + Hostname string + // Labels is a set of key-value labels that have been applied to the + // pod. + Labels map[string]string `json:"Labels,omitempty"` + // CreateCgroup is whether this pod will create its own CGroup to group + // containers under. + CreateCgroup bool + // CgroupParent is the parent of the pod's CGroup. + CgroupParent string `json:"CgroupParent,omitempty"` + // CgroupPath is the path to the pod's CGroup. + CgroupPath string `json:"CgroupPath,omitempty"` + // CreateInfra is whether this pod will create an infra container to + // share namespaces. + CreateInfra bool + // InfraContainerID is the ID of the pod's infra container, if one is + // present. + InfraContainerID string `json:"InfraContainerID,omitempty"` + // InfraConfig is the configuration of the infra container of the pod. + // Will only be set if CreateInfra is true. + InfraConfig *InspectPodInfraConfig `json:"InfraConfig,omitempty"` + // SharedNamespaces contains a list of namespaces that will be shared by + // containers within the pod. Can only be set if CreateInfra is true. + SharedNamespaces []string `json:"SharedNamespaces,omitempty"` + // NumContainers is the number of containers in the pod, including the + // infra container. + NumContainers uint + // Containers gives a brief summary of all containers in the pod and + // their current status. + Containers []InspectPodContainerInfo `json:"Containers,omitempty"` +} + +// InspectPodInfraConfig contains the configuration of the pod's infra +// container. +type InspectPodInfraConfig struct { + // PortBindings are ports that will be forwarded to the infra container + // and then shared with the pod. + PortBindings []ocicni.PortMapping + // HostNetwork is whether the infra container (and thus the whole pod) + // will use the host's network and not create a network namespace. + HostNetwork bool + // StaticIP is a static IPv4 that will be assigned to the infra + // container and then used by the pod. + StaticIP net.IP + // StaticMAC is a static MAC address that will be assigned to the infra + // container and then used by the pod. + StaticMAC net.HardwareAddr + // NoManageResolvConf indicates that the pod will not manage resolv.conf + // and instead each container will handle their own. + NoManageResolvConf bool + // DNSServer is a set of DNS Servers that will be used by the infra + // container's resolv.conf and shared with the remainder of the pod. + DNSServer []string + // DNSSearch is a set of DNS search domains that will be used by the + // infra container's resolv.conf and shared with the remainder of the + // pod. + DNSSearch []string + // DNSOption is a set of DNS options that will be used by the infra + // container's resolv.conf and shared with the remainder of the pod. + DNSOption []string + // NoManageHosts indicates that the pod will not manage /etc/hosts and + // instead each container will handle their own. + NoManageHosts bool + // HostAdd adds a number of hosts to the infra container's resolv.conf + // which will be shared with the rest of the pod. + HostAdd []string +} + +// InspectPodContainerInfo contains information on a container in a pod. +type InspectPodContainerInfo struct { + // ID is the ID of the container. + ID string `json:"Id"` + // Name is the name of the container. + Name string + // State is the current status of the container. + State string +} diff --git a/libpod/filters/containers.go b/libpod/filters/containers.go new file mode 100644 index 000000000..c13372f91 --- /dev/null +++ b/libpod/filters/containers.go @@ -0,0 +1,157 @@ +package lpfilters + +import ( + "regexp" + "strconv" + "strings" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/timetype" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +// GenerateContainerFilterFuncs return ContainerFilter functions based of filter. +func GenerateContainerFilterFuncs(filter, filterValue string, r *libpod.Runtime) (func(container *libpod.Container) bool, error) { + switch filter { + case "id": + return func(c *libpod.Container) bool { + return strings.Contains(c.ID(), filterValue) + }, nil + case "label": + var filterArray = strings.SplitN(filterValue, "=", 2) + var filterKey = filterArray[0] + if len(filterArray) > 1 { + filterValue = filterArray[1] + } else { + filterValue = "" + } + return func(c *libpod.Container) bool { + for labelKey, labelValue := range c.Labels() { + if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { + return true + } + } + return false + }, nil + case "name": + return func(c *libpod.Container) bool { + match, err := regexp.MatchString(filterValue, c.Name()) + if err != nil { + return false + } + return match + }, nil + case "exited": + exitCode, err := strconv.ParseInt(filterValue, 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "exited code out of range %q", filterValue) + } + return func(c *libpod.Container) bool { + ec, exited, err := c.ExitCode() + if ec == int32(exitCode) && err == nil && exited { + return true + } + return false + }, nil + case "status": + 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 + } + if filterValue == "stopped" { + filterValue = "exited" + } + state := status.String() + if status == define.ContainerStateConfigured { + state = "created" + } else if status == define.ContainerStateStopped { + state = "exited" + } + return state == filterValue + }, 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 { + containerConfig := c.Config() + if strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) { + return true + } + return false + }, nil + case "before": + ctr, err := r.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + createTime := containerConfig.CreatedTime + return func(c *libpod.Container) bool { + cc := c.Config() + return createTime.After(cc.CreatedTime) + }, nil + case "since": + ctr, err := r.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + 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 + arr := strings.Split(filterValue, ":") + 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 + } + } + return false + }, nil + case "health": + return func(c *libpod.Container) bool { + hcStatus, err := c.HealthCheckStatus() + if err != nil { + return false + } + return hcStatus == filterValue + }, nil + case "until": + ts, err := timetype.GetTimestamp(filterValue, 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/podfilters/pods.go b/libpod/filters/pods.go index 54fa85edc..9bf436eab 100644 --- a/libpod/podfilters/pods.go +++ b/libpod/filters/pods.go @@ -1,4 +1,4 @@ -package podfilters +package lpfilters import ( "strconv" diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go index 08a613dfe..daddb6561 100644 --- a/libpod/healthcheck.go +++ b/libpod/healthcheck.go @@ -108,7 +108,7 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { hcw := hcWriteCloser{ captureBuffer, } - streams := new(AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = hcw streams.ErrorStream = hcw diff --git a/libpod/info.go b/libpod/info.go index 8d411f0d4..d7ed5bb16 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -13,7 +13,9 @@ import ( "time" "github.com/containers/buildah" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/cgroups" + registries2 "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/system" @@ -21,14 +23,85 @@ import ( "github.com/sirupsen/logrus" ) +// Info returns the store and host information +func (r *Runtime) info() (*define.Info, error) { + info := define.Info{} + versionInfo, err := define.GetVersion() + if err != nil { + return nil, errors.Wrapf(err, "error getting version info") + } + info.Version = versionInfo + // get host information + hostInfo, err := r.hostInfo() + if err != nil { + return nil, errors.Wrapf(err, "error getting host info") + } + info.Host = hostInfo + + // get store information + storeInfo, err := r.storeInfo() + if err != nil { + return nil, errors.Wrapf(err, "error getting store info") + } + info.Store = storeInfo + registries := make(map[string]interface{}) + data, err := registries2.GetRegistriesData() + if err != nil { + return nil, errors.Wrapf(err, "error getting registries") + } + for _, reg := range data { + registries[reg.Prefix] = reg + } + regs, err := registries2.GetRegistries() + if err != nil { + return nil, errors.Wrapf(err, "error getting registries") + } + if len(regs) > 0 { + registries["search"] = regs + } + + info.Registries = registries + return &info, nil +} + // top-level "host" info -func (r *Runtime) hostInfo() (map[string]interface{}, error) { +func (r *Runtime) hostInfo() (*define.HostInfo, error) { // lets say OS, arch, number of cpus, amount of memory, maybe os distribution/version, hostname, kernel version, uptime - info := map[string]interface{}{} - info["os"] = runtime.GOOS - info["arch"] = runtime.GOARCH - info["cpus"] = runtime.NumCPU() - info["rootless"] = rootless.IsRootless() + mi, err := system.ReadMemInfo() + if err != nil { + return nil, errors.Wrapf(err, "error reading memory info") + } + + hostDistributionInfo := r.GetHostDistributionInfo() + + kv, err := readKernelVersion() + if err != nil { + return nil, errors.Wrapf(err, "error reading kernel version") + } + + host, err := os.Hostname() + if err != nil { + return nil, errors.Wrapf(err, "error getting hostname") + } + info := define.HostInfo{ + Arch: runtime.GOARCH, + BuildahVersion: buildah.Version, + CPUs: runtime.NumCPU(), + Distribution: hostDistributionInfo, + EventLogger: r.eventer.String(), + Hostname: host, + IDMappings: define.IDMappings{}, + Kernel: kv, + MemFree: mi.MemFree, + MemTotal: mi.MemTotal, + OS: runtime.GOOS, + Rootless: rootless.IsRootless(), + Slirp4NetNS: define.SlirpInfo{}, + SwapFree: mi.SwapFree, + SwapTotal: mi.SwapTotal, + } + + // CGroups version unified, err := cgroups.IsCgroup2UnifiedMode() if err != nil { return nil, errors.Wrapf(err, "error reading cgroups mode") @@ -37,17 +110,8 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { if unified { cgroupVersion = "v2" } - info["CgroupVersion"] = cgroupVersion - mi, err := system.ReadMemInfo() - if err != nil { - return nil, errors.Wrapf(err, "error reading memory info") - } - // TODO this might be a place for github.com/dustin/go-humanize - info["MemTotal"] = mi.MemTotal - info["MemFree"] = mi.MemFree - info["SwapTotal"] = mi.SwapTotal - info["SwapFree"] = mi.SwapFree - hostDistributionInfo := r.GetHostDistributionInfo() + info.CGroupsVersion = cgroupVersion + if rootless.IsRootless() { if path, err := exec.LookPath("slirp4netns"); err == nil { logrus.Warnf("Failed to retrieve program version for %s: %v", path, err) @@ -55,11 +119,12 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { if err != nil { logrus.Warnf("Failed to retrieve program version for %s: %v", path, err) } - program := map[string]interface{}{} - program["Executable"] = path - program["Version"] = version - program["Package"] = packageVersion(path) - info["slirp4netns"] = program + program := define.SlirpInfo{ + Executable: path, + Package: packageVersion(path), + Version: version, + } + info.Slirp4NetNS = program } uidmappings, err := rootless.ReadMappingsProc("/proc/self/uid_map") if err != nil { @@ -69,29 +134,19 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { if err != nil { return nil, errors.Wrapf(err, "error reading gid mappings") } - idmappings := make(map[string]interface{}) - idmappings["uidmap"] = uidmappings - idmappings["gidmap"] = gidmappings - info["IDMappings"] = idmappings - } - info["Distribution"] = map[string]interface{}{ - "distribution": hostDistributionInfo["Distribution"], - "version": hostDistributionInfo["Version"], - } - info["BuildahVersion"] = buildah.Version - kv, err := readKernelVersion() - if err != nil { - return nil, errors.Wrapf(err, "error reading kernel version") + idmappings := define.IDMappings{ + GIDMap: gidmappings, + UIDMap: uidmappings, + } + info.IDMappings = idmappings } - info["kernel"] = kv - runtimeInfo, err := r.defaultOCIRuntime.RuntimeInfo() + conmonInfo, ociruntimeInfo, err := r.defaultOCIRuntime.RuntimeInfo() if err != nil { logrus.Errorf("Error getting info on OCI runtime %s: %v", r.defaultOCIRuntime.Name(), err) } else { - for k, v := range runtimeInfo { - info[k] = v - } + info.Conmon = conmonInfo + info.OCIRuntime = ociruntimeInfo } up, err := readUptime() @@ -105,6 +160,7 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { return nil, errors.Wrapf(err, "error parsing system uptime") } + // TODO Isnt there a simple lib for this, something like humantime? hoursFound := false var timeBuffer bytes.Buffer var hoursBuffer bytes.Buffer @@ -121,32 +177,75 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { } } - info["uptime"] = timeBuffer.String() + info.Uptime = timeBuffer.String() if hoursFound { hours, err := strconv.ParseFloat(hoursBuffer.String(), 64) if err == nil { days := hours / 24 - info["uptime"] = fmt.Sprintf("%s (Approximately %.2f days)", info["uptime"], days) + info.Uptime = fmt.Sprintf("%s (Approximately %.2f days)", info.Uptime, days) } } - host, err := os.Hostname() + return &info, nil +} + +func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) { + var ( + paused, running, stopped int + ) + cs := define.ContainerStore{} + cons, err := r.GetAllContainers() if err != nil { - return nil, errors.Wrapf(err, "error getting hostname") + return cs, err } - info["hostname"] = host - info["eventlogger"] = r.eventer.String() - - return info, nil + for _, con := range cons { + state, err := con.State() + if err != nil { + return cs, err + } + switch state { + case define.ContainerStateRunning: + running += 1 + case define.ContainerStatePaused: + paused += 1 + default: + stopped += 1 + } + } + cs.Number = len(cons) + cs.Paused = paused + cs.Stopped = stopped + cs.Running = running + return cs, nil } // top-level "store" info -func (r *Runtime) storeInfo() (map[string]interface{}, error) { +func (r *Runtime) storeInfo() (*define.StoreInfo, error) { // lets say storage driver in use, number of images, number of containers - info := map[string]interface{}{} - info["GraphRoot"] = r.store.GraphRoot() - info["RunRoot"] = r.store.RunRoot() - info["GraphDriverName"] = r.store.GraphDriverName() + configFile, err := storage.DefaultConfigFile(rootless.IsRootless()) + if err != nil { + return nil, err + } + images, err := r.store.Images() + if err != nil { + return nil, errors.Wrapf(err, "error getting number of images") + } + conInfo, err := r.getContainerStoreInfo() + if err != nil { + return nil, err + } + imageInfo := define.ImageStore{Number: len(images)} + + info := define.StoreInfo{ + ImageStore: imageInfo, + ContainerStore: conInfo, + GraphRoot: r.store.GraphRoot(), + RunRoot: r.store.RunRoot(), + GraphDriverName: r.store.GraphDriverName(), + GraphOptions: nil, + VolumePath: r.config.Engine.VolumePath, + ConfigFile: configFile, + } graphOptions := map[string]interface{}{} for _, o := range r.store.GraphOptions() { split := strings.SplitN(o, "=", 2) @@ -164,14 +263,8 @@ func (r *Runtime) storeInfo() (map[string]interface{}, error) { graphOptions[split[0]] = split[1] } } - info["GraphOptions"] = graphOptions - info["VolumePath"] = r.config.Engine.VolumePath + info.GraphOptions = graphOptions - configFile, err := storage.DefaultConfigFile(rootless.IsRootless()) - if err != nil { - return nil, err - } - info["ConfigFile"] = configFile statusPairs, err := r.store.Status() if err != nil { return nil, err @@ -180,24 +273,8 @@ func (r *Runtime) storeInfo() (map[string]interface{}, error) { for _, pair := range statusPairs { status[pair[0]] = pair[1] } - info["GraphStatus"] = status - images, err := r.store.Images() - if err != nil { - return nil, errors.Wrapf(err, "error getting number of images") - } - info["ImageStore"] = map[string]interface{}{ - "number": len(images), - } - - containers, err := r.store.Containers() - if err != nil { - return nil, errors.Wrapf(err, "error getting number of containers") - } - info["ContainerStore"] = map[string]interface{}{ - "number": len(containers), - } - - return info, nil + info.GraphStatus = status + return &info, nil } func readKernelVersion() (string, error) { @@ -225,14 +302,13 @@ func readUptime() (string, error) { } // GetHostDistributionInfo returns a map containing the host's distribution and version -func (r *Runtime) GetHostDistributionInfo() map[string]string { - dist := make(map[string]string) - +func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo { // Populate values in case we cannot find the values // or the file - dist["Distribution"] = "unknown" - dist["Version"] = "unknown" - + dist := define.DistributionInfo{ + Distribution: "unknown", + Version: "unknown", + } f, err := os.Open("/etc/os-release") if err != nil { return dist @@ -242,10 +318,10 @@ func (r *Runtime) GetHostDistributionInfo() map[string]string { l := bufio.NewScanner(f) for l.Scan() { if strings.HasPrefix(l.Text(), "ID=") { - dist["Distribution"] = strings.TrimPrefix(l.Text(), "ID=") + dist.Distribution = strings.TrimPrefix(l.Text(), "ID=") } if strings.HasPrefix(l.Text(), "VERSION_ID=") { - dist["Version"] = strings.Trim(strings.TrimPrefix(l.Text(), "VERSION_ID="), "\"") + dist.Version = strings.Trim(strings.TrimPrefix(l.Text(), "VERSION_ID="), "\"") } } return dist diff --git a/libpod/oci.go b/libpod/oci.go index ef46cf5c3..9991c5625 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -4,6 +4,7 @@ import ( "bufio" "net" + "github.com/containers/libpod/libpod/define" "k8s.io/client-go/tools/remotecommand" ) @@ -102,6 +103,9 @@ type OCIRuntime interface { // SupportsNoCgroups is whether the runtime supports running containers // without cgroups. SupportsNoCgroups() bool + // SupportsKVM os whether the OCI runtime supports running containers + // without KVM separation + SupportsKVM() bool // AttachSocketPath is the path to the socket to attach to a given // container. @@ -120,7 +124,7 @@ type OCIRuntime interface { ExitFilePath(ctr *Container) (string, error) // RuntimeInfo returns verbose information about the runtime. - RuntimeInfo() (map[string]interface{}, error) + RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) } // ExecOptions are options passed into ExecContainer. They control the command @@ -141,7 +145,7 @@ type ExecOptions struct { // the container was run as will be used. User string // Streams are the streams that will be attached to the container. - Streams *AttachStreams + Streams *define.AttachStreams // PreserveFDs is a number of additional file descriptors (in addition // to 0, 1, 2) that will be passed to the executed process. The total FDs // passed will be 3 + PreserveFDs. diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index fb0a54bff..ff158c2d1 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -31,7 +31,7 @@ const ( // Attach to the given container // Does not check if state is appropriate // started is only required if startContainer is true -func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { +func (c *Container) attach(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput { return errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") } @@ -94,7 +94,7 @@ func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan re // 4. attachToExec sends on startFd, signalling it has attached to the socket and child is ready to go // 5. child receives on startFd, runs the runtime exec command // attachToExec is responsible for closing startFd and attachFd -func (c *Container) attachToExec(streams *AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File) error { +func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File) error { if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput { return errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") } @@ -189,7 +189,7 @@ func buildSocketPath(socketPath string) string { return socketPath } -func setupStdioChannels(streams *AttachStreams, conn *net.UnixConn, detachKeys []byte) (chan error, chan error) { +func setupStdioChannels(streams *define.AttachStreams, conn *net.UnixConn, detachKeys []byte) (chan error, chan error) { receiveStdoutError := make(chan error) go func() { receiveStdoutError <- redirectResponseToOutputStreams(streams.OutputStream, streams.ErrorStream, streams.AttachOutput, streams.AttachError, conn) @@ -257,7 +257,7 @@ func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, writeO return err } -func readStdio(streams *AttachStreams, receiveStdoutError, stdinDone chan error) error { +func readStdio(streams *define.AttachStreams, receiveStdoutError, stdinDone chan error) error { var err error select { case err = <-receiveStdoutError: diff --git a/libpod/oci_attach_unsupported.go b/libpod/oci_attach_unsupported.go index 987d2c973..3b0216e5d 100644 --- a/libpod/oci_attach_unsupported.go +++ b/libpod/oci_attach_unsupported.go @@ -9,10 +9,10 @@ import ( "k8s.io/client-go/tools/remotecommand" ) -func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { +func (c *Container) attach(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { return define.ErrNotImplemented } -func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, sessionID string, startFd *os.File, attachFd *os.File) error { +func (c *Container) attachToExec(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, sessionID string, startFd *os.File, attachFd *os.File) error { return define.ErrNotImplemented } diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 6a0097b8e..da4b85067 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -5,7 +5,6 @@ package libpod import ( "bufio" "bytes" - "encoding/binary" "fmt" "io" "io/ioutil" @@ -61,6 +60,7 @@ type ConmonOCIRuntime struct { noPivot bool reservePorts bool supportsJSON bool + supportsKVM bool supportsNoCgroups bool sdNotify bool } @@ -71,11 +71,25 @@ type ConmonOCIRuntime struct { // The first path that points to a valid executable will be used. // Deliberately private. Someone should not be able to construct this outside of // libpod. -func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *config.Config, supportsJSON, supportsNoCgroups bool) (OCIRuntime, error) { +func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *config.Config) (OCIRuntime, error) { if name == "" { return nil, errors.Wrapf(define.ErrInvalidArg, "the OCI runtime must be provided a non-empty name") } + // Make lookup tables for runtime support + supportsJSON := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsJSON)) + supportsNoCgroups := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsNoCgroups)) + supportsKVM := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsKVM)) + for _, r := range runtimeCfg.Engine.RuntimeSupportsJSON { + supportsJSON[r] = true + } + for _, r := range runtimeCfg.Engine.RuntimeSupportsNoCgroups { + supportsNoCgroups[r] = true + } + for _, r := range runtimeCfg.Engine.RuntimeSupportsKVM { + supportsKVM[r] = true + } + runtime := new(ConmonOCIRuntime) runtime.name = name runtime.conmonPath = conmonPath @@ -90,8 +104,9 @@ func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtime // TODO: probe OCI runtime for feature and enable automatically if // available. - runtime.supportsJSON = supportsJSON - runtime.supportsNoCgroups = supportsNoCgroups + runtime.supportsJSON = supportsJSON[name] + runtime.supportsNoCgroups = supportsNoCgroups[name] + runtime.supportsKVM = supportsKVM[name] foundPath := false for _, path := range paths { @@ -353,6 +368,9 @@ func (r *ConmonOCIRuntime) StartContainer(ctr *Container) error { if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok { env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify)) } + if path, ok := os.LookupEnv("PATH"); ok { + env = append(env, fmt.Sprintf("PATH=%s", path)) + } if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "start", ctr.ID()); err != nil { return err } @@ -477,8 +495,7 @@ func (r *ConmonOCIRuntime) UnpauseContainer(ctr *Container) error { } // HTTPAttach performs an attach for the HTTP API. -// This will consume, and automatically close, the hijacked HTTP session. -// It is not necessary to close it independently. +// The caller must handle closing the HTTP connection after this returns. // The cancel channel is not closed; it is up to the caller to do so after // this function returns. // If this is a container with a terminal, we will stream raw. If it is not, we @@ -489,13 +506,7 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, httpConn net.Conn, httpBuf isTerminal = ctr.config.Spec.Process.Terminal } - // Ensure that our contract of closing the HTTP connection is honored. - defer hijackWriteErrorAndClose(deferredErr, ctr.ID(), httpConn, httpBuf) - if streams != nil { - if isTerminal { - return errors.Wrapf(define.ErrInvalidArg, "cannot specify which streams to attach as container %s has a terminal", ctr.ID()) - } if !streams.Stdin && !streams.Stdout && !streams.Stderr { return errors.Wrapf(define.ErrInvalidArg, "must specify at least one stream to attach to") } @@ -544,8 +555,16 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, httpConn net.Conn, httpBuf go func() { var err error if isTerminal { + // Hack: return immediately if attachStdout not set to + // emulate Docker. + // Basically, when terminal is set, STDERR goes nowhere. + // Everything does over STDOUT. + // Therefore, if not attaching STDOUT - we'll never copy + // anything from here. logrus.Debugf("Performing terminal HTTP attach for container %s", ctr.ID()) - err = httpAttachTerminalCopy(conn, httpBuf, ctr.ID()) + if attachStdout { + err = httpAttachTerminalCopy(conn, httpBuf, ctr.ID()) + } } else { logrus.Debugf("Performing non-terminal HTTP attach for container %s", ctr.ID()) err = httpAttachNonTerminalCopy(conn, httpBuf, ctr.ID(), attachStdin, attachStdout, attachStderr) @@ -930,6 +949,13 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container if options.TCPEstablished { args = append(args, "--tcp-established") } + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + if err = os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil { + return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR") + } args = append(args, ctr.ID()) return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) } @@ -939,7 +965,7 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container func (r *ConmonOCIRuntime) SupportsCheckpoint() bool { // Check if the runtime implements checkpointing. Currently only // runc's checkpoint/restore implementation is supported. - cmd := exec.Command(r.path, "checkpoint", "-h") + cmd := exec.Command(r.path, "checkpoint", "--help") if err := cmd.Start(); err != nil { return false } @@ -961,6 +987,12 @@ func (r *ConmonOCIRuntime) SupportsNoCgroups() bool { return r.supportsNoCgroups } +// SupportsKVM checks if the OCI runtime supports running containers +// without KVM separation +func (r *ConmonOCIRuntime) SupportsKVM() bool { + return r.supportsKVM +} + // AttachSocketPath is the path to a single container's attach socket. func (r *ConmonOCIRuntime) AttachSocketPath(ctr *Container) (string, error) { if ctr == nil { @@ -989,32 +1021,30 @@ func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { } // RuntimeInfo provides information on the runtime. -func (r *ConmonOCIRuntime) RuntimeInfo() (map[string]interface{}, error) { +func (r *ConmonOCIRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { runtimePackage := packageVersion(r.path) conmonPackage := packageVersion(r.conmonPath) runtimeVersion, err := r.getOCIRuntimeVersion() if err != nil { - return nil, errors.Wrapf(err, "error getting version of OCI runtime %s", r.name) + return nil, nil, errors.Wrapf(err, "error getting version of OCI runtime %s", r.name) } conmonVersion, err := r.getConmonVersion() if err != nil { - return nil, errors.Wrapf(err, "error getting conmon version") + return nil, nil, errors.Wrapf(err, "error getting conmon version") } - info := make(map[string]interface{}) - info["Conmon"] = map[string]interface{}{ - "path": r.conmonPath, - "package": conmonPackage, - "version": conmonVersion, + conmon := define.ConmonInfo{ + Package: conmonPackage, + Path: r.conmonPath, + Version: conmonVersion, } - info["OCIRuntime"] = map[string]interface{}{ - "name": r.name, - "path": r.path, - "package": runtimePackage, - "version": runtimeVersion, + ocirt := define.OCIRuntimeInfo{ + Name: r.name, + Path: r.path, + Package: runtimePackage, + Version: runtimeVersion, } - - return info, nil + return &conmon, &ocirt, nil } // makeAccessible changes the path permission and each parent directory to have --x--x--x @@ -1397,9 +1427,9 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p var logDriver string switch ctr.LogDriver() { - case JournaldLogging: - logDriver = JournaldLogging - case JSONLogging: + case define.JournaldLogging: + logDriver = define.JournaldLogging + case define.JSONLogging: fallthrough default: //nolint-stylecheck // No case here should happen except JSONLogging, but keep this here in case the options are extended @@ -1409,8 +1439,8 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p // to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod // since the former case is obscure, and the latter case isn't an error, let's silently fallthrough fallthrough - case KubernetesLogging: - logDriver = fmt.Sprintf("%s:%s", KubernetesLogging, logPath) + case define.KubernetesLogging: + logDriver = fmt.Sprintf("%s:%s", define.KubernetesLogging, logPath) } args = append(args, "-l", logDriver) @@ -1717,13 +1747,16 @@ func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, for { numR, err := container.Read(buf) if numR > 0 { - headerBuf := []byte{0, 0, 0, 0} + var headerBuf []byte + // Subtract 1 because we strip the first byte (used for + // multiplexing by Conmon). + headerLen := uint32(numR - 1) // Practically speaking, we could make this buf[0] - 1, // but we need to validate it anyways... switch buf[0] { case AttachPipeStdin: - headerBuf[0] = 0 + headerBuf = makeHTTPAttachHeader(0, headerLen) if !stdin { continue } @@ -1731,24 +1764,17 @@ func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, if !stdout { continue } - headerBuf[0] = 1 + headerBuf = makeHTTPAttachHeader(1, headerLen) case AttachPipeStderr: if !stderr { continue } - headerBuf[0] = 2 + headerBuf = makeHTTPAttachHeader(2, headerLen) default: logrus.Errorf("Received unexpected attach type %+d, discarding %d bytes", buf[0], numR) continue } - // Get big-endian length and append. - // Subtract 1 because we strip the first byte (used for - // multiplexing by Conmon). - lenBuf := []byte{0, 0, 0, 0} - binary.BigEndian.PutUint32(lenBuf, uint32(numR-1)) - headerBuf = append(headerBuf, lenBuf...) - numH, err2 := http.Write(headerBuf) if err2 != nil { if err != nil { diff --git a/libpod/oci_conmon_unsupported.go b/libpod/oci_conmon_unsupported.go index 395b6f6d9..309e0d417 100644 --- a/libpod/oci_conmon_unsupported.go +++ b/libpod/oci_conmon_unsupported.go @@ -17,7 +17,7 @@ type ConmonOCIRuntime struct { } // newConmonOCIRuntime is not supported on this OS. -func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *config.Config, supportsJSON, supportsNoCgroups bool) (OCIRuntime, error) { +func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *config.Config) (OCIRuntime, error) { return nil, define.ErrNotImplemented } @@ -117,8 +117,8 @@ func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { } // RuntimeInfo is not supported on this OS. -func (r *ConmonOCIRuntime) RuntimeInfo() (map[string]interface{}, error) { - return nil, define.ErrNotImplemented +func (r *ConmonOCIRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { + return nil, nil, define.ErrNotImplemented } // Package is not supported on this OS. diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index a5d589255..172805b0d 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -168,6 +168,12 @@ func (r *MissingRuntime) SupportsNoCgroups() bool { return false } +// SupportsKVM checks if the OCI runtime supports running containers +// without KVM separation +func (r *MissingRuntime) SupportsKVM() bool { + return false +} + // AttachSocketPath does not work as there is no runtime to attach to. // (Theoretically we could follow ExitFilePath but there is no guarantee the // container is running and thus has an attach socket...) @@ -195,15 +201,14 @@ func (r *MissingRuntime) ExitFilePath(ctr *Container) (string, error) { } // RuntimeInfo returns information on the missing runtime -func (r *MissingRuntime) RuntimeInfo() (map[string]interface{}, error) { - info := make(map[string]interface{}) - info["OCIRuntime"] = map[string]interface{}{ - "name": r.name, - "path": "missing", - "package": "missing", - "version": "missing", +func (r *MissingRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { + ocirt := define.OCIRuntimeInfo{ + Name: r.name, + Path: "missing", + Package: "missing", + Version: "missing", } - return info, nil + return nil, &ocirt, nil } // Return an error indicating the runtime is missing diff --git a/libpod/options.go b/libpod/options.go index dfbec364a..b4e436b63 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -985,7 +985,7 @@ func WithLogDriver(driver string) CtrCreateOption { switch driver { case "": return errors.Wrapf(define.ErrInvalidArg, "log driver must be set") - case JournaldLogging, KubernetesLogging, JSONLogging: + case define.JournaldLogging, define.KubernetesLogging, define.JSONLogging: break default: return errors.Wrapf(define.ErrInvalidArg, "invalid log driver") @@ -1334,7 +1334,7 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption { } destinations[vol.Dest] = true - mountOpts, err := util.ProcessOptions(vol.Options, false, nil) + mountOpts, err := util.ProcessOptions(vol.Options, false, "") if err != nil { return errors.Wrapf(err, "error processing options for named volume %q mounted at %q", vol.Name, vol.Dest) } diff --git a/libpod/pod_api.go b/libpod/pod_api.go index 200732652..ed4dc0727 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -431,9 +431,9 @@ func containerStatusFromContainers(allCtrs []*Container) (map[string]define.Cont } // Inspect returns a PodInspect struct to describe the pod -func (p *Pod) Inspect() (*PodInspect, error) { +func (p *Pod) Inspect() (*define.InspectPodData, error) { var ( - podContainers []PodContainerInfo + ctrs []define.InspectPodContainerInfo ) p.lock.Lock() @@ -444,14 +444,6 @@ func (p *Pod) Inspect() (*PodInspect, error) { containers, err := p.runtime.state.PodContainers(p) if err != nil { - return &PodInspect{}, err - } - ctrStatuses, err := containerStatusFromContainers(containers) - if err != nil { - return nil, err - } - status, err := CreatePodStatusResults(ctrStatuses) - if err != nil { return nil, err } for _, c := range containers { @@ -462,26 +454,29 @@ func (p *Pod) Inspect() (*PodInspect, error) { if err == nil { containerStatus = containerState.String() } - pc := PodContainerInfo{ + ctrs = append(ctrs, define.InspectPodContainerInfo{ ID: c.ID(), + Name: c.Name(), State: containerStatus, - } - podContainers = append(podContainers, pc) + }) + } + inspectData := define.InspectPodData{ + ID: p.ID(), + Name: p.Name(), + Namespace: p.Namespace(), + Created: p.CreatedTime(), + Hostname: "", + Labels: p.Labels(), + CreateCgroup: false, + CgroupParent: p.CgroupParent(), + CgroupPath: p.state.CgroupPath, + CreateInfra: false, + InfraContainerID: p.state.InfraContainerID, + InfraConfig: nil, + SharedNamespaces: nil, + NumContainers: uint(len(containers)), + Containers: ctrs, } - infraContainerID := p.state.InfraContainerID - config := new(PodConfig) - if err := JSONDeepCopy(p.config, config); err != nil { - return nil, err - } - inspectData := PodInspect{ - Config: config, - State: &PodInspectState{ - CgroupPath: p.state.CgroupPath, - InfraContainerID: infraContainerID, - Status: status, - }, - Containers: podContainers, - } return &inspectData, nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index 422b79359..3b8f9e057 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -9,16 +9,14 @@ import ( "sync" "syscall" + "github.com/containers/common/pkg/config" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/types" - - "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/lock" "github.com/containers/libpod/pkg/cgroups" - sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" @@ -361,25 +359,13 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { } } - // Make lookup tables for runtime support - supportsJSON := make(map[string]bool) - supportsNoCgroups := make(map[string]bool) - for _, r := range runtime.config.Engine.RuntimeSupportsJSON { - supportsJSON[r] = true - } - for _, r := range runtime.config.Engine.RuntimeSupportsNoCgroups { - supportsNoCgroups[r] = true - } - // Get us at least one working OCI runtime. runtime.ociRuntimes = make(map[string]OCIRuntime) // Initialize remaining OCI runtimes for name, paths := range runtime.config.Engine.OCIRuntimes { - json := supportsJSON[name] - nocgroups := supportsNoCgroups[name] - ociRuntime, err := newConmonOCIRuntime(name, paths, runtime.conmonPath, runtime.config, json, nocgroups) + ociRuntime, err := newConmonOCIRuntime(name, paths, runtime.conmonPath, runtime.config) if err != nil { // Don't fatally error. // This will allow us to ship configs including optional @@ -399,10 +385,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { if strings.HasPrefix(runtime.config.Engine.OCIRuntime, "/") { name := filepath.Base(runtime.config.Engine.OCIRuntime) - json := supportsJSON[name] - nocgroups := supportsNoCgroups[name] - - ociRuntime, err := newConmonOCIRuntime(name, []string{runtime.config.Engine.OCIRuntime}, runtime.conmonPath, runtime.config, json, nocgroups) + ociRuntime, err := newConmonOCIRuntime(name, []string{runtime.config.Engine.OCIRuntime}, runtime.conmonPath, runtime.config) if err != nil { return err } @@ -675,40 +658,8 @@ func (r *Runtime) refresh(alivePath string) error { } // Info returns the store and host information -func (r *Runtime) Info() ([]define.InfoData, error) { - info := []define.InfoData{} - // get host information - hostInfo, err := r.hostInfo() - if err != nil { - return nil, errors.Wrapf(err, "error getting host info") - } - info = append(info, define.InfoData{Type: "host", Data: hostInfo}) - - // get store information - storeInfo, err := r.storeInfo() - if err != nil { - return nil, errors.Wrapf(err, "error getting store info") - } - info = append(info, define.InfoData{Type: "store", Data: storeInfo}) - - registries := make(map[string]interface{}) - data, err := sysreg.GetRegistriesData() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries") - } - for _, reg := range data { - registries[reg.Prefix] = reg - } - regs, err := sysreg.GetRegistries() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries") - } - if len(regs) > 0 { - registries["search"] = regs - } - - info = append(info, define.InfoData{Type: "registries", Data: registries}) - return info, nil +func (r *Runtime) Info() (*define.Info, error) { + return r.info() } // generateName generates a unique name for a container or pod. @@ -847,3 +798,7 @@ func (r *Runtime) mergeDBConfig(dbConfig *DBConfig) error { } return nil } + +func (r *Runtime) EnableLabeling() bool { + return r.config.Containers.EnableLabeling +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 207ac6477..3dc8d3d0f 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -321,7 +321,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai ctrNamedVolumes = append(ctrNamedVolumes, newVol) } - if ctr.config.LogPath == "" && ctr.config.LogDriver != JournaldLogging { + if ctr.config.LogPath == "" && ctr.config.LogDriver != define.JournaldLogging { ctr.config.LogPath = filepath.Join(ctr.config.StaticDir, "ctr.log") } @@ -887,8 +887,9 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int continue } err = r.RemoveContainer(context.Background(), ctr, false, false) - pruneErrors[ctr.ID()] = err if err != nil { + pruneErrors[ctr.ID()] = err + } else { prunedContainers[ctr.ID()] = size } } diff --git a/libpod/storage.go b/libpod/storage.go index d675f4ffe..34e40f699 100644 --- a/libpod/storage.go +++ b/libpod/storage.go @@ -8,6 +8,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod/define" "github.com/containers/storage" + "github.com/containers/storage/pkg/idtools" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" @@ -35,6 +36,8 @@ type ContainerInfo struct { Config *v1.Image ProcessLabel string MountLabel string + UIDMap []idtools.IDMap + GIDMap []idtools.IDMap } // RuntimeContainerMetadata is the structure that we encode as JSON and store @@ -166,6 +169,8 @@ func (r *storageService) CreateContainerStorage(ctx context.Context, systemConte logrus.Debugf("container %q has run directory %q", container.ID, containerRunDir) return ContainerInfo{ + UIDMap: options.UIDMap, + GIDMap: options.GIDMap, Dir: containerDir, RunDir: containerRunDir, Config: imageConfig, diff --git a/libpod/util.go b/libpod/util.go index e9d234bbe..6457dac1c 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -2,6 +2,7 @@ package libpod import ( "bufio" + "encoding/binary" "fmt" "io" "os" @@ -239,11 +240,22 @@ func checkDependencyContainer(depCtr, ctr *Container) error { // hijackWriteErrorAndClose writes an error to a hijacked HTTP session and // closes it. Intended to HTTPAttach function. // If error is nil, it will not be written; we'll only close the connection. -func hijackWriteErrorAndClose(toWrite error, cid string, httpCon io.Closer, httpBuf *bufio.ReadWriter) { +func hijackWriteErrorAndClose(toWrite error, cid string, terminal bool, httpCon io.Closer, httpBuf *bufio.ReadWriter) { if toWrite != nil { - if _, err := httpBuf.Write([]byte(toWrite.Error())); err != nil { - logrus.Errorf("Error writing error %q to container %s HTTP attach connection: %v", toWrite, cid, err) - } else if err := httpBuf.Flush(); err != nil { + errString := []byte(fmt.Sprintf("%v\n", toWrite)) + if !terminal { + // We need a header. + header := makeHTTPAttachHeader(2, uint32(len(errString))) + if _, err := httpBuf.Write(header); err != nil { + logrus.Errorf("Error writing header for container %s attach connection error: %v", cid, err) + } + // TODO: May want to return immediately here to avoid + // writing garbage to the socket? + } + if _, err := httpBuf.Write(errString); err != nil { + logrus.Errorf("Error writing error to container %s HTTP attach connection: %v", cid, err) + } + if err := httpBuf.Flush(); err != nil { logrus.Errorf("Error flushing HTTP buffer for container %s HTTP attach connection: %v", cid, err) } } @@ -252,3 +264,14 @@ func hijackWriteErrorAndClose(toWrite error, cid string, httpCon io.Closer, http logrus.Errorf("Error closing container %s HTTP attach connection: %v", cid, err) } } + +// makeHTTPAttachHeader makes an 8-byte HTTP header for a buffer of the given +// length and stream. Accepts an integer indicating which stream we are sending +// to (STDIN = 0, STDOUT = 1, STDERR = 2). +func makeHTTPAttachHeader(stream byte, length uint32) []byte { + headerBuf := []byte{stream, 0, 0, 0} + lenBuf := []byte{0, 0, 0, 0} + binary.BigEndian.PutUint32(lenBuf, length) + headerBuf = append(headerBuf, lenBuf...) + return headerBuf +} |