#!/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()