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 }