github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/test/utils/utils.go (about)

     1  package utils
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"runtime"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/containers/storage/pkg/parsers/kernel"
    15  	. "github.com/onsi/ginkgo"
    16  	. "github.com/onsi/gomega"
    17  	. "github.com/onsi/gomega/gexec"
    18  )
    19  
    20  var (
    21  	defaultWaitTimeout   = 90
    22  	OSReleasePath        = "/etc/os-release"
    23  	ProcessOneCgroupPath = "/proc/1/cgroup"
    24  )
    25  
    26  // PodmanTestCommon contains common functions will be updated later in
    27  // the inheritance structs
    28  type PodmanTestCommon interface {
    29  	MakeOptions(args []string, noEvents, noCache bool) []string
    30  	WaitForContainer() bool
    31  	WaitContainerReady(id string, expStr string, timeout int, step int) bool
    32  }
    33  
    34  // PodmanTest struct for command line options
    35  type PodmanTest struct {
    36  	PodmanMakeOptions  func(args []string, noEvents, noCache bool) []string
    37  	PodmanBinary       string
    38  	ArtifactPath       string
    39  	TempDir            string
    40  	RemoteTest         bool
    41  	RemotePodmanBinary string
    42  	RemoteSession      *os.Process
    43  	RemoteSocket       string
    44  	RemoteCommand      *exec.Cmd
    45  	ImageCacheDir      string
    46  	ImageCacheFS       string
    47  }
    48  
    49  // PodmanSession wraps the gexec.session so we can extend it
    50  type PodmanSession struct {
    51  	*Session
    52  }
    53  
    54  // HostOS is a simple struct for the test os
    55  type HostOS struct {
    56  	Distribution string
    57  	Version      string
    58  	Arch         string
    59  }
    60  
    61  // MakeOptions assembles all podman options
    62  func (p *PodmanTest) MakeOptions(args []string, noEvents, noCache bool) []string {
    63  	return p.PodmanMakeOptions(args, noEvents, noCache)
    64  }
    65  
    66  // PodmanAsUserBase exec podman as user. uid and gid is set for credentials usage. env is used
    67  // to record the env for debugging
    68  func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string, env []string, noEvents, noCache bool, extraFiles []*os.File) *PodmanSession {
    69  	var command *exec.Cmd
    70  	podmanOptions := p.MakeOptions(args, noEvents, noCache)
    71  	podmanBinary := p.PodmanBinary
    72  	if p.RemoteTest {
    73  		podmanBinary = p.RemotePodmanBinary
    74  	}
    75  	if p.RemoteTest {
    76  		podmanOptions = append([]string{"--remote", "--url", p.RemoteSocket}, podmanOptions...)
    77  	}
    78  	if env == nil {
    79  		fmt.Printf("Running: %s %s\n", podmanBinary, strings.Join(podmanOptions, " "))
    80  	} else {
    81  		fmt.Printf("Running: (env: %v) %s %s\n", env, podmanBinary, strings.Join(podmanOptions, " "))
    82  	}
    83  	if uid != 0 || gid != 0 {
    84  		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)
    85  		nsEnterOpts := append([]string{"-c", pythonCmd, podmanBinary}, podmanOptions...)
    86  		command = exec.Command("python", nsEnterOpts...)
    87  	} else {
    88  		command = exec.Command(podmanBinary, podmanOptions...)
    89  	}
    90  	if env != nil {
    91  		command.Env = env
    92  	}
    93  	if cwd != "" {
    94  		command.Dir = cwd
    95  	}
    96  
    97  	command.ExtraFiles = extraFiles
    98  
    99  	session, err := Start(command, GinkgoWriter, GinkgoWriter)
   100  	if err != nil {
   101  		Fail(fmt.Sprintf("unable to run podman command: %s\n%v", strings.Join(podmanOptions, " "), err))
   102  	}
   103  	return &PodmanSession{session}
   104  }
   105  
   106  // PodmanBase exec podman with default env.
   107  func (p *PodmanTest) PodmanBase(args []string, noEvents, noCache bool) *PodmanSession {
   108  	return p.PodmanAsUserBase(args, 0, 0, "", nil, noEvents, noCache, nil)
   109  }
   110  
   111  // WaitForContainer waits on a started container
   112  func (p *PodmanTest) WaitForContainer() bool {
   113  	for i := 0; i < 10; i++ {
   114  		if p.NumberOfContainersRunning() > 0 {
   115  			return true
   116  		}
   117  		time.Sleep(1 * time.Second)
   118  	}
   119  	return false
   120  }
   121  
   122  // NumberOfContainersRunning returns an int of how many
   123  // containers are currently running.
   124  func (p *PodmanTest) NumberOfContainersRunning() int {
   125  	var containers []string
   126  	ps := p.PodmanBase([]string{"ps", "-q"}, false, true)
   127  	ps.WaitWithDefaultTimeout()
   128  	Expect(ps).Should(Exit(0))
   129  	for _, i := range ps.OutputToStringArray() {
   130  		if i != "" {
   131  			containers = append(containers, i)
   132  		}
   133  	}
   134  	return len(containers)
   135  }
   136  
   137  // NumberOfContainers returns an int of how many
   138  // containers are currently defined.
   139  func (p *PodmanTest) NumberOfContainers() int {
   140  	var containers []string
   141  	ps := p.PodmanBase([]string{"ps", "-aq"}, false, true)
   142  	ps.WaitWithDefaultTimeout()
   143  	Expect(ps.ExitCode()).To(Equal(0))
   144  	for _, i := range ps.OutputToStringArray() {
   145  		if i != "" {
   146  			containers = append(containers, i)
   147  		}
   148  	}
   149  	return len(containers)
   150  }
   151  
   152  // NumberOfPods returns an int of how many
   153  // pods are currently defined.
   154  func (p *PodmanTest) NumberOfPods() int {
   155  	var pods []string
   156  	ps := p.PodmanBase([]string{"pod", "ps", "-q"}, false, true)
   157  	ps.WaitWithDefaultTimeout()
   158  	Expect(ps.ExitCode()).To(Equal(0))
   159  	for _, i := range ps.OutputToStringArray() {
   160  		if i != "" {
   161  			pods = append(pods, i)
   162  		}
   163  	}
   164  	return len(pods)
   165  }
   166  
   167  // GetContainerStatus returns the containers state.
   168  // This function assumes only one container is active.
   169  func (p *PodmanTest) GetContainerStatus() string {
   170  	var podmanArgs = []string{"ps"}
   171  	podmanArgs = append(podmanArgs, "--all", "--format={{.Status}}")
   172  	session := p.PodmanBase(podmanArgs, false, true)
   173  	session.WaitWithDefaultTimeout()
   174  	return session.OutputToString()
   175  }
   176  
   177  // WaitContainerReady waits process or service inside container start, and ready to be used.
   178  func (p *PodmanTest) WaitContainerReady(id string, expStr string, timeout int, step int) bool {
   179  	startTime := time.Now()
   180  	s := p.PodmanBase([]string{"logs", id}, false, true)
   181  	s.WaitWithDefaultTimeout()
   182  
   183  	for {
   184  		if time.Since(startTime) >= time.Duration(timeout)*time.Second {
   185  			fmt.Printf("Container %s is not ready in %ds", id, timeout)
   186  			return false
   187  		}
   188  
   189  		if strings.Contains(s.OutputToString(), expStr) {
   190  			return true
   191  		}
   192  		time.Sleep(time.Duration(step) * time.Second)
   193  		s = p.PodmanBase([]string{"logs", id}, false, true)
   194  		s.WaitWithDefaultTimeout()
   195  	}
   196  }
   197  
   198  // WaitForContainer is a wrapper function for accept inheritance PodmanTest struct.
   199  func WaitForContainer(p PodmanTestCommon) bool {
   200  	return p.WaitForContainer()
   201  }
   202  
   203  // WaitForContainerReady is a wrapper function for accept inheritance PodmanTest struct.
   204  func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout int, step int) bool {
   205  	return p.WaitContainerReady(id, expStr, timeout, step)
   206  }
   207  
   208  // OutputToString formats session output to string
   209  func (s *PodmanSession) OutputToString() string {
   210  	if s == nil || s.Out == nil || s.Out.Contents() == nil {
   211  		return ""
   212  	}
   213  
   214  	fields := strings.Fields(string(s.Out.Contents()))
   215  	return strings.Join(fields, " ")
   216  }
   217  
   218  // OutputToStringArray returns the output as a []string
   219  // where each array item is a line split by newline
   220  func (s *PodmanSession) OutputToStringArray() []string {
   221  	var results []string
   222  	output := string(s.Out.Contents())
   223  	for _, line := range strings.Split(output, "\n") {
   224  		if line != "" {
   225  			results = append(results, line)
   226  		}
   227  	}
   228  	return results
   229  }
   230  
   231  // ErrorToString formats session stderr to string
   232  func (s *PodmanSession) ErrorToString() string {
   233  	fields := strings.Fields(string(s.Err.Contents()))
   234  	return strings.Join(fields, " ")
   235  }
   236  
   237  // ErrorToStringArray returns the stderr output as a []string
   238  // where each array item is a line split by newline
   239  func (s *PodmanSession) ErrorToStringArray() []string {
   240  	output := string(s.Err.Contents())
   241  	return strings.Split(output, "\n")
   242  }
   243  
   244  // GrepString takes session output and behaves like grep. it returns a bool
   245  // if successful and an array of strings on positive matches
   246  func (s *PodmanSession) GrepString(term string) (bool, []string) {
   247  	var (
   248  		greps   []string
   249  		matches bool
   250  	)
   251  
   252  	for _, line := range s.OutputToStringArray() {
   253  		if strings.Contains(line, term) {
   254  			matches = true
   255  			greps = append(greps, line)
   256  		}
   257  	}
   258  	return matches, greps
   259  }
   260  
   261  // ErrorGrepString takes session stderr output and behaves like grep. it returns a bool
   262  // if successful and an array of strings on positive matches
   263  func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) {
   264  	var (
   265  		greps   []string
   266  		matches bool
   267  	)
   268  
   269  	for _, line := range s.ErrorToStringArray() {
   270  		if strings.Contains(line, term) {
   271  			matches = true
   272  			greps = append(greps, line)
   273  		}
   274  	}
   275  	return matches, greps
   276  }
   277  
   278  // LineInOutputStartsWith returns true if a line in a
   279  // session output starts with the supplied string
   280  func (s *PodmanSession) LineInOutputStartsWith(term string) bool {
   281  	for _, i := range s.OutputToStringArray() {
   282  		if strings.HasPrefix(i, term) {
   283  			return true
   284  		}
   285  	}
   286  	return false
   287  }
   288  
   289  // LineInOutputContains returns true if a line in a
   290  // session output contains the supplied string
   291  func (s *PodmanSession) LineInOutputContains(term string) bool {
   292  	for _, i := range s.OutputToStringArray() {
   293  		if strings.Contains(i, term) {
   294  			return true
   295  		}
   296  	}
   297  	return false
   298  }
   299  
   300  // LineInOutputContainsTag returns true if a line in the
   301  // session's output contains the repo-tag pair as returned
   302  // by podman-images(1).
   303  func (s *PodmanSession) LineInOutputContainsTag(repo, tag string) bool {
   304  	tagMap := tagOutputToMap(s.OutputToStringArray())
   305  	return tagMap[repo][tag]
   306  }
   307  
   308  // IsJSONOutputValid attempts to unmarshal the session buffer
   309  // and if successful, returns true, else false
   310  func (s *PodmanSession) IsJSONOutputValid() bool {
   311  	var i interface{}
   312  	if err := json.Unmarshal(s.Out.Contents(), &i); err != nil {
   313  		fmt.Println(err)
   314  		return false
   315  	}
   316  	return true
   317  }
   318  
   319  // WaitWithDefaultTimeout waits for process finished with defaultWaitTimeout
   320  func (s *PodmanSession) WaitWithDefaultTimeout() {
   321  	Eventually(s, defaultWaitTimeout).Should(Exit())
   322  	os.Stdout.Sync()
   323  	os.Stderr.Sync()
   324  	fmt.Println("output:", s.OutputToString())
   325  }
   326  
   327  // CreateTempDirinTempDir create a temp dir with prefix podman_test
   328  func CreateTempDirInTempDir() (string, error) {
   329  	return ioutil.TempDir("", "podman_test")
   330  }
   331  
   332  // SystemExec is used to exec a system command to check its exit code or output
   333  func SystemExec(command string, args []string) *PodmanSession {
   334  	c := exec.Command(command, args...)
   335  	session, err := Start(c, GinkgoWriter, GinkgoWriter)
   336  	if err != nil {
   337  		Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))
   338  	}
   339  	session.Wait(defaultWaitTimeout)
   340  	return &PodmanSession{session}
   341  }
   342  
   343  // StartSystemExec is used to start exec a system command
   344  func StartSystemExec(command string, args []string) *PodmanSession {
   345  	c := exec.Command(command, args...)
   346  	session, err := Start(c, GinkgoWriter, GinkgoWriter)
   347  	if err != nil {
   348  		Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " ")))
   349  	}
   350  	return &PodmanSession{session}
   351  }
   352  
   353  // StringInSlice determines if a string is in a string slice, returns bool
   354  func StringInSlice(s string, sl []string) bool {
   355  	for _, i := range sl {
   356  		if i == s {
   357  			return true
   358  		}
   359  	}
   360  	return false
   361  }
   362  
   363  // tagOutPutToMap parses each string in imagesOutput and returns
   364  // a map whose key is a repo, and value is another map whose keys
   365  // are the tags found for that repo. Notice, the first array item will
   366  // be skipped as it's considered to be the header.
   367  func tagOutputToMap(imagesOutput []string) map[string]map[string]bool {
   368  	m := make(map[string]map[string]bool)
   369  	// iterate over output but skip the header
   370  	for _, i := range imagesOutput[1:] {
   371  		tmp := []string{}
   372  		for _, x := range strings.Split(i, " ") {
   373  			if x != "" {
   374  				tmp = append(tmp, x)
   375  			}
   376  		}
   377  		// podman-images(1) return a list like output
   378  		// in the format of "Repository Tag [...]"
   379  		if len(tmp) < 2 {
   380  			continue
   381  		}
   382  		if m[tmp[0]] == nil {
   383  			m[tmp[0]] = map[string]bool{}
   384  		}
   385  		m[tmp[0]][tmp[1]] = true
   386  	}
   387  	return m
   388  }
   389  
   390  // GetHostDistributionInfo returns a struct with its distribution name and version
   391  func GetHostDistributionInfo() HostOS {
   392  	f, err := os.Open(OSReleasePath)
   393  	defer f.Close()
   394  	if err != nil {
   395  		return HostOS{}
   396  	}
   397  
   398  	l := bufio.NewScanner(f)
   399  	host := HostOS{}
   400  	host.Arch = runtime.GOARCH
   401  	for l.Scan() {
   402  		if strings.HasPrefix(l.Text(), "ID=") {
   403  			host.Distribution = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1)
   404  		}
   405  		if strings.HasPrefix(l.Text(), "VERSION_ID=") {
   406  			host.Version = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1)
   407  		}
   408  	}
   409  	return host
   410  }
   411  
   412  // IsKernelNewerThan compares the current kernel version to one provided.  If
   413  // the kernel is equal to or greater, returns true
   414  func IsKernelNewerThan(version string) (bool, error) {
   415  	inputVersion, err := kernel.ParseRelease(version)
   416  	if err != nil {
   417  		return false, err
   418  	}
   419  	kv, err := kernel.GetKernelVersion()
   420  	if err != nil {
   421  		return false, err
   422  	}
   423  
   424  	// CompareKernelVersion compares two kernel.VersionInfo structs.
   425  	// Returns -1 if a < b, 0 if a == b, 1 it a > b
   426  	result := kernel.CompareKernelVersion(*kv, *inputVersion)
   427  	if result >= 0 {
   428  		return true, nil
   429  	}
   430  	return false, nil
   431  
   432  }
   433  
   434  // IsCommandAvaible check if command exist
   435  func IsCommandAvailable(command string) bool {
   436  	check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " "))
   437  	err := check.Run()
   438  	if err != nil {
   439  		return false
   440  	}
   441  	return true
   442  }
   443  
   444  // WriteJsonFile write json format data to a json file
   445  func WriteJsonFile(data []byte, filePath string) error {
   446  	var jsonData map[string]interface{}
   447  	json.Unmarshal(data, &jsonData)
   448  	formatJson, _ := json.MarshalIndent(jsonData, "", "	")
   449  	return ioutil.WriteFile(filePath, formatJson, 0644)
   450  }
   451  
   452  // Containerized check the podman command run inside container
   453  func Containerized() bool {
   454  	container := os.Getenv("container")
   455  	if container != "" {
   456  		return true
   457  	}
   458  	b, err := ioutil.ReadFile(ProcessOneCgroupPath)
   459  	if err != nil {
   460  		// shrug, if we cannot read that file, return false
   461  		return false
   462  	}
   463  	if strings.Index(string(b), "docker") > -1 {
   464  		return true
   465  	}
   466  	return false
   467  }