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 }