New module: Jenkins node management (#9016)
* New module: Jenkins node management * Fix sanity errors * (Try to) fix mock import for Python 2.7 * Remove encoding from XML tostring in hopes of appeasing py27 Default encoding is probably always good enough. * Turns out that encoding parameter was important for python3... It's not super obvious how to resolve in a 2 + 3 compatible way, so branch and omit encoding for 2. * Implement review fixes and suggestionspull/9040/head
parent
86166ccade
commit
064f76c27b
|
@ -762,6 +762,8 @@ files:
|
|||
maintainers: sermilrod
|
||||
$modules/jenkins_job_info.py:
|
||||
maintainers: stpierre
|
||||
$modules/jenkins_node.py:
|
||||
maintainers: phyrwork
|
||||
$modules/jenkins_plugin.py:
|
||||
maintainers: jtyr
|
||||
$modules/jenkins_script.py:
|
||||
|
|
|
@ -0,0 +1,385 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: jenkins_node
|
||||
short_description: Manage Jenkins nodes
|
||||
version_added: 10.0.0
|
||||
description:
|
||||
- Manage Jenkins nodes with Jenkins REST API.
|
||||
requirements:
|
||||
- "python-jenkins >= 0.4.12"
|
||||
author:
|
||||
- Connor Newton (@phyrwork)
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: partial
|
||||
details:
|
||||
- Check mode is unable to show configuration changes for a node that is not yet
|
||||
present.
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
url:
|
||||
description:
|
||||
- URL of the Jenkins server.
|
||||
default: http://localhost:8080
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name of the Jenkins node to manage.
|
||||
required: true
|
||||
type: str
|
||||
user:
|
||||
description:
|
||||
- User to authenticate with the Jenkins server.
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- API token to authenticate with the Jenkins server.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Specifies whether the Jenkins node should be V(present) (created), V(absent)
|
||||
(deleted), V(enabled) (online) or V(disabled) (offline).
|
||||
default: present
|
||||
choices: ['enabled', 'disabled', 'present', 'absent']
|
||||
type: str
|
||||
num_executors:
|
||||
description:
|
||||
- When specified, sets the Jenkins node executor count.
|
||||
type: int
|
||||
labels:
|
||||
description:
|
||||
- When specified, sets the Jenkins node labels.
|
||||
type: list
|
||||
elements: str
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a Jenkins node using token authentication
|
||||
community.general.jenkins_node:
|
||||
url: http://localhost:8080
|
||||
user: jenkins
|
||||
token: 11eb751baabb66c4d1cb8dc4e0fb142cde
|
||||
name: my-node
|
||||
state: present
|
||||
|
||||
- name: Set number of executors on Jenkins node
|
||||
community.general.jenkins_node:
|
||||
name: my-node
|
||||
state: present
|
||||
num_executors: 4
|
||||
|
||||
- name: Set labels on Jenkins node
|
||||
community.general.jenkins_node:
|
||||
name: my-node
|
||||
state: present
|
||||
labels:
|
||||
- label-1
|
||||
- label-2
|
||||
- label-3
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
url:
|
||||
description: URL used to connect to the Jenkins server.
|
||||
returned: success
|
||||
type: str
|
||||
sample: https://jenkins.mydomain.com
|
||||
user:
|
||||
description: User used for authentication.
|
||||
returned: success
|
||||
type: str
|
||||
sample: jenkins
|
||||
name:
|
||||
description: Name of the Jenkins node.
|
||||
returned: success
|
||||
type: str
|
||||
sample: my-node
|
||||
state:
|
||||
description: State of the Jenkins node.
|
||||
returned: success
|
||||
type: str
|
||||
sample: present
|
||||
created:
|
||||
description: Whether or not the Jenkins node was created by the task.
|
||||
returned: success
|
||||
type: bool
|
||||
deleted:
|
||||
description: Whether or not the Jenkins node was deleted by the task.
|
||||
returned: success
|
||||
type: bool
|
||||
disabled:
|
||||
description: Whether or not the Jenkins node was disabled by the task.
|
||||
returned: success
|
||||
type: bool
|
||||
enabled:
|
||||
description: Whether or not the Jenkins node was enabled by the task.
|
||||
returned: success
|
||||
type: bool
|
||||
configured:
|
||||
description: Whether or not the Jenkins node was configured by the task.
|
||||
returned: success
|
||||
type: bool
|
||||
'''
|
||||
|
||||
import sys
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible_collections.community.general.plugins.module_utils import deps
|
||||
|
||||
with deps.declare(
|
||||
"python-jenkins",
|
||||
reason="python-jenkins is required to interact with Jenkins",
|
||||
url="https://opendev.org/jjb/python-jenkins",
|
||||
):
|
||||
import jenkins
|
||||
|
||||
|
||||
IS_PYTHON_2 = sys.version_info[0] <= 2
|
||||
|
||||
|
||||
class JenkinsNode:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
self.name = module.params['name']
|
||||
self.state = module.params['state']
|
||||
self.token = module.params['token']
|
||||
self.user = module.params['user']
|
||||
self.url = module.params['url']
|
||||
self.num_executors = module.params['num_executors']
|
||||
self.labels = module.params['labels']
|
||||
|
||||
if self.labels is not None:
|
||||
for label in self.labels:
|
||||
if " " in label:
|
||||
self.module.fail_json("labels must not contain spaces: got invalid label {}".format(label))
|
||||
|
||||
self.instance = self.get_jenkins_instance()
|
||||
self.result = {
|
||||
'changed': False,
|
||||
'url': self.url,
|
||||
'user': self.user,
|
||||
'name': self.name,
|
||||
'state': self.state,
|
||||
'created': False,
|
||||
'deleted': False,
|
||||
'disabled': False,
|
||||
'enabled': False,
|
||||
'configured': False,
|
||||
'warnings': [],
|
||||
}
|
||||
|
||||
def get_jenkins_instance(self):
|
||||
try:
|
||||
if self.user and self.token:
|
||||
return jenkins.Jenkins(self.url, self.user, self.token)
|
||||
elif self.user and not self.token:
|
||||
return jenkins.Jenkins(self.url, self.user)
|
||||
else:
|
||||
return jenkins.Jenkins(self.url)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Unable to connect to Jenkins server, %s' % to_native(e))
|
||||
|
||||
def configure_node(self, present):
|
||||
if not present:
|
||||
# Node would only not be present if in check mode and if not present there
|
||||
# is no way to know what would and would not be changed.
|
||||
if not self.module.check_mode:
|
||||
raise Exception("configure_node present is False outside of check mode")
|
||||
return
|
||||
|
||||
configured = False
|
||||
|
||||
data = self.instance.get_node_config(self.name)
|
||||
root = ElementTree.fromstring(data)
|
||||
|
||||
if self.num_executors is not None:
|
||||
elem = root.find('numExecutors')
|
||||
if elem is None:
|
||||
elem = ElementTree.SubElement(root, 'numExecutors')
|
||||
if elem.text is None or int(elem.text) != self.num_executors:
|
||||
elem.text = str(self.num_executors)
|
||||
configured = True
|
||||
|
||||
if self.labels is not None:
|
||||
elem = root.find('label')
|
||||
if elem is None:
|
||||
elem = ElementTree.SubElement(root, 'label')
|
||||
labels = []
|
||||
if elem.text:
|
||||
labels = elem.text.split()
|
||||
if labels != self.labels:
|
||||
elem.text = " ".join(self.labels)
|
||||
configured = True
|
||||
|
||||
if configured:
|
||||
if IS_PYTHON_2:
|
||||
data = ElementTree.tostring(root)
|
||||
else:
|
||||
data = ElementTree.tostring(root, encoding="unicode")
|
||||
|
||||
self.instance.reconfig_node(self.name, data)
|
||||
|
||||
self.result['configured'] = configured
|
||||
if configured:
|
||||
self.result['changed'] = True
|
||||
|
||||
def present_node(self):
|
||||
def create_node():
|
||||
try:
|
||||
self.instance.create_node(self.name, launcher=jenkins.LAUNCHER_SSH)
|
||||
except jenkins.JenkinsException as e:
|
||||
# Some versions of python-jenkins < 1.8.3 has an authorization bug when
|
||||
# handling redirects returned when posting new resources. If the node is
|
||||
# created OK then can ignore the error.
|
||||
if not self.instance.node_exists(self.name):
|
||||
raise e
|
||||
|
||||
# TODO: Remove authorization workaround.
|
||||
self.result['warnings'].append(
|
||||
"suppressed 401 Not Authorized on redirect after node created: see https://review.opendev.org/c/jjb/python-jenkins/+/931707"
|
||||
)
|
||||
|
||||
present = self.instance.node_exists(self.name)
|
||||
created = False
|
||||
if not present:
|
||||
if not self.module.check_mode:
|
||||
create_node()
|
||||
present = True
|
||||
|
||||
created = True
|
||||
|
||||
self.configure_node(present)
|
||||
|
||||
self.result['created'] = created
|
||||
if created:
|
||||
self.result['changed'] = True
|
||||
|
||||
return present # Used to gate downstream queries when in check mode.
|
||||
|
||||
def absent_node(self):
|
||||
def delete_node():
|
||||
try:
|
||||
self.instance.delete_node(self.name)
|
||||
except jenkins.JenkinsException as e:
|
||||
# Some versions of python-jenkins < 1.8.3 has an authorization bug when
|
||||
# handling redirects returned when posting new resources. If the node is
|
||||
# deleted OK then can ignore the error.
|
||||
if self.instance.node_exists(self.name):
|
||||
raise e
|
||||
|
||||
# TODO: Remove authorization workaround.
|
||||
self.result['warnings'].append(
|
||||
"suppressed 401 Not Authorized on redirect after node deleted: see https://review.opendev.org/c/jjb/python-jenkins/+/931707"
|
||||
)
|
||||
|
||||
present = self.instance.node_exists(self.name)
|
||||
deleted = False
|
||||
if present:
|
||||
if not self.module.check_mode:
|
||||
delete_node()
|
||||
|
||||
deleted = True
|
||||
|
||||
self.result['deleted'] = deleted
|
||||
if deleted:
|
||||
self.result['changed'] = True
|
||||
|
||||
def enabled_node(self):
|
||||
present = self.present_node()
|
||||
|
||||
enabled = False
|
||||
|
||||
if present:
|
||||
info = self.instance.get_node_info(self.name)
|
||||
|
||||
if info['offline']:
|
||||
if not self.module.check_mode:
|
||||
self.instance.enable_node(self.name)
|
||||
|
||||
enabled = True
|
||||
else:
|
||||
# Would have created node with initial state enabled therefore would not have
|
||||
# needed to enable therefore not enabled.
|
||||
if not self.module.check_mode:
|
||||
raise Exception("enabled_node present is False outside of check mode")
|
||||
enabled = False
|
||||
|
||||
self.result['enabled'] = enabled
|
||||
if enabled:
|
||||
self.result['changed'] = True
|
||||
|
||||
def disabled_node(self):
|
||||
present = self.present_node()
|
||||
|
||||
disabled = False
|
||||
|
||||
if present:
|
||||
info = self.instance.get_node_info(self.name)
|
||||
|
||||
if not info['offline']:
|
||||
if not self.module.check_mode:
|
||||
self.instance.disable_node(self.name)
|
||||
|
||||
disabled = True
|
||||
else:
|
||||
# Would have created node with initial state enabled therefore would have
|
||||
# needed to disable therefore disabled.
|
||||
if not self.module.check_mode:
|
||||
raise Exception("disabled_node present is False outside of check mode")
|
||||
disabled = True
|
||||
|
||||
self.result['disabled'] = disabled
|
||||
if disabled:
|
||||
self.result['changed'] = True
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(required=True, type='str'),
|
||||
url=dict(default='http://localhost:8080'),
|
||||
user=dict(),
|
||||
token=dict(no_log=True),
|
||||
state=dict(choices=['enabled', 'disabled', 'present', 'absent'], default='present'),
|
||||
num_executors=dict(type='int'),
|
||||
labels=dict(type='list', elements='str'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
deps.validate(module)
|
||||
|
||||
jenkins_node = JenkinsNode(module)
|
||||
|
||||
state = module.params.get('state')
|
||||
if state == 'enabled':
|
||||
jenkins_node.enabled_node()
|
||||
elif state == 'disabled':
|
||||
jenkins_node.disabled_node()
|
||||
elif state == 'present':
|
||||
jenkins_node.present_node()
|
||||
else:
|
||||
jenkins_node.absent_node()
|
||||
|
||||
module.exit_json(**jenkins_node.result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,575 @@
|
|||
# Copyright (c) Ansible project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import jenkins
|
||||
import json
|
||||
|
||||
from xml.etree import ElementTree as et
|
||||
|
||||
import pytest
|
||||
|
||||
from ansible.module_utils import basic
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import patch, call
|
||||
from ansible_collections.community.general.plugins.modules import jenkins_node
|
||||
from pytest import fixture, raises, mark, param
|
||||
|
||||
|
||||
def xml_equal(x, y):
|
||||
# type: (et.Element | str, et.Element | str) -> bool
|
||||
if isinstance(x, str):
|
||||
x = et.fromstring(x)
|
||||
|
||||
if isinstance(y, str):
|
||||
y = et.fromstring(y)
|
||||
|
||||
if x.tag != y.tag:
|
||||
return False
|
||||
|
||||
if x.attrib != y.attrib:
|
||||
return False
|
||||
|
||||
if (x.text or "").strip() != (y.text or "").strip():
|
||||
return False
|
||||
|
||||
x_children = list(x)
|
||||
y_children = list(y)
|
||||
|
||||
if len(x_children) != len(y_children):
|
||||
return False
|
||||
|
||||
for x, y in zip(x_children, y_children):
|
||||
if not xml_equal(x, y):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def assert_xml_equal(x, y):
|
||||
if xml_equal(x, y):
|
||||
return True
|
||||
|
||||
if not isinstance(x, str):
|
||||
x = et.tostring(x)
|
||||
|
||||
if not isinstance(y, str):
|
||||
y = et.tostring(y)
|
||||
|
||||
raise AssertionError("{} != {}".format(x, y))
|
||||
|
||||
|
||||
def set_module_args(args):
|
||||
"""prepare arguments so that they will be picked up during module creation"""
|
||||
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
|
||||
basic._ANSIBLE_ARGS = to_bytes(args)
|
||||
|
||||
|
||||
class AnsibleExitJson(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.value[item]
|
||||
|
||||
|
||||
def exit_json(*args, **kwargs):
|
||||
if 'changed' not in kwargs:
|
||||
kwargs['changed'] = False
|
||||
raise AnsibleExitJson(kwargs)
|
||||
|
||||
|
||||
class AnsibleFailJson(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def fail_json(*args, **kwargs):
|
||||
kwargs['failed'] = True
|
||||
raise AnsibleFailJson(kwargs)
|
||||
|
||||
|
||||
@fixture(autouse=True)
|
||||
def module():
|
||||
with patch.multiple(
|
||||
"ansible.module_utils.basic.AnsibleModule",
|
||||
exit_json=exit_json,
|
||||
fail_json=fail_json,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@fixture
|
||||
def instance():
|
||||
with patch("jenkins.Jenkins", autospec=True) as instance:
|
||||
yield instance
|
||||
|
||||
|
||||
@fixture
|
||||
def get_instance(instance):
|
||||
with patch(
|
||||
"ansible_collections.community.general.plugins.modules.jenkins_node.JenkinsNode.get_jenkins_instance",
|
||||
autospec=True,
|
||||
) as mock:
|
||||
mock.return_value = instance
|
||||
yield mock
|
||||
|
||||
|
||||
def test_get_jenkins_instance_with_user_and_token(instance):
|
||||
instance.node_exists.return_value = False
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "absent",
|
||||
"url": "https://localhost:8080",
|
||||
"user": "admin",
|
||||
"token": "password",
|
||||
})
|
||||
|
||||
with pytest.raises(AnsibleExitJson):
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.call_args == call("https://localhost:8080", "admin", "password")
|
||||
|
||||
|
||||
def test_get_jenkins_instance_with_user(instance):
|
||||
instance.node_exists.return_value = False
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "absent",
|
||||
"url": "https://localhost:8080",
|
||||
"user": "admin",
|
||||
})
|
||||
|
||||
with pytest.raises(AnsibleExitJson):
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.call_args == call("https://localhost:8080", "admin")
|
||||
|
||||
|
||||
def test_get_jenkins_instance_with_no_credential(instance):
|
||||
instance.node_exists.return_value = False
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "absent",
|
||||
"url": "https://localhost:8080",
|
||||
})
|
||||
|
||||
with pytest.raises(AnsibleExitJson):
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.call_args == call("https://localhost:8080")
|
||||
|
||||
|
||||
PRESENT_STATES = ["present", "enabled", "disabled"]
|
||||
|
||||
|
||||
@mark.parametrize(["state"], [param(state) for state in PRESENT_STATES])
|
||||
def test_state_present_when_absent(get_instance, instance, state):
|
||||
instance.node_exists.return_value = False
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": state,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.create_node.call_args == call("my-node", launcher=jenkins.LAUNCHER_SSH)
|
||||
|
||||
assert result.value["created"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
@mark.parametrize(["state"], [param(state) for state in PRESENT_STATES])
|
||||
def test_state_present_when_absent_check_mode(get_instance, instance, state):
|
||||
instance.node_exists.return_value = False
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": state,
|
||||
"_ansible_check_mode": True,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.create_node.called
|
||||
|
||||
assert result.value["created"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_state_present_when_present(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.create_node.called
|
||||
|
||||
assert result.value["created"] is False
|
||||
assert result.value["changed"] is False
|
||||
|
||||
|
||||
def test_state_absent_when_present(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "absent",
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.delete_node.call_args == call("my-node")
|
||||
|
||||
assert result.value["deleted"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_state_absent_when_present_check_mode(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "absent",
|
||||
"_ansible_check_mode": True,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.delete_node.called
|
||||
|
||||
assert result.value["deleted"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_state_absent_when_absent(get_instance, instance):
|
||||
instance.node_exists.return_value = False
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "absent",
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.delete_node.called
|
||||
|
||||
assert result.value["deleted"] is False
|
||||
assert result.value["changed"] is False
|
||||
|
||||
|
||||
def test_state_enabled_when_offline(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
instance.get_node_info.return_value = {"offline": True}
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "enabled",
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.enable_node.call_args == call("my-node")
|
||||
|
||||
assert result.value["enabled"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_state_enabled_when_offline_check_mode(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
instance.get_node_info.return_value = {"offline": True}
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "enabled",
|
||||
"_ansible_check_mode": True,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.enable_node.called
|
||||
|
||||
assert result.value["enabled"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_state_enabled_when_not_offline(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
instance.get_node_info.return_value = {"offline": False}
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "enabled",
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.enable_node.called
|
||||
|
||||
assert result.value["enabled"] is False
|
||||
assert result.value["changed"] is False
|
||||
|
||||
|
||||
def test_state_disabled_when_not_offline(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
instance.get_node_info.return_value = {"offline": False}
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "disabled",
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.disable_node.call_args == call("my-node")
|
||||
|
||||
assert result.value["disabled"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_state_disabled_when_not_offline_check_mode(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
instance.get_node_info.return_value = {"offline": False}
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "disabled",
|
||||
"_ansible_check_mode": True,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.disable_node.called
|
||||
|
||||
assert result.value["disabled"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_state_disabled_when_offline(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
instance.get_node_info.return_value = {"offline": True}
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "disabled",
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.disable_node.called
|
||||
|
||||
assert result.value["disabled"] is False
|
||||
assert result.value["changed"] is False
|
||||
|
||||
|
||||
def test_configure_num_executors_when_not_configured(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
"num_executors": 3,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.reconfig_node.call_args[0][0] == "my-node"
|
||||
assert_xml_equal(instance.reconfig_node.call_args[0][1], """
|
||||
<slave>
|
||||
<numExecutors>3</numExecutors>
|
||||
</slave>
|
||||
""")
|
||||
|
||||
assert result.value["configured"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_configure_num_executors_when_not_equal(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = """
|
||||
<slave>
|
||||
<numExecutors>3</numExecutors>
|
||||
</slave>
|
||||
"""
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
"num_executors": 2,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert_xml_equal(instance.reconfig_node.call_args[0][1], """
|
||||
<slave>
|
||||
<numExecutors>2</numExecutors>
|
||||
</slave>
|
||||
""")
|
||||
|
||||
assert result.value["configured"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_configure_num_executors_when_equal(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = """
|
||||
<slave>
|
||||
<numExecutors>2</numExecutors>
|
||||
</slave>
|
||||
"""
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
"num_executors": 2,
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.reconfig_node.called
|
||||
|
||||
assert result.value["configured"] is False
|
||||
assert result.value["changed"] is False
|
||||
|
||||
|
||||
def test_configure_labels_when_not_configured(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
"labels": [
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
],
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.reconfig_node.call_args[0][0] == "my-node"
|
||||
assert_xml_equal(instance.reconfig_node.call_args[0][1], """
|
||||
<slave>
|
||||
<label>a b c</label>
|
||||
</slave>
|
||||
""")
|
||||
|
||||
assert result.value["configured"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_configure_labels_when_not_equal(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = """
|
||||
<slave>
|
||||
<label>a b c</label>
|
||||
</slave>
|
||||
"""
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
"labels": [
|
||||
"a",
|
||||
"z",
|
||||
"c",
|
||||
],
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert instance.reconfig_node.call_args[0][0] == "my-node"
|
||||
assert_xml_equal(instance.reconfig_node.call_args[0][1], """
|
||||
<slave>
|
||||
<label>a z c</label>
|
||||
</slave>
|
||||
""")
|
||||
|
||||
assert result.value["configured"] is True
|
||||
assert result.value["changed"] is True
|
||||
|
||||
|
||||
def test_configure_labels_when_equal(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = """
|
||||
<slave>
|
||||
<label>a b c</label>
|
||||
</slave>
|
||||
"""
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
"labels": [
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
],
|
||||
})
|
||||
|
||||
with raises(AnsibleExitJson) as result:
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.reconfig_node.called
|
||||
|
||||
assert result.value["configured"] is False
|
||||
assert result.value["changed"] is False
|
||||
|
||||
|
||||
def test_configure_labels_fail_when_contains_space(get_instance, instance):
|
||||
instance.node_exists.return_value = True
|
||||
instance.get_node_config.return_value = "<slave />"
|
||||
|
||||
set_module_args({
|
||||
"name": "my-node",
|
||||
"state": "present",
|
||||
"labels": [
|
||||
"a error",
|
||||
],
|
||||
})
|
||||
|
||||
with raises(AnsibleFailJson):
|
||||
jenkins_node.main()
|
||||
|
||||
assert not instance.reconfig_node.called
|
|
@ -54,3 +54,6 @@ proxmoxer ; python_version > '3.6'
|
|||
#requirements for nomad_token modules
|
||||
python-nomad < 2.0.0 ; python_version <= '3.6'
|
||||
python-nomad >= 2.0.0 ; python_version >= '3.7'
|
||||
|
||||
# requirement for jenkins_build, jenkins_node, jenkins_plugin modules
|
||||
python-jenkins >= 0.4.12
|
Loading…
Reference in New Issue