mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #6300/a35542d0 backport][stable-6] Rundeck modules fixes and improvements (#6343)
Rundeck modules fixes and improvements (#6300)
* feat: add token alias to api_token parameter
* fix: return (None, info) on empty content response
* feat: update the modules for using module_utils.rundeck funcs
* docs: add changelog fragment
* fix: add trailing commas
* fix: changelog fragment invalid syntax
* fix: changelog typos
* fix: remove token aliases from api_token
* fix: add token alias to api_token param
* fix: add partial overwrite of params and docs
(cherry picked from commit a35542d0d1
)
Co-authored-by: Phillipe Smith <phsmithcc@gmail.com>
This commit is contained in:
parent
590ff351b4
commit
aa8728b22c
4 changed files with 106 additions and 134 deletions
|
@ -0,0 +1,8 @@
|
||||||
|
bugfixes:
|
||||||
|
- rundeck_acl_policy - fix ``TypeError - byte indices must be integers or slices, not str`` error caused by empty API response. Update the module to use ``module_utils.rundeck`` functions
|
||||||
|
(https://github.com/ansible-collections/community.general/pull/5887,
|
||||||
|
https://github.com/ansible-collections/community.general/pull/6300).
|
||||||
|
- rundeck_project - update the module to use ``module_utils.rundeck`` functions
|
||||||
|
(https://github.com/ansible-collections/community.general/issues/5742)
|
||||||
|
(https://github.com/ansible-collections/community.general/pull/6300)
|
||||||
|
- rundeck module utils - fix errors caused by the API empty responses (https://github.com/ansible-collections/community.general/pull/6300)
|
|
@ -81,12 +81,18 @@ def api_request(module, endpoint, data=None, method="GET"):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = response.read()
|
content = response.read()
|
||||||
json_response = json.loads(content)
|
|
||||||
return json_response, info
|
if not content:
|
||||||
|
return None, info
|
||||||
|
else:
|
||||||
|
json_response = json.loads(content)
|
||||||
|
return json_response, info
|
||||||
except AttributeError as error:
|
except AttributeError as error:
|
||||||
module.fail_json(msg="Rundeck API request error",
|
module.fail_json(
|
||||||
exception=to_native(error),
|
msg="Rundeck API request error",
|
||||||
execution_info=info)
|
exception=to_native(error),
|
||||||
|
execution_info=info
|
||||||
|
)
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="No valid JSON response",
|
msg="No valid JSON response",
|
||||||
|
|
|
@ -36,22 +36,10 @@ options:
|
||||||
description:
|
description:
|
||||||
- Sets the project name.
|
- Sets the project name.
|
||||||
required: true
|
required: true
|
||||||
url:
|
api_token:
|
||||||
type: str
|
|
||||||
description:
|
|
||||||
- Sets the rundeck instance URL.
|
|
||||||
required: true
|
|
||||||
api_version:
|
|
||||||
type: int
|
|
||||||
description:
|
|
||||||
- Sets the API version used by module.
|
|
||||||
- API version must be at least 14.
|
|
||||||
default: 14
|
|
||||||
token:
|
|
||||||
type: str
|
|
||||||
description:
|
description:
|
||||||
- Sets the token to authenticate against Rundeck API.
|
- Sets the token to authenticate against Rundeck API.
|
||||||
required: true
|
aliases: ["token"]
|
||||||
project:
|
project:
|
||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
|
@ -82,8 +70,9 @@ options:
|
||||||
validate_certs:
|
validate_certs:
|
||||||
version_added: '0.2.0'
|
version_added: '0.2.0'
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- ansible.builtin.url
|
- ansible.builtin.url
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
|
- community.general.rundeck
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
|
@ -107,7 +96,7 @@ EXAMPLES = '''
|
||||||
|
|
||||||
- name: Remove a rundeck system policy
|
- name: Remove a rundeck system policy
|
||||||
community.general.rundeck_acl_policy:
|
community.general.rundeck_acl_policy:
|
||||||
name: "Project_02"
|
name: "Project_01"
|
||||||
url: "https://rundeck.example.org"
|
url: "https://rundeck.example.org"
|
||||||
token: "mytoken"
|
token: "mytoken"
|
||||||
state: absent
|
state: absent
|
||||||
|
@ -129,49 +118,25 @@ after:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# import module snippets
|
# import module snippets
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.urls import fetch_url, url_argument_spec
|
|
||||||
from ansible.module_utils.common.text.converters import to_text
|
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.rundeck import (
|
||||||
|
api_argument_spec,
|
||||||
|
api_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RundeckACLManager:
|
class RundeckACLManager:
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
self.module = module
|
self.module = module
|
||||||
|
|
||||||
def handle_http_code_if_needed(self, infos):
|
|
||||||
if infos["status"] == 403:
|
|
||||||
self.module.fail_json(msg="Token not allowed. Please ensure token is allowed or has the correct "
|
|
||||||
"permissions.", rundeck_response=infos["body"])
|
|
||||||
elif infos["status"] >= 500:
|
|
||||||
self.module.fail_json(msg="Fatal Rundeck API error.", rundeck_response=infos["body"])
|
|
||||||
|
|
||||||
def request_rundeck_api(self, query, data=None, method="GET"):
|
|
||||||
resp, info = fetch_url(self.module,
|
|
||||||
"%s/api/%d/%s" % (self.module.params["url"], self.module.params["api_version"], query),
|
|
||||||
data=json.dumps(data),
|
|
||||||
method=method,
|
|
||||||
headers={
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Accept": "application/json",
|
|
||||||
"X-Rundeck-Auth-Token": self.module.params["token"]
|
|
||||||
})
|
|
||||||
|
|
||||||
self.handle_http_code_if_needed(info)
|
|
||||||
if resp is not None:
|
|
||||||
resp = resp.read()
|
|
||||||
if resp != b"":
|
|
||||||
try:
|
|
||||||
json_resp = json.loads(to_text(resp, errors='surrogate_or_strict'))
|
|
||||||
return json_resp, info
|
|
||||||
except ValueError as e:
|
|
||||||
self.module.fail_json(msg="Rundeck response was not a valid JSON. Exception was: %s. "
|
|
||||||
"Object was: %s" % (str(e), resp))
|
|
||||||
return resp, info
|
|
||||||
|
|
||||||
def get_acl(self):
|
def get_acl(self):
|
||||||
resp, info = self.request_rundeck_api("system/acl/%s.aclpolicy" % self.module.params["name"])
|
resp, info = api_request(
|
||||||
|
module=self.module,
|
||||||
|
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||||
|
)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def create_or_update_acl(self):
|
def create_or_update_acl(self):
|
||||||
|
@ -181,9 +146,12 @@ class RundeckACLManager:
|
||||||
if self.module.check_mode:
|
if self.module.check_mode:
|
||||||
self.module.exit_json(changed=True, before={}, after=self.module.params["policy"])
|
self.module.exit_json(changed=True, before={}, after=self.module.params["policy"])
|
||||||
|
|
||||||
dummy, info = self.request_rundeck_api("system/acl/%s.aclpolicy" % self.module.params["name"],
|
resp, info = api_request(
|
||||||
method="POST",
|
module=self.module,
|
||||||
data={"contents": self.module.params["policy"]})
|
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||||
|
method="POST",
|
||||||
|
data={"contents": self.module.params["policy"]},
|
||||||
|
)
|
||||||
|
|
||||||
if info["status"] == 201:
|
if info["status"] == 201:
|
||||||
self.module.exit_json(changed=True, before={}, after=self.get_acl())
|
self.module.exit_json(changed=True, before={}, after=self.get_acl())
|
||||||
|
@ -202,9 +170,12 @@ class RundeckACLManager:
|
||||||
if self.module.check_mode:
|
if self.module.check_mode:
|
||||||
self.module.exit_json(changed=True, before=facts, after=facts)
|
self.module.exit_json(changed=True, before=facts, after=facts)
|
||||||
|
|
||||||
dummy, info = self.request_rundeck_api("system/acl/%s.aclpolicy" % self.module.params["name"],
|
resp, info = api_request(
|
||||||
method="PUT",
|
module=self.module,
|
||||||
data={"contents": self.module.params["policy"]})
|
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||||
|
method="PUT",
|
||||||
|
data={"contents": self.module.params["policy"]},
|
||||||
|
)
|
||||||
|
|
||||||
if info["status"] == 200:
|
if info["status"] == 200:
|
||||||
self.module.exit_json(changed=True, before=facts, after=self.get_acl())
|
self.module.exit_json(changed=True, before=facts, after=self.get_acl())
|
||||||
|
@ -216,34 +187,39 @@ class RundeckACLManager:
|
||||||
|
|
||||||
def remove_acl(self):
|
def remove_acl(self):
|
||||||
facts = self.get_acl()
|
facts = self.get_acl()
|
||||||
|
|
||||||
if facts is None:
|
if facts is None:
|
||||||
self.module.exit_json(changed=False, before={}, after={})
|
self.module.exit_json(changed=False, before={}, after={})
|
||||||
else:
|
else:
|
||||||
# If not in check mode, remove the project
|
# If not in check mode, remove the project
|
||||||
if not self.module.check_mode:
|
if not self.module.check_mode:
|
||||||
self.request_rundeck_api("system/acl/%s.aclpolicy" % self.module.params["name"], method="DELETE")
|
api_request(
|
||||||
self.module.exit_json(changed=True, before=facts, after={})
|
module=self.module,
|
||||||
|
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
||||||
|
method="DELETE",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.module.exit_json(changed=True, before=facts, after={})
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Also allow the user to set values for fetch_url
|
# Also allow the user to set values for fetch_url
|
||||||
argument_spec = url_argument_spec()
|
argument_spec = api_argument_spec()
|
||||||
argument_spec.update(dict(
|
argument_spec.update(dict(
|
||||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||||
name=dict(required=True, type='str'),
|
name=dict(required=True, type='str'),
|
||||||
url=dict(required=True, type='str'),
|
|
||||||
api_version=dict(type='int', default=14),
|
|
||||||
token=dict(required=True, type='str', no_log=True),
|
|
||||||
policy=dict(type='str'),
|
policy=dict(type='str'),
|
||||||
project=dict(type='str'),
|
project=dict(type='str'),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
argument_spec['api_token']['aliases'] = ['token']
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec,
|
argument_spec=argument_spec,
|
||||||
required_if=[
|
required_if=[
|
||||||
['state', 'present', ['policy']],
|
['state', 'present', ['policy']],
|
||||||
],
|
],
|
||||||
supports_check_mode=True
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not bool(re.match("[a-zA-Z0-9,.+_-]+", module.params["name"])):
|
if not bool(re.match("[a-zA-Z0-9,.+_-]+", module.params["name"])):
|
||||||
|
|
|
@ -38,22 +38,10 @@ options:
|
||||||
description:
|
description:
|
||||||
- Sets the project name.
|
- Sets the project name.
|
||||||
required: true
|
required: true
|
||||||
url:
|
api_token:
|
||||||
type: str
|
|
||||||
description:
|
|
||||||
- Sets the rundeck instance URL.
|
|
||||||
required: true
|
|
||||||
api_version:
|
|
||||||
type: int
|
|
||||||
description:
|
|
||||||
- Sets the API version used by module.
|
|
||||||
- API version must be at least 14.
|
|
||||||
default: 14
|
|
||||||
token:
|
|
||||||
type: str
|
|
||||||
description:
|
description:
|
||||||
- Sets the token to authenticate against Rundeck API.
|
- Sets the token to authenticate against Rundeck API.
|
||||||
required: true
|
aliases: ["token"]
|
||||||
client_cert:
|
client_cert:
|
||||||
version_added: '0.2.0'
|
version_added: '0.2.0'
|
||||||
client_key:
|
client_key:
|
||||||
|
@ -73,24 +61,27 @@ options:
|
||||||
validate_certs:
|
validate_certs:
|
||||||
version_added: '0.2.0'
|
version_added: '0.2.0'
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- ansible.builtin.url
|
- ansible.builtin.url
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
|
- community.general.rundeck
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
- name: Create a rundeck project
|
- name: Create a rundeck project
|
||||||
community.general.rundeck_project:
|
community.general.rundeck_project:
|
||||||
name: "Project_01"
|
name: "Project_01"
|
||||||
api_version: 18
|
label: "Project 01"
|
||||||
|
description: "My Project 01"
|
||||||
url: "https://rundeck.example.org"
|
url: "https://rundeck.example.org"
|
||||||
token: "mytoken"
|
api_version: 39
|
||||||
|
api_token: "mytoken"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Remove a rundeck project
|
- name: Remove a rundeck project
|
||||||
community.general.rundeck_project:
|
community.general.rundeck_project:
|
||||||
name: "Project_02"
|
name: "Project_01"
|
||||||
url: "https://rundeck.example.org"
|
url: "https://rundeck.example.org"
|
||||||
token: "mytoken"
|
api_token: "mytoken"
|
||||||
state: absent
|
state: absent
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -111,60 +102,47 @@ after:
|
||||||
|
|
||||||
# import module snippets
|
# import module snippets
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
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.rundeck import (
|
||||||
from ansible.module_utils.urls import fetch_url, url_argument_spec
|
api_argument_spec,
|
||||||
import json
|
api_request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RundeckProjectManager(object):
|
class RundeckProjectManager(object):
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
self.module = module
|
self.module = module
|
||||||
|
|
||||||
def handle_http_code_if_needed(self, infos):
|
|
||||||
if infos["status"] == 403:
|
|
||||||
self.module.fail_json(msg="Token not allowed. Please ensure token is allowed or has the correct "
|
|
||||||
"permissions.", rundeck_response=infos["body"])
|
|
||||||
elif infos["status"] >= 500:
|
|
||||||
self.module.fail_json(msg="Fatal Rundeck API error.", rundeck_response=infos["body"])
|
|
||||||
|
|
||||||
def request_rundeck_api(self, query, data=None, method="GET"):
|
|
||||||
resp, info = fetch_url(self.module,
|
|
||||||
"%s/api/%d/%s" % (self.module.params["url"], self.module.params["api_version"], query),
|
|
||||||
data=json.dumps(data),
|
|
||||||
method=method,
|
|
||||||
headers={
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Accept": "application/json",
|
|
||||||
"X-Rundeck-Auth-Token": self.module.params["token"]
|
|
||||||
})
|
|
||||||
|
|
||||||
self.handle_http_code_if_needed(info)
|
|
||||||
if resp is not None:
|
|
||||||
resp = resp.read()
|
|
||||||
if resp != "":
|
|
||||||
try:
|
|
||||||
json_resp = json.loads(resp)
|
|
||||||
return json_resp, info
|
|
||||||
except ValueError as e:
|
|
||||||
self.module.fail_json(msg="Rundeck response was not a valid JSON. Exception was: %s. "
|
|
||||||
"Object was: %s" % (to_native(e), resp))
|
|
||||||
return resp, info
|
|
||||||
|
|
||||||
def get_project_facts(self):
|
def get_project_facts(self):
|
||||||
resp, info = self.request_rundeck_api("project/%s" % self.module.params["name"])
|
resp, info = api_request(
|
||||||
|
module=self.module,
|
||||||
|
endpoint="project/%s" % self.module.params["name"],
|
||||||
|
)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def create_or_update_project(self):
|
def create_or_update_project(self):
|
||||||
facts = self.get_project_facts()
|
facts = self.get_project_facts()
|
||||||
|
|
||||||
if facts is None:
|
if facts is None:
|
||||||
# If in check mode don't create project, simulate a fake project creation
|
# If in check mode don't create project, simulate a fake project creation
|
||||||
if self.module.check_mode:
|
if self.module.check_mode:
|
||||||
self.module.exit_json(changed=True, before={}, after={"name": self.module.params["name"]})
|
self.module.exit_json(
|
||||||
|
changed=True,
|
||||||
|
before={},
|
||||||
|
after={
|
||||||
|
"name": self.module.params["name"]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
resp, info = self.request_rundeck_api("projects", method="POST", data={
|
resp, info = api_request(
|
||||||
"name": self.module.params["name"],
|
module=self.module,
|
||||||
"config": {}
|
endpoint="projects",
|
||||||
})
|
method="POST",
|
||||||
|
data={
|
||||||
|
"name": self.module.params["name"],
|
||||||
|
"config": {},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if info["status"] == 201:
|
if info["status"] == 201:
|
||||||
self.module.exit_json(changed=True, before={}, after=self.get_project_facts())
|
self.module.exit_json(changed=True, before={}, after=self.get_project_facts())
|
||||||
|
@ -181,21 +159,25 @@ class RundeckProjectManager(object):
|
||||||
else:
|
else:
|
||||||
# If not in check mode, remove the project
|
# If not in check mode, remove the project
|
||||||
if not self.module.check_mode:
|
if not self.module.check_mode:
|
||||||
self.request_rundeck_api("project/%s" % self.module.params["name"], method="DELETE")
|
api_request(
|
||||||
|
module=self.module,
|
||||||
|
endpoint="project/%s" % self.module.params["name"],
|
||||||
|
method="DELETE",
|
||||||
|
)
|
||||||
|
|
||||||
self.module.exit_json(changed=True, before=facts, after={})
|
self.module.exit_json(changed=True, before=facts, after={})
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Also allow the user to set values for fetch_url
|
# Also allow the user to set values for fetch_url
|
||||||
argument_spec = url_argument_spec()
|
argument_spec = api_argument_spec()
|
||||||
argument_spec.update(dict(
|
argument_spec.update(dict(
|
||||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||||
name=dict(required=True, type='str'),
|
name=dict(required=True, type='str'),
|
||||||
url=dict(required=True, type='str'),
|
|
||||||
api_version=dict(type='int', default=14),
|
|
||||||
token=dict(required=True, type='str', no_log=True),
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
argument_spec['api_token']['aliases'] = ['token']
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec,
|
argument_spec=argument_spec,
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
|
|
Loading…
Reference in a new issue