From 81e025b41478fbe62b80a89ef184ea820c440ed9 Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Wed, 14 Aug 2024 09:34:54 -0700 Subject: [PATCH] ansible-test - Add Windows remote connection option --- .../fragments/ansible-test-windows-remote.yml | 2 + .../test_connection.inventory.j2 | 2 +- .../test_connection.inventory.j2 | 2 +- .../ansible_test/_data/completion/windows.txt | 8 ++-- .../cli/parsers/key_value_parsers.py | 3 ++ test/lib/ansible_test/_internal/completion.py | 2 + .../ansible_test/_internal/host_configs.py | 10 +++++ .../ansible_test/_internal/host_profiles.py | 22 +++++----- test/lib/ansible_test/_internal/util.py | 40 +++++++++++++++++++ 9 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 changelogs/fragments/ansible-test-windows-remote.yml diff --git a/changelogs/fragments/ansible-test-windows-remote.yml b/changelogs/fragments/ansible-test-windows-remote.yml new file mode 100644 index 00000000000..2ab8bd79266 --- /dev/null +++ b/changelogs/fragments/ansible-test-windows-remote.yml @@ -0,0 +1,2 @@ +minor_changes: + - ansible-test - Connection options can be set for ansible-test managed remote Windows instances. diff --git a/test/integration/targets/connection_psrp/test_connection.inventory.j2 b/test/integration/targets/connection_psrp/test_connection.inventory.j2 index d2d3a4929c2..6e199d1f08c 100644 --- a/test/integration/targets/connection_psrp/test_connection.inventory.j2 +++ b/test/integration/targets/connection_psrp/test_connection.inventory.j2 @@ -1,6 +1,6 @@ [windows] {% for host in vars.groups.windows %} -{{ host }} ansible_host={{ hostvars[host]['ansible_host'] }} ansible_port={{ hostvars[host]['ansible_port'] }} ansible_user={{ hostvars[host]['ansible_user'] }} ansible_password={{ hostvars[host]['ansible_password'] }} +{{ host }} ansible_host={{ hostvars[host]['ansible_host'] }}{% if hostvars[host]['ansible_connection'] != 'ssh' %} ansible_port={{ hostvars[host]['ansible_port'] }}{% endif %} ansible_user={{ hostvars[host]['ansible_user'] }} ansible_password={{ hostvars[host]['ansible_password'] | default(hostvars[host]['ansible_test_connection_password']) }} {% endfor %} [windows:vars] diff --git a/test/integration/targets/connection_winrm/test_connection.inventory.j2 b/test/integration/targets/connection_winrm/test_connection.inventory.j2 index 7c4f3dc9e61..c5671401c7d 100644 --- a/test/integration/targets/connection_winrm/test_connection.inventory.j2 +++ b/test/integration/targets/connection_winrm/test_connection.inventory.j2 @@ -1,6 +1,6 @@ [windows] {% for host in vars.groups.windows %} -{{ host }} ansible_host={{ hostvars[host]['ansible_host'] }} ansible_port={{ hostvars[host]['ansible_port'] }} ansible_user={{ hostvars[host]['ansible_user'] }} ansible_password={{ hostvars[host]['ansible_password'] }} +{{ host }} ansible_host={{ hostvars[host]['ansible_host'] }}{% if hostvars[host]['ansible_connection'] != 'ssh' %} ansible_port={{ hostvars[host]['ansible_port'] }}{% endif %} ansible_user={{ hostvars[host]['ansible_user'] }} ansible_password={{ hostvars[host]['ansible_password'] | default(hostvars[host]['ansible_test_connection_password']) }} {% endfor %} [windows:vars] diff --git a/test/lib/ansible_test/_data/completion/windows.txt b/test/lib/ansible_test/_data/completion/windows.txt index 860a2e32a7d..85d29810aca 100644 --- a/test/lib/ansible_test/_data/completion/windows.txt +++ b/test/lib/ansible_test/_data/completion/windows.txt @@ -1,4 +1,4 @@ -windows/2016 provider=aws arch=x86_64 -windows/2019 provider=aws arch=x86_64 -windows/2022 provider=aws arch=x86_64 -windows provider=aws arch=x86_64 +windows/2016 provider=aws arch=x86_64 connection=winrm+http +windows/2019 provider=aws arch=x86_64 connection=winrm+https +windows/2022 provider=aws arch=x86_64 connection=winrm+https +windows provider=aws arch=x86_64 connection=winrm+https diff --git a/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py b/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py index a046e51a4ab..e389fd5c1a8 100644 --- a/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py +++ b/test/lib/ansible_test/_internal/cli/parsers/key_value_parsers.py @@ -17,6 +17,7 @@ from ...completion import ( from ...util import ( REMOTE_ARCHITECTURES, + WINDOWS_CONNECTIONS, ) from ...host_configs import ( @@ -177,6 +178,7 @@ class WindowsRemoteKeyValueParser(KeyValueParser): return dict( provider=ChoicesParser(REMOTE_PROVIDERS), arch=ChoicesParser(REMOTE_ARCHITECTURES), + connection=ChoicesParser(WINDOWS_CONNECTIONS), ) def document(self, state: DocumentationState) -> t.Optional[str]: @@ -186,6 +188,7 @@ class WindowsRemoteKeyValueParser(KeyValueParser): state.sections[f'target {section_name} (comma separated):'] = '\n'.join([ f' provider={ChoicesParser(REMOTE_PROVIDERS).document(state)}', f' arch={ChoicesParser(REMOTE_ARCHITECTURES).document(state)}', + f' connection={ChoicesParser(WINDOWS_CONNECTIONS).document(state)}', ]) return f'{{{section_name}}}' diff --git a/test/lib/ansible_test/_internal/completion.py b/test/lib/ansible_test/_internal/completion.py index 31f890872f5..bbb39ba00f7 100644 --- a/test/lib/ansible_test/_internal/completion.py +++ b/test/lib/ansible_test/_internal/completion.py @@ -246,6 +246,8 @@ class PosixRemoteCompletionConfig(RemoteCompletionConfig, PythonCompletionConfig class WindowsRemoteCompletionConfig(RemoteCompletionConfig): """Configuration for remote Windows platforms.""" + connection: str = '' + TCompletionConfig = t.TypeVar('TCompletionConfig', bound=CompletionConfig) diff --git a/test/lib/ansible_test/_internal/host_configs.py b/test/lib/ansible_test/_internal/host_configs.py index ddc4727ccd1..8e9817004b6 100644 --- a/test/lib/ansible_test/_internal/host_configs.py +++ b/test/lib/ansible_test/_internal/host_configs.py @@ -399,10 +399,20 @@ class WindowsConfig(HostConfig, metaclass=abc.ABCMeta): class WindowsRemoteConfig(RemoteConfig, WindowsConfig): """Configuration for a remote Windows host.""" + connection: t.Optional[str] = None + def get_defaults(self, context: HostContext) -> WindowsRemoteCompletionConfig: """Return the default settings.""" return filter_completion(windows_completion()).get(self.name) or windows_completion().get(self.platform) + def apply_defaults(self, context: HostContext, defaults: CompletionConfig) -> None: + """Apply default settings.""" + assert isinstance(defaults, WindowsRemoteCompletionConfig) + + super().apply_defaults(context, defaults) + + self.connection = self.connection or defaults.connection + @dataclasses.dataclass class WindowsInventoryConfig(InventoryConfig, WindowsConfig): diff --git a/test/lib/ansible_test/_internal/host_profiles.py b/test/lib/ansible_test/_internal/host_profiles.py index 39fe7d209ab..9258bd19e15 100644 --- a/test/lib/ansible_test/_internal/host_profiles.py +++ b/test/lib/ansible_test/_internal/host_profiles.py @@ -56,6 +56,7 @@ from .util import ( InternalError, HostConnectionError, ANSIBLE_TEST_TARGET_ROOT, + WINDOWS_CONNECTION_VARIABLES, ) from .util_common import ( @@ -1367,23 +1368,18 @@ class WindowsRemoteProfile(RemoteProfile[WindowsRemoteConfig]): connection = core_ci.connection variables: dict[str, t.Optional[t.Union[str, int]]] = dict( - ansible_connection='winrm', - ansible_pipelining='yes', - ansible_winrm_server_cert_validation='ignore', ansible_host=connection.hostname, - ansible_port=connection.port, + # ansible_port is intentionally not set using connection.port -- connection-specific variables can set this instead ansible_user=connection.username, - ansible_password=connection.password, - ansible_ssh_private_key_file=core_ci.ssh_key.key, + ansible_ssh_private_key_file=core_ci.ssh_key.key, # required for scenarios which change the connection plugin to SSH + ansible_test_connection_password=connection.password, # required for scenarios which change the connection plugin to require a password ) - # HACK: force 2016 to use NTLM + HTTP message encryption - if self.config.version == '2016': - variables.update( - ansible_winrm_transport='ntlm', - ansible_winrm_scheme='http', - ansible_port='5985', - ) + variables.update(ansible_connection=self.config.connection.split('+')[0]) + variables.update(WINDOWS_CONNECTION_VARIABLES[self.config.connection]) + + if variables.pop('use_password'): + variables.update(ansible_password=connection.password) return variables diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py index 903cbcc50aa..23d6a81209a 100644 --- a/test/lib/ansible_test/_internal/util.py +++ b/test/lib/ansible_test/_internal/util.py @@ -134,6 +134,46 @@ class Architecture: REMOTE_ARCHITECTURES = list(value for key, value in Architecture.__dict__.items() if not key.startswith('__')) +WINDOWS_CONNECTION_VARIABLES: dict[str, t.Any] = { + 'psrp+http': dict( + ansible_port=5985, + ansible_psrp_protocol='http', + use_password=True, + ), + 'psrp+https': dict( + ansible_port=5986, + ansible_psrp_protocol='https', + ansible_psrp_cert_validation='ignore', + use_password=True, + ), + 'ssh+key': dict( + ansible_port=22, + ansible_shell_type='powershell', + use_password=False, + ), + 'ssh+password': dict( + ansible_port=22, + ansible_shell_type='powershell', + use_password=True, + ), + 'winrm+http': dict( + ansible_port=5985, + ansible_winrm_scheme='http', + ansible_winrm_transport='ntlm', + use_password=True, + ), + 'winrm+https': dict( + ansible_port=5986, + ansible_winrm_scheme='https', + ansible_winrm_server_cert_validation='ignore', + use_password=True, + ), +} +"""Dictionary of Windows connection types and variables required to use them.""" + +WINDOWS_CONNECTIONS = list(WINDOWS_CONNECTION_VARIABLES) + + def is_valid_identifier(value: str) -> bool: """Return True if the given value is a valid non-keyword Python identifier, otherwise return False.""" return value.isidentifier() and not keyword.iskeyword(value)