14
crypto_rand "crypto/rand"
19
"github.com/sirupsen/logrus"
21
"github.com/containers/storage/pkg/parsers/kernel"
22
. "github.com/onsi/ginkgo/v2" //nolint:revive,stylecheck
23
. "github.com/onsi/gomega" //nolint:revive,stylecheck
24
. "github.com/onsi/gomega/gexec" //nolint:revive,stylecheck
27
type NetworkBackend int
30
// Container Networking backend
31
CNI NetworkBackend = iota
32
// Netavark network backend
33
Netavark NetworkBackend = iota
34
// Env variable for creating time files.
35
EnvTimeDir = "_PODMAN_TIME_DIR"
38
func (n NetworkBackend) ToString() string {
45
logrus.Errorf("unknown network backend: %q", n)
50
DefaultWaitTimeout = 90
51
OSReleasePath = "/etc/os-release"
52
ProcessOneCgroupPath = "/proc/1/cgroup"
55
// PodmanTestCommon contains common functions will be updated later in
56
// the inheritance structs
57
type PodmanTestCommon interface {
58
MakeOptions(args []string, noEvents, noCache bool) []string
59
WaitForContainer() bool
60
WaitContainerReady(id string, expStr string, timeout int, step int) bool
63
// PodmanTest struct for command line options
64
type PodmanTest struct {
67
NetworkBackend NetworkBackend
68
DatabaseBackend string
70
PodmanMakeOptions func(args []string, noEvents, noCache bool) []string
71
RemoteCommand *exec.Cmd
72
RemotePodmanBinary string
73
RemoteSession *os.Process
75
RemoteSocketLock string // If not "", should be removed _after_ RemoteSocket is removed
80
// PodmanSession wraps the gexec.session so we can extend it
81
type PodmanSession struct {
85
// HostOS is a simple struct for the test os
92
// MakeOptions assembles all podman options
93
func (p *PodmanTest) MakeOptions(args []string, noEvents, noCache bool) []string {
94
return p.PodmanMakeOptions(args, noEvents, noCache)
97
// PodmanAsUserBase exec podman as user. uid and gid is set for credentials usage. env is used
98
// to record the env for debugging
99
func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, noEvents, noCache bool, wrapper []string, extraFiles []*os.File) *PodmanSession {
100
var command *exec.Cmd
101
podmanOptions := p.MakeOptions(args, noEvents, noCache)
102
podmanBinary := p.PodmanBinary
104
podmanBinary = p.RemotePodmanBinary
107
if timeDir := os.Getenv(EnvTimeDir); timeDir != "" {
108
timeFile, err := os.CreateTemp(timeDir, ".time")
110
Fail(fmt.Sprintf("Error creating time file: %v", err))
112
timeArgs := []string{"-f", "%M", "-o", timeFile.Name()}
113
timeCmd := append([]string{"/usr/bin/time"}, timeArgs...)
114
wrapper = append(timeCmd, wrapper...)
117
runCmd = append(runCmd, podmanBinary)
120
GinkgoWriter.Printf("Running: %s %s\n", strings.Join(runCmd, " "), strings.Join(podmanOptions, " "))
122
GinkgoWriter.Printf("Running: (env: %v) %s %s\n", env, strings.Join(runCmd, " "), strings.Join(podmanOptions, " "))
124
if uid != 0 || gid != 0 {
125
pythonCmd := fmt.Sprintf("import os; import sys; uid = %d; gid = %d; cwd = '%s'; os.setgid(gid); os.setuid(uid); os.chdir(cwd) if len(cwd)>0 else True; os.execv(sys.argv[1], sys.argv[1:])", gid, uid, cwd)
126
runCmd = append(runCmd, podmanOptions...)
127
nsEnterOpts := append([]string{"-c", pythonCmd}, runCmd...)
128
command = exec.Command("python", nsEnterOpts...)
130
runCmd = append(runCmd, podmanOptions...)
131
command = exec.Command(runCmd[0], runCmd[1:]...)
140
command.ExtraFiles = extraFiles
142
session, err := Start(command, GinkgoWriter, GinkgoWriter)
144
Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err))
146
return &PodmanSession{session}
149
// PodmanBase exec podman with default env.
150
func (p *PodmanTest) PodmanBase(args []string, noEvents, noCache bool) *PodmanSession {
151
return p.PodmanAsUserBase(args, 0, 0, "", nil, noEvents, noCache, nil, nil)
154
// WaitForContainer waits on a started container
155
func (p *PodmanTest) WaitForContainer() bool {
156
for i := 0; i < 10; i++ {
157
if p.NumberOfContainersRunning() > 0 {
160
time.Sleep(1 * time.Second)
162
GinkgoWriter.Printf("WaitForContainer(): timed out\n")
166
// NumberOfContainersRunning returns an int of how many
167
// containers are currently running.
168
func (p *PodmanTest) NumberOfContainersRunning() int {
169
var containers []string
170
ps := p.PodmanBase([]string{"ps", "-q"}, false, true)
171
ps.WaitWithDefaultTimeout()
172
Expect(ps).Should(Exit(0))
173
for _, i := range ps.OutputToStringArray() {
175
containers = append(containers, i)
178
return len(containers)
181
// NumberOfContainers returns an int of how many
182
// containers are currently defined.
183
func (p *PodmanTest) NumberOfContainers() int {
184
var containers []string
185
ps := p.PodmanBase([]string{"ps", "-aq"}, false, true)
186
ps.WaitWithDefaultTimeout()
187
Expect(ps.ExitCode()).To(Equal(0))
188
for _, i := range ps.OutputToStringArray() {
190
containers = append(containers, i)
193
return len(containers)
196
// NumberOfPods returns an int of how many
197
// pods are currently defined.
198
func (p *PodmanTest) NumberOfPods() int {
200
ps := p.PodmanBase([]string{"pod", "ps", "-q"}, false, true)
201
ps.WaitWithDefaultTimeout()
202
Expect(ps.ExitCode()).To(Equal(0))
203
for _, i := range ps.OutputToStringArray() {
205
pods = append(pods, i)
211
// GetContainerStatus returns the containers state.
212
// This function assumes only one container is active.
213
func (p *PodmanTest) GetContainerStatus() string {
214
var podmanArgs = []string{"ps"}
215
podmanArgs = append(podmanArgs, "--all", "--format={{.Status}}")
216
session := p.PodmanBase(podmanArgs, false, true)
217
session.WaitWithDefaultTimeout()
218
return session.OutputToString()
221
// WaitContainerReady waits process or service inside container start, and ready to be used.
222
func (p *PodmanTest) WaitContainerReady(id string, expStr string, timeout int, step int) bool {
223
startTime := time.Now()
224
s := p.PodmanBase([]string{"logs", id}, false, true)
225
s.WaitWithDefaultTimeout()
228
if strings.Contains(s.OutputToString(), expStr) || strings.Contains(s.ErrorToString(), expStr) {
232
if time.Since(startTime) >= time.Duration(timeout)*time.Second {
233
GinkgoWriter.Printf("Container %s is not ready in %ds", id, timeout)
236
time.Sleep(time.Duration(step) * time.Second)
237
s = p.PodmanBase([]string{"logs", id}, false, true)
238
s.WaitWithDefaultTimeout()
242
// WaitForContainer is a wrapper function for accept inheritance PodmanTest struct.
243
func WaitForContainer(p PodmanTestCommon) bool {
244
return p.WaitForContainer()
247
// WaitForContainerReady is a wrapper function for accept inheritance PodmanTest struct.
248
func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout int, step int) bool {
249
return p.WaitContainerReady(id, expStr, timeout, step)
252
// OutputToString formats session output to string
253
func (s *PodmanSession) OutputToString() string {
254
if s == nil || s.Out == nil || s.Out.Contents() == nil {
258
fields := strings.Fields(string(s.Out.Contents()))
259
return strings.Join(fields, " ")
262
// OutputToStringArray returns the output as a []string
263
// where each array item is a line split by newline
264
func (s *PodmanSession) OutputToStringArray() []string {
266
output := string(s.Out.Contents())
267
for _, line := range strings.Split(output, "\n") {
269
results = append(results, line)
275
// ErrorToString formats session stderr to string
276
func (s *PodmanSession) ErrorToString() string {
277
fields := strings.Fields(string(s.Err.Contents()))
278
return strings.Join(fields, " ")
281
// ErrorToStringArray returns the stderr output as a []string
282
// where each array item is a line split by newline
283
func (s *PodmanSession) ErrorToStringArray() []string {
284
output := string(s.Err.Contents())
285
return strings.Split(output, "\n")
288
// GrepString takes session output and behaves like grep. it returns a bool
289
// if successful and an array of strings on positive matches
290
func (s *PodmanSession) GrepString(term string) (bool, []string) {
296
for _, line := range s.OutputToStringArray() {
297
if strings.Contains(line, term) {
299
greps = append(greps, line)
302
return matches, greps
305
// ErrorGrepString takes session stderr output and behaves like grep. it returns a bool
306
// if successful and an array of strings on positive matches
307
func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) {
313
for _, line := range s.ErrorToStringArray() {
314
if strings.Contains(line, term) {
316
greps = append(greps, line)
319
return matches, greps
322
// LineInOutputStartsWith returns true if a line in a
323
// session output starts with the supplied string
324
func (s *PodmanSession) LineInOutputStartsWith(term string) bool {
325
for _, i := range s.OutputToStringArray() {
326
if strings.HasPrefix(i, term) {
333
// LineInOutputContains returns true if a line in a
334
// session output contains the supplied string
335
func (s *PodmanSession) LineInOutputContains(term string) bool {
336
for _, i := range s.OutputToStringArray() {
337
if strings.Contains(i, term) {
344
// LineInOutputContainsTag returns true if a line in the
345
// session's output contains the repo-tag pair as returned
346
// by podman-images(1).
347
func (s *PodmanSession) LineInOutputContainsTag(repo, tag string) bool {
348
tagMap := tagOutputToMap(s.OutputToStringArray())
349
return tagMap[repo][tag]
352
// IsJSONOutputValid attempts to unmarshal the session buffer
353
// and if successful, returns true, else false
354
func (s *PodmanSession) IsJSONOutputValid() bool {
356
if err := json.Unmarshal(s.Out.Contents(), &i); err != nil {
357
GinkgoWriter.Println(err)
363
// WaitWithDefaultTimeout waits for process finished with DefaultWaitTimeout
364
func (s *PodmanSession) WaitWithDefaultTimeout() {
365
s.WaitWithTimeout(DefaultWaitTimeout)
368
// WaitWithTimeout waits for process finished with DefaultWaitTimeout
369
func (s *PodmanSession) WaitWithTimeout(timeout int) {
370
Eventually(s, timeout).Should(Exit(), func() string {
371
// in case of timeouts show output
372
return fmt.Sprintf("command timed out after %ds: %v\nSTDOUT: %s\nSTDERR: %s",
373
timeout, s.Command.Args, string(s.Out.Contents()), string(s.Err.Contents()))
379
// CreateTempDirInTempDir create a temp dir with prefix podman_test
380
func CreateTempDirInTempDir() (string, error) {
381
return os.MkdirTemp("", "podman_test")
384
// SystemExec is used to exec a system command to check its exit code or output
385
func SystemExec(command string, args []string) *PodmanSession {
386
c := exec.Command(command, args...)
387
GinkgoWriter.Println("Execing " + c.String() + "\n")
388
session, err := Start(c, GinkgoWriter, GinkgoWriter)
390
Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))
392
session.Wait(DefaultWaitTimeout)
393
return &PodmanSession{session}
396
// StartSystemExec is used to start exec a system command
397
func StartSystemExec(command string, args []string) *PodmanSession {
398
c := exec.Command(command, args...)
399
GinkgoWriter.Println("Execing " + c.String() + "\n")
400
session, err := Start(c, GinkgoWriter, GinkgoWriter)
402
Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))
404
return &PodmanSession{session}
407
// tagOutPutToMap parses each string in imagesOutput and returns
408
// a map whose key is a repo, and value is another map whose keys
409
// are the tags found for that repo. Notice, the first array item will
410
// be skipped as it's considered to be the header.
411
func tagOutputToMap(imagesOutput []string) map[string]map[string]bool {
412
m := make(map[string]map[string]bool)
413
// iterate over output but skip the header
414
for _, i := range imagesOutput[1:] {
416
for _, x := range strings.Split(i, " ") {
421
// podman-images(1) return a list like output
422
// in the format of "Repository Tag [...]"
426
if m[tmp[0]] == nil {
427
m[tmp[0]] = map[string]bool{}
429
m[tmp[0]][tmp[1]] = true
434
// GetHostDistributionInfo returns a struct with its distribution Name and version
435
func GetHostDistributionInfo() HostOS {
436
f, err := os.Open(OSReleasePath)
442
l := bufio.NewScanner(f)
444
host.Arch = runtime.GOARCH
446
if strings.HasPrefix(l.Text(), "ID=") {
447
host.Distribution = strings.ReplaceAll(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "")
449
if strings.HasPrefix(l.Text(), "VERSION_ID=") {
450
host.Version = strings.ReplaceAll(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "")
456
// IsKernelNewerThan compares the current kernel version to one provided. If
457
// the kernel is equal to or greater, returns true
458
func IsKernelNewerThan(version string) (bool, error) {
459
inputVersion, err := kernel.ParseRelease(version)
463
kv, err := kernel.GetKernelVersion()
468
// CompareKernelVersion compares two kernel.VersionInfo structs.
469
// Returns -1 if a < b, 0 if a == b, 1 it a > b
470
result := kernel.CompareKernelVersion(*kv, *inputVersion)
477
// IsCommandAvailable check if command exist
478
func IsCommandAvailable(command string) bool {
479
check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " "))
484
// WriteJSONFile write json format data to a json file
485
func WriteJSONFile(data []byte, filePath string) error {
486
var jsonData map[string]interface{}
487
if err := json.Unmarshal(data, &jsonData); err != nil {
490
formatJSON, err := json.MarshalIndent(jsonData, "", " ")
494
return os.WriteFile(filePath, formatJSON, 0644)
497
// Containerized check the podman command run inside container
498
func Containerized() bool {
499
container := os.Getenv("container")
503
b, err := os.ReadFile(ProcessOneCgroupPath)
505
// shrug, if we cannot read that file, return false
508
return strings.Contains(string(b), "docker")
511
var randomLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
513
// RandomString returns a string of given length composed of random characters
514
func RandomString(n int) string {
517
b[i] = randomLetters[rand.Intn(len(randomLetters))]
522
// Encode *rsa.PublicKey and store it in a file.
523
// Adds appropriate extension to the fileName, and returns the complete fileName of
524
// the file storing the public key.
525
func savePublicKey(fileName string, publicKey *rsa.PublicKey) (string, error) {
526
// Encode public key to PKIX, ASN.1 DER form
527
pubBytes, err := x509.MarshalPKIXPublicKey(publicKey)
532
pubPEM := pem.EncodeToMemory(
534
Type: "RSA PUBLIC KEY",
539
// Write public key to file
540
publicKeyFileName := fileName + ".rsa.pub"
541
if err := os.WriteFile(publicKeyFileName, pubPEM, 0600); err != nil {
545
return publicKeyFileName, nil
548
// Encode *rsa.PrivateKey and store it in a file.
549
// Adds appropriate extension to the fileName, and returns the complete fileName of
550
// the file storing the private key.
551
func savePrivateKey(fileName string, privateKey *rsa.PrivateKey) (string, error) {
552
// Encode private key to PKCS#1, ASN.1 DER form
553
privBytes := x509.MarshalPKCS1PrivateKey(privateKey)
554
keyPEM := pem.EncodeToMemory(
556
Type: "RSA PRIVATE KEY",
561
// Write private key to file
562
privateKeyFileName := fileName + ".rsa"
563
if err := os.WriteFile(privateKeyFileName, keyPEM, 0600); err != nil {
567
return privateKeyFileName, nil
570
// Generate RSA key pair of specified bit size and write them to files.
571
// Adds appropriate extension to the fileName, and returns the complete fileName of
572
// the files storing the public and private key respectively.
573
func WriteRSAKeyPair(fileName string, bitSize int) (string, string, error) {
575
privateKey, err := rsa.GenerateKey(crypto_rand.Reader, bitSize)
580
publicKey := privateKey.Public().(*rsa.PublicKey)
582
publicKeyFileName, err := savePublicKey(fileName, publicKey)
587
privateKeyFileName, err := savePrivateKey(fileName, privateKey)
592
return publicKeyFileName, privateKeyFileName, nil