run-tests-container.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. #!/usr/bin/env -S python -u
  2. import argparse
  3. import os
  4. import subprocess as sp
  5. import time
  6. ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
  7. TIMESTAMP = int(time.time())
  8. def _container_image_exist(container_name, container_type):
  9. cmd = [
  10. "podman",
  11. "image",
  12. "exists",
  13. containers[container_name][container_type],
  14. ]
  15. return _call_command(cmd)
  16. # fmt: off
  17. def _build_container(container_name, container_type, result_path,
  18. container_volume=None, **kwargs):
  19. # fmt: on
  20. # kwargs can be used to pass '--build-arg'
  21. build_args = []
  22. #for arg in kwargs.values():
  23. for key, value in kwargs.items():
  24. build_args.append("--build-arg")
  25. build_args.append("{}={}".format(key, value))
  26. volume = []
  27. if container_volume:
  28. volume.append("-v")
  29. volume.append(container_volume)
  30. container_file = ""
  31. if container_type == "base":
  32. container_file = containers[container_name]["base"]
  33. container_name = container_file
  34. if container_type == "code":
  35. container_file = containers[container_name]["code"]
  36. container_name = container_file
  37. cmd = [
  38. "podman",
  39. "build",
  40. "--no-cache",
  41. "--rm",
  42. "-t",
  43. container_name,
  44. "-f",
  45. ROOT + "/dev/containers/%s" % container_file,
  46. ROOT + "/dev/containers",
  47. ]
  48. cmd += build_args
  49. cmd += volume
  50. logfile = "{}/{}_{}-build.log".format(
  51. result_path, TIMESTAMP, container_type
  52. )
  53. return _call_command(cmd, logfile)
  54. def _call_command(cmd, logfile=None):
  55. print("Command: " + " ".join(cmd))
  56. if logfile is None:
  57. rc = sp.call(cmd)
  58. else:
  59. # 'tee' like behavior, Kudos: falsetru
  60. # https://stackoverflow.com/a/31583238
  61. tee = sp.Popen(["tee", logfile], stdin=sp.PIPE)
  62. rc = sp.call(cmd, stdout=tee.stdin, stderr=sp.STDOUT)
  63. tee.stdin.close()
  64. if rc != 0:
  65. return False
  66. else:
  67. return True
  68. def _check_pre_reqs():
  69. programs = [
  70. {"name": "podman", "cmd": ["podman", "version"]},
  71. {"name": "git", "cmd": ["git", "version"]},
  72. ]
  73. # 'os.devnull' used for backward compatibility with Python2.
  74. # for Py3 only, 'sp.DEVNULL' can be used and this workaround removed.
  75. FNULL = open(os.devnull, "w")
  76. missing = []
  77. for program in programs:
  78. try:
  79. sp.call(program["cmd"], stdout=FNULL, stderr=sp.STDOUT)
  80. except OSError:
  81. missing.append(program["name"])
  82. if len(missing) > 0:
  83. print("Error! Required programs not found: " + ", ".join(missing))
  84. os._exit(1)
  85. def setup_parser():
  86. """Setup the cli arguments"""
  87. parser = argparse.ArgumentParser(prog="pagure-test")
  88. parser.add_argument(
  89. "test_case", nargs="?", default="", help="Run the given test case"
  90. )
  91. parser.add_argument(
  92. "--toxenv",
  93. default="",
  94. help="Pass a custom value for the target python environment to tox tests. "
  95. "Only used by the pip test container. "
  96. "Default: Use all Environments defined in tox.ini",
  97. )
  98. parser.add_argument(
  99. "--fedora",
  100. action="store_true",
  101. help="Run the tests in fedora environment (DEFAULT)",
  102. )
  103. parser.add_argument(
  104. "--centos",
  105. action="store_true",
  106. help="Run the tests in centos environment",
  107. )
  108. parser.add_argument(
  109. "--pip",
  110. action="store_true",
  111. help="Run the tests in a venv on a Fedora host",
  112. )
  113. parser.add_argument(
  114. "--rebuild",
  115. dest="rebuild",
  116. action="store_true",
  117. help="Enforce rebuild of container images",
  118. )
  119. parser.add_argument(
  120. "--rebuild-code",
  121. dest="rebuild_code",
  122. action="store_true",
  123. help="Enforce rebuild of code container images only",
  124. )
  125. parser.add_argument(
  126. "--shell",
  127. dest="shell",
  128. action="store_true",
  129. help="Gives you a shell into the container instead "
  130. "of running the tests",
  131. )
  132. parser.add_argument(
  133. "--repo",
  134. dest="repo",
  135. default="/wrkdir",
  136. help="URL or local path to git repository as source of the "
  137. "public repo to use as source, defaults to git repo in "
  138. "current directory, can also be overridden using the "
  139. "REPO environment variable",
  140. )
  141. parser.add_argument(
  142. "--branch",
  143. dest="branch",
  144. default="wrkdirbranch",
  145. help="Branch name to use as source, defaults to the active "
  146. "branch in current directory, can also be overridden by "
  147. "using the BRANCH environment variable",
  148. )
  149. return parser
  150. if __name__ == "__main__":
  151. _check_pre_reqs()
  152. parser = setup_parser()
  153. args = parser.parse_args()
  154. containers = {
  155. "centos": {
  156. "name": "pagure-tests-centos-stream9-rpms-py3",
  157. "base": "base-centos-stream9-rpms-py3",
  158. "code": "code-centos-stream9-rpms-py3",
  159. },
  160. "fedora": {
  161. "name": "pagure-tests-fedora-rpms-py3",
  162. "base": "base-fedora-rpms-py3",
  163. "code": "code-fedora-rpms-py3",
  164. },
  165. "pip": {
  166. "name": "pagure-tests-fedora-pip-py3",
  167. "base": "base-fedora-pip-py3",
  168. "code": "code-fedora-pip-py3",
  169. },
  170. }
  171. if args.centos:
  172. container_names = ["centos"]
  173. elif args.fedora:
  174. container_names = ["fedora"]
  175. elif args.pip:
  176. container_names = ["pip"]
  177. else:
  178. container_names = ["centos", "fedora", "pip"]
  179. mount_wrkdir = False
  180. # get full path of git repo in current directory
  181. # and set var to mount it into the container
  182. if args.repo == "/wrkdir":
  183. # 'git rev-parse --show-toplevel' via python, Kudos: Ryne Everett
  184. # https://stackoverflow.com/questions/22081209#comment44778829_22081487
  185. wrkdir_path = (
  186. sp.Popen(["git", "rev-parse", "--show-toplevel"], stdout=sp.PIPE)
  187. .communicate()[0]
  188. .rstrip()
  189. .decode("ascii")
  190. )
  191. mount_wrkdir = True
  192. # 'args.repo' will be set as path to mount it into the container and then
  193. # overridden with '/wrkdir' to leverage existing logic to use a local path
  194. elif "http://" not in args.repo and "https://" not in args.repo:
  195. wrkdir_path = args.repo
  196. args.repo = "/wrkdir"
  197. mount_wrkdir = True
  198. if args.branch == "wrkdirbranch":
  199. args.branch = (
  200. sp.Popen(["git", "branch", "--show-current"], stdout=sp.PIPE)
  201. .communicate()[0]
  202. .rstrip()
  203. .decode("ascii")
  204. )
  205. failed = []
  206. print("Running for %d containers:" % len(container_names))
  207. print(" - " + "\n - ".join(container_names))
  208. for container_name in container_names:
  209. result_path = "{}/results_{}".format(
  210. os.getcwd(), containers[container_name]["name"]
  211. )
  212. if not os.path.exists(result_path):
  213. os.mkdir(result_path)
  214. print("\n------ Building Container Image -----")
  215. if not _container_image_exist(container_name, "base") or args.rebuild:
  216. print(
  217. "Container does not exist, building: %s"
  218. % containers[container_name]["base"]
  219. )
  220. container_volume = None
  221. if mount_wrkdir:
  222. container_volume = "{}:/wrkdir:z,ro".format(wrkdir_path)
  223. if _build_container(
  224. container_name,
  225. "base",
  226. result_path,
  227. container_volume,
  228. branch="{}".format(os.environ.get("BRANCH") or args.branch),
  229. repo="{}".format(os.environ.get("REPO") or args.repo),
  230. ):
  231. base_build = True
  232. else:
  233. print(
  234. "Failed building: %s" % containers[container_name]["base"]
  235. )
  236. break
  237. else:
  238. base_build = False
  239. print(
  240. "Container already exist, skipped building: %s"
  241. % containers[container_name]["base"]
  242. )
  243. if (
  244. not _container_image_exist(container_name, "code")
  245. or base_build
  246. or args.rebuild
  247. or args.rebuild_code
  248. ):
  249. print(
  250. "Container does not exist, building: %s"
  251. % containers[container_name]["code"]
  252. )
  253. if not _build_container(container_name, "code", result_path):
  254. print(
  255. "Failed building: %s" % containers[container_name]["code"]
  256. )
  257. break
  258. else:
  259. print(
  260. "Container already exist, skipped building: %s"
  261. % containers[container_name]["code"]
  262. )
  263. volumes = ["-v", "{}:/results:z".format(result_path)]
  264. if mount_wrkdir:
  265. volumes += ["-v", "{}:/wrkdir:z,ro".format(wrkdir_path)]
  266. env_vars = [
  267. "-e",
  268. "BRANCH={}".format(os.environ.get("BRANCH") or args.branch),
  269. "-e",
  270. "REPO={}".format(os.environ.get("REPO") or args.repo),
  271. "-e",
  272. "TESTCASE={}".format(args.test_case or ""),
  273. "-e",
  274. "TOXENV={}".format(os.environ.get("TOXENV") or args.toxenv),
  275. ]
  276. if args.shell:
  277. print("--------- Shelling in the container --------------")
  278. cmd = [
  279. "podman",
  280. "run",
  281. "-it",
  282. "--rm",
  283. "--name",
  284. containers[container_name]["name"],
  285. ]
  286. cmd += volumes
  287. cmd += env_vars
  288. cmd += [
  289. "--entrypoint=/bin/bash",
  290. containers[container_name]["code"],
  291. ]
  292. logfile = "{}/{}_shell.log".format(result_path, TIMESTAMP)
  293. _call_command(cmd, logfile)
  294. else:
  295. print("--------- Running Test --------------")
  296. cmd = [
  297. "podman",
  298. "run",
  299. "-it",
  300. "--rm",
  301. "--name",
  302. containers[container_name]["name"],
  303. ]
  304. cmd += volumes
  305. cmd += env_vars
  306. cmd += [
  307. containers[container_name]["code"],
  308. ]
  309. logfile = "{}/{}_tests.log".format(result_path, TIMESTAMP)
  310. if not _call_command(cmd, logfile):
  311. failed.append(container_name)
  312. if not args.shell:
  313. print("\nSummary:")
  314. if not failed:
  315. print(" ALL TESTS PASSED")
  316. else:
  317. print(" %s TESTS FAILED:" % len(failed))
  318. for fail in failed:
  319. print(" - %s" % fail)