1""" 2General management functions for salt, tools like seeing what hosts are up 3and what hosts are down 4""" 5 6 7import logging 8import operator 9import os 10import re 11import subprocess 12import tempfile 13import time 14import urllib.request 15import uuid 16 17import salt.client 18import salt.client.ssh 19import salt.key 20import salt.utils.compat 21import salt.utils.files 22import salt.utils.minions 23import salt.utils.path 24import salt.utils.versions 25import salt.version 26import salt.wheel 27from salt.exceptions import SaltClientError, SaltSystemExit 28 29FINGERPRINT_REGEX = re.compile(r"^([a-f0-9]{2}:){15}([a-f0-9]{2})$") 30 31log = logging.getLogger(__name__) 32 33 34def _ping(tgt, tgt_type, timeout, gather_job_timeout): 35 with salt.client.get_local_client(__opts__["conf_file"]) as client: 36 pub_data = client.run_job( 37 tgt, "test.ping", (), tgt_type, "", timeout, "", listen=True 38 ) 39 40 if not pub_data: 41 return pub_data 42 43 log.debug( 44 "manage runner will ping the following minion(s): %s", 45 ", ".join(sorted(pub_data["minions"])), 46 ) 47 48 returned = set() 49 for fn_ret in client.get_cli_event_returns( 50 pub_data["jid"], 51 pub_data["minions"], 52 client._get_timeout(timeout), 53 tgt, 54 tgt_type, 55 gather_job_timeout=gather_job_timeout, 56 ): 57 58 if fn_ret: 59 for mid, _ in fn_ret.items(): 60 log.debug("minion '%s' returned from ping", mid) 61 returned.add(mid) 62 63 not_returned = sorted(set(pub_data["minions"]) - returned) 64 returned = sorted(returned) 65 66 return returned, not_returned 67 68 69def status( 70 output=True, tgt="*", tgt_type="glob", timeout=None, gather_job_timeout=None 71): 72 """ 73 .. versionchanged:: 2017.7.0 74 75 The ``expr_form`` argument has been renamed to ``tgt_type``, earlier 76 releases must use ``expr_form``. 77 78 Print the status of all known salt minions 79 80 CLI Example: 81 82 .. code-block:: bash 83 84 salt-run manage.status 85 salt-run manage.status tgt="webservers" tgt_type="nodegroup" 86 salt-run manage.status timeout=5 gather_job_timeout=10 87 """ 88 ret = {} 89 90 if not timeout: 91 timeout = __opts__["timeout"] 92 if not gather_job_timeout: 93 gather_job_timeout = __opts__["gather_job_timeout"] 94 95 res = _ping(tgt, tgt_type, timeout, gather_job_timeout) 96 ret["up"], ret["down"] = ([], []) if not res else res 97 return ret 98 99 100def key_regen(): 101 """ 102 This routine is used to regenerate all keys in an environment. This is 103 invasive! ALL KEYS IN THE SALT ENVIRONMENT WILL BE REGENERATED!! 104 105 The key_regen routine sends a command out to minions to revoke the master 106 key and remove all minion keys, it then removes all keys from the master 107 and prompts the user to restart the master. The minions will all reconnect 108 and keys will be placed in pending. 109 110 After the master is restarted and minion keys are in the pending directory 111 execute a salt-key -A command to accept the regenerated minion keys. 112 113 The master *must* be restarted within 60 seconds of running this command or 114 the minions will think there is something wrong with the keys and abort. 115 116 Only Execute this runner after upgrading minions and master to 0.15.1 or 117 higher! 118 119 CLI Example: 120 121 .. code-block:: bash 122 123 salt-run manage.key_regen 124 """ 125 client = salt.client.get_local_client(__opts__["conf_file"]) 126 try: 127 client.cmd("*", "saltutil.regen_keys") 128 except SaltClientError as client_error: 129 print(client_error) 130 return False 131 132 for root, _, files in salt.utils.path.os_walk(__opts__["pki_dir"]): 133 for fn_ in files: 134 path = os.path.join(root, fn_) 135 try: 136 os.remove(path) 137 except os.error: 138 pass 139 msg = ( 140 "The minion and master keys have been deleted. Restart the Salt\n" 141 "Master within the next 60 seconds!!!\n\n" 142 "Wait for the minions to reconnect. Once the minions reconnect\n" 143 "the new keys will appear in pending and will need to be re-\n" 144 "accepted by running:\n" 145 " salt-key -A\n\n" 146 "Be advised that minions not currently connected to the master\n" 147 "will not be able to reconnect and may require manual\n" 148 "regeneration via a local call to\n" 149 " salt-call saltutil.regen_keys" 150 ) 151 return msg 152 153 154def down( 155 removekeys=False, tgt="*", tgt_type="glob", timeout=None, gather_job_timeout=None 156): 157 """ 158 .. versionchanged:: 2017.7.0 159 160 The ``expr_form`` argument has been renamed to ``tgt_type``, earlier 161 releases must use ``expr_form``. 162 163 Print a list of all the down or unresponsive salt minions 164 Optionally remove keys of down minions 165 166 CLI Example: 167 168 .. code-block:: bash 169 170 salt-run manage.down 171 salt-run manage.down removekeys=True 172 salt-run manage.down tgt="webservers" tgt_type="nodegroup" 173 """ 174 ret = status( 175 output=False, 176 tgt=tgt, 177 tgt_type=tgt_type, 178 timeout=timeout, 179 gather_job_timeout=gather_job_timeout, 180 ).get("down", []) 181 for minion in ret: 182 if removekeys: 183 wheel = salt.wheel.Wheel(__opts__) 184 wheel.call_func("key.delete", match=minion) 185 return ret 186 187 188def up( 189 tgt="*", tgt_type="glob", timeout=None, gather_job_timeout=None 190): # pylint: disable=C0103 191 """ 192 .. versionchanged:: 2017.7.0 193 194 The ``expr_form`` argument has been renamed to ``tgt_type``, earlier 195 releases must use ``expr_form``. 196 197 Print a list of all of the minions that are up 198 199 CLI Example: 200 201 .. code-block:: bash 202 203 salt-run manage.up 204 salt-run manage.up tgt="webservers" tgt_type="nodegroup" 205 salt-run manage.up timeout=5 gather_job_timeout=10 206 """ 207 ret = status( 208 output=False, 209 tgt=tgt, 210 tgt_type=tgt_type, 211 timeout=timeout, 212 gather_job_timeout=gather_job_timeout, 213 ).get("up", []) 214 return ret 215 216 217def list_state(subset=None, show_ip=False): 218 """ 219 .. versionadded:: 2015.8.0 220 .. versionchanged:: 2019.2.0 221 222 Print a list of all minions that are up according to Salt's presence 223 detection (no commands will be sent to minions) 224 225 subset : None 226 Pass in a CIDR range to filter minions by IP address. 227 228 show_ip : False 229 Also show the IP address each minion is connecting from. 230 231 CLI Example: 232 233 .. code-block:: bash 234 235 salt-run manage.list_state 236 """ 237 # Always return 'present' for 0MQ for now 238 # TODO: implement other states support for 0MQ 239 ckminions = salt.utils.minions.CkMinions(__opts__) 240 minions = ckminions.connected_ids(show_ip=show_ip, subset=subset) 241 242 connected = dict(minions) if show_ip else sorted(minions) 243 244 return connected 245 246 247def list_not_state(subset=None, show_ip=False): 248 """ 249 .. versionadded:: 2015.8.0 250 .. versionchanged:: 2019.2.0 251 252 Print a list of all minions that are NOT up according to Salt's presence 253 detection (no commands will be sent to minions) 254 255 subset : None 256 Pass in a CIDR range to filter minions by IP address. 257 258 show_ip : False 259 Also show the IP address each minion is connecting from. 260 261 CLI Example: 262 263 .. code-block:: bash 264 265 salt-run manage.list_not_state 266 """ 267 connected = list_state(subset=None, show_ip=show_ip) 268 269 with salt.key.get_key(__opts__) as key: 270 keys = key.list_keys() 271 272 not_connected = [] 273 for minion in keys[key.ACC]: 274 if minion not in connected and (subset is None or minion in subset): 275 not_connected.append(minion) 276 277 return not_connected 278 279 280def present(subset=None, show_ip=False): 281 """ 282 .. versionchanged:: 2019.2.0 283 284 Print a list of all minions that are up according to Salt's presence 285 detection (no commands will be sent to minions) 286 287 subset : None 288 Pass in a CIDR range to filter minions by IP address. 289 290 show_ip : False 291 Also show the IP address each minion is connecting from. 292 293 CLI Example: 294 295 .. code-block:: bash 296 297 salt-run manage.present 298 """ 299 return list_state(subset=subset, show_ip=show_ip) 300 301 302def not_present(subset=None, show_ip=False): 303 """ 304 .. versionadded:: 2015.5.0 305 .. versionchanged:: 2019.2.0 306 307 Print a list of all minions that are NOT up according to Salt's presence 308 detection (no commands will be sent) 309 310 subset : None 311 Pass in a CIDR range to filter minions by IP address. 312 313 show_ip : False 314 Also show the IP address each minion is connecting from. 315 316 CLI Example: 317 318 .. code-block:: bash 319 320 salt-run manage.not_present 321 """ 322 return list_not_state(subset=subset, show_ip=show_ip) 323 324 325def joined(subset=None, show_ip=False): 326 """ 327 .. versionadded:: 2015.8.0 328 .. versionchanged:: 2019.2.0 329 330 Print a list of all minions that are up according to Salt's presence 331 detection (no commands will be sent to minions) 332 333 subset : None 334 Pass in a CIDR range to filter minions by IP address. 335 336 show_ip : False 337 Also show the IP address each minion is connecting from. 338 339 CLI Example: 340 341 .. code-block:: bash 342 343 salt-run manage.joined 344 """ 345 return list_state(subset=subset, show_ip=show_ip) 346 347 348def not_joined(subset=None, show_ip=False): 349 """ 350 .. versionadded:: 2015.8.0 351 .. versionchanged:: 2019.2.0 352 353 Print a list of all minions that are NOT up according to Salt's presence 354 detection (no commands will be sent) 355 356 subset : None 357 Pass in a CIDR range to filter minions by IP address. 358 359 show_ip : False 360 Also show the IP address each minion is connecting from. 361 362 CLI Example: 363 364 .. code-block:: bash 365 366 salt-run manage.not_joined 367 """ 368 return list_not_state(subset=subset, show_ip=show_ip) 369 370 371def allowed(subset=None, show_ip=False): 372 """ 373 .. versionadded:: 2015.8.0 374 .. versionchanged:: 2019.2.0 375 376 Print a list of all minions that are up according to Salt's presence 377 detection (no commands will be sent to minions) 378 379 subset : None 380 Pass in a CIDR range to filter minions by IP address. 381 382 show_ip : False 383 Also show the IP address each minion is connecting from. 384 385 CLI Example: 386 387 .. code-block:: bash 388 389 salt-run manage.allowed 390 """ 391 return list_state(subset=subset, show_ip=show_ip) 392 393 394def not_allowed(subset=None, show_ip=False): 395 """ 396 .. versionadded:: 2015.8.0 397 .. versionchanged:: 2019.2.0 398 399 Print a list of all minions that are NOT up according to Salt's presence 400 detection (no commands will be sent) 401 402 subset : None 403 Pass in a CIDR range to filter minions by IP address. 404 405 show_ip : False 406 Also show the IP address each minion is connecting from. 407 408 CLI Example: 409 410 .. code-block:: bash 411 412 salt-run manage.not_allowed 413 """ 414 return list_not_state(subset=subset, show_ip=show_ip) 415 416 417def alived(subset=None, show_ip=False): 418 """ 419 .. versionadded:: 2015.8.0 420 .. versionchanged:: 2019.2.0 421 422 Print a list of all minions that are up according to Salt's presence 423 detection (no commands will be sent to minions) 424 425 subset : None 426 Pass in a CIDR range to filter minions by IP address. 427 428 show_ip : False 429 Also show the IP address each minion is connecting from. 430 431 CLI Example: 432 433 .. code-block:: bash 434 435 salt-run manage.alived 436 """ 437 return list_state(subset=subset, show_ip=show_ip) 438 439 440def not_alived(subset=None, show_ip=False): 441 """ 442 .. versionadded:: 2015.8.0 443 .. versionchanged:: 2019.2.0 444 445 Print a list of all minions that are NOT up according to Salt's presence 446 detection (no commands will be sent) 447 448 subset : None 449 Pass in a CIDR range to filter minions by IP address. 450 451 show_ip : False 452 Also show the IP address each minion is connecting from. 453 454 CLI Example: 455 456 .. code-block:: bash 457 458 salt-run manage.not_alived 459 """ 460 return list_not_state(subset=subset, show_ip=show_ip) 461 462 463def reaped(subset=None, show_ip=False): 464 """ 465 .. versionadded:: 2015.8.0 466 .. versionchanged:: 2019.2.0 467 468 Print a list of all minions that are up according to Salt's presence 469 detection (no commands will be sent to minions) 470 471 subset : None 472 Pass in a CIDR range to filter minions by IP address. 473 474 show_ip : False 475 Also show the IP address each minion is connecting from. 476 477 CLI Example: 478 479 .. code-block:: bash 480 481 salt-run manage.reaped 482 """ 483 return list_state(subset=subset, show_ip=show_ip) 484 485 486def not_reaped(subset=None, show_ip=False): 487 """ 488 .. versionadded:: 2015.8.0 489 .. versionchanged:: 2019.2.0 490 491 Print a list of all minions that are NOT up according to Salt's presence 492 detection (no commands will be sent) 493 494 subset : None 495 Pass in a CIDR range to filter minions by IP address. 496 497 show_ip : False 498 Also show the IP address each minion is connecting from. 499 500 CLI Example: 501 502 .. code-block:: bash 503 504 salt-run manage.not_reaped 505 """ 506 return list_not_state(subset=subset, show_ip=show_ip) 507 508 509def safe_accept(target, tgt_type="glob"): 510 """ 511 .. versionchanged:: 2017.7.0 512 513 The ``expr_form`` argument has been renamed to ``tgt_type``, earlier 514 releases must use ``expr_form``. 515 516 Accept a minion's public key after checking the fingerprint over salt-ssh 517 518 CLI Example: 519 520 .. code-block:: bash 521 522 salt-run manage.safe_accept my_minion 523 salt-run manage.safe_accept minion1,minion2 tgt_type=list 524 """ 525 ssh_client = salt.client.ssh.client.SSHClient() 526 ret = ssh_client.cmd(target, "key.finger", tgt_type=tgt_type) 527 528 failures = {} 529 for minion, finger in ret.items(): 530 if not FINGERPRINT_REGEX.match(finger): 531 failures[minion] = finger 532 else: 533 with salt.key.Key(__opts__) as salt_key: 534 fingerprints = salt_key.finger(minion) 535 accepted = fingerprints.get("minions", {}) 536 pending = fingerprints.get("minions_pre", {}) 537 if minion in accepted: 538 del ret[minion] 539 continue 540 elif minion not in pending: 541 failures[minion] = "Minion key {} not found by salt-key".format(minion) 542 elif pending[minion] != finger: 543 failures[ 544 minion 545 ] = "Minion key {} does not match the key in salt-key: {}".format( 546 finger, pending[minion] 547 ) 548 else: 549 subprocess.call(["salt-key", "-qya", minion]) 550 551 if minion in failures: 552 del ret[minion] 553 554 if failures: 555 print("safe_accept failed on the following minions:") 556 for minion, message in failures.items(): 557 print(minion) 558 print("-" * len(minion)) 559 print(message) 560 print("") 561 562 __jid_event__.fire_event( 563 {"message": "Accepted {:d} keys".format(len(ret))}, "progress" 564 ) 565 return ret, failures 566 567 568def versions(): 569 """ 570 Check the version of active minions 571 572 CLI Example: 573 574 .. code-block:: bash 575 576 salt-run manage.versions 577 """ 578 ret = {} 579 client = salt.client.get_local_client(__opts__["conf_file"]) 580 try: 581 minions = client.cmd("*", "test.version", timeout=__opts__["timeout"]) 582 except SaltClientError as client_error: 583 print(client_error) 584 return ret 585 586 labels = { 587 -2: "Minion offline", 588 -1: "Minion requires update", 589 0: "Up to date", 590 1: "Minion newer than master", 591 2: "Master", 592 } 593 594 version_status = {} 595 596 master_version = salt.version.__saltstack_version__ 597 598 for minion in minions: 599 if not minions[minion]: 600 minion_version = False 601 ver_diff = -2 602 else: 603 minion_version = salt.version.SaltStackVersion.parse(minions[minion]) 604 ver_diff = salt.utils.compat.cmp(minion_version, master_version) 605 606 if ver_diff not in version_status: 607 version_status[ver_diff] = {} 608 if minion_version: 609 version_status[ver_diff][minion] = minion_version.string 610 else: 611 version_status[ver_diff][minion] = minion_version 612 613 # Add version of Master to output 614 version_status[2] = master_version.string 615 616 for key in version_status: 617 if key == 2: 618 ret[labels[key]] = version_status[2] 619 else: 620 for minion in sorted(version_status[key]): 621 ret.setdefault(labels[key], {})[minion] = version_status[key][minion] 622 return ret 623 624 625def bootstrap( 626 version="develop", 627 script=None, 628 hosts="", 629 script_args="", 630 roster="flat", 631 ssh_user=None, 632 ssh_password=None, 633 ssh_priv_key=None, 634 tmp_dir="/tmp/.bootstrap", 635 http_backend="tornado", 636): 637 """ 638 Bootstrap minions with salt-bootstrap 639 640 version : develop 641 Git tag of version to install 642 643 script : https://bootstrap.saltstack.com 644 URL containing the script to execute 645 646 hosts 647 Comma-separated hosts [example: hosts='host1.local,host2.local']. These 648 hosts need to exist in the specified roster. 649 650 script_args 651 Any additional arguments that you want to pass to the script. 652 653 .. versionadded:: 2016.11.0 654 655 roster : flat 656 The roster to use for Salt SSH. More information about roster files can 657 be found in :ref:`Salt's Roster Documentation <ssh-roster>`. 658 659 A full list of roster types, see the :ref:`builtin roster modules <all-salt.roster>` 660 documentation. 661 662 .. versionadded:: 2016.11.0 663 664 ssh_user 665 If ``user`` isn't found in the ``roster``, a default SSH user can be set here. 666 Keep in mind that ``ssh_user`` will not override the roster ``user`` value if 667 it is already defined. 668 669 .. versionadded:: 2016.11.0 670 671 ssh_password 672 If ``passwd`` isn't found in the ``roster``, a default SSH password can be set 673 here. Keep in mind that ``ssh_password`` will not override the roster ``passwd`` 674 value if it is already defined. 675 676 .. versionadded:: 2016.11.0 677 678 ssh_privkey 679 If ``priv`` isn't found in the ``roster``, a default SSH private key can be set 680 here. Keep in mind that ``ssh_password`` will not override the roster ``passwd`` 681 value if it is already defined. 682 683 .. versionadded:: 2016.11.0 684 685 tmp_dir : /tmp/.bootstrap 686 The temporary directory to download the bootstrap script in. This 687 directory will have ``-<uuid4>`` appended to it. For example: 688 ``/tmp/.bootstrap-a19a728e-d40a-4801-aba9-d00655c143a7/`` 689 690 .. versionadded:: 2016.11.0 691 692 http_backend : tornado 693 The backend library to use to download the script. If you need to use 694 a ``file:///`` URL, then you should set this to ``urllib2``. 695 696 .. versionadded:: 2016.11.0 697 698 CLI Example: 699 700 .. code-block:: bash 701 702 salt-run manage.bootstrap hosts='host1,host2' 703 salt-run manage.bootstrap hosts='host1,host2' version='v0.17' 704 salt-run manage.bootstrap hosts='host1,host2' version='v0.17' script='https://bootstrap.saltstack.com/develop' 705 """ 706 if script is None: 707 script = "https://bootstrap.saltstack.com" 708 709 client_opts = __opts__.copy() 710 if roster is not None: 711 client_opts["roster"] = roster 712 713 if ssh_user is not None: 714 client_opts["ssh_user"] = ssh_user 715 716 if ssh_password is not None: 717 client_opts["ssh_passwd"] = ssh_password 718 719 if ssh_priv_key is not None: 720 client_opts["ssh_priv"] = ssh_priv_key 721 722 for host in hosts.split(","): 723 client_opts["tgt"] = host 724 client_opts["selected_target_option"] = "glob" 725 tmp_dir = "{}-{}/".format(tmp_dir.rstrip("/"), uuid.uuid4()) 726 deploy_command = os.path.join(tmp_dir, "deploy.sh") 727 try: 728 client_opts["argv"] = ["file.makedirs", tmp_dir, "mode=0700"] 729 salt.client.ssh.SSH(client_opts).run() 730 client_opts["argv"] = [ 731 "http.query", 732 script, 733 "backend={}".format(http_backend), 734 "text_out={}".format(deploy_command), 735 ] 736 salt.client.ssh.SSH(client_opts).run() 737 client_opts["argv"] = [ 738 "cmd.run", 739 " ".join(["sh", deploy_command, script_args]), 740 "python_shell=False", 741 ] 742 salt.client.ssh.SSH(client_opts).run() 743 client_opts["argv"] = ["file.remove", tmp_dir] 744 salt.client.ssh.SSH(client_opts).run() 745 except SaltSystemExit as exc: 746 log.error(str(exc)) 747 748 749def bootstrap_psexec( 750 hosts="", 751 master=None, 752 version=None, 753 arch="win32", 754 installer_url=None, 755 username=None, 756 password=None, 757): 758 """ 759 Bootstrap Windows minions via PsExec. 760 761 hosts 762 Comma separated list of hosts to deploy the Windows Salt minion. 763 764 master 765 Address of the Salt master passed as an argument to the installer. 766 767 version 768 Point release of installer to download. Defaults to the most recent. 769 770 arch 771 Architecture of installer to download. Defaults to win32. 772 773 installer_url 774 URL of minion installer executable. Defaults to the latest version from 775 https://repo.saltproject.io/windows/ 776 777 username 778 Optional user name for login on remote computer. 779 780 password 781 Password for optional username. If omitted, PsExec will prompt for one 782 to be entered for each host. 783 784 CLI Example: 785 786 .. code-block:: bash 787 788 salt-run manage.bootstrap_psexec hosts='host1,host2' 789 salt-run manage.bootstrap_psexec hosts='host1,host2' version='0.17' username='DOMAIN\\Administrator' 790 salt-run manage.bootstrap_psexec hosts='host1,host2' installer_url='http://exampledomain/salt-installer.exe' 791 """ 792 793 if not installer_url: 794 base_url = "https://repo.saltproject.io/windows/" 795 source = urllib.request.urlopen(base_url).read() 796 salty_rx = re.compile( 797 '>(Salt-Minion-(.+?)-(.+)-Setup.exe)</a></td><td align="right">(.*?)\\s*<' 798 ) 799 source_list = sorted( 800 [ 801 [path, ver, plat, time.strptime(date, "%d-%b-%Y %H:%M")] 802 for path, ver, plat, date in salty_rx.findall(source) 803 ], 804 key=operator.itemgetter(3), 805 reverse=True, 806 ) 807 if version: 808 source_list = [s for s in source_list if s[1] == version] 809 if arch: 810 source_list = [s for s in source_list if s[2] == arch] 811 812 if not source_list: 813 return -1 814 815 version = source_list[0][1] 816 arch = source_list[0][2] 817 installer_url = base_url + source_list[0][0] 818 819 # It's no secret that Windows is notoriously command-line hostile. 820 # Win 7 and newer can use PowerShell out of the box, but to reach 821 # all those XP and 2K3 machines we must suppress our gag-reflex 822 # and use VB! 823 824 # The following script was borrowed from an informative article about 825 # downloading exploit payloads for malware. Nope, no irony here. 826 # http://www.greyhathacker.net/?p=500 827 vb_script = """strFileURL = "{0}" 828strHDLocation = "{1}" 829Set objXMLHTTP = CreateObject("MSXML2.XMLHTTP") 830objXMLHTTP.open "GET", strFileURL, false 831objXMLHTTP.send() 832If objXMLHTTP.Status = 200 Then 833Set objADOStream = CreateObject("ADODB.Stream") 834objADOStream.Open 835objADOStream.Type = 1 836objADOStream.Write objXMLHTTP.ResponseBody 837objADOStream.Position = 0 838objADOStream.SaveToFile strHDLocation 839objADOStream.Close 840Set objADOStream = Nothing 841End if 842Set objXMLHTTP = Nothing 843Set objShell = CreateObject("WScript.Shell") 844objShell.Exec("{1}{2}")""" 845 846 vb_saltexec = "saltinstall.exe" 847 vb_saltexec_args = " /S /minion-name=%COMPUTERNAME%" 848 if master: 849 vb_saltexec_args += " /master={}".format(master) 850 851 # One further thing we need to do; the Windows Salt minion is pretty 852 # self-contained, except for the Microsoft Visual C++ 2008 runtime. 853 # It's tiny, so the bootstrap will attempt a silent install. 854 vb_vcrunexec = "vcredist.exe" 855 if arch == "AMD64": 856 vb_vcrun = vb_script.format( 857 "http://download.microsoft.com/download/d/2/4/d242c3fb-da5a-4542-ad66-f9661d0a8d19/vcredist_x64.exe", 858 vb_vcrunexec, 859 " /q", 860 ) 861 else: 862 vb_vcrun = vb_script.format( 863 "http://download.microsoft.com/download/d/d/9/dd9a82d0-52ef-40db-8dab-795376989c03/vcredist_x86.exe", 864 vb_vcrunexec, 865 " /q", 866 ) 867 868 vb_salt = vb_script.format(installer_url, vb_saltexec, vb_saltexec_args) 869 870 # PsExec doesn't like extra long arguments; save the instructions as a batch 871 # file so we can fire it over for execution. 872 873 # First off, change to the local temp directory, stop salt-minion (if 874 # running), and remove the master's public key. 875 # This is to accommodate for reinstalling Salt over an old or broken build, 876 # e.g. if the master address is changed, the salt-minion process will fail 877 # to authenticate and quit; which means infinite restarts under Windows. 878 batch = ( 879 "cd /d %TEMP%\nnet stop salt-minion\ndel" 880 " c:\\salt\\conf\\pki\\minion\\minion_master.pub\n" 881 ) 882 883 # Speaking of command-line hostile, cscript only supports reading a script 884 # from a file. Glue it together line by line. 885 for x, y in ((vb_vcrunexec, vb_vcrun), (vb_saltexec, vb_salt)): 886 vb_lines = y.split("\n") 887 batch += ( 888 "\ndel " 889 + x 890 + "\n@echo " 891 + vb_lines[0] 892 + " >" 893 + x 894 + ".vbs\n@echo " 895 + (" >>" + x + ".vbs\n@echo ").join(vb_lines[1:]) 896 + " >>" 897 + x 898 + ".vbs\ncscript.exe /NoLogo " 899 + x 900 + ".vbs" 901 ) 902 903 batch_path = tempfile.mkstemp(suffix=".bat")[1] 904 with salt.utils.files.fopen(batch_path, "wb") as batch_file: 905 batch_file.write(batch) 906 907 for host in hosts.split(","): 908 argv = ["psexec", "\\\\" + host] 909 if username: 910 argv += ["-u", username] 911 if password: 912 argv += ["-p", password] 913 argv += ["-h", "-c", batch_path] 914 subprocess.call(argv) 915