mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-06-28 11:10:21 -07:00
rundeck_acl_policy: fix project acls are put/posted to the wrong endpoint (#10097)
* Fix project acls are put/posted to the wrong endpoint * Add changelog fragment. * Fix 2.7 sanity errors in github * Fix fragment extension and use 2.7 syntax in test * Update changelogs/fragments/10097-fix-rundeck_acl_policy-project-endpoint.yml Co-authored-by: Felix Fontein <felix@fontein.de> * Fix pep8 formatting * Add licensing to unit test --------- Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
2b4cb6dabc
commit
ff0ed6f912
7 changed files with 277 additions and 7 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
bugfixes:
|
||||||
|
- rundeck_acl_policy - ensure that project ACLs are sent to the correct endpoint (https://github.com/ansible-collections/community.general/pull/10097).
|
|
@ -129,11 +129,18 @@ from ansible_collections.community.general.plugins.module_utils.rundeck import (
|
||||||
class RundeckACLManager:
|
class RundeckACLManager:
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
self.module = module
|
self.module = module
|
||||||
|
if module.params.get("project"):
|
||||||
|
self.endpoint = "project/%s/acl/%s.aclpolicy" % (
|
||||||
|
self.module.params["project"],
|
||||||
|
self.module.params["name"],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.endpoint = "system/acl/%s.aclpolicy" % self.module.params["name"]
|
||||||
|
|
||||||
def get_acl(self):
|
def get_acl(self):
|
||||||
resp, info = api_request(
|
resp, info = api_request(
|
||||||
module=self.module,
|
module=self.module,
|
||||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
endpoint=self.endpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
@ -147,7 +154,7 @@ class RundeckACLManager:
|
||||||
|
|
||||||
resp, info = api_request(
|
resp, info = api_request(
|
||||||
module=self.module,
|
module=self.module,
|
||||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
endpoint=self.endpoint,
|
||||||
method="POST",
|
method="POST",
|
||||||
data={"contents": self.module.params["policy"]},
|
data={"contents": self.module.params["policy"]},
|
||||||
)
|
)
|
||||||
|
@ -171,7 +178,7 @@ class RundeckACLManager:
|
||||||
|
|
||||||
resp, info = api_request(
|
resp, info = api_request(
|
||||||
module=self.module,
|
module=self.module,
|
||||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
endpoint=self.endpoint,
|
||||||
method="PUT",
|
method="PUT",
|
||||||
data={"contents": self.module.params["policy"]},
|
data={"contents": self.module.params["policy"]},
|
||||||
)
|
)
|
||||||
|
@ -194,7 +201,7 @@ class RundeckACLManager:
|
||||||
if not self.module.check_mode:
|
if not self.module.check_mode:
|
||||||
api_request(
|
api_request(
|
||||||
module=self.module,
|
module=self.module,
|
||||||
endpoint="system/acl/%s.aclpolicy" % self.module.params["name"],
|
endpoint=self.endpoint,
|
||||||
method="DELETE",
|
method="DELETE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,32 @@
|
||||||
rundeck_url: http://localhost:4440
|
rundeck_url: http://localhost:4440
|
||||||
rundeck_api_version: 39
|
rundeck_api_version: 39
|
||||||
rundeck_job_id: 3b8a6e54-69fb-42b7-b98f-f82e59238478
|
rundeck_job_id: 3b8a6e54-69fb-42b7-b98f-f82e59238478
|
||||||
|
|
||||||
|
system_acl_policy: |
|
||||||
|
description: Test ACL
|
||||||
|
context:
|
||||||
|
application: 'rundeck'
|
||||||
|
for:
|
||||||
|
project:
|
||||||
|
- allow:
|
||||||
|
- read
|
||||||
|
by:
|
||||||
|
group:
|
||||||
|
- users
|
||||||
|
|
||||||
|
project_acl_policy: |
|
||||||
|
description: Test project acl
|
||||||
|
for:
|
||||||
|
resource:
|
||||||
|
- equals:
|
||||||
|
kind: node
|
||||||
|
allow: [read,refresh]
|
||||||
|
- equals:
|
||||||
|
kind: event
|
||||||
|
allow: [read]
|
||||||
|
job:
|
||||||
|
- allow: [run,kill]
|
||||||
|
node:
|
||||||
|
- allow: [read,run]
|
||||||
|
by:
|
||||||
|
group: users
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
RD_USER: admin
|
RD_USER: admin
|
||||||
RD_PASSWORD: admin
|
RD_PASSWORD: admin
|
||||||
register: rundeck_api_token
|
register: rundeck_api_token
|
||||||
|
retries: 3
|
||||||
|
until: rundeck_api_token.rc == 0
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
- name: Create a Rundeck project
|
- name: Create a Rundeck project
|
||||||
community.general.rundeck_project:
|
community.general.rundeck_project:
|
||||||
|
@ -24,6 +27,71 @@
|
||||||
token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
- name: Create a system ACL
|
||||||
|
community.general.rundeck_acl_policy:
|
||||||
|
name: test_acl
|
||||||
|
api_version: "{{ rundeck_api_version }}"
|
||||||
|
url: "{{ rundeck_url }}"
|
||||||
|
token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
||||||
|
state: present
|
||||||
|
policy: "{{ system_acl_policy }}"
|
||||||
|
|
||||||
|
- name: Create a project ACL
|
||||||
|
community.general.rundeck_acl_policy:
|
||||||
|
name: test_acl
|
||||||
|
api_version: "{{ rundeck_api_version }}"
|
||||||
|
url: "{{ rundeck_url }}"
|
||||||
|
token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
||||||
|
state: present
|
||||||
|
policy: "{{ project_acl_policy }}"
|
||||||
|
project: test_project
|
||||||
|
|
||||||
|
- name: Retrieve ACLs
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ rundeck_url }}/api/{{ rundeck_api_version }}/{{ item }}"
|
||||||
|
headers:
|
||||||
|
accept: application/json
|
||||||
|
x-rundeck-auth-token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
||||||
|
register: acl_policy_check
|
||||||
|
loop:
|
||||||
|
- system/acl/test_acl.aclpolicy
|
||||||
|
- project/test_project/acl/test_acl.aclpolicy
|
||||||
|
|
||||||
|
- name: Assert ACL content is correct
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- acl_policy_check['results'][0]['json']['contents'] == system_acl_policy
|
||||||
|
- acl_policy_check['results'][1]['json']['contents'] == project_acl_policy
|
||||||
|
|
||||||
|
- name: Remove system ACL
|
||||||
|
community.general.rundeck_acl_policy:
|
||||||
|
name: test_acl
|
||||||
|
api_version: "{{ rundeck_api_version }}"
|
||||||
|
url: "{{ rundeck_url }}"
|
||||||
|
token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Remove project ACL
|
||||||
|
community.general.rundeck_acl_policy:
|
||||||
|
name: test_acl
|
||||||
|
api_version: "{{ rundeck_api_version }}"
|
||||||
|
url: "{{ rundeck_url }}"
|
||||||
|
token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
||||||
|
state: absent
|
||||||
|
project: test_project
|
||||||
|
|
||||||
|
- name: Check that ACLs have been removed
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ rundeck_url }}/api/{{ rundeck_api_version }}/{{ item }}"
|
||||||
|
headers:
|
||||||
|
accept: application/json
|
||||||
|
x-rundeck-auth-token: "{{ rundeck_api_token.stdout_lines[-1] }}"
|
||||||
|
status_code:
|
||||||
|
- 404
|
||||||
|
loop:
|
||||||
|
- system/acl/test_acl.aclpolicy
|
||||||
|
- project/test_project/acl/test_acl.aclpolicy
|
||||||
|
|
||||||
- name: Copy test_job definition to /tmp
|
- name: Copy test_job definition to /tmp
|
||||||
copy:
|
copy:
|
||||||
src: test_job.yaml
|
src: test_job.yaml
|
||||||
|
|
|
@ -3,5 +3,13 @@
|
||||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
rundeck_war_url: https://packagecloud.io/pagerduty/rundeck/packages/java/org.rundeck/rundeck-3.4.4-20210920.war/artifacts/rundeck-3.4.4-20210920.war/download
|
rundeck_version: 5.11.1-20250415
|
||||||
rundeck_cli_url: https://github.com/rundeck/rundeck-cli/releases/download/v1.3.10/rundeck-cli-1.3.10-all.jar
|
rundeck_cli_version: "2.0.8"
|
||||||
|
|
||||||
|
rundeck_war_url:
|
||||||
|
"https://packagecloud.io/pagerduty/rundeck/packages/java/org.rundeck/\
|
||||||
|
rundeck-{{ rundeck_version }}.war/artifacts/rundeck-{{ rundeck_version }}.war/download"
|
||||||
|
|
||||||
|
rundeck_cli_url:
|
||||||
|
"https://github.com/rundeck/rundeck-cli/releases/download/\
|
||||||
|
v{{ rundeck_cli_version }}/rundeck-cli-{{ rundeck_cli_version }}-all.jar"
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
openjdk_pkg: java-1.8.0-openjdk
|
openjdk_pkg: java-11-openjdk-headless
|
||||||
|
|
156
tests/unit/plugins/modules/test_rundeck_acl_policy.py
Normal file
156
tests/unit/plugins/modules/test_rundeck_acl_policy.py
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
# -*- 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
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ansible_collections.community.general.plugins.modules import rundeck_acl_policy
|
||||||
|
from ansible_collections.community.internal_test_tools.tests.unit.compat.mock import patch
|
||||||
|
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import (
|
||||||
|
set_module_args,
|
||||||
|
AnsibleExitJson,
|
||||||
|
exit_json,
|
||||||
|
fail_json
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def module():
|
||||||
|
with patch.multiple(
|
||||||
|
"ansible.module_utils.basic.AnsibleModule",
|
||||||
|
exit_json=exit_json,
|
||||||
|
fail_json=fail_json,
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
# define our two table entries: system ACL vs. project ACL
|
||||||
|
PROJECT_TABLE = [
|
||||||
|
(None, "system/acl"),
|
||||||
|
("test_project", "project/test_project/acl"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("project, prefix", PROJECT_TABLE)
|
||||||
|
@patch.object(rundeck_acl_policy, 'api_request')
|
||||||
|
def test_acl_create(api_request_mock, project, prefix):
|
||||||
|
"""Test creating a new ACL, both system-level and project-level."""
|
||||||
|
name = "my_policy"
|
||||||
|
policy = "test_policy_yaml"
|
||||||
|
# simulate: GET→404, POST→201, final GET→200
|
||||||
|
api_request_mock.side_effect = [
|
||||||
|
(None, {'status': 404}),
|
||||||
|
(None, {'status': 201}),
|
||||||
|
({"contents": policy}, {'status': 200}),
|
||||||
|
]
|
||||||
|
args = {
|
||||||
|
'name': name,
|
||||||
|
'url': "https://rundeck.example.org",
|
||||||
|
'api_token': "mytoken",
|
||||||
|
'policy': policy,
|
||||||
|
}
|
||||||
|
if project:
|
||||||
|
args['project'] = project
|
||||||
|
|
||||||
|
with pytest.raises(AnsibleExitJson):
|
||||||
|
with set_module_args(args):
|
||||||
|
rundeck_acl_policy.main()
|
||||||
|
|
||||||
|
# should have done GET → POST → GET
|
||||||
|
assert api_request_mock.call_count == 3
|
||||||
|
args, kwargs = api_request_mock.call_args_list[1]
|
||||||
|
assert kwargs['endpoint'] == "%s/%s.aclpolicy" % (prefix, name)
|
||||||
|
assert kwargs['method'] == 'POST'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("project, prefix", PROJECT_TABLE)
|
||||||
|
@patch.object(rundeck_acl_policy, 'api_request')
|
||||||
|
def test_acl_unchanged(api_request_mock, project, prefix):
|
||||||
|
"""Test no-op when existing ACL contents match the desired policy."""
|
||||||
|
name = "unchanged_policy"
|
||||||
|
policy = "same_policy_yaml"
|
||||||
|
# first GET returns matching contents
|
||||||
|
api_request_mock.return_value = ({"contents": policy}, {'status': 200})
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'name': name,
|
||||||
|
'url': "https://rundeck.example.org",
|
||||||
|
'api_token': "mytoken",
|
||||||
|
'policy': policy,
|
||||||
|
}
|
||||||
|
if project:
|
||||||
|
args['project'] = project
|
||||||
|
|
||||||
|
with pytest.raises(AnsibleExitJson):
|
||||||
|
with set_module_args(args):
|
||||||
|
rundeck_acl_policy.main()
|
||||||
|
|
||||||
|
# only a single GET
|
||||||
|
assert api_request_mock.call_count == 1
|
||||||
|
args, kwargs = api_request_mock.call_args
|
||||||
|
assert kwargs['endpoint'] == "%s/%s.aclpolicy" % (prefix, name)
|
||||||
|
# default method is GET
|
||||||
|
assert kwargs.get('method', 'GET') == 'GET'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("project, prefix", PROJECT_TABLE)
|
||||||
|
@patch.object(rundeck_acl_policy, 'api_request')
|
||||||
|
def test_acl_remove(api_request_mock, project, prefix):
|
||||||
|
"""Test removing an existing ACL, both system- and project-level."""
|
||||||
|
name = "remove_me"
|
||||||
|
# GET finds it, DELETE removes it
|
||||||
|
api_request_mock.side_effect = [
|
||||||
|
({"contents": "old_yaml"}, {'status': 200}),
|
||||||
|
(None, {'status': 204}),
|
||||||
|
]
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'name': name,
|
||||||
|
'url': "https://rundeck.example.org",
|
||||||
|
'api_token': "mytoken",
|
||||||
|
'state': 'absent',
|
||||||
|
}
|
||||||
|
if project:
|
||||||
|
args['project'] = project
|
||||||
|
|
||||||
|
with pytest.raises(AnsibleExitJson):
|
||||||
|
with set_module_args(args):
|
||||||
|
rundeck_acl_policy.main()
|
||||||
|
|
||||||
|
# GET → DELETE
|
||||||
|
assert api_request_mock.call_count == 2
|
||||||
|
args, kwargs = api_request_mock.call_args_list[1]
|
||||||
|
assert kwargs['endpoint'] == "%s/%s.aclpolicy" % (prefix, name)
|
||||||
|
assert kwargs['method'] == 'DELETE'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("project, prefix", PROJECT_TABLE)
|
||||||
|
@patch.object(rundeck_acl_policy, 'api_request')
|
||||||
|
def test_acl_remove_nonexistent(api_request_mock, project, prefix):
|
||||||
|
"""Test removing a non-existent ACL results in no change."""
|
||||||
|
name = "not_there"
|
||||||
|
# GET returns 404
|
||||||
|
api_request_mock.return_value = (None, {'status': 404})
|
||||||
|
|
||||||
|
args = {
|
||||||
|
'name': name,
|
||||||
|
'url': "https://rundeck.example.org",
|
||||||
|
'api_token': "mytoken",
|
||||||
|
'state': 'absent',
|
||||||
|
}
|
||||||
|
if project:
|
||||||
|
args['project'] = project
|
||||||
|
|
||||||
|
with pytest.raises(AnsibleExitJson):
|
||||||
|
with set_module_args(args):
|
||||||
|
rundeck_acl_policy.main()
|
||||||
|
|
||||||
|
# only the initial GET
|
||||||
|
assert api_request_mock.call_count == 1
|
||||||
|
args, kwargs = api_request_mock.call_args
|
||||||
|
assert kwargs['endpoint'] == "%s/%s.aclpolicy" % (prefix, name)
|
||||||
|
assert kwargs.get('method', 'GET') == 'GET'
|
Loading…
Add table
Add a link
Reference in a new issue