diff --git a/plugins/module_utils/Jenkins.py b/plugins/module_utils/Jenkins.py new file mode 100644 index 0000000..9f4defd --- /dev/null +++ b/plugins/module_utils/Jenkins.py @@ -0,0 +1,46 @@ +import xml.etree.ElementTree as ET +from typing import Optional +from dataclasses import dataclass + +import requests +from pathspec import PathSpec + + +class Jenkins: + + @dataclass + class NodeInfo: + server_url: str + name: str + secret: Optional[str] + work_dir: PathSpec + internal_dir: PathSpec + server_url: str + + def __init__(self, server_url, username, api_token): + self.server_url = server_url + self.username = username + self.api_token = api_token + + def _log_in(self, username: str, password: str) -> (str, str): + response = requests.get(f"{self.server_url}/crumbIssuer/api/json") + response.raise_for_status() + payload = response.json() + return payload["crumbRequestField"], payload["crumb"] + + def get_node_jnlp(self, node_name) -> str: + response = requests.get( + f"{self.server_url}/manage/computer/{node_name}/slave-agent.jnlp", + auth=(self.username, self.api_token), + ) + response.raise_for_status() + return response.text + + def get_node_info(self, node_name: str) -> NodeInfo: + jnlp_info_raw = self.get_node_jnlp(node_name) + tree = ET.ElementTree(ET.fromstring(jnlp_info_raw)) + arguments = tree.findall("./application-desc/") + (node_secret, node_name, _, work_dir, _, internal_dir, _, url) = [ + arg.text for arg in arguments[:8] + ] + return Jenkins.NodeInfo(url, node_name, node_secret, work_dir, internal_dir) diff --git a/plugins/modules/jenkins_node.py b/plugins/modules/jenkins_node.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/modules/jenkins_node_info.py b/plugins/modules/jenkins_node_info.py new file mode 100644 index 0000000..825415a --- /dev/null +++ b/plugins/modules/jenkins_node_info.py @@ -0,0 +1,112 @@ +# pylint: disable=E0401 + +from __future__ import absolute_import, annotations, division, print_function + +__metaclass__ = type # pylint: disable=C0103 + +from typing import TYPE_CHECKING + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.finallycoffee.cicd.plugins.module_utils.Jenkins import Jenkins + +if TYPE_CHECKING: + from typing import Optional, Dict, Any + +DOCUMENTATION = r""" +--- +module: jenkins_node + +short_description: Retrieve Jenkins node information +# If this is part of a collection, you need to use semantic versioning, +# i.e. the version is of the form "2.5.0" and not "2.4". +version_added: "0.0.1" + +description: This is my longer description explaining my test module. + +options: + name: + description: The name of the jenkins node. + required: true + type: str + aliases: + - agent + server: + description: URL of the jenkins instance + required: true + type: str + aliases: + - server_url + username: + description: Username to use for authentication to jenkins + required: true + type: str + aliases: + - user + api_token: + description: Jenkins API token for the user + required: true + type: str +author: + - transcaffeine (@transcaffeine) +""" + +EXAMPLES = r""" +# Pass in a message +- name: Retrieve information about the jenkins node named 'my_jenkins_node_name' + finallycoffee.cicd.jenkins_node_info: + name: my_jenkins_node_name + server: https://jenkins.example.org + username: admin + api_token: yoursecretapitokenhere +""" + +RETURN = r""" +# These are examples of possible return values, and in general should use other names for return values. +name: + description: The name of the jenkins node + type: str + returned: always + sample: 'jenkins-agent-jdk21-alpine' +secret: + description: The secret of the agent + type: str + returned: always + sample: 'secretverylongstringwith64chars' +work_dir: + description: The local working directory of the jenkins agent + type: str + returned: always +""" + + +def run_module(): + module_args = dict( + name=dict(type="str", required=True, aliases=["node"]), + server=dict(type="str", required=True, aliases=["server_url"]), + username=dict(type="str", required=True, aliases=["user"]), + api_token=dict(type="str", required=True, aliases=["password", "pass"]), + ) + result = dict(changed=False) + module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) + + jenkins = Jenkins( + module.params["server"], module.params["username"], module.params["api_token"] + ) + node = jenkins.get_node_info(module.params["name"]) + + result["name"] = node.name + result["secret"] = node.secret + result["work_dir"] = node.work_dir + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..5726625 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +ansible-dev-tools==25.* \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..413df83 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,91 @@ +alabaster==1.0.0 +annotated-types==0.7.0 +ansible==11.2.0 +ansible-core==2.18.2 +argcomplete==3.4.0 +attrs==23.2.1.dev0 +autocommand==2.2.2 +Babel==2.15.0 +Brlapi==0.8.6 +btrfsutil==6.12 +build==1.2.2 +canonicaljson==2.0.0 +certifi==2025.1.31 +cffi==1.17.1 +charset-normalizer==3.4.1 +coverage==7.6.10 +cryptography==44.0.0 +dbus-python==1.3.2 +diceware==1.0.1 +distro==1.9.0 +distro-info==1.10 +dnspython==2.7.0 +docutils==0.21.2 +fastjsonschema==2.21.1 +filelock==3.17.0 +idna==3.10 +imagesize==1.4.1 +inflect==7.5.0 +iniconfig==2.0.0 +installer==0.7.0 +jaraco.collections==5.1.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 +jaraco.text==4.0.0 +Jinja2==3.1.5 +lensfun==0.3.4 +libfdt==1.7.2 +libvirt-python==11.0.0 +louis==3.32.0 +lxml==5.3.0 +MarkupSafe==2.1.5 +more-itertools==10.5.0 +netsnmp-python==1.0a1 +nftables==0.1 +ordered-set==4.1.0 +packaging==24.2 +platformdirs==4.3.6 +pluggy==1.5.0 +ply==3.11 +pycairo==1.27.0 +pycparser==2.22 +pydantic==2.10.6 +pydantic_core==2.27.2 +Pygments==2.19.1 +PyGObject==3.50.0 +PyNaCl==1.5.0 +pyproject_hooks==1.2.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytz==2024.2 +PyYAML==6.0.2 +requests==2.32.3 +resolvelib==1.0.1 +setuptools==75.2.0 +signedjson==1.1.4 +six==1.17.0 +snowballstemmer==2.2.0 +Sphinx==8.1.3 +sphinx_rtd_theme==2.0.0 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +TBB==0.2 +tomli==2.0.1 +tomlkit==0.13.2 +trove-classifiers==2025.1.15.22 +typeguard==4.4.1 +typing_extensions==4.12.2 +ufw==0.36.2 +unpaddedbase64==2.1.0 +urllib3==2.3.0 +validate-pyproject==0.23.post1.dev0+gf45606b.d20250111 +wheel==0.45.0 +wxPython==4.2.2 +xmltodict==0.14.2 +yq==3.4.3 +yt-dlp==2025.1.26