podman

Форк
0
/
utils.go 
593 строки · 17.1 Кб
1
package utils
2

3
import (
4
	"bufio"
5
	"encoding/json"
6
	"fmt"
7
	"math/rand"
8
	"os"
9
	"os/exec"
10
	"runtime"
11
	"strings"
12
	"time"
13

14
	crypto_rand "crypto/rand"
15
	"crypto/rsa"
16
	"crypto/x509"
17
	"encoding/pem"
18

19
	"github.com/sirupsen/logrus"
20

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
25
)
26

27
type NetworkBackend int
28

29
const (
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"
36
)
37

38
func (n NetworkBackend) ToString() string {
39
	switch n {
40
	case CNI:
41
		return "cni"
42
	case Netavark:
43
		return "netavark"
44
	}
45
	logrus.Errorf("unknown network backend: %q", n)
46
	return ""
47
}
48

49
var (
50
	DefaultWaitTimeout   = 90
51
	OSReleasePath        = "/etc/os-release"
52
	ProcessOneCgroupPath = "/proc/1/cgroup"
53
)
54

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
61
}
62

63
// PodmanTest struct for command line options
64
type PodmanTest struct {
65
	ImageCacheDir      string
66
	ImageCacheFS       string
67
	NetworkBackend     NetworkBackend
68
	DatabaseBackend    string
69
	PodmanBinary       string
70
	PodmanMakeOptions  func(args []string, noEvents, noCache bool) []string
71
	RemoteCommand      *exec.Cmd
72
	RemotePodmanBinary string
73
	RemoteSession      *os.Process
74
	RemoteSocket       string
75
	RemoteSocketLock   string // If not "", should be removed _after_ RemoteSocket is removed
76
	RemoteTest         bool
77
	TempDir            string
78
}
79

80
// PodmanSession wraps the gexec.session so we can extend it
81
type PodmanSession struct {
82
	*Session
83
}
84

85
// HostOS is a simple struct for the test os
86
type HostOS struct {
87
	Distribution string
88
	Version      string
89
	Arch         string
90
}
91

92
// MakeOptions assembles all podman options
93
func (p *PodmanTest) MakeOptions(args []string, noEvents, noCache bool) []string {
94
	return p.PodmanMakeOptions(args, noEvents, noCache)
95
}
96

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
103
	if p.RemoteTest {
104
		podmanBinary = p.RemotePodmanBinary
105
	}
106

107
	if timeDir := os.Getenv(EnvTimeDir); timeDir != "" {
108
		timeFile, err := os.CreateTemp(timeDir, ".time")
109
		if err != nil {
110
			Fail(fmt.Sprintf("Error creating time file: %v", err))
111
		}
112
		timeArgs := []string{"-f", "%M", "-o", timeFile.Name()}
113
		timeCmd := append([]string{"/usr/bin/time"}, timeArgs...)
114
		wrapper = append(timeCmd, wrapper...)
115
	}
116
	runCmd := wrapper
117
	runCmd = append(runCmd, podmanBinary)
118

119
	if env == nil {
120
		GinkgoWriter.Printf("Running: %s %s\n", strings.Join(runCmd, " "), strings.Join(podmanOptions, " "))
121
	} else {
122
		GinkgoWriter.Printf("Running: (env: %v) %s %s\n", env, strings.Join(runCmd, " "), strings.Join(podmanOptions, " "))
123
	}
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...)
129
	} else {
130
		runCmd = append(runCmd, podmanOptions...)
131
		command = exec.Command(runCmd[0], runCmd[1:]...)
132
	}
133
	if env != nil {
134
		command.Env = env
135
	}
136
	if cwd != "" {
137
		command.Dir = cwd
138
	}
139

140
	command.ExtraFiles = extraFiles
141

142
	session, err := Start(command, GinkgoWriter, GinkgoWriter)
143
	if err != nil {
144
		Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err))
145
	}
146
	return &PodmanSession{session}
147
}
148

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)
152
}
153

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 {
158
			return true
159
		}
160
		time.Sleep(1 * time.Second)
161
	}
162
	GinkgoWriter.Printf("WaitForContainer(): timed out\n")
163
	return false
164
}
165

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() {
174
		if i != "" {
175
			containers = append(containers, i)
176
		}
177
	}
