github.com/containers/podman/v5@v5.1.0-rc1/test/e2e/common_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"math/rand"
    10  	"net"
    11  	"net/url"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"slices"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"syscall"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/containers/common/pkg/cgroups"
    25  	"github.com/containers/podman/v5/libpod/define"
    26  	"github.com/containers/podman/v5/pkg/inspect"
    27  	. "github.com/containers/podman/v5/test/utils"
    28  	"github.com/containers/storage/pkg/lockfile"
    29  	"github.com/containers/storage/pkg/reexec"
    30  	"github.com/containers/storage/pkg/stringid"
    31  	jsoniter "github.com/json-iterator/go"
    32  	. "github.com/onsi/ginkgo/v2"
    33  	. "github.com/onsi/gomega"
    34  	. "github.com/onsi/gomega/gexec"
    35  	"github.com/sirupsen/logrus"
    36  	"golang.org/x/sys/unix"
    37  )
    38  
    39  var (
    40  	//lint:ignore ST1003
    41  	PODMAN_BINARY      string                              //nolint:revive,stylecheck
    42  	INTEGRATION_ROOT   string                              //nolint:revive,stylecheck
    43  	CGROUP_MANAGER     = "systemd"                         //nolint:revive,stylecheck
    44  	RESTORE_IMAGES     = []string{ALPINE, BB, NGINX_IMAGE} //nolint:revive,stylecheck
    45  	defaultWaitTimeout = 90
    46  	CGROUPSV2, _       = cgroups.IsCgroup2UnifiedMode()
    47  )
    48  
    49  // PodmanTestIntegration struct for command line options
    50  type PodmanTestIntegration struct {
    51  	PodmanTest
    52  	ConmonBinary        string
    53  	QuadletBinary       string
    54  	Root                string
    55  	NetworkConfigDir    string
    56  	OCIRuntime          string
    57  	RunRoot             string
    58  	StorageOptions      string
    59  	SignaturePolicyPath string
    60  	CgroupManager       string
    61  	Host                HostOS
    62  	TmpDir              string
    63  }
    64  
    65  var GlobalTmpDir string // Single top-level tmpdir for all tests
    66  var LockTmpDir string
    67  
    68  // PodmanSessionIntegration struct for command line session
    69  type PodmanSessionIntegration struct {
    70  	*PodmanSession
    71  }
    72  
    73  type testResult struct {
    74  	name   string
    75  	length float64
    76  }
    77  
    78  type testResultsSorted []testResult
    79  
    80  func (a testResultsSorted) Len() int      { return len(a) }
    81  func (a testResultsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    82  
    83  type testResultsSortedLength struct{ testResultsSorted }
    84  
    85  func (a testResultsSorted) Less(i, j int) bool { return a[i].length < a[j].length }
    86  
    87  func TestMain(m *testing.M) {
    88  	if reexec.Init() {
    89  		return
    90  	}
    91  	os.Exit(m.Run())
    92  }
    93  
    94  // TestLibpod ginkgo master function
    95  func TestLibpod(t *testing.T) {
    96  	if os.Getenv("NOCACHE") == "1" {
    97  		CACHE_IMAGES = []string{}
    98  		RESTORE_IMAGES = []string{}
    99  	}
   100  	RegisterFailHandler(Fail)
   101  	RunSpecs(t, "Libpod Suite")
   102  }
   103  
   104  var (
   105  	tempdir      string // Working dir for _one_ subtest
   106  	err          error
   107  	podmanTest   *PodmanTestIntegration
   108  	safeIPOctets [2]uint8
   109  	timingsFile  *os.File
   110  
   111  	_ = BeforeEach(func() {
   112  		tempdir, err = os.MkdirTemp(GlobalTmpDir, "subtest-")
   113  		Expect(err).ToNot(HaveOccurred())
   114  		podmanTest = PodmanTestCreate(tempdir)
   115  		podmanTest.Setup()
   116  		// see GetSafeIPAddress() below
   117  		safeIPOctets[0] = uint8(GinkgoT().ParallelProcess()) + 128
   118  		safeIPOctets[1] = 2
   119  	})
   120  
   121  	_ = AfterEach(func() {
   122  		// First unset CONTAINERS_CONF before doing Cleanup() to prevent
   123  		// invalid containers.conf files to fail the cleanup.
   124  		os.Unsetenv("CONTAINERS_CONF")
   125  		os.Unsetenv("CONTAINERS_CONF_OVERRIDE")
   126  		os.Unsetenv("PODMAN_CONNECTIONS_CONF")
   127  		podmanTest.Cleanup()
   128  		f := CurrentSpecReport()
   129  		processTestResult(f)
   130  	})
   131  )
   132  
   133  const (
   134  	// lockdir - do not use directly; use LockTmpDir
   135  	lockdir = "libpodlock"
   136  	// imageCacheDir - do not use directly use ImageCacheDir
   137  	imageCacheDir = "imagecachedir"
   138  )
   139  
   140  var _ = SynchronizedBeforeSuite(func() []byte {
   141  	globalTmpDir, err := os.MkdirTemp("", "podman-e2e-")
   142  	Expect(err).ToNot(HaveOccurred())
   143  
   144  	// make cache dir
   145  	ImageCacheDir = filepath.Join(globalTmpDir, imageCacheDir)
   146  	err = os.MkdirAll(ImageCacheDir, 0700)
   147  	Expect(err).ToNot(HaveOccurred())
   148  
   149  	// Cache images
   150  	cwd, _ := os.Getwd()
   151  	INTEGRATION_ROOT = filepath.Join(cwd, "../../")
   152  	podman := PodmanTestSetup(filepath.Join(globalTmpDir, "image-init"))
   153  
   154  	// Pull cirros but don't put it into the cache
   155  	pullImages := []string{CIRROS_IMAGE, volumeTest}
   156  	pullImages = append(pullImages, CACHE_IMAGES...)
   157  	for _, image := range pullImages {
   158  		podman.createArtifact(image)
   159  	}
   160  
   161  	if err := os.MkdirAll(filepath.Join(ImageCacheDir, podman.ImageCacheFS+"-images"), 0777); err != nil {
   162  		GinkgoWriter.Printf("%q\n", err)
   163  		os.Exit(1)
   164  	}
   165  	podman.Root = ImageCacheDir
   166  	// If running localized tests, the cache dir is created and populated. if the
   167  	// tests are remote, this is a no-op
   168  	populateCache(podman)
   169  
   170  	if err := os.MkdirAll(filepath.Join(globalTmpDir, lockdir), 0700); err != nil {
   171  		GinkgoWriter.Printf("%q\n", err)
   172  		os.Exit(1)
   173  	}
   174  
   175  	// If running remote, we need to stop the associated podman system service
   176  	if podman.RemoteTest {
   177  		podman.StopRemoteService()
   178  	}
   179  
   180  	// remove temporary podman files; images are now cached in ImageCacheDir
   181  	rmAll(podman.PodmanBinary, podman.TempDir)
   182  
   183  	return []byte(globalTmpDir)
   184  }, func(data []byte) {
   185  	cwd, _ := os.Getwd()
   186  	INTEGRATION_ROOT = filepath.Join(cwd, "../../")
   187  	GlobalTmpDir = string(data)
   188  	ImageCacheDir = filepath.Join(GlobalTmpDir, imageCacheDir)
   189  	LockTmpDir = filepath.Join(GlobalTmpDir, lockdir)
   190  
   191  	timingsFile, err = os.Create(fmt.Sprintf("%s/timings-%d", LockTmpDir, GinkgoParallelProcess()))
   192  	Expect(err).ToNot(HaveOccurred())
   193  })
   194  
   195  func (p *PodmanTestIntegration) Setup() {
   196  	cwd, _ := os.Getwd()
   197  	INTEGRATION_ROOT = filepath.Join(cwd, "../../")
   198  }
   199  
   200  var _ = SynchronizedAfterSuite(func() {
   201  	timingsFile.Close()
   202  	timingsFile = nil
   203  },
   204  	func() {
   205  		testTimings := make(testResultsSorted, 0, 2000)
   206  		for i := 1; i <= GinkgoT().ParallelTotal(); i++ {
   207  			f, err := os.Open(fmt.Sprintf("%s/timings-%d", LockTmpDir, i))
   208  			Expect(err).ToNot(HaveOccurred())
   209  			defer f.Close()
   210  			scanner := bufio.NewScanner(f)
   211  			for scanner.Scan() {
   212  				text := scanner.Text()
   213  				name, durationString, ok := strings.Cut(text, "\t\t")
   214  				if !ok {
   215  					Fail(fmt.Sprintf("incorrect timing line: %q", text))
   216  				}
   217  				duration, err := strconv.ParseFloat(durationString, 64)
   218  				Expect(err).ToNot(HaveOccurred(), "failed to parse float from timings file")
   219  				testTimings = append(testTimings, testResult{name: name, length: duration})
   220  			}
   221  			if err := scanner.Err(); err != nil {
   222  				Expect(err).ToNot(HaveOccurred(), "read timings %d", i)
   223  			}
   224  		}
   225  		sort.Sort(testResultsSortedLength{testTimings})
   226  		GinkgoWriter.Println("integration timing results")
   227  		for _, result := range testTimings {
   228  			GinkgoWriter.Printf("%s\t\t%f\n", result.name, result.length)
   229  		}
   230  
   231  		cwd, _ := os.Getwd()
   232  		rmAll(getPodmanBinary(cwd), GlobalTmpDir)
   233  	})
   234  
   235  func getPodmanBinary(cwd string) string {
   236  	podmanBinary := filepath.Join(cwd, "../../bin/podman")
   237  	if os.Getenv("PODMAN_BINARY") != "" {
   238  		podmanBinary = os.Getenv("PODMAN_BINARY")
   239  	}
   240  	return podmanBinary
   241  }
   242  
   243  // PodmanTestCreate creates a PodmanTestIntegration instance for the tests
   244  func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration {
   245  	host := GetHostDistributionInfo()
   246  	cwd, _ := os.Getwd()
   247  
   248  	root := filepath.Join(tempDir, "root")
   249  	podmanBinary := getPodmanBinary(cwd)
   250  
   251  	podmanRemoteBinary := os.Getenv("PODMAN_REMOTE_BINARY")
   252  	if podmanRemoteBinary == "" {
   253  		podmanRemoteBinary = filepath.Join(cwd, "../../bin/podman-remote")
   254  	}
   255  
   256  	quadletBinary := os.Getenv("QUADLET_BINARY")
   257  	if quadletBinary == "" {
   258  		quadletBinary = filepath.Join(cwd, "../../bin/quadlet")
   259  	}
   260  
   261  	conmonBinary := os.Getenv("CONMON_BINARY")
   262  	if conmonBinary == "" {
   263  		conmonBinary = "/usr/libexec/podman/conmon"
   264  		if _, err := os.Stat(conmonBinary); errors.Is(err, os.ErrNotExist) {
   265  			conmonBinary = "/usr/bin/conmon"
   266  		}
   267  	}
   268  
   269  	storageOptions := os.Getenv("STORAGE_OPTIONS")
   270  	if storageOptions == "" {
   271  		storageOptions = STORAGE_OPTIONS
   272  	}
   273  
   274  	cgroupManager := os.Getenv("CGROUP_MANAGER")
   275  	if cgroupManager == "" {
   276  		cgroupManager = CGROUP_MANAGER
   277  	}
   278  
   279  	ociRuntime := os.Getenv("OCI_RUNTIME")
   280  	if ociRuntime == "" {
   281  		ociRuntime = "crun"
   282  	}
   283  	os.Setenv("DISABLE_HC_SYSTEMD", "true")
   284  
   285  	dbBackend := "sqlite"
   286  	if os.Getenv("PODMAN_DB") == "boltdb" {
   287  		dbBackend = "boltdb"
   288  	}
   289  
   290  	networkBackend := Netavark
   291  	networkConfigDir := "/etc/containers/networks"
   292  	if isRootless() {
   293  		networkConfigDir = filepath.Join(root, "etc", "networks")
   294  	}
   295  
   296  	if strings.ToLower(os.Getenv("NETWORK_BACKEND")) == "cni" {
   297  		networkBackend = CNI
   298  		networkConfigDir = "/etc/cni/net.d"
   299  		if isRootless() {
   300  			networkConfigDir = filepath.Join(os.Getenv("HOME"), ".config/cni/net.d")
   301  		}
   302  	}
   303  
   304  	if err := os.MkdirAll(root, 0755); err != nil {
   305  		panic(err)
   306  	}
   307  
   308  	if err := os.MkdirAll(networkConfigDir, 0755); err != nil {
   309  		panic(err)
   310  	}
   311  
   312  	storageFs := STORAGE_FS
   313  	if isRootless() {
   314  		storageFs = ROOTLESS_STORAGE_FS
   315  	}
   316  	if os.Getenv("STORAGE_FS") != "" {
   317  		storageFs = os.Getenv("STORAGE_FS")
   318  		storageOptions = "--storage-driver " + storageFs
   319  	}
   320  
   321  	p := &PodmanTestIntegration{
   322  		PodmanTest: PodmanTest{
   323  			PodmanBinary:       podmanBinary,
   324  			RemotePodmanBinary: podmanRemoteBinary,
   325  			TempDir:            tempDir,
   326  			RemoteTest:         remote,
   327  			ImageCacheFS:       storageFs,
   328  			ImageCacheDir:      ImageCacheDir,
   329  			NetworkBackend:     networkBackend,
   330  			DatabaseBackend:    dbBackend,
   331  		},
   332  		ConmonBinary:        conmonBinary,
   333  		QuadletBinary:       quadletBinary,
   334  		Root:                root,
   335  		TmpDir:              tempDir,
   336  		NetworkConfigDir:    networkConfigDir,
   337  		OCIRuntime:          ociRuntime,
   338  		RunRoot:             filepath.Join(tempDir, "runroot"),
   339  		StorageOptions:      storageOptions,
   340  		SignaturePolicyPath: filepath.Join(INTEGRATION_ROOT, "test/policy.json"),
   341  		CgroupManager:       cgroupManager,
   342  		Host:                host,
   343  	}
   344  
   345  	if remote {
   346  		var pathPrefix string
   347  		if !isRootless() {
   348  			pathPrefix = "/run/podman/podman"
   349  		} else {
   350  			runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
   351  			pathPrefix = filepath.Join(runtimeDir, "podman")
   352  		}
   353  		// We want to avoid collisions in socket paths, but using the
   354  		// socket directly for a collision check doesn’t work; bind(2) on AF_UNIX
   355  		// creates the file, and we need to pass a unique path now before the bind(2)
   356  		// happens. So, use a podman-%s.sock-lock empty file as a marker.
   357  		tries := 0
   358  		for {
   359  			uuid := stringid.GenerateRandomID()
   360  			lockPath := fmt.Sprintf("%s-%s.sock-lock", pathPrefix, uuid)
   361  			lockFile, err := os.OpenFile(lockPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0700)
   362  			if err == nil {
   363  				lockFile.Close()
   364  				p.RemoteSocketLock = lockPath
   365  				p.RemoteSocket = fmt.Sprintf("unix://%s-%s.sock", pathPrefix, uuid)
   366  				break
   367  			}
   368  			tries++
   369  			if tries >= 1000 {
   370  				panic("Too many RemoteSocket collisions")
   371  			}
   372  		}
   373  	}
   374  
   375  	// Set up registries.conf ENV variable
   376  	p.setDefaultRegistriesConfigEnv()
   377  	// Rewrite the PodmanAsUser function
   378  	p.PodmanMakeOptions = p.makeOptions
   379  	return p
   380  }
   381  
   382  func (p PodmanTestIntegration) AddImageToRWStore(image string) {
   383  	if err := p.RestoreArtifact(image); err != nil {
   384  		logrus.Errorf("Unable to restore %s to RW store", image)
   385  	}
   386  }
   387  
   388  func imageTarPath(image string) string {
   389  	cacheDir := os.Getenv("PODMAN_TEST_IMAGE_CACHE_DIR")
   390  	if cacheDir == "" {
   391  		// Avoid /tmp: it may be tmpfs, and these images are large
   392  		cacheDir = "/var/tmp"
   393  	}
   394  
   395  	// e.g., registry.com/fubar:latest -> registry.com-fubar-latest.tar
   396  	imageCacheName := strings.ReplaceAll(strings.ReplaceAll(image, ":", "-"), "/", "-") + ".tar"
   397  
   398  	return filepath.Join(cacheDir, imageCacheName)
   399  }
   400  
   401  // createArtifact creates a cached image tarball in a local directory
   402  func (p *PodmanTestIntegration) createArtifact(image string) {
   403  	if os.Getenv("NO_TEST_CACHE") != "" {
   404  		return
   405  	}
   406  	destName := imageTarPath(image)
   407  	if _, err := os.Stat(destName); os.IsNotExist(err) {
   408  		GinkgoWriter.Printf("Caching %s at %s...\n", image, destName)
   409  		for try := 0; try < 3; try++ {
   410  			pull := p.PodmanNoCache([]string{"pull", image})
   411  			pull.Wait(440)
   412  			if pull.ExitCode() == 0 {
   413  				break
   414  			}
   415  			if try == 2 {
   416  				Expect(pull).Should(Exit(0), "Failed after many retries")
   417  			}
   418  
   419  			GinkgoWriter.Println("Will wait and retry")
   420  			time.Sleep(time.Duration(try+1) * 5 * time.Second)
   421  		}
   422  
   423  		save := p.PodmanNoCache([]string{"save", "-o", destName, image})
   424  		save.Wait(90)
   425  		Expect(save).Should(Exit(0))
   426  		GinkgoWriter.Printf("\n")
   427  	} else {
   428  		GinkgoWriter.Printf("[image already cached: %s]\n", destName)
   429  	}
   430  }
   431  
   432  // InspectImageJSON takes the session output of an inspect
   433  // image and returns json
   434  func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData {
   435  	var i []inspect.ImageData
   436  	err := jsoniter.Unmarshal(s.Out.Contents(), &i)
   437  	Expect(err).ToNot(HaveOccurred())
   438  	return i
   439  }
   440  
   441  // InspectContainer returns a container's inspect data in JSON format
   442  func (p *PodmanTestIntegration) InspectContainer(name string) []define.InspectContainerData {
   443  	cmd := []string{"inspect", name}
   444  	session := p.Podman(cmd)
   445  	session.WaitWithDefaultTimeout()
   446  	Expect(session).Should(Exit(0))
   447  	return session.InspectContainerToJSON()
   448  }
   449  
   450  // Pull a single field from a container using `podman inspect --format {{ field }}`,
   451  // and verify it against the given expected value.
   452  func (p *PodmanTestIntegration) CheckContainerSingleField(name, field, expected string) {
   453  	inspect := p.Podman([]string{"inspect", "--format", fmt.Sprintf("{{ %s }}", field), name})
   454  	inspect.WaitWithDefaultTimeout()
   455  	ExpectWithOffset(1, inspect).Should(Exit(0))
   456  	ExpectWithOffset(1, inspect.OutputToString()).To(Equal(expected))
   457  }
   458  
   459  // Check that the contents of a single file in the given container matches the expected value.
   460  func (p *PodmanTestIntegration) CheckFileInContainer(name, filepath, expected string) {
   461  	exec := p.Podman([]string{"exec", name, "cat", filepath})
   462  	exec.WaitWithDefaultTimeout()
   463  	ExpectWithOffset(1, exec).Should(Exit(0))
   464  	ExpectWithOffset(1, exec.OutputToString()).To(Equal(expected))
   465  }
   466  
   467  // Check that the contents of a single file in the given container containers the given value.
   468  func (p *PodmanTestIntegration) CheckFileInContainerSubstring(name, filepath, expected string) {
   469  	exec := p.Podman([]string{"exec", name, "cat", filepath})
   470  	exec.WaitWithDefaultTimeout()
   471  	ExpectWithOffset(1, exec).Should(Exit(0))
   472  	ExpectWithOffset(1, exec.OutputToString()).To(ContainSubstring(expected))
   473  }
   474  
   475  // StopContainer stops a container with no timeout, ensuring a fast test.
   476  func (p *PodmanTestIntegration) StopContainer(nameOrID string) {
   477  	stop := p.Podman([]string{"stop", "-t0", nameOrID})
   478  	stop.WaitWithDefaultTimeout()
   479  	Expect(stop).Should(ExitCleanly())
   480  }
   481  
   482  func (p *PodmanTestIntegration) StopPod(nameOrID string) {
   483  	stop := p.Podman([]string{"pod", "stop", "-t0", nameOrID})
   484  	stop.WaitWithDefaultTimeout()
   485  	Expect(stop).Should(ExitCleanly())
   486  }
   487  
   488  func processTestResult(r SpecReport) {
   489  	tr := testResult{length: r.RunTime.Seconds(), name: r.FullText()}
   490  	_, err := timingsFile.WriteString(fmt.Sprintf("%s\t\t%f\n", tr.name, tr.length))
   491  	Expect(err).ToNot(HaveOccurred(), "write timings")
   492  }
   493  
   494  func GetPortLock(port string) *lockfile.LockFile {
   495  	lockFile := filepath.Join(LockTmpDir, port)
   496  	lock, err := lockfile.GetLockFile(lockFile)
   497  	if err != nil {
   498  		GinkgoWriter.Println(err)
   499  		os.Exit(1)
   500  	}
   501  	lock.Lock()
   502  	return lock
   503  }
   504  
   505  // GetSafeIPAddress returns a sequentially allocated IP address that _should_
   506  // be safe and unique across parallel tasks
   507  //
   508  // Used by tests which want to use "--ip SOMETHING-SAFE". Picking at random
   509  // just doesn't work: we get occasional collisions. Our current approach
   510  // allocates a /24 subnet for each ginkgo process, starting at .128.x, see
   511  // BeforeEach() above. Unfortunately, CNI remembers each address assigned
   512  // and assigns <previous+1> by default -- so other parallel jobs may
   513  // get IPs in our block. The +10 leaves a gap for that. (Netavark works
   514  // differently, allocating sequentially from .0.0, hence our .128.x).
   515  // This heuristic will fail if run in parallel on >127 processors or if
   516  // one test calls us more than 25 times or if some other test runs more
   517  // than ten networked containers at the same time as any test that
   518  // relies on GetSafeIPAddress(). I'm finding it hard to care.
   519  //
   520  // DO NOT USE THIS FUNCTION unless there is no possible alternative. In
   521  // most cases you should use 'podman network create' + 'podman run --network'.
   522  func GetSafeIPAddress() string {
   523  	safeIPOctets[1] += 10
   524  	return fmt.Sprintf("10.88.%d.%d", safeIPOctets[0], safeIPOctets[1])
   525  }
   526  
   527  // RunTopContainer runs a simple container in the background that
   528  // runs top.  If the name passed != "", it will have a name
   529  func (p *PodmanTestIntegration) RunTopContainer(name string) *PodmanSessionIntegration {
   530  	return p.RunTopContainerWithArgs(name, nil)
   531  }
   532  
   533  // RunTopContainerWithArgs runs a simple container in the background that
   534  // runs top.  If the name passed != "", it will have a name, command args can also be passed in
   535  func (p *PodmanTestIntegration) RunTopContainerWithArgs(name string, args []string) *PodmanSessionIntegration {
   536  	// In proxy environment, some tests need to the --http-proxy=false option (#16684)
   537  	var podmanArgs = []string{"run", "--http-proxy=false"}
   538  	if name != "" {
   539  		podmanArgs = append(podmanArgs, "--name", name)
   540  	}
   541  	podmanArgs = append(podmanArgs, args...)
   542  	podmanArgs = append(podmanArgs, "-d", ALPINE, "top", "-b")
   543  	session := p.Podman(podmanArgs)
   544  	session.WaitWithDefaultTimeout()
   545  	Expect(session).To(ExitCleanly())
   546  	cid := session.OutputToString()
   547  	// Output indicates that top is running, which means it's safe
   548  	// for our caller to invoke `podman stop`
   549  	if !WaitContainerReady(p, cid, "Mem:", 20, 1) {
   550  		Fail("Could not start a top container")
   551  	}
   552  	return session
   553  }
   554  
   555  // RunLsContainer runs a simple container in the background that
   556  // simply runs ls. If the name passed != "", it will have a name
   557  func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionIntegration, int, string) {
   558  	var podmanArgs = []string{"run"}
   559  	if name != "" {
   560  		podmanArgs = append(podmanArgs, "--name", name)
   561  	}
   562  	podmanArgs = append(podmanArgs, "-d", ALPINE, "ls")
   563  	session := p.Podman(podmanArgs)
   564  	session.WaitWithDefaultTimeout()
   565  	if session.ExitCode() != 0 {
   566  		return session, session.ExitCode(), session.OutputToString()
   567  	}
   568  	cid := session.OutputToString()
   569  
   570  	wsession := p.Podman([]string{"wait", cid})
   571  	wsession.WaitWithDefaultTimeout()
   572  	return session, wsession.ExitCode(), cid
   573  }
   574  
   575  // RunNginxWithHealthCheck runs the alpine nginx container with an optional name and adds a healthcheck into it
   576  func (p *PodmanTestIntegration) RunNginxWithHealthCheck(name string) (*PodmanSessionIntegration, string) {
   577  	var podmanArgs = []string{"run"}
   578  	if name != "" {
   579  		podmanArgs = append(podmanArgs, "--name", name)
   580  	}
   581  	// curl without -f exits 0 even if http code >= 400!
   582  	podmanArgs = append(podmanArgs, "-dt", "-P", "--health-cmd", "curl -f http://localhost/", NGINX_IMAGE)
   583  	session := p.Podman(podmanArgs)
   584  	session.WaitWithDefaultTimeout()
   585  	return session, session.OutputToString()
   586  }
   587  
   588  // RunContainerWithNetworkTest runs the fedoraMinimal curl with the specified network mode.
   589  func (p *PodmanTestIntegration) RunContainerWithNetworkTest(mode string) *PodmanSessionIntegration {
   590  	var podmanArgs = []string{"run"}
   591  	if mode != "" {
   592  		podmanArgs = append(podmanArgs, "--network", mode)
   593  	}
   594  	podmanArgs = append(podmanArgs, fedoraMinimal, "curl", "-s", "-S", "-k", "-o", "/dev/null", "http://www.redhat.com:80")
   595  	session := p.Podman(podmanArgs)
   596  	return session
   597  }
   598  
   599  func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) {
   600  	var podmanArgs = []string{"run", "--pod", pod}
   601  	if name != "" {
   602  		podmanArgs = append(podmanArgs, "--name", name)
   603  	}
   604  	podmanArgs = append(podmanArgs, "-d", ALPINE, "ls")
   605  	session := p.Podman(podmanArgs)
   606  	session.WaitWithDefaultTimeout()
   607  	if session.ExitCode() != 0 {
   608  		return session, session.ExitCode(), session.OutputToString()
   609  	}
   610  	cid := session.OutputToString()
   611  
   612  	wsession := p.Podman([]string{"wait", cid})
   613  	wsession.WaitWithDefaultTimeout()
   614  	return session, wsession.ExitCode(), cid
   615  }
   616  
   617  // BuildImage uses podman build and buildah to build an image
   618  // called imageName based on a string dockerfile
   619  func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) string {
   620  	return p.buildImage(dockerfile, imageName, layers, "")
   621  }
   622  
   623  // BuildImageWithLabel uses podman build and buildah to build an image
   624  // called imageName based on a string dockerfile, adds desired label to paramset
   625  func (p *PodmanTestIntegration) BuildImageWithLabel(dockerfile, imageName string, layers string, label string) string {
   626  	return p.buildImage(dockerfile, imageName, layers, label)
   627  }
   628  
   629  // PodmanPID execs podman and returns its PID
   630  func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) {
   631  	podmanOptions := p.MakeOptions(args, false, false)
   632  	GinkgoWriter.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " "))
   633  
   634  	command := exec.Command(p.PodmanBinary, podmanOptions...)
   635  	session, err := Start(command, GinkgoWriter, GinkgoWriter)
   636  	if err != nil {
   637  		Fail("unable to run podman command: " + strings.Join(podmanOptions, " "))
   638  	}
   639  	podmanSession := &PodmanSession{Session: session}
   640  	return &PodmanSessionIntegration{podmanSession}, command.Process.Pid
   641  }
   642  
   643  func (p *PodmanTestIntegration) Quadlet(args []string, sourceDir string) *PodmanSessionIntegration {
   644  	GinkgoWriter.Printf("Running: %s %s with QUADLET_UNIT_DIRS=%s\n", p.QuadletBinary, strings.Join(args, " "), sourceDir)
   645  
   646  	// quadlet uses PODMAN env to get a stable podman path
   647  	podmanPath, found := os.LookupEnv("PODMAN")
   648  	if !found {
   649  		podmanPath = p.PodmanBinary
   650  	}
   651  
   652  	command := exec.Command(p.QuadletBinary, args...)
   653  	command.Env = []string{
   654  		fmt.Sprintf("QUADLET_UNIT_DIRS=%s", sourceDir),
   655  		fmt.Sprintf("PODMAN=%s", podmanPath),
   656  	}
   657  	session, err := Start(command, GinkgoWriter, GinkgoWriter)
   658  	if err != nil {
   659  		Fail("unable to run quadlet command: " + strings.Join(args, " "))
   660  	}
   661  	quadletSession := &PodmanSession{Session: session}
   662  	return &PodmanSessionIntegration{quadletSession}
   663  }
   664  
   665  // Cleanup cleans up the temporary store
   666  func (p *PodmanTestIntegration) Cleanup() {
   667  	// ginkgo v2 still goes into AfterEach() when Skip() was called,
   668  	// some tests call skip before the podman test is initialized.
   669  	if p == nil {
   670  		return
   671  	}
   672  
   673  	// first stop everything, rm -fa is unreliable
   674  	// https://github.com/containers/podman/issues/18180
   675  	stop := p.Podman([]string{"stop", "--all", "-t", "0"})
   676  	stop.WaitWithDefaultTimeout()
   677  
   678  	// Remove all pods...
   679  	podrm := p.Podman([]string{"pod", "rm", "-fa", "-t", "0"})
   680  	podrm.WaitWithDefaultTimeout()
   681  
   682  	// ...and containers
   683  	rmall := p.Podman([]string{"rm", "-fa", "-t", "0"})
   684  	rmall.WaitWithDefaultTimeout()
   685  
   686  	p.StopRemoteService()
   687  	// Nuke tempdir
   688  	rmAll(p.PodmanBinary, p.TempDir)
   689  
   690  	// Clean up the registries configuration file ENV variable set in Create
   691  	resetRegistriesConfigEnv()
   692  
   693  	// Make sure to only check exit codes after all cleanup is done.
   694  	// An error would cause it to stop and return early otherwise.
   695  	Expect(stop).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", stop.Command.Args, stop.OutputToString(), stop.ErrorToString())
   696  	checkStderrCleanupError(stop, "stop --all -t0 error logged")
   697  	Expect(podrm).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", podrm.Command.Args, podrm.OutputToString(), podrm.ErrorToString())
   698  	checkStderrCleanupError(podrm, "pod rm -fa -t0 error logged")
   699  	Expect(rmall).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", rmall.Command.Args, rmall.OutputToString(), rmall.ErrorToString())
   700  	checkStderrCleanupError(rmall, "rm -fa -t0 error logged")
   701  }
   702  
   703  func checkStderrCleanupError(s *PodmanSessionIntegration, cmd string) {
   704  	if strings.Contains(podmanTest.OCIRuntime, "runc") {
   705  		// we cannot check stderr for runc, way to many errors
   706  		return
   707  	}
   708  	// offset is 1 so the stacj trace doesn't link to this helper function here
   709  	ExpectWithOffset(1, s.ErrorToString()).To(BeEmpty(), cmd)
   710  }
   711  
   712  // CleanupVolume cleans up the volumes and containers.
   713  // This already calls Cleanup() internally.
   714  func (p *PodmanTestIntegration) CleanupVolume() {
   715  	// Remove all containers
   716  	session := p.Podman([]string{"volume", "rm", "-fa"})
   717  	session.WaitWithDefaultTimeout()
   718  	Expect(session).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", session.Command.Args, session.OutputToString(), session.ErrorToString())
   719  	checkStderrCleanupError(session, "volume rm -fa error logged")
   720  }
   721  
   722  // CleanupSecret cleans up the secrets and containers.
   723  // This already calls Cleanup() internally.
   724  func (p *PodmanTestIntegration) CleanupSecrets() {
   725  	// Remove all containers
   726  	session := p.Podman([]string{"secret", "rm", "-a"})
   727  	session.Wait(90)
   728  	Expect(session).To(Exit(0), "command: %v\nstdout: %s\nstderr: %s", session.Command.Args, session.OutputToString(), session.ErrorToString())
   729  	checkStderrCleanupError(session, "secret rm -a error logged")
   730  }
   731  
   732  // InspectContainerToJSON takes the session output of an inspect
   733  // container and returns json
   734  func (s *PodmanSessionIntegration) InspectContainerToJSON() []define.InspectContainerData {
   735  	var i []define.InspectContainerData
   736  	err := jsoniter.Unmarshal(s.Out.Contents(), &i)
   737  	Expect(err).ToNot(HaveOccurred())
   738  	return i
   739  }
   740  
   741  // InspectPodToJSON takes the sessions output from a pod inspect and returns json
   742  func (s *PodmanSessionIntegration) InspectPodToJSON() define.InspectPodData {
   743  	var i []define.InspectPodData
   744  	err := jsoniter.Unmarshal(s.Out.Contents(), &i)
   745  	Expect(err).ToNot(HaveOccurred())
   746  	Expect(i).To(HaveLen(1))
   747  	return i[0]
   748  }
   749  
   750  // InspectPodToJSON takes the sessions output from an inspect and returns json
   751  func (s *PodmanSessionIntegration) InspectPodArrToJSON() []define.InspectPodData {
   752  	var i []define.InspectPodData
   753  	err := jsoniter.Unmarshal(s.Out.Contents(), &i)
   754  	Expect(err).ToNot(HaveOccurred())
   755  	return i
   756  }
   757  
   758  // CreatePod creates a pod with no infra container
   759  // it optionally takes a pod name
   760  func (p *PodmanTestIntegration) CreatePod(options map[string][]string) (*PodmanSessionIntegration, int, string) {
   761  	var args = []string{"pod", "create", "--infra=false", "--share", ""}
   762  	for k, values := range options {
   763  		for _, v := range values {
   764  			args = append(args, k+"="+v)
   765  		}
   766  	}
   767  
   768  	session := p.Podman(args)
   769  	session.WaitWithDefaultTimeout()
   770  	return session, session.ExitCode(), session.OutputToString()
   771  }
   772  
   773  func (p *PodmanTestIntegration) CreateVolume(options map[string][]string) (*PodmanSessionIntegration, int, string) {
   774  	var args = []string{"volume", "create"}
   775  	for k, values := range options {
   776  		for _, v := range values {
   777  			args = append(args, k+"="+v)
   778  		}
   779  	}
   780  
   781  	session := p.Podman(args)
   782  	session.WaitWithDefaultTimeout()
   783  	return session, session.ExitCode(), session.OutputToString()
   784  }
   785  
   786  func (p *PodmanTestIntegration) RunTopContainerInPod(name, pod string) *PodmanSessionIntegration {
   787  	return p.RunTopContainerWithArgs(name, []string{"--pod", pod})
   788  }
   789  
   790  func (p *PodmanTestIntegration) RunHealthCheck(cid string) error {
   791  	for i := 0; i < 10; i++ {
   792  		hc := p.Podman([]string{"healthcheck", "run", cid})
   793  		hc.WaitWithDefaultTimeout()
   794  		if hc.ExitCode() == 0 {
   795  			return nil
   796  		}
   797  		// Restart container if it's not running
   798  		ps := p.Podman([]string{"ps", "--no-trunc", "--quiet", "--filter", fmt.Sprintf("id=%s", cid)})
   799  		ps.WaitWithDefaultTimeout()
   800  		if ps.ExitCode() == 0 {
   801  			if !strings.Contains(ps.OutputToString(), cid) {
   802  				GinkgoWriter.Printf("Container %s is not running, restarting", cid)
   803  				restart := p.Podman([]string{"restart", cid})
   804  				restart.WaitWithDefaultTimeout()
   805  				if restart.ExitCode() != 0 {
   806  					return fmt.Errorf("unable to restart %s", cid)
   807  				}
   808  			}
   809  		}
   810  		GinkgoWriter.Printf("Waiting for %s to pass healthcheck\n", cid)
   811  		time.Sleep(1 * time.Second)
   812  	}
   813  	return fmt.Errorf("unable to detect %s as running", cid)
   814  }
   815  
   816  func (p *PodmanTestIntegration) CreateSeccompJSON(in []byte) (string, error) {
   817  	jsonFile := filepath.Join(p.TempDir, "seccomp.json")
   818  	err := WriteJSONFile(in, jsonFile)
   819  	if err != nil {
   820  		return "", err
   821  	}
   822  	return jsonFile, nil
   823  }
   824  
   825  func checkReason(reason string) {
   826  	if len(reason) < 5 {
   827  		panic("Test must specify a reason to skip")
   828  	}
   829  }
   830  
   831  func SkipIfRunc(p *PodmanTestIntegration, reason string) {
   832  	checkReason(reason)
   833  	if p.OCIRuntime == "runc" {
   834  		Skip("[runc]: " + reason)
   835  	}
   836  }
   837  
   838  func SkipIfRootlessCgroupsV1(reason string) {
   839  	checkReason(reason)
   840  	if isRootless() && !CGROUPSV2 {
   841  		Skip("[rootless]: " + reason)
   842  	}
   843  }
   844  
   845  func SkipIfRootless(reason string) {
   846  	checkReason(reason)
   847  	if isRootless() {
   848  		Skip("[rootless]: " + reason)
   849  	}
   850  }
   851  
   852  func SkipIfNotRootless(reason string) {
   853  	checkReason(reason)
   854  	if !isRootless() {
   855  		Skip("[notRootless]: " + reason)
   856  	}
   857  }
   858  
   859  func SkipIfSystemdNotRunning(reason string) {
   860  	checkReason(reason)
   861  
   862  	cmd := exec.Command("systemctl", "list-units")
   863  	err := cmd.Run()
   864  	if err != nil {
   865  		if _, ok := err.(*exec.Error); ok {
   866  			Skip("[notSystemd]: not running " + reason)
   867  		}
   868  		Expect(err).ToNot(HaveOccurred())
   869  	}
   870  }
   871  
   872  func SkipIfNotSystemd(manager, reason string) {
   873  	checkReason(reason)
   874  	if manager != "systemd" {
   875  		Skip("[notSystemd]: " + reason)
   876  	}
   877  }
   878  
   879  func SkipOnOSVersion(os, version string) {
   880  	info := GetHostDistributionInfo()
   881  	if info.Distribution == os && info.Version == version {
   882  		Skip(fmt.Sprintf("Test doesn't work on %s %s", os, version))
   883  	}
   884  }
   885  
   886  func SkipIfNotFedora(reason string) {
   887  	info := GetHostDistributionInfo()
   888  	if info.Distribution != "fedora" {
   889  		Skip(reason)
   890  	}
   891  }
   892  
   893  type journaldTests struct {
   894  	journaldSkip bool
   895  	journaldOnce sync.Once
   896  }
   897  
   898  var journald journaldTests
   899  
   900  // Check if journalctl is unavailable
   901  func checkAvailableJournald() {
   902  	f := func() {
   903  		journald.journaldSkip = false
   904  
   905  		cmd := exec.Command("journalctl", "-n", "1")
   906  		if err := cmd.Run(); err != nil {
   907  			journald.journaldSkip = true
   908  		}
   909  	}
   910  	journald.journaldOnce.Do(f)
   911  }
   912  
   913  func SkipIfJournaldUnavailable() {
   914  	checkAvailableJournald()
   915  
   916  	// In container, journalctl does not return an error even if
   917  	// journald is unavailable
   918  	SkipIfInContainer("[journald]: journalctl inside a container doesn't work correctly")
   919  	if journald.journaldSkip {
   920  		Skip("[journald]: journald is unavailable")
   921  	}
   922  }
   923  
   924  // Use isRootless() instead of rootless.IsRootless()
   925  // This function can detect to join the user namespace by mistake
   926  func isRootless() bool {
   927  	return os.Geteuid() != 0
   928  }
   929  
   930  func isCgroupsV1() bool {
   931  	return !CGROUPSV2
   932  }
   933  
   934  func SkipIfCgroupV1(reason string) {
   935  	checkReason(reason)
   936  	if isCgroupsV1() {
   937  		Skip(reason)
   938  	}
   939  }
   940  
   941  func SkipIfCgroupV2(reason string) {
   942  	checkReason(reason)
   943  	if CGROUPSV2 {
   944  		Skip(reason)
   945  	}
   946  }
   947  
   948  func isContainerized() bool {
   949  	// This is set to "podman" by podman automatically
   950  	return os.Getenv("container") != ""
   951  }
   952  
   953  func SkipIfContainerized(reason string) {
   954  	checkReason(reason)
   955  	if isContainerized() {
   956  		Skip(reason)
   957  	}
   958  }
   959  
   960  func SkipIfRemote(reason string) {
   961  	checkReason(reason)
   962  	if !IsRemote() {
   963  		return
   964  	}
   965  	Skip("[remote]: " + reason)
   966  }
   967  
   968  func SkipIfNotRemote(reason string) {
   969  	checkReason(reason)
   970  	if IsRemote() {
   971  		return
   972  	}
   973  	Skip("[local]: " + reason)
   974  }
   975  
   976  // SkipIfInContainer skips a test if the test is run inside a container
   977  func SkipIfInContainer(reason string) {
   978  	checkReason(reason)
   979  	if os.Getenv("TEST_ENVIRON") == "container" {
   980  		Skip("[container]: " + reason)
   981  	}
   982  }
   983  
   984  // SkipIfNotActive skips a test if the given systemd unit is not active
   985  func SkipIfNotActive(unit string, reason string) {
   986  	checkReason(reason)
   987  
   988  	cmd := exec.Command("systemctl", "is-active", unit)
   989  	cmd.Stdout = GinkgoWriter
   990  	cmd.Stderr = GinkgoWriter
   991  	err := cmd.Run()
   992  	if cmd.ProcessState.ExitCode() == 0 {
   993  		return
   994  	}
   995  	Skip(fmt.Sprintf("[systemd]: unit %s is not active (%v): %s", unit, err, reason))
   996  }
   997  
   998  func SkipIfCNI(p *PodmanTestIntegration) {
   999  	if p.NetworkBackend == CNI {
  1000  		Skip("this test is not compatible with the CNI network backend")
  1001  	}
  1002  }
  1003  
  1004  func SkipIfNetavark(p *PodmanTestIntegration) {
  1005  	if p.NetworkBackend == Netavark {
  1006  		Skip("This test is not compatible with the netavark network backend")
  1007  	}
  1008  }
  1009  
  1010  // PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment
  1011  func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, cwd string, env []string) *PodmanSessionIntegration {
  1012  	podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false, nil, nil)
  1013  	return &PodmanSessionIntegration{podmanSession}
  1014  }
  1015  
  1016  // RestartRemoteService stop and start API Server, usually to change config
  1017  func (p *PodmanTestIntegration) RestartRemoteService() {
  1018  	p.StopRemoteService()
  1019  	p.StartRemoteService()
  1020  }
  1021  
  1022  // RestoreArtifactToCache populates the imagecache from tarballs that were cached earlier
  1023  func (p *PodmanTestIntegration) RestoreArtifactToCache(image string) error {
  1024  	tarball := imageTarPath(image)
  1025  	if _, err := os.Stat(tarball); err == nil {
  1026  		GinkgoWriter.Printf("Restoring %s...\n", image)
  1027  		p.Root = p.ImageCacheDir
  1028  		restore := p.PodmanNoEvents([]string{"load", "-q", "-i", tarball})
  1029  		restore.WaitWithDefaultTimeout()
  1030  		Expect(restore).To(ExitCleanly())
  1031  	}
  1032  	return nil
  1033  }
  1034  
  1035  func populateCache(podman *PodmanTestIntegration) {
  1036  	for _, image := range CACHE_IMAGES {
  1037  		err := podman.RestoreArtifactToCache(image)
  1038  		Expect(err).ToNot(HaveOccurred())
  1039  	}
  1040  	// logformatter uses this to recognize the first test
  1041  	GinkgoWriter.Printf("-----------------------------\n")
  1042  }
  1043  
  1044  // rmAll removes the directory and its content, when running rootless we use
  1045  // podman unshare to prevent any subuid/gid problems
  1046  func rmAll(podmanBin string, path string) {
  1047  	// Remove cache dirs
  1048  	if isRootless() {
  1049  		// If rootless, os.RemoveAll() is failed due to permission denied
  1050  		cmd := exec.Command(podmanBin, "unshare", "rm", "-rf", path)
  1051  		cmd.Stdout = GinkgoWriter
  1052  		cmd.Stderr = GinkgoWriter
  1053  		if err := cmd.Run(); err != nil {
  1054  			GinkgoWriter.Printf("%v\n", err)
  1055  		}
  1056  	} else {
  1057  		// When using overlay as root, podman leaves a stray mount behind.
  1058  		// This leak causes remote tests to take a loooooong time, which
  1059  		// then causes Cirrus to time out. Unmount that stray.
  1060  		overlayPath := path + "/root/overlay"
  1061  		if _, err := os.Stat(overlayPath); err == nil {
  1062  			if err = unix.Unmount(overlayPath, unix.MNT_DETACH); err != nil {
  1063  				GinkgoWriter.Printf("Error unmounting %s: %v\n", overlayPath, err)
  1064  			}
  1065  		}
  1066  
  1067  		if err = os.RemoveAll(path); err != nil {
  1068  			GinkgoWriter.Printf("%q\n", err)
  1069  		}
  1070  	}
  1071  }
  1072  
  1073  // PodmanNoCache calls the podman command with no configured imagecache
  1074  func (p *PodmanTestIntegration) PodmanNoCache(args []string) *PodmanSessionIntegration {
  1075  	podmanSession := p.PodmanBase(args, false, true)
  1076  	return &PodmanSessionIntegration{podmanSession}
  1077  }
  1078  
  1079  func PodmanTestSetup(tempDir string) *PodmanTestIntegration {
  1080  	return PodmanTestCreateUtil(tempDir, false)
  1081  }
  1082  
  1083  // PodmanNoEvents calls the Podman command without an imagecache and without an
  1084  // events backend. It is used mostly for caching and uncaching images.
  1085  func (p *PodmanTestIntegration) PodmanNoEvents(args []string) *PodmanSessionIntegration {
  1086  	podmanSession := p.PodmanBase(args, true, true)
  1087  	return &PodmanSessionIntegration{podmanSession}
  1088  }
  1089  
  1090  // MakeOptions assembles all the podman main options
  1091  func (p *PodmanTestIntegration) makeOptions(args []string, noEvents, noCache bool) []string {
  1092  	if p.RemoteTest {
  1093  		if !slices.Contains(args, "--remote") {
  1094  			return append([]string{"--remote", "--url", p.RemoteSocket}, args...)
  1095  		}
  1096  		return args
  1097  	}
  1098  
  1099  	var debug string
  1100  	if _, ok := os.LookupEnv("E2E_DEBUG"); ok {
  1101  		debug = "--log-level=debug --syslog=true "
  1102  	}
  1103  
  1104  	eventsType := "file"
  1105  	if noEvents {
  1106  		eventsType = "none"
  1107  	}
  1108  
  1109  	podmanOptions := strings.Split(fmt.Sprintf("%s--root %s --runroot %s --runtime %s --conmon %s --network-config-dir %s --network-backend %s --cgroup-manager %s --tmpdir %s --events-backend %s --db-backend %s",
  1110  		debug, p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.NetworkConfigDir, p.NetworkBackend.ToString(), p.CgroupManager, p.TmpDir, eventsType, p.DatabaseBackend), " ")
  1111  
  1112  	podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...)
  1113  	if !noCache {
  1114  		cacheOptions := []string{"--storage-opt",
  1115  			fmt.Sprintf("%s.imagestore=%s", p.PodmanTest.ImageCacheFS, p.PodmanTest.ImageCacheDir)}
  1116  		podmanOptions = append(cacheOptions, podmanOptions...)
  1117  	}
  1118  	podmanOptions = append(podmanOptions, args...)
  1119  	return podmanOptions
  1120  }
  1121  
  1122  func writeConf(conf []byte, confPath string) {
  1123  	if _, err := os.Stat(filepath.Dir(confPath)); os.IsNotExist(err) {
  1124  		if err := os.MkdirAll(filepath.Dir(confPath), 0o777); err != nil {
  1125  			GinkgoWriter.Println(err)
  1126  		}
  1127  	}
  1128  	if err := os.WriteFile(confPath, conf, 0o777); err != nil {
  1129  		GinkgoWriter.Println(err)
  1130  	}
  1131  }
  1132  
  1133  func removeConf(confPath string) {
  1134  	if err := os.Remove(confPath); err != nil {
  1135  		GinkgoWriter.Println(err)
  1136  	}
  1137  }
  1138  
  1139  // generateNetworkConfig generates a CNI or Netavark config with a random name
  1140  // it returns the network name and the filepath
  1141  func generateNetworkConfig(p *PodmanTestIntegration) (string, string) {
  1142  	var (
  1143  		path string
  1144  		conf string
  1145  	)
  1146  	// generate a random name to prevent conflicts with other tests
  1147  	name := "net" + stringid.GenerateRandomID()
  1148  	if p.NetworkBackend != Netavark {
  1149  		path = filepath.Join(p.NetworkConfigDir, fmt.Sprintf("%s.conflist", name))
  1150  		conf = fmt.Sprintf(`{
  1151  		"cniVersion": "0.3.0",
  1152  		"name": "%s",
  1153  		"plugins": [
  1154  		  {
  1155  			"type": "bridge",
  1156  			"bridge": "cni1",
  1157  			"isGateway": true,
  1158  			"ipMasq": true,
  1159  			"ipam": {
  1160  				"type": "host-local",
  1161  				"subnet": "10.99.0.0/16",
  1162  				"routes": [
  1163  					{ "dst": "0.0.0.0/0" }
  1164  				]
  1165  			}
  1166  		  },
  1167  		  {
  1168  			"type": "portmap",
  1169  			"capabilities": {
  1170  			  "portMappings": true
  1171  			}
  1172  		  }
  1173  		]
  1174  	}`, name)
  1175  	} else {
  1176  		path = filepath.Join(p.NetworkConfigDir, fmt.Sprintf("%s.json", name))
  1177  		conf = fmt.Sprintf(`
  1178  {
  1179       "name": "%s",
  1180       "id": "e1ef2749024b88f5663ca693a9118e036d6bfc48bcfe460faf45e9614a513e5c",
  1181       "driver": "bridge",
  1182       "network_interface": "netavark1",
  1183       "created": "2022-01-05T14:15:10.975493521-06:00",
  1184       "subnets": [
  1185            {
  1186                 "subnet": "10.100.0.0/16",
  1187                 "gateway": "10.100.0.1"
  1188            }
  1189       ],
  1190       "ipv6_enabled": false,
  1191       "internal": false,
  1192       "dns_enabled": true,
  1193       "ipam_options": {
  1194            "driver": "host-local"
  1195       }
  1196  }
  1197  `, name)
  1198  	}
  1199  	writeConf([]byte(conf), path)
  1200  	return name, path
  1201  }
  1202  
  1203  func (p *PodmanTestIntegration) removeNetwork(name string) {
  1204  	session := p.Podman([]string{"network", "rm", "-f", name})
  1205  	session.WaitWithDefaultTimeout()
  1206  	Expect(session.ExitCode()).To(BeNumerically("<=", 1), "Exit code must be 0 or 1")
  1207  }
  1208  
  1209  // generatePolicyFile generates a signature verification policy file.
  1210  // it returns the policy file path.
  1211  func generatePolicyFile(tempDir string, port int) string {
  1212  	keyPath := filepath.Join(tempDir, "key.gpg")
  1213  	policyPath := filepath.Join(tempDir, "policy.json")
  1214  	conf := fmt.Sprintf(`
  1215  {
  1216      "default": [
  1217          {
  1218              "type": "insecureAcceptAnything"
  1219          }
  1220      ],
  1221      "transports": {
  1222          "docker": {
  1223              "localhost:%[1]d": [
  1224                  {
  1225                      "type": "signedBy",
  1226                      "keyType": "GPGKeys",
  1227                      "keyPath": "%[2]s"
  1228                  }
  1229              ],
  1230              "localhost:%[1]d/sigstore-signed": [
  1231                  {
  1232                      "type": "sigstoreSigned",
  1233                      "keyPath": "testdata/sigstore-key.pub"
  1234                  }
  1235              ],
  1236              "localhost:%[1]d/sigstore-signed-params": [
  1237                  {
  1238                      "type": "sigstoreSigned",
  1239                      "keyPath": "testdata/sigstore-key.pub"
  1240                  }
  1241              ]
  1242          }
  1243      }
  1244  }
  1245  `, port, keyPath)
  1246  	writeConf([]byte(conf), policyPath)
  1247  	return policyPath
  1248  }
  1249  
  1250  func (s *PodmanSessionIntegration) jq(jqCommand string) (string, error) {
  1251  	var out bytes.Buffer
  1252  	cmd := exec.Command("jq", jqCommand)
  1253  	cmd.Stdin = strings.NewReader(s.OutputToString())
  1254  	cmd.Stdout = &out
  1255  	err := cmd.Run()
  1256  	return strings.TrimRight(out.String(), "\n"), err
  1257  }
  1258  
  1259  func (p *PodmanTestIntegration) buildImage(dockerfile, imageName string, layers string, label string) string {
  1260  	dockerfilePath := filepath.Join(p.TempDir, "Dockerfile-"+stringid.GenerateRandomID())
  1261  	err := os.WriteFile(dockerfilePath, []byte(dockerfile), 0755)
  1262  	Expect(err).ToNot(HaveOccurred())
  1263  	cmd := []string{"build", "--pull-never", "--layers=" + layers, "--file", dockerfilePath}
  1264  	if label != "" {
  1265  		cmd = append(cmd, "--label="+label)
  1266  	}
  1267  	if len(imageName) > 0 {
  1268  		cmd = append(cmd, []string{"-t", imageName}...)
  1269  	}
  1270  	cmd = append(cmd, p.TempDir)
  1271  	session := p.Podman(cmd)
  1272  	session.Wait(240)
  1273  	Expect(session).Should(Exit(0), fmt.Sprintf("BuildImage session output: %q", session.OutputToString()))
  1274  	output := session.OutputToStringArray()
  1275  	return output[len(output)-1]
  1276  }
  1277  
  1278  func writeYaml(content string, fileName string) error {
  1279  	f, err := os.Create(fileName)
  1280  	if err != nil {
  1281  		return err
  1282  	}
  1283  	defer f.Close()
  1284  
  1285  	_, err = f.WriteString(content)
  1286  	if err != nil {
  1287  		return err
  1288  	}
  1289  
  1290  	return nil
  1291  }
  1292  
  1293  // GetPort finds an unused TCP/IP port on the system, in the range 5000-5999
  1294  func GetPort() int {
  1295  	portMin := 5000
  1296  	portMax := 5999
  1297  	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
  1298  
  1299  	// Avoid dup-allocation races between parallel ginkgo processes
  1300  	nProcs := GinkgoT().ParallelTotal()
  1301  	myProc := GinkgoT().ParallelProcess() - 1
  1302  
  1303  	for i := 0; i < 50; i++ {
  1304  		// Random port within that range
  1305  		port := portMin + rng.Intn((portMax-portMin)/nProcs)*nProcs + myProc
  1306  
  1307  		used, err := net.Listen("tcp", "0.0.0.0:"+strconv.Itoa(port))
  1308  		if err == nil {
  1309  			// it's open. Return it.
  1310  			err = used.Close()
  1311  			Expect(err).ToNot(HaveOccurred(), "closing random port")
  1312  			return port
  1313  		}
  1314  	}
  1315  
  1316  	Fail(fmt.Sprintf("unable to get free port in range %d-%d", portMin, portMax))
  1317  	return 0 // notreached
  1318  }
  1319  
  1320  func ncz(port int) bool {
  1321  	timeout := 500 * time.Millisecond
  1322  	for i := 0; i < 5; i++ {
  1323  		ncCmd := []string{"-z", "localhost", strconv.Itoa(port)}
  1324  		GinkgoWriter.Printf("Running: nc %s\n", strings.Join(ncCmd, " "))
  1325  		check := SystemExec("nc", ncCmd)
  1326  		if check.ExitCode() == 0 {
  1327  			return true
  1328  		}
  1329  		time.Sleep(timeout)
  1330  		timeout++
  1331  	}
  1332  	return false
  1333  }
  1334  
  1335  func createNetworkName(name string) string {
  1336  	return name + stringid.GenerateRandomID()[:10]
  1337  }
  1338  
  1339  var IPRegex = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`
  1340  
  1341  // digShort execs into the given container and does a dig lookup with a timeout
  1342  // backoff.  If it gets a response, it ensures that the output is in the correct
  1343  // format and iterates a string array for match
  1344  func digShort(container, lookupName, expectedIP string, p *PodmanTestIntegration) {
  1345  	digInterval := time.Millisecond * 250
  1346  	for i := 0; i < 6; i++ {
  1347  		time.Sleep(digInterval * time.Duration(i))
  1348  		dig := p.Podman([]string{"exec", container, "dig", "+short", lookupName})
  1349  		dig.WaitWithDefaultTimeout()
  1350  		output := dig.OutputToString()
  1351  		if dig.ExitCode() == 0 && output != "" {
  1352  			Expect(output).To(Equal(expectedIP))
  1353  			// success
  1354  			return
  1355  		}
  1356  	}
  1357  	Fail("dns is not responding")
  1358  }
  1359  
  1360  // WaitForFile to be created in defaultWaitTimeout seconds, returns false if file not created
  1361  func WaitForFile(path string) (err error) {
  1362  	until := time.Now().Add(time.Duration(defaultWaitTimeout) * time.Second)
  1363  	for time.Now().Before(until) {
  1364  		_, err = os.Stat(path)
  1365  		switch {
  1366  		case err == nil:
  1367  			return nil
  1368  		case errors.Is(err, os.ErrNotExist):
  1369  			time.Sleep(10 * time.Millisecond)
  1370  		default:
  1371  			return err
  1372  		}
  1373  	}
  1374  	return err
  1375  }
  1376  
  1377  // WaitForService blocks for defaultWaitTimeout seconds, waiting for some service listening on given host:port
  1378  func WaitForService(address url.URL) {
  1379  	// Wait for podman to be ready
  1380  	var err error
  1381  	until := time.Now().Add(time.Duration(defaultWaitTimeout) * time.Second)
  1382  	for time.Now().Before(until) {
  1383  		var conn net.Conn
  1384  		conn, err = net.Dial("tcp", address.Host)
  1385  		if err == nil {
  1386  			conn.Close()
  1387  			break
  1388  		}
  1389  
  1390  		// Podman not available yet...
  1391  		time.Sleep(10 * time.Millisecond)
  1392  	}
  1393  	Expect(err).ShouldNot(HaveOccurred())
  1394  }
  1395  
  1396  // useCustomNetworkDir makes sure this test uses a custom network dir.
  1397  // This needs to be called for all test they may remove networks from other tests,
  1398  // so netwokr prune, system prune, or system reset.
  1399  // see https://github.com/containers/podman/issues/17946
  1400  func useCustomNetworkDir(podmanTest *PodmanTestIntegration, tempdir string) {
  1401  	// set custom network directory to prevent flakes since the dir is shared with all tests by default
  1402  	podmanTest.NetworkConfigDir = tempdir
  1403  	if IsRemote() {
  1404  		podmanTest.RestartRemoteService()
  1405  	}
  1406  }
  1407  
  1408  // copy directories recursively from source path to destination path
  1409  func CopyDirectory(srcDir, dest string) error {
  1410  	entries, err := os.ReadDir(srcDir)
  1411  	if err != nil {
  1412  		return err
  1413  	}
  1414  	for _, entry := range entries {
  1415  		sourcePath := filepath.Join(srcDir, entry.Name())
  1416  		destPath := filepath.Join(dest, entry.Name())
  1417  
  1418  		fileInfo, err := os.Stat(sourcePath)
  1419  		if err != nil {
  1420  			return err
  1421  		}
  1422  
  1423  		stat, ok := fileInfo.Sys().(*syscall.Stat_t)
  1424  		if !ok {
  1425  			return fmt.Errorf("failed to get raw syscall.Stat_t data for %q", sourcePath)
  1426  		}
  1427  
  1428  		switch fileInfo.Mode() & os.ModeType {
  1429  		case os.ModeDir:
  1430  			if err := os.MkdirAll(destPath, 0755); err != nil {
  1431  				return fmt.Errorf("failed to create directory: %q, error: %q", destPath, err.Error())
  1432  			}
  1433  			if err := CopyDirectory(sourcePath, destPath); err != nil {
  1434  				return err
  1435  			}
  1436  		case os.ModeSymlink:
  1437  			if err := CopySymLink(sourcePath, destPath); err != nil {
  1438  				return err
  1439  			}
  1440  		default:
  1441  			if err := Copy(sourcePath, destPath); err != nil {
  1442  				return err
  1443  			}
  1444  		}
  1445  
  1446  		if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil {
  1447  			return err
  1448  		}
  1449  
  1450  		fInfo, err := entry.Info()
  1451  		if err != nil {
  1452  			return err
  1453  		}
  1454  
  1455  		isSymlink := fInfo.Mode()&os.ModeSymlink != 0
  1456  		if !isSymlink {
  1457  			if err := os.Chmod(destPath, fInfo.Mode()); err != nil {
  1458  				return err
  1459  			}
  1460  		}
  1461  	}
  1462  	return nil
  1463  }
  1464  
  1465  func Copy(srcFile, dstFile string) error {
  1466  	out, err := os.Create(dstFile)
  1467  	if err != nil {
  1468  		return err
  1469  	}
  1470  
  1471  	defer out.Close()
  1472  
  1473  	in, err := os.Open(srcFile)
  1474  	if err != nil {
  1475  		return err
  1476  	}
  1477  
  1478  	_, err = io.Copy(out, in)
  1479  	if err != nil {
  1480  		return err
  1481  	}
  1482  	defer in.Close()
  1483  	return nil
  1484  }
  1485  
  1486  func CopySymLink(source, dest string) error {
  1487  	link, err := os.Readlink(source)
  1488  	if err != nil {
  1489  		return err
  1490  	}
  1491  	return os.Symlink(link, dest)
  1492  }