Add new options to salt cloud for Windows installer

This commit is contained in:
Shane Lee 2024-04-16 12:49:57 -06:00 committed by Daniel Wozniak
parent aad71fdbcf
commit 352b83aea7
4 changed files with 127 additions and 21 deletions

2
changelog/61318.added.md Normal file
View file

@ -0,0 +1,2 @@
Added two new options, ``win_delay_start`` and ``win_install_dir``, to pass to
the Windows installer in salt-cloud

View file

@ -31,7 +31,6 @@ which Salt Cloud is running. See
and using the Salt Minion Windows installer. and using the Salt Minion Windows installer.
.. _new-pywinrm: .. _new-pywinrm:
Self Signed Certificates with WinRM 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``. 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 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. ``False`` if the certificate is self signed and not verifiable.
Firewall Settings Firewall Settings
================= =================
Because Salt Cloud makes use of `smbclient` and `winexe`, port 445 must be open Because Salt Cloud makes use of ``smbclient`` and ``winexe``, port 445 must be
on the target image. This port is not generally open by default on a standard open on the target image. This port is not generally open by default on a
Windows distribution, and care must be taken to use an image in which this port standard Windows distribution, and care must be taken to use an image in which
is open, or the Windows firewall is disabled. 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 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: script would open up port 445, and apply the changes:
.. code-block:: text .. code-block:: text
@ -62,7 +61,7 @@ script would open up port 445, and apply the changes:
</powershell> </powershell>
For EC2, this script may be saved as a file, and specified in the provider or 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 .. code-block:: yaml
@ -142,9 +141,9 @@ the following userdata example:
Restart-Service winrm Restart-Service winrm
</powershell> </powershell>
No certificate store is available by default on EC2 images and creating No certificate store is available by default on EC2 images and creating one does
one does not seem possible without an MMC (cannot be automated). To use the not seem possible without an MMC (cannot be automated). To use the default EC2
default EC2 Windows images the above copies the RDP store. Windows images the above copies the RDP store.
Configuration Configuration
============= =============
@ -168,23 +167,42 @@ Setting the installer in ``/etc/salt/cloud.providers``:
win_password: letmein win_password: letmein
smb_port: 445 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. 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 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 Auto-Generated Passwords on EC2
=============================== ===============================
On EC2, when the `win_password` is set to `auto`, Salt Cloud will query EC2 for On EC2, when the ``win_password`` is set to ``auto``, Salt Cloud will query EC2
an auto-generated password. This password is expected to take at least 4 minutes for an auto-generated password. This password is expected to take at least 4
to generate, adding additional time to the deploy process. 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 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 in a message encrypted with the specified ``keyname``. This requires that the
appropriate `private_key` file is also specified. Such a profile configuration appropriate ``private_key`` file is also specified. Such a profile configuration
might look like: might look like:
.. code-block:: yaml .. code-block:: yaml

View file

@ -574,6 +574,13 @@ def bootstrap(vm_, opts=None):
"smb_port", vm_, opts, default=445 "smb_port", vm_, opts, default=445
) )
deploy_kwargs["win_installer"] = win_installer 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_) minion = minion_config(opts, vm_)
deploy_kwargs["master"] = minion["master"] deploy_kwargs["master"] = minion["master"]
deploy_kwargs["username"] = salt.config.get_cloud_config_value( deploy_kwargs["username"] = salt.config.get_cloud_config_value(
@ -1236,6 +1243,8 @@ def deploy_windows(
port_timeout=15, port_timeout=15,
preseed_minion_keys=None, preseed_minion_keys=None,
win_installer=None, win_installer=None,
win_delay_start=False,
win_install_dir="",
master=None, master=None,
tmp_dir="C:\\salttmp", tmp_dir="C:\\salttmp",
opts=None, opts=None,
@ -1357,6 +1366,11 @@ def deploy_windows(
f"/master={_format_master_param(master)}", f"/master={_format_master_param(master)}",
f"/minion-name={name}", 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: if use_winrm:
winrm_cmd(winrm_session, cmd, args) winrm_cmd(winrm_session, cmd, args)
@ -1423,7 +1437,7 @@ def deploy_windows(
if ret_code != 0: if ret_code != 0:
return False 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( stdout, stderr, ret_code = run_psexec_command(
"cmd.exe", "/c net start salt-minion", host, username, password "cmd.exe", "/c net start salt-minion", host, username, password
) )

View file

@ -449,6 +449,78 @@ def test_deploy_windows_programdata_minion_conf():
mock_smb.put_str.assert_called_with(config, expected, conn=mock_conn) 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.") @pytest.mark.skip_unless_on_windows(reason="Only applicable for Windows.")
def test_winrm_pinnned_version(): def test_winrm_pinnned_version():
""" """