Add application_credential module
Create or delete a 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. The returned result also includes a usable cloud config which allows playbooks to easily run openstack tasks using the credential created by this module. Change-Id: I0ed86dc8785b0e9d10cc89cd9137a11d02d03945
This commit is contained in:
parent
032a5222c1
commit
94afde008b
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:
|
||||
- { role: address_scope, tags: address_scope }
|
||||
- { role: application_credential, tags: application_credential }
|
||||
- { role: auth, tags: auth }
|
||||
- { role: catalog_service, tags: catalog_service }
|
||||
- { 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()
|
Loading…
x
Reference in New Issue
Block a user