Skip to content

Commit 319811a

Browse files
authored
feat: Add dynamic tagging and descriptions (#7)
This change will allow us to have a dynamic allocation of tags and project descriptions. Within the example map file, the DDI information will be defined as tag the tags allow projects to be searched. The change asserts that defined tags will always be set whenever a user is authenticated against a cloud with the plugin enabled. ``` shell openstack --os-cloud default project show --domain rackspace_cloud_domain 1342135_Flex +-------------+----------------------------------+ | Field | Value | +-------------+----------------------------------+ | ddi | 1342135 | | description | Project for DDI 1342135 | | domain_id | 61ad3a247515421aaeaba18ae35aa095 | | enabled | True | | id | de15709c1cd242a6b7451d43c52d1640 | | is_domain | False | | name | 1342135_Flex | | options | {} | | parent_id | 61ad3a247515421aaeaba18ae35aa095 | | tags | ['1342135'] | +-------------+----------------------------------+ ``` As seen in the above snippet, the project is now fully tagged, and labeled based on the mapping. Additionally admins can now search projects using the DDI as a tag, ``` shell openstack --os-cloud default project list --domain rackspace_cloud_domain --long --tags 1342135 +----------------------------------+--------------------------------------+----------------------------------+-------------------------+---------+ | ID | Name | Domain ID | Description | Enabled | +----------------------------------+--------------------------------------+----------------------------------+-------------------------+---------+ | 31a43637efc24ea19eea40af4e72d43d | 3965512c-e2c0-48a7-acef-3cdfb2b95ef8 | 61ad3a247515421aaeaba18ae35aa095 | Project for DDI 1342135 | True | | de15709c1cd242a6b7451d43c52d1640 | 1342135_Flex | 61ad3a247515421aaeaba18ae35aa095 | Project for DDI 1342135 | True | +----------------------------------+--------------------------------------+----------------------------------+-------------------------+---------+ ``` Issue: https://rackspace.atlassian.net/browse/OSPC-746 Signed-off-by: Kevin Carter <kevin.carter@rackspace.com>
1 parent f5afdf2 commit 319811a

File tree

3 files changed

+136
-1
lines changed

3 files changed

+136
-1
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ roles.
9090
Once the plugin is setup and running, everything will be operating normally. The plugin is passive until
9191
the Keystone is informed about the identity provider and we have the `rackspace_cloud_domain` created.
9292

93+
#### Mapping Setup
94+
95+
Available environment variables.
96+
97+
| Environment Variable | Explanation |
98+
| ----------------- | ----- |
99+
| `RXT_UserName` | Username to be mapped |
100+
| `RXT_Email` | Email address from the user name |
101+
| `RXT_DomainID` | Domain ID for the federated assignment |
102+
| `RXT_TenantName` | Semicolon separated list of tenants mapped to the user |
103+
| `RXT_TenantID` | Tenant ID for the project |
104+
| `RXT_orgPersonType` | RBAC association |
105+
93106
##### Craete the domain
94107

95108
``` shell

files/mapping.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@
1717
"domain": {
1818
"name": "rackspace_cloud_domain"
1919
},
20+
"description": "Project for DDI {3}",
21+
"metadata": [
22+
{
23+
"key": "ddi",
24+
"value": "{3}"
25+
}
26+
],
27+
"tags": [
28+
{
29+
"project_tag": "{3}"
30+
}
31+
],
2032
"roles": [
2133
{
2234
"name": "member"
@@ -26,6 +38,9 @@
2638
},
2739
{
2840
"name": "heat_stack_user"
41+
},
42+
{
43+
"name": "creator"
2944
}
3045
]
3146
}
@@ -42,6 +57,9 @@
4257
{
4358
"type": "RXT_TenantName"
4459
},
60+
{
61+
"type": "RXT_DomainID"
62+
},
4563
{
4664
"type": "RXT_orgPersonType",
4765
"any_one_of": [

keystone_rxt/rackspace.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def _merge_dict(self, base, new, extend=True):
222222
:param new: New dictionary to merge items from
223223
:type new: Dictionary
224224
:param extend: Boolean option to enable or disable extending
225-
iterable arrays.
225+
iterable arrays.
226226
:type extend: Boolean
227227
:returns: Dictionary
228228
"""
@@ -270,6 +270,110 @@ class RuleProcessorToHonorDomainOption(
270270
"""
271271

272272

273+
def _handle_projects_from_mapping(
274+
shadow_projects,
275+
idp_domain_id,
276+
existing_roles,
277+
user,
278+
assignment_api,
279+
resource_api,
280+
):
281+
for shadow_project in shadow_projects:
282+
mapped.configure_project_domain(
283+
shadow_project, idp_domain_id, resource_api
284+
)
285+
try:
286+
# Check and see if the project already exists and if it
287+
# does not, try to create it.
288+
project = resource_api.get_project_by_name(
289+
shadow_project["name"], shadow_project["domain"]["id"]
290+
)
291+
except exception.ProjectNotFound:
292+
LOG.info(
293+
"Project %(project_name)s does not exist. It will be "
294+
"automatically provisioning for user %(user_id)s.",
295+
{
296+
"project_name": shadow_project["name"],
297+
"user_id": user["id"],
298+
},
299+
)
300+
project_ref = {
301+
"id": mapped.uuid.uuid4().hex,
302+
"name": shadow_project["name"],
303+
"domain_id": shadow_project["domain"]["id"],
304+
}
305+
project = resource_api.create_project(
306+
project_ref["id"], project_ref
307+
)
308+
shadow_roles = shadow_project["roles"]
309+
for shadow_role in shadow_roles:
310+
assignment_api.create_grant(
311+
existing_roles[shadow_role["name"]]["id"],
312+
user_id=user["id"],
313+
project_id=project["id"],
314+
)
315+
# Run project update to ensure that the project has the correct tags
316+
# and description.
317+
update_needed = False
318+
for shadow_tag in shadow_project.get("tags", list()):
319+
shadow_tag = shadow_tag.get("project_tag")
320+
if shadow_tag and shadow_tag not in project["tags"]:
321+
project["tags"].append(shadow_tag)
322+
update_needed = True
323+
324+
description = shadow_project.get("description", "").strip()
325+
if description and project.get("description") != description:
326+
project["description"] = description
327+
update_needed = True
328+
329+
metadata = shadow_project.get("metadata", list())
330+
for item in metadata:
331+
if item['key'] not in project:
332+
project[item['key']] = item['value']
333+
update_needed = True
334+
335+
if update_needed:
336+
resource_api.update_project(
337+
project_id=project["id"], project=project
338+
)
339+
340+
341+
# NOTE(cloudnull): Adds tag and description support to the project mapping.
342+
mapped.handle_projects_from_mapping = _handle_projects_from_mapping
343+
344+
# NOTE(cloudnull): Ensures that the Rackspace plugin is permits the use of tags
345+
# and a description within PROJECTS_SCHEMA_2_0.
346+
mapped.utils.PROJECTS_SCHEMA_2_0["items"]["properties"]["tags"] = {
347+
"type": "array",
348+
"items": {
349+
"type": "object",
350+
"required": ["project_tag"],
351+
"properties": {
352+
"project_tag": {"type": "string"},
353+
},
354+
"additionalProperties": False,
355+
},
356+
}
357+
mapped.utils.PROJECTS_SCHEMA_2_0["items"]["properties"]["metadata"] = {
358+
"type": "array",
359+
"items": {
360+
"type": "object",
361+
"required": ["key", "value"],
362+
"properties": {
363+
"key": {"type": "string"},
364+
"value": {"type": "string"},
365+
},
366+
"additionalProperties": False,
367+
},
368+
}
369+
mapped.utils.PROJECTS_SCHEMA_2_0["items"]["properties"]["description"] = {
370+
"type": "string"
371+
}
372+
mapped.utils.IDP_ATTRIBUTE_MAPPING_SCHEMA_2_0["properties"]["rules"]["items"][
373+
"properties"
374+
]["local"]["items"]["properties"][
375+
"projects"
376+
] = mapped.utils.PROJECTS_SCHEMA_2_0
273377
# NOTE(cloudnull): This is to ensure that the RuleProcessor is used for the
274378
# 1.0 schema and the RuleProcessorToHonorDomainOption is
275379
# used for the 2.0 schema when running with the rxt auth

0 commit comments

Comments
 (0)