diff options
Diffstat (limited to 'awx_collection')
-rw-r--r-- | awx_collection/meta/runtime.yml | 1 | ||||
-rw-r--r-- | awx_collection/plugins/module_utils/controller_api.py | 2 | ||||
-rw-r--r-- | awx_collection/plugins/modules/instance.py | 47 | ||||
-rw-r--r-- | awx_collection/plugins/modules/receptor_address.py | 136 | ||||
-rw-r--r-- | awx_collection/test/awx/test_completeness.py | 6 | ||||
-rw-r--r-- | awx_collection/test/awx/test_instance.py | 20 | ||||
-rw-r--r-- | awx_collection/tests/integration/targets/instance/tasks/main.yml | 60 |
7 files changed, 227 insertions, 45 deletions
diff --git a/awx_collection/meta/runtime.yml b/awx_collection/meta/runtime.yml index 18fa4b592e..8ffc5bd122 100644 --- a/awx_collection/meta/runtime.yml +++ b/awx_collection/meta/runtime.yml @@ -34,6 +34,7 @@ action_groups: - organization - project - project_update + - receptor_address - role - schedule - settings diff --git a/awx_collection/plugins/module_utils/controller_api.py b/awx_collection/plugins/module_utils/controller_api.py index 166d43c49e..2c110a9572 100644 --- a/awx_collection/plugins/module_utils/controller_api.py +++ b/awx_collection/plugins/module_utils/controller_api.py @@ -930,6 +930,8 @@ class ControllerAPIModule(ControllerModule): item_name = existing_item['id'] elif item_type == 'instance': item_name = existing_item['hostname'] + elif item_type == 'receptor_address': + item_name = existing_item['address'] else: item_name = existing_item['name'] item_id = existing_item['id'] diff --git a/awx_collection/plugins/modules/instance.py b/awx_collection/plugins/modules/instance.py index 54866fb731..c29246ae31 100644 --- a/awx_collection/plugins/modules/instance.py +++ b/awx_collection/plugins/modules/instance.py @@ -58,23 +58,15 @@ options: - installed required: False type: str - listener_port: - description: - - Port that Receptor will listen for incoming connections on. - required: False - type: int peers: description: - List of peers to connect outbound to. Only configurable for hop and execution nodes. - To remove all current peers, set value to an empty list, []. + - Each item is an ID or address of a receptor address. + - If item is address, it must be unique across all receptor addresses. required: False type: list elements: str - peers_from_control_nodes: - description: - - If enabled, control plane nodes will automatically peer to this node. - required: False - type: bool extends_documentation_fragment: awx.awx.auth ''' @@ -83,12 +75,24 @@ EXAMPLES = ''' awx.awx.instance: hostname: my-instance.prod.example.com capacity_adjustment: 0.4 - listener_port: 31337 - name: Deprovision the instance awx.awx.instance: hostname: my-instance.prod.example.com node_state: deprovisioning + +- name: Create execution node + awx.awx.instance: + hostname: execution.example.com + node_type: execution + peers: + - 12 + - route.to.hop.example.com + +- name: Remove peers + awx.awx.instance: + hostname: execution.example.com + peers: ''' from ..module_utils.controller_api import ControllerAPIModule @@ -103,9 +107,7 @@ def main(): managed_by_policy=dict(type='bool'), node_type=dict(type='str', choices=['execution', 'hop']), node_state=dict(type='str', choices=['deprovisioning', 'installed']), - listener_port=dict(type='int'), peers=dict(required=False, type='list', elements='str'), - peers_from_control_nodes=dict(required=False, type='bool'), ) # Create a module for ourselves @@ -118,12 +120,21 @@ def main(): managed_by_policy = module.params.get('managed_by_policy') node_type = module.params.get('node_type') node_state = module.params.get('node_state') - listener_port = module.params.get('listener_port') peers = module.params.get('peers') - peers_from_control_nodes = module.params.get('peers_from_control_nodes') # Attempt to look up an existing item based on the provided data existing_item = module.get_one('instances', name_or_id=hostname) + # peer item can be an id or address + # if address, get the id + peers_ids = [] + if peers: + for p in peers: + if not p.isdigit(): + p_id = module.get_one('receptor_addresses', allow_none=False, data={'address': p}) + peers_ids.append(p_id['id']) + else: + peers_ids.append(p) + # Create the data that gets sent for create and update new_fields = {'hostname': hostname} if capacity_adjustment is not None: @@ -136,12 +147,8 @@ def main(): new_fields['node_type'] = node_type if node_state is not None: new_fields['node_state'] = node_state - if listener_port is not None: - new_fields['listener_port'] = listener_port if peers is not None: - new_fields['peers'] = peers - if peers_from_control_nodes is not None: - new_fields['peers_from_control_nodes'] = peers_from_control_nodes + new_fields['peers'] = peers_ids module.create_or_update_if_needed( existing_item, diff --git a/awx_collection/plugins/modules/receptor_address.py b/awx_collection/plugins/modules/receptor_address.py new file mode 100644 index 0000000000..0e2e2b6f62 --- /dev/null +++ b/awx_collection/plugins/modules/receptor_address.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# coding: utf-8 -*- + + +# (c) 2023 Red Hat, Inc. +# 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 + + +ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: receptor_address +author: "Seth Foster (@fosterseth)" +version_added: "4.5.0" +short_description: create, update, or destroy Automation Platform Controller receptor addresses. +description: + - Create, update, or destroy Automation Platform Controller receptor addresses. See + U(https://www.ansible.com/tower) for an overview. +options: + address: + description: + - Routable address for this instance. + required: True + type: str + instance: + description: + - ID or hostname of instance this address belongs to. + required: True + type: str + peers_from_control_nodes: + description: + - If True, control plane cluster nodes should automatically peer to it. + required: False + type: bool + port: + description: + - Port for the address. + required: False + type: int + protocol: + description: + - Protocol to use when connecting. + required: False + type: str + choices: [ 'tcp', 'ws', 'wss' ] + websocket_path: + description: + - Websocket path. + required: False + type: str + state: + description: + - Desired state of the resource. + choices: ["present", "absent", "exists"] + default: "present" + type: str +extends_documentation_fragment: awx.awx.auth +''' + +EXAMPLES = ''' + - name: Create receptor address + awx.awx.receptor_address: + address: exec1addr + instance: exec1.example.com + peers_from_control_nodes: false + port: 6791 + protocol: ws + websocket_path: service + state: present + register: exec1addr +''' + +from ..module_utils.controller_api import ControllerAPIModule + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + address=dict(required=True, type='str'), + instance=dict(type='str'), + peers_from_control_nodes=dict(type='bool'), + port=dict(type='int'), + protocol=dict(type='str'), + websocket_path=dict(type='str'), + state=dict(choices=['present', 'absent', 'exists'], default='present'), + + ) + + # Create a module for ourselves + module = ControllerAPIModule(argument_spec=argument_spec) + + # Extract our parameters + address = module.params.get('address') + peers_from_control_nodes = module.params.get('peers_from_control_nodes') + port = module.params.get('port') + protocol = module.params.get('protocol', 'tcp') + websocket_path = module.params.get('websocket_path') + instance_name_or_id = module.params.get('instance') + state = module.params.get('state') + + # Attempt to look up an existing instance + receptor_address = module.get_one('receptor_addresses', allow_none=True, data=dict(address=address, protocol=protocol)) + if receptor_address: + receptor_address['type'] = 'receptor_address' + + if receptor_address and state == 'absent': + module.delete_if_needed(receptor_address) + + instance = module.get_one('instances', allow_none=False, name_or_id=instance_name_or_id) + + # Create the data that gets sent for create and update + new_fields = {'instance': instance['id'], 'address': address} + if port: + new_fields['port'] = port + if protocol: + new_fields['protocol'] = protocol + if peers_from_control_nodes: + new_fields['peers_from_control_nodes'] = peers_from_control_nodes + if websocket_path: + new_fields['websocket_path'] = websocket_path + + module.create_or_update_if_needed( + receptor_address, + new_fields, + endpoint='receptor_addresses', + item_type='receptor_address', + ) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/test/awx/test_completeness.py b/awx_collection/test/awx/test_completeness.py index b3c6e6e27f..6f5c459e09 100644 --- a/awx_collection/test/awx/test_completeness.py +++ b/awx_collection/test/awx/test_completeness.py @@ -54,7 +54,7 @@ extra_endpoints = { } # Global module parameters we can ignore -ignore_parameters = ['state', 'new_name', 'update_secrets', 'copy_from'] +ignore_parameters = ['state', 'new_name', 'update_secrets', 'copy_from', 'is_internal'] # Some modules take additional parameters that do not appear in the API # Add the module name as the key with the value being the list of params to ignore @@ -248,7 +248,9 @@ def test_completeness(collection_import, request, admin_user, job_template, exec singular_endpoint = '{0}'.format(endpoint) if singular_endpoint.endswith('ies'): singular_endpoint = singular_endpoint[:-3] - if singular_endpoint != 'settings' and singular_endpoint.endswith('s'): + elif singular_endpoint.endswith('ses'): # receptor_addresses + singular_endpoint = singular_endpoint[:-2] + elif singular_endpoint != 'settings' and singular_endpoint.endswith('s'): singular_endpoint = singular_endpoint[:-1] module_name = '{0}'.format(singular_endpoint) diff --git a/awx_collection/test/awx/test_instance.py b/awx_collection/test/awx/test_instance.py index a2218be68f..1cef3220da 100644 --- a/awx_collection/test/awx/test_instance.py +++ b/awx_collection/test/awx/test_instance.py @@ -13,39 +13,39 @@ def test_peers_adding_and_removing(run_module, admin_user): with override_settings(IS_K8S=True): result = run_module( 'instance', - {'hostname': 'hopnode1', 'node_type': 'hop', 'peers_from_control_nodes': True, 'node_state': 'installed', 'listener_port': 27199}, + {'hostname': 'hopnode', 'node_type': 'hop', 'node_state': 'installed'}, admin_user, ) assert result['changed'] - hop_node_1 = Instance.objects.get(pk=result.get('id')) + hop_node = Instance.objects.get(pk=result.get('id')) - assert hop_node_1.peers_from_control_nodes is True - assert hop_node_1.node_type == 'hop' + assert hop_node.node_type == 'hop' result = run_module( - 'instance', - {'hostname': 'hopnode2', 'node_type': 'hop', 'peers_from_control_nodes': True, 'node_state': 'installed', 'listener_port': 27199}, + 'receptor_address', + {'address': 'hopnodeaddr', 'instance': 'hopnode', 'port': 6789}, admin_user, ) assert result['changed'] - hop_node_2 = Instance.objects.get(pk=result.get('id')) + address = hop_node.receptor_addresses.get(pk=result.get('id')) + assert address.port == 6789 result = run_module( 'instance', - {'hostname': 'executionnode', 'node_type': 'execution', 'node_state': 'installed', 'listener_port': 27199, 'peers': ['hopnode1', 'hopnode2']}, + {'hostname': 'executionnode', 'node_type': 'execution', 'node_state': 'installed', 'peers': ['hopnodeaddr']}, admin_user, ) assert result['changed'] execution_node = Instance.objects.get(pk=result.get('id')) - assert set(execution_node.peers.all()) == {hop_node_1, hop_node_2} + assert set(execution_node.peers.all()) == {address} result = run_module( 'instance', - {'hostname': 'executionnode', 'node_type': 'execution', 'node_state': 'installed', 'listener_port': 27199, 'peers': []}, + {'hostname': 'executionnode', 'node_type': 'execution', 'node_state': 'installed', 'peers': []}, admin_user, ) diff --git a/awx_collection/tests/integration/targets/instance/tasks/main.yml b/awx_collection/tests/integration/targets/instance/tasks/main.yml index 798db734df..f97f80b9cb 100644 --- a/awx_collection/tests/integration/targets/instance/tasks/main.yml +++ b/awx_collection/tests/integration/targets/instance/tasks/main.yml @@ -42,7 +42,6 @@ node_type: execution node_state: installed capacity_adjustment: 0.4 - listener_port: 31337 register: result - assert: @@ -74,11 +73,24 @@ - block: - name: Create hop node 1 awx.awx.instance: - hostname: hopnode1 + hostname: "{{ hostname1 }}" node_type: hop node_state: installed - listener_port: 27199 - peers_from_control_nodes: True + register: result + + - assert: + that: + - result is changed + + - name: Create address for hop node 1 + awx.awx.receptor_address: + address: "{{ hostname1 }}addr" + instance: "{{ hostname1 }}" + peers_from_control_nodes: false + port: 27199 + protocol: ws + websocket_path: service + state: present register: result - assert: @@ -87,11 +99,24 @@ - name: Create hop node 2 awx.awx.instance: - hostname: hopnode2 + hostname: "{{ hostname2 }}" node_type: hop node_state: installed - listener_port: 27199 - peers_from_control_nodes: True + register: result + + - assert: + that: + - result is changed + + - name: Create address for hop node 2 + awx.awx.receptor_address: + address: "{{ hostname2 }}addr" + instance: "{{ hostname2 }}" + peers_from_control_nodes: false + port: 27199 + protocol: ws + websocket_path: service + state: present register: result - assert: @@ -100,13 +125,12 @@ - name: Create execution node awx.awx.instance: - hostname: executionnode + hostname: "{{ hostname3 }}" node_type: execution node_state: installed - listener_port: 27199 peers: - - "hopnode1" - - "hopnode2" + - "{{ hostname1 }}addr" + - "{{ hostname2 }}addr" register: result - assert: @@ -115,10 +139,9 @@ - name: Remove execution node peers awx.awx.instance: - hostname: executionnode + hostname: "{{ hostname3 }}" node_type: execution node_state: installed - listener_port: 27199 peers: [] register: result @@ -126,4 +149,15 @@ that: - result is changed + always: + - name: Deprovision the instances + awx.awx.instance: + hostname: "{{ item }}" + node_state: deprovisioning + with_items: + - "{{ hostname1 }}" + - "{{ hostname2 }}" + - "{{ hostname3 }}" + + when: IS_K8S |