Skip to content

Commit 50663a1

Browse files
authored
Merge pull request #208 from mdellweg/user_group
User group
2 parents f4049a4 + 44a39e0 commit 50663a1

File tree

21 files changed

+129624
-43
lines changed

21 files changed

+129624
-43
lines changed

meta/runtime.yml

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,46 @@
11
---
22
action_groups:
33
squeezer:
4-
- access_policy
5-
- ansible_distribution
6-
- ansible_remote
7-
- ansible_repository
8-
- ansible_role
9-
- ansible_sync
10-
- api_call
11-
- artifact
12-
- container_distribution
13-
- container_remote
14-
- container_repository
15-
- container_sync
16-
- deb_distribution
17-
- deb_publication
18-
- deb_remote
19-
- deb_repository
20-
- deb_sync
21-
- delete_orphans
22-
- file_content
23-
- file_distribution
24-
- file_publication
25-
- file_remote
26-
- file_repository
27-
- file_sync
28-
- purge_tasks
29-
- python_distribution
30-
- python_publication
31-
- python_remote
32-
- python_repository
33-
- python_sync
34-
- repair
35-
- rpm_distribution
36-
- rpm_publication
37-
- rpm_remote
38-
- rpm_repository
39-
- rpm_sync
40-
- status
41-
- task
42-
- x509_cert_guard
43-
requires_ansible: ">=2.15.0,<2.19"
4+
- access_policy
5+
- ansible_distribution
6+
- ansible_remote
7+
- ansible_repository
8+
- ansible_role
9+
- ansible_sync
10+
- api_call
11+
- artifact
12+
- container_distribution
13+
- container_remote
14+
- container_repository
15+
- container_sync
16+
- deb_distribution
17+
- deb_publication
18+
- deb_remote
19+
- deb_repository
20+
- deb_sync
21+
- delete_orphans
22+
- file_content
23+
- file_distribution
24+
- file_publication
25+
- file_remote
26+
- file_repository
27+
- file_sync
28+
- group
29+
- purge_tasks
30+
- python_distribution
31+
- python_publication
32+
- python_remote
33+
- python_repository
34+
- python_sync
35+
- repair
36+
- rpm_distribution
37+
- rpm_publication
38+
- rpm_remote
39+
- rpm_repository
40+
- rpm_sync
41+
- status
42+
- task
43+
- user
44+
- x509_cert_guard
45+
requires_ansible: '>=2.15.0,<2.19'
4446
...

plugins/module_utils/pulp_glue.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib
1212

13-
GLUE_VERSION_SPEC = ">=0.29.2,<0.32"
13+
GLUE_VERSION_SPEC = ">=0.29.2,<0.35"
1414
GLUE_DEB_VERSION_SPEC = ">=0.3.0,<0.4"
1515

1616

@@ -184,7 +184,7 @@ def process(self, natural_key, desired_attributes, defaults=None):
184184
self.process_special(desired_attributes, defaults=defaults),
185185
)
186186
return
187-
changed, before, after = self.context.converge(desired_entity, defaults=defaults)
187+
changed, before, after = self.process_converge(desired_entity, defaults=defaults)
188188
if before is not None:
189189
before = self.represent(before)
190190
if after is not None:
@@ -195,6 +195,9 @@ def process(self, natural_key, desired_attributes, defaults=None):
195195
self.record_diff_state(after)
196196
self.set_result(self.entity_singular, after)
197197

198+
def process_converge(self, desired_entity, defaults=None):
199+
return self.context.converge(desired_entity, defaults=defaults)
200+
198201
def process_info(self, natural_key, desired_attributes):
199202
if any((value is not None for value in desired_attributes.values())):
200203
raise SqueezerException("Cannot use attributes when querying entities.")

