diff --git a/lib/ansible/modules/network/meraki/meraki_mx_l7_firewall.py b/lib/ansible/modules/network/meraki/meraki_mx_l7_firewall.py new file mode 100644 index 00000000000000..e30aa4c8ec008d --- /dev/null +++ b/lib/ansible/modules/network/meraki/meraki_mx_l7_firewall.py @@ -0,0 +1,506 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Kevin Breit (@kbreit) +# 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 = r''' +--- +module: meraki_mx_l7_firewall +short_description: Manage MX appliance layer 7 firewalls in the Meraki cloud +version_added: "2.9" +description: +- Allows for creation, management, and visibility into layer 7 firewalls implemented on Meraki MX firewalls. +notes: +- Module assumes a complete list of firewall rules are passed as a parameter. +- If there is interest in this module allowing manipulation of a single firewall rule, please submit an issue against this module. +options: + state: + description: + - Query or modify a firewall rule. + choices: ['present', 'query'] + default: present + type: str + net_name: + description: + - Name of network which MX firewall is in. + type: str + net_id: + description: + - ID of network which MX firewall is in. + type: str + rules: + description: + - List of layer 7 firewall rules. + type: list + suboptions: + policy: + description: + - Policy to apply if rule is hit. + choices: [deny] + default: deny + type: str + type: + description: + - Type of policy to apply. + choices: [application, + application_category, + blacklisted_countries, + host, + ip_range, + port, + whitelisted_countries] + type: str + application: + description: + - Application to filter. + suboptions: + name: + description: + - Name of application to filter as defined by Meraki. + type: str + id: + description: + - URI of application as defined by Meraki. + type: str + application_category: + description: + - Category of applications to filter. + suboptions: + name: + description: + - Name of application category to filter as defined by Meraki. + type: str + id: + description: + - URI of application category as defined by Meraki. + type: str + host: + description: + - FQDN of host to filter. + type: str + ip_range: + description: + - CIDR notation range of IP addresses to apply rule to. + - Port can be appended to range with a C(":"). + type: str + port: + description: + - TCP or UDP based port to filter. + type: str + countries: + description: + - List of countries to whitelist or blacklist. + - The countries follow the two-letter ISO 3166-1 alpha-2 format. + type: list + categories: + description: + - When C(True), specifies that applications and application categories should be queried instead of firewall rules. + type: bool +author: +- Kevin Breit (@kbreit) +extends_documentation_fragment: meraki +''' + +EXAMPLES = r''' +- name: Query firewall rules + meraki_mx_l7_firewall: + auth_key: abc123 + org_name: YourOrg + net_name: YourNet + state: query + delegate_to: localhost + +- name: Query applications and application categories + meraki_mx_l7_firewall: + auth_key: abc123 + org_name: YourOrg + net_name: YourNet + categories: yes + state: query + delegate_to: localhost + +- name: Set firewall rules + meraki_mx_l7_firewall: + auth_key: abc123 + org_name: YourOrg + net_name: YourNet + state: present + rules: + - type: whitelisted_countries + countries: + - US + - FR + - type: blacklisted_countries + countries: + - CN + - policy: deny + type: port + port: 8080 + - type: port + port: 1234 + - type: host + host: asdf.com + - type: application + application: + id: meraki:layer7/application/205 + - type: application_category + application: + id: meraki:layer7/category/24 + delegate_to: localhost +''' + +RETURN = r''' +data: + description: Firewall rules associated to network. + returned: success + type: complex + contains: + rules: + description: Ordered list of firewall rules. + returned: success, when not querying applications + type: list + contains: + policy: + description: Action to apply when rule is hit. + returned: success + type: string + sample: deny + type: + description: Type of rule category. + returned: success + type: string + sample: applications + applications: + description: List of applications within a category. + type: list + contains: + id: + description: URI of application. + returned: success + type: string + sample: Gmail + name: + description: Descriptive name of application. + returned: success + type: string + sample: meraki:layer7/application/4 + applicationCategory: + description: List of application categories within a category. + type: list + contains: + id: + description: URI of application. + returned: success + type: string + sample: Gmail + name: + description: Descriptive name of application. + returned: success + type: string + sample: meraki:layer7/application/4 + port: + description: Port number in rule. + returned: success + type: string + sample: 23 + ipRange: + description: Range of IP addresses in rule. + returned: success + type: string + sample: 1.1.1.0/23 + whitelistedCountries: + description: Countries to be whitelisted. + returned: success + type: string + sample: CA + blacklistedCountries: + description: Countries to be blacklisted. + returned: success + type: string + sample: RU + application_categories: + description: List of application categories and applications. + type: list + returned: success, when querying applications + contains: + applications: + description: List of applications within a category. + type: list + contains: + id: + description: URI of application. + returned: success + type: string + sample: Gmail + name: + description: Descriptive name of application. + returned: success + type: string + sample: meraki:layer7/application/4 + id: + description: URI of application category. + returned: success + type: string + sample: Email + name: + description: Descriptive name of application category. + returned: success + type: string + sample: layer7/category/1 +''' + +import copy +import os +from ansible.module_utils.basic import AnsibleModule, json, env_fallback +from ansible.module_utils.common.dict_transformations import recursive_diff +from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec + + +def get_applications(meraki, net_id): + path = meraki.construct_path('get_categories', net_id=net_id) + return meraki.request(path, method='GET') + + +def lookup_application(meraki, net_id, application): + response = get_applications(meraki, net_id) + for category in response['applicationCategories']: + if category['name'].lower() == application.lower(): + return category['id'] + for app in category['applications']: + if app['name'].lower() == application.lower(): + return app['id'] + meraki.fail_json(msg="No application or category named {0} found".format(application)) + + +def assemble_payload(meraki, net_id, rule): + if rule['type'] == 'application': + new_rule = {'policy': rule['policy'], + 'type': 'application', + } + if rule['application']['id']: + new_rule['value'] = {'id': rule['application']['id']} + elif rule['application']['name']: + new_rule['value'] = {'id': lookup_application(meraki, net_id, rule['application']['name'])} + elif rule['type'] == 'application_category': + new_rule = {'policy': rule['policy'], + 'type': 'applicationCategory', + } + if rule['application']['id']: + new_rule['value'] = {'id': rule['application']['id']} + elif rule['application']['name']: + new_rule['value'] = {'id': lookup_application(meraki, net_id, rule['application']['name'])} + elif rule['type'] == 'ip_range': + new_rule = {'policy': rule['policy'], + 'type': 'ipRange', + 'value': rule['ip_range']} + elif rule['type'] == 'host': + new_rule = {'policy': rule['policy'], + 'type': rule['type'], + 'value': rule['host']} + elif rule['type'] == 'port': + new_rule = {'policy': rule['policy'], + 'type': rule['type'], + 'value': rule['port']} + elif rule['type'] == 'blacklisted_countries': + new_rule = {'policy': rule['policy'], + 'type': 'blacklistedCountries', + 'value': rule['countries'] + } + elif rule['type'] == 'whitelisted_countries': + new_rule = {'policy': rule['policy'], + 'type': 'whitelistedCountries', + 'value': rule['countries'] + } + return new_rule + + +def restructure_response(rules): + for rule in rules['rules']: + type = rule['type'] + rule[type] = copy.deepcopy(rule['value']) + del rule['value'] + return rules + + +def get_rules(meraki, net_id): + path = meraki.construct_path('get_all', net_id=net_id) + response = meraki.request(path, method='GET') + if meraki.status == 200: + return response + + +def rename_id_to_appid(rules): + for rule in rules['rules']: + print(rule['type']) + if rule['type'] == 'application' or rule['type'] == 'applicationCategory': + rule['value']['appId'] = rule['value'].pop('id') + return rules + + +def rename_appid_to_id(rules): + for rule in rules['rules']: + if rule['type'] == 'application' or rule['type'] == 'applicationCategory': + rule['value']['id'] = rule['value'].pop('appId') + return rules + + +def main(): + # define the available arguments/parameters that a user can pass to + # the module + + application_arg_spec = dict(id=dict(type='str'), + name=dict(type='str'), + ) + + rule_arg_spec = dict(policy=dict(type='str', choices=['deny'], default='deny'), + type=dict(type='str', choices=['application', + 'application_category', + 'blacklisted_countries', + 'host', + 'ip_range', + 'port', + 'whitelisted_countries']), + ip_range=dict(type='str'), + application=dict(type='dict', default=None, options=application_arg_spec), + host=dict(type='str'), + port=dict(type='str'), + countries=dict(type='list'), + ) + + argument_spec = meraki_argument_spec() + argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'), + net_name=dict(type='str'), + net_id=dict(type='str'), + rules=dict(type='list', default=None, elements='dict', options=rule_arg_spec), + categories=dict(type='bool'), + ) + + # seed the result dict in the object + # we primarily care about changed and state + # change is if this module effectively modified the target + # state will include any data that you want your module to pass back + # for consumption, for example, in a subsequent task + result = dict( + changed=False, + ) + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True, + ) + meraki = MerakiModule(module, function='mx_l7_firewall') + + # check for argument completeness + if meraki.params['rules']: + for rule in meraki.params['rules']: + if rule['type'] == 'application' and rule['application'] is None: + meraki.fail_json(msg="application argument is required when type is application.") + elif rule['type'] == 'application_category' and rule['application'] is None: + meraki.fail_json(msg="application argument is required when type is application_category.") + elif rule['type'] == 'blacklisted_countries' and rule['countries'] is None: + meraki.fail_json(msg="countries argument is required when type is blacklisted_countries.") + elif rule['type'] == 'host' and rule['host'] is None: + meraki.fail_json(msg="host argument is required when type is host.") + elif rule['type'] == 'port' and rule['port'] is None: + meraki.fail_json(msg="port argument is required when type is port.") + elif rule['type'] == 'whitelisted_countries' and rule['countries'] is None: + meraki.fail_json(msg="countries argument is required when type is whitelisted_countries.") + + meraki.params['follow_redirects'] = 'all' + + query_urls = {'mx_l7_firewall': '/networks/{net_id}/l7FirewallRules/'} + query_category_urls = {'mx_l7_firewall': '/networks/{net_id}/l7FirewallRules/applicationCategories'} + update_urls = {'mx_l7_firewall': '/networks/{net_id}/l7FirewallRules/'} + + meraki.url_catalog['get_all'].update(query_urls) + meraki.url_catalog['get_categories'] = (query_category_urls) + meraki.url_catalog['update'] = update_urls + + payload = None + + # manipulate or modify the state as needed (this is going to be the + # part where your module will do what it needs to do) + org_id = meraki.params['org_id'] + orgs = None + if org_id is None: + orgs = meraki.get_orgs() + for org in orgs: + if org['name'] == meraki.params['org_name']: + org_id = org['id'] + net_id = meraki.params['net_id'] + if net_id is None: + if orgs is None: + orgs = meraki.get_orgs() + net_id = meraki.get_net_id(net_name=meraki.params['net_name'], + data=meraki.get_nets(org_id=org_id)) + + if meraki.params['state'] == 'query': + if meraki.params['categories'] is True: # Output only applications + meraki.result['data'] = get_applications(meraki, net_id) + else: + meraki.result['data'] = restructure_response(get_rules(meraki, net_id)) + elif meraki.params['state'] == 'present': + rules = get_rules(meraki, net_id) + path = meraki.construct_path('get_all', net_id=net_id) + if meraki.params['rules']: + payload = {'rules': []} + for rule in meraki.params['rules']: + payload['rules'].append(assemble_payload(meraki, net_id, rule)) + else: + payload = dict() + + ''' + The rename_* functions are needed because the key is id and + is_update_required() by default ignores id. + ''' + rules = rename_id_to_appid(rules) + payload = rename_id_to_appid(payload) + if meraki.is_update_required(rules, payload): + rules = rename_appid_to_id(rules) + payload = rename_appid_to_id(payload) + if meraki.module.check_mode is True: + response = restructure_response(payload) + diff = recursive_diff(restructure_response(rules), response) + meraki.result['diff'] = {'before': diff[0], + 'after': diff[1], + } + meraki.result['data'] = response + meraki.result['changed'] = True + meraki.exit_json(**meraki.result) + response = meraki.request(path, method='PUT', payload=json.dumps(payload)) + response = restructure_response(response) + if meraki.status == 200: + diff = recursive_diff(restructure_response(rules), response) + meraki.result['diff'] = {'before': diff[0], + 'after': diff[1], + } + meraki.result['data'] = response + meraki.result['changed'] = True + else: + rules = rename_appid_to_id(rules) + payload = rename_appid_to_id(payload) + if meraki.module.check_mode is True: + meraki.result['data'] = rules + meraki.result['changed'] = False + meraki.exit_json(**meraki.result) + meraki.result['data'] = payload + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + meraki.exit_json(**meraki.result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/meraki_mx_l7_firewall/aliases b/test/integration/targets/meraki_mx_l7_firewall/aliases new file mode 100644 index 00000000000000..06fe32bc66d06e --- /dev/null +++ b/test/integration/targets/meraki_mx_l7_firewall/aliases @@ -0,0 +1,2 @@ +unsupported + diff --git a/test/integration/targets/meraki_mx_l7_firewall/tasks/main.yml b/test/integration/targets/meraki_mx_l7_firewall/tasks/main.yml new file mode 100644 index 00000000000000..bb4c6fc5941e7a --- /dev/null +++ b/test/integration/targets/meraki_mx_l7_firewall/tasks/main.yml @@ -0,0 +1,7 @@ +# Test code for the Meraki Organization module +# Copyright: (c) 2018, Kevin Breit (@kbreit) + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: Run test cases + include: tests.yml ansible_connection=local \ No newline at end of file diff --git a/test/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml b/test/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml new file mode 100644 index 00000000000000..72adef560ec07d --- /dev/null +++ b/test/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml @@ -0,0 +1,494 @@ +# Test code for the Meraki Organization module +# Copyright: (c) 2018, Kevin Breit (@kbreit) + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- block: + - name: Test an API key is provided + fail: + msg: Please define an API key + when: auth_key is not defined + + - name: Create network + meraki_network: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + type: appliance + + - name: Query firewall rules + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: query + register: query + + - debug: + var: query + + - assert: + that: + - query.data is defined + + - name: Query firewall application categories + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: query + categories: yes + register: query_categories + + - assert: + that: + - query_categories.data is defined + + - name: Create firewall rule for IP range in check mode + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_check + check_mode: yes + + - debug: + var: create_ip_range_check + + - assert: + that: + - create_ip_range_check is changed + + - name: Create firewall rule for IP range + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range + + - debug: + var: create_ip_range + + - assert: + that: + - create_ip_range is changed + - create_ip_range.data.rules | length == 1 + + - name: Create firewall rule for IP range with idempotency with check mode + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent_check + check_mode: yes + + - assert: + that: + - create_ip_range_idempotent_check is not changed + + - name: Create firewall rule for IP range with idempotency + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent + + - assert: + that: + - create_ip_range_idempotent is not changed + + - name: Create firewall rule for IP and port + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.1:23 + register: create_ip_range_port + + - debug: + var: create_ip_range_port + + - assert: + that: + - create_ip_range_port is changed + + - name: Create firewall rule for IP range + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range + + - debug: + var: create_ip_range + + - assert: + that: + - create_ip_range is changed + - create_ip_range.data.rules | length == 1 + + - name: Create firewall rule for IP range with idempotency with check mode + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent_check + check_mode: yes + + - assert: + that: + - create_ip_range_idempotent_check is not changed + + - name: Create firewall rule for IP range with idempotency + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent + + - assert: + that: + - create_ip_range_idempotent is not changed + + - name: Create firewall rule for application + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application + application: + name: facebook + register: application_rule + + - assert: + that: + - application_rule is changed + - application_rule.data.rules is defined + + - name: Create firewall rule for application via ID + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application + application: + id: meraki:layer7/application/205 + register: application_rule_id + + - assert: + that: + - application_rule_id is changed + + - name: Create firewall rule for invalid application + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application + application: + name: ansible + register: application_rule_invalid + ignore_errors: yes + + - name: Create firewall rule for application category + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + application: + name: Advertising + register: application_category_rule + + - debug: + var: application_category_rule + + - assert: + that: + - application_category_rule is changed + + - name: Create firewall rule for application category with ID and conflict + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + application: + id: meraki:layer7/category/27 + register: application_category_rule_id_conflict + + - assert: + that: + - application_category_rule_id_conflict is not changed + + - name: Create firewall rule for application category with ID + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + application: + id: meraki:layer7/category/24 + register: application_category_rule_id + + - assert: + that: + - application_category_rule_id is changed + + - name: Create firewall rule for host + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: host + host: asdf.com + register: host_rule + + - assert: + that: + - host_rule is changed + + - name: Create firewall rule for port + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: port + port: 1234 + register: port_rule + + - assert: + that: + - port_rule is changed + + - name: Create firewall rule for blacklisted countries + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: blacklisted_countries + countries: + - CA + - AX + register: blacklist_countries + + - assert: + that: + - blacklist_countries is changed + + - name: Create firewall rule for whitelisted countries + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: whitelisted_countries + countries: + - US + - FR + register: whitelist_countries + + - assert: + that: + - whitelist_countries is changed + + - name: Create firewall rule for whitelisted countries with idempotency + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: whitelisted_countries + countries: + - US + - FR + register: whitelist_countries_idempotent + + - assert: + that: + - whitelist_countries_idempotent is not changed + + - name: Create multiple firewall rules + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + application: + id: meraki:layer7/category/27 + - type: blacklisted_countries + countries: + - CN + - policy: deny + type: port + port: 8080 + register: multiple_rules + + - debug: + var: multiple_rules + + - assert: + that: + - multiple_rules.data.rules | length == 3 + - multiple_rules is changed + + ######################################### + ## Tests for argument completeness ## + ######################################### + + - name: Test whitelisted_countries incomplete arguments + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: whitelisted_countries + register: error_whitelist + ignore_errors: yes + + - assert: + that: + - 'error_whitelist.msg == "countries argument is required when type is whitelisted_countries."' + + - name: Test blacklisted_countries incomplete arguments + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: blacklisted_countries + register: error_blacklist + ignore_errors: yes + + - assert: + that: + - 'error_blacklist.msg == "countries argument is required when type is blacklisted_countries."' + + - name: Test application_category incomplete arguments + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + register: error_app_cat + ignore_errors: yes + + - assert: + that: + - 'error_app_cat.msg == "application argument is required when type is application_category."' + + - name: Test application incomplete arguments + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application + register: error_app_cat + ignore_errors: yes + + - assert: + that: + - 'error_app_cat.msg == "application argument is required when type is application."' + + - name: Test host incomplete arguments + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: host + register: error_app_cat + ignore_errors: yes + + - assert: + that: + - 'error_app_cat.msg == "host argument is required when type is host."' + + - name: Test port incomplete arguments + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: port + register: error_app_cat + ignore_errors: yes + + - assert: + that: + - 'error_app_cat.msg == "port argument is required when type is port."' + + ################# + ## Cleanup ## + ################# + + # always: + # - name: Delete network + # meraki_network: + # auth_key: '{{ auth_key }}' + # org_name: '{{test_org_name}}' + # net_name: TestNetAppliance + # state: absent + # delegate_to: localhost