Refactored neutron_rbac_polic{ies_info,y} modules

Change-Id: I1cd37096834d4159b5fa46719f5c479cb2bc29a9
This commit is contained in:
Jakob Meng 2022-11-10 19:21:08 +01:00
parent daf79de37e
commit 0f37ed795b
6 changed files with 356 additions and 471 deletions

View File

@ -60,65 +60,6 @@
description: | description: |
Run openstack collections functional tests against a master devstack Run openstack collections functional tests against a master devstack
using master of openstacksdk with latest ansible release 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: - job:
name: ansible-collections-openstack-functional-devstack-octavia-base name: ansible-collections-openstack-functional-devstack-octavia-base
@ -166,9 +107,6 @@
Run openstack collections functional tests against a master devstack Run openstack collections functional tests against a master devstack
with Octavia plugin enabled, using latest releases of openstacksdk with Octavia plugin enabled, using latest releases of openstacksdk
and latest ansible release. Run it only on Load Balancer changes. 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: - job:
name: ansible-collections-openstack-functional-devstack-releases name: ansible-collections-openstack-functional-devstack-releases
@ -179,8 +117,6 @@
using latest releases of openstacksdk and latest ansible release using latest releases of openstacksdk and latest ansible release
vars: vars:
tox_constraints_file: '{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/constraints-openstacksdk-1.x.x.txt' 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 tox_install_siblings: false
# Job with Ansible 2.9 for checking backward compatibility # Job with Ansible 2.9 for checking backward compatibility
@ -196,8 +132,6 @@
override-checkout: stable-2.9 override-checkout: stable-2.9
vars: vars:
tox_envlist: ansible-2.9 tox_envlist: ansible-2.9
# TODO: Remove tox_extra_args once all ci roles pass
tox_extra_args: *skip_broken_ci_roles
- job: - job:
name: ansible-collections-openstack-functional-devstack-ansible-2.11 name: ansible-collections-openstack-functional-devstack-ansible-2.11
@ -211,8 +145,6 @@
override-checkout: stable-2.11 override-checkout: stable-2.11
vars: vars:
tox_envlist: ansible-2.11 tox_envlist: ansible-2.11
# TODO: Remove tox_extra_args once all ci roles pass
tox_extra_args: *skip_broken_ci_roles
- job: - job:
name: ansible-collections-openstack-functional-devstack-ansible-2.12 name: ansible-collections-openstack-functional-devstack-ansible-2.12
@ -226,8 +158,6 @@
override-checkout: stable-2.12 override-checkout: stable-2.12
vars: vars:
tox_envlist: ansible-2.12 tox_envlist: ansible-2.12
# TODO: Remove tox_extra_args once all ci roles pass
tox_extra_args: *skip_broken_ci_roles
- job: - job:
name: ansible-collections-openstack-functional-devstack-ansible-devel name: ansible-collections-openstack-functional-devstack-ansible-devel
@ -242,9 +172,6 @@
required-projects: required-projects:
- name: github.com/ansible/ansible - name: github.com/ansible/ansible
override-checkout: devel override-checkout: devel
vars:
# TODO: Remove tox_extra_args once all ci roles pass
tox_extra_args: *skip_broken_ci_roles
# Linters # Linters
- job: - job:

View File

@ -0,0 +1,9 @@
expected_fields:
- action
- id
- name
- object_id
- object_type
- project_id
- target_project_id
- tenant_id

View File

