Merge pull request #25097 from mtrmac/PodmanOptions

Refactor Podman E2E helpers to allow passing/adding more options to the low-level executor
This commit is contained in:
openshift-merge-bot[bot]
2025-01-23 10:20:25 +00:00
committed by GitHub
12 changed files with 112 additions and 90 deletions

View File

@@ -1559,8 +1559,8 @@ var _ = Describe("Podman checkpoint", func() {
// Prevent --runtime arg from being set to force using default
// runtime unless explicitly set through passed args.
preservedMakeOptions := podmanTest.PodmanMakeOptions
podmanTest.PodmanMakeOptions = func(args []string, noEvents, noCache bool) []string {
defaultArgs := preservedMakeOptions(args, noEvents, noCache)
podmanTest.PodmanMakeOptions = func(args []string, options PodmanExecOptions) []string {
defaultArgs := preservedMakeOptions(args, options)
for i := range args {
// Runtime is set explicitly, so we should keep --runtime arg.
if args[i] == "--runtime" {

View File

@@ -441,7 +441,10 @@ func (p *PodmanTestIntegration) pullImage(image string, toCache bool) {
}()
}
for try := 0; try < 3; try++ {
podmanSession := p.PodmanBase([]string{"pull", image}, toCache, true)
podmanSession := p.PodmanExecBaseWithOptions([]string{"pull", image}, PodmanExecOptions{
NoEvents: toCache,
NoCache: true,
})
pull := PodmanSessionIntegration{podmanSession}
pull.Wait(440)
if pull.ExitCode() == 0 {
@@ -489,7 +492,14 @@ func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData {
// It returns the session (to allow consuming output if desired).
func (p *PodmanTestIntegration) PodmanExitCleanly(args ...string) *PodmanSessionIntegration {
GinkgoHelper()
session := p.Podman(args)
return p.PodmanExitCleanlyWithOptions(PodmanExecOptions{}, args...)
}
// PodmanExitCleanlyWithOptions runs a podman command with (optinos, args), and expects it to ExitCleanly within the default timeout.
// It returns the session (to allow consuming output if desired).
func (p *PodmanTestIntegration) PodmanExitCleanlyWithOptions(options PodmanExecOptions, args ...string) *PodmanSessionIntegration {
GinkgoHelper()
session := p.PodmanWithOptions(options, args...)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
return session
@@ -679,7 +689,7 @@ func (p *PodmanTestIntegration) BuildImageWithLabel(dockerfile, imageName string
// PodmanPID execs podman and returns its PID
func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) {
podmanOptions := p.MakeOptions(args, false, false)
podmanOptions := p.MakeOptions(args, PodmanExecOptions{})
GinkgoWriter.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " "))
command := exec.Command(p.PodmanBinary, podmanOptions...)
@@ -1060,7 +1070,12 @@ func SkipIfNetavark(p *PodmanTestIntegration) {
// PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment
func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, cwd string, env []string) *PodmanSessionIntegration {
podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false, nil, nil)
podmanSession := p.PodmanExecBaseWithOptions(args, PodmanExecOptions{
UID: uid,
GID: gid,
CWD: cwd,
Env: env,
})
return &PodmanSessionIntegration{podmanSession}
}
@@ -1119,7 +1134,9 @@ func rmAll(podmanBin string, path string) {
// PodmanNoCache calls the podman command with no configured imagecache
func (p *PodmanTestIntegration) PodmanNoCache(args []string) *PodmanSessionIntegration {
podmanSession := p.PodmanBase(args, false, true)
podmanSession := p.PodmanExecBaseWithOptions(args, PodmanExecOptions{
NoCache: true,
})
return &PodmanSessionIntegration{podmanSession}
}
@@ -1130,12 +1147,15 @@ func PodmanTestSetup(tempDir string) *PodmanTestIntegration {
// PodmanNoEvents calls the Podman command without an imagecache and without an
// events backend. It is used mostly for caching and uncaching images.
func (p *PodmanTestIntegration) PodmanNoEvents(args []string) *PodmanSessionIntegration {
podmanSession := p.PodmanBase(args, true, true)
podmanSession := p.PodmanExecBaseWithOptions(args, PodmanExecOptions{
NoEvents: true,
NoCache: true,
})
return &PodmanSessionIntegration{podmanSession}
}
// MakeOptions assembles all the podman main options
func (p *PodmanTestIntegration) makeOptions(args []string, noEvents, noCache bool) []string {
func (p *PodmanTestIntegration) makeOptions(args []string, options PodmanExecOptions) []string {
if p.RemoteTest {
if !slices.Contains(args, "--remote") {
return append([]string{"--remote", "--url", p.RemoteSocket}, args...)
@@ -1149,7 +1169,7 @@ func (p *PodmanTestIntegration) makeOptions(args []string, noEvents, noCache boo
}
eventsType := "file"
if noEvents {
if options.NoEvents {
eventsType = "none"
}
@@ -1157,7 +1177,7 @@ func (p *PodmanTestIntegration) makeOptions(args []string, noEvents, noCache boo
debug, p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.NetworkConfigDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, eventsType, p.DatabaseBackend), " ")
podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...)
if !noCache {
if !options.NoCache {
cacheOptions := []string{"--storage-opt",
fmt.Sprintf("%s.imagestore=%s", p.PodmanTest.ImageCacheFS, p.PodmanTest.ImageCacheDir)}
podmanOptions = append(cacheOptions, podmanOptions...)

View File

@@ -457,9 +457,9 @@ var _ = Describe("Podman exec", func() {
files := []*os.File{
devNull,
}
session := podmanTest.PodmanExtraFiles([]string{"exec", "--preserve-fds", "1", "test1", "ls"}, files)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
podmanTest.PodmanExitCleanlyWithOptions(PodmanExecOptions{
ExtraFiles: files,
}, "exec", "--preserve-fds", "1", "test1", "ls")
})
It("podman exec preserves --group-add groups", func() {

View File

@@ -13,6 +13,7 @@ import (
"syscall"
"time"
. "github.com/containers/podman/v5/test/utils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@@ -21,30 +22,15 @@ func IsRemote() bool {
return true
}
// Podman is the exec call to podman on the filesystem
// Podman executes podman on the filesystem with default options.
func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration {
args = p.makeOptions(args, false, false)
podmanSession := p.PodmanBase(args, false, false)
return &PodmanSessionIntegration{podmanSession}
return p.PodmanWithOptions(PodmanExecOptions{}, args...)
}
// PodmanSystemdScope runs the podman command in a new systemd scope
func (p *PodmanTestIntegration) PodmanSystemdScope(args []string) *PodmanSessionIntegration {
args = p.makeOptions(args, false, false)
wrapper := []string{"systemd-run", "--scope"}
if isRootless() {
wrapper = []string{"systemd-run", "--scope", "--user"}
}
podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, wrapper, nil)
return &PodmanSessionIntegration{podmanSession}
}
// PodmanExtraFiles is the exec call to podman on the filesystem and passes down extra files
func (p *PodmanTestIntegration) PodmanExtraFiles(args []string, extraFiles []*os.File) *PodmanSessionIntegration {
args = p.makeOptions(args, false, false)
podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, nil, extraFiles)
// PodmanWithOptions executes podman on the filesystem with the supplied options.
func (p *PodmanTestIntegration) PodmanWithOptions(options PodmanExecOptions, args ...string) *PodmanSessionIntegration {
args = p.makeOptions(args, options)
podmanSession := p.PodmanExecBaseWithOptions(args, options)
return &PodmanSessionIntegration{podmanSession}
}

View File

@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
. "github.com/containers/podman/v5/test/utils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@@ -14,25 +15,14 @@ func IsRemote() bool {
return false
}
// Podman is the exec call to podman on the filesystem
// Podman executes podman on the filesystem with default options.
func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration {
podmanSession := p.PodmanBase(args, false, false)
return &PodmanSessionIntegration{podmanSession}
return p.PodmanWithOptions(PodmanExecOptions{}, args...)
}
// PodmanSystemdScope runs the podman command in a new systemd scope
func (p *PodmanTestIntegration) PodmanSystemdScope(args []string) *PodmanSessionIntegration {
wrapper := []string{"systemd-run", "--scope"}
if isRootless() {
wrapper = []string{"systemd-run", "--scope", "--user"}
}
podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, wrapper, nil)
return &PodmanSessionIntegration{podmanSession}
}
// PodmanExtraFiles is the exec call to podman on the filesystem and passes down extra files
func (p *PodmanTestIntegration) PodmanExtraFiles(args []string, extraFiles []*os.File) *PodmanSessionIntegration {
podmanSession := p.PodmanAsUserBase(args, 0, 0, "", nil, false, false, nil, extraFiles)
// PodmanWithOptions executes podman on the filesystem with the supplied options.
func (p *PodmanTestIntegration) PodmanWithOptions(options PodmanExecOptions, args ...string) *PodmanSessionIntegration {
podmanSession := p.PodmanExecBaseWithOptions(args, options)
return &PodmanSessionIntegration{podmanSession}
}

View File

@@ -618,7 +618,7 @@ RUN touch /file
It("authenticated push", func() {
registryOptions := &podmanRegistry.Options{
PodmanPath: podmanTest.PodmanBinary,
PodmanArgs: podmanTest.MakeOptions(nil, false, false),
PodmanArgs: podmanTest.MakeOptions(nil, PodmanExecOptions{}),
Image: "docker-archive:" + imageTarPath(REGISTRY_IMAGE),
}

View File

@@ -36,7 +36,7 @@ var _ = Describe("Podman mount", func() {
// command: podman <options> unshare podman <options> mount cid
args := []string{"unshare", podmanTest.PodmanBinary}
opts := podmanTest.PodmanMakeOptions([]string{"mount", cid}, false, false)
opts := podmanTest.PodmanMakeOptions([]string{"mount", cid}, PodmanExecOptions{})
args = append(args, opts...)
// container root file system location is podmanTest.TempDir/...
@@ -59,7 +59,7 @@ var _ = Describe("Podman mount", func() {
// command: podman <options> unshare podman <options> image mount IMAGE
args := []string{"unshare", podmanTest.PodmanBinary}
opts := podmanTest.PodmanMakeOptions([]string{"image", "mount", CITEST_IMAGE}, false, false)
opts := podmanTest.PodmanMakeOptions([]string{"image", "mount", CITEST_IMAGE}, PodmanExecOptions{})
args = append(args, opts...)
// image location is podmanTest.TempDir/... because "--root podmanTest.TempDir/..."

View File

@@ -61,7 +61,7 @@ var _ = Describe("Podman run exit", func() {
// command: podman <options> unshare podman <options> image mount ALPINE
args := []string{"unshare", podmanTest.PodmanBinary}
opts := podmanTest.PodmanMakeOptions([]string{"mount", "--no-trunc"}, false, false)
opts := podmanTest.PodmanMakeOptions([]string{"mount", "--no-trunc"}, PodmanExecOptions{})
args = append(args, opts...)
pmount := podmanTest.Podman(args)

View File

@@ -1704,7 +1704,13 @@ VOLUME %s`, ALPINE, volPath, volPath)
}
}
container := podmanTest.PodmanSystemdScope([]string{"run", "--rm", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup"})
scopeOptions := PodmanExecOptions{
Wrapper: []string{"systemd-run", "--scope"},
}
if isRootless() {
scopeOptions.Wrapper = append(scopeOptions.Wrapper, "--user")
}
container := podmanTest.PodmanWithOptions(scopeOptions, "run", "--rm", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup")
container.WaitWithDefaultTimeout()
Expect(container).Should(Exit(0))
checkLines(container.OutputToStringArray())
@@ -1713,7 +1719,7 @@ VOLUME %s`, ALPINE, volPath, volPath)
ContainSubstring("Running as unit: "))) // systemd >= 255
// check that --cgroups=split is honored also when a container runs in a pod
container = podmanTest.PodmanSystemdScope([]string{"run", "--rm", "--pod", "new:split-test-pod", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup"})
container = podmanTest.PodmanWithOptions(scopeOptions, "run", "--rm", "--pod", "new:split-test-pod", "--cgroups=split", ALPINE, "cat", "/proc/self/cgroup")
container.WaitWithDefaultTimeout()
Expect(container).Should(Exit(0))
checkLines(container.OutputToStringArray())
@@ -1849,7 +1855,9 @@ VOLUME %s`, ALPINE, volPath, volPath)
files := []*os.File{
devNull,
}
session := podmanTest.PodmanExtraFiles([]string{"run", "--preserve-fds", "1", ALPINE, "ls"}, files)
session := podmanTest.PodmanWithOptions(PodmanExecOptions{
ExtraFiles: files,
}, "run", "--preserve-fds", "1", ALPINE, "ls")
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})

View File

@@ -49,7 +49,7 @@ var _ = Describe("Systemd activate", func() {
Expect(err).ToNot(HaveOccurred())
addr := net.JoinHostPort(host, strconv.Itoa(port))
podmanOptions := podmanTest.makeOptions(nil, false, false)
podmanOptions := podmanTest.makeOptions(nil, testUtils.PodmanExecOptions{})
systemdArgs := []string{
"-E", "http_proxy", "-E", "https_proxy", "-E", "no_proxy",

View File

@@ -55,7 +55,7 @@ var (
// PodmanTestCommon contains common functions will be updated later in
// the inheritance structs
type PodmanTestCommon interface {
MakeOptions(args []string, noEvents, noCache bool) []string
MakeOptions(args []string, options PodmanExecOptions) []string
WaitForContainer() bool
WaitContainerReady(id string, expStr string, timeout int, step int) bool
}
@@ -67,7 +67,7 @@ type PodmanTest struct {
NetworkBackend NetworkBackend
DatabaseBackend string
PodmanBinary string
PodmanMakeOptions func(args []string, noEvents, noCache bool) []string
PodmanMakeOptions func(args []string, options PodmanExecOptions) []string
RemoteCommand *exec.Cmd
RemotePodmanBinary string
RemoteSession *os.Process
@@ -90,20 +90,32 @@ type HostOS struct {
}
// MakeOptions assembles all podman options
func (p *PodmanTest) MakeOptions(args []string, noEvents, noCache bool) []string {
return p.PodmanMakeOptions(args, noEvents, noCache)
func (p *PodmanTest) MakeOptions(args []string, options PodmanExecOptions) []string {
return p.PodmanMakeOptions(args, options)
}
// PodmanAsUserBase exec podman as user. uid and gid is set for credentials usage. env is used
// to record the env for debugging
func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, noEvents, noCache bool, wrapper []string, extraFiles []*os.File) *PodmanSession {
// PodmanExecOptions modify behavior of PodmanTest.PodmanExecBaseWithOptions and its callers.
// Users should typically leave most fields default-initialized, and only set those that are relevant to them.
type PodmanExecOptions struct {
UID, GID uint32 // default: inherited form the current process
CWD string // default: inherited form the current process
Env []string // default: inherited form the current process
NoEvents bool
NoCache bool
Wrapper []string // A command to run, receiving the Podman command line. default: none
ExtraFiles []*os.File
}
// PodmanExecBaseWithOptions execs podman with the specified args, and in an environment defined by options
func (p *PodmanTest) PodmanExecBaseWithOptions(args []string, options PodmanExecOptions) *PodmanSession {
var command *exec.Cmd
podmanOptions := p.MakeOptions(args, noEvents, noCache)
podmanOptions := p.MakeOptions(args, options)
podmanBinary := p.PodmanBinary
if p.RemoteTest {
podmanBinary = p.RemotePodmanBinary
}
runCmd := options.Wrapper
if timeDir := os.Getenv(EnvTimeDir); timeDir != "" {
timeFile, err := os.CreateTemp(timeDir, ".time")
if err != nil {
@@ -111,18 +123,17 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string
}
timeArgs := []string{"-f", "%M", "-o", timeFile.Name()}
timeCmd := append([]string{"/usr/bin/time"}, timeArgs...)
wrapper = append(timeCmd, wrapper...)
runCmd = append(timeCmd, runCmd...)
}
runCmd := wrapper
runCmd = append(runCmd, podmanBinary)
if env == nil {
if options.Env == nil {
GinkgoWriter.Printf("Running: %s %s\n", strings.Join(runCmd, " "), strings.Join(podmanOptions, " "))
} else {
GinkgoWriter.Printf("Running: (env: %v) %s %s\n", env, strings.Join(runCmd, " "), strings.Join(podmanOptions, " "))
GinkgoWriter.Printf("Running: (env: %v) %s %s\n", options.Env, strings.Join(runCmd, " "), strings.Join(podmanOptions, " "))
}
if uid != 0 || gid != 0 {
pythonCmd := fmt.Sprintf("import os; import sys; uid = %d; gid = %d; cwd = '%s'; os.setgid(gid); os.setuid(uid); os.chdir(cwd) if len(cwd)>0 else True; os.execv(sys.argv[1], sys.argv[1:])", gid, uid, cwd)
if options.UID != 0 || options.GID != 0 {
pythonCmd := fmt.Sprintf("import os; import sys; uid = %d; gid = %d; cwd = '%s'; os.setgid(gid); os.setuid(uid); os.chdir(cwd) if len(cwd)>0 else True; os.execv(sys.argv[1], sys.argv[1:])", options.GID, options.UID, options.CWD)
runCmd = append(runCmd, podmanOptions...)
nsEnterOpts := append([]string{"-c", pythonCmd}, runCmd...)
command = exec.Command("python", nsEnterOpts...)
@@ -130,14 +141,14 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string
runCmd = append(runCmd, podmanOptions...)
command = exec.Command(runCmd[0], runCmd[1:]...)
}
if env != nil {
command.Env = env
if options.Env != nil {
command.Env = options.Env
}
if cwd != "" {
command.Dir = cwd
if options.CWD != "" {
command.Dir = options.CWD
}
command.ExtraFiles = extraFiles
command.ExtraFiles = options.ExtraFiles
session, err := Start(command, GinkgoWriter, GinkgoWriter)
if err != nil {
@@ -146,11 +157,6 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string
return &PodmanSession{session}
}
// PodmanBase exec podman with default env.
func (p *PodmanTest) PodmanBase(args []string, noEvents, noCache bool) *PodmanSession {
return p.PodmanAsUserBase(args, 0, 0, "", nil, noEvents, noCache, nil, nil)
}
// WaitForContainer waits on a started container
func (p *PodmanTest) WaitForContainer() bool {
for i := 0; i < 10; i++ {
@@ -167,7 +173,9 @@ func (p *PodmanTest) WaitForContainer() bool {
// containers are currently running.
func (p *PodmanTest) NumberOfContainersRunning() int {
var containers []string
ps := p.PodmanBase([]string{"ps", "-q"}, false, true)
ps := p.PodmanExecBaseWithOptions([]string{"ps", "-q"}, PodmanExecOptions{
NoCache: true,
})
ps.WaitWithDefaultTimeout()
Expect(ps).Should(Exit(0))
for _, i := range ps.OutputToStringArray() {
@@ -182,7 +190,9 @@ func (p *PodmanTest) NumberOfContainersRunning() int {
// containers are currently defined.
func (p *PodmanTest) NumberOfContainers() int {
var containers []string
ps := p.PodmanBase([]string{"ps", "-aq"}, false, true)
ps := p.PodmanExecBaseWithOptions([]string{"ps", "-aq"}, PodmanExecOptions{
NoCache: true,
})
ps.WaitWithDefaultTimeout()
Expect(ps.ExitCode()).To(Equal(0))
for _, i := range ps.OutputToStringArray() {
@@ -197,7 +207,9 @@ func (p *PodmanTest) NumberOfContainers() int {
// pods are currently defined.
func (p *PodmanTest) NumberOfPods() int {
var pods []string
ps := p.PodmanBase([]string{"pod", "ps", "-q"}, false, true)
ps := p.PodmanExecBaseWithOptions([]string{"pod", "ps", "-q"}, PodmanExecOptions{
NoCache: true,
})
ps.WaitWithDefaultTimeout()
Expect(ps.ExitCode()).To(Equal(0))
for _, i := range ps.OutputToStringArray() {
@@ -213,7 +225,9 @@ func (p *PodmanTest) NumberOfPods() int {
func (p *PodmanTest) GetContainerStatus() string {
var podmanArgs = []string{"ps"}
podmanArgs = append(podmanArgs, "--all", "--format={{.Status}}")
session := p.PodmanBase(podmanArgs, false, true)
session := p.PodmanExecBaseWithOptions(podmanArgs, PodmanExecOptions{
NoCache: true,
})
session.WaitWithDefaultTimeout()
return session.OutputToString()
}
@@ -221,7 +235,9 @@ func (p *PodmanTest) GetContainerStatus() string {
// WaitContainerReady waits process or service inside container start, and ready to be used.
func (p *PodmanTest) WaitContainerReady(id string, expStr string, timeout int, step int) bool {
startTime := time.Now()
s := p.PodmanBase([]string{"logs", id}, false, true)
s := p.PodmanExecBaseWithOptions([]string{"logs", id}, PodmanExecOptions{
NoCache: true,
})
s.WaitWithDefaultTimeout()
for {
@@ -234,7 +250,9 @@ func (p *PodmanTest) WaitContainerReady(id string, expStr string, timeout int, s
return false
}
time.Sleep(time.Duration(step) * time.Second)
s = p.PodmanBase([]string{"logs", id}, false, true)
s = p.PodmanExecBaseWithOptions([]string{"logs", id}, PodmanExecOptions{
NoCache: true,
})
s.WaitWithDefaultTimeout()
}
}

View File

@@ -31,7 +31,7 @@ func FakePodmanTestCreate() *FakePodmanTest {
return p
}
func (p *FakePodmanTest) makeOptions(args []string, noEvents, noCache bool) []string {
func (p *FakePodmanTest) makeOptions(args []string, options PodmanExecOptions) []string {
return FakeOutputs[strings.Join(args, " ")]
}