diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container.go | 3 | ||||
-rw-r--r-- | libpod/container_api.go | 5 | ||||
-rw-r--r-- | libpod/container_internal.go | 18 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 44 | ||||
-rw-r--r-- | libpod/define/errors.go | 4 | ||||
-rw-r--r-- | libpod/driver/driver.go | 4 | ||||
-rw-r--r-- | libpod/events/config.go | 10 | ||||
-rw-r--r-- | libpod/events/journal_linux.go | 41 | ||||
-rw-r--r-- | libpod/events/logfile.go | 5 | ||||
-rw-r--r-- | libpod/events/nullout.go | 8 | ||||
-rw-r--r-- | libpod/image/docker_registry_options.go | 7 | ||||
-rw-r--r-- | libpod/image/image.go | 6 | ||||
-rw-r--r-- | libpod/image/image_test.go | 11 | ||||
-rw-r--r-- | libpod/info.go | 2 | ||||
-rw-r--r-- | libpod/oci.go | 2 | ||||
-rw-r--r-- | libpod/oci_internal_linux.go | 58 | ||||
-rw-r--r-- | libpod/options.go | 27 | ||||
-rw-r--r-- | libpod/pod.go | 2 | ||||
-rw-r--r-- | libpod/runtime.go | 96 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 10 | ||||
-rw-r--r-- | libpod/runtime_img.go | 7 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 6 | ||||
-rw-r--r-- | libpod/runtime_pod_linux.go | 4 | ||||
-rw-r--r-- | libpod/util_linux.go | 11 |
24 files changed, 309 insertions, 82 deletions
diff --git a/libpod/container.go b/libpod/container.go index 2d96b1120..9c01d2adf 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -377,6 +377,9 @@ type ContainerConfig struct { RestartRetries uint `json:"restart_retries,omitempty"` // TODO log options for log drivers + // PostConfigureNetNS needed when a user namespace is created by an OCI runtime + // if the network namespace is created before the user namespace it will be + // owned by the wrong user namespace. PostConfigureNetNS bool `json:"postConfigureNetNS"` // OCIRuntime used to create the container diff --git a/libpod/container_api.go b/libpod/container_api.go index ef9c3f006..abcfcb271 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -773,6 +773,11 @@ type ContainerCheckpointOptions struct { // IgnoreRootfs tells the API to not export changes to // the container's root file-system (or to not import) IgnoreRootfs bool + // IgnoreStaticIP tells the API to ignore the IP set + // during 'podman run' with '--ip'. This is especially + // important to be able to restore a container multiple + // times with '--import --name'. + IgnoreStaticIP bool } // Checkpoint checkpoints a container diff --git a/libpod/container_internal.go b/libpod/container_internal.go index aba9c5b93..313f67963 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1152,9 +1152,27 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e c.newContainerEvent(events.Restart) if c.state.State == define.ContainerStateRunning { + conmonPID := c.state.ConmonPID if err := c.stop(timeout); err != nil { return err } + // Old versions of conmon have a bug where they create the exit file before + // closing open file descriptors causing a race condition when restarting + // containers with open ports since we cannot bind the ports as they're not + // yet closed by conmon. + // + // Killing the old conmon PID is ~okay since it forces the FDs of old conmons + // to be closed, while it's a NOP for newer versions which should have + // exited already. + if conmonPID != 0 { + // Ignore errors from FindProcess() as conmon could already have exited. + p, err := os.FindProcess(conmonPID) + if p != nil && err == nil { + if err = p.Kill(); err != nil { + logrus.Debugf("error killing conmon process: %v", err) + } + } + } } defer func() { if err != nil { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 6dbd53fbf..5aa4ee9a9 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -471,9 +471,9 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro return err } - g.RemoveMount("/sys/fs/cgroup") - if unified { + g.RemoveMount("/sys/fs/cgroup") + sourcePath := filepath.Join("/sys/fs/cgroup") systemdMnt := spec.Mount{ Destination: "/sys/fs/cgroup", @@ -483,31 +483,13 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro } g.AddMount(systemdMnt) } else { - // rootless containers have no write access to /sys/fs/cgroup, so don't - // add any mount into the container. - if !rootless.IsRootless() { - cgroupPath, err := c.CGroupPath() - if err != nil { - return err - } - sourcePath := filepath.Join("/sys/fs/cgroup", cgroupPath) - - systemdMnt := spec.Mount{ - Destination: "/sys/fs/cgroup", - Type: "bind", - Source: sourcePath, - Options: []string{"bind", "private"}, - } - g.AddMount(systemdMnt) - } else { - systemdMnt := spec.Mount{ - Destination: "/sys/fs/cgroup", - Type: "bind", - Source: "/sys/fs/cgroup", - Options: []string{"bind", "nodev", "noexec", "nosuid"}, - } - g.AddMount(systemdMnt) + systemdMnt := spec.Mount{ + Destination: "/sys/fs/cgroup/systemd", + Type: "bind", + Source: "/sys/fs/cgroup/systemd", + Options: []string{"bind", "nodev", "noexec", "nosuid"}, } + g.AddMount(systemdMnt) } return nil @@ -743,6 +725,14 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return err } + // If a container is restored multiple times from an exported checkpoint with + // the help of '--import --name', the restore will fail if during 'podman run' + // a static container IP was set with '--ip'. The user can tell the restore + // process to ignore the static IP with '--ignore-static-ip' + if options.IgnoreStaticIP { + c.config.StaticIP = nil + } + // Read network configuration from checkpoint // Currently only one interface with one IP is supported. networkStatusFile, err := os.Open(filepath.Join(c.bundlePath(), "network.status")) @@ -752,7 +742,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // TODO: This implicit restoring with or without IP depending on an // unrelated restore parameter (--name) does not seem like the // best solution. - if err == nil && options.Name == "" { + if err == nil && options.Name == "" && !options.IgnoreStaticIP { // The file with the network.status does exist. Let's restore the // container with the same IP address as during checkpointing. defer networkStatusFile.Close() diff --git a/libpod/define/errors.go b/libpod/define/errors.go index a4368a9aa..9d532263c 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -107,4 +107,8 @@ var ( // ErrOCIRuntimeNotFound indicates the OCI runtime attempted to invoke a command // that was not found ErrOCIRuntimeNotFound = errors.New("OCI runtime command not found error") + + // ErrConmonOutdated indicates the version of conmon found (whether via the configuration or $PATH) + // is out of date for the current podman version + ErrConmonOutdated = errors.New("outdated conmon version") ) diff --git a/libpod/driver/driver.go b/libpod/driver/driver.go index f9442fa21..85eda5a21 100644 --- a/libpod/driver/driver.go +++ b/libpod/driver/driver.go @@ -38,6 +38,10 @@ func GetDriverData(store cstorage.Store, layerID string) (*Data, error) { if err != nil { return nil, err } + if mountTimes, err := store.Mounted(layerID); mountTimes == 0 || err != nil { + delete(metaData, "MergedDir") + } + return &Data{ Name: name, Data: metaData, diff --git a/libpod/events/config.go b/libpod/events/config.go index 96172d47b..453c64f8c 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -22,13 +22,13 @@ const ( type Event struct { // ContainerExitCode is for storing the exit code of a container which can // be used for "internal" event notification - ContainerExitCode int + ContainerExitCode int `json:",omitempty"` // ID can be for the container, image, volume, etc - ID string + ID string `json:",omitempty"` // Image used where applicable - Image string + Image string `json:",omitempty"` // Name where applicable - Name string + Name string `json:",omitempty"` // Status describes the event that occurred Status Status // Time the event occurred @@ -53,6 +53,8 @@ type Eventer interface { Write(event Event) error // Read an event from the backend Read(options ReadOptions) error + // String returns the type of event logger + String() string } // ReadOptions describe the attributes needed to read event logs diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index d5bce4334..470c76959 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -4,6 +4,7 @@ package events import ( "fmt" + "strconv" "time" "github.com/coreos/go-systemd/journal" @@ -42,6 +43,9 @@ func (e EventJournalD) Write(ee Event) error { m["PODMAN_IMAGE"] = ee.Image m["PODMAN_NAME"] = ee.Name m["PODMAN_ID"] = ee.ID + if ee.ContainerExitCode != 0 { + m["PODMAN_EXIT_CODE"] = strconv.Itoa(ee.ContainerExitCode) + } case Volume: m["PODMAN_NAME"] = ee.Name } @@ -54,18 +58,26 @@ func (e EventJournalD) Read(options ReadOptions) error { if err != nil { return errors.Wrapf(err, "failed to generate event options") } - podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} //nolint - j, err := sdjournal.NewJournal() //nolint + j, err := sdjournal.NewJournal() //nolint if err != nil { return err } - if err := j.AddMatch(podmanJournal.String()); err != nil { - return errors.Wrap(err, "failed to add filter for event log") - } + // 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") + //} 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") + } } // the api requires a next|prev before getting a cursor if _, err := j.Next(); err != nil { @@ -96,6 +108,12 @@ func (e EventJournalD) Read(options ReadOptions) error { 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 + } newEvent, err := newEventFromJournalEntry(entry) if err != nil { // We can't decode this event. @@ -141,8 +159,21 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { / case Container, Pod: newEvent.ID = entry.Fields["PODMAN_ID"] newEvent.Image = entry.Fields["PODMAN_IMAGE"] + if code, ok := entry.Fields["PODMAN_EXIT_CODE"]; ok { + intCode, err := strconv.Atoi(code) + if err != nil { + logrus.Errorf("Error parsing event exit code %s", code) + } else { + newEvent.ContainerExitCode = intCode + } + } case Image: newEvent.ID = entry.Fields["PODMAN_ID"] } return &newEvent, nil } + +// String returns a string representation of the logger +func (e EventJournalD) String() string { + return Journald.String() +} diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index 30d72b9fc..4b65b0ad0 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -71,3 +71,8 @@ func (e EventLogFile) Read(options ReadOptions) error { close(options.EventChannel) return nil } + +// String returns a string representation of the logger +func (e EventLogFile) String() string { + return LogFile.String() +} diff --git a/libpod/events/nullout.go b/libpod/events/nullout.go index b11afcf80..f3b36e609 100644 --- a/libpod/events/nullout.go +++ b/libpod/events/nullout.go @@ -17,6 +17,10 @@ func (e EventToNull) Read(options ReadOptions) error { // NewNullEventer returns a new null eventer. You should only do this for // the purposes on internal libpod testing. func NewNullEventer() Eventer { - e := EventToNull{} - return e + return EventToNull{} +} + +// String returns a string representation of the logger +func (e EventToNull) String() string { + return "none" } diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go index c191a3ca2..60bb3c33f 100644 --- a/libpod/image/docker_registry_options.go +++ b/libpod/image/docker_registry_options.go @@ -1,8 +1,12 @@ package image import ( + "fmt" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" + + podmanVersion "github.com/containers/libpod/version" ) // DockerRegistryOptions encapsulates settings that affect how we connect or @@ -36,6 +40,7 @@ func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, add sc.SignaturePolicyPath = parent.SignaturePolicyPath sc.AuthFilePath = parent.AuthFilePath sc.DirForceCompress = parent.DirForceCompress + sc.DockerRegistryUserAgent = parent.DockerRegistryUserAgent } return sc } @@ -48,5 +53,7 @@ func GetSystemContext(signaturePolicyPath, authFilePath string, forceCompress bo } sc.AuthFilePath = authFilePath sc.DirForceCompress = forceCompress + sc.DockerRegistryUserAgent = fmt.Sprintf("libpod/%s", podmanVersion.Version) + return sc } diff --git a/libpod/image/image.go b/libpod/image/image.go index 068491f28..cb7c390c6 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -135,7 +135,7 @@ func (ir *Runtime) NewFromLocal(name string) (*Image, error) { // New creates a new image object where the image could be local // or remote -func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, forcePull bool, label *string) (*Image, error) { +func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, label *string, pullType util.PullType) (*Image, error) { span, _ := opentracing.StartSpanFromContext(ctx, "newImage") span.SetTag("type", "runtime") defer span.Finish() @@ -145,11 +145,13 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile InputName: name, imageruntime: ir, } - if !forcePull { + if pullType != util.PullImageAlways { localImage, err := newImage.getLocalImage() if err == nil { newImage.image = localImage return &newImage, nil + } else if pullType == util.PullImageNever { + return nil, err } } diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index e93ebf797..5a6d095f6 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -3,12 +3,13 @@ package image import ( "context" "fmt" - "github.com/containers/libpod/libpod/events" "io" "io/ioutil" "os" "testing" + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" @@ -89,9 +90,9 @@ func TestImage_NewFromLocal(t *testing.T) { ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) ir.Eventer = events.NewNullEventer() - bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false, nil) + bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) - bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false, nil) + bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) tm, err := makeLocalMatrix(bb, bbglibc) @@ -139,7 +140,7 @@ func TestImage_New(t *testing.T) { // Iterate over the names and delete the image // after the pull for _, img := range names { - newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil) + newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) assert.NotEqual(t, newImage.ID(), "") err = newImage.Remove(context.Background(), false) @@ -168,7 +169,7 @@ func TestImage_MatchRepoTag(t *testing.T) { ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) ir.Eventer = events.NewNullEventer() - newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false, nil) + newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing) assert.NoError(t, err) err = newImage.TagImage("foo:latest") assert.NoError(t, err) diff --git a/libpod/info.go b/libpod/info.go index 4a89fa648..9ab6f7e52 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -102,7 +102,7 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { return nil, errors.Wrapf(err, "error getting hostname") } info["hostname"] = host - + info["eventlogger"] = r.eventer.String() return info, nil } diff --git a/libpod/oci.go b/libpod/oci.go index 2eb004b84..4ba3114e3 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -60,6 +60,7 @@ type OCIRuntime struct { noPivot bool reservePorts bool supportsJSON bool + sdNotify bool } // ociError is used to parse the OCI runtime JSON log. It is not part of the @@ -87,6 +88,7 @@ func newOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *R runtime.logSizeMax = runtimeCfg.MaxLogSize runtime.noPivot = runtimeCfg.NoPivotRoot runtime.reservePorts = runtimeCfg.EnablePortReservation + runtime.sdNotify = runtimeCfg.SDNotify // TODO: probe OCI runtime for feature and enable automatically if // available. diff --git a/libpod/oci_internal_linux.go b/libpod/oci_internal_linux.go index 0bcd021db..6e4ee2cf2 100644 --- a/libpod/oci_internal_linux.go +++ b/libpod/oci_internal_linux.go @@ -247,10 +247,14 @@ func (r *OCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*os.File if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok { env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify)) } - if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok { - env = append(env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1") - fds := activation.Files(false) - extraFiles = append(extraFiles, fds...) + if !r.sdNotify { + if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok { + env = append(env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1") + fds := activation.Files(false) + extraFiles = append(extraFiles, fds...) + } + } else { + logrus.Debug("disabling SD notify") } return env, extraFiles, nil } @@ -278,6 +282,10 @@ func (r *OCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath // No case here should happen except JSONLogging, but keep this here in case the options are extended logrus.Errorf("%s logging specified but not supported. Choosing k8s-file logging instead", ctr.LogDriver()) fallthrough + case "": + // 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) } @@ -348,31 +356,29 @@ func startCommandGivenSelinux(cmd *exec.Cmd) error { // it then signals for conmon to start by sending nonse data down the start fd func (r *OCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File, uuid string) error { cgroupParent := ctr.CgroupParent() - if os.Geteuid() == 0 { - if r.cgroupManager == SystemdCgroupsManager { - unitName := createUnitName("libpod-conmon", ctr.ID()) - - realCgroupParent := cgroupParent - splitParent := strings.Split(cgroupParent, "/") - if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 { - realCgroupParent = splitParent[len(splitParent)-1] - } + if r.cgroupManager == SystemdCgroupsManager { + unitName := createUnitName("libpod-conmon", ctr.ID()) - logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) - if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { - logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) - } + realCgroupParent := cgroupParent + splitParent := strings.Split(cgroupParent, "/") + if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 { + realCgroupParent = splitParent[len(splitParent)-1] + } + + logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) + if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { + logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) + } + } else { + cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") + control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) + if err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) } else { - cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") - control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) - if err != nil { + // we need to remove this defer and delete the cgroup once conmon exits + // maybe need a conmon monitor? + if err := control.AddPid(cmd.Process.Pid); err != nil { logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) - } else { - // we need to remove this defer and delete the cgroup once conmon exits - // maybe need a conmon monitor? - if err := control.AddPid(cmd.Process.Pid); err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) - } } } } diff --git a/libpod/options.go b/libpod/options.go index 3a123a2a6..a7ddbec34 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -482,6 +482,15 @@ func WithEventsLogger(logger string) RuntimeOption { } } +// WithEnableSDNotify sets a runtime option so we know whether to disable socket/FD +// listening +func WithEnableSDNotify() RuntimeOption { + return func(rt *Runtime) error { + rt.config.SDNotify = true + return nil + } +} + // Container Creation Options // WithShmDir sets the directory that should be mounted on /dev/shm. @@ -1499,6 +1508,24 @@ func WithPodName(name string) PodCreateOption { } } +// WithPodHostname sets the hostname of the pod. +func WithPodHostname(hostname string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + // Check the hostname against a regex + if !nameRegex.MatchString(hostname) { + return regexError + } + + pod.config.Hostname = hostname + + return nil + } +} + // WithPodLabels sets the labels of a pod. func WithPodLabels(labels map[string]string) PodCreateOption { return func(pod *Pod) error { diff --git a/libpod/pod.go b/libpod/pod.go index 60626bfd7..3b9bb9c60 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -36,6 +36,8 @@ type PodConfig struct { // Namespace the pod is in Namespace string `json:"namespace,omitempty"` + Hostname string `json:"hostname,omitempty"` + // Labels contains labels applied to the pod Labels map[string]string `json:"labels"` // CgroupParent contains the pod's CGroup parent diff --git a/libpod/runtime.go b/libpod/runtime.go index ffdbc32f1..2fa8dd424 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -1,6 +1,7 @@ package libpod import ( + "bytes" "context" "fmt" "io/ioutil" @@ -8,6 +9,8 @@ import ( "os/exec" "os/user" "path/filepath" + "regexp" + "strconv" "strings" "sync" "syscall" @@ -52,7 +55,7 @@ const ( var ( // InstallPrefix is the prefix where podman will be installed. // It can be overridden at build time. - installPrefix = "/usr/local" + installPrefix = "/usr" // EtcDir is the sysconfdir where podman should look for system config files. // It can be overridden at build time. etcDir = "/etc" @@ -249,6 +252,10 @@ type RuntimeConfig struct { EventsLogFilePath string `toml:"-events_logfile_path"` //DetachKeys is the sequence of keys used to detach a container DetachKeys string `toml:"detach_keys"` + + // SDNotify tells Libpod to allow containers to notify the host + // systemd of readiness using the SD_NOTIFY mechanism + SDNotify bool } // runtimeConfiguredFrom is a struct used during early runtime init to help @@ -271,6 +278,8 @@ type runtimeConfiguredFrom struct { runtimePath bool cniPluginDir bool noPivotRoot bool + runtimeSupportsJSON bool + ociRuntime bool } func defaultRuntimeConfig() (RuntimeConfig, error) { @@ -328,16 +337,19 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { }, nil } -// SetXdgRuntimeDir ensures the XDG_RUNTIME_DIR env variable is set -// containers/image uses XDG_RUNTIME_DIR to locate the auth file. -// It internally calls EnableLinger() so that the user's processes are not +// SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set. +// containers/image uses XDG_RUNTIME_DIR to locate the auth file, XDG_CONFIG_HOME is +// use for the libpod.conf configuration file. +// SetXdgDirs internally calls EnableLinger() so that the user's processes are not // killed once the session is terminated. EnableLinger() also attempts to // get the runtime directory when XDG_RUNTIME_DIR is not specified. -func SetXdgRuntimeDir() error { +// This function should only be called when running rootless. +func SetXdgDirs() error { if !rootless.IsRootless() { return nil } + // Setup XDG_RUNTIME_DIR runtimeDir := os.Getenv("XDG_RUNTIME_DIR") runtimeDirLinger, err := rootless.EnableLinger() @@ -365,6 +377,16 @@ func SetXdgRuntimeDir() error { if err := os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil { return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR") } + + // Setup XDG_CONFIG_HOME + if cfgHomeDir := os.Getenv("XDG_CONFIG_HOME"); cfgHomeDir == "" { + if cfgHomeDir, err = util.GetRootlessConfigHomeDir(); err != nil { + return err + } + if err = os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { + return errors.Wrapf(err, "cannot set XDG_CONFIG_HOME") + } + } return nil } @@ -577,6 +599,12 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. if tmpConfig.NoPivotRoot { runtime.configuredFrom.noPivotRoot = true } + if tmpConfig.RuntimeSupportsJSON != nil { + runtime.configuredFrom.runtimeSupportsJSON = true + } + if tmpConfig.OCIRuntime != "" { + runtime.configuredFrom.ociRuntime = true + } if _, err := toml.Decode(string(contents), runtime.config); err != nil { return nil, errors.Wrapf(err, "error decoding configuration file %s", configPath) @@ -617,6 +645,13 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. if !runtime.configuredFrom.noPivotRoot { runtime.config.NoPivotRoot = tmpConfig.NoPivotRoot } + if !runtime.configuredFrom.runtimeSupportsJSON { + runtime.config.RuntimeSupportsJSON = tmpConfig.RuntimeSupportsJSON + } + if !runtime.configuredFrom.ociRuntime { + runtime.config.OCIRuntime = tmpConfig.OCIRuntime + } + break } } @@ -726,11 +761,43 @@ func getLockManager(runtime *Runtime) (lock.Manager, error) { return manager, nil } +// probeConmon calls conmon --version and verifies it is a new enough version for +// the runtime expectations podman currently has +func probeConmon(conmonBinary string) error { + cmd := exec.Command(conmonBinary, "--version") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return err + } + r := regexp.MustCompile(`^conmon version (?P<Major>\d+).(?P<Minor>\d+).(?P<Patch>\d+)`) + + matches := r.FindStringSubmatch(out.String()) + if len(matches) != 4 { + return errors.Wrapf(err, "conmon version changed format") + } + major, err := strconv.Atoi(matches[1]) + if err != nil || major < 1 { + return define.ErrConmonOutdated + } + // conmon used to be shipped with CRI-O, and was versioned along with it. + // even though the conmon that came with crio-1.9 to crio-1.15 has a higher + // version number than conmon 1.0.0, 1.0.0 is newer, so we need this check + minor, err := strconv.Atoi(matches[2]) + if err != nil || minor > 9 { + return define.ErrConmonOutdated + } + + return nil +} + // Make a new runtime based on the given configuration // Sets up containers/storage, state store, OCI runtime func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { // Find a working conmon binary foundConmon := false + foundOutdatedConmon := false for _, path := range runtime.config.ConmonPath { stat, err := os.Stat(path) if err != nil { @@ -739,6 +806,11 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { if stat.IsDir() { continue } + if err := probeConmon(path); err != nil { + logrus.Warnf("conmon at %s invalid: %v", path, err) + foundOutdatedConmon = true + continue + } foundConmon = true runtime.conmonPath = path logrus.Debugf("using conmon: %q", path) @@ -748,13 +820,21 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { // Search the $PATH as last fallback if !foundConmon { if conmon, err := exec.LookPath("conmon"); err == nil { - foundConmon = true - runtime.conmonPath = conmon - logrus.Debugf("using conmon from $PATH: %q", conmon) + if err := probeConmon(conmon); err != nil { + logrus.Warnf("conmon at %s is invalid: %v", conmon, err) + foundOutdatedConmon = true + } else { + foundConmon = true + runtime.conmonPath = conmon + logrus.Debugf("using conmon from $PATH: %q", conmon) + } } } if !foundConmon { + if foundOutdatedConmon { + return errors.Wrapf(define.ErrConmonOutdated, "please update to v1.0.0 or later") + } return errors.Wrapf(define.ErrInvalidArg, "could not find a working conmon binary (configured options: %v)", runtime.config.ConmonPath) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 47d49f6aa..92b2faefb 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -52,6 +52,16 @@ func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config if err != nil { return nil, errors.Wrapf(err, "error initializing container variables") } + // For an imported checkpoint no one has ever set the StartedTime. Set it now. + ctr.state.StartedTime = time.Now() + + // If the path to ConmonPidFile starts with the default value (RunRoot), then + // the user has not specified '--conmon-pidfile' during run or create (probably). + // In that case reset ConmonPidFile to be set to the default value later. + if strings.HasPrefix(ctr.config.ConmonPidFile, r.config.StorageConfig.RunRoot) { + ctr.config.ConmonPidFile = "" + } + return r.setupContainer(ctx, ctr) } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 4055734eb..20dee4080 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -28,6 +28,7 @@ import ( // RemoveImage deletes an image from local storage // Images being used by running containers can only be removed if force=true func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) (string, error) { + var returnMessage string r.lock.Lock() defer r.lock.Unlock() @@ -93,7 +94,11 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) err = errStorage } } - return img.ID(), err + for _, name := range img.Names() { + returnMessage = returnMessage + fmt.Sprintf("Untagged: %s\n", name) + } + returnMessage = returnMessage + fmt.Sprintf("Deleted: %s", img.ID()) + return returnMessage, err } // Remove containers that are in storage rather than Podman. diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index da35b7f93..ad6662f03 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -30,6 +31,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID return nil, err } + // Set Pod hostname + g.Config.Hostname = p.config.Hostname + isRootless := rootless.IsRootless() entryCmd := []string{r.config.InfraCommand} @@ -108,7 +112,7 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container, return nil, define.ErrRuntimeStopped } - newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, false, nil) + newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, nil, util.PullImageMissing) if err != nil { return nil, err } diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index f38e6e7c1..073c5054d 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -52,6 +52,10 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (_ *Po pod.config.Name = name } + if pod.config.Hostname == "" { + pod.config.Hostname = pod.config.Name + } + // Allocate a lock for the pod lock, err := r.lockManager.AllocateLock() if err != nil { diff --git a/libpod/util_linux.go b/libpod/util_linux.go index 78cbc75a7..d5c113daf 100644 --- a/libpod/util_linux.go +++ b/libpod/util_linux.go @@ -48,6 +48,9 @@ func makeSystemdCgroup(path string) error { return err } + if rootless.IsRootless() { + return controller.CreateSystemdUserUnit(path, rootless.GetRootlessUID()) + } return controller.CreateSystemdUnit(path) } @@ -57,6 +60,14 @@ func deleteSystemdCgroup(path string) error { if err != nil { return err } + if rootless.IsRootless() { + conn, err := cgroups.GetUserConnection(rootless.GetRootlessUID()) + if err != nil { + return err + } + defer conn.Close() + return controller.DeleteByPathConn(path, conn) + } return controller.DeleteByPath(path) } |