@ -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 - name: Create source project
openstack.cloud.project: openstack.cloud.project:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: source_project name: ansible_source_project
description: Source project for network RBAC test description: Source project for network RBAC test
domain_id: default domain_id: default
enabled: True enabled: True
register: source_project register: source_project
- name: Create network - generic - name: Create network
openstack.cloud.network: openstack.cloud.network:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
name: "{{ network_name }}" name: "ansible_network"
state: present state: present
project: "{{ source_project.project.id }}" project: "{{ source_project.project.id }}"
shared: false shared: false
@ -32,7 +23,7 @@
openstack.cloud.project: openstack.cloud.project:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: ansible_project name: ansible_target_project
description: Target project for network RBAC test description: Target project for network RBAC test
domain_id: default domain_id: default
enabled: True enabled: True
@ -48,38 +39,62 @@
project_id: "{{ source_project.project.id }}" project_id: "{{ source_project.project.id }}"
register: rbac_policy 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 - name: Get all rbac policies for {{ source_project.project.name }} - after creation
openstack.cloud.neutron_rbac_policies_info: openstack.cloud.neutron_rbac_policies_info:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
project_id: "{{ source_project.project.id }}" project: "{{ source_project.project.id }}"
register: rbac_policies register: rbac_policies
- name: Capture all existing policy IDs - name: Assert return values of neutron_rbac_policy_info module
set_fact: assert:
rbac_policy_ids: "{{ rbac_policies.policies | map(attribute='id') | list }}" 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 - name: Verify policy exists - after creation
assert: assert:
that: 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 - name: Delete RBAC policy
openstack.cloud.neutron_rbac_policy: openstack.cloud.neutron_rbac_policy:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
policy_id: "{{ rbac_policy.policy.id }}" id: "{{ rbac_policy.rbac_policy.id }}"
state: absent state: absent
- name: Get all rbac policies for {{ source_project.project.name }} - after deletion - name: Get all rbac policies for {{ source_project.project.name }} - after deletion
openstack.cloud.neutron_rbac_policies_info: openstack.cloud.neutron_rbac_policies_info:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
project_id: "{{ source_project.project.id }}" project: "{{ source_project.project.id }}"
register: rbac_policies_remaining 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 - name: Verify policy does not exist - after deletion
assert: assert:
that: 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

View File

@ -40,10 +40,7 @@
when: sdk_version is version(0.44, '>=') when: sdk_version is version(0.44, '>=')
- { role: logging, tags: logging } - { role: logging, tags: logging }
- { role: network, tags: network } - { role: network, tags: network }
- role: neutron_rbac - { role: neutron_rbac_policy, tags: neutron_rbac_policy }
tags:
- rbac
- neutron_rbac
- role: nova_services - role: nova_services
tags: nova_services tags: nova_services
when: sdk_version is version(0.44, '>=') when: sdk_version is version(0.44, '>=')

View File