plugins/modules/group.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/python
2+
3+
# copyright (c) 2025, Matthias Dellweg
4+
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
7+
DOCUMENTATION = r"""
8+
---
9+
module: group
10+
short_description: Manage groups of a pulp api server instance
11+
description:
12+
- "This performs CRUD operations on groups in a pulp api server instance."
13+
options:
14+
group:
15+
description:
16+
- Group object to manipulate
17+
type: dict
18+
suboptions:
19+
name:
20+
description:
21+
- Name of the group to query or manipulate
22+
type: str
23+
required: true
24+
extends_documentation_fragment:
25+
- pulp.squeezer.pulp.entity_state
26+
- pulp.squeezer.pulp
27+
author:
28+
- Matthias Dellweg (@mdellweg)
29+
"""
30+
31+
EXAMPLES = r"""
32+
- name: Read list of groups from pulp api server
33+
pulp.squeezer.group:
34+
pulp_url: https://pulp.example.org
35+
username: admin
36+
password: password
37+
register: groups
38+
- name: Report groups
39+
debug:
40+
var: groups
41+
"""
42+
43+
RETURN = r"""
44+
users:
45+
description: List of groups
46+
type: list
47+
returned: when no group is given
48+
user:
49+
description: group details
50+
type: dict
51+
returned: when group.name is given
52+
"""
53+
54+
55+
import traceback
56+
57+
from ansible_collections.pulp.squeezer.plugins.module_utils.pulp_glue import PulpEntityAnsibleModule
58+
59+
try:
60+
from pulp_glue.core.context import PulpGroupContext
61+
62+
PULP_GLUE_IMPORT_ERR = None
63+
except ImportError:
64+
PULP_GLUE_IMPORT_ERR = traceback.format_exc()
65+
PulpGroupContext = None
66+
67+
68+
def main():
69+
with PulpEntityAnsibleModule(
70+
context_class=PulpGroupContext,
71+
entity_singular="group",
72+
entity_plural="groups",
73+
import_errors=[("pulp-glue", PULP_GLUE_IMPORT_ERR)],
74+
argument_spec={
75+
"group": {
76+
"type": "dict",
77+
"options": {
78+
"name": {"required": True},
79+
},
80+
}
81+
},
82+
required_if=[("state", "present", ["group"]), ("state", "absent", ["group"])],
83+
) as module:
84+
group = module.params["group"]
85+
natural_key = {"name": group and group["name"]}
86+
desired_attributes = {}
87+
88+
module.process(natural_key, desired_attributes)
89+
90+
91+
if __name__ == "__main__":
92+
main()

