1""" 2RPM Package builder system 3 4.. versionadded:: 2015.8.0 5 6This system allows for all of the components to build rpms safely in chrooted 7environments. This also provides a function to generate yum repositories 8 9This module implements the pkgbuild interface 10""" 11 12 13import errno 14import functools 15import logging 16import os 17import re 18import shutil 19import tempfile 20import time 21import traceback 22import urllib.parse 23 24import salt.utils.files 25import salt.utils.path 26import salt.utils.user 27import salt.utils.vt 28from salt.exceptions import CommandExecutionError, SaltInvocationError 29 30HAS_LIBS = False 31 32try: 33 import gnupg # pylint: disable=unused-import 34 import salt.modules.gpg 35 36 HAS_LIBS = True 37except ImportError: 38 pass 39 40log = logging.getLogger(__name__) 41 42__virtualname__ = "pkgbuild" 43 44 45def __virtual__(): 46 """ 47 Confirm this module is on a RPM based system, and has required utilities 48 """ 49 missing_util = False 50 utils_reqd = ["gpg", "rpm", "rpmbuild", "mock", "createrepo"] 51 for named_util in utils_reqd: 52 if not salt.utils.path.which(named_util): 53 missing_util = True 54 break 55 56 if HAS_LIBS and not missing_util: 57 if __grains__.get("os_family", False) in ("RedHat", "Suse"): 58 return __virtualname__ 59 else: 60 # The module will be exposed as `rpmbuild` on non-RPM based systems 61 return "rpmbuild" 62 else: 63 return ( 64 False, 65 "The rpmbuild module could not be loaded: requires python-gnupg, " 66 "gpg, rpm, rpmbuild, mock and createrepo utilities to be installed", 67 ) 68 69 70def _create_rpmmacros(runas="root"): 71 """ 72 Create the .rpmmacros file in user's home directory 73 """ 74 home = os.path.expanduser("~" + runas) 75 rpmbuilddir = os.path.join(home, "rpmbuild") 76 if not os.path.isdir(rpmbuilddir): 77 __salt__["file.makedirs_perms"](name=rpmbuilddir, user=runas, group="mock") 78 79 mockdir = os.path.join(home, "mock") 80 if not os.path.isdir(mockdir): 81 __salt__["file.makedirs_perms"](name=mockdir, user=runas, group="mock") 82 83 rpmmacros = os.path.join(home, ".rpmmacros") 84 with salt.utils.files.fopen(rpmmacros, "w") as afile: 85 afile.write(salt.utils.stringutils.to_str("%_topdir {}\n".format(rpmbuilddir))) 86 afile.write("%signature gpg\n") 87 afile.write("%_source_filedigest_algorithm 8\n") 88 afile.write("%_binary_filedigest_algorithm 8\n") 89 afile.write("%_gpg_name packaging@saltstack.com\n") 90 91 92def _mk_tree(runas="root"): 93 """ 94 Create the rpm build tree 95 """ 96 basedir = tempfile.mkdtemp() 97 paths = ["BUILD", "RPMS", "SOURCES", "SPECS", "SRPMS"] 98 for path in paths: 99 full = os.path.join(basedir, path) 100 __salt__["file.makedirs_perms"](name=full, user=runas, group="mock") 101 return basedir 102 103 104def _get_spec(tree_base, spec, template, saltenv="base"): 105 """ 106 Get the spec file and place it in the SPECS dir 107 """ 108 spec_tgt = os.path.basename(spec) 109 dest = os.path.join(tree_base, "SPECS", spec_tgt) 110 return __salt__["cp.get_url"](spec, dest, saltenv=saltenv) 111 112 113def _get_src(tree_base, source, saltenv="base", runas="root"): 114 """ 115 Get the named sources and place them into the tree_base 116 """ 117 parsed = urllib.parse.urlparse(source) 118 sbase = os.path.basename(source) 119 dest = os.path.join(tree_base, "SOURCES", sbase) 120 if parsed.scheme: 121 lsrc = __salt__["cp.get_url"](source, dest, saltenv=saltenv) 122 else: 123 shutil.copy(source, dest) 124 __salt__["file.chown"](path=dest, user=runas, group="mock") 125 126 127def _get_distset(tgt): 128 """ 129 Get the distribution string for use with rpmbuild and mock 130 """ 131 # Centos adds 'centos' string to rpm names, removing that to have 132 # consistent naming on Centos and Redhat, and allow for Amazon naming 133 tgtattrs = tgt.split("-") 134 if tgtattrs[0] == "amzn2": 135 distset = '--define "dist .{}"'.format(tgtattrs[0]) 136 elif tgtattrs[1] in ["6", "7", "8"]: 137 distset = '--define "dist .el{}"'.format(tgtattrs[1]) 138 else: 139 distset = "" 140 141 return distset 142 143 144def _get_deps(deps, tree_base, saltenv="base"): 145 """ 146 Get include string for list of dependent rpms to build package 147 """ 148 deps_list = "" 149 if deps is None: 150 return deps_list 151 if not isinstance(deps, list): 152 raise SaltInvocationError( 153 "'deps' must be a Python list or comma-separated string" 154 ) 155 for deprpm in deps: 156 parsed = urllib.parse._urlparse(deprpm) 157 depbase = os.path.basename(deprpm) 158 dest = os.path.join(tree_base, depbase) 159 if parsed.scheme: 160 __salt__["cp.get_url"](deprpm, dest, saltenv=saltenv) 161 else: 162 shutil.copy(deprpm, dest) 163 164 deps_list += " {}".format(dest) 165 166 return deps_list 167 168 169def _check_repo_gpg_phrase_utils(): 170 """ 171 Check for /usr/libexec/gpg-preset-passphrase is installed 172 """ 173 util_name = "/usr/libexec/gpg-preset-passphrase" 174 if __salt__["file.file_exists"](util_name): 175 return True 176 else: 177 raise CommandExecutionError( 178 "utility '{}' needs to be installed".format(util_name) 179 ) 180 181 182def _get_gpg_key_resources(keyid, env, use_passphrase, gnupghome, runas): 183 """ 184 Obtain gpg key resource infomation to sign repo files with 185 186 keyid 187 188 Optional Key ID to use in signing packages and repository. 189 Utilizes Public and Private keys associated with keyid which have 190 been loaded into the minion's Pillar data. 191 192 env 193 194 A dictionary of environment variables to be utilized in creating the 195 repository. 196 197 use_passphrase : False 198 199 Use a passphrase with the signing key presented in ``keyid``. 200 Passphrase is received from Pillar data which could be passed on the 201 command line with ``pillar`` parameter. 202 203 gnupghome : /etc/salt/gpgkeys 204 205 Location where GPG related files are stored, used with ``keyid``. 206 207 runas : root 208 209 User to create the repository as, and optionally sign packages. 210 211 .. note:: 212 213 Ensure the user has correct permissions to any files and 214 directories which are to be utilized. 215 216 217 Returns: 218 tuple 219 use_gpg_agent True | False, Redhat 8 now makes use of a gpg-agent similar ot Debian 220 local_keyid key id to use in signing 221 define_gpg_name string containing definition to use with addsign (use_gpg_agent False) 222 phrase pass phrase (may not be used) 223 224 """ 225 local_keygrip_to_use = None 226 local_key_fingerprint = None 227 local_keyid = None 228 local_uids = None 229 define_gpg_name = "" 230 phrase = "" 231 retrc = 0 232 use_gpg_agent = False 233 234 if ( 235 __grains__.get("os_family") == "RedHat" 236 and __grains__.get("osmajorrelease") >= 8 237 ): 238 use_gpg_agent = True 239 240 if keyid is not None: 241 # import_keys 242 pkg_pub_key_file = "{}/{}".format( 243 gnupghome, __salt__["pillar.get"]("gpg_pkg_pub_keyname", None) 244 ) 245 pkg_priv_key_file = "{}/{}".format( 246 gnupghome, __salt__["pillar.get"]("gpg_pkg_priv_keyname", None) 247 ) 248 249 if pkg_pub_key_file is None or pkg_priv_key_file is None: 250 raise SaltInvocationError( 251 "Pillar data should contain Public and Private keys associated with" 252 " 'keyid'" 253 ) 254 try: 255 __salt__["gpg.import_key"]( 256 user=runas, filename=pkg_pub_key_file, gnupghome=gnupghome 257 ) 258 __salt__["gpg.import_key"]( 259 user=runas, filename=pkg_priv_key_file, gnupghome=gnupghome 260 ) 261 262 except SaltInvocationError: 263 raise SaltInvocationError( 264 "Public and Private key files associated with Pillar data and 'keyid' " 265 "{} could not be found".format(keyid) 266 ) 267 268 # gpg keys should have been loaded as part of setup 269 # retrieve specified key and preset passphrase 270 local_keys = __salt__["gpg.list_keys"](user=runas, gnupghome=gnupghome) 271 for gpg_key in local_keys: 272 if keyid == gpg_key["keyid"][8:]: 273 local_uids = gpg_key["uids"] 274 local_keyid = gpg_key["keyid"] 275 if use_gpg_agent: 276 local_keygrip_to_use = gpg_key["fingerprint"] 277 local_key_fingerprint = gpg_key["fingerprint"] 278 break 279 280 if use_gpg_agent: 281 cmd = "gpg --with-keygrip --list-secret-keys" 282 local_keys2_keygrip = __salt__["cmd.run"](cmd, runas=runas, env=env) 283 local_keys2 = iter(local_keys2_keygrip.splitlines()) 284 try: 285 for line in local_keys2: 286 if line.startswith("sec"): 287 line_fingerprint = next(local_keys2).lstrip().rstrip() 288 if local_key_fingerprint == line_fingerprint: 289 lkeygrip = next(local_keys2).split("=") 290 local_keygrip_to_use = lkeygrip[1].lstrip().rstrip() 291 break 292 except StopIteration: 293 raise SaltInvocationError( 294 "unable to find keygrip associated with fingerprint '{}' for keyid" 295 " '{}'".format(local_key_fingerprint, local_keyid) 296 ) 297 298 if local_keyid is None: 299 raise SaltInvocationError( 300 "The key ID '{}' was not found in GnuPG keyring at '{}'".format( 301 keyid, gnupghome 302 ) 303 ) 304 305 if use_passphrase: 306 phrase = __salt__["pillar.get"]("gpg_passphrase") 307 if use_gpg_agent: 308 _check_repo_gpg_phrase_utils() 309 cmd = ( 310 "/usr/libexec/gpg-preset-passphrase --verbose --preset " 311 '--passphrase "{}" {}'.format(phrase, local_keygrip_to_use) 312 ) 313 retrc = __salt__["cmd.retcode"](cmd, runas=runas, env=env) 314 if retrc != 0: 315 raise SaltInvocationError( 316 "Failed to preset passphrase, error {1}, " 317 "check logs for further details".format(retrc) 318 ) 319 320 if local_uids: 321 define_gpg_name = ( 322 "--define='%_signature gpg' --define='%_gpg_name {}'".format( 323 local_uids[0] 324 ) 325 ) 326 327 # need to update rpm with public key 328 cmd = "rpm --import {}".format(pkg_pub_key_file) 329 retrc = __salt__["cmd.retcode"](cmd, runas=runas, use_vt=True) 330 if retrc != 0: 331 raise SaltInvocationError( 332 "Failed to import public key from file {} with return " 333 "error {}, check logs for further details".format( 334 pkg_pub_key_file, retrc 335 ) 336 ) 337 338 return (use_gpg_agent, local_keyid, define_gpg_name, phrase) 339 340 341def _sign_file(runas, define_gpg_name, phrase, abs_file, timeout): 342 """ 343 Sign file with provided key and definition 344 """ 345 SIGN_PROMPT_RE = re.compile(r"Enter pass phrase: ", re.M) 346 347 # interval of 0.125 is really too fast on some systems 348 interval = 0.5 349 number_retries = timeout / interval 350 times_looped = 0 351 error_msg = "Failed to sign file {}".format(abs_file) 352 353 cmd = "rpm {} --addsign {}".format(define_gpg_name, abs_file) 354 preexec_fn = functools.partial(salt.utils.user.chugid_and_umask, runas, None) 355 try: 356 stdout, stderr = None, None 357 proc = salt.utils.vt.Terminal( 358 cmd, 359 shell=True, 360 preexec_fn=preexec_fn, 361 stream_stdout=True, 362 stream_stderr=True, 363 ) 364 while proc.has_unread_data: 365 stdout, stderr = proc.recv() 366 if stdout and SIGN_PROMPT_RE.search(stdout): 367 # have the prompt for inputting the passphrase 368 proc.sendline(phrase) 369 else: 370 times_looped += 1 371 372 if times_looped > number_retries: 373 raise SaltInvocationError( 374 "Attemping to sign file {} failed, timed out after {} seconds".format( 375 abs_file, int(times_looped * interval) 376 ) 377 ) 378 time.sleep(interval) 379 380 proc_exitstatus = proc.exitstatus 381 if proc_exitstatus != 0: 382 raise SaltInvocationError( 383 "Signing file {} failed with proc.status {}".format( 384 abs_file, proc_exitstatus 385 ) 386 ) 387 except salt.utils.vt.TerminalException as err: 388 trace = traceback.format_exc() 389 log.error(error_msg, err, trace) 390 finally: 391 proc.close(terminate=True, kill=True) 392 393 394def _sign_files_with_gpg_agent(runas, local_keyid, abs_file, repodir, env, timeout): 395 """ 396 Sign file with provided key utilizing gpg-agent 397 """ 398 cmd = "rpmsign --verbose --key-id={} --addsign {}".format(local_keyid, abs_file) 399 retrc = __salt__["cmd.retcode"](cmd, runas=runas, cwd=repodir, use_vt=True, env=env) 400 if retrc != 0: 401 raise SaltInvocationError( 402 "Signing encountered errors for command '{}', " 403 "return error {}, check logs for further details".format(cmd, retrc) 404 ) 405 406 407def make_src_pkg( 408 dest_dir, spec, sources, env=None, template=None, saltenv="base", runas="root" 409): 410 """ 411 Create a source rpm from the given spec file and sources 412 413 CLI Example: 414 415 .. code-block:: bash 416 417 salt '*' pkgbuild.make_src_pkg /var/www/html/ 418 https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/rpm/python-libnacl.spec 419 https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz 420 421 This example command should build the libnacl SOURCE package and place it in 422 /var/www/html/ on the minion 423 424 .. versionchanged:: 2017.7.0 425 426 dest_dir 427 The directory on the minion to place the built package(s) 428 429 spec 430 The location of the spec file (used for rpms) 431 432 sources 433 The list of package sources 434 435 env 436 A dictionary of environment variables to be set prior to execution. 437 438 template 439 Run the spec file through a templating engine 440 Optional argument, allows for no templating engine used to be 441 if none is desired. 442 443 saltenv 444 The saltenv to use for files downloaded from the salt filesever 445 446 runas 447 The user to run the build process as 448 449 .. versionadded:: 2018.3.3 450 451 452 .. note:: 453 454 using SHA256 as digest and minimum level dist el6 455 456 """ 457 _create_rpmmacros(runas) 458 tree_base = _mk_tree(runas) 459 spec_path = _get_spec(tree_base, spec, template, saltenv) 460 __salt__["file.chown"](path=spec_path, user=runas, group="mock") 461 __salt__["file.chown"](path=tree_base, user=runas, group="mock") 462 463 if isinstance(sources, str): 464 sources = sources.split(",") 465 for src in sources: 466 _get_src(tree_base, src, saltenv, runas) 467 468 # make source rpms for dist el6 with SHA256, usable with mock on other dists 469 cmd = 'rpmbuild --verbose --define "_topdir {}" -bs --define "dist .el6" {}'.format( 470 tree_base, spec_path 471 ) 472 retrc = __salt__["cmd.retcode"](cmd, runas=runas) 473 if retrc != 0: 474 raise SaltInvocationError( 475 "Make source package for destination directory {}, spec {}, sources {}," 476 " failed with return error {}, check logs for further details".format( 477 dest_dir, spec, sources, retrc 478 ) 479 ) 480 481 srpms = os.path.join(tree_base, "SRPMS") 482 ret = [] 483 if not os.path.isdir(dest_dir): 484 __salt__["file.makedirs_perms"](name=dest_dir, user=runas, group="mock") 485 for fn_ in os.listdir(srpms): 486 full = os.path.join(srpms, fn_) 487 tgt = os.path.join(dest_dir, fn_) 488 shutil.copy(full, tgt) 489 ret.append(tgt) 490 return ret 491 492 493def build( 494 runas, 495 tgt, 496 dest_dir, 497 spec, 498 sources, 499 deps, 500 env, 501 template, 502 saltenv="base", 503 log_dir="/var/log/salt/pkgbuild", 504): 505 """ 506 Given the package destination directory, the spec file source and package 507 sources, use mock to safely build the rpm defined in the spec file 508 509 CLI Example: 510 511 .. code-block:: bash 512 513 salt '*' pkgbuild.build mock epel-7-x86_64 /var/www/html 514 https://raw.githubusercontent.com/saltstack/libnacl/master/pkg/rpm/python-libnacl.spec 515 https://pypi.python.org/packages/source/l/libnacl/libnacl-1.3.5.tar.gz 516 517 This example command should build the libnacl package for rhel 7 using user 518 mock and place it in /var/www/html/ on the minion 519 """ 520 ret = {} 521 try: 522 __salt__["file.chown"](path=dest_dir, user=runas, group="mock") 523 except OSError as exc: 524 if exc.errno != errno.EEXIST: 525 raise 526 srpm_dir = os.path.join(dest_dir, "SRPMS") 527 srpm_build_dir = tempfile.mkdtemp() 528 try: 529 srpms = make_src_pkg( 530 srpm_build_dir, spec, sources, env, template, saltenv, runas 531 ) 532 except Exception as exc: # pylint: disable=broad-except 533 shutil.rmtree(srpm_build_dir) 534 log.error("Failed to make src package") 535 return ret 536 537 distset = _get_distset(tgt) 538 539 noclean = "" 540 deps_dir = tempfile.mkdtemp() 541 deps_list = _get_deps(deps, deps_dir, saltenv) 542 543 retrc = 0 544 for srpm in srpms: 545 dbase = os.path.dirname(srpm) 546 results_dir = tempfile.mkdtemp() 547 try: 548 __salt__["file.chown"](path=dbase, user=runas, group="mock") 549 __salt__["file.chown"](path=results_dir, user=runas, group="mock") 550 cmd = "mock --root={} --resultdir={} --init".format(tgt, results_dir) 551 retrc |= __salt__["cmd.retcode"](cmd, runas=runas) 552 if deps_list and not deps_list.isspace(): 553 cmd = "mock --root={} --resultdir={} --install {} {}".format( 554 tgt, results_dir, deps_list, noclean 555 ) 556 retrc |= __salt__["cmd.retcode"](cmd, runas=runas) 557 noclean += " --no-clean" 558 559 cmd = "mock --root={} --resultdir={} {} {} {}".format( 560 tgt, results_dir, distset, noclean, srpm 561 ) 562 retrc |= __salt__["cmd.retcode"](cmd, runas=runas) 563 cmdlist = [ 564 "rpm", 565 "-qp", 566 "--queryformat", 567 "{0}/%{{name}}/%{{version}}-%{{release}}".format(log_dir), 568 srpm, 569 ] 570 log_dest = __salt__["cmd.run_stdout"](cmdlist, python_shell=False) 571 for filename in os.listdir(results_dir): 572 full = os.path.join(results_dir, filename) 573 if filename.endswith("src.rpm"): 574 sdest = os.path.join(srpm_dir, filename) 575 try: 576 __salt__["file.makedirs_perms"]( 577 name=srpm_dir, user=runas, group="mock" 578 ) 579 except OSError as exc: 580 if exc.errno != errno.EEXIST: 581 raise 582 shutil.copy(full, sdest) 583 ret.setdefault("Source Packages", []).append(sdest) 584 elif filename.endswith(".rpm"): 585 bdist = os.path.join(dest_dir, filename) 586 shutil.copy(full, bdist) 587 ret.setdefault("Packages", []).append(bdist) 588 else: 589 log_file = os.path.join(log_dest, filename) 590 try: 591 __salt__["file.makedirs_perms"]( 592 name=log_dest, user=runas, group="mock" 593 ) 594 except OSError as exc: 595 if exc.errno != errno.EEXIST: 596 raise 597 shutil.copy(full, log_file) 598 ret.setdefault("Log Files", []).append(log_file) 599 except Exception as exc: # pylint: disable=broad-except 600 log.error("Error building from %s: %s", srpm, exc) 601 finally: 602 shutil.rmtree(results_dir) 603 if retrc != 0: 604 raise SaltInvocationError( 605 "Building packages for destination directory {}, spec {}, sources {}," 606 " failed with return error {}, check logs for further details".format( 607 dest_dir, spec, sources, retrc 608 ) 609 ) 610 shutil.rmtree(deps_dir) 611 shutil.rmtree(srpm_build_dir) 612 return ret 613 614 615def make_repo( 616 repodir, 617 keyid=None, 618 env=None, 619 use_passphrase=False, 620 gnupghome="/etc/salt/gpgkeys", 621 runas="root", 622 timeout=15.0, 623): 624 """ 625 Make a package repository and optionally sign packages present 626 627 Given the repodir, create a ``yum`` repository out of the rpms therein 628 and optionally sign it and packages present, the name is directory to 629 turn into a repo. This state is best used with onchanges linked to 630 your package building states. 631 632 repodir 633 The directory to find packages that will be in the repository. 634 635 keyid 636 .. versionchanged:: 2016.3.0 637 638 Optional Key ID to use in signing packages and repository. 639 Utilizes Public and Private keys associated with keyid which have 640 been loaded into the minion's Pillar data. 641 642 For example, contents from a Pillar data file with named Public 643 and Private keys as follows: 644 645 .. code-block:: yaml 646 647 gpg_pkg_priv_key: | 648 -----BEGIN PGP PRIVATE KEY BLOCK----- 649 Version: GnuPG v1 650 651 lQO+BFciIfQBCADAPCtzx7I5Rl32escCMZsPzaEKWe7bIX1em4KCKkBoX47IG54b 652 w82PCE8Y1jF/9Uk2m3RKVWp3YcLlc7Ap3gj6VO4ysvVz28UbnhPxsIkOlf2cq8qc 653 . 654 . 655 Ebe+8JCQTwqSXPRTzXmy/b5WXDeM79CkLWvuGpXFor76D+ECMRPv/rawukEcNptn 656 R5OmgHqvydEnO4pWbn8JzQO9YX/Us0SMHBVzLC8eIi5ZIopzalvX 657 =JvW8 658 -----END PGP PRIVATE KEY BLOCK----- 659 660 gpg_pkg_priv_keyname: gpg_pkg_key.pem 661 662 gpg_pkg_pub_key: | 663 -----BEGIN PGP PUBLIC KEY BLOCK----- 664 Version: GnuPG v1 665 666 mQENBFciIfQBCADAPCtzx7I5Rl32escCMZsPzaEKWe7bIX1em4KCKkBoX47IG54b 667 w82PCE8Y1jF/9Uk2m3RKVWp3YcLlc7Ap3gj6VO4ysvVz28UbnhPxsIkOlf2cq8qc 668 . 669 . 670 bYP7t5iwJmQzRMyFInYRt77wkJBPCpJc9FPNebL9vlZcN4zv0KQta+4alcWivvoP 671 4QIxE+/+trC6QRw2m2dHk6aAeq/J0Sc7ilZufwnNA71hf9SzRIwcFXMsLx4iLlki 672 inNqW9c= 673 =s1CX 674 -----END PGP PUBLIC KEY BLOCK----- 675 676 gpg_pkg_pub_keyname: gpg_pkg_key.pub 677 678 env 679 .. versionchanged:: 2016.3.0 680 681 A dictionary of environment variables to be utilized in creating the 682 repository. 683 684 .. note:: 685 686 This parameter is not used for making ``yum`` repositories. 687 688 use_passphrase : False 689 .. versionadded:: 2016.3.0 690 691 Use a passphrase with the signing key presented in ``keyid``. 692 Passphrase is received from Pillar data which could be passed on the 693 command line with ``pillar`` parameter. 694 695 .. code-block:: bash 696 697 pillar='{ "gpg_passphrase" : "my_passphrase" }' 698 699 .. versionadded:: 3001.1 700 701 RHEL 8 and above leverages gpg-agent and gpg-preset-passphrase for 702 caching keys, etc. 703 704 gnupghome : /etc/salt/gpgkeys 705 .. versionadded:: 2016.3.0 706 707 Location where GPG related files are stored, used with ``keyid``. 708 709 runas : root 710 .. versionadded:: 2016.3.0 711 712 User to create the repository as, and optionally sign packages. 713 714 .. note:: 715 716 Ensure the user has correct permissions to any files and 717 directories which are to be utilized. 718 719 timeout : 15.0 720 .. versionadded:: 2016.3.4 721 722 Timeout in seconds to wait for the prompt for inputting the passphrase. 723 724 CLI Example: 725 726 .. code-block:: bash 727 728 salt '*' pkgbuild.make_repo /var/www/html/ 729 730 """ 731 home = os.path.expanduser("~" + runas) 732 rpmmacros = os.path.join(home, ".rpmmacros") 733 if not os.path.exists(rpmmacros): 734 _create_rpmmacros(runas) 735 736 if gnupghome and env is None: 737 env = {} 738 env["GNUPGHOME"] = gnupghome 739 740 use_gpg_agent, local_keyid, define_gpg_name, phrase = _get_gpg_key_resources( 741 keyid, env, use_passphrase, gnupghome, runas 742 ) 743 744 # sign_it_here 745 for fileused in os.listdir(repodir): 746 if fileused.endswith(".rpm"): 747 abs_file = os.path.join(repodir, fileused) 748 if use_gpg_agent: 749 _sign_files_with_gpg_agent( 750 runas, local_keyid, abs_file, repodir, env, timeout 751 ) 752 else: 753 _sign_file(runas, define_gpg_name, phrase, abs_file, timeout) 754 755 cmd = "createrepo --update {}".format(repodir) 756 retrc = __salt__["cmd.run_all"](cmd, runas=runas) 757 return retrc 758