From 352b83aea7d3c1cabe750073accd839474edeff9 Mon Sep 17 00:00:00 2001 From: Shane Lee Date: Tue, 16 Apr 2024 12:49:57 -0600 Subject: [PATCH] Add new options to salt cloud for Windows installer --- changelog/61318.added.md | 2 + doc/topics/cloud/windows.rst | 58 ++++++++++++++------- salt/utils/cloud.py | 16 +++++- tests/pytests/unit/utils/test_cloud.py | 72 ++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 changelog/61318.added.md diff --git a/changelog/61318.added.md b/changelog/61318.added.md new file mode 100644 index 00000000000..3b9c5ed6315 --- /dev/null +++ b/changelog/61318.added.md @@ -0,0 +1,2 @@ +Added two new options, ``win_delay_start`` and ``win_install_dir``, to pass to +the Windows installer in salt-cloud diff --git a/doc/topics/cloud/windows.rst b/doc/topics/cloud/windows.rst index a80014faf7f..983e62cb0d2 100644 --- a/doc/topics/cloud/windows.rst +++ b/doc/topics/cloud/windows.rst @@ -31,7 +31,6 @@ which Salt Cloud is running. See and using the Salt Minion Windows installer. - .. _new-pywinrm: Self Signed Certificates with WinRM @@ -39,18 +38,18 @@ Self Signed Certificates with WinRM Salt-Cloud can use versions of ``pywinrm<=0.1.1`` or ``pywinrm>=0.2.1``. -For versions greater than `0.2.1`, ``winrm_verify_ssl`` needs to be set to -`False` if the certificate is self signed and not verifiable. +For versions greater than ``0.2.1``, ``winrm_verify_ssl`` needs to be set to +``False`` if the certificate is self signed and not verifiable. Firewall Settings ================= -Because Salt Cloud makes use of `smbclient` and `winexe`, port 445 must be open -on the target image. This port is not generally open by default on a standard -Windows distribution, and care must be taken to use an image in which this port -is open, or the Windows firewall is disabled. +Because Salt Cloud makes use of ``smbclient`` and ``winexe``, port 445 must be +open on the target image. This port is not generally open by default on a +standard Windows distribution, and care must be taken to use an image in which +this port is open, or the Windows firewall is disabled. If supported by the cloud provider, a PowerShell script may be used to open up -this port automatically, using the cloud provider's `userdata`. The following +this port automatically, using the cloud provider's ``userdata``. The following script would open up port 445, and apply the changes: .. code-block:: text @@ -62,7 +61,7 @@ script would open up port 445, and apply the changes: For EC2, this script may be saved as a file, and specified in the provider or -profile configuration as `userdata_file`. For instance: +profile configuration as ``userdata_file``. For instance: .. code-block:: yaml @@ -142,9 +141,9 @@ the following userdata example: Restart-Service winrm -No certificate store is available by default on EC2 images and creating -one does not seem possible without an MMC (cannot be automated). To use the -default EC2 Windows images the above copies the RDP store. +No certificate store is available by default on EC2 images and creating one does +not seem possible without an MMC (cannot be automated). To use the default EC2 +Windows images the above copies the RDP store. Configuration ============= @@ -168,23 +167,42 @@ Setting the installer in ``/etc/salt/cloud.providers``: win_password: letmein smb_port: 445 -The default Windows user is `Administrator`, and the default Windows password +The default Windows user is ``Administrator``, and the default Windows password is blank. -If WinRM is to be used ``use_winrm`` needs to be set to `True`. ``winrm_port`` +If WinRM is to be used ``use_winrm`` needs to be set to ``True``. ``winrm_port`` can be used to specify a custom port (must be HTTPS listener). And -``winrm_verify_ssl`` can be set to `False` to use a self signed certificate. +``winrm_verify_ssl`` can be set to ``False`` to use a self signed certificate. +Two new options have been added to allow you to set some additional parameters +to pass to the installer. ``win_delay_start`` will set the minion service to +start delayed. ``win_install_dir`` will allow you to specify the Salt install +location. + +.. code-block:: yaml + + my-softlayer: + driver: softlayer + user: MYUSER1138 + apikey: 'e3b68aa711e6deadc62d5b76355674beef7cc3116062ddbacafe5f7e465bfdc9' + minion: + master: saltmaster.example.com + win_installer: /root/Salt-Minion-2014.7.0-AMD64-Setup.exe + win_delay_start: True + win_install_dir: D:\Program Files\Salt Project\Salt + win_username: Administrator + win_password: letmein + smb_port: 445 Auto-Generated Passwords on EC2 =============================== -On EC2, when the `win_password` is set to `auto`, Salt Cloud will query EC2 for -an auto-generated password. This password is expected to take at least 4 minutes -to generate, adding additional time to the deploy process. +On EC2, when the ``win_password`` is set to ``auto``, Salt Cloud will query EC2 +for an auto-generated password. This password is expected to take at least 4 +minutes to generate, adding additional time to the deploy process. When the EC2 API is queried for the auto-generated password, it will be returned -in a message encrypted with the specified `keyname`. This requires that the -appropriate `private_key` file is also specified. Such a profile configuration +in a message encrypted with the specified ``keyname``. This requires that the +appropriate ``private_key`` file is also specified. Such a profile configuration might look like: .. code-block:: yaml diff --git a/salt/utils/cloud.py b/salt/utils/cloud.py index ab389610938..2217795833b 100644 --- a/salt/utils/cloud.py +++ b/salt/utils/cloud.py @@ -574,6 +574,13 @@ def bootstrap(vm_, opts=None): "smb_port", vm_, opts, default=445 ) deploy_kwargs["win_installer"] = win_installer + deploy_kwargs["win_delay_start"] = salt.config.get_cloud_config_value( + "win_delay_start", vm_, opts, default="" + ) + deploy_kwargs["win_install_dir"] = salt.config.get_cloud_config_value( + "win_install_dir", vm_, opts, default="" + ) + minion = minion_config(opts, vm_) deploy_kwargs["master"] = minion["master"] deploy_kwargs["username"] = salt.config.get_cloud_config_value( @@ -1236,6 +1243,8 @@ def deploy_windows( port_timeout=15, preseed_minion_keys=None, win_installer=None, + win_delay_start=False, + win_install_dir="", master=None, tmp_dir="C:\\salttmp", opts=None, @@ -1357,6 +1366,11 @@ def deploy_windows( f"/master={_format_master_param(master)}", f"/minion-name={name}", ] + if win_delay_start: + args.append("/start-minion-delayed") + + if win_install_dir: + args.append(f'/install-dir="{win_install_dir}"') if use_winrm: winrm_cmd(winrm_session, cmd, args) @@ -1423,7 +1437,7 @@ def deploy_windows( if ret_code != 0: return False - log.debug("Run psexec: sc start salt-minion") + log.debug("Run psexec: net start salt-minion") stdout, stderr, ret_code = run_psexec_command( "cmd.exe", "/c net start salt-minion", host, username, password ) diff --git a/tests/pytests/unit/utils/test_cloud.py b/tests/pytests/unit/utils/test_cloud.py index 1c15f356280..bbe9f83b55b 100644 --- a/tests/pytests/unit/utils/test_cloud.py +++ b/tests/pytests/unit/utils/test_cloud.py @@ -449,6 +449,78 @@ def test_deploy_windows_programdata_minion_conf(): mock_smb.put_str.assert_called_with(config, expected, conn=mock_conn) +@pytest.mark.skip_unless_on_windows(reason="Only applicable for Windows.") +def test_deploy_windows_install_delay_start(): + mock_true = MagicMock(return_value=True) + mock_tuple = MagicMock(return_value=(0, 0, 0)) + mock_conn = MagicMock() + + with patch("salt.utils.smb", MagicMock()) as mock_smb: + mock_smb.get_conn.return_value = mock_conn + mock_smb.mkdirs.return_value = None + mock_smb.put_file.return_value = None + mock_smb.put_str.return_value = None + mock_smb.delete_file.return_value = None + mock_smb.delete_directory.return_value = None + with patch("time.sleep", MagicMock()), patch.object( + cloud, "wait_for_port", mock_true + ), patch.object(cloud, "fire_event", MagicMock()), patch.object( + cloud, "wait_for_psexecsvc", mock_true + ), patch.object( + cloud, "run_psexec_command", mock_tuple + ) as mock_psexec: + minion_conf = {"master": "test-master"} + cloud.deploy_windows( + host="test", + minion_conf=minion_conf, + win_installer="install.exe", + win_delay_start=True, + ) + mock_psexec.assert_any_call( + "c:\\salttemp\\install.exe", + "/S /master=None /minion-name=None /start-minion-delayed", + "test", + "Administrator", + None, + ) + + +@pytest.mark.skip_unless_on_windows(reason="Only applicable for Windows.") +def test_deploy_windows_install_install_dir(): + mock_true = MagicMock(return_value=True) + mock_tuple = MagicMock(return_value=(0, 0, 0)) + mock_conn = MagicMock() + + with patch("salt.utils.smb", MagicMock()) as mock_smb: + mock_smb.get_conn.return_value = mock_conn + mock_smb.mkdirs.return_value = None + mock_smb.put_file.return_value = None + mock_smb.put_str.return_value = None + mock_smb.delete_file.return_value = None + mock_smb.delete_directory.return_value = None + with patch("time.sleep", MagicMock()), patch.object( + cloud, "wait_for_port", mock_true + ), patch.object(cloud, "fire_event", MagicMock()), patch.object( + cloud, "wait_for_psexecsvc", mock_true + ), patch.object( + cloud, "run_psexec_command", mock_tuple + ) as mock_psexec: + minion_conf = {"master": "test-master"} + cloud.deploy_windows( + host="test", + minion_conf=minion_conf, + win_installer="install.exe", + win_install_dir="C:\\salt", + ) + mock_psexec.assert_any_call( + "c:\\salttemp\\install.exe", + '/S /master=None /minion-name=None /install-dir="C:\\salt"', + "test", + "Administrator", + None, + ) + + @pytest.mark.skip_unless_on_windows(reason="Only applicable for Windows.") def test_winrm_pinnned_version(): """