summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile9
-rwxr-xr-xcontrib/build_rpm.sh1
-rw-r--r--docs/source/markdown/podman-build.1.md4
-rw-r--r--docs/source/markdown/podman-create.1.md4
-rw-r--r--docs/source/markdown/podman-info.1.md27
-rw-r--r--docs/source/markdown/podman-run.1.md4
-rwxr-xr-xhack/systemd_tag.sh7
-rw-r--r--install.md6
-rw-r--r--libpod/container_internal_linux.go98
-rw-r--r--libpod/diff.go44
-rw-r--r--libpod/runtime.go20
-rw-r--r--pkg/adapter/checkpoint_restore.go1
-rw-r--r--pkg/registries/registries.go7
-rw-r--r--test/e2e/checkpoint_test.go20
-rw-r--r--test/e2e/e2e.coverprofile11
-rw-r--r--test/system/010-images.bats39
-rw-r--r--test/system/helpers.bash2
-rw-r--r--troubleshooting.md18
18 files changed, 213 insertions, 109 deletions
diff --git a/Makefile b/Makefile
index 0cb194cbb..fb6e48585 100644
--- a/Makefile
+++ b/Makefile
@@ -45,7 +45,7 @@ endif
ifeq (,$(findstring systemd,$(BUILDTAGS)))
$(warning \
Podman is being compiled without the systemd build tag.\
- Install libsystemd for journald support)
+ Install libsystemd on Ubuntu or systemd-devel on rpm based distro for journald support)
endif
BUILDTAGS_CROSS ?= containers_image_openpgp exclude_graphdriver_btrfs exclude_graphdriver_devicemapper exclude_graphdriver_overlay
@@ -241,6 +241,7 @@ testunit: libpodimage ## Run unittest on the built image
localunit: test/goecho/goecho varlink_generate
ginkgo \
-r \
+ $(TESTFLAGS) \
--skipPackage test/e2e,pkg/apparmor,test/endpoint \
--cover \
--covermode atomic \
@@ -248,13 +249,13 @@ localunit: test/goecho/goecho varlink_generate
--succinct
ginkgo:
- ginkgo -v -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 -debug test/e2e/.
+ ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 -debug test/e2e/.
ginkgo-remote:
- ginkgo -v -tags "$(BUILDTAGS) remoteclient" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/.
+ ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS) remoteclient" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/.
endpoint:
- ginkgo -v -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -debug test/endpoint/.
+ ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -debug test/endpoint/.
localintegration: varlink_generate test-binaries ginkgo
diff --git a/contrib/build_rpm.sh b/contrib/build_rpm.sh
index e41763fa7..088d8b7a5 100755
--- a/contrib/build_rpm.sh
+++ b/contrib/build_rpm.sh
@@ -26,6 +26,7 @@ declare -a PKGS=(device-mapper-devel \
make \
rpm-build \
go-compilers-golang-compiler \
+ systemd-devel \
)
if [[ $pkg_manager == *dnf ]]; then
diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md
index 6c8f239a6..fac8296ad 100644
--- a/docs/source/markdown/podman-build.1.md
+++ b/docs/source/markdown/podman-build.1.md
@@ -176,6 +176,10 @@ value can be entered. The password is entered without echo.
Add a host device to the container. The format is `<device-on-host>[:<device-on-container>][:<permissions>]` (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
+Note: if the user only has access rights via a group then accessing the device
+from inside a rootless container will fail. The `crun` runtime offers a
+workaround for this by adding the option `--annotation io.crun.keep_original_groups=1`.
+
**--disable-compression, -D**
Don't compress filesystem layers when building the image unless it is required
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 82d2e8f6a..85aa81553 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -204,6 +204,10 @@ Specify the key sequence for detaching a container. Format is a single character
Add a host device to the container. The format is `<device-on-host>[:<device-on-container>][:<permissions>]` (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
+Note: if the user only has access rights via a group then accessing the device
+from inside a rootless container will fail. The `crun` runtime offers a
+workaround for this by adding the option `--annotation io.crun.keep_original_groups=1`.
+
**--device-read-bps**=*path*
Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
diff --git a/docs/source/markdown/podman-info.1.md b/docs/source/markdown/podman-info.1.md
index 9721755ef..b539f1d3c 100644
--- a/docs/source/markdown/podman-info.1.md
+++ b/docs/source/markdown/podman-info.1.md
@@ -53,13 +53,26 @@ host:
os: linux
uptime: 218h 49m 33.66s (Approximately 9.08 days)
registries:
- blocked: null
- insecure: null
- search:
- - quay.io
- - registry.fedoraproject.org
- - docker.io
- - registry.redhat.io
+ docker.io:
+ Blocked: true
+ Insecure: true
+ Location: docker.io
+ MirrorByDigestOnly: false
+ Mirrors:
+ - Insecure: true
+ Location: example2.io/example/ubi8-minimal
+ Prefix: docker.io
+ redhat.com:
+ Blocked: false
+ Insecure: false
+ Location: registry.access.redhat.com/ubi8
+ MirrorByDigestOnly: true
+ Mirrors:
+ - Insecure: false
+ Location: example.io/example/ubi8-minimal
+ - Insecure: true
+ Location: example3.io/example/ubi8-minimal
+ Prefix: redhat.com
store:
ConfigFile: /etc/containers/storage.conf
ContainerStore:
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index e1177cb34..e8744de35 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -210,6 +210,10 @@ Specify the key sequence for detaching a container. Format is a single character
Add a host device to the container. The format is `<device-on-host>[:<device-on-container>][:<permissions>]` (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
+Note: if the user only has access rights via a group then accessing the device
+from inside a rootless container will fail. The `crun` runtime offers a
+workaround for this by adding the option `--annotation io.crun.keep_original_groups=1`.
+
**--device-read-bps**=*path*
Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
diff --git a/hack/systemd_tag.sh b/hack/systemd_tag.sh
index c59cad559..19a7bf6a6 100755
--- a/hack/systemd_tag.sh
+++ b/hack/systemd_tag.sh
@@ -1,4 +1,7 @@
#!/usr/bin/env bash
-if pkg-config --exists libsystemd; then
- echo systemd
+cc -E - > /dev/null 2> /dev/null << EOF
+#include <systemd/sd-daemon.h>
+EOF
+if test $? -eq 0 ; then
+ echo systemd
fi
diff --git a/install.md b/install.md
index 218994587..5771fff45 100644
--- a/install.md
+++ b/install.md
@@ -149,6 +149,12 @@ sudo apt-get install \
uidmap
```
+On openSUSE Leap 15.x and Tumbleweed:
+
+```bash
+sudo zypper -n in libseccomp-devel libgpgme-devel
+```
+
On Manjaro (and maybe other Linux distributions):
Make sure that the Linux kernel supports user namespaces:
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 1b0570998..6ec06943f 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -593,22 +593,68 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error)
// Get root file-system changes included in the checkpoint archive
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
+ deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files")
if !ignoreRootfs {
- rootfsDiffFile, err := os.Create(rootfsDiffPath)
- if err != nil {
- return errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath)
- }
- tarStream, err := c.runtime.GetDiffTarStream("", c.ID())
+ // To correctly track deleted files, let's go through the output of 'podman diff'
+ tarFiles, err := c.runtime.GetDiff("", c.ID())
if err != nil {
return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
}
- _, err = io.Copy(rootfsDiffFile, tarStream)
- if err != nil {
- return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
+ var rootfsIncludeFiles []string
+ var deletedFiles []string
+
+ for _, file := range tarFiles {
+ if file.Kind == archive.ChangeAdd {
+ rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
+ continue
+ }
+ if file.Kind == archive.ChangeDelete {
+ deletedFiles = append(deletedFiles, file.Path)
+ continue
+ }
+ fileName, err := os.Stat(file.Path)
+ if err != nil {
+ continue
+ }
+ if !fileName.IsDir() && file.Kind == archive.ChangeModify {
+ rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path)
+ continue
+ }
+ }
+
+ if len(rootfsIncludeFiles) > 0 {
+ rootfsTar, err := archive.TarWithOptions(c.state.Mountpoint, &archive.TarOptions{
+ Compression: archive.Uncompressed,
+ IncludeSourceDir: true,
+ IncludeFiles: rootfsIncludeFiles,
+ })
+ if err != nil {
+ return errors.Wrapf(err, "error exporting root file-system diff to %q", rootfsDiffPath)
+ }
+ rootfsDiffFile, err := os.Create(rootfsDiffPath)
+ if err != nil {
+ return errors.Wrapf(err, "error creating root file-system diff file %q", rootfsDiffPath)
+ }
+ defer rootfsDiffFile.Close()
+ _, err = io.Copy(rootfsDiffFile, rootfsTar)
+ if err != nil {
+ return err
+ }
+
+ includeFiles = append(includeFiles, "rootfs-diff.tar")
+ }
+
+ if len(deletedFiles) > 0 {
+ formatJSON, err := json.MarshalIndent(deletedFiles, "", " ")
+ if err != nil {
+ 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)
+ }
+
+ includeFiles = append(includeFiles, "deleted.files")
}
- tarStream.Close()
- rootfsDiffFile.Close()
- includeFiles = append(includeFiles, "rootfs-diff.tar")
}
input, err := archive.TarWithOptions(c.bundlePath(), &archive.TarOptions{
@@ -637,6 +683,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error)
}
os.Remove(rootfsDiffPath)
+ os.Remove(deleteFilesList)
return nil
}
@@ -941,10 +988,35 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if err != nil {
return errors.Wrapf(err, "Failed to open root file-system diff file %s", rootfsDiffPath)
}
+ defer rootfsDiffFile.Close()
if err := c.runtime.ApplyDiffTarStream(c.ID(), rootfsDiffFile); err != nil {
return errors.Wrapf(err, "Failed to apply root file-system diff file %s", rootfsDiffPath)
}
- rootfsDiffFile.Close()
+ }
+ deletedFilesPath := filepath.Join(c.bundlePath(), "deleted.files")
+ if _, err := os.Stat(deletedFilesPath); err == nil {
+ deletedFilesFile, err := os.Open(deletedFilesPath)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to open deleted files file %s", deletedFilesPath)
+ }
+ defer deletedFilesFile.Close()
+
+ var deletedFiles []string
+ deletedFilesJSON, err := ioutil.ReadAll(deletedFilesFile)
+ if err != nil {
+ return errors.Wrapf(err, "Failed to read deleted files file %s", deletedFilesPath)
+ }
+ if err := json.Unmarshal(deletedFilesJSON, &deletedFiles); err != nil {
+ return errors.Wrapf(err, "Failed to read 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())
+ }
+ }
}
}
@@ -965,7 +1037,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if err != nil {
logrus.Debugf("Non-fatal: removal of checkpoint directory (%s) failed: %v", c.CheckpointPath(), err)
}
- cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status", "rootfs-diff.tar"}
+ cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status", "rootfs-diff.tar", "deleted.files"}
for _, del := range cleanup {
file := filepath.Join(c.bundlePath(), del)
err = os.Remove(file)
diff --git a/libpod/diff.go b/libpod/diff.go
index 925bda927..baa4d6ad7 100644
--- a/libpod/diff.go
+++ b/libpod/diff.go
@@ -1,7 +1,6 @@
package libpod
import (
- "archive/tar"
"io"
"github.com/containers/libpod/libpod/layers"
@@ -47,49 +46,6 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
return rchanges, err
}
-// skipFileInTarAchive is an archive.TarModifierFunc function
-// which tells archive.ReplaceFileTarWrapper to skip files
-// from the tarstream
-func skipFileInTarAchive(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
- return nil, nil, nil
-}
-
-// GetDiffTarStream returns the differences between the two images, layers, or containers.
-// It is the same functionality as GetDiff() except that it returns a tarstream
-func (r *Runtime) GetDiffTarStream(from, to string) (io.ReadCloser, error) {
- toLayer, err := r.getLayerID(to)
- if err != nil {
- return nil, err
- }
- fromLayer := ""
- if from != "" {
- fromLayer, err = r.getLayerID(from)
- if err != nil {
- return nil, err
- }
- }
- rc, err := r.store.Diff(fromLayer, toLayer, nil)
- if err != nil {
- return nil, err
- }
-
- // Skip files in the tar archive which are listed
- // in containerMounts map. Just as in the GetDiff()
- // function from above
- filterMap := make(map[string]archive.TarModifierFunc)
- for key := range containerMounts {
- filterMap[key[1:]] = skipFileInTarAchive
- // In the tarstream directories always include a trailing '/'.
- // For simplicity this duplicates every entry from
- // containerMounts with a trailing '/', as containerMounts
- // does not use trailing '/' for directories.
- filterMap[key[1:]+"/"] = skipFileInTarAchive
- }
-
- filteredTarStream := archive.ReplaceFileTarWrapper(rc, filterMap)
- return filteredTarStream, nil
-}
-
// ApplyDiffTarStream applies the changes stored in 'diff' to the layer 'to'
func (r *Runtime) ApplyDiffTarStream(to string, diff io.Reader) error {
toLayer, err := r.getLayerID(to)
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 3873079ce..001d850b0 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -691,24 +691,22 @@ func (r *Runtime) Info() ([]define.InfoData, error) {
}
info = append(info, define.InfoData{Type: "store", Data: storeInfo})
- reg, err := sysreg.GetRegistries()
- if err != nil {
- return nil, errors.Wrapf(err, "error getting registries")
- }
registries := make(map[string]interface{})
- registries["search"] = reg
-
- ireg, err := sysreg.GetInsecureRegistries()
+ data, err := sysreg.GetRegistriesData()
if err != nil {
return nil, errors.Wrapf(err, "error getting registries")
}
- registries["insecure"] = ireg
-
- breg, err := sysreg.GetBlockedRegistries()
+ for _, reg := range data {
+ registries[reg.Prefix] = reg
+ }
+ regs, err := sysreg.GetRegistries()
if err != nil {
return nil, errors.Wrapf(err, "error getting registries")
}
- registries["blocked"] = breg
+ if len(regs) > 0 {
+ registries["search"] = regs
+ }
+
info = append(info, define.InfoData{Type: "registries", Data: registries})
return info, nil
}
diff --git a/pkg/adapter/checkpoint_restore.go b/pkg/adapter/checkpoint_restore.go
index 15f9e8105..7f80b782a 100644
--- a/pkg/adapter/checkpoint_restore.go
+++ b/pkg/adapter/checkpoint_restore.go
@@ -60,6 +60,7 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
"ctr.log",
"rootfs-diff.tar",
"network.status",
+ "deleted.files",
},
}
dir, err := ioutil.TempDir("", "checkpoint")
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 9643c947f..ba7de7cf9 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -34,7 +34,8 @@ func SystemRegistriesConfPath() string {
return ""
}
-func getRegistries() ([]sysregistriesv2.Registry, error) {
+// GetRegistriesData obtains the list of registries
+func GetRegistriesData() ([]sysregistriesv2.Registry, error) {
registries, err := sysregistriesv2.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()})
if err != nil {
return nil, errors.Wrapf(err, "unable to parse the registries.conf file")
@@ -50,7 +51,7 @@ func GetRegistries() ([]string, error) {
// GetBlockedRegistries obtains the list of blocked registries defined in the global registries file.
func GetBlockedRegistries() ([]string, error) {
var blockedRegistries []string
- registries, err := getRegistries()
+ registries, err := GetRegistriesData()
if err != nil {
return nil, err
}
@@ -65,7 +66,7 @@ func GetBlockedRegistries() ([]string, error) {
// GetInsecureRegistries obtains the list of insecure registries from the global registration file.
func GetInsecureRegistries() ([]string, error) {
var insecureRegistries []string
- registries, err := getRegistries()
+ registries, err := GetRegistriesData()
if err != nil {
return nil, err
}
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index f208a4cf0..237223283 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -439,6 +439,18 @@ var _ = Describe("Podman checkpoint", func() {
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
+ result = podmanTest.Podman([]string{"exec", "-l", "/bin/sh", "-c", "rm /etc/motd"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ result = podmanTest.Podman([]string{"diff", "-l"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(result.OutputToString()).To(ContainSubstring("C /etc"))
+ Expect(result.OutputToString()).To(ContainSubstring("A /test.output"))
+ Expect(result.OutputToString()).To(ContainSubstring("D /etc/motd"))
+ Expect(len(result.OutputToStringArray())).To(Equal(3))
+
// Checkpoint the container
result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", fileName})
result.WaitWithDefaultTimeout()
@@ -462,6 +474,14 @@ var _ = Describe("Podman checkpoint", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(result.OutputToString()).To(ContainSubstring("test" + cid + "test"))
+ result = podmanTest.Podman([]string{"diff", "-l"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(result.OutputToString()).To(ContainSubstring("C /etc"))
+ Expect(result.OutputToString()).To(ContainSubstring("A /test.output"))
+ Expect(result.OutputToString()).To(ContainSubstring("D /etc/motd"))
+ Expect(len(result.OutputToStringArray())).To(Equal(3))
+
// Remove exported checkpoint
os.Remove(fileName)
})
diff --git a/test/e2e/e2e.coverprofile b/test/e2e/e2e.coverprofile
deleted file mode 100644
index d413679ea..000000000
--- a/test/e2e/e2e.coverprofile
+++ /dev/null
@@ -1,11 +0,0 @@
-mode: atomic
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:14.46,21.20 2 3
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:32.2,32.19 1 3
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:39.2,39.53 1 3
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:66.2,66.52 1 3
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:21.20,23.17 2 6
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:26.3,29.36 4 6
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:23.17,25.4 1 0
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:32.19,37.3 3 6
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:39.53,64.3 20 3
-github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:66.52,91.3 20 3 \ No newline at end of file
diff --git a/test/system/010-images.bats b/test/system/010-images.bats
index 543876509..66ef53590 100644
--- a/test/system/010-images.bats
+++ b/test/system/010-images.bats
@@ -45,18 +45,33 @@ size | [0-9]\\\+
}
@test "podman images - history output" {
- run_podman images --format json
- actual=$(echo $output | jq -r '.[0].history | length')
- is "$actual" "0"
-
- run_podman tag $PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG test-image
- run_podman images --format json
- actual=$(echo $output | jq -r '.[1].history | length')
- is "$actual" "0"
- actual=$(echo $output | jq -r '.[0].history | length')
- is "$actual" "1"
- actual=$(echo $output | jq -r '.[0].history[0]')
- is "$actual" "$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG"
+ # podman history is persistent: it permanently alters our base image.
+ # Create a dummy image here so we leave our setup as we found it.
+ run_podman run --name my-container $IMAGE true
+ run_podman commit my-container my-test-image
+
+ run_podman images my-test-image --format '{{ .History }}'
+ is "$output" "" "Image has empty history to begin with"
+
+ # Generate two randomish tags; 'tr' because they must be all lower-case
+ rand_name1="test-image-history-$(random_string 10 | tr A-Z a-z)"
+ rand_name2="test-image-history-$(random_string 10 | tr A-Z a-z)"
+
+ # Tag once, rmi, and make sure the tag name appears in history
+ run_podman tag my-test-image $rand_name1
+ run_podman rmi $rand_name1
+ run_podman images my-test-image --format '{{ .History }}'
+ is "$output" "localhost/${rand_name1}:latest" "image history after one tag"
+
+ # Repeat with second tag. Now both tags should be in history
+ run_podman tag my-test-image $rand_name2
+ run_podman rmi $rand_name2
+ run_podman images my-test-image --format '{{ .History }}'
+ is "$output" "localhost/${rand_name2}:latest, localhost/${rand_name1}:latest" \
+ "image history after two tags"
+
+ run_podman rmi my-test-image
+ run_podman rm my-container
}
# vim: filetype=sh
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 8c061d2c9..940f3f426 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -36,7 +36,7 @@ function basic_setup() {
if [ "$1" == "$PODMAN_TEST_IMAGE_FQN" ]; then
found_needed_image=1
else
- echo "# setup(): removing stray images" >&3
+ echo "# setup(): removing stray images $1 $2" >&3
run_podman rmi --force "$1" >/dev/null 2>&1 || true
run_podman rmi --force "$2" >/dev/null 2>&1 || true
fi
diff --git a/troubleshooting.md b/troubleshooting.md
index 432c0e32b..d122983d7 100644
--- a/troubleshooting.md
+++ b/troubleshooting.md
@@ -468,4 +468,20 @@ $ podman unshare cat /proc/self/uid_map
1 100000 65536
```
-Reference [subuid](http://man7.org/linux/man-pages/man5/subuid.5.html) and [subgid](http://man7.org/linux/man-pages/man5/subgid.5.html) man pages for more detail. \ No newline at end of file
+Reference [subuid](http://man7.org/linux/man-pages/man5/subuid.5.html) and [subgid](http://man7.org/linux/man-pages/man5/subgid.5.html) man pages for more detail.
+
+### 20) Passed-in device can't be accessed in rootless container
+
+As a non-root user you have group access rights to a device that you want to
+pass into a rootless container with `--device=...`.
+
+#### Symptom
+
+Any access inside the container is rejected with "Permission denied".
+
+#### Solution
+
+The runtime uses `setgroups(2)` hence the process looses all additional groups
+the non-root user has. If you use the `crun` runtime, 0.10.4 or newer,
+then you can enable a workaround by adding `--annotation io.crun.keep_original_groups=1`
+to the `podman` command line.