plugins/modules/user.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/python
2+
3+
# copyright (c) 2025, Matthias Dellweg
4+
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
7+
DOCUMENTATION = r"""
8+
---
9+
module: user
10+
short_description: Manage users of a pulp api server instance
11+
description:
12+
- "This performs CRUD operations on users in a pulp api server instance."
13+
options:
14+
user:
15+
description:
16+
- User object to manipulate
17+
type: dict
18+
suboptions:
19+
username:
20+
description:
21+
- Username of the user to query or manipulate
22+
type: str
23+
required: true
24+
password:
25+
description:
26+
- Users password
27+
type: str
28+
first_name:
29+
description:
30+
- Users first name
31+
type: str
32+
last_name:
33+
description:
34+
- Users last name
35+
type: str
36+
email:
37+
description:
38+
- Users e-mail
39+
type: str
40+
is_active:
41+
description:
42+
- Whether the user can log in
43+
type: bool
44+
is_staff:
45+
description:
46+
- Whether the user belongs to the staff
47+
- This django attribute has no effect on pulp operations
48+
type: bool
49+
groups:
50+
description:
51+
- List of groups the user should be in
52+
type: list
53+
elements: str
54+
extends_documentation_fragment:
55+
- pulp.squeezer.pulp.entity_state
56+
- pulp.squeezer.pulp
57+
author:
58+
- Matthias Dellweg (@mdellweg)
59+
"""
60+
61+
EXAMPLES = r"""
62+
- name: Read list of users from pulp api server
63+
pulp.squeezer.user:
64+
pulp_url: https://pulp.example.org
65+
username: admin
66+
password: password
67+
register: users
68+
- name: Report users
69+
debug:
70+
var: users
71+
"""
72+
73+
RETURN = r"""
74+
users:
75+
description: List of users
76+
type: list
77+
returned: when no user is given
78+
user:
79+
description: user details
80+
type: dict
81+
returned: when user.username is given
82+
"""
83+
84+
85+
import traceback
86+
87+
from ansible_collections.pulp.squeezer.plugins.module_utils.pulp_glue import PulpEntityAnsibleModule
88+
89+
try:
90+
from pulp_glue.core.context import PulpGroupContext, PulpGroupUserContext, PulpUserContext
91+
92+
PULP_GLUE_IMPORT_ERR = None
93+
except ImportError:
94+
PULP_GLUE_IMPORT_ERR = traceback.format_exc()
95+
PulpUserContext = None
96+
PulpGroupContext = None
97+
PulpGroupUserContext = None
98+
99+
100+
class PulpUserAnsibleModule(PulpEntityAnsibleModule):
101+
def process_converge(self, desired_entity, defaults=None):
102+
# Ideally glue would do this for us...
103+
groups = desired_entity and desired_entity.pop("groups", None)
104+
changed, before, after = super().process_converge(desired_entity, defaults=defaults)
105+
if self.check_mode and after is not None:
106+
# Fake the groups.
107+
after.setdefault("groups", [])
108+
if groups is not None:
109+
desired_groups = set(groups)
110+
actual_groups = {g["name"] for g in after["groups"]}
111+
missing_groups = desired_groups - actual_groups
112+
superfluous_groups = actual_groups - desired_groups
113+
for group in missing_groups:
114+
group_ctx = PulpGroupContext(self.pulp_ctx, entity={"name": group})
115+
# Apparently pulp_glue did never implement this.
116+
# group_ctx.add_user(after["username"])
117+
# ---8<--- Workaround ----8<----
118+
group_user_ctx = PulpGroupUserContext(self.pulp_ctx, group_ctx)
119+
group_user_ctx.create(body={"username": self.context.entity["username"]})
120+
# ---8<-------8<----
121+
after["groups"].append(group_ctx.entity)
122+
changed = True
123+
for group in superfluous_groups:
124+
group_ctx = PulpGroupContext(self.pulp_ctx, entity={"name": group})
125+
group_ctx.remove_user(self.context)
126+
after["groups"] = [g for g in after["groups"] if g["name"] != group]
127+
changed = True
128+
129+
return changed, before, after
130+
131+
132+
def main():
133+
with PulpUserAnsibleModule(
134+
context_class=PulpUserContext,
135+
entity_singular="user",
136+
entity_plural="users",
137+
import_errors=[("pulp-glue", PULP_GLUE_IMPORT_ERR)],
138+
argument_spec={
139+
"user": {
140+
"type": "dict",
141+
"options": {
142+
"username": {"required": True},
143+
"password": {"no_log": True},
144+
"first_name": {},
145+
"last_name": {},
146+
"email": {},
147+
"is_active": {"type": "bool"},
148+
"is_staff": {"type": "bool"},
149+
"groups": {"type": "list", "elements": "str"},
150+
},
151+
}
152+
},
153+
required_if=[("state", "present", ["user"]), ("state", "absent", ["user"])],
154+
) as module:
155+
user = module.params["user"]
156+
natural_key = {"username": user and user["username"]}
157+
desired_attributes = {}
158+
if user is not None:
159+
for key, value in user.items():
160+
if key not in ["username"] and value is not None:
161+
desired_attributes[key] = value
162+
163+
module.process(natural_key, desired_attributes)
164+
165+
166+
if __name__ == "__main__":
167+
main()

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pulp-glue<0.32,>=0.29.2
1+
pulp-glue<0.35,>=0.29.2
22
pulp-glue-deb<0.4.0,>=0.3.0
33

44
ansible_runner<2.3; python_version < '3.7'

0 commit comments

Comments
 (0)