From c5d5d08b6b665a5ed2c5a44689dd0bec07aa574f Mon Sep 17 00:00:00 2001 From: abarbare Date: Mon, 1 Oct 2018 11:37:48 +0000 Subject: [PATCH] feat: add scaleway security_group_rule management (#45694) * feat: add scaleway security_group_rule management --- lib/ansible/module_utils/scaleway.py | 8 + .../scaleway/scaleway_security_group_rule.py | 257 ++++++++++++++++++ .../defaults/main.yml | 8 + .../tasks/main.yml | 226 +++++++++++++++ test/legacy/scaleway.yml | 1 + 5 files changed, 500 insertions(+) create mode 100644 lib/ansible/modules/cloud/scaleway/scaleway_security_group_rule.py create mode 100644 test/legacy/roles/scaleway_security_group_rule/defaults/main.yml create mode 100644 test/legacy/roles/scaleway_security_group_rule/tasks/main.yml diff --git a/lib/ansible/module_utils/scaleway.py b/lib/ansible/module_utils/scaleway.py index 51e18ce04bb..b4afc9fe3e6 100644 --- a/lib/ansible/module_utils/scaleway.py +++ b/lib/ansible/module_utils/scaleway.py @@ -15,6 +15,14 @@ def scaleway_argument_spec(): ) +def payload_from_object(scw_object): + return dict( + (k, v) + for k, v in scw_object.items() + if k != 'id' and v is not None + ) + + class ScalewayException(Exception): def __init__(self, message): diff --git a/lib/ansible/modules/cloud/scaleway/scaleway_security_group_rule.py b/lib/ansible/modules/cloud/scaleway/scaleway_security_group_rule.py new file mode 100644 index 00000000000..507dd65ec14 --- /dev/null +++ b/lib/ansible/modules/cloud/scaleway/scaleway_security_group_rule.py @@ -0,0 +1,257 @@ +#!/usr/bin/python +# +# Scaleway Security Group Rule management module +# +# Copyright (C) 2018 Antoine Barbare (antoinebarbare@gmail.com). +# +# 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: scaleway_security_group_rule +short_description: Scaleway Security Group Rule management module +version_added: "2.8" +author: Antoine Barbare (@abarbare) +description: + - This module manages Security Group Rule on Scaleway account + U(https://developer.scaleway.com) +extends_documentation_fragment: scaleway + +options: + state: + description: + - Indicate desired state of the Security Group Rule. + default: present + choices: + - present + - absent + + region: + description: + - Scaleway region to use (for example C(par1)). + required: true + choices: + - ams1 + - EMEA-NL-EVS + - par1 + - EMEA-FR-PAR1 + + protocol: + description: + - Network protocol to use + choices: + - TCP + - UDP + - ICMP + required: true + + port: + description: + - Port related to the rule, null value for all the ports + required: true + type: int + + ip_range: + description: + - IPV4 CIDR notation to apply to the rule + default: 0.0.0.0/0 + + direction: + description: + - Rule direction + choices: + - inbound + - outbound + required: true + + action: + description: + - Rule action + choices: + - accept + - drop + required: true + + security_group: + description: + - Security Group unique identifier + required: true +''' + +EXAMPLES = ''' + - name: Create a Security Group Rule + scaleway_security_group_rule: + state: present + region: par1 + protocol: TCP + port: 80 + ip_range: 0.0.0.0/0 + direction: inbound + action: accept + security_group: b57210ee-1281-4820-a6db-329f78596ecb + register: security_group_rule_creation_task +''' + +RETURN = ''' +data: + description: This is only present when C(state=present) + returned: when C(state=present) + type: dict + sample: { + "scaleway_security_group_rule": { + "direction": "inbound", + "protocol": "TCP", + "ip_range": "0.0.0.0/0", + "dest_port_from": 80, + "action": "accept", + "position": 2, + "dest_port_to": null, + "editable": null, + "id": "10cb0b9a-80f6-4830-abd7-a31cd828b5e9" + } + } +''' + +from ansible.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway, payload_from_object +from ansible.module_utils.compat.ipaddress import ip_network +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule + + +def get_sgr_from_api(security_group_rules, security_group_rule): + """ Check if a security_group_rule specs are present in security_group_rules + Return None if no rules match the specs + Return the rule if found + """ + for sgr in security_group_rules: + if (sgr['ip_range'] == security_group_rule['ip_range'] and sgr['dest_port_from'] == security_group_rule['dest_port_from'] and + sgr['direction'] == security_group_rule['direction'] and sgr['action'] == security_group_rule['action'] and + sgr['protocol'] == security_group_rule['protocol']): + return sgr + + return None + + +def present_strategy(api, security_group_id, security_group_rule): + ret = {'changed': False} + + response = api.get('security_groups/%s/rules' % security_group_id) + if not response.ok: + api.module.fail_json( + msg='Error getting security group rules "%s": "%s" (%s)' % + (response.info['msg'], response.json['message'], response.json)) + + existing_rule = get_sgr_from_api( + response.json['rules'], security_group_rule) + + if not existing_rule: + ret['changed'] = True + if api.module.check_mode: + return ret + + # Create Security Group Rule + response = api.post('/security_groups/%s/rules' % security_group_id, + data=payload_from_object(security_group_rule)) + + if not response.ok: + api.module.fail_json( + msg='Error during security group rule creation: "%s": "%s" (%s)' % + (response.info['msg'], response.json['message'], response.json)) + ret['scaleway_security_group_rule'] = response.json['rule'] + + else: + ret['scaleway_security_group_rule'] = existing_rule + + return ret + + +def absent_strategy(api, security_group_id, security_group_rule): + ret = {'changed': False} + + response = api.get('security_groups/%s/rules' % security_group_id) + if not response.ok: + api.module.fail_json( + msg='Error getting security group rules "%s": "%s" (%s)' % + (response.info['msg'], response.json['message'], response.json)) + + existing_rule = get_sgr_from_api( + response.json['rules'], security_group_rule) + + if not existing_rule: + return ret + + ret['changed'] = True + if api.module.check_mode: + return ret + + response = api.delete( + '/security_groups/%s/rules/%s' % + (security_group_id, existing_rule['id'])) + if not response.ok: + api.module.fail_json( + msg='Error deleting security group rule "%s": "%s" (%s)' % + (response.info['msg'], response.json['message'], response.json)) + + return ret + + +def core(module): + api = Scaleway(module=module) + + security_group_rule = { + 'protocol': module.params['protocol'], + 'dest_port_from': module.params['port'], + 'ip_range': module.params['ip_range'], + 'direction': module.params['direction'], + 'action': module.params['action'], + } + + region = module.params['region'] + module.params['api_url'] = SCALEWAY_LOCATION[region]['api_endpoint'] + + if module.params['state'] == 'present': + summary = present_strategy( + api=api, + security_group_id=module.params['security_group'], + security_group_rule=security_group_rule) + else: + summary = absent_strategy( + api=api, + security_group_id=module.params['security_group'], + security_group_rule=security_group_rule) + module.exit_json(**summary) + + +def main(): + argument_spec = scaleway_argument_spec() + argument_spec.update(dict( + state=dict(default='present', choices=['absent', 'present']), + region=dict(required=True, choices=SCALEWAY_LOCATION.keys()), + protocol=dict(required=True, choices=['TCP', 'UDP', 'ICMP']), + port=dict(required=True, type=int), + ip_range=dict(default='0.0.0.0/0', type=lambda x: to_text(ip_network(to_text(x)))), + direction=dict(required=True, choices=['inbound', 'outbound']), + action=dict(required=True, choices=['accept', 'drop']), + security_group=dict(required=True), + )) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + core(module) + + +if __name__ == '__main__': + main() diff --git a/test/legacy/roles/scaleway_security_group_rule/defaults/main.yml b/test/legacy/roles/scaleway_security_group_rule/defaults/main.yml new file mode 100644 index 00000000000..9c56c4db77d --- /dev/null +++ b/test/legacy/roles/scaleway_security_group_rule/defaults/main.yml @@ -0,0 +1,8 @@ +--- +scaleway_organization: '{{ scw_org }}' +scaleway_region: par1 +protocol: "TCP" +port: 80 +ip_range: "0.0.0.0/0" +direction: "inbound" +action: "accept" diff --git a/test/legacy/roles/scaleway_security_group_rule/tasks/main.yml b/test/legacy/roles/scaleway_security_group_rule/tasks/main.yml new file mode 100644 index 00000000000..606b0c6034c --- /dev/null +++ b/test/legacy/roles/scaleway_security_group_rule/tasks/main.yml @@ -0,0 +1,226 @@ +# SCW_API_KEY='XXX' SCW_SG='GGG' ansible-playbook ./test/legacy/scaleway.yml --tags test_scaleway_security_group_rule + +- name: Set security group fact + set_fact: + security_group: "{{ lookup('env','SCW_SG') }}" + +- name: Check if SCW_SG is defined + debug: + msg: "SCW_SG env variable is required" + failed_when: security_group == "" + +- name: Create security_group_rule check + check_mode: true + scaleway_security_group_rule: + state: present + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: '{{ port }}' + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_creation_task + +- debug: var=security_group_rule_creation_task + +- assert: + that: + - security_group_rule_creation_task is success + - security_group_rule_creation_task is changed + +- block: + - name: Create security_group_rule check + scaleway_security_group_rule: + state: present + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: '{{ port }}' + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_creation_task + + - debug: var=security_group_rule_creation_task + + - assert: + that: + - security_group_rule_creation_task is success + - security_group_rule_creation_task is changed + + - name: Create security_group_rule duplicate + scaleway_security_group_rule: + state: present + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: '{{ port }}' + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_creation_task + + - debug: var=security_group_rule_creation_task + + - assert: + that: + - security_group_rule_creation_task is success + - security_group_rule_creation_task is not changed + + - name: Delete security_group_rule check + check_mode: true + scaleway_security_group_rule: + state: absent + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: '{{ port }}' + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_deletion_task + + - debug: var=security_group_rule_deletion_task + + - assert: + that: + - security_group_rule_deletion_task is success + - security_group_rule_deletion_task is changed + + always: + - name: Delete security_group_rule check + scaleway_security_group_rule: + state: absent + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: '{{ port }}' + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_deletion_task + + - debug: var=security_group_rule_deletion_task + + - assert: + that: + - security_group_rule_deletion_task is success + - security_group_rule_deletion_task is changed + +- name: Delete security_group_rule check + scaleway_security_group_rule: + state: absent + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: '{{ port }}' + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_deletion_task + +- debug: var=security_group_rule_deletion_task + +- assert: + that: + - security_group_rule_deletion_task is success + - security_group_rule_deletion_task is not changed + +- block: + - name: Create security_group_rule with null check + scaleway_security_group_rule: + state: present + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: null + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_creation_task + + - debug: var=security_group_rule_creation_task + + - assert: + that: + - security_group_rule_creation_task is success + - security_group_rule_creation_task is changed + + - name: Create security_group_rule with null duplicate + scaleway_security_group_rule: + state: present + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: null + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_creation_task + + - debug: var=security_group_rule_creation_task + + - assert: + that: + - security_group_rule_creation_task is success + - security_group_rule_creation_task is not changed + + - name: Delete security_group_rule with null check + check_mode: true + scaleway_security_group_rule: + state: absent + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: null + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_deletion_task + + - debug: var=security_group_rule_deletion_task + + - assert: + that: + - security_group_rule_deletion_task is success + - security_group_rule_deletion_task is changed + + always: + - name: Delete security_group_rule with null check + scaleway_security_group_rule: + state: absent + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: null + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_deletion_task + + - debug: var=security_group_rule_deletion_task + + - assert: + that: + - security_group_rule_deletion_task is success + - security_group_rule_deletion_task is changed + +- name: Delete security_group_rule with null check + scaleway_security_group_rule: + state: absent + region: '{{ scaleway_region }}' + protocol: '{{ protocol }}' + port: null + ip_range: '{{ ip_range }}' + direction: '{{ direction }}' + action: '{{ action }}' + security_group: '{{ security_group }}' + register: security_group_rule_deletion_task + +- debug: var=security_group_rule_deletion_task + +- assert: + that: + - security_group_rule_deletion_task is success + - security_group_rule_deletion_task is not changed diff --git a/test/legacy/scaleway.yml b/test/legacy/scaleway.yml index 5e173023ed6..2ba170d61ab 100644 --- a/test/legacy/scaleway.yml +++ b/test/legacy/scaleway.yml @@ -19,3 +19,4 @@ - { role: scaleway_volume, tags: test_scaleway_volume } - { role: scaleway_volume_facts, tags: test_scaleway_volume_facts } - { role: scaleway_security_group, tags: test_scaleway_security_group } + - { role: scaleway_security_group_rule, tags: test_scaleway_security_group_rule }