Files
podman/test/e2e/artifact_mount_test.go
Paul Holzinger 9e94dc53b2 add new artifact mount type
Add a new option to allow for mounting artifacts in the container, the
syntax is added to the existing --mount option:
type=artifact,src=$artifactName,dest=/path[,digest=x][,title=x]

This works very similar to image mounts. The name is passed down into
the container config and then on each start we lookup the artifact and
the figure out which blobs to mount. There is no protaction against a
user removing the artifact while still being used in a container. When
the container is running the bind mounted files will stay there (as the
kernel keeps the mounts active even if the bind source was deleted).
On the next start it will fail to start as if it does not find the
artifact. The good thing is that this technically allows someone to
update the artifact with the new file by creating a new artifact with
the same name.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2025-03-12 19:42:14 +01:00

224 lines
7.0 KiB
Go

//go:build linux || freebsd
package integration
import (
"os"
"path/filepath"
. "github.com/containers/podman/v5/test/utils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Podman artifact mount", func() {
BeforeEach(func() {
SkipIfRemote("artifacts are not supported on the remote client yet due to being in development still")
})
It("podman artifact mount single blob", func() {
podmanTest.PodmanExitCleanly("artifact", "pull", ARTIFACT_SINGLE)
const artifactContent = "mRuO9ykak1Q2j"
tests := []struct {
name string
mountOpts string
containerFile string
}{
{
name: "single artifact mount",
mountOpts: "dst=/test",
containerFile: "/test/testfile",
},
{
name: "single artifact mount on existing file",
mountOpts: "dst=/etc/os-release",
containerFile: "/etc/os-release",
},
{
name: "single artifact mount with title",
mountOpts: "dst=/tmp,title=testfile",
containerFile: "/tmp/testfile",
},
{
name: "single artifact mount with digest",
mountOpts: "dst=/data,digest=sha256:e9510923578af3632946ecf5ae479c1b5f08b47464e707b5cbab9819272a9752",
containerFile: "/data/sha256-e9510923578af3632946ecf5ae479c1b5f08b47464e707b5cbab9819272a9752",
},
}
for _, tt := range tests {
By(tt.name)
// FIXME: we need https://github.com/containers/container-selinux/pull/360 to fix the selinux access problem, until then disable it.
session := podmanTest.PodmanExitCleanly("run", "--security-opt=label=disable", "--rm", "--mount", "type=artifact,src="+ARTIFACT_SINGLE+","+tt.mountOpts, ALPINE, "cat", tt.containerFile)
Expect(session.OutputToString()).To(Equal(artifactContent))
}
})
It("podman artifact mount multi blob", func() {
podmanTest.PodmanExitCleanly("artifact", "pull", ARTIFACT_MULTI)
podmanTest.PodmanExitCleanly("artifact", "pull", ARTIFACT_MULTI_NO_TITLE)
const (
artifactContent1 = "xuHWedtC0ADST"
artifactContent2 = "tAyZczFlgFsi4"
)
type expectedFiles struct {
file string
content string
}
tests := []struct {
name string
mountOpts string
containerFiles []expectedFiles
}{
{
name: "multi blob with title",
mountOpts: "src=" + ARTIFACT_MULTI + ",dst=/test",
containerFiles: []expectedFiles{
{
file: "/test/test1",
content: artifactContent1,
},
{
file: "/test/test2",
content: artifactContent2,
},
},
},
{
name: "multi blob without title",
mountOpts: "src=" + ARTIFACT_MULTI_NO_TITLE + ",dst=/test",
containerFiles: []expectedFiles{
{
file: "/test/sha256-8257bba28b9d19ac353c4b713b470860278857767935ef7e139afd596cb1bb2d",
content: artifactContent1,
},
{
file: "/test/sha256-63700c54129c6daaafe3a20850079f82d6d658d69de73d6158d81f920c6fbdd7",
content: artifactContent2,
},
},
},
{
name: "multi blob filter by title",
mountOpts: "src=" + ARTIFACT_MULTI + ",dst=/test,title=test2",
containerFiles: []expectedFiles{
{
file: "/test/test2",
content: artifactContent2,
},
},
},
{
name: "multi blob filter by digest",
mountOpts: "src=" + ARTIFACT_MULTI + ",dst=/test,digest=sha256:8257bba28b9d19ac353c4b713b470860278857767935ef7e139afd596cb1bb2d",
containerFiles: []expectedFiles{
{
file: "/test/sha256-8257bba28b9d19ac353c4b713b470860278857767935ef7e139afd596cb1bb2d",
content: artifactContent1,
},
},
},
}
for _, tt := range tests {
By(tt.name)
// FIXME: we need https://github.com/containers/container-selinux/pull/360 to fix the selinux access problem, until then disable it.
args := []string{"run", "--security-opt=label=disable", "--rm", "--mount", "type=artifact," + tt.mountOpts, ALPINE, "cat"}
for _, f := range tt.containerFiles {
args = append(args, f.file)
}
session := podmanTest.PodmanExitCleanly(args...)
outs := session.OutputToStringArray()
Expect(outs).To(HaveLen(len(tt.containerFiles)))
for i, f := range tt.containerFiles {
Expect(outs[i]).To(Equal(f.content))
}
}
})
It("podman artifact mount remove while in use", func() {
ctrName := "ctr1"
artifactName := "localhost/test"
artifactFileName := "somefile"
artifactFile := filepath.Join(podmanTest.TempDir, artifactFileName)
err := os.WriteFile(artifactFile, []byte("hello world\n"), 0o644)
Expect(err).ToNot(HaveOccurred())
podmanTest.PodmanExitCleanly("artifact", "add", artifactName, artifactFile)
// FIXME: we need https://github.com/containers/container-selinux/pull/360 to fix the selinux access problem, until then disable it.
podmanTest.PodmanExitCleanly("run", "--security-opt=label=disable", "--name", ctrName, "-d", "--mount", "type=artifact,src="+artifactName+",dst=/test", ALPINE, "sleep", "100")
podmanTest.PodmanExitCleanly("artifact", "rm", artifactName)
// file must sill be readable after artifact removal
session := podmanTest.PodmanExitCleanly("exec", ctrName, "cat", "/test/"+artifactFileName)
Expect(session.OutputToString()).To(Equal("hello world"))
// restart will fail if artifact does not exist
session = podmanTest.Podman([]string{"restart", "-t0", ctrName})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, artifactName+": artifact does not exist"))
// create a artifact with the same name again and add another file to ensure it picks up the changes
artifactFile2Name := "otherfile"
artifactFile2 := filepath.Join(podmanTest.TempDir, artifactFile2Name)
err = os.WriteFile(artifactFile2, []byte("second file"), 0o644)
Expect(err).ToNot(HaveOccurred())
podmanTest.PodmanExitCleanly("artifact", "add", artifactName, artifactFile, artifactFile2)
podmanTest.PodmanExitCleanly("start", ctrName)
session = podmanTest.PodmanExitCleanly("exec", ctrName, "cat", "/test/"+artifactFileName, "/test/"+artifactFile2Name)
Expect(session.OutputToString()).To(Equal("hello world second file"))
})
It("podman artifact mount dest conflict", func() {
tests := []struct {
name string
mount string
}{
{
name: "bind mount --volume",
mount: "--volume=/tmp:/test",
},
{
name: "overlay mount",
mount: "--volume=/tmp:/test:O",
},
{
name: "named volume",
mount: "--volume=abc:/test:O",
},
{
name: "bind mount --mount type=bind",
mount: "--mount=type=bind,src=/tmp,dst=/test",
},
{
name: "image mount",
mount: "--mount=type=bind,src=someimage,dst=/test",
},
{
name: "tmpfs mount",
mount: "--tmpfs=/test",
},
{
name: "artifact mount",
mount: "--mount=type=artifact,src=abc,dst=/test",
},
}
for _, tt := range tests {
By(tt.name)
session := podmanTest.Podman([]string{"run", "--rm", "--mount", "type=artifact,src=someartifact,dst=/test", tt.mount, ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError(125, "/test: duplicate mount destination"))
}
})
})