178
	return len(containers)
179
}
180

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() {
189
		if i != "" {
190
			containers = append(containers, i)
191
		}
192
	}
193
	return len(containers)
194
}
195

196
// NumberOfPods returns an int of how many
197
// pods are currently defined.
198
func (p *PodmanTest) NumberOfPods() int {
199
	var pods []string
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() {
204
		if i != "" {
205
			pods = append(pods, i)
206
		}
207
	}
208
	return len(pods)
209
}
210

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()
219
}
220

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()
226

227
	for {
228
		if strings.Contains(s.OutputToString(), expStr) || strings.Contains(s.ErrorToString(), expStr) {
229
			return true
230
		}
231

232
		if time.Since(startTime) >= time.Duration(timeout)*time.Second {
233
			GinkgoWriter.Printf("Container %s is not ready in %ds", id, timeout)
234
			return false
235
		}
236
		time.Sleep(time.Duration(step) * time.Second)
237
		s = p.PodmanBase([]string{"logs", id}, false, true)
238
		s.WaitWithDefaultTimeout()
239
	}
240
}
241

242
// WaitForContainer is a wrapper function for accept inheritance PodmanTest struct.
243
func WaitForContainer(p PodmanTestCommon) bool {
244
	return p.WaitForContainer()
245
}
246

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)
250
}
251

252
// OutputToString formats session output to string
253
func (s *PodmanSession) OutputToString() string {
254
	if s == nil || s.Out == nil || s.Out.Contents() == nil {
255
		return ""
256
	}
257

258
	fields := strings.Fields(string(s.Out.Contents()))
259
	return strings.Join(fields, " ")
260
}
261

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 {
265
	var results []string
266
	output := string(s.Out.Contents())
267
	for _, line := range strings.Split(output, "\n") {
268
		if line != "" {
269
			results = append(results, line)
270
		}
271
	}
272
	return results
273
}
274

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, " ")
279
}
280

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")
286
}
287

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) {
291
	var (
292
		greps   []string
293
		matches bool
294
	)
295

296
	for _, line := range s.OutputToStringArray() {
297
		if strings.Contains(line, term) {
298
			matches = true
299
			greps = append(greps, line)
300
		}
301
	}
302
	return matches, greps
303
}
304

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) {
308
	var (
309
		greps   []string
310
		matches bool
311
	)
312

313
	for _, line := range s.ErrorToStringArray() {
314
		if strings.Contains(line, term) {
315
			matches = true
316
			greps = append(greps, line)
317
		}
318
	}
319
	return matches, greps
320
}
321

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) {
327
			return true
328
		}
329
	}
330
	return false
331
}
332

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) {
338
			return true
339
		}
340
	}
341
	return false
342
}
343

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]
350
}
351

352
// IsJSONOutputValid attempts to unmarshal the session buffer
353
// and if successful, returns true, else false
354
func (s *PodmanSession) IsJSONOutputValid() bool {
355
	var i interface{}
356
	if err := json.Unmarshal(s.Out.Contents(), &i); err != nil {
357
		GinkgoWriter.Println(err)
358
		return false
359
	}
360
	return true
361
}
362

363
// WaitWithDefaultTimeout waits for process finished with DefaultWaitTimeout
364
func (s *PodmanSession) WaitWithDefaultTimeout() {
365
	s.WaitWithTimeout(DefaultWaitTimeout)
366
}
367

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()))
374
	})
375
	os.Stdout.Sync()
376
	os.Stderr.Sync()
377
}
378

379
// CreateTempDirInTempDir create a temp dir with prefix podman_test
380
func CreateTempDirInTempDir() (string, error) {
381
	return os.MkdirTemp("", "podman_test")
382
}
383

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)
389
	if err != nil {
390
		Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))
391
	}
392
	session.Wait(DefaultWaitTimeout)
393
	return &PodmanSession{session}
394
}
395

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)
401
	if err != nil {
402
		Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))
403
	}
404
	return &PodmanSession{session}
