From c904336db050d8d77653a7bc0ea778138637474c Mon Sep 17 00:00:00 2001 From: zhongjun2 Date: Sat, 19 Oct 2019 18:53:02 +0800 Subject: [PATCH] hwc: Add security group rule module (#60186) --- .../huawei/hwc_vpc_security_group_rule.py | 572 ++++++++++++++++++ .../hwc_vpc_security_group_rule/aliases | 1 + .../tasks/main.yml | 157 +++++ 3 files changed, 730 insertions(+) create mode 100644 lib/ansible/modules/cloud/huawei/hwc_vpc_security_group_rule.py create mode 100644 test/integration/targets/hwc_vpc_security_group_rule/aliases create mode 100644 test/integration/targets/hwc_vpc_security_group_rule/tasks/main.yml diff --git a/lib/ansible/modules/cloud/huawei/hwc_vpc_security_group_rule.py b/lib/ansible/modules/cloud/huawei/hwc_vpc_security_group_rule.py new file mode 100644 index 00000000000..57995b7ee8c --- /dev/null +++ b/lib/ansible/modules/cloud/huawei/hwc_vpc_security_group_rule.py @@ -0,0 +1,572 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2019 Huawei +# 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 +############################################################################### + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ["preview"], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: hwc_vpc_security_group_rule +description: + - vpc security group management. +short_description: Creates a resource of Vpc/SecurityGroupRule in Huawei Cloud +notes: + - If I(id) option is provided, it takes precedence over + I(enterprise_project_id) for security group rule selection. + - I(security_group_id) is used for security group rule selection. If more + than one security group rule with this options exists, execution is + aborted. + - No parameter support updating. If one of option is changed, the module + will create a new resource. +version_added: '2.10' +author: Huawei Inc. (@huaweicloud) +requirements: + - keystoneauth1 >= 3.6.0 +options: + state: + description: + - Whether the given object should exist in Huawei Cloud. + type: str + choices: ['present', 'absent'] + default: 'present' + direction: + description: + - Specifies the direction of access control. The value can be + egress or ingress. + type: str + required: true + security_group_id: + description: + - Specifies the security group rule ID, which uniquely identifies + the security group rule. + type: str + required: true + description: + description: + - Provides supplementary information about the security group rule. + The value is a string of no more than 255 characters that can + contain letters and digits. + type: str + required: false + ethertype: + description: + - Specifies the IP protocol version. The value can be IPv4 or IPv6. + If you do not set this parameter, IPv4 is used by default. + type: str + required: false + port_range_max: + description: + - Specifies the end port number. The value ranges from 1 to 65535. + If the protocol is not icmp, the value cannot be smaller than the + port_range_min value. An empty value indicates all ports. + type: int + required: false + port_range_min: + description: + - Specifies the start port number. The value ranges from 1 to + 65535. The value cannot be greater than the port_range_max value. + An empty value indicates all ports. + type: int + required: false + protocol: + description: + - Specifies the protocol type. The value can be icmp, tcp, or udp. + If the parameter is left blank, the security group supports all + protocols. + type: str + required: false + remote_group_id: + description: + - Specifies the ID of the peer security group. The value is + exclusive with parameter remote_ip_prefix. + type: str + required: false + remote_ip_prefix: + description: + - Specifies the remote IP address. If the access control direction + is set to egress, the parameter specifies the source IP address. + If the access control direction is set to ingress, the parameter + specifies the destination IP address. The value can be in the + CIDR format or IP addresses. The parameter is exclusive with + parameter remote_group_id. + type: str + required: false +extends_documentation_fragment: hwc +''' + +EXAMPLES = ''' +# create a security group rule +- name: create a security group + hwc_vpc_security_group: + name: "ansible_network_security_group_test" + register: sg +- name: create a security group rule + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 22 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" +''' + +RETURN = ''' + direction: + description: + - Specifies the direction of access control. The value can be + egress or ingress. + type: str + returned: success + security_group_id: + description: + - Specifies the security group rule ID, which uniquely identifies + the security group rule. + type: str + returned: success + description: + description: + - Provides supplementary information about the security group rule. + The value is a string of no more than 255 characters that can + contain letters and digits. + type: str + returned: success + ethertype: + description: + - Specifies the IP protocol version. The value can be IPv4 or IPv6. + If you do not set this parameter, IPv4 is used by default. + type: str + returned: success + port_range_max: + description: + - Specifies the end port number. The value ranges from 1 to 65535. + If the protocol is not icmp, the value cannot be smaller than the + port_range_min value. An empty value indicates all ports. + type: int + returned: success + port_range_min: + description: + - Specifies the start port number. The value ranges from 1 to + 65535. The value cannot be greater than the port_range_max value. + An empty value indicates all ports. + type: int + returned: success + protocol: + description: + - Specifies the protocol type. The value can be icmp, tcp, or udp. + If the parameter is left blank, the security group supports all + protocols. + type: str + returned: success + remote_group_id: + description: + - Specifies the ID of the peer security group. The value is + exclusive with parameter remote_ip_prefix. + type: str + returned: success + remote_ip_prefix: + description: + - Specifies the remote IP address. If the access control direction + is set to egress, the parameter specifies the source IP address. + If the access control direction is set to ingress, the parameter + specifies the destination IP address. The value can be in the + CIDR format or IP addresses. The parameter is exclusive with + parameter remote_group_id. + type: str + returned: success +''' + +from ansible.module_utils.hwc_utils import ( + Config, HwcClientException, HwcModule, are_different_dicts, build_path, + get_region, is_empty_value, navigate_value) + + +def build_module(): + return HwcModule( + argument_spec=dict( + state=dict(default='present', choices=['present', 'absent'], + type='str'), + direction=dict(type='str', required=True), + security_group_id=dict(type='str', required=True), + description=dict(type='str'), + ethertype=dict(type='str'), + port_range_max=dict(type='int'), + port_range_min=dict(type='int'), + protocol=dict(type='str'), + remote_group_id=dict(type='str'), + remote_ip_prefix=dict(type='str') + ), + supports_check_mode=True, + ) + + +def main(): + """Main function""" + + module = build_module() + config = Config(module, "vpc") + + try: + resource = None + if module.params['id']: + resource = True + else: + v = search_resource(config) + if len(v) > 1: + raise Exception("Found more than one resource(%s)" % ", ".join([ + navigate_value(i, ["id"]) for i in v])) + + if len(v) == 1: + resource = v[0] + module.params['id'] = navigate_value(resource, ["id"]) + + result = {} + changed = False + if module.params['state'] == 'present': + if resource is None: + if not module.check_mode: + create(config) + changed = True + + current = read_resource(config, exclude_output=True) + expect = user_input_parameters(module) + if are_different_dicts(expect, current): + raise Exception( + "Cannot change option from (%s) to (%s) for an" + " existing security group(%s)." % (current, expect, module.params.get('id'))) + result = read_resource(config) + result['id'] = module.params.get('id') + else: + if resource: + if not module.check_mode: + delete(config) + changed = True + + except Exception as ex: + module.fail_json(msg=str(ex)) + + else: + result['changed'] = changed + module.exit_json(**result) + + +def user_input_parameters(module): + return { + "description": module.params.get("description"), + "direction": module.params.get("direction"), + "ethertype": module.params.get("ethertype"), + "port_range_max": module.params.get("port_range_max"), + "port_range_min": module.params.get("port_range_min"), + "protocol": module.params.get("protocol"), + "remote_group_id": module.params.get("remote_group_id"), + "remote_ip_prefix": module.params.get("remote_ip_prefix"), + "security_group_id": module.params.get("security_group_id"), + } + + +def create(config): + module = config.module + client = config.client(get_region(module), "vpc", "project") + opts = user_input_parameters(module) + + params = build_create_parameters(opts) + r = send_create_request(module, params, client) + module.params['id'] = navigate_value(r, ["security_group_rule", "id"]) + + +def delete(config): + module = config.module + client = config.client(get_region(module), "vpc", "project") + + send_delete_request(module, None, client) + + +def read_resource(config, exclude_output=False): + module = config.module + client = config.client(get_region(module), "vpc", "project") + + res = {} + + r = send_read_request(module, client) + res["read"] = fill_read_resp_body(r) + + return update_properties(module, res, None, exclude_output) + + +def _build_query_link(opts): + query_link = "?marker={marker}&limit=10" + v = navigate_value(opts, ["security_group_id"]) + if v: + query_link += "&security_group_id=" + str(v) + + return query_link + + +def search_resource(config): + module = config.module + client = config.client(get_region(module), "vpc", "project") + opts = user_input_parameters(module) + identity_obj = _build_identity_object(opts) + query_link = _build_query_link(opts) + link = "security-group-rules" + query_link + + result = [] + p = {'marker': ''} + while True: + url = link.format(**p) + r = send_list_request(module, client, url) + if not r: + break + + for item in r: + item = fill_list_resp_body(item) + if not are_different_dicts(identity_obj, item): + result.append(item) + + if len(result) > 1: + break + + p['marker'] = r[-1].get('id') + + return result + + +def build_create_parameters(opts): + params = dict() + + v = navigate_value(opts, ["description"], None) + if not is_empty_value(v): + params["description"] = v + + v = navigate_value(opts, ["direction"], None) + if not is_empty_value(v): + params["direction"] = v + + v = navigate_value(opts, ["ethertype"], None) + if not is_empty_value(v): + params["ethertype"] = v + + v = navigate_value(opts, ["port_range_max"], None) + if not is_empty_value(v): + params["port_range_max"] = v + + v = navigate_value(opts, ["port_range_min"], None) + if not is_empty_value(v): + params["port_range_min"] = v + + v = navigate_value(opts, ["protocol"], None) + if not is_empty_value(v): + params["protocol"] = v + + v = navigate_value(opts, ["remote_group_id"], None) + if not is_empty_value(v): + params["remote_group_id"] = v + + v = navigate_value(opts, ["remote_ip_prefix"], None) + if not is_empty_value(v): + params["remote_ip_prefix"] = v + + v = navigate_value(opts, ["security_group_id"], None) + if not is_empty_value(v): + params["security_group_id"] = v + + if not params: + return params + + params = {"security_group_rule": params} + + return params + + +def send_create_request(module, params, client): + url = "security-group-rules" + try: + r = client.post(url, params) + except HwcClientException as ex: + msg = ("module(hwc_vpc_security_group_rule): error running " + "api(create), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return r + + +def send_delete_request(module, params, client): + url = build_path(module, "security-group-rules/{id}") + + try: + r = client.delete(url, params) + except HwcClientException as ex: + msg = ("module(hwc_vpc_security_group_rule): error running " + "api(delete), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return r + + +def send_read_request(module, client): + url = build_path(module, "security-group-rules/{id}") + + r = None + try: + r = client.get(url) + except HwcClientException as ex: + msg = ("module(hwc_vpc_security_group_rule): error running " + "api(read), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return navigate_value(r, ["security_group_rule"], None) + + +def fill_read_resp_body(body): + result = dict() + + result["description"] = body.get("description") + + result["direction"] = body.get("direction") + + result["ethertype"] = body.get("ethertype") + + result["id"] = body.get("id") + + result["port_range_max"] = body.get("port_range_max") + + result["port_range_min"] = body.get("port_range_min") + + result["protocol"] = body.get("protocol") + + result["remote_address_group_id"] = body.get("remote_address_group_id") + + result["remote_group_id"] = body.get("remote_group_id") + + result["remote_ip_prefix"] = body.get("remote_ip_prefix") + + result["security_group_id"] = body.get("security_group_id") + + return result + + +def update_properties(module, response, array_index, exclude_output=False): + r = user_input_parameters(module) + + v = navigate_value(response, ["read", "description"], array_index) + r["description"] = v + + v = navigate_value(response, ["read", "direction"], array_index) + r["direction"] = v + + v = navigate_value(response, ["read", "ethertype"], array_index) + r["ethertype"] = v + + v = navigate_value(response, ["read", "port_range_max"], array_index) + r["port_range_max"] = v + + v = navigate_value(response, ["read", "port_range_min"], array_index) + r["port_range_min"] = v + + v = navigate_value(response, ["read", "protocol"], array_index) + r["protocol"] = v + + v = navigate_value(response, ["read", "remote_group_id"], array_index) + r["remote_group_id"] = v + + v = navigate_value(response, ["read", "remote_ip_prefix"], array_index) + r["remote_ip_prefix"] = v + + v = navigate_value(response, ["read", "security_group_id"], array_index) + r["security_group_id"] = v + + return r + + +def send_list_request(module, client, url): + + r = None + try: + r = client.get(url) + except HwcClientException as ex: + msg = ("module(hwc_vpc_security_group_rule): error running " + "api(list), error: %s" % str(ex)) + module.fail_json(msg=msg) + + return navigate_value(r, ["security_group_rules"], None) + + +def _build_identity_object(all_opts): + result = dict() + + v = navigate_value(all_opts, ["description"], None) + result["description"] = v + + v = navigate_value(all_opts, ["direction"], None) + result["direction"] = v + + v = navigate_value(all_opts, ["ethertype"], None) + result["ethertype"] = v + + result["id"] = None + + v = navigate_value(all_opts, ["port_range_max"], None) + result["port_range_max"] = v + + v = navigate_value(all_opts, ["port_range_min"], None) + result["port_range_min"] = v + + v = navigate_value(all_opts, ["protocol"], None) + result["protocol"] = v + + result["remote_address_group_id"] = None + + v = navigate_value(all_opts, ["remote_group_id"], None) + result["remote_group_id"] = v + + v = navigate_value(all_opts, ["remote_ip_prefix"], None) + result["remote_ip_prefix"] = v + + v = navigate_value(all_opts, ["security_group_id"], None) + result["security_group_id"] = v + + return result + + +def fill_list_resp_body(body): + result = dict() + + result["description"] = body.get("description") + + result["direction"] = body.get("direction") + + result["ethertype"] = body.get("ethertype") + + result["id"] = body.get("id") + + result["port_range_max"] = body.get("port_range_max") + + result["port_range_min"] = body.get("port_range_min") + + result["protocol"] = body.get("protocol") + + result["remote_address_group_id"] = body.get("remote_address_group_id") + + result["remote_group_id"] = body.get("remote_group_id") + + result["remote_ip_prefix"] = body.get("remote_ip_prefix") + + result["security_group_id"] = body.get("security_group_id") + + return result + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/hwc_vpc_security_group_rule/aliases b/test/integration/targets/hwc_vpc_security_group_rule/aliases new file mode 100644 index 00000000000..ad7ccf7ada2 --- /dev/null +++ b/test/integration/targets/hwc_vpc_security_group_rule/aliases @@ -0,0 +1 @@ +unsupported diff --git a/test/integration/targets/hwc_vpc_security_group_rule/tasks/main.yml b/test/integration/targets/hwc_vpc_security_group_rule/tasks/main.yml new file mode 100644 index 00000000000..c5313ec467d --- /dev/null +++ b/test/integration/targets/hwc_vpc_security_group_rule/tasks/main.yml @@ -0,0 +1,157 @@ +--- +# Pre-test setup +- name: create a security group + hwc_vpc_security_group: + name: "ansible_network_security_group_test" + state: present + register: sg +- name: delete a security group rule + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: absent +#---------------------------------------------------------- +- name: create a security group rule (check mode) + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: present + check_mode: yes + register: result +- name: assert changed is true + assert: + that: + - not result.id + - result.changed +#---------------------------------------------------------- +- name: create a security group rule + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: present + register: result +- name: assert changed is true + assert: + that: + - result is changed +#---------------------------------------------------------- +- name: create a security group rule (idemponent) + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: present + register: result +- name: idemponent + assert: + that: + - not result.changed +# ---------------------------------------------------------------------------- +- name: create a security group rule that already exists + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: present + register: result +- name: assert changed is false + assert: + that: + - result.failed == 0 + - result.changed == false +#---------------------------------------------------------- +- name: delete a security group rule (check mode) + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: absent + check_mode: yes + register: result +- name: assert changed is true + assert: + that: + - result is changed +#---------------------------------------------------------- +- name: delete a security group rule + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: absent + register: result +- name: assert changed is true + assert: + that: + - result is changed +#---------------------------------------------------------- +- name: delete a security group rule (idemponent) + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: absent + register: result +- name: idemponent + assert: + that: + - not result.changed +# ---------------------------------------------------------------------------- +- name: delete a security group rule that does not exist + hwc_vpc_security_group_rule: + direction: "ingress" + protocol: "tcp" + ethertype: "IPv4" + port_range_max: 55 + security_group_id: "{{ sg.id }}" + port_range_min: 22 + remote_ip_prefix: "0.0.0.0/0" + state: absent + register: result +- name: assert changed is false + assert: + that: + - result.failed == 0 + - result.changed == false +#--------------------------------------------------------- +# Post-test teardown +- name: delete a security group + hwc_vpc_security_group: + name: "ansible_network_security_group_test" + state: absent + register: sg