diff --git a/.zuul.yaml b/.zuul.yaml index 8ef9fb36..a9262777 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -60,65 +60,6 @@ description: | Run openstack collections functional tests against a master devstack using master of openstacksdk with latest ansible release - vars: - # TODO: Remove tox_extra_args once all ci roles pass - tox_extra_args: &skip_broken_ci_roles >- - address_scope - auth - catalog_service - compute_flavor - compute_flavor_access - config - dns - dns_zone_info - endpoint - federation_mapping - floating_ip - host_aggregate - identity_domain_info - identity_group - identity_group_info - identity_user - identity_user_info - identity_role - identity_role_info - image - image_info - keypair - keystone_domain - keystone_federation_protocol - keystone_idp - logging - loadbalancer - network - nova_services - object - object_container - port - project - project_info - quota - recordset - role_assignment - router - security_group - security_group_rule - server - server_group - server_metadata - server_volume - stack - subnet - subnet_pool - user - user_group - user_role - volume - volume_backup - volume_snapshot - volume_type_access - # failing tags - # neutron_rbac - job: name: ansible-collections-openstack-functional-devstack-octavia-base @@ -166,9 +107,6 @@ Run openstack collections functional tests against a master devstack with Octavia plugin enabled, using latest releases of openstacksdk and latest ansible release. Run it only on Load Balancer changes. - vars: - # TODO: Remove tox_extra_args once all ci roles pass - tox_extra_args: *skip_broken_ci_roles - job: name: ansible-collections-openstack-functional-devstack-releases @@ -179,8 +117,6 @@ using latest releases of openstacksdk and latest ansible release vars: tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-1.x.x.txt' - # TODO: Remove tox_extra_args once all ci roles pass - tox_extra_args: *skip_broken_ci_roles tox_install_siblings: false # Job with Ansible 2.9 for checking backward compatibility @@ -196,8 +132,6 @@ override-checkout: stable-2.9 vars: tox_envlist: ansible-2.9 - # TODO: Remove tox_extra_args once all ci roles pass - tox_extra_args: *skip_broken_ci_roles - job: name: ansible-collections-openstack-functional-devstack-ansible-2.11 @@ -211,8 +145,6 @@ override-checkout: stable-2.11 vars: tox_envlist: ansible-2.11 - # TODO: Remove tox_extra_args once all ci roles pass - tox_extra_args: *skip_broken_ci_roles - job: name: ansible-collections-openstack-functional-devstack-ansible-2.12 @@ -226,8 +158,6 @@ override-checkout: stable-2.12 vars: tox_envlist: ansible-2.12 - # TODO: Remove tox_extra_args once all ci roles pass - tox_extra_args: *skip_broken_ci_roles - job: name: ansible-collections-openstack-functional-devstack-ansible-devel @@ -242,9 +172,6 @@ required-projects: - name: github.com/ansible/ansible override-checkout: devel - vars: - # TODO: Remove tox_extra_args once all ci roles pass - tox_extra_args: *skip_broken_ci_roles # Linters - job: diff --git a/ci/roles/neutron_rbac_policy/defaults/main.yml b/ci/roles/neutron_rbac_policy/defaults/main.yml new file mode 100644 index 00000000..3df0dce1 --- /dev/null +++ b/ci/roles/neutron_rbac_policy/defaults/main.yml @@ -0,0 +1,9 @@ +expected_fields: + - action + - id + - name + - object_id + - object_type + - project_id + - target_project_id + - tenant_id diff --git a/ci/roles/neutron_rbac/tasks/main.yml b/ci/roles/neutron_rbac_policy/tasks/main.yml similarity index 54% rename from ci/roles/neutron_rbac/tasks/main.yml rename to ci/roles/neutron_rbac_policy/tasks/main.yml index ba2902b1..840095c3 100644 --- a/ci/roles/neutron_rbac/tasks/main.yml +++ b/ci/roles/neutron_rbac_policy/tasks/main.yml @@ -1,27 +1,18 @@ --- -# General run of tests -# - Prepare projects/network objects -# - Create rbac object -# - Get rbac object info -# - Verify RBAC object match -# - Delete rbac object -# - Get rbac object info -# - Verify RBAC object deleted - - name: Create source project openstack.cloud.project: cloud: "{{ cloud }}" state: present - name: source_project + name: ansible_source_project description: Source project for network RBAC test domain_id: default enabled: True register: source_project -- name: Create network - generic +- name: Create network openstack.cloud.network: cloud: "{{ cloud }}" - name: "{{ network_name }}" + name: "ansible_network" state: present project: "{{ source_project.project.id }}" shared: false @@ -32,7 +23,7 @@ openstack.cloud.project: cloud: "{{ cloud }}" state: present - name: ansible_project + name: ansible_target_project description: Target project for network RBAC test domain_id: default enabled: True @@ -48,38 +39,62 @@ project_id: "{{ source_project.project.id }}" register: rbac_policy +- name: Assert return values of neutron_rbac_policy module + assert: + that: + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(rbac_policy.rbac_policy.keys())|length == 0 + - name: Get all rbac policies for {{ source_project.project.name }} - after creation openstack.cloud.neutron_rbac_policies_info: cloud: "{{ cloud }}" - project_id: "{{ source_project.project.id }}" + project: "{{ source_project.project.id }}" register: rbac_policies -- name: Capture all existing policy IDs - set_fact: - rbac_policy_ids: "{{ rbac_policies.policies | map(attribute='id') | list }}" +- name: Assert return values of neutron_rbac_policy_info module + assert: + that: + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(rbac_policies.rbac_policies[0].keys())|length == 0 - name: Verify policy exists - after creation assert: that: - - rbac_policy.policy.id in rbac_policy_ids + - rbac_policy.rbac_policy.id in + ( rbac_policies.rbac_policies | map(attribute='id') | list ) - name: Delete RBAC policy openstack.cloud.neutron_rbac_policy: cloud: "{{ cloud }}" - policy_id: "{{ rbac_policy.policy.id }}" + id: "{{ rbac_policy.rbac_policy.id }}" state: absent - name: Get all rbac policies for {{ source_project.project.name }} - after deletion openstack.cloud.neutron_rbac_policies_info: cloud: "{{ cloud }}" - project_id: "{{ source_project.project.id }}" + project: "{{ source_project.project.id }}" register: rbac_policies_remaining -- name: Capture all remaining policy IDs - set_fact: - remaining_rbac_policy_ids: "{{ rbac_policies_remaining.policies | map(attribute='id') | list }}" - - name: Verify policy does not exist - after deletion assert: that: - - not rbac_policy.policy.id in remaining_rbac_policy_ids + - rbac_policy.rbac_policy.id not in + ( rbac_policies_remaining.rbac_policies | map(attribute='id') | list ) + +- name: Delete target project + openstack.cloud.project: + cloud: "{{ cloud }}" + state: absent + name: ansible_target_project + +- name: Delete network + openstack.cloud.network: + cloud: "{{ cloud }}" + name: "ansible_network" + state: absent + +- name: Delete source project + openstack.cloud.project: + cloud: "{{ cloud }}" + state: absent + name: ansible_source_project diff --git a/ci/run-collection.yml b/ci/run-collection.yml index 11c8ea88..9bc11383 100644 --- a/ci/run-collection.yml +++ b/ci/run-collection.yml @@ -40,10 +40,7 @@ when: sdk_version is version(0.44, '>=') - { role: logging, tags: logging } - { role: network, tags: network } - - role: neutron_rbac - tags: - - rbac - - neutron_rbac + - { role: neutron_rbac_policy, tags: neutron_rbac_policy } - role: nova_services tags: nova_services when: sdk_version is version(0.44, '>=') diff --git a/plugins/modules/neutron_rbac_policies_info.py b/plugins/modules/neutron_rbac_policies_info.py index 99ec7ac6..a70714d3 100644 --- a/plugins/modules/neutron_rbac_policies_info.py +++ b/plugins/modules/neutron_rbac_policies_info.py @@ -5,61 +5,57 @@ # (c) 2021, Ashraf Hasson # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - DOCUMENTATION = r''' --- module: neutron_rbac_policies_info -short_description: Fetch Neutron policies. +short_description: Fetch Neutron RBAC policies. author: OpenStack Ansible SIG description: - - Get RBAC policies against a network, security group or a QoS Policy for one or more projects. - - If a C(policy_id) was not provided, this module will attempt to fetch all available policies. - - Accepts same arguments as OpenStackSDK network proxy C(find_rbac_policy) and C(rbac_policies) functions which are ultimately passed over to C(RBACPolicy) - - All parameters passed in to this module act as a filter for when no C(policy_id) was provided, otherwise they're ignored. - - Returns None if no matching policy was found as opposed to failing. - + - Fetch RBAC policies against a network, security group or a QoS Policy for + one or more projects. options: - policy_id: + action: description: - - The RBAC policy ID - - If provided, all other filters are ignored + - Action for the RBAC policy. + - Can be either of the following options C(access_as_shared) or + C(access_as_external). + - Logically AND'ed with other filters. + choices: ['access_as_shared', 'access_as_external'] type: str object_id: description: - - The object ID (the subject of the policy) to which the RBAC rules applies - - This would be the ID of a network, security group or a qos policy - - Mutually exclusive with the C(object_type) + - The object ID (the subject of the policy) to which the RBAC rules + applies. + - This is an ID of a network, security group or a qos policy. + - Mutually exclusive with the C(object_type). type: str object_type: description: - - Can be one of the following object types C(network), C(security_group) or C(qos_policy) - - Mutually exclusive with the C(object_id) + - Type of the object that this RBAC policy affects. + - Can be one of the following object types C(network), C(security_group) + or C(qos_policy). + - Mutually exclusive with the C(object_id). choices: ['network', 'security_group', 'qos_policy'] type: str - target_project_id: + policy_id: description: - - Filters the RBAC rules based on the target project id - - Logically AND'ed with other filters - - Mutually exclusive with C(project_id) - type: str - project_id: - description: - - Filters the RBAC rules based on the project id to which the object belongs to - - Logically AND'ed with other filters - - Mutually exclusive with C(target_project_id) + - The RBAC policy ID. + - If C(policy_id) is not provided, all available policies will be + fetched. + - If C(policy_id) provided, all other filters are ignored. type: str project: description: - - Filters the RBAC rules based on the project name - - Logically AND'ed with other filters + - ID or name of the project to which C(object_id) belongs to. + - Filters the RBAC rules based on the project name. + - Logically AND'ed with other filters. type: str - action: + aliases: ['project_id'] + target_project_id: description: - - Can be either of the following options C(access_as_shared) | C(access_as_external) - - Logically AND'ed with other filters - choices: ['access_as_shared', 'access_as_external'] + - The ID of the project this RBAC will be enforced. + - Filters the RBAC rules based on the target project id. + - Logically AND'ed with other filters. type: str extends_documentation_fragment: @@ -67,40 +63,18 @@ extends_documentation_fragment: ''' EXAMPLES = r''' -# Gather all rbac policies for a project -- name: Get all rbac policies for {{ project }} +- name: Get all rbac policies for a project openstack.cloud.neutron_rbac_policies_info: - project_id: "{{ project.id }}" + project: one_project ''' RETURN = r''' -# return value can either be plural or signular depending on what was passed in as parameters -policies: - description: - - List of rbac policies, this could also be returned as a singular element, i.e., 'policy' - type: complex +rbac_policies: + description: List of Neutron RBAC policies. + type: list + elements: dict returned: always contains: - object_id: - description: - - The UUID of the object to which the RBAC rules apply - type: str - sample: "7422172b-2961-475c-ac68-bd0f2a9960ad" - target_project_id: - description: - - The UUID of the target project - type: str - sample: "c201a689c016435c8037977166f77368" - project_id: - description: - - The UUID of the project to which access is granted - type: str - sample: "84b8774d595b41e89f3dfaa1fd76932c" - object_type: - description: - - The object type to which the RBACs apply - type: str - sample: "network" action: description: - The access model specified by the RBAC rules @@ -116,120 +90,96 @@ policies: - The name of the RBAC rule; usually null type: str sample: null - location: - description: - - A dictionary of the project details to which access is granted - type: dict - sample: >- - { - "cloud": "devstack", - "region_name": "", - "zone": null, - "project": { - "id": "84b8774d595b41e89f3dfaa1fd76932c", - "name": null, - "domain_id": null, - "domain_name": null - } - } + object_id: + description: + - The UUID of the object to which the RBAC rules apply + type: str + sample: "7422172b-2961-475c-ac68-bd0f2a9960ad" + object_type: + description: + - The object type to which the RBACs apply + type: str + sample: "network" + project_id: + description: + - The UUID of the project to which access is granted + type: str + sample: "84b8774d595b41e89f3dfaa1fd76932c" + target_project_id: + description: + - The UUID of the target project + type: str + sample: "c201a689c016435c8037977166f77368" + tenant_id: + description: + - The UUID of the project to which access is granted. Deprecated. + type: str + sample: "84b8774d595b41e89f3dfaa1fd76932c" +policies: + description: Same as C(rbac_policies), kept for backward compatibility. + returned: always + type: list + elements: dict ''' -import re from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule -class NeutronRbacPoliciesInfo(OpenStackModule): +class NeutronRBACPoliciesInfo(OpenStackModule): argument_spec = dict( + action=dict(choices=['access_as_external', 'access_as_shared']), + object_id=dict(), + object_type=dict(choices=['security_group', 'qos_policy', 'network']), policy_id=dict(), - object_id=dict(), # ID of the object that this RBAC policy affects. - object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects. - target_project_id=dict(), # The ID of the project this RBAC will be enforced. - project_id=dict(), # The owner project ID. - project=dict(), - action=dict(choices=['access_as_external', 'access_as_shared']), # Action for the RBAC policy. + project=dict(aliases=['project_id']), + target_project_id=dict(), ) module_kwargs = dict( + mutually_exclusive=[ + ('object_id', 'object_type'), + ], supports_check_mode=True, ) - def _filter_policies_by(self, policies, key, value): - filtered = [] - regexp = re.compile(r"location\.project\.([A-Za-z]+)") - if regexp.match(key): - attribute = key.split('.')[-1] - for p in policies: - if p['location']['project'][attribute] == value: - filtered.append(p) - else: - for p in policies: - if getattr(p, key) == value: - filtered.append(p) - - return filtered - - def _get_rbac_policies(self): - object_type = self.params.get('object_type') - project_id = self.params.get('project_id') - action = self.params.get('action') - - search_attributes = {} - if object_type is not None: - search_attributes['object_type'] = object_type - if project_id is not None: - search_attributes['project_id'] = project_id - if action is not None: - search_attributes['action'] = action - - try: - policies = [] - generator = self.conn.network.rbac_policies(**search_attributes) - for p in generator: - policies.append(p) - except self.sdk.exceptions.OpenStackCloudException as ex: - self.fail_json(msg='Failed to get RBAC policies: {0}'.format(str(ex))) - - return policies - def run(self): - policy_id = self.params.get('policy_id') - object_id = self.params.get('object_id') - object_type = self.params.get('object_type') - project_id = self.params.get('project_id') - project = self.params.get('project') - target_project_id = self.params.get('target_project_id') + project_name_or_id = self.params['project'] + project = None + if project_name_or_id is not None: + project = self.conn.identity.find_project(project_name_or_id) + if not project: + self.exit_json(changed=False, rbac_policies=[], policies=[]) - if self.ansible.check_mode: - self.exit_json(changed=False) - - if policy_id is not None: - try: - policy = self.conn.network.get_rbac_policy(policy_id) - self.exit_json(changed=False, policy=policy) - except self.sdk.exceptions.ResourceNotFound: - self.exit_json(changed=False, policy=None) - except self.sdk.exceptions.OpenStackCloudException as ex: - self.fail_json(msg='Failed to get RBAC policy: {0}'.format(str(ex))) + policy_id = self.params['policy_id'] + if policy_id: + policy = self.conn.network.find_rbac_policy(policy_id) + policies = [policy] if policy else [] else: - if object_id is not None and object_type is not None: - self.fail_json(msg='object_id and object_type are mutually exclusive, please specify one of the two.') - if project_id is not None and target_project_id is not None: - self.fail_json(msg='project_id and target_project_id are mutually exclusive, please specify one of the two.') + kwargs = dict((k, self.params[k]) + for k in ['action', 'object_type'] + if self.params[k] is not None) - filtered_policies = self._get_rbac_policies() + if project: + kwargs['project_id'] = project.id - if project is not None: - filtered_policies = self._filter_policies_by(filtered_policies, 'location.project.name', project) - if object_id is not None: - filtered_policies = self._filter_policies_by(filtered_policies, 'object_id', object_id) - if target_project_id is not None: - filtered_policies = self._filter_policies_by(filtered_policies, 'target_project_id', target_project_id) + policies = list(self.conn.network.rbac_policies(**kwargs)) - self.exit_json(policies=filtered_policies, changed=False) + for k in ['object_id', 'target_project_id']: + if self.params[k] is not None: + policies = [p for p in policies if p[k] == self.params[k]] + + if project: + policies = [p for p in policies + if p['location']['project']['id'] == project.id] + + policies = [p.to_dict(computed=False) for p in policies] + self.exit_json(changed=False, + rbac_policies=policies, + policies=policies) def main(): - module = NeutronRbacPoliciesInfo() + module = NeutronRBACPoliciesInfo() module() diff --git a/plugins/modules/neutron_rbac_policy.py b/plugins/modules/neutron_rbac_policy.py index 94a5fc64..c0e30523 100644 --- a/plugins/modules/neutron_rbac_policy.py +++ b/plugins/modules/neutron_rbac_policy.py @@ -8,50 +8,64 @@ DOCUMENTATION = r''' --- module: neutron_rbac_policy -short_description: Create or delete a Neutron policy to apply a RBAC rule against an object. +short_description: Create or delete a Neutron RBAC policy. author: OpenStack Ansible SIG description: - - Create a policy to apply a RBAC rule against a network, security group or a QoS Policy or update/delete an existing policy. - - If a C(policy_id) was provided but not found, this module will attempt to create a new policy rather than error out when updating an existing rule. - - Accepts same arguments as OpenStackSDK network proxy C(find_rbac_policy) and C(rbac_policies) functions which are ultimately passed over to C(RBACPolicy) + - Create, update or delete a policy to apply a RBAC rule against a network, + security group or QoS Policy. + options: - policy_id: + action: description: - - The RBAC policy ID - - Required when deleting or updating an existing RBAC policy rule, ignored otherwise + - Action for the RBAC policy. + - Can be either of the following options C(access_as_shared) or + C(access_as_external). + - Cannot be changed when updating an existing policy. + - Required when creating a RBAC policy rule, ignored when deleting a + policy. + choices: ['access_as_shared', 'access_as_external'] type: str + id: + description: + - The RBAC policy ID. + - Required when deleting or updating an existing RBAC policy rule, + ignored otherwise. + - If a I(id) was provided but a policy with this ID cannot be found, + an error will be raised. + type: str + aliases: ['policy_id'] object_id: description: - - The object ID (the subject of the policy) to which the RBAC rule applies - - Cannot be changed when updating an existing policy - - Required when creating a RBAC policy rule, ignored when deleting a policy + - The object ID (the subject of the policy) to which the RBAC rule + applies. + - Cannot be changed when updating an existing policy. + - Required when creating a RBAC policy rule, ignored when deleting a + policy. type: str object_type: description: - - Can be one of the following object types C(network), C(security_group) or C(qos_policy) - - Cannot be changed when updating an existing policy - - Required when creating a RBAC policy rule, ignored when deleting a policy + - Type of the object that this RBAC policy affects. + - Can be one of the following object types C(network), C(security_group) + or C(qos_policy). + - Cannot be changed when updating an existing policy. + - Required when creating a RBAC policy rule, ignored when deleting a + policy. choices: ['network', 'security_group', 'qos_policy'] type: str - target_project_id: - description: - - The project to which access to be allowed or revoked/disallowed - - Can be specified/changed when updating an existing policy - - Required when creating or updating a RBAC policy rule, ignored when deleting a policy - type: str project_id: description: - - The project to which the object_id belongs - - Cannot be changed when updating an existing policy - - Required when creating a RBAC policy rule, ignored when deleting a policy + - The ID of the project to which C(object_id) belongs to. + - Cannot be changed when updating an existing policy. + - Required when creating a RBAC policy rule, ignored when deleting a + policy. type: str - action: + target_project_id: description: - - Can be either of the following options C(access_as_shared) | C(access_as_external) - - Cannot be changed when updating an existing policy - - Required when creating a RBAC policy rule, ignored when deleting a policy - choices: ['access_as_shared', 'access_as_external'] + - The ID of the project to which access to be allowed or revoked aka + disallowed. + - Required when creating or updating a RBAC policy rule, ignored when + deleting a policy. type: str state: description: @@ -65,54 +79,25 @@ extends_documentation_fragment: ''' EXAMPLES = r''' -# Ensure network RBAC policy exists -- name: Create a new network RBAC policy +- name: Create or update RBAC policy neutron_rbac_policy: object_id: '7422172b-2961-475c-ac68-bd0f2a9960ad' object_type: 'network' - target_project_id: 'a12f9ce1de0645e0a0b01c2e679f69ec' project_id: '84b8774d595b41e89f3dfaa1fd76932d' + target_project_id: 'a12f9ce1de0645e0a0b01c2e679f69ec' -# Update network RBAC policy -- name: Update an existing network RBAC policy - neutron_rbac_policy: - policy_id: 'f625242a-6a73-47ac-8d1f-91440b2c617f' - target_project_id: '163c89e065a94e069064e551e15daf0e' - -# Delete an existing RBAC policy - name: Delete RBAC policy openstack.cloud.openstack.neutron_rbac_policy: - policy_id: 'f625242a-6a73-47ac-8d1f-91440b2c617f' + id: 'f625242a-6a73-47ac-8d1f-91440b2c617f' state: absent ''' RETURN = r''' -policy: - description: - - A hash representing the policy - type: complex +rbac_policy: + description: A dictionary describing the RBAC policy. returned: always + type: dict contains: - object_id: - description: - - The UUID of the object to which the RBAC rules apply - type: str - sample: "7422172b-2961-475c-ac68-bd0f2a9960ad" - target_project_id: - description: - - The UUID of the target project - type: str - sample: "c201a689c016435c8037977166f77368" - project_id: - description: - - The UUID of the project to which access is granted - type: str - sample: "84b8774d595b41e89f3dfaa1fd76932c" - object_type: - description: - - The object type to which the RBACs apply - type: str - sample: "network" action: description: - The access model specified by the RBAC rules @@ -128,180 +113,182 @@ policy: - The name of the RBAC rule; usually null type: str sample: null - location: - description: - - A dictionary of the project details to which access is granted - type: dict - sample: >- - { - "cloud": "devstack", - "region_name": "", - "zone": null, - "project": { - "id": "84b8774d595b41e89f3dfaa1fd76932c", - "name": null, - "domain_id": null, - "domain_name": null - } - } + object_id: + description: + - The UUID of the object to which the RBAC rules apply + type: str + sample: "7422172b-2961-475c-ac68-bd0f2a9960ad" + object_type: + description: + - The object type to which the RBACs apply + type: str + sample: "network" + project_id: + description: + - The UUID of the project to which access is granted + type: str + sample: "84b8774d595b41e89f3dfaa1fd76932c" + target_project_id: + description: + - The UUID of the target project + type: str + sample: "c201a689c016435c8037977166f77368" + tenant_id: + description: + - The UUID of the project to which access is granted. Deprecated. + type: str + sample: "84b8774d595b41e89f3dfaa1fd76932c" +policy: + description: Same as C(rbac_policy), kept for backward compatibility. + returned: always + type: dict ''' from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule -class NeutronRbacPolicy(OpenStackModule): +class NeutronRBACPolicy(OpenStackModule): argument_spec = dict( - policy_id=dict(), - object_id=dict(), # ID of the object that this RBAC policy affects. - object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects. - target_project_id=dict(), # The ID of the project this RBAC will be enforced. - project_id=dict(), # The owner project ID. - action=dict(choices=['access_as_external', 'access_as_shared']), # Action for the RBAC policy. - state=dict(default='present', choices=['absent', 'present']) + action=dict(choices=['access_as_external', 'access_as_shared']), + id=dict(aliases=['policy_id']), + object_id=dict(), + object_type=dict(choices=['security_group', 'qos_policy', 'network']), + project_id=dict(), + state=dict(default='present', choices=['absent', 'present']), + target_project_id=dict(), ) module_kwargs = dict( + required_if=[ + ('state', 'present', ('target_project_id',)), + ('state', 'absent', ('id',)), + ], supports_check_mode=True, ) - def _delete_rbac_policy(self, policy): - """ - Delete an existing RBAC policy - returns: the "Changed" state - """ - - if policy is None: - self.fail_json(msg='Must specify policy_id for delete') - - try: - self.conn.network.delete_rbac_policy(policy.id) - except self.sdk.exceptions.OpenStackCloudException as ex: - self.fail_json(msg='Failed to delete RBAC policy: {0}'.format(str(ex))) - - return True - - def _create_rbac_policy(self): - """ - Creates a new RBAC policy - returns: the "Changed" state of the RBAC policy - """ - - object_id = self.params.get('object_id') - object_type = self.params.get('object_type') - target_project_id = self.params.get('target_project_id') - project_id = self.params.get('project_id') - action = self.params.get('action') - - attributes = { - 'object_id': object_id, - 'object_type': object_type, - 'target_project_id': target_project_id, - 'project_id': project_id, - 'action': action - } - - if not all(attributes.values()): - self.fail_json(msg='Missing one or more required parameter for creating a RBAC policy') - - try: - search_attributes = dict(attributes) - del search_attributes['object_id'] - del search_attributes['target_project_id'] - policies = self.conn.network.rbac_policies(**search_attributes) - for p in policies: - if p.object_id == object_id and p.target_project_id == target_project_id: - return (False, p) - - # if no matching policy exists, attempt to create one - policy = self.conn.network.create_rbac_policy(**attributes) - except self.sdk.exceptions.OpenStackCloudException as ex: - self.fail_json(msg='Failed to create RBAC policy: {0}'.format(str(ex))) - - return (True, policy) - - def _update_rbac_policy(self, policy): - """ - Updates an existing RBAC policy - returns: the "Changed" state of the RBAC policy - """ - - object_id = self.params.get('object_id') - object_type = self.params.get('object_type') - target_project_id = self.params.get('target_project_id') - project_id = self.params.get('project_id') - action = self.params.get('action') - - allowed_attributes = { - 'rbac_policy': policy.id, - 'target_project_id': target_project_id - } - - disallowed_attributes = { - 'object_id': object_id, - 'object_type': object_type, - 'project_id': project_id, - 'action': action - } - - if not all(allowed_attributes.values()): - self.fail_json(msg='Missing one or more required parameter for updating a RBAC policy') - - if any(disallowed_attributes.values()): - self.fail_json(msg='Cannot change disallowed parameters while updating a RBAC policy: ["object_id", "object_type", "project_id", "action"]') - - try: - policy = self.conn.network.update_rbac_policy(**allowed_attributes) - except self.sdk.exceptions.OpenStackCloudException as ex: - self.fail_json(msg='Failed to update the RBAC policy: {0}'.format(str(ex))) - - return (True, policy) - - def _policy_state_change(self, policy): - state = self.params['state'] - if state == 'present': - if not policy: - return True - if state == 'absent' and policy: - return True - return False - def run(self): - policy_id = self.params.get('policy_id') - state = self.params.get('state') + state = self.params['state'] - if policy_id is not None: - try: - policy = self.conn.network.get_rbac_policy(policy_id) - except self.sdk.exceptions.ResourceNotFound: - policy = None - except self.sdk.exceptions.OpenStackCloudException as ex: - self.fail_json(msg='Failed to get RBAC policy: {0}'.format(str(ex))) - else: - policy = None + policy = self._find() if self.ansible.check_mode: - self.exit_json(changed=self._policy_state_change(policy), policy=policy) + self.exit_json(changed=self._will_change(state, policy)) - if state == 'absent': - if policy is None and policy_id: - self.exit_json(changed=False) - if policy_id is None: - self.fail_json(msg='Must specify policy_id when state is absent') - if policy is not None: - changed = self._delete_rbac_policy(policy) - self.exit_json(changed=changed) - # state == 'present' + if state == 'present' and not policy: + # Create policy + policy = self._create() + self.exit_json(changed=True, + rbac_policy=policy.to_dict(computed=False), + policy=policy.to_dict(computed=False)) + + elif state == 'present' and policy: + # Update policy + update = self._build_update(policy) + if update: + policy = self._update(policy, update) + + self.exit_json(changed=bool(update), + rbac_policy=policy.to_dict(computed=False), + policy=policy.to_dict(computed=False)) + + elif state == 'absent' and policy: + # Delete policy + self._delete(policy) + self.exit_json(changed=True) + + elif state == 'absent' and not policy: + # Do nothing + self.exit_json(changed=False) + + def _build_update(self, policy): + update = {} + + non_updateable_keys = [k for k in ['object_id', 'object_type', + 'project_id', 'action'] + if self.params[k] is not None + and self.params[k] != policy[k]] + + if non_updateable_keys: + self.fail_json(msg='Cannot update parameters {0}' + .format(non_updateable_keys)) + + attributes = dict((k, self.params[k]) + for k in ['target_project_id'] + if self.params[k] is not None + and self.params[k] != policy[k]) + + if attributes: + update['attributes'] = attributes + + return update + + def _create(self): + kwargs = dict((k, self.params[k]) + for k in ['object_id', 'object_type', + 'target_project_id', 'project_id', + 'action'] + if self.params[k] is not None) + + return self.conn.network.create_rbac_policy(**kwargs) + + def _delete(self, policy): + self.conn.network.delete_rbac_policy(policy.id) + + def _find(self): + id = self.params['id'] + + if id is not None: + return self.conn.network.find_rbac_policy(id) + + matches = self._find_matches() + if len(matches) > 1: + self.fail_json(msg='Found more a single matching RBAC policy' + ' which match the given parameters.') + elif len(matches) == 1: + return matches[0] + else: # len(matches) == 0 + return None + + def _find_matches(self): + missing_keys = [k for k in ['action', 'object_id', 'object_type', + 'project_id', 'target_project_id'] + if self.params[k] is None] + if missing_keys: + self.fail_json(msg='Missing parameter(s) for finding' + ' a matching RBAC policy: {0}' + .format(', '.join(missing_keys))) + + kwargs = dict((k, self.params[k]) + for k in ['action', 'object_type', 'project_id']) + + policies = self.conn.network.rbac_policies(**kwargs) + + return [p for p in policies + if any(p[k] == self.params[k] + for k in ['object_id', 'target_project_id'])] + + def _update(self, policy, update): + attributes = update.get('attributes') + if attributes: + policy = self.conn.network.update_rbac_policy(policy.id, + **attributes) + + return policy + + def _will_change(self, state, policy): + if state == 'present' and not policy: + return True + elif state == 'present' and policy: + return bool(self._build_update(policy)) + elif state == 'absent' and policy: + return True else: - if policy is None: - (changed, new_policy) = self._create_rbac_policy() - else: - (changed, new_policy) = self._update_rbac_policy(policy) - - self.exit_json(changed=changed, policy=new_policy) + # state == 'absent' and not policy: + return False def main(): - module = NeutronRbacPolicy() + module = NeutronRBACPolicy() module()