@ -5,61 +5,57 @@
# (c) 2021, Ashraf Hasson <ahasson@redhat.com> # (c) 2021, Ashraf Hasson <ahasson@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # 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''' DOCUMENTATION = r'''
--- ---
module: neutron_rbac_policies_info module: neutron_rbac_policies_info
short_description: Fetch Neutron policies. short_description: Fetch Neutron RBAC policies.
author: OpenStack Ansible SIG author: OpenStack Ansible SIG
description: description:
- Get RBAC policies against a network, security group or a QoS Policy for one or more projects. - Fetch RBAC policies against a network, security group or a QoS Policy for
- If a C(policy_id) was not provided, this module will attempt to fetch all available policies. one or more projects.
- 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.
options: options:
policy_id: action:
description: description:
- The RBAC policy ID - Action for the RBAC policy.
- If provided, all other filters are ignored - 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 type: str
object_id: object_id:
description: description:
- The object ID (the subject of the policy) to which the RBAC rules applies - The object ID (the subject of the policy) to which the RBAC rules
- This would be the ID of a network, security group or a qos policy applies.
- Mutually exclusive with the C(object_type) - This is an ID of a network, security group or a qos policy.
- Mutually exclusive with the C(object_type).
type: str type: str
object_type: object_type:
description: description:
- Can be one of the following object types C(network), C(security_group) or C(qos_policy) - Type of the object that this RBAC policy affects.
- Mutually exclusive with the C(object_id) - 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'] choices: ['network', 'security_group', 'qos_policy']
type: str type: str
target_project_id: policy_id:
description: description:
- Filters the RBAC rules based on the target project id - The RBAC policy ID.
- Logically AND'ed with other filters - If C(policy_id) is not provided, all available policies will be
- Mutually exclusive with C(project_id) fetched.
type: str - If C(policy_id) provided, all other filters are ignored.
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)
type: str type: str
project: project:
description: description:
- Filters the RBAC rules based on the project name - ID or name of the project to which C(object_id) belongs to.
- Logically AND'ed with other filters - Filters the RBAC rules based on the project name.
- Logically AND'ed with other filters.
type: str type: str
action: aliases: ['project_id']
target_project_id:
description: description:
- Can be either of the following options C(access_as_shared) | C(access_as_external) - The ID of the project this RBAC will be enforced.
- Logically AND'ed with other filters - Filters the RBAC rules based on the target project id.
choices: ['access_as_shared', 'access_as_external'] - Logically AND'ed with other filters.
type: str type: str
extends_documentation_fragment: extends_documentation_fragment:
@ -67,40 +63,18 @@ extends_documentation_fragment:
''' '''
EXAMPLES = r''' EXAMPLES = r'''
# Gather all rbac policies for a project - name: Get all rbac policies for a project
- name: Get all rbac policies for {{ project }}
openstack.cloud.neutron_rbac_policies_info: openstack.cloud.neutron_rbac_policies_info:
project_id: "{{ project.id }}" project: one_project
''' '''
RETURN = r''' RETURN = r'''
# return value can either be plural or signular depending on what was passed in as parameters rbac_policies:
policies: description: List of Neutron RBAC policies.
description: type: list
- List of rbac policies, this could also be returned as a singular element, i.e., 'policy' elements: dict
type: complex
returned: always returned: always
contains: 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: action:
description: description:
- The access model specified by the RBAC rules - The access model specified by the RBAC rules
@ -116,120 +90,96 @@ policies:
- The name of the RBAC rule; usually null - The name of the RBAC rule; usually null
type: str type: str
sample: null sample: null
location: object_id:
description: description:
- A dictionary of the project details to which access is granted - The UUID of the object to which the RBAC rules apply
type: dict type: str
sample: >- sample: "7422172b-2961-475c-ac68-bd0f2a9960ad"
{ object_type:
"cloud": "devstack", description:
"region_name": "", - The object type to which the RBACs apply
"zone": null, type: str
"project": { sample: "network"
"id": "84b8774d595b41e89f3dfaa1fd76932c", project_id:
"name": null, description:
"domain_id": null, - The UUID of the project to which access is granted
"domain_name": null 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 from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class NeutronRbacPoliciesInfo(OpenStackModule): class NeutronRBACPoliciesInfo(OpenStackModule):
argument_spec = dict( 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(), policy_id=dict(),
object_id=dict(), # ID of the object that this RBAC policy affects. project=dict(aliases=['project_id']),
object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects. target_project_id=dict(),
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.
) )
module_kwargs = dict( module_kwargs = dict(
mutually_exclusive=[
('object_id', 'object_type'),
],
supports_check_mode=True, 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): def run(self):
policy_id = self.params.get('policy_id') project_name_or_id = self.params['project']
object_id = self.params.get('object_id') project = None
object_type = self.params.get('object_type') if project_name_or_id is not None:
project_id = self.params.get('project_id') project = self.conn.identity.find_project(project_name_or_id)
project = self.params.get('project') if not project:
target_project_id = self.params.get('target_project_id') self.exit_json(changed=False, rbac_policies=[], policies=[])
if self.ansible.check_mode: policy_id = self.params['policy_id']
self.exit_json(changed=False) if policy_id:
policy = self.conn.network.find_rbac_policy(policy_id)
if policy_id is not None: policies = [policy] if policy else []
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)))
else: else:
if object_id is not None and object_type is not None: kwargs = dict((k, self.params[k])
self.fail_json(msg='object_id and object_type are mutually exclusive, please specify one of the two.') for k in ['action', 'object_type']
if project_id is not None and target_project_id is not None: if self.params[k] is not None)
self.fail_json(msg='project_id and target_project_id are mutually exclusive, please specify one of the two.')
filtered_policies = self._get_rbac_policies() if project:
kwargs['project_id'] = project.id
if project is not None: policies = list(self.conn.network.rbac_policies(**kwargs))
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)
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(): def main():
module = NeutronRbacPoliciesInfo() module = NeutronRBACPoliciesInfo()
module() module()

View File