405
}
406

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:] {
415
		tmp := []string{}
416
		for _, x := range strings.Split(i, " ") {
417
			if x != "" {
418
				tmp = append(tmp, x)
419
			}
420
		}
421
		// podman-images(1) return a list like output
422
		// in the format of "Repository Tag [...]"
423
		if len(tmp) < 2 {
424
			continue
425
		}
426
		if m[tmp[0]] == nil {
427
			m[tmp[0]] = map[string]bool{}
428
		}
429
		m[tmp[0]][tmp[1]] = true
430
	}
431
	return m
432
}
433

434
// GetHostDistributionInfo returns a struct with its distribution Name and version
435
func GetHostDistributionInfo() HostOS {
436
	f, err := os.Open(OSReleasePath)
437
	if err != nil {
438
		return HostOS{}
439
	}
440
	defer f.Close()
441

442
	l := bufio.NewScanner(f)
443
	host := HostOS{}
444
	host.Arch = runtime.GOARCH
445
	for l.Scan() {
446
		if strings.HasPrefix(l.Text(), "ID=") {
447
			host.Distribution = strings.ReplaceAll(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "")
448
		}
449
		if strings.HasPrefix(l.Text(), "VERSION_ID=") {
450
			host.Version = strings.ReplaceAll(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "")
451
		}
452
	}
453
	return host
454
}
455

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)
460
	if err != nil {
461
		return false, err
462
	}
463
	kv, err := kernel.GetKernelVersion()
464
	if err != nil {
465
		return false, err
466
	}
467

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)
471
	if result >= 0 {
472
		return true, nil
473
	}
474
	return false, nil
475
}
476

477
// IsCommandAvailable check if command exist
478
func IsCommandAvailable(command string) bool {
479
	check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " "))
480
	err := check.Run()
481
	return err == nil
482
}
483

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 {
488
		return err
489
	}
490
	formatJSON, err := json.MarshalIndent(jsonData, "", "	")
491
	if err != nil {
492
		return err
493
	}
494
	return os.WriteFile(filePath, formatJSON, 0644)
495
}
496

497
// Containerized check the podman command run inside container
498
func Containerized() bool {
499
	container := os.Getenv("container")
500
	if container != "" {
501
		return true
502
	}
503
	b, err := os.ReadFile(ProcessOneCgroupPath)
504
	if err != nil {
505
		// shrug, if we cannot read that file, return false
506
		return false
507
	}
508
	return strings.Contains(string(b), "docker")
509
}
510

511
var randomLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
512

513
// RandomString returns a string of given length composed of random characters
514
func RandomString(n int) string {
515
	b := make([]rune, n)
516
	for i := range b {
517
		b[i] = randomLetters[rand.Intn(len(randomLetters))]
518
	}
519
	return string(b)
520
}
521

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)
528
	if err != nil {
529
		return "", err
530
	}
531

532
	pubPEM := pem.EncodeToMemory(
533
		&pem.Block{
534
			Type:  "RSA PUBLIC KEY",
535
			Bytes: pubBytes,
536
		},
537
	)
538

539
	// Write public key to file
540
	publicKeyFileName := fileName + ".rsa.pub"
541
	if err := os.WriteFile(publicKeyFileName, pubPEM, 0600); err != nil {
542
		return "", err
543
	}
544

545
	return publicKeyFileName, nil
546
}
547

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(
555
		&pem.Block{
556
			Type:  "RSA PRIVATE KEY",
557
			Bytes: privBytes,
558
		},
559
	)
560

561
	// Write private key to file
562
	privateKeyFileName := fileName + ".rsa"
563
	if err := os.WriteFile(privateKeyFileName, keyPEM, 0600); err != nil {
564
		return "", err
565
	}
566

567
	return privateKeyFileName, nil
568
}
569

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) {
574
	// Generate RSA key
575
	privateKey, err := rsa.GenerateKey(crypto_rand.Reader, bitSize)
576
	if err != nil {
577
		return "", "", err
578
	}
579

580
	publicKey := privateKey.Public().(*rsa.PublicKey)
581

582
	publicKeyFileName, err := savePublicKey(fileName, publicKey)
583
	if err != nil {
584
		return "", "", err
585
	}
586

587
	privateKeyFileName, err := savePrivateKey(fileName, privateKey)
588
	if err != nil {
589
		return "", "", err
590
	}
591

592
	return publicKeyFileName, privateKeyFileName, nil
593
}
594

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.