Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jenkins_node: Add launch_ssh option and suboptions #9101

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

phyrwork
Copy link
Contributor

@phyrwork phyrwork commented Nov 4, 2024

  • Implement launch_ssh option which will configure a node to use the SSH launcher method if specified.

  • Implement common suboptions for launch_ssh option, notably the various SSH host verification strategies.

  • Establish a pattern for dealing with non-trivial XML in this module - ...Element wrappers for handling structural XML and ...Config for idempotent application of configuration.

ISSUE TYPE
  • Feature Pull Request
COMPONENT NAME

jenkins_node

* Implement launch_ssh option which will configure a node to use
  the SSH launcher method if specified.

* Implement common suboptions for launch_ssh option, notably the
  various SSH host verification strategies.

* Establish a pattern for dealing with non-trivial XML in this
  module - ...Element wrappers for handling structural XML and
  ...Config for idempotent application of configuration.
@ansibullbot ansibullbot added feature This issue/PR relates to a feature request module module plugins plugin (any type) tests tests unit tests/unit labels Nov 4, 2024
@felixfontein felixfontein added check-before-release PR will be looked at again shortly before release and merged if possible. backport-10 Automatically create a backport for the stable-10 branch labels Nov 5, 2024
Copy link
Collaborator

@felixfontein felixfontein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution! I've added some first comments below.

plugins/modules/jenkins_node.py Outdated Show resolved Hide resolved
plugins/modules/jenkins_node.py Outdated Show resolved Hide resolved
plugins/modules/jenkins_node.py Show resolved Hide resolved
plugins/modules/jenkins_node.py Outdated Show resolved Hide resolved
plugins/modules/jenkins_node.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@russoz russoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @phyrwork

Thanks once again for contributing!

I spotted few other things in there - a couple of them are a matter of style, but others are not.

Comment on lines +260 to +271
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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot easily check this right now but I fairly sure that something like that already exists within the Ansible codebase - if not here in c.g., maybe in ansible-core itself. Worth a check.

Comment on lines +100 to +101
choices:
- true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to add this? Being a bool automatically restraints the accepted values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question.

This is an example of where there are N mutually exclusive options of which you may specify zero or one of them.

Some of the options have suboptions - these are either specified with a dict of suboptions or not specified at all.

The other options are flags - these are either specified with True or not specified at all.

I wanted to be explicitly about this by preventing setting False for these flags because it might be tempting to specify

d1: {...}  # Selected option
f1: no
f2: no

which looks correct but will fail to validate.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is kind of a common usage pattern in Ansible to assume that bool options will handle false and null in the exact same way. If you still think you should keep the choices, then I would suggest you make an explicit mention of that in the description - to ensure users are warned about this being different (reinforcing the fact that "choices" will appear in the docs as well).

Comment on lines +243 to +257
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest making it one notch more generic with:

Suggested change
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
try:
from functools import cached_property
except ImportError:
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

plugins/modules/jenkins_node.py Show resolved Hide resolved
Comment on lines +303 to +310
@abstractmethod
def init(self): # type: () -> et.Element
"""Initialize XML element from config.

Returns:
Initialized XML element.
"""
raise NotImplementedError
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decorator is not really necessary if all the method does is raising NotImplementedError. It's not wrong, but it is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - I had wanted to use it to prevent instantiating instances of classes with unimplemented methods but I couldn't be bothered faffing about with a 2 + 3 compatible ABC/ABCMeta.

I will remove it.

Comment on lines +373 to +377
TEMPLATE = """
<sshHostKeyVerificationStrategy class="hudson.plugins.sshslaves.verifiers.ManuallyTrustedKeyVerificationStrategy">
<requireInitialManualTrust>false</requireInitialManualTrust>
</sshHostKeyVerificationStrategy>
""".strip()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to do this, without having to call .strip() is:

Suggested change
TEMPLATE = """
<sshHostKeyVerificationStrategy class="hudson.plugins.sshslaves.verifiers.ManuallyTrustedKeyVerificationStrategy">
<requireInitialManualTrust>false</requireInitialManualTrust>
</sshHostKeyVerificationStrategy>
""".strip()
TEMPLATE = (
"<sshHostKeyVerificationStrategy class="hudson.plugins.sshslaves.verifiers.ManuallyTrustedKeyVerificationStrategy">\n"
" <requireInitialManualTrust>false</requireInitialManualTrust>\n"
"</sshHostKeyVerificationStrategy>"
)

Maybe one notch "cleaner", though it seems this TEMPLATE is only passed on to et.fromstring() for parsing - in which case the newlines at the end should probably not matter at all.

Copy link
Collaborator

@russoz russoz Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to handle that could be:

Suggested change
TEMPLATE = """
<sshHostKeyVerificationStrategy class="hudson.plugins.sshslaves.verifiers.ManuallyTrustedKeyVerificationStrategy">
<requireInitialManualTrust>false</requireInitialManualTrust>
</sshHostKeyVerificationStrategy>
""".strip()
TEMPLATE = """\
<sshHostKeyVerificationStrategy class="hudson.plugins.sshslaves.verifiers.ManuallyTrustedKeyVerificationStrategy">
<requireInitialManualTrust>false</requireInitialManualTrust>
</sshHostKeyVerificationStrategy>\
"""

Comment on lines +652 to +657
@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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be simpler - and more consistent with existing code - to define self.launch in the __init__() method, like self.offline_message. By doing that you could remove the cached_property decorator entirely, also discarding the import check for Python 3.8.

@@ -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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit pickly, this could be made one notch simpler with:

Suggested change
if self.launch is not None:
if self.launch:

Copy link
Contributor Author

@phyrwork phyrwork Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been wondering about this sort of situation recently and had come to the conclusion that it's more explicit and faster to do the identity check to constrain from T | None to None than invoke __bool__.

host=dict(type='str'),
port=dict(type='int'),
credentials_id=dict(type='str'),
host_key_verify_none=dict(type='bool', choices=[True]),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned it before, but the choices really seem redundant here.

@ansibullbot ansibullbot added the stale_ci CI is older than 7 days, rerun before merging label Nov 15, 2024
@felixfontein
Copy link
Collaborator

@phyrwork ping!

needs_info

@ansibullbot ansibullbot added needs_info This issue requires further information. Please answer any outstanding questions needs_rebase https://docs.ansible.com/ansible/devel/dev_guide/developing_rebasing.html needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR labels Dec 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-10 Automatically create a backport for the stable-10 branch check-before-release PR will be looked at again shortly before release and merged if possible. feature This issue/PR relates to a feature request module module needs_info This issue requires further information. Please answer any outstanding questions needs_rebase https://docs.ansible.com/ansible/devel/dev_guide/developing_rebasing.html needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR plugins plugin (any type) stale_ci CI is older than 7 days, rerun before merging tests tests unit tests/unit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants