diff --git a/changelogs/fragments/9101-jenkins_node-add-launch_ssh-option.yaml b/changelogs/fragments/9101-jenkins_node-add-launch_ssh-option.yaml new file mode 100644 index 00000000000..4a8e2e39af4 --- /dev/null +++ b/changelogs/fragments/9101-jenkins_node-add-launch_ssh-option.yaml @@ -0,0 +1,2 @@ +minor_changes: + - jenkins_node - implement ``launch_ssh`` option which will configure a node to use the SSH launcher if specified and common suboptions (https://github.com/ansible-collections/community.general/pull/9101). diff --git a/plugins/modules/jenkins_node.py b/plugins/modules/jenkins_node.py index 9406eab4c5b..571f1d2b044 100644 --- a/plugins/modules/jenkins_node.py +++ b/plugins/modules/jenkins_node.py @@ -75,6 +75,62 @@ possible. In this case, a warning will be issued. type: str version_added: 10.0.0 + launch_ssh: + description: + - When specified, sets the Jenkins node to launch by SSH. + type: dict + suboptions: + host: + description: + - When specified, sets the SSH host name. + type: str + port: + description: + - When specified, sets the SSH port number. + type: int + credentials_id: + description: + - When specified, sets the Jenkins credential used for SSH authentication by + its ID. + type: str + host_key_verify_none: + description: + - When set, sets the SSH host key non-verifying strategy. + type: bool + choices: + - true + host_key_verify_known_hosts: + description: + - When set, sets the SSH host key known hosts file verification strategy. + type: bool + choices: + - true + host_key_verify_provided: + description: + - When specified, sets the SSH host key manually provided verification strategy. + type: dict + suboptions: + algorithm: + description: + - Key type, for example V(ssh-rsa). + type: str + required: true + key: + description: + - Key value. + type: str + required: true + host_key_verify_trusted: + description: + - When specified, sets the SSH host key manually trusted verification strategy. + type: dict + suboptions: + allow_initial: + description: + - When specified, enables or disables the requiring manual verification of + the first connected host for this node. + type: bool + version_added: 10.1.0 ''' EXAMPLES = ''' @@ -101,12 +157,23 @@ - label-2 - label-3 -- name: Set Jenkins node offline with offline message. +- name: Set Jenkins node offline with offline message community.general.jenkins_node: name: my-node state: disabled offline_message: > This node is offline for some reason. + +- name: > + Set Jenkins node to launch via SSH using stored credential and trust host key on + initial connection + community.general.jenkins_node: + name: my-node + launch_ssh: + host: my-node.test + credentials_id: deaddead-dead-dead-dead-deaddeaddead + host_key_verify_trusted: + allow_initial: yes ''' RETURN = ''' @@ -155,6 +222,7 @@ import sys import traceback +from abc import abstractmethod from xml.etree import ElementTree as et from ansible.module_utils.basic import AnsibleModule @@ -172,6 +240,376 @@ IS_PYTHON_2 = sys.version_info[0] <= 2 +if sys.version_info[0] <= 3 or sys.version_info[1] < 8: + class cached_property(object): # noqa + def __init__(self, func): + self.func = func + + def __get__(self, instance, cls): + if instance is None: + return self + + value = self.func(instance) + setattr(instance, self.func.__name__, value) + + return value +else: + from functools import cached_property + + +def bool_to_text(value): # type: (bool) -> str + return "true" if value else "false" + + +def text_to_bool(text): # type: (str) -> bool + if text == "true": + return True + + if text == "false": + return False + + raise ValueError("unexpected boolean text value '{}'".format(text)) + + +class Element: + def __init__(self, root): # type: (et.Element | None) -> None + self.root = root + + def get(self, key): # type: (str) -> str | None + return None if self.root is None else self.root.get(key) + + def set(self, key, value): # type: (str, str) -> None + self.root.set(key, value) + + def find(self, path): # type: (str) -> et.Element | None + return None if self.root is None else self.root.find(path) + + def remove(self, element): # type: (et.Element) -> None + self.root.remove(element) + + def append(self, element): # type: (et.Element) -> None + self.root.append(element) + + @property + def class_(self): # type: () -> str | None + return self.get("class") + + @class_.setter + def class_(self, value): # (str) -> None + self.set("class", value) + + +class Config: + @abstractmethod + def init(self): # type: () -> et.Element + """Initialize XML element from config. + + Returns: + Initialized XML element. + """ + raise NotImplementedError + + @abstractmethod + def update(self, root): # (et.Element) -> bool + """Update XML element with config. + + Args: + root: XML element to update. + + Returns: + True if was updated, False otherwise. + """ + raise NotImplementedError + + +class LauncherElement(Element): + TAG = "launcher" + + +class LauncherConfig(Config): + CLASS = "" + + +class SSHHostKeyVerifyElement(Element): + TAG = "sshHostKeyVerificationStrategy" + + +class SSHHostKeyVerifyConfig(Config): + CLASS = "" + + def init(self): # type: () -> et.Element + root = et.Element(SSHHostKeyVerifyElement.TAG) + + Element(root).class_ = self.CLASS + + return root + + def update(self, root): # type: (et.Element) -> bool + class_ = Element(root).class_ + + if class_ != self.CLASS: + raise ValueError("unexpected class {}: expected {}".format(class_, self.CLASS)) + + return False + + +class KnownHostsSSHHostKeyVerifyConfig(SSHHostKeyVerifyConfig): + CLASS = "hudson.plugins.sshslaves.verifiers.KnownHostsFileKeyVerificationStrategy" + + +class NoneSSHHostKeyVerifyConfig(SSHHostKeyVerifyConfig): + CLASS = "hudson.plugins.sshslaves.verifiers.NonVerifyingKeyVerificationStrategy" + + +class TrustedSSHHostKeyVerifyElement(SSHHostKeyVerifyElement): + @property + def allow_initial(self): # type: () -> et.Element | None + return self.find("requireInitialManualTrust") + + +class TrustedSSHHostKeyVerifyConfig(SSHHostKeyVerifyConfig): + CLASS = "hudson.plugins.sshslaves.verifiers.ManuallyTrustedKeyVerificationStrategy" + + TEMPLATE = """ + + false + +""".strip() + + def __init__(self, allow_initial=None): # type: (bool | None) -> None + self.allow_initial = allow_initial + + def init(self): # type: () -> et.Element + return et.fromstring(self.TEMPLATE) + + def update(self, root): # type: (et.Element) -> bool + super(TrustedSSHHostKeyVerifyConfig, self).update(root) + + wrapper = TrustedSSHHostKeyVerifyElement(root) + updated = False + + if self.allow_initial is not None: + if wrapper.allow_initial.text != bool_to_text(self.allow_initial): + wrapper.allow_initial.text = bool_to_text(self.allow_initial) + updated = True + + return updated + + +class ProvidedSSHHostKeyVerifyElement(SSHHostKeyVerifyElement): + class Key(Element): + TAG = "key" + + @property + def algorithm(self): # type: () -> et.Element | None + return self.find("algorithm") + + @property + def key(self): # type: () -> et.Element | None + return self.find("key") + + @property + def key(self): # type: () -> ProvidedSSHHostKeyVerifyElement.Key | None + root = self.find('key') + + if root is None: + return None + + return ProvidedSSHHostKeyVerifyElement.Key(root) + + +class ProvidedSSHHostKeyVerifyConfig(KnownHostsSSHHostKeyVerifyConfig): + CLASS = "hudson.plugins.sshslaves.verifiers.ManuallyProvidedKeyVerificationStrategy" + + TEMPLATE = """ + + + + + + +""".strip() + + def __init__(self, algorithm, key): # type: (str, str) -> None + self.algorithm = algorithm + self.key = key + + def init(self): # type: () -> et.Element + return et.fromstring(self.TEMPLATE) + + def update(self, root): # type: (et.Element) -> bool + super(ProvidedSSHHostKeyVerifyConfig, self).update(root) + + wrapper = ProvidedSSHHostKeyVerifyElement(root) + updated = False + + if wrapper.key.algorithm.text != self.algorithm: + wrapper.key.algorithm.text = self.algorithm + updated = True + + if wrapper.key.key.text != self.key: + wrapper.key.key.text = self.key + updated = True + + return updated + + +class SSHLauncherElement(LauncherElement): + @property + def host(self): # type: () -> et.Element | None + return self.find("host") + + @host.setter + def host(self, element): + if self.host is not None: + self.remove(self.host) + + self.append(element) + + def ensure_host(self): + if self.host is None: + return et.SubElement(self.root, "host") + + return self.host + + @property + def port(self): + return self.find("port") + + @property + def credentials_id(self): + return self.find("credentialsId") + + def ensure_credentials_id(self): + if self.credentials_id is None: + return et.SubElement(self.root, "credentialsId") + + return self.credentials_id + + @property + def host_key_verify(self): + return self.find(SSHHostKeyVerifyElement.TAG) + + @host_key_verify.setter + def host_key_verify(self, element): + if self.host_key_verify is not None: + self.remove(self.host_key_verify) + + self.append(element) + + +class SSHLauncherConfig(Config): + CLASS = "hudson.plugins.sshslaves.SSHLauncher" + + def __init__( + self, + host=None, + port=None, + credentials_id=None, + host_key_verify=None, + ): # type: (str | None, int | None, str | None, SSHHostKeyVerifyConfig | None) -> None + self.host = host + self.port = port + self.credentials_id = credentials_id + self.host_key_verify = host_key_verify + + TEMPLATE = """ + + 22 + + 60 + 10 + 15 + true + +""".strip() + + def init(self): # type: () -> et.Element + root = et.fromstring(self.TEMPLATE) + + wrapper = SSHLauncherElement(root) + + if self.host_key_verify is not None: + wrapper.host_key_verify = self.host_key_verify.init() + + return root + + def update(self, root): # type: (et.Element) -> bool + wrapper = SSHLauncherElement(root) + updated = False + + if self.host is not None: + if wrapper.host is None: + wrapper.ensure_host() + updated = True + + if wrapper.host.text != self.host: + wrapper.host.text = self.host + updated = True + + if self.port is not None: + if wrapper.port.text != str(self.port): + wrapper.port.text = str(self.port) + updated = True + + if self.credentials_id is not None: + if wrapper.credentials_id is None: + wrapper.ensure_credentials_id() + updated = True + + if wrapper.credentials_id.text != self.credentials_id: + wrapper.credentials_id.text = self.credentials_id + updated = True + + if self.host_key_verify is not None: + if Element(wrapper.host_key_verify).class_ != self.host_key_verify.CLASS: + wrapper.host_key_verify = self.host_key_verify.init() + updated = True + + if self.host_key_verify.update(wrapper.host_key_verify): + updated = True + + return updated + + +def ssh_host_key_verify_config(args): # type: (dict) -> SSHHostKeyVerifyConfig | None + """Get SSH host key verify config from args. + + Args: + args: SSH launcher args. + + Returns: + SSH host key verify config. + """ + if args.get("host_key_verify_none"): + return NoneSSHHostKeyVerifyConfig() + + if args.get('host_key_verify_known_hosts'): + return KnownHostsSSHHostKeyVerifyConfig() + + if args.get("host_key_verify_provided"): + return ProvidedSSHHostKeyVerifyConfig( + args["host_key_verify_provided"]["algorithm"], + args["host_key_verify_provided"]["key"], + ) + + if args.get("host_key_verify_trusted"): + return TrustedSSHHostKeyVerifyConfig( + allow_initial=args["host_key_verify_trusted"].get("allow_initial") + ) + + return None + + +def ssh_launcher_args_config(args): # type: (dict) -> SSHLauncherConfig + return SSHLauncherConfig( + host=args.get("host"), + port=args.get("port"), + credentials_id=args.get("credentials_id"), + host_key_verify=ssh_host_key_verify_config(args), + ) + + class JenkinsNode: def __init__(self, module): self.module = module @@ -211,6 +649,13 @@ def __init__(self, module): 'warnings': [], } + @cached_property + def launch(self): # type: () -> LauncherConfig | None + if self.module.params["launch_ssh"]: + return ssh_launcher_args_config(self.module.params["launch_ssh"]) + + return None + def get_jenkins_instance(self): try: if self.user and self.token: @@ -222,6 +667,25 @@ def get_jenkins_instance(self): except Exception as e: self.module.fail_json(msg='Unable to connect to Jenkins server, %s' % to_native(e)) + def configure_launch(self, config): # type: (et.Element) -> bool + configured = False + + launcher = config.find(LauncherElement.TAG) + + if Element(launcher).class_ != self.launch.CLASS: + if launcher is not None: + config.remove(launcher) + + launcher = self.launch.init() + config.append(launcher) + + configured = True + + if self.launch.update(launcher): + configured = True + + return configured + def configure_node(self, present): if not present: # Node would only not be present if in check mode and if not present there @@ -235,6 +699,10 @@ def configure_node(self, present): data = self.instance.get_node_config(self.name) root = et.fromstring(data) + if self.launch is not None: + if self.configure_launch(root): + configured = True + if self.num_executors is not None: elem = root.find('numExecutors') if elem is None: @@ -467,6 +935,41 @@ def main(): num_executors=dict(type='int'), labels=dict(type='list', elements='str'), offline_message=dict(type='str'), + launch_ssh=dict( + type='dict', + options=dict( + host=dict(type='str'), + port=dict(type='int'), + credentials_id=dict(type='str'), + host_key_verify_none=dict(type='bool', choices=[True]), + host_key_verify_known_hosts=dict(type='bool', choices=[True]), + host_key_verify_provided=dict( + type='dict', + options=dict( + algorithm=dict(type='str', required=True), + key=dict( + type='str', + required=True, + no_log=False, # Is public key. + ), + ), + no_log=False, # Is not sensitive. + ), + host_key_verify_trusted=dict( + type='dict', + options=dict( + allow_initial=dict(type='bool'), + ), + no_log=False, # Is not sensitive. + ), + ), + mutually_exclusive=[[ + 'host_key_verify_none', + 'host_key_verify_known_hosts', + 'host_key_verify_provided', + 'host_key_verify_trusted', + ]], + ), ), supports_check_mode=True, ) diff --git a/tests/unit/plugins/modules/test_jenkins_node.py b/tests/unit/plugins/modules/test_jenkins_node.py index 7c2634744d4..d7640c8786a 100644 --- a/tests/unit/plugins/modules/test_jenkins_node.py +++ b/tests/unit/plugins/modules/test_jenkins_node.py @@ -841,3 +841,654 @@ def test_set_offline_message_when_not_equal_offline(get_instance, instance): assert not instance.disable_node.called assert result.value["changed"] is False + + +class TestConfigureLaunchSSH: + @fixture(autouse=True) + def node_exists(self, get_instance, instance): + instance.node_exists.return_value = True + + def test_configure_when_launcher_is_not_ssh(self, instance): + instance.get_node_config.return_value = """ + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": {}, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + {} + +""".format(jenkins_node.SSHLauncherConfig.TEMPLATE)) + + assert result.value["configured"] is True + + def test_configure_when_not_configured(self, instance): + instance.get_node_config.return_value = """ + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": {}, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_port_when_not_equal(self, instance): + instance.get_node_config.return_value = """ + + + 22 + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "port": 23, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + 23 + + +""") + + assert result.value["configured"] is True + + def test_configure_port_when_equal(self, instance): + instance.get_node_config.return_value = """ + + + 22 + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "port": 22, + } + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_host_when_unset(self, instance): + instance.get_node_config.return_value = """ + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host": "my-node.test", + } + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal( + instance.reconfig_node.call_args[0][1], """ + + + my-node.test + + +""") + + assert result.value["configured"] is True + + def test_configure_host_when_equal(self, instance): + instance.get_node_config.return_value = """ + + + my-node.test + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host": "my-node.test", + } + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_host_when_not_equal(self, instance): + instance.get_node_config.return_value = """ + + + my-node.test + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host": "my-other-node.test", + } + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + my-other-node.test + + +""") + + assert result.value["configured"] is True + + def test_configure_credentials_id_when_unset(self, instance): + instance.get_node_config.return_value = """ + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "credentials_id": "deaddead-dead-dead-dead-deaddeaddead", + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + deaddead-dead-dead-dead-deaddeaddead + + +""") + + assert result.value["configured"] is True + + def test_configure_credentials_id_when_equal(self, instance): + instance.get_node_config.return_value = """ + + + deaddead-dead-dead-dead-deaddeaddead + + +""" + + set_module_args( + { + "name": "my-node", + "state": "present", + "launch_ssh": { + "credentials_id": "deaddead-dead-dead-dead-deaddeaddead", + }, + } + ) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_credentials_id_when_not_equal(self, instance): + instance.get_node_config.return_value = """ + + + deaddead-dead-dead-dead-deaddeaddead + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "credentials_id": "deaddead-dead-dead-dead-deaddeadbeef", + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + deaddead-dead-dead-dead-deaddeadbeef + + +""") + + assert result.value["configured"] is True + + def test_configure_host_key_verify_known_hosts_when_host_key_verify_is_not_known_hosts( + self, instance + ): + instance.get_node_config.return_value = """ + + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_known_hosts": True, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + + + +""") + + assert result.value["configured"] is True + + def test_configure_host_key_verify_none_when_host_key_verify_is_not_none( + self, instance + ): + instance.get_node_config.return_value = """ + + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_none": True, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + + + +""") + + assert result.value["configured"] is True + + def test_configure_host_key_verify_trusted_when_host_key_verify_is_not_trusted( + self, instance + ): + instance.get_node_config.return_value = """ + + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_trusted": {}, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + + false + + + +""") + + assert result.value["configured"] is True + + def test_configure_host_key_verify_trusted_when_not_configured(self, instance): + instance.get_node_config.return_value = """ + + + + false + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_trusted": {}, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_host_key_verify_trusted_allow_initial_when_equal(self, instance): + instance.get_node_config.return_value = """ + + + + false + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_trusted": { + "allow_initial": False, + }, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_host_key_verify_trusted_allow_initial_when_not_equal(self, instance): + instance.get_node_config.return_value = """ + + + + false + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_trusted": { + "allow_initial": True, + }, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + + true + + + +""") + + assert result.value["configured"] is True + + ####### + + def test_configure_host_key_verify_provided_when_host_key_verify_is_not_provided( + self, instance + ): + instance.get_node_config.return_value = """ + + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_provided": { + "algorithm": "ssh-ed25519", + "key": "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", + }, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + + + ssh-ed25519 + AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + + + + +""") + + assert result.value["configured"] is True + + def test_configure_host_key_verify_provided_algorithm_when_equal(self, instance): + instance.get_node_config.return_value = """ + + + + + ssh-ed25519 + AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_provided": { + "algorithm": "ssh-ed25519", + "key": "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", + }, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_host_key_verify_provided_algorithm_when_not_equal(self, instance): + instance.get_node_config.return_value = """ + + + + + ssh-ed25519 + AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_provided": { + "algorithm": "ssh-rsa", + "key": "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", + }, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + + + ssh-rsa + AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + + + + +""") + + assert result.value["configured"] is True + + def test_configure_host_key_verify_provided_key_when_equal(self, instance): + instance.get_node_config.return_value = """ + + + + + ssh-ed25519 + AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_provided": { + "algorithm": "ssh-ed25519", + "key": "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", + }, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert not instance.reconfig_node.called + + assert result.value["configured"] is False + + def test_configure_host_key_verify_provided_key_when_not_equal(self, instance): + instance.get_node_config.return_value = """ + + + + + ssh-ed25519 + AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + + + + +""" + + set_module_args({ + "name": "my-node", + "state": "present", + "launch_ssh": { + "host_key_verify_provided": { + "algorithm": "ssh-ed25519", + "key": "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJk", + }, + }, + }) + + with raises(AnsibleExitJson) as result: + jenkins_node.main() + + assert_xml_equal(instance.reconfig_node.call_args[0][1], """ + + + + + ssh-ed25519 + AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJk + + + + +""") + + assert result.value["configured"] is True