diff options
Diffstat (limited to 'libpod')
28 files changed, 299 insertions, 102 deletions
diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index d5c755a52..a48de3092 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -3,6 +3,7 @@ package libpod import ( "bytes" "os" + "path/filepath" "runtime" "strings" @@ -108,25 +109,25 @@ func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { }, { "libpod root directory (staticdir)", - rt.config.Engine.StaticDir, + filepath.Clean(rt.config.Engine.StaticDir), staticDirKey, "", }, { "libpod temporary files directory (tmpdir)", - rt.config.Engine.TmpDir, + filepath.Clean(rt.config.Engine.TmpDir), tmpDirKey, "", }, { "storage temporary directory (runroot)", - rt.StorageConfig().RunRoot, + filepath.Clean(rt.StorageConfig().RunRoot), runRootKey, storeOpts.RunRoot, }, { "storage graph root directory (graphroot)", - rt.StorageConfig().GraphRoot, + filepath.Clean(rt.StorageConfig().GraphRoot), graphRootKey, storeOpts.GraphRoot, }, diff --git a/libpod/container.go b/libpod/container.go index 01419500e..ea5a6e09c 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -235,6 +235,19 @@ type ContainerOverlayVolume struct { Source string `json:"source,omitempty"` } +// ContainerImageVolume is a volume based on a container image. The container +// image is first mounted on the host and is then bind-mounted into the +// container. +type ContainerImageVolume struct { + // Source is the source of the image volume. The image can be referred + // to by name and by ID. + Source string `json:"source"` + // Dest is the absolute path of the mount in the container. + Dest string `json:"dest"` + // ReadWrite sets the volume writable. + ReadWrite bool `json:"rw"` +} + // Config accessors // Unlocked diff --git a/libpod/container_api.go b/libpod/container_api.go index aef37dd59..a9808a30e 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -249,7 +249,7 @@ func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <- // attaching, and I really do not want to do that right now. // Send a SIGWINCH after attach succeeds so that most programs will // redraw the screen for the new attach session. - attachRdy := make(chan bool) + attachRdy := make(chan bool, 1) if c.config.Spec.Process != nil && c.config.Spec.Process.Terminal { go func() { <-attachRdy diff --git a/libpod/container_config.go b/libpod/container_config.go index fb1ba373b..d73fbb42f 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -134,6 +134,8 @@ type ContainerRootFSConfig struct { NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` // OverlayVolumes lists the overlay volumes to mount into the container. OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` + // ImageVolumes lists the image volumes to mount into the container. + ImageVolumes []*ContainerImageVolume `json:"imageVolumes,omitempty"` // CreateWorkingDir indicates that Libpod should create the container's // working directory if it does not exist. Some OCI runtimes do this by // default, but others do not. diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index b8bce1272..f78d74ef7 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -90,7 +90,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) } namedVolumes, mounts := c.sortUserVolumes(ctrSpec) - inspectMounts, err := c.getInspectMounts(namedVolumes, mounts) + inspectMounts, err := c.getInspectMounts(namedVolumes, c.config.ImageVolumes, mounts) if err != nil { return nil, err } @@ -192,7 +192,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) // Get inspect-formatted mounts list. // Only includes user-specified mounts. Only includes bind mounts and named // volumes, not tmpfs volumes. -func (c *Container) getInspectMounts(namedVolumes []*ContainerNamedVolume, mounts []spec.Mount) ([]define.InspectMount, error) { +func (c *Container) getInspectMounts(namedVolumes []*ContainerNamedVolume, imageVolumes []*ContainerImageVolume, mounts []spec.Mount) ([]define.InspectMount, error) { inspectMounts := []define.InspectMount{} // No mounts, return early @@ -219,6 +219,17 @@ func (c *Container) getInspectMounts(namedVolumes []*ContainerNamedVolume, mount inspectMounts = append(inspectMounts, mountStruct) } + + for _, volume := range imageVolumes { + mountStruct := define.InspectMount{} + mountStruct.Type = "image" + mountStruct.Destination = volume.Dest + mountStruct.Source = volume.Source + mountStruct.RW = volume.ReadWrite + + inspectMounts = append(inspectMounts, mountStruct) + } + for _, mount := range mounts { // It's a mount. // Is it a tmpfs? If so, discard. @@ -259,7 +270,7 @@ func parseMountOptionsForInspect(options []string, mount *define.InspectMount) { isRW = false case "rw": // Do nothing, silently discard - case "shared", "slave", "private", "rshared", "rslave", "rprivate": + case "shared", "slave", "private", "rshared", "rslave", "rprivate", "unbindable", "runbindable": mountProp = opt case "z", "Z": zZ = opt diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 4ae571de6..0aeaae43d 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -578,10 +578,10 @@ func (c *Container) refresh() error { if len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0 { info, err := os.Stat(c.runtime.config.Engine.TmpDir) if err != nil { - return errors.Wrapf(err, "cannot stat `%s`", c.runtime.config.Engine.TmpDir) + return err } if err := os.Chmod(c.runtime.config.Engine.TmpDir, info.Mode()|0111); err != nil { - return errors.Wrapf(err, "cannot chmod `%s`", c.runtime.config.Engine.TmpDir) + return err } root := filepath.Join(c.runtime.config.Engine.TmpDir, "containers-root", c.ID()) if err := os.MkdirAll(root, 0755); err != nil { @@ -1734,6 +1734,25 @@ func (c *Container) cleanup(ctx context.Context) error { } } + // Unmount image volumes + for _, v := range c.config.ImageVolumes { + img, err := c.runtime.ImageRuntime().NewFromLocal(v.Source) + if err != nil { + if lastError == nil { + lastError = err + continue + } + logrus.Errorf("error unmounting image volume %q:%q :%v", v.Source, v.Dest, err) + } + if err := img.Unmount(false); err != nil { + if lastError == nil { + lastError = err + continue + } + logrus.Errorf("error unmounting image volume %q:%q :%v", v.Source, v.Dest, err) + } + } + return lastError } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index a1b4334fb..83d5c20cb 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -39,6 +39,7 @@ import ( "github.com/containers/storage/pkg/idtools" securejoin "github.com/cyphar/filepath-securejoin" runcuser "github.com/opencontainers/runc/libcontainer/user" + "github.com/opencontainers/runtime-spec/specs-go" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -308,7 +309,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { fallthrough case "Z": if err := label.Relabel(m.Source, c.MountLabel(), label.IsShared(o)); err != nil { - return nil, errors.Wrapf(err, "relabel failed %q", m.Source) + return nil, err } default: @@ -343,7 +344,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { Type: "bind", Source: srcPath, Destination: dstPath, - Options: []string{"bind", "private"}, + Options: []string{"bind", "rprivate"}, } if c.IsReadOnly() && dstPath != "/dev/shm" { newMount.Options = append(newMount.Options, "ro", "nosuid", "noexec", "nodev") @@ -359,11 +360,40 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { for _, overlayVol := range c.config.OverlayVolumes { contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) if err != nil { - return nil, errors.Wrapf(err, "failed to create TempDir in the %s directory", c.config.StaticDir) + return nil, err } overlayMount, err := overlay.Mount(contentDir, overlayVol.Source, overlayVol.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) if err != nil { - return nil, errors.Wrapf(err, "creating overlay failed %q", overlayVol.Source) + return nil, errors.Wrapf(err, "mounting overlay failed %q", overlayVol.Source) + } + g.AddMount(overlayMount) + } + + // Add image volumes as overlay mounts + for _, volume := range c.config.ImageVolumes { + // Mount the specified image. + img, err := c.runtime.ImageRuntime().NewFromLocal(volume.Source) + if err != nil { + return nil, errors.Wrapf(err, "error creating image volume %q:%q", volume.Source, volume.Dest) + } + mountPoint, err := img.Mount(nil, "") + if err != nil { + return nil, errors.Wrapf(err, "error mounting image volume %q:%q", volume.Source, volume.Dest) + } + + contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) + if err != nil { + return nil, errors.Wrapf(err, "failed to create TempDir in the %s directory", c.config.StaticDir) + } + + var overlayMount specs.Mount + if volume.ReadWrite { + overlayMount, err = overlay.Mount(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + } else { + overlayMount, err = overlay.MountReadOnly(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + } + if err != nil { + return nil, errors.Wrapf(err, "creating overlay mount for image %q failed", volume.Source) } g.AddMount(overlayMount) } @@ -668,11 +698,31 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro } g.AddMount(systemdMnt) } else { + mountOptions := []string{"bind", "rprivate"} + + var statfs unix.Statfs_t + if err := unix.Statfs("/sys/fs/cgroup/systemd", &statfs); err != nil { + mountOptions = append(mountOptions, "nodev", "noexec", "nosuid") + } else { + if statfs.Flags&unix.MS_NODEV == unix.MS_NODEV { + mountOptions = append(mountOptions, "nodev") + } + if statfs.Flags&unix.MS_NOEXEC == unix.MS_NOEXEC { + mountOptions = append(mountOptions, "noexec") + } + if statfs.Flags&unix.MS_NOSUID == unix.MS_NOSUID { + mountOptions = append(mountOptions, "nosuid") + } + if statfs.Flags&unix.MS_RDONLY == unix.MS_RDONLY { + mountOptions = append(mountOptions, "ro") + } + } + systemdMnt := spec.Mount{ Destination: "/sys/fs/cgroup/systemd", Type: "bind", Source: "/sys/fs/cgroup/systemd", - Options: []string{"bind", "nodev", "noexec", "nosuid", "rprivate"}, + Options: mountOptions, } g.AddMount(systemdMnt) g.AddLinuxMaskedPaths("/sys/fs/cgroup/systemd/release_agent") @@ -781,7 +831,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error { return errors.Wrapf(err, "error creating delete files list file %q", deleteFilesList) } if err := ioutil.WriteFile(deleteFilesList, formatJSON, 0600); err != nil { - return errors.Wrapf(err, "error creating delete files list file %q", deleteFilesList) + return errors.Wrap(err, "error creating delete files list file") } includeFiles = append(includeFiles, "deleted.files") @@ -805,7 +855,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error { defer outFile.Close() if err := os.Chmod(dest, 0600); err != nil { - return errors.Wrapf(err, "cannot chmod %q", dest) + return err } _, err = io.Copy(outFile, input) @@ -1029,7 +1079,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if n.Sandbox != "" { MAC, err = net.ParseMAC(n.Mac) if err != nil { - return errors.Wrapf(err, "failed to parse MAC %v", n.Mac) + return err } break } @@ -1133,14 +1183,14 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return errors.Wrapf(err, "failed to read deleted files file") } if err := json.Unmarshal(deletedFilesJSON, &deletedFiles); err != nil { - return errors.Wrapf(err, "failed to read deleted files file %s", deletedFilesPath) + return errors.Wrapf(err, "failed to unmarshal deleted files file %s", deletedFilesPath) } for _, deleteFile := range deletedFiles { // Using RemoveAll as deletedFiles, which is generated from 'podman diff' // lists completely deleted directories as a single entry: 'D /root'. err = os.RemoveAll(filepath.Join(c.state.Mountpoint, deleteFile)) if err != nil { - return errors.Wrapf(err, "failed to delete file %s from container %s during restore", deletedFilesPath, c.ID()) + return errors.Wrapf(err, "failed to delete files from container %s during restore", c.ID()) } } } @@ -1179,7 +1229,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // Make standard bind mounts to include in the container func (c *Container) makeBindMounts() error { if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { - return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir) + return errors.Wrap(err, "cannot chown run directory") } if c.state.BindMounts == nil { @@ -1197,13 +1247,13 @@ func (c *Container) makeBindMounts() error { if c.config.NetNsCtr == "" { if resolvePath, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { if err := os.Remove(resolvePath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error removing container %s resolv.conf", c.ID()) + return errors.Wrapf(err, "container %s", c.ID()) } delete(c.state.BindMounts, "/etc/resolv.conf") } if hostsPath, ok := c.state.BindMounts["/etc/hosts"]; ok { if err := os.Remove(hostsPath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error removing container %s hosts", c.ID()) + return errors.Wrapf(err, "container %s", c.ID()) } delete(c.state.BindMounts, "/etc/hosts") } @@ -1403,7 +1453,7 @@ func (c *Container) generateResolvConf() (string, error) { if err == nil { resolvConf = definedPath } else if !os.IsNotExist(err) { - return "", errors.Wrapf(err, "failed to stat %s", definedPath) + return "", err } } break @@ -1425,7 +1475,7 @@ func (c *Container) generateResolvConf() (string, error) { contents, err := ioutil.ReadFile(resolvPath) // resolv.conf doesn't have to exists if err != nil && !os.IsNotExist(err) { - return "", errors.Wrapf(err, "unable to read %s", resolvPath) + return "", err } // Ensure that the container's /etc/resolv.conf is compatible with its @@ -1494,7 +1544,7 @@ func (c *Container) generateResolvConf() (string, error) { destPath := filepath.Join(c.state.RunDir, "resolv.conf") if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { - return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID()) + return "", errors.Wrapf(err, "container %s", c.ID()) } // Build resolv.conf @@ -1514,7 +1564,7 @@ func (c *Container) generateResolvConf() (string, error) { func (c *Container) generateHosts(path string) (string, error) { orig, err := ioutil.ReadFile(path) if err != nil { - return "", errors.Wrapf(err, "unable to read %s", path) + return "", err } hosts := string(orig) hosts += c.getHosts() @@ -1917,7 +1967,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) { } orig, err := ioutil.ReadFile(originPasswdFile) if err != nil && !os.IsNotExist(err) { - return "", "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile) + return "", "", err } passwdFile, err := c.writeStringToStaticDir("passwd", string(orig)+passwdEntry) if err != nil { @@ -1936,7 +1986,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) { f, err := os.OpenFile(containerPasswd, os.O_APPEND|os.O_WRONLY, 0600) if err != nil { - return "", "", errors.Wrapf(err, "error opening container %s /etc/passwd", c.ID()) + return "", "", errors.Wrapf(err, "container %s", c.ID()) } defer f.Close() @@ -1963,7 +2013,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) { } orig, err := ioutil.ReadFile(originGroupFile) if err != nil && !os.IsNotExist(err) { - return "", "", errors.Wrapf(err, "unable to read group file %s", originGroupFile) + return "", "", err } groupFile, err := c.writeStringToStaticDir("group", string(orig)+groupEntry) if err != nil { @@ -1982,7 +2032,7 @@ func (c *Container) generatePasswdAndGroup() (string, string, error) { f, err := os.OpenFile(containerGroup, os.O_APPEND|os.O_WRONLY, 0600) if err != nil { - return "", "", errors.Wrapf(err, "error opening container %s /etc/group", c.ID()) + return "", "", errors.Wrapf(err, "container %s", c.ID()) } defer f.Close() @@ -2003,13 +2053,13 @@ func (c *Container) copyOwnerAndPerms(source, dest string) error { if os.IsNotExist(err) { return nil } - return errors.Wrapf(err, "cannot stat `%s`", dest) + return err } if err := os.Chmod(dest, info.Mode()); err != nil { - return errors.Wrapf(err, "cannot chmod `%s`", dest) + return err } if err := os.Chown(dest, int(info.Sys().(*syscall.Stat_t).Uid), int(info.Sys().(*syscall.Stat_t).Gid)); err != nil { - return errors.Wrapf(err, "cannot chown `%s`", dest) + return err } return nil } @@ -2100,7 +2150,7 @@ func (c *Container) checkFileExistsInRootfs(file string) (bool, error) { if os.IsNotExist(err) { return false, nil } - return false, errors.Wrapf(err, "error accessing container %s file %q", c.ID(), file) + return false, errors.Wrapf(err, "container %s", c.ID()) } if stat.IsDir() { return false, nil diff --git a/libpod/container_validate.go b/libpod/container_validate.go index ee3c8583c..fa809436e 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 and overlay volumes destination conflits + // Check named volume, overlay volume and image volume destination conflits destinations := make(map[string]bool) for _, vol := range c.config.NamedVolumes { // Don't check if they already exist. @@ -106,6 +106,14 @@ func (c *Container) validate() error { } destinations[vol.Dest] = true } + for _, vol := range c.config.ImageVolumes { + // Don't check if they already exist. + // If they don't we will automatically create them. + if _, ok := destinations[vol.Dest]; ok { + return errors.Wrapf(define.ErrInvalidArg, "two volumes found with destination %s", vol.Dest) + } + destinations[vol.Dest] = true + } // Check that networks and network aliases match up. ctrNets := make(map[string]bool) diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 36a919cf6..27c5febf4 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -14,6 +14,9 @@ var ( // ErrNoSuchImage indicates the requested image does not exist ErrNoSuchImage = errors.New("no such image") + // ErrMultipleImages found multiple name and tag matches + ErrMultipleImages = errors.New("found multiple name and tag matches") + // ErrNoSuchTag indicates the requested image tag does not exist ErrNoSuchTag = errors.New("no such tag") @@ -148,15 +151,15 @@ var ( // ErrOCIRuntimePermissionDenied indicates the OCI runtime attempted to invoke a command that returned // a permission denied error - ErrOCIRuntimePermissionDenied = errors.New("OCI runtime permission denied error") + ErrOCIRuntimePermissionDenied = errors.New("OCI permission denied") // ErrOCIRuntimeNotFound indicates the OCI runtime attempted to invoke a command // that was not found - ErrOCIRuntimeNotFound = errors.New("OCI runtime command not found error") + ErrOCIRuntimeNotFound = errors.New("OCI not found") // ErrOCIRuntimeUnavailable indicates that the OCI runtime associated to a container // could not be found in the configuration - ErrOCIRuntimeUnavailable = errors.New("OCI runtime not available in the current configuration") + ErrOCIRuntimeUnavailable = errors.New("OCI unavailable") // ErrConmonOutdated indicates the version of conmon found (whether via the configuration or $PATH) // is out of date for the current podman version diff --git a/libpod/define/podstate.go b/libpod/define/podstate.go index 2b59aabfb..e02671972 100644 --- a/libpod/define/podstate.go +++ b/libpod/define/podstate.go @@ -10,9 +10,12 @@ const ( PodStateExited = "Exited" // PodStatePaused indicates the pod has been paused PodStatePaused = "Paused" - // PodStateRunning indicates that one or more of the containers in - // the pod is running + // PodStateRunning indicates that all of the containers in the pod are + // running. PodStateRunning = "Running" + // PodStateDegraded indicates that at least one, but not all, of the + // containers in the pod are running. + PodStateDegraded = "Degraded" // PodStateStopped indicates all of the containers belonging to the pod // are stopped. PodStateStopped = "Stopped" diff --git a/libpod/events.go b/libpod/events.go index b519fe324..95317eb01 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -26,6 +26,12 @@ func (c *Container) newContainerEvent(status events.Status) { e.Name = c.Name() e.Image = c.config.RootfsImageName e.Type = events.Container + + e.Details = events.Details{ + ID: e.ID, + Attributes: c.Labels(), + } + if err := c.runtime.eventer.Write(e); err != nil { logrus.Errorf("unable to write pod event: %q", err) } diff --git a/libpod/events/config.go b/libpod/events/config.go index bb35c03c0..2ec3111fe 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -36,6 +36,18 @@ type Event struct { Time time.Time // Type of event that occurred Type Type + + Details +} + +// Details describes specifics about certain events, specifically around +// container events +type Details struct { + // ID is the event ID + ID string + // Attributes can be used to describe specifics about the event + // in the case of a container event, labels for example + Attributes map[string]string } // EventerOptions describe options that need to be passed to create diff --git a/libpod/events/events.go b/libpod/events/events.go index 722c9595e..42939d64c 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -69,7 +69,14 @@ func (e *Event) ToHumanReadable() string { var humanFormat string switch e.Type { case Container, Pod: - humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s)", e.Time, e.Type, e.Status, e.ID, e.Image, e.Name) + humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", e.Time, e.Type, e.Status, e.ID, e.Image, e.Name) + // check if the container has labels and add it to the output + if len(e.Attributes) > 0 { + for k, v := range e.Attributes { + humanFormat += fmt.Sprintf(", %s=%s", k, v) + } + } + humanFormat += ")" case Image: humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, e.ID, e.Name) case System: diff --git a/libpod/events/filters.go b/libpod/events/filters.go index 171e2237f..62891d32c 100644 --- a/libpod/events/filters.go +++ b/libpod/events/filters.go @@ -55,6 +55,24 @@ func generateEventFilter(filter, filterValue string) (func(e *Event) bool, error return func(e *Event) bool { return string(e.Type) == filterValue }, nil + + case "LABEL": + return func(e *Event) bool { + var found bool + // iterate labels and see if we match a key and value + for eventKey, eventValue := range e.Attributes { + filterValueSplit := strings.SplitN(filterValue, "=", 2) + // if the filter isn't right, just return false + if len(filterValueSplit) < 2 { + return false + } + if eventKey == filterValueSplit[0] && eventValue == filterValueSplit[1] { + found = true + break + } + } + return found + }, nil } return nil, errors.Errorf("%s is an invalid filter", filter) } @@ -74,7 +92,7 @@ func generateEventUntilOption(timeUntil time.Time) func(e *Event) bool { func parseFilter(filter string) (string, string, error) { filterSplit := strings.SplitN(filter, "=", 2) - if len(filterSplit) == 1 { + if len(filterSplit) != 2 { return "", "", errors.Errorf("%s is an invalid filter", filter) } return filterSplit[0], filterSplit[1], nil diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index dc55dbc77..5e3be8009 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -4,6 +4,7 @@ package events import ( "context" + "encoding/json" "strconv" "time" @@ -46,6 +47,15 @@ func (e EventJournalD) Write(ee Event) error { if ee.ContainerExitCode != 0 { m["PODMAN_EXIT_CODE"] = strconv.Itoa(ee.ContainerExitCode) } + // If we have container labels, we need to convert them to a string so they + // can be recorded with the event + if len(ee.Details.Attributes) > 0 { + b, err := json.Marshal(ee.Details.Attributes) + if err != nil { + return err + } + m["PODMAN_LABELS"] = string(b) + } case Volume: m["PODMAN_NAME"] = ee.Name } @@ -59,35 +69,39 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { if err != nil { return errors.Wrapf(err, "failed to generate event options") } - j, err := sdjournal.NewJournal() //nolint + j, err := sdjournal.NewJournal() if err != nil { return err } - // TODO AddMatch and Seek seem to conflict - // Issue filed upstream -> https://github.com/coreos/go-systemd/issues/315 - // Leaving commented code in case upstream fixes things - //podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint - //if err := j.AddMatch(podmanJournal.String()); err != nil { - // return errors.Wrap(err, "failed to add filter for event log") - //} + + // match only podman journal entries + podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} + if err := j.AddMatch(podmanJournal.String()); err != nil { + return errors.Wrap(err, "failed to add journal filter for event log") + } + if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream { if err := j.SeekTail(); err != nil { return errors.Wrap(err, "failed to seek end of journal") } - } else { - podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint - if err := j.AddMatch(podmanJournal.String()); err != nil { - return errors.Wrap(err, "failed to add filter for event log") + // After SeekTail calling Next moves to a random entry. + // To prevent this we have to call Previous first. + // see: https://bugs.freedesktop.org/show_bug.cgi?id=64614 + if _, err := j.Previous(); err != nil { + return errors.Wrap(err, "failed to move journal cursor to previous entry") } } + // the api requires a next|prev before getting a cursor if _, err := j.Next(); err != nil { - return err + return errors.Wrap(err, "failed to move journal cursor to next entry") } + prevCursor, err := j.GetCursor() if err != nil { - return err + return errors.Wrap(err, "failed to get journal cursor") } + for { select { case <-ctx.Done(): @@ -96,30 +110,26 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { default: // fallthrough } + if _, err := j.Next(); err != nil { - return err + return errors.Wrap(err, "failed to move journal cursor to next entry") } newCursor, err := j.GetCursor() if err != nil { - return err + return errors.Wrap(err, "failed to get journal cursor") } if prevCursor == newCursor { if len(options.Until) > 0 || !options.Stream { break } - _ = j.Wait(sdjournal.IndefiniteWait) //nolint + _ = j.Wait(sdjournal.IndefiniteWait) continue } prevCursor = newCursor + entry, err := j.GetEntry() if err != nil { - return err - } - // TODO this keeps us from feeding the podman event parser with - // with regular journal content; it can be removed if the above - // problem with AddMatch is resolved. - if entry.Fields["PODMAN_EVENT"] == "" { - continue + return errors.Wrap(err, "failed to read journal entry") } newEvent, err := newEventFromJournalEntry(entry) if err != nil { @@ -174,6 +184,19 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { / newEvent.ContainerExitCode = intCode } } + + // we need to check for the presence of labels recorded to a container event + if stringLabels, ok := entry.Fields["PODMAN_LABELS"]; ok && len(stringLabels) > 0 { + labels := make(map[string]string, 0) + if err := json.Unmarshal([]byte(stringLabels), &labels); err != nil { + return nil, err + } + + // if we have labels, add them to the event + if len(labels) > 0 { + newEvent.Details = Details{Attributes: labels} + } + } case Image: newEvent.ID = entry.Fields["PODMAN_ID"] } diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go index bd55b852e..f77075893 100644 --- a/libpod/healthcheck.go +++ b/libpod/healthcheck.go @@ -223,7 +223,7 @@ func (c *Container) GetHealthCheckLog() (define.HealthCheckResults, error) { } b, err := ioutil.ReadFile(c.healthCheckLogPath()) if err != nil { - return healthCheck, errors.Wrapf(err, "failed to read health check log file %s", c.healthCheckLogPath()) + return healthCheck, errors.Wrap(err, "failed to read health check log file") } if err := json.Unmarshal(b, &healthCheck); err != nil { return healthCheck, errors.Wrapf(err, "failed to unmarshal existing healthcheck results in %s", c.healthCheckLogPath()) diff --git a/libpod/image/utils.go b/libpod/image/utils.go index 2538f429b..7429a7f10 100644 --- a/libpod/image/utils.go +++ b/libpod/image/utils.go @@ -11,6 +11,7 @@ import ( "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/libpod/define" "github.com/containers/storage" "github.com/pkg/errors" ) @@ -42,7 +43,7 @@ func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, er 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.Errorf("found multiple name and tag matches for %s in repotags", searchName) + return &storage.Image{}, errors.Wrapf(define.ErrMultipleImages, searchName) } return results[0], nil } diff --git a/libpod/kube.go b/libpod/kube.go index cd5064c84..067e7827d 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -327,7 +327,7 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) { period := *c.config.Spec.Linux.Resources.CPU.Period if quota > 0 && period > 0 { - cpuLimitMilli := int64(1000 * float64(quota) / float64(period)) + cpuLimitMilli := int64(1000 * util.PeriodAndQuotaToCores(period, quota)) // Kubernetes: precision finer than 1m is not allowed if cpuLimitMilli >= 1 { diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 9ff6e40b7..28dca8dd8 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -102,17 +102,7 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re requestedMAC = ctr.config.StaticMAC } - // If we are in a pod use the pod name for the network, otherwise the container name - var podName string - if ctr.PodID() != "" { - pod, err := r.GetPod(ctr.PodID()) - if err == nil { - podName = pod.Name() - } - } - if podName == "" { - podName = ctr.Name() - } + podName := getCNIPodName(ctr) podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC) diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 94630e57b..89d64537d 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -120,7 +120,7 @@ func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtime if os.IsNotExist(err) { continue } - return nil, errors.Wrapf(err, "cannot stat OCI runtime %s path %q", name, path) + return nil, errors.Wrapf(err, "cannot stat OCI runtime %s path", name) } if !stat.Mode().IsRegular() { continue diff --git a/libpod/options.go b/libpod/options.go index da2fc983a..0f55f34a3 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1296,7 +1296,7 @@ func WithRootFS(rootfs string) CtrCreateOption { return define.ErrCtrFinalized } if _, err := os.Stat(rootfs); err != nil { - return errors.Wrapf(err, "error checking path %q", rootfs) + return err } ctr.config.Rootfs = rootfs return nil @@ -1439,6 +1439,25 @@ func WithOverlayVolumes(volumes []*ContainerOverlayVolume) CtrCreateOption { } } +// WithImageVolumes adds the given image volumes to the container. +func WithImageVolumes(volumes []*ContainerImageVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + for _, vol := range volumes { + ctr.config.ImageVolumes = append(ctr.config.ImageVolumes, &ContainerImageVolume{ + Dest: vol.Dest, + Source: vol.Source, + ReadWrite: vol.ReadWrite, + }) + } + + return nil + } +} + // WithHealthCheck adds the healthcheck to the container config func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption { return func(ctr *Container) error { diff --git a/libpod/pod_api.go b/libpod/pod_api.go index f2ddba9c9..87ac5c07a 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -506,7 +506,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { }) ctrStatuses[c.ID()] = c.state.State } - podState, err := CreatePodStatusResults(ctrStatuses) + podState, err := createPodStatusResults(ctrStatuses) if err != nil { return nil, err } diff --git a/libpod/pod_status.go b/libpod/pod_status.go index f4ccf308a..668d45ec7 100644 --- a/libpod/pod_status.go +++ b/libpod/pod_status.go @@ -10,10 +10,10 @@ func (p *Pod) GetPodStatus() (string, error) { if err != nil { return define.PodStateErrored, err } - return CreatePodStatusResults(ctrStatuses) + return createPodStatusResults(ctrStatuses) } -func CreatePodStatusResults(ctrStatuses map[string]define.ContainerStatus) (string, error) { +func createPodStatusResults(ctrStatuses map[string]define.ContainerStatus) (string, error) { ctrNum := len(ctrStatuses) if ctrNum == 0 { return define.PodStateCreated, nil @@ -43,8 +43,10 @@ func CreatePodStatusResults(ctrStatuses map[string]define.ContainerStatus) (stri } switch { - case statuses[define.PodStateRunning] > 0: + case statuses[define.PodStateRunning] == ctrNum: return define.PodStateRunning, nil + case statuses[define.PodStateRunning] > 0: + return define.PodStateDegraded, nil case statuses[define.PodStatePaused] == ctrNum: return define.PodStatePaused, nil case statuses[define.PodStateStopped] == ctrNum: diff --git a/libpod/rootless_cni_linux.go b/libpod/rootless_cni_linux.go index 21e43ebd0..3d4ff6e86 100644 --- a/libpod/rootless_cni_linux.go +++ b/libpod/rootless_cni_linux.go @@ -53,7 +53,7 @@ func AllocRootlessCNI(ctx context.Context, c *Container) (ns.NetNS, []*cnitypes. if err != nil { return nil, nil, err } - k8sPodName := getPodOrContainerName(c) // passed to CNI as K8S_POD_NAME + k8sPodName := getCNIPodName(c) // passed to CNI as K8S_POD_NAME cniResults := make([]*cnitypes.Result, len(c.config.Networks)) for i, nw := range c.config.Networks { cniRes, err := rootlessCNIInfraCallAlloc(infra, c.ID(), nw, k8sPodName) @@ -115,12 +115,16 @@ func getRootlessCNIInfraLock(r *Runtime) (lockfile.Locker, error) { return lockfile.GetLockfile(fname) } -func getPodOrContainerName(c *Container) string { - pod, err := c.runtime.GetPod(c.PodID()) - if err != nil || pod.config.Name == "" { - return c.Name() +// getCNIPodName return the pod name (hostname) used by CNI and the dnsname plugin. +// If we are in the pod network namespace use the pod name otherwise the container name +func getCNIPodName(c *Container) string { + if c.config.NetMode.IsPod() || c.IsInfra() { + pod, err := c.runtime.GetPod(c.PodID()) + if err == nil { + return pod.Name() + } } - return pod.config.Name + return c.Name() } func rootlessCNIInfraCallAlloc(infra *Container, id, nw, k8sPodName string) (*cnitypes.Result, error) { diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go index 3dc38f442..1ad32fe9c 100644 --- a/libpod/runtime_migrate.go +++ b/libpod/runtime_migrate.go @@ -29,7 +29,7 @@ func stopPauseProcess() error { if os.IsNotExist(err) { return nil } - return errors.Wrapf(err, "cannot read pause process pid file %s", pausePidPath) + return errors.Wrap(err, "cannot read pause process pid file") } pausePid, err := strconv.Atoi(string(data)) if err != nil { diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 7f58e86d8..76419587a 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -131,6 +131,7 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm logrus.Debugf("Using %q as infra container entrypoint", entryCmd) + g.RemoveMount("/dev/shm") if isRootless { g.RemoveMount("/dev/pts") devPts := spec.Mount{ diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go index e4e6d87e6..055a243c0 100644 --- a/libpod/runtime_volume.go +++ b/libpod/runtime_volume.go @@ -86,8 +86,8 @@ func (r *Runtime) HasVolume(name string) (bool, error) { // Volumes retrieves all volumes // Filters can be provided which will determine which volumes are included in the -// output. Multiple filters are handled by ANDing their output, so only volumes -// matching all filters are returned +// output. If multiple filters are used, a volume will be returned if +// any of the filters are matched func (r *Runtime) Volumes(filters ...VolumeFilter) ([]*Volume, error) { r.lock.RLock() defer r.lock.RUnlock() @@ -101,11 +101,15 @@ func (r *Runtime) Volumes(filters ...VolumeFilter) ([]*Volume, error) { return nil, err } + if len(filters) == 0 { + return vols, nil + } + volsFiltered := make([]*Volume, 0, len(vols)) for _, vol := range vols { - include := true + include := false for _, filter := range filters { - include = include && filter(vol) + include = include || filter(vol) } if include { diff --git a/libpod/util.go b/libpod/util.go index 585b07aca..c26039c50 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -74,7 +74,7 @@ func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, e return false, nil } if !os.IsNotExist(err) { - return false, errors.Wrapf(err, "checking file %s", path) + return false, err } case <-time.After(25 * time.Millisecond): // Check periodically for the file existence. It is needed @@ -86,7 +86,7 @@ func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, e return false, nil } if !os.IsNotExist(err) { - return false, errors.Wrapf(err, "checking file %s", path) + return false, err } case <-timeoutChan: return false, errors.Wrapf(define.ErrInternal, "timed out waiting for file %s", path) @@ -184,11 +184,11 @@ func DefaultSeccompPath() (string, error) { return config.SeccompOverridePath, nil } if !os.IsNotExist(err) { - return "", errors.Wrapf(err, "can't check if %q exists", config.SeccompOverridePath) + return "", err } if _, err := os.Stat(config.SeccompDefaultPath); err != nil { if !os.IsNotExist(err) { - return "", errors.Wrapf(err, "can't check if %q exists", config.SeccompDefaultPath) + return "", err } return "", nil } |