mirror of
https://opendev.org/openstack/ansible-collections-openstack.git
synced 2026-05-08 22:34:12 +00:00
Merge "Add application_credential module"
This commit is contained in:
9
ci/roles/application_credential/defaults/main.yml
Normal file
9
ci/roles/application_credential/defaults/main.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
expected_fields:
|
||||||
|
- description
|
||||||
|
- expires_at
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- project_id
|
||||||
|
- roles
|
||||||
|
- secret
|
||||||
|
- unrestricted
|
||||||
61
ci/roles/application_credential/tasks/main.yml
Normal file
61
ci/roles/application_credential/tasks/main.yml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Create application credentials
|
||||||
|
openstack.cloud.application_credential:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
state: present
|
||||||
|
name: ansible_creds
|
||||||
|
description: dummy description
|
||||||
|
register: appcred
|
||||||
|
|
||||||
|
- name: Assert return values of application_credential module
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- appcred is changed
|
||||||
|
# allow new fields to be introduced but prevent fields from being removed
|
||||||
|
- expected_fields|difference(appcred.application_credential.keys())|length == 0
|
||||||
|
|
||||||
|
- name: Create the application credential again
|
||||||
|
openstack.cloud.application_credential:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
state: present
|
||||||
|
name: ansible_creds
|
||||||
|
description: dummy description
|
||||||
|
register: appcred
|
||||||
|
|
||||||
|
- name: Assert return values of ansible_credential module
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
# credentials are immutable so creating twice will cause delete and create
|
||||||
|
- appcred is changed
|
||||||
|
# allow new fields to be introduced but prevent fields from being removed
|
||||||
|
- expected_fields|difference(appcred.application_credential.keys())|length == 0
|
||||||
|
|
||||||
|
- name: Update the application credential again
|
||||||
|
openstack.cloud.application_credential:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
state: present
|
||||||
|
name: ansible_creds
|
||||||
|
description: new description
|
||||||
|
register: appcred
|
||||||
|
|
||||||
|
- name: Assert application credential changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- appcred is changed
|
||||||
|
- appcred.application_credential.description == 'new description'
|
||||||
|
|
||||||
|
- name: Get list of all keypairs using application credential
|
||||||
|
openstack.cloud.keypair_info:
|
||||||
|
cloud: "{{ appcred.cloud }}"
|
||||||
|
|
||||||
|
- name: Delete application credential
|
||||||
|
openstack.cloud.application_credential:
|
||||||
|
cloud: "{{ cloud }}"
|
||||||
|
state: absent
|
||||||
|
name: ansible_creds
|
||||||
|
register: appcred
|
||||||
|
|
||||||
|
- name: Assert application credential changed
|
||||||
|
assert:
|
||||||
|
that: appcred is changed
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
roles:
|
roles:
|
||||||
- { role: address_scope, tags: address_scope }
|
- { role: address_scope, tags: address_scope }
|
||||||
|
- { role: application_credential, tags: application_credential }
|
||||||
- { role: auth, tags: auth }
|
- { role: auth, tags: auth }
|
||||||
- { role: catalog_service, tags: catalog_service }
|
- { role: catalog_service, tags: catalog_service }
|
||||||
- { role: coe_cluster, tags: coe_cluster }
|
- { role: coe_cluster, tags: coe_cluster }
|
||||||
|
|||||||
332
plugins/modules/application_credential.py
Normal file
332
plugins/modules/application_credential.py
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 Red Hat, Inc.
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
DOCUMENTATION = r"""
|
||||||
|
---
|
||||||
|
module: application_credential
|
||||||
|
short_description: Manage OpenStack Identity (Keystone) application credentials
|
||||||
|
author: OpenStack Ansible SIG
|
||||||
|
description:
|
||||||
|
- Create or delete an OpenStack Identity (Keystone) application credential.
|
||||||
|
- When the secret parameter is not set a secret will be generated and returned
|
||||||
|
- in the response. Existing credentials cannot be modified so running this module
|
||||||
|
- against an existing credential will result in it being deleted and recreated.
|
||||||
|
- This needs to be taken into account when the secret is generated, as the secret
|
||||||
|
- will change on each run of the module.
|
||||||
|
options:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the application credential.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
description:
|
||||||
|
- Application credential description.
|
||||||
|
type: str
|
||||||
|
secret:
|
||||||
|
description:
|
||||||
|
- Secret to use for authentication
|
||||||
|
- (if not provided, one will be generated).
|
||||||
|
type: str
|
||||||
|
roles:
|
||||||
|
description:
|
||||||
|
- Roles to authorize (name or ID).
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description: Name of role
|
||||||
|
type: str
|
||||||
|
id:
|
||||||
|
description: ID of role
|
||||||
|
type: str
|
||||||
|
domain_id:
|
||||||
|
description: Domain ID
|
||||||
|
type: str
|
||||||
|
expires_at:
|
||||||
|
description:
|
||||||
|
- Sets an expiration date for the application credential,
|
||||||
|
- format of YYYY-mm-ddTHH:MM:SS
|
||||||
|
- (if not provided, the application credential will not expire).
|
||||||
|
type: str
|
||||||
|
unrestricted:
|
||||||
|
description:
|
||||||
|
- Enable application credential to create and delete other application
|
||||||
|
- credentials and trusts (this is potentially dangerous behavior and is
|
||||||
|
- disabled by default).
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
access_rules:
|
||||||
|
description:
|
||||||
|
- List of access rules, each containing a request method, path, and service.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
suboptions:
|
||||||
|
service:
|
||||||
|
description: Name of service endpoint
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
path:
|
||||||
|
description: Path portion of access URL
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
method:
|
||||||
|
description: HTTP method
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Should the resource be present or absent.
|
||||||
|
- Application credentials are immutable so running with an existing present
|
||||||
|
- credential will result in the credential being deleted and recreated.
|
||||||
|
choices: [present, absent]
|
||||||
|
default: present
|
||||||
|
type: str
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- openstack.cloud.openstack
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
- name: Create application credential
|
||||||
|
openstack.cloud.application_credential:
|
||||||
|
cloud: mycloud
|
||||||
|
description: demodescription
|
||||||
|
name: democreds
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create application credential with expiration, access rules and roles
|
||||||
|
openstack.cloud.application_credential:
|
||||||
|
cloud: mycloud
|
||||||
|
description: demodescription
|
||||||
|
name: democreds
|
||||||
|
access_rules:
|
||||||
|
- service: "compute"
|
||||||
|
path: "/v2.1/servers"
|
||||||
|
method: "GET"
|
||||||
|
expires_at: "2024-02-29T09:29:59"
|
||||||
|
roles:
|
||||||
|
- name: Member
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Delete application credential
|
||||||
|
openstack.cloud.application_credential:
|
||||||
|
cloud: mycloud
|
||||||
|
name: democreds
|
||||||
|
state: absent
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = r"""
|
||||||
|
application_credential:
|
||||||
|
description: Dictionary describing the project.
|
||||||
|
returned: On success when I(state) is C(present).
|
||||||
|
type: dict
|
||||||
|
contains:
|
||||||
|
id:
|
||||||
|
description: The ID of the application credential.
|
||||||
|
type: str
|
||||||
|
sample: "2e73d1b4f0cb473f920bd54dfce3c26d"
|
||||||
|
name:
|
||||||
|
description: The name of the application credential.
|
||||||
|
type: str
|
||||||
|
sample: "appcreds"
|
||||||
|
secret:
|
||||||
|
description: Secret to use for authentication
|
||||||
|
(if not provided, returns the generated value).
|
||||||
|
type: str
|
||||||
|
sample: "JxE7LajLY75NZgDH1hfu0N_6xS9hQ-Af40W3"
|
||||||
|
description:
|
||||||
|
description: A description of the application credential's purpose.
|
||||||
|
type: str
|
||||||
|
sample: "App credential"
|
||||||
|
expires_at:
|
||||||
|
description: The expiration time of the application credential in UTC,
|
||||||
|
if one was specified.
|
||||||
|
type: str
|
||||||
|
sample: "2024-02-29T09:29:59.000000"
|
||||||
|
project_id:
|
||||||
|
description: The ID of the project the application credential was created
|
||||||
|
for and that authentication requests using this application
|
||||||
|
credential will be scoped to.
|
||||||
|
type: str
|
||||||
|
sample: "4b633c451ac74233be3721a3635275e5"
|
||||||
|
roles:
|
||||||
|
description: A list of one or more roles that this application credential
|
||||||
|
has associated with its project. A token using this application
|
||||||
|
credential will have these same roles.
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
sample: [{"name": "Member"}]
|
||||||
|
access_rules:
|
||||||
|
description: A list of access_rules objects
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
sample:
|
||||||
|
- id: "edecb6c791d541a3b458199858470d20"
|
||||||
|
service: "compute"
|
||||||
|
path: "/v2.1/servers"
|
||||||
|
method: "GET"
|
||||||
|
unrestricted:
|
||||||
|
description: A flag indicating whether the application credential may be
|
||||||
|
used for creation or destruction of other application credentials
|
||||||
|
or trusts.
|
||||||
|
type: bool
|
||||||
|
cloud:
|
||||||
|
description: The current cloud config with the username and password replaced
|
||||||
|
with the name and secret of the application credential. This
|
||||||
|
can be passed to the cloud parameter of other tasks, or written
|
||||||
|
to an openstack cloud config file.
|
||||||
|
returned: On success when I(state) is C(present).
|
||||||
|
type: dict
|
||||||
|
sample:
|
||||||
|
auth_type: "v3applicationcredential"
|
||||||
|
auth:
|
||||||
|
auth_url: "https://192.0.2.1/identity"
|
||||||
|
application_credential_secret: "JxE7LajLY75NZgDH1hfu0N_6xS9hQ-Af40W3"
|
||||||
|
application_credential_id: "3e73d1b4f0cb473f920bd54dfce3c26d"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
|
||||||
|
OpenStackModule,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import openstack.config
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IdentityApplicationCredentialModule(OpenStackModule):
|
||||||
|
argument_spec = dict(
|
||||||
|
name=dict(required=True),
|
||||||
|
description=dict(),
|
||||||
|
secret=dict(no_log=True),
|
||||||
|
roles=dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=dict(name=dict(), id=dict(), domain_id=dict()),
|
||||||
|
),
|
||||||
|
expires_at=dict(),
|
||||||
|
unrestricted=dict(type="bool", default=False),
|
||||||
|
access_rules=dict(
|
||||||
|
type="list",
|
||||||
|
elements="dict",
|
||||||
|
options=dict(
|
||||||
|
service=dict(required=True),
|
||||||
|
path=dict(required=True),
|
||||||
|
method=dict(required=True),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
state=dict(default="present", choices=["absent", "present"]),
|
||||||
|
)
|
||||||
|
module_kwargs = dict()
|
||||||
|
cloud = None
|
||||||
|
|
||||||
|
def openstack_cloud_from_module(self):
|
||||||
|
# Fetch cloud param before it is popped
|
||||||
|
self.cloud = self.params["cloud"]
|
||||||
|
return OpenStackModule.openstack_cloud_from_module(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
state = self.params["state"]
|
||||||
|
|
||||||
|
creds = self._find()
|
||||||
|
|
||||||
|
if state == "present" and not creds:
|
||||||
|
# Create creds
|
||||||
|
creds = self._create().to_dict(computed=False)
|
||||||
|
cloud_config = self._get_cloud_config(creds)
|
||||||
|
self.exit_json(
|
||||||
|
changed=True, application_credential=creds, cloud=cloud_config
|
||||||
|
)
|
||||||
|
|
||||||
|
elif state == "present" and creds:
|
||||||
|
# Recreate immutable creds
|
||||||
|
self._delete(creds)
|
||||||
|
creds = self._create().to_dict(computed=False)
|
||||||
|
cloud_config = self._get_cloud_config(creds)
|
||||||
|
self.exit_json(
|
||||||
|
changed=True, application_credential=creds, cloud=cloud_config
|
||||||
|
)
|
||||||
|
|
||||||
|
elif state == "absent" and creds:
|
||||||
|
# Delete creds
|
||||||
|
self._delete(creds)
|
||||||
|
self.exit_json(changed=True)
|
||||||
|
|
||||||
|
elif state == "absent" and not creds:
|
||||||
|
# Do nothing
|
||||||
|
self.exit_json(changed=False)
|
||||||
|
|
||||||
|
def _get_user_id(self):
|
||||||
|
return self.conn.session.get_user_id()
|
||||||
|
|
||||||
|
def _create(self):
|
||||||
|
kwargs = dict(
|
||||||
|
(k, self.params[k])
|
||||||
|
for k in [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"secret",
|
||||||
|
"expires_at",
|
||||||
|
"unrestricted",
|
||||||
|
"access_rules",
|
||||||
|
]
|
||||||
|
if self.params[k] is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
roles = self.params["roles"]
|
||||||
|
if roles:
|
||||||
|
kwroles = []
|
||||||
|
for role in roles:
|
||||||
|
kwroles.append(
|
||||||
|
dict(
|
||||||
|
(k, role[k])
|
||||||
|
for k in ["name", "id", "domain_id"]
|
||||||
|
if role[k] is not None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
kwargs["roles"] = kwroles
|
||||||
|
|
||||||
|
kwargs["user"] = self._get_user_id()
|
||||||
|
creds = self.conn.identity.create_application_credential(**kwargs)
|
||||||
|
return creds
|
||||||
|
|
||||||
|
def _get_cloud_config(self, creds):
|
||||||
|
cloud_region = openstack.config.OpenStackConfig().get_one(self.cloud)
|
||||||
|
|
||||||
|
conf = cloud_region.config
|
||||||
|
cloud_config = copy.deepcopy(conf)
|
||||||
|
cloud_config["auth_type"] = "v3applicationcredential"
|
||||||
|
cloud_config["auth"] = {
|
||||||
|
"application_credential_id": creds["id"],
|
||||||
|
"application_credential_secret": creds["secret"],
|
||||||
|
"auth_url": conf["auth"]["auth_url"],
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloud_config
|
||||||
|
|
||||||
|
def _delete(self, creds):
|
||||||
|
user = self._get_user_id()
|
||||||
|
self.conn.identity.delete_application_credential(user, creds.id)
|
||||||
|
|
||||||
|
def _find(self):
|
||||||
|
name = self.params["name"]
|
||||||
|
user = self._get_user_id()
|
||||||
|
return self.conn.identity.find_application_credential(
|
||||||
|
user=user, name_or_id=name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = IdentityApplicationCredentialModule()
|
||||||
|
module()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user