@ -8,50 +8,64 @@
DOCUMENTATION = r''' DOCUMENTATION = r'''
--- ---
module: neutron_rbac_policy 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 author: OpenStack Ansible SIG
description: description:
- Create a policy to apply a RBAC rule against a network, security group or a QoS Policy or update/delete an existing policy. - Create, update or delete a policy to apply a RBAC rule against a network,
- 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. security group or QoS Policy.
- Accepts same arguments as OpenStackSDK network proxy C(find_rbac_policy) and C(rbac_policies) functions which are ultimately passed over to C(RBACPolicy)
options: options:
policy_id: action:
description: description:
- The RBAC policy ID - Action for the RBAC policy.
- Required when deleting or updating an existing RBAC policy rule, ignored otherwise - 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 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: object_id:
description: description:
- The object ID (the subject of the policy) to which the RBAC rule applies - The object ID (the subject of the policy) to which the RBAC rule
- Cannot be changed when updating an existing policy applies.
- Required when creating a RBAC policy rule, ignored when deleting a policy - Cannot be changed when updating an existing policy.
- Required when creating a RBAC policy rule, ignored when deleting a
policy.
type: str type: str
object_type: object_type:
description: description:
- Can be one of the following object types C(network), C(security_group) or C(qos_policy) - Type of the object that this RBAC policy affects.
- Cannot be changed when updating an existing policy - Can be one of the following object types C(network), C(security_group)
- Required when creating a RBAC policy rule, ignored when deleting a policy 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'] choices: ['network', 'security_group', 'qos_policy']
type: str 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: project_id:
description: description:
- The project to which the object_id belongs - The ID of the project to which C(object_id) belongs to.
- Cannot be changed when updating an existing policy - Cannot be changed when updating an existing policy.
- Required when creating a RBAC policy rule, ignored when deleting a policy - Required when creating a RBAC policy rule, ignored when deleting a
policy.
type: str type: str
action: target_project_id:
description: description:
- Can be either of the following options C(access_as_shared) | C(access_as_external) - The ID of the project to which access to be allowed or revoked aka
- Cannot be changed when updating an existing policy disallowed.
- Required when creating a RBAC policy rule, ignored when deleting a policy - Required when creating or updating a RBAC policy rule, ignored when
choices: ['access_as_shared', 'access_as_external'] deleting a policy.
type: str type: str
state: state:
description: description:
@ -65,54 +79,25 @@ extends_documentation_fragment:
''' '''
EXAMPLES = r''' EXAMPLES = r'''
# Ensure network RBAC policy exists - name: Create or update RBAC policy
- name: Create a new network RBAC policy
neutron_rbac_policy: neutron_rbac_policy:
object_id: '7422172b-2961-475c-ac68-bd0f2a9960ad' object_id: '7422172b-2961-475c-ac68-bd0f2a9960ad'
object_type: 'network' object_type: 'network'
target_project_id: 'a12f9ce1de0645e0a0b01c2e679f69ec'
project_id: '84b8774d595b41e89f3dfaa1fd76932d' 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 - name: Delete RBAC policy
openstack.cloud.openstack.neutron_rbac_policy: openstack.cloud.openstack.neutron_rbac_policy:
policy_id: 'f625242a-6a73-47ac-8d1f-91440b2c617f' id: 'f625242a-6a73-47ac-8d1f-91440b2c617f'
state: absent state: absent
''' '''
RETURN = r''' RETURN = r'''
policy: rbac_policy:
description: description: A dictionary describing the RBAC policy.
- A hash representing the policy
type: complex
returned: always returned: always
type: dict
contains: 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: action:
description: description:
- The access model specified by the RBAC rules - The access model specified by the RBAC rules
@ -128,180 +113,182 @@ policy:
- The name of the RBAC rule; usually null - The name of the RBAC rule; usually null
type: str type: str
sample: null sample: null
location: object_id:
description: description:
- A dictionary of the project details to which access is granted - 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 type: dict
sample: >-
{
"cloud": "devstack",
"region_name": "",
"zone": null,
"project": {
"id": "84b8774d595b41e89f3dfaa1fd76932c",
"name": null,
"domain_id": null,
"domain_name": null
}
}
''' '''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class NeutronRbacPolicy(OpenStackModule): class NeutronRBACPolicy(OpenStackModule):
argument_spec = dict( argument_spec = dict(
policy_id=dict(), action=dict(choices=['access_as_external', 'access_as_shared']),
object_id=dict(), # ID of the object that this RBAC policy affects. id=dict(aliases=['policy_id']),
object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects. object_id=dict(),
target_project_id=dict(), # The ID of the project this RBAC will be enforced. object_type=dict(choices=['security_group', 'qos_policy', 'network']),
project_id=dict(), # The owner project ID. project_id=dict(),
action=dict(choices=['access_as_external', 'access_as_shared']), # Action for the RBAC policy. state=dict(default='present', choices=['absent', 'present']),
state=dict(default='present', choices=['absent', 'present']) target_project_id=dict(),
) )
module_kwargs = dict( module_kwargs = dict(
required_if=[
('state', 'present', ('target_project_id',)),
('state', 'absent', ('id',)),
],
supports_check_mode=True, 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): def run(self):
policy_id = self.params.get('policy_id') state = self.params['state']
state = self.params.get('state')
if policy_id is not None: policy = self._find()
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
if self.ansible.check_mode: 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 state == 'present' and not policy:
if policy is None and policy_id: # 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) 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'
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) 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:
# state == 'absent' and not policy:
return False
def main(): def main():
module = NeutronRbacPolicy() module = NeutronRBACPolicy()
module() module()