diff options
-rw-r--r-- | cmd/podman/images/list.go | 12 | ||||
-rw-r--r-- | cmd/podman/inspect.go | 18 | ||||
-rw-r--r-- | cmd/podman/main.go | 5 | ||||
-rw-r--r-- | docs/source/markdown/podman-inspect.1.md | 14 | ||||
-rw-r--r-- | libpod/container_internal.go | 52 | ||||
-rw-r--r-- | libpod/options.go | 13 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 2 | ||||
-rw-r--r-- | libpod/volume.go | 36 | ||||
-rw-r--r-- | libpod/volume_inspect.go | 11 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 1 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/volumes.go | 30 | ||||
-rw-r--r-- | pkg/api/handlers/types.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/images.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/images_list.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/volumes.go | 26 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 9 | ||||
-rwxr-xr-x | test/apiv2/test-apiv2 | 2 | ||||
-rw-r--r-- | test/e2e/run_userns_test.go | 27 | ||||
-rw-r--r-- | utils/utils_supported.go | 4 |
19 files changed, 226 insertions, 42 deletions
diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index de7cca40d..53be82dda 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -128,7 +128,7 @@ func writeID(imgs []imageReporter) error { func writeJSON(images []imageReporter) error { type image struct { entities.ImageSummary - Created string + Created int64 CreatedAt string } @@ -136,8 +136,8 @@ func writeJSON(images []imageReporter) error { for _, e := range images { var h image h.ImageSummary = e.ImageSummary - h.Created = units.HumanDuration(time.Since(e.ImageSummary.Created)) + " ago" - h.CreatedAt = e.ImageSummary.Created.Format(time.RFC3339Nano) + h.Created = e.ImageSummary.Created + h.CreatedAt = e.created().Format(time.RFC3339Nano) h.RepoTags = nil imgs = append(imgs, h) @@ -284,11 +284,11 @@ func (i imageReporter) ID() string { } func (i imageReporter) Created() string { - return units.HumanDuration(time.Since(i.ImageSummary.Created)) + " ago" + return units.HumanDuration(time.Since(i.created())) + " ago" } func (i imageReporter) created() time.Time { - return i.ImageSummary.Created + return time.Unix(i.ImageSummary.Created, 0).UTC() } func (i imageReporter) Size() string { @@ -302,7 +302,7 @@ func (i imageReporter) History() string { } func (i imageReporter) CreatedAt() string { - return i.ImageSummary.Created.String() + return i.created().String() } func (i imageReporter) CreatedSince() string { diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 12e11d0f5..befdeb445 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -8,12 +8,22 @@ import ( ) var ( + inspectDescription = `Displays the low-level information on an object identified by name or ID. + For more inspection options, see: + + podman container inspect + podman image inspect + podman network inspect + podman pod inspect + podman volume inspect` + // Command: podman _inspect_ Object_ID inspectCmd = &cobra.Command{ - Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID} [...]", - Short: "Display the configuration of object denoted by ID", - Long: "Displays the low-level information on an object identified by name or ID", - RunE: inspectExec, + Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID} [...]", + Short: "Display the configuration of object denoted by ID", + RunE: inspectExec, + Long: inspectDescription, + TraverseChildren: true, Example: `podman inspect fedora podman inspect --type image fedora podman inspect CtrID ImgID diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 7a015b300..636538131 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -29,6 +29,11 @@ func main() { return } + // Hard code TMPDIR functions to use /var/tmp, if user did not override + if _, ok := os.LookupEnv("TMPDIR"); !ok { + os.Setenv("TMPDIR", "/var/tmp") + } + cfg := registry.PodmanConfig() for _, c := range registry.Commands { for _, m := range c.Mode { diff --git a/docs/source/markdown/podman-inspect.1.md b/docs/source/markdown/podman-inspect.1.md index 4998772c3..a1dcd1a0e 100644 --- a/docs/source/markdown/podman-inspect.1.md +++ b/docs/source/markdown/podman-inspect.1.md @@ -6,15 +6,21 @@ podman\-inspect - Display a container or image's configuration ## SYNOPSIS **podman inspect** [*options*] *name* [...] -**podman image inspect** [*options*] *image* - -**podman container inspect** [*options*] *container* - ## DESCRIPTION + This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type. If a format is specified, the given template will be executed for each result. +For more inspection options, see: + + podman container inspect + podman image inspect + podman network inspect + podman pod inspect + podman volume inspect + + ## OPTIONS **--type**, **-t**=*type* diff --git a/libpod/container_internal.go b/libpod/container_internal.go index db64f5eeb..27b795871 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1015,6 +1015,12 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error { return err } + for _, v := range c.config.NamedVolumes { + if err := c.chownVolume(v.Name); err != nil { + return err + } + } + // With the spec complete, do an OCI create if err := c.ociRuntime.CreateContainer(c, nil); err != nil { // Fedora 31 is carrying a patch to display improved error @@ -1508,6 +1514,48 @@ func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string) return vol, nil } +// Chown the specified volume if necessary. +func (c *Container) chownVolume(volumeName string) error { + vol, err := c.runtime.state.Volume(volumeName) + if err != nil { + return errors.Wrapf(err, "error retrieving named volume %s for container %s", volumeName, c.ID()) + } + + uid := int(c.config.Spec.Process.User.UID) + gid := int(c.config.Spec.Process.User.GID) + + vol.lock.Lock() + defer vol.lock.Unlock() + + // The volume may need a copy-up. Check the state. + if err := vol.update(); err != nil { + return err + } + + if vol.state.NeedsChown { + vol.state.NeedsChown = false + vol.state.UIDChowned = uid + vol.state.GIDChowned = gid + + if err := vol.save(); err != nil { + return err + } + err := filepath.Walk(vol.MountPoint(), func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if err := os.Chown(path, uid, gid); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + } + return nil +} + // cleanupStorage unmounts and cleans up the container's root filesystem func (c *Container) cleanupStorage() error { if !c.state.Mounted { @@ -1854,8 +1902,8 @@ func (c *Container) unmount(force bool) error { // this should be from chrootarchive. // Container MUST be mounted before calling. func (c *Container) copyWithTarFromImage(source, dest string) error { - a := archive.NewDefaultArchiver() - + mappings := idtools.NewIDMappingsFromMaps(c.config.IDMappings.UIDMap, c.config.IDMappings.GIDMap) + a := archive.NewArchiver(mappings) if err := c.copyOwnerAndPerms(source, dest); err != nil { return err } diff --git a/libpod/options.go b/libpod/options.go index 28be1bc03..4041fb1cf 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1497,6 +1497,19 @@ func WithVolumeGID(gid int) VolumeCreateOption { } } +// WithVolumeNeedsChown sets the NeedsChown flag for the volume. +func WithVolumeNeedsChown() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.state.NeedsChown = true + + return nil + } +} + // withSetAnon sets a bool notifying libpod that this volume is anonymous and // should be removed when containers using it are removed and volumes are // specified for removal. diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index dd6602acb..74647dab8 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -309,7 +309,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai logrus.Debugf("Creating new volume %s for container", vol.Name) // The volume does not exist, so we need to create it. - volOptions := []VolumeCreateOption{WithVolumeName(vol.Name), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())} + volOptions := []VolumeCreateOption{WithVolumeName(vol.Name), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID()), WithVolumeNeedsChown()} if isAnonymous { volOptions = append(volOptions, withSetAnon()) } diff --git a/libpod/volume.go b/libpod/volume.go index b29ac7ddf..58d1f81a6 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -64,6 +64,14 @@ type VolumeState struct { // create time, then cleared after the copy up is done and never set // again. NeedsCopyUp bool `json:"notYetMounted,omitempty"` + // NeedsChown indicates that the next time the volume is mounted into + // a container, the container will chown the volume to the container process + // UID/GID. + NeedsChown bool `json:"notYetChowned,omitempty"` + // UIDChowned is the UID the volume was chowned to. + UIDChowned int `json:"uidChowned,omitempty"` + // GIDChowned is the GID the volume was chowned to. + GIDChowned int `json:"gidChowned,omitempty"` } // Name retrieves the volume's name @@ -113,13 +121,33 @@ func (v *Volume) Anonymous() bool { } // UID returns the UID the volume will be created as. -func (v *Volume) UID() int { - return v.config.UID +func (v *Volume) UID() (int, error) { + v.lock.Lock() + defer v.lock.Unlock() + + if !v.valid { + return -1, define.ErrVolumeRemoved + } + + if v.state.UIDChowned > 0 { + return v.state.UIDChowned, nil + } + return v.config.UID, nil } // GID returns the GID the volume will be created as. -func (v *Volume) GID() int { - return v.config.GID +func (v *Volume) GID() (int, error) { + v.lock.Lock() + defer v.lock.Unlock() + + if !v.valid { + return -1, define.ErrVolumeRemoved + } + + if v.state.GIDChowned > 0 { + return v.state.GIDChowned, nil + } + return v.config.GID, nil } // CreatedTime returns the time the volume was created at. It was not tracked diff --git a/libpod/volume_inspect.go b/libpod/volume_inspect.go index 136f9da5e..2be0aeaec 100644 --- a/libpod/volume_inspect.go +++ b/libpod/volume_inspect.go @@ -65,8 +65,15 @@ func (v *Volume) Inspect() (*InspectVolumeData, error) { for k, v := range v.config.Options { data.Options[k] = v } - data.UID = v.config.UID - data.GID = v.config.GID + var err error + data.UID, err = v.UID() + if err != nil { + return nil, err + } + data.GID, err = v.GID() + if err != nil { + return nil, err + } data.Anonymous = v.config.IsAnon return data, nil diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 54e202103..ebcb1f460 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -112,7 +112,6 @@ func GetImages(w http.ResponseWriter, r *http.Request) { return } // libpod has additional fields that we need to populate. - is.Created = img.Created() is.ReadOnly = img.IsReadOnly() summaries[j] = is } diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index 4b3b5430b..6523244f3 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -86,6 +86,17 @@ func InspectVolume(w http.ResponseWriter, r *http.Request) { utils.VolumeNotFound(w, name, err) return } + var uid, gid int + uid, err = vol.UID() + if err != nil { + utils.Error(w, "Error fetching volume UID", http.StatusInternalServerError, err) + return + } + gid, err = vol.GID() + if err != nil { + utils.Error(w, "Error fetching volume GID", http.StatusInternalServerError, err) + return + } volResponse := entities.VolumeConfigResponse{ Name: vol.Name(), Driver: vol.Driver(), @@ -94,8 +105,8 @@ func InspectVolume(w http.ResponseWriter, r *http.Request) { Labels: vol.Labels(), Scope: vol.Scope(), Options: vol.Options(), - UID: vol.UID(), - GID: vol.GID(), + UID: uid, + GID: gid, } utils.WriteResponse(w, http.StatusOK, volResponse) } @@ -130,6 +141,17 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { } volumeConfigs := make([]*entities.VolumeListReport, 0, len(vols)) for _, v := range vols { + var uid, gid int + uid, err = v.UID() + if err != nil { + utils.Error(w, "Error fetching volume UID", http.StatusInternalServerError, err) + return + } + gid, err = v.GID() + if err != nil { + utils.Error(w, "Error fetching volume GID", http.StatusInternalServerError, err) + return + } config := entities.VolumeConfigResponse{ Name: v.Name(), Driver: v.Driver(), @@ -138,8 +160,8 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { Labels: v.Labels(), Scope: v.Scope(), Options: v.Options(), - UID: v.UID(), - GID: v.GID(), + UID: uid, + GID: gid, } volumeConfigs = append(volumeConfigs, &entities.VolumeListReport{VolumeConfigResponse: config}) } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index c1e84ab5a..72e1a756e 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -221,7 +221,7 @@ func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) { ID: l.ID(), ParentId: l.Parent, RepoTags: repoTags, - Created: l.Created(), + Created: l.Created().Unix(), Size: int64(*size), SharedSize: 0, VirtualSize: l.VirtualSize, diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 81f52fef5..27f887e8e 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -53,7 +53,7 @@ type ImageSummary struct { ID string `json:"Id"` ParentId string `json:",omitempty"` // nolint RepoTags []string `json:",omitempty"` - Created time.Time `json:",omitempty"` + Created int64 `json:",omitempty"` Size int64 `json:",omitempty"` SharedSize int `json:",omitempty"` VirtualSize int64 `json:",omitempty"` diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 98c041c15..92ab0a998 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -52,7 +52,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ID: img.ID(), ConfigDigest: string(img.ConfigDigest), - Created: img.Created(), + Created: img.Created().Unix(), Dangling: img.Dangling(), Digest: string(img.Digest()), Digests: digests, diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index 702e11003..36847dd79 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -95,6 +95,15 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin } reports := make([]*entities.VolumeInspectReport, 0, len(vols)) for _, v := range vols { + var uid, gid int + uid, err = v.UID() + if err != nil { + return nil, err + } + gid, err = v.GID() + if err != nil { + return nil, err + } config := entities.VolumeConfigResponse{ Name: v.Name(), Driver: v.Driver(), @@ -103,8 +112,8 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin Labels: v.Labels(), Scope: v.Scope(), Options: v.Options(), - UID: v.UID(), - GID: v.GID(), + UID: uid, + GID: gid, } reports = append(reports, &entities.VolumeInspectReport{VolumeConfigResponse: &config}) } @@ -141,6 +150,15 @@ func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeL } reports := make([]*entities.VolumeListReport, 0, len(vols)) for _, v := range vols { + var uid, gid int + uid, err = v.UID() + if err != nil { + return nil, err + } + gid, err = v.GID() + if err != nil { + return nil, err + } config := entities.VolumeConfigResponse{ Name: v.Name(), Driver: v.Driver(), @@ -149,8 +167,8 @@ func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeL Labels: v.Labels(), Scope: v.Scope(), Options: v.Options(), - UID: v.UID(), - GID: v.GID(), + UID: uid, + GID: gid, } reports = append(reports, &entities.VolumeListReport{VolumeConfigResponse: config}) } diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index e19c582b5..a04afa00f 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -287,10 +287,11 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithCommand(c.UserCommand)) } - // Add entrypoint unconditionally - // If it's empty it's because it was explicitly set to "" or the image - // does not have one - options = append(options, libpod.WithEntrypoint(c.Entrypoint)) + // Add entrypoint if it was set + // If it's empty it's because it was explicitly set to "" + if c.Entrypoint != nil { + options = append(options, libpod.WithEntrypoint(c.Entrypoint)) + } // TODO: MNT, USER, CGROUP options = append(options, libpod.WithStopSignal(c.StopSignal)) diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index 7a3518df2..d0bf28b9a 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -84,7 +84,7 @@ function like() { if expr "$actual" : "$expect" &>/dev/null; then # On success, include expected value; this helps readers understand - _show_ok 1 "$testname~$expect" + _show_ok 1 "$testname ('$actual') ~ $expect" return fi _show_ok 0 "$testname" "~ $expect" "$actual" diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index be0981408..3e55f56c0 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -245,4 +245,31 @@ var _ = Describe("Podman UserNS support", func() { ok, _ := session.GrepString("4998") Expect(ok).To(BeTrue()) }) + + It("podman --user with volume", func() { + tests := []struct { + uid, gid, arg, vol string + }{ + {"0", "0", "0:0", "vol-0"}, + {"1000", "0", "1000", "vol-1"}, + {"1000", "1000", "1000:1000", "vol-2"}, + } + + for _, tt := range tests { + session := podmanTest.Podman([]string{"run", "-d", "--user", tt.arg, "--mount", "type=volume,src=" + tt.vol + ",dst=/home/user", "alpine", "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspectUID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .UID }}", tt.vol}) + inspectUID.WaitWithDefaultTimeout() + Expect(inspectUID.ExitCode()).To(Equal(0)) + Expect(inspectUID.OutputToString()).To(Equal(tt.uid)) + + // Make sure we're defaulting to 0. + inspectGID := podmanTest.Podman([]string{"volume", "inspect", "--format", "{{ .GID }}", tt.vol}) + inspectGID.WaitWithDefaultTimeout() + Expect(inspectGID.ExitCode()).To(Equal(0)) + Expect(inspectGID.OutputToString()).To(Equal(tt.gid)) + } + }) }) diff --git a/utils/utils_supported.go b/utils/utils_supported.go index 201ddb57b..4258e6d7a 100644 --- a/utils/utils_supported.go +++ b/utils/utils_supported.go @@ -64,7 +64,7 @@ func getCgroupProcess(procFile string) (string, error) { cgroup := "/" for scanner.Scan() { line := scanner.Text() - parts := strings.Split(line, ":") + parts := strings.SplitN(line, ":", 3) if len(parts) != 3 { return "", errors.Errorf("cannot parse cgroup line %q", line) } @@ -116,7 +116,7 @@ func MoveUnderCgroupSubtree(subtree string) error { scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() - parts := strings.Split(line, ":") + parts := strings.SplitN(line, ":", 3) if len(parts) != 3 { return errors.Errorf("cannot parse cgroup line %q", line) } |