summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/meraki
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cisco/meraki
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/meraki')
-rw-r--r--ansible_collections/cisco/meraki/.pytest_cache/.gitignore2
-rw-r--r--ansible_collections/cisco/meraki/.pytest_cache/CACHEDIR.TAG4
-rw-r--r--ansible_collections/cisco/meraki/.pytest_cache/README.md8
-rw-r--r--ansible_collections/cisco/meraki/.pytest_cache/v/cache/lastfailed3
-rw-r--r--ansible_collections/cisco/meraki/.pytest_cache/v/cache/nodeids16
-rw-r--r--ansible_collections/cisco/meraki/.pytest_cache/v/cache/stepwise1
-rw-r--r--ansible_collections/cisco/meraki/CHANGELOG.rst356
-rw-r--r--ansible_collections/cisco/meraki/COPYING674
-rw-r--r--ansible_collections/cisco/meraki/FILES.json1937
-rw-r--r--ansible_collections/cisco/meraki/MANIFEST.json36
-rw-r--r--ansible_collections/cisco/meraki/README.md180
-rw-r--r--ansible_collections/cisco/meraki/changelogs/.plugin-cache.yaml234
-rw-r--r--ansible_collections/cisco/meraki/changelogs/changelog.yaml410
-rw-r--r--ansible_collections/cisco/meraki/changelogs/config.yaml29
-rw-r--r--ansible_collections/cisco/meraki/contributing.md80
-rw-r--r--ansible_collections/cisco/meraki/meta/execution-environment.yml3
-rw-r--r--ansible_collections/cisco/meraki/meta/runtime.yml29
-rw-r--r--ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py0
-rw-r--r--ansible_collections/cisco/meraki/plugins/doc_fragments/meraki.py84
-rw-r--r--ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py0
-rw-r--r--ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py539
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_action_batch.py394
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_admin.py504
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py395
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_config_template.py331
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_device.py431
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_firewalled_services.py233
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_management_interface.py384
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py300
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l7_firewall.py503
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mr_radio.py490
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py662
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py221
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py744
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_list.py319
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_policies.py608
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_l3_interface.py373
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_link_aggregation.py258
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py323
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py278
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack_l3_interface.py395
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_storm_control.py201
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py680
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py302
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py371
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py272
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py377
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py475
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_malware.py264
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_nat.py679
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_network_vlan_settings.py161
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_firewall.py330
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py270
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py438
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_third_party_vpn_peers.py493
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink_bandwidth.py325
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py585
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_network.py469
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_network_settings.py337
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_organization.py242
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_snmp.py387
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py317
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py431
-rw-r--r--ansible_collections/cisco/meraki/plugins/modules/meraki_webhook_payload_template.py352
-rw-r--r--ansible_collections/cisco/meraki/scripts/sublime-build/build.py76
-rwxr-xr-xansible_collections/cisco/meraki/scripts/sublime-build/build.py.generic76
-rw-r--r--ansible_collections/cisco/meraki/scripts/sublime-build/requirements.txt1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/inventory.networking42
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/inventory.networking.template37
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/target-prefixes.network1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/tasks/main.yml325
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/tasks/main.yml428
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/aliases0
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/tasks/main.yml356
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/tasks/main.yml210
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/tasks/main.yml265
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/tests.yml234
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/tasks/main.yml298
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml212
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/tests.yml520
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/aliases0
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/tasks/main.yml166
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/aliases0
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml342
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/aliases0
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/tasks/main.yml108
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/tests.yml132
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_policies/tasks/main.yml561
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_l3_interface/tasks/main.yml236
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml130
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_ospf/tasks/main.yml141
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/tasks/main.yml215
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack_l3_interface/tasks/main.yml281
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/tasks/main.yml113
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/tasks/main.yml782
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/tasks/main.yml309
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/tests.yml330
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml161
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml369
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/aliases2
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml524
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/tasks/main.yml253
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/tasks/tests.yml363
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/aliases0
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/tasks/main.yml95
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/tasks/main.yml306
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/tasks/main.yml188
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/tasks/main.yml210
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/tasks/main.yml319
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/tasks/main.yml125
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/tasks/main.yml475
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/tasks/main.yml550
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/tasks/main.yml294
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/main.yml8
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/tests.yml149
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/tasks/main.yml290
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/tasks/main.yml747
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/tasks/main.yml228
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/aliases1
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/tests.yml426
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/aliases2
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/main.yml7
-rw-r--r--ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/tests.yml270
-rw-r--r--ansible_collections/cisco/meraki/tests/sanity/ignore-2.10.txt1
-rw-r--r--ansible_collections/cisco/meraki/tests/sanity/ignore-2.11.txt2
-rw-r--r--ansible_collections/cisco/meraki/tests/sanity/ignore-2.12.txt1
-rw-r--r--ansible_collections/cisco/meraki/tests/sanity/ignore-2.13.txt1
-rw-r--r--ansible_collections/cisco/meraki/tests/sanity/ignore-2.14.txt1
-rw-r--r--ansible_collections/cisco/meraki/tests/sanity/ignore-2.15.txt1
-rw-r--r--ansible_collections/cisco/meraki/tests/sanity/ignore-2.9.txt2
168 files changed, 34906 insertions, 0 deletions
diff --git a/ansible_collections/cisco/meraki/.pytest_cache/.gitignore b/ansible_collections/cisco/meraki/.pytest_cache/.gitignore
new file mode 100644
index 000000000..bc1a1f616
--- /dev/null
+++ b/ansible_collections/cisco/meraki/.pytest_cache/.gitignore
@@ -0,0 +1,2 @@
+# Created by pytest automatically.
+*
diff --git a/ansible_collections/cisco/meraki/.pytest_cache/CACHEDIR.TAG b/ansible_collections/cisco/meraki/.pytest_cache/CACHEDIR.TAG
new file mode 100644
index 000000000..fce15ad7e
--- /dev/null
+++ b/ansible_collections/cisco/meraki/.pytest_cache/CACHEDIR.TAG
@@ -0,0 +1,4 @@
+Signature: 8a477f597d28d172789f06886806bc55
+# This file is a cache directory tag created by pytest.
+# For information about cache directory tags, see:
+# https://bford.info/cachedir/spec.html
diff --git a/ansible_collections/cisco/meraki/.pytest_cache/README.md b/ansible_collections/cisco/meraki/.pytest_cache/README.md
new file mode 100644
index 000000000..b89018ced
--- /dev/null
+++ b/ansible_collections/cisco/meraki/.pytest_cache/README.md
@@ -0,0 +1,8 @@
+# pytest cache directory #
+
+This directory contains data from the pytest's cache plugin,
+which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
+
+**Do not** commit this to version control.
+
+See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
diff --git a/ansible_collections/cisco/meraki/.pytest_cache/v/cache/lastfailed b/ansible_collections/cisco/meraki/.pytest_cache/v/cache/lastfailed
new file mode 100644
index 000000000..2d9329df1
--- /dev/null
+++ b/ansible_collections/cisco/meraki/.pytest_cache/v/cache/lastfailed
@@ -0,0 +1,3 @@
+{
+ "tests/unit/test_meraki_ms_switchport.py": true
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/meraki/.pytest_cache/v/cache/nodeids b/ansible_collections/cisco/meraki/.pytest_cache/v/cache/nodeids
new file mode 100644
index 000000000..741ac239b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/.pytest_cache/v/cache/nodeids
@@ -0,0 +1,16 @@
+[
+ "tests/unit/test_meraki_admin.py::test_find_admin_fail",
+ "tests/unit/test_meraki_admin.py::test_find_admin_success",
+ "tests/unit/test_meraki_admin.py::test_get_admin_id_by_email_not_found",
+ "tests/unit/test_meraki_admin.py::test_get_admin_id_by_email_success",
+ "tests/unit/test_meraki_admin.py::test_get_admin_id_by_emailnot_found",
+ "tests/unit/test_meraki_admin.py::test_get_admin_id_by_name_success",
+ "tests/unit/test_meraki_admin.py::test_get_admin_id_by_namenot_found",
+ "tests/unit/test_meraki_admin.py::test_get_admin_id_not_found",
+ "tests/unit/test_meraki_admin.py::test_get_admin_id_success",
+ "tests/unit/test_meraki_admin.py::test_get_admin_not_found",
+ "tests/unit/test_meraki_admin.py::test_get_admin_success",
+ "tests/unit/test_meraki_admin.py::test_get_admins_success",
+ "tests/unit/test_meraki_ms_switchport.py::test_bye",
+ "tests/unit/test_meraki_ms_switchport.py::test_hello"
+] \ No newline at end of file
diff --git a/ansible_collections/cisco/meraki/.pytest_cache/v/cache/stepwise b/ansible_collections/cisco/meraki/.pytest_cache/v/cache/stepwise
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/ansible_collections/cisco/meraki/.pytest_cache/v/cache/stepwise
@@ -0,0 +1 @@
+[] \ No newline at end of file
diff --git a/ansible_collections/cisco/meraki/CHANGELOG.rst b/ansible_collections/cisco/meraki/CHANGELOG.rst
new file mode 100644
index 000000000..2aed51c01
--- /dev/null
+++ b/ansible_collections/cisco/meraki/CHANGELOG.rst
@@ -0,0 +1,356 @@
+==========================
+Cisco.Meraki Release Notes
+==========================
+
+.. contents:: Topics
+
+
+v2.15.1
+=======
+
+Bugfixes
+--------
+
+- Corrects constraints applied to local and remote status page settings to align with API behaviour (https://github.com/CiscoDevNet/ansible-meraki/issues/437)
+- Enables meraki_network query by net_id (https://github.com/CiscoDevNet/ansible-meraki/issues/441)
+- Resolved an issue where an empty response from the API triggered an exception in module meraki_webhook (https://github.com/CiscoDevNet/ansible-meraki/issues/433)
+- Resolves issues with meraki_webhook shared_secret defaulting to null; (https://github.com/CiscoDevNet/ansible-meraki/issues/439); Also adds Test Coverage for shared secret idempotency and resolves test file lint issues.
+
+v2.15.0
+=======
+
+Minor Changes
+-------------
+
+- New module - meraki_network_settings - Configure detailed settings of a network.
+
+Bugfixes
+--------
+
+- Resolved issue
+- Update pipeline to use newer version of action to detect changed files.
+- meraki_alert - Fix situation where specifying emails may crash.
+- meraki_mx_site_to_site_vpn - Check mode should no longer apply changes when enabled.
+
+Known Issues
+------------
+
+- meraki_network - Updated documentation for `local_status_page_enabled` and `remote_status_page_enabled` as these no longer work.
+
+v2.14.0
+=======
+
+Minor Changes
+-------------
+
+- meraki_webhook - Add payload template parameter
+
+Bugfixes
+--------
+
+- Fix checkmode on merak webhook payload template update
+- meraki_webhook - First error when updating URL in a webhook
+
+v2.13.0
+=======
+
+Major Changes
+-------------
+
+- meraki_mr_l7_firewall - New module
+
+v2.12.0
+=======
+
+Major Changes
+-------------
+
+- meraki_webhook_payload_template - New module
+
+Bugfixes
+--------
+
+- Update defaults in documentation for new sanity tests
+- meraki_device - Fix URL for LLDP and CDP lookups
+
+v2.11.0
+=======
+
+Minor Changes
+-------------
+
+- Add GPLv3 license. Always was GPLv3, but didn't have the file.
+- Change shebang in Sublime utils to point to env instead of direct to the path
+- meraki_alert - Change type for opbject to alert_type in examples
+- meraki_ms_access_policies - New module to create, delete, update Access Policies in the Switch settings
+- meraki_ssid - Add support for `ap_availability_tags`.
+- meraki_ssid - Add support for `available_on_all_aps`
+- meraki_ssid - Add support for `lan_isolation_enabled`.
+- meraki_ssid - Add support for `visible`.
+
+v2.10.1
+=======
+
+Minor Changes
+-------------
+
+- Change shebang in Sublime utils to point to env instead of direct to the path
+
+v2.10.0
+=======
+
+Minor Changes
+-------------
+
+- meraki_network - Add support for `copy_from_network_id`.
+
+v2.9.0
+======
+
+Bugfixes
+--------
+
+- meraki_switchport - Setting VLAN to 0 on trunk port clears the VLAN.
+
+v2.8.0
+======
+
+Minor Changes
+-------------
+
+- meraki_action_batch - New module for CRUD operations on Meraki Action Batches
+- meraki_switchport - Add support for flexible stacking
+
+v2.7.0
+======
+
+Minor Changes
+-------------
+
+- meraki_mx_network_vlan_settings - New module to enable or disable VLANs on a network
+- meraki_mx_third_party_vpn_peers - New module for managing third party VPM peers
+
+Bugfixes
+--------
+
+- meraki_mx_static_route - Add support for gateway_vlan_id otherwise requests could error
+
+v2.6.2
+======
+
+Minor Changes
+-------------
+
+- Add execution-environment.yml in meta as the base to a Meraki ee
+- meraki_network - Add Products to net_type list
+
+Bugfixes
+--------
+
+- meraki_alert - Updates now properly set default destination webhook
+- meraki_syslog - Fix crash due to incorrect dictionary reference
+
+v2.6.1
+======
+
+Minor Changes
+-------------
+
+- meraki_ssid - Add support for enterprise_admin_access and splash_guest_sponsor_domains with the latter required for creating a sponsor portal.
+
+Bugfixes
+--------
+
+- meraki_mr_rf_profile - Fix issue with idempotency and creation of RF Profiles by name only
+- meraki_syslog - Improve reliability for multiple roles or capitalization.
+
+v2.6.0
+======
+
+Major Changes
+-------------
+
+- meraki_mr_radio - New module
+
+Minor Changes
+-------------
+
+- meraki_mx_l7_firewall - Allow passing an empty ruleset to delete all rules
+- meraki_utils - Add debugging output for failed socket connections
+
+Bugfixes
+--------
+
+- meraki_mr_ssid - Fix issue with SSID removal idempotency when ID doesn't exist
+
+v2.5.0
+======
+
+Minor Changes
+-------------
+
+- meraki_mr_l3_firewall - Return each MR L3 firewall rule's values in lowercase.
+- meraki_mr_ssid - Add support for radius_proxy_enabled SSID setting.
+- meraki_mx_l3_firewall - Return each MX L3 firewall rule's values in lowercase.
+- meraki_mx_vlan - Fix dhcp_boot_options_enabled parameter
+
+v2.4.2
+======
+
+Bugfixes
+--------
+
+- Fix some flake8 sanity errors as reported by Ansible Galaxy. Should be no functional change.
+
+v2.4.0
+======
+
+Minor Changes
+-------------
+
+- meraki_mx_switchport - Improve documentation for response
+
+Bugfixes
+--------
+
+- Allow a state of absent in voice vlan to allow the value to be nulled out(https://github.com/CiscoDevNet/ansible-meraki/issues/238)
+
+v2.3.1
+======
+
+Bugfixes
+--------
+
+- meraki_ms_switchport - link_negotiation choice for 100 Megabit Auto is incorrect causing failures. (https://github.com/CiscoDevNet/ansible-meraki/issues/235).
+
+v2.3.0
+======
+
+Minor Changes
+-------------
+
+- meraki_ms_switchport - Adding additional functionality to support the access_policy_types "MAC allow list" and "Sticky MAC allow list" port security configuration options. (https://github.com/CiscoDevNet/ansible-meraki/issues/227).
+- meraki_mx_intrusion_prevention - Rename message to rule_message to avoid conflicts with internal Ansible variables.
+
+Bugfixes
+--------
+
+- meraki_ms_switchport - access_policy_types choices are incorrect causing failures. (https://github.com/CiscoDevNet/ansible-meraki/issues/227).
+
+v2.2.1
+======
+
+Bugfixes
+--------
+
+- meraki_mx_content_filtering - Fix crash with idempotent condition due to improper sorting
+
+v2.2.0
+======
+
+Minor Changes
+-------------
+
+- meraki_network - Update documentation to show querying of local or remote settings.
+- meraki_ssid - Add Cisco ISE as a splash page option.
+
+Bugfixes
+--------
+
+- meraki_network - Fix bug where local or remote settings always show changed.
+
+v2.1.3
+======
+
+Bugfixes
+--------
+
+- meraki_device - Support pagination. This allows for more than 1,000 devices to be listed at a time.
+- meraki_network - Support pagination. This allows for more than 1,000 networks to be listed at a time.
+
+v2.1.2
+======
+
+Bugfixes
+--------
+
+- Remove test output as it made the collection, and Ansible, huge.
+
+v2.1.1
+======
+
+Bugfixes
+--------
+
+- meraki_management_interface - Fix crash when modifying a non-MX management interface.
+
+v2.1.0
+======
+
+New Modules
+-----------
+
+- meraki_alert - Manage alerts in the Meraki cloud
+- meraki_mx_l2_interface - Configure MX layer 2 interfaces
+
+v2.0.0
+======
+
+Major Changes
+-------------
+
+- Rewrite requests method for version 1.0 API and improved readability
+- meraki_mr_rf_profile - Configure wireless RF profiles.
+- meraki_mr_settings - Configure network settings for wireless.
+- meraki_ms_l3_interface - New module
+- meraki_ms_ospf - Configure OSPF.
+
+Minor Changes
+-------------
+
+- meraki - Add optional debugging for is_update_required() method.
+- meraki_admin - Update endpoints for API v1
+- meraki_alert - Manage network wide alert settings.
+- meraki_device - Added query parameter
+- meraki_intrusion_prevention - Change documentation to show proper way to clear rules
+- meraki_malware - Update documentation to show how to allow multiple URLs at once.
+- meraki_mx_l2_interface - Configure physical interfaces on MX appliances.
+- meraki_mx_uplink - Renamed to meraki_mx_uplink_bandwidth
+- meraki_ssid - Add `WPA3 Only` and `WPA3 Transition Mode`
+- meraki_switchport - Add support for `access_policy_type` parameter
+
+Breaking Changes / Porting Guide
+--------------------------------
+
+- meraki_device - Changed tags from string to list
+- meraki_device - Removed serial_lldp_cdp parameter
+- meraki_device - Removed serial_uplink parameter
+- meraki_intrusion_prevention - Rename whitedlisted_rules to allowed_rules
+- meraki_mx_l3_firewall - Rule responses are now in a `rules` list
+- meraki_mx_l7_firewall - Rename blacklisted_countries to blocked_countries
+- meraki_mx_l7_firewall - Rename whitelisted_countries to allowed_countries
+- meraki_network - Local and remote status page settings cannot be set during network creation
+- meraki_network - `disableRemoteStatusPage` response is now `remote_status_page_enabled`
+- meraki_network - `disable_my_meraki_com` response is now `local_status_page_enabled`
+- meraki_network - `disable_my_meraki` has been deprecated
+- meraki_network - `enable_my_meraki` is now called `local_status_page_enabled`
+- meraki_network - `enable_remote_status_page` is now called `remote_status_page_enabled`
+- meraki_network - `enabled` response for VLAN status is now `vlans_enabled`
+- meraki_network - `tags` and `type` now return a list
+- meraki_snmp - peer_ips is now a list
+- meraki_switchport - `access_policy_number` is now an int and not a string
+- meraki_switchport - `tags` is now a list and not a string
+- meraki_webhook - Querying test status now uses state of query.
+
+Security Fixes
+--------------
+
+- meraki_webhook - diff output may show data for values set to not display
+
+Bugfixes
+--------
+
+- Remove unnecessary files from the collection package, significantly reduces package size
+- meraki_admin - Fix error when adding network privileges to admin using network name
+- meraki_switch_stack - Fix situation where module may crash due to switch being in or not in a stack already
+- meraki_webhook - Proper response is shown when creating webhook test
diff --git a/ansible_collections/cisco/meraki/COPYING b/ansible_collections/cisco/meraki/COPYING
new file mode 100644
index 000000000..f288702d2
--- /dev/null
+++ b/ansible_collections/cisco/meraki/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/ansible_collections/cisco/meraki/FILES.json b/ansible_collections/cisco/meraki/FILES.json
new file mode 100644
index 000000000..432cbceef
--- /dev/null
+++ b/ansible_collections/cisco/meraki/FILES.json
@@ -0,0 +1,1937 @@
+{
+ "files": [
+ {
+ "name": ".",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/CACHEDIR.TAG",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "37dc88ef9a0abeddbe81053a6dd8fdfb13afb613045ea1eb4a5c815a74a3bde4",
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/README.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "73fd6fccdd802c419a6b2d983d6c3173b7da97558ac4b589edec2dfe443db9ad",
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/.gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3ed731b65d06150c138e2dadb0be0697550888a6b47eb8c45ecc9adba8b8e9bd",
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/v",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/v/cache",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/v/cache/nodeids",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ca80dcea77ed1e1fed033f0c8897f5e5fe2d3bdc77edb670654c2af60218b61a",
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/v/cache/lastfailed",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "21fc740c26b5dbc5db048bd81785b158ead820252b8aa5606170c410a179a5df",
+ "format": 1
+ },
+ {
+ "name": ".pytest_cache/v/cache/stepwise",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
+ "format": 1
+ },
+ {
+ "name": "plugins",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/meraki.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ebda9dda820d58025091f55f0b3bd99c824b6a0b18cf5374dbae8420e007227b",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/network",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/network/meraki",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/network/meraki/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/network/meraki/meraki.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f53700bacf722e6e132ce5893f8005b7837987499c4f678779546eadeef8b69f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_organization.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5f907f7bd6c204c0b14d1db52ed9b0f95b09e670d3ea7743f63537ef53810464",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_l3_interface.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bd59734f9d396f039f29596887969bfac7ae3dd299d483c70c70a4d22b36c52f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_action_batch.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "adb625b488f123202a6c5fb90d9368f220dbec040a56b62d859b0cb5bc65410c",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_network_vlan_settings.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "10e67065ebc8085711fccfafbf7ff5ce804f6ade0766afbf403b723d5009a2ac",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mr_settings.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0ffd0027a102dd8cdb7c0b1abb7a6b808dac550998f9d8d9032f9e795ccd7d45",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_config_template.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "08ad9de33ead9e13e3ed3d88544ba05ddfd95ae6c61325d06bc4e97651ef99b5",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_device.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9f4a83ac53ad9787150d449b0cd68510ca2fd89a0405d2cd79cec5abaefb6c63",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_ospf.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e8d1150a1f020b82d3a6ea1738c916c1dcb7b6cab12fb4c3f03990bbd22d9ee2",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_network.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b293a4f520c1225cb01d2c79ebc75c8f780a667c7cdb10ef91a4ff54075adcfa",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_site_to_site_vpn.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f4a005f9d366bdbe7a4e4f2ab4f6ab3e6c84e444bcdf66abb6ec1a627dbabfd5",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_switchport.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "06e7e243b1a964ff00415e5072408bc395a1908cedcc0cff9fa7c3a401e335ed",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mr_l3_firewall.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5627bb23a4831184116f189b60018f02a13623cfbc4d9b6d0c84944b78ee6aa8",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_third_party_vpn_peers.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "56fd86cb1c75b11619f86881236a50567660159182d9e2ffee2dfb06c4e843cf",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_storm_control.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a46f5a974df824bb1f719940c0528ede43ccecaba64fbf50bdb3375413850d57",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_site_to_site_firewall.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3e6642b28ed148788356bf28a7d1d2a4b63fa13bfb37a6877c7376b5bb2c7e3",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_l3_firewall.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fb91983b7fcb584119b473e2488640f64dab06911a8d8e429bffbe43cabe0acb",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_content_filtering.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d5940c8d47e6fe6934b240fc4fdf95fea9514ec8b9f30c940c8ff08a8c9dfb62",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_uplink_bandwidth.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e2cab9c6e4a22b513a34f7fba0ada79d209c0293d0eb583c03b663bc5f591f5d",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mr_ssid.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2f70cb32e63b271c6605123a416cbdb599563a01eb46b94e3ab4b8f0128f7091",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_intrusion_prevention.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b320578369fa53e45a8454033dcd73fcc59a5c1ae08a51e7a800a4d7a69f024b",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mr_rf_profile.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3f5b6de01d4a9c61ea9b2ccdd0f7d8113febdff50a89759a90d5c0ae573bd35e",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_malware.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fbe45461689899351b41f387d612000f0ebe03e9666537a1abcc16d2831a9351",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_vlan.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a5cdbcf02770ebb76acf501305289208223ae05e6a2a922a2692ee473c9256c5",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_firewalled_services.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0613cf3fc9bdd2e51cde3b9b32cbf94a6c764d27610ae0a1b208384dcdf7ebd7",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_webhook.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6dab3f4bf81fe90474e0aea4f4d47808c9393b72ea97aed95553e44927fa18af",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_network_settings.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "95b4a5e274005ff090b4bc0cbcd5e8704f33187e86b5d09ffda59753fd63abff",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_admin.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "99c71762a628869683d836a105a341fe005e7e53ebfad49d4f839e50e3d20f0f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_l2_interface.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7e5171a739285a555e109c8737edd40eb9835c9dc308b06c3e342676af0e3e86",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mr_l7_firewall.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8cd6b71217334df944114e2d0bd1610d9bad7bc33e658a1fd8d445d01781d90e",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_nat.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6c5169c46925e2b277c46180f28196b0f9c2b69336af33b85ac925c3b5b4f97d",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_webhook_payload_template.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1f9f98abc25470e344529682d33d5d99f4448de91a4cdc8ea953767633c643b0",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_access_policies.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "37989d7fbae79b5c7c9768269c6ceb565b2da8c4341105e131e89396e6c3785f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_stack_l3_interface.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1276aff5f145d567a897a0db60a042dc79846ae82e8204b845cba1a02030fba6",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_management_interface.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b9ac21a825768548919f086c14f49e4073199f9f3f1c971370cac6007c345961",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mr_radio.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bee57acb120e76e250df2717b3d08682e6d9eaf157d6324ecacdf7709627ff05",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_alert.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7897d49064b123a75b97ded232651bf2bbadd0702fbc99f833f33f02c977db33",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_access_list.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a8de5fc940e2c036bbffbc5f5fb818066ae1ed7cc3b9c6d5f720d861dfd7a863",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_link_aggregation.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ae9bd2aecf70cb11de9153bbd7b861b2dfefe3d2d598f326bc10517d9b5db622",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_ms_stack.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "75b84e56c88fd376b25242ef55ef816ebc3d6e7759a43948937054a359d6eb73",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_l7_firewall.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "78126a8d150f02210c2725488f284a98dd0a7241a21815a3873f77d5eea5611c",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_syslog.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d019f5f035dfbfe712eed862065491d4b9fc820338c44add24c59bfa9d5513d1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_mx_static_route.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ba48e990113b163004545ebba0d726f8a6d06a2b66f6e038e18023596c390ac1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/meraki_snmp.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "70937b2aa262d6a16dce79265106dff2316135c3b034b7c030796267e3c6466f",
+ "format": 1
+ },
+ {
+ "name": "tests",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/unit",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_policies",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_policies/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_policies/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "856f355608a6c2a3209656ae8756470a411ec7316cd134738adee4b75cdc828c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_nat",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_nat/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_nat/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "efa894c594a21000062c96fc37d06a7f545ade22c3f3068f28d2ebb477765890",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_nat/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7d721fb7675ced2f9f23e2b731c75c625e3d550de22eed6a6e188fb8bcc20591",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_nat/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l7_firewall",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l7_firewall/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "aaf76d252cac297488358809213bd809203ba7c21a98dd8e48a172e07f949591",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l7_firewall/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "617754c4028cc50da675746f5b74f3f2282a7136d8b3808da969391cb9bbce7d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l7_firewall/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a2f0223f94e7b3ac8a2fc3e175f868505568612d55d9b3b30631ab93fa118bd3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a8efba141b16c68000d03c7e0f30d70ea456f882099f4f316debc766fbc6f51f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_ssid",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_static_route",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_static_route/meraki_static_route",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_static_route/meraki_static_route/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_static_route/meraki_static_route/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b40e7bde195cf837d827e95fedcfd76d4c45976ce7e3122068d93d1d86a000bb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_static_route/meraki_static_route/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_auth_user",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_syslog",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_syslog/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_syslog/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5b0987de1d2a75ecba4001294bb13ab960ac6dd8d33721ecde955a2cb6880c13",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_syslog/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_switchport",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_switchport/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_switchport/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2401611b0da476c2a5da53c9e4a93752cfad773d57203b1bac05c86e1a2bbc0c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_switchport/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_storm_control",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_storm_control/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_storm_control/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5e8b56438a594fcf53a98e0900d6dc11ad3763698a5a2ba81683f973c16b673a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_storm_control/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_admin",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_admin/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_admin/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "561dfe521d0fa9715d22b5d24d9ce8dff9305acc5a8e993a4cbd52a33f8816fb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_admin/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a98d2f88f6624a3d94454fe5f101bc43fba692330307ba636df8dde3dd38b4ee",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "53fa9852c9f50ce3ef61cdbfbd87831e062cfd27869b6607c27c7a41225148a7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook_payload_template",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook_payload_template/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook_payload_template/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c1a6bdf1780513d1bd202c425eb378e308f5a380127458b1f55520c2ac372564",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook_payload_template/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "275dcba4aa10a765041ab64225f064ddc039c26f5b8fc25a62226aa7a52fe913",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_webhook_payload_template/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a2f0223f94e7b3ac8a2fc3e175f868505568612d55d9b3b30631ab93fa118bd3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_snmp",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_snmp/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_snmp/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1868354c9d8bf9432572c0042c3772c3a2a0564e597a0b1baba53985cf3b47b8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_snmp/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l7_firewall",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l7_firewall/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l7_firewall/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fd75bc12441f90562c6de43ddad023f5892dce3b2c1ec4d46cf149aeb22f5857",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l7_firewall/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "275dcba4aa10a765041ab64225f064ddc039c26f5b8fc25a62226aa7a52fe913",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l7_firewall/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_vlan",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_vlan/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_vlan/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7dc6069b800d4a0b42f25d2cebba83a192857ae837d6b6006a8bcde384f5ab30",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_vlan/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_ospf",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_ospf/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_ospf/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "14e82e32c22d73d9514eadffac8756ac31d4cda45227d2f97ab324075127cea5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_alert",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_alert/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_alert/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b35afb700cca80628f5fe5a1f672a82c342f5f14c7ef68eaa5b63906f730ce89",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_alert/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_config_template",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_config_template/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_config_template/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "975aea9880268c4f0db375cb10f6fa63bd2cee08c0089f71408249b0ce11e769",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_config_template/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_rf_profile",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_rf_profile/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f934a85d93d1411ac997ec2dd225c7e475240e92c9b5ebdeb68b80df3fbf8202",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_rf_profile/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_settings",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_settings/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_settings/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "70e83a4986fbc422d3718b4dc0495d079f60a658aef1efcd122d21a244cd1a87",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_settings/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_content_filtering",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_content_filtering/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_content_filtering/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5abe3a1d6fd17cd1ffc60b6512536bce22275957df0e3b95aa547adf083c50fe",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_content_filtering/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_stack",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_stack/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_stack/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b82d33b125db4f277ed905409581ec3bd6bc564ab358429f8b20ac8c8fe70a37",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_stack/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_network_vlan_settings",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_network_vlan_settings/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_network_vlan_settings/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "df37732e269ff88d20786d2f5650b9fdfdbd0e9dc62908420abd8644f9ef9ce1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_network_vlan_settings/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_intrusion_prevention",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_intrusion_prevention/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_intrusion_prevention/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d5b2c8198b06ae90ba61842d135f8f57de3aa9f283e9fb03d769e6a0ab943306",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_intrusion_prevention/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "69bdb506dd6aca6d6d77003468945c223df3e569568b27d325136d5957aa43c2",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_intrusion_prevention/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_stack_l3_interface",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_stack_l3_interface/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_stack_l3_interface/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5ad9a9322588cde46c02fabedcbb1dfffa5ab6a2417b1b9fd7ef84e56aac30c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_organization",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_organization/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_organization/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c066b25f1d46b51fa6f74fac91376b4d9911bc71a7e85cfddfd61b8d9f3abba3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_organization/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "197afdd10ec2d16f63702c979136a54bca85025209ffdcd516db712ffbe89cde",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_organization/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_uplink_bandwidth",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_uplink_bandwidth/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_uplink_bandwidth/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d4abc0313199c76186bb2b4494019521d34531934cf2400edcb1511a703ddb1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_uplink_bandwidth/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_management_interface",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_management_interface/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_management_interface/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "86c56e23849d4da583ae4fc1a0c2e018695a0a165e58a4d3727727259032eb0b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_management_interface/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_vpn",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_vpn/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_vpn/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "28e6673dae8235b61aa63f3c7d6cf2c73e39b15d4abb35490be9b6648e9d2b50",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_vpn/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_firewalled_services",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_firewalled_services/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_firewalled_services/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bac4189bbe118dd6dfbc89c089e4495279aee22a178be5f30ef6c0801fcc25cd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_firewalled_services/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1e83581a07172337934cc5c220a258defe30f04086b0c9e6f96f4ec6015fbcce",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_firewalled_services/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_firewall",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_firewall/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_firewall/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d1c0d85ff24d1b9836e7f6075a1a582e7e343c6108e805a8ca6c6618a791cce9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_site_to_site_firewall/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l3_firewall",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l3_firewall/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7be2325d7f7cd1793d5529c8e730524fe5726d17265d34b605c6fe6e718ab3bb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_l3_firewall/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_action_batch",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_action_batch/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_action_batch/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c4412dc662eb207513564d3b3d35708b4108816e0b52d6a4288ce223b45b6fd9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_action_batch/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ssid",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ssid/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ssid/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "05c17e347a8d52c5ee40ff5fd4906399d379ec0d2a4ea95bf4528f42a7013901",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ssid/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_device",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_device/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_device/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "605d45503b2e304d9b93705d9c62b5b5ca3158c0353e98da8481d53e44f08327",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_device/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "60945d49535300be8e42108658dba31fcd5d665fc40d6f186798e7e0682320ae",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_malware",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_malware/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_malware/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ea3aa66ab9f472e5a48fc0285d52cd41baa5daa9c8b2559996b3ae10020860c2",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_malware/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_radio",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_radio/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_radio/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "42b44f49aef11977ca604f1e11f3d968fe7135fd3dadc9319b6cc1cfa704b8fb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mr_radio/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network_settings",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network_settings/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network_settings/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6561f7c5b6942fda2d2e5e35980d34231276a943d44dbcdc5fe13eb5f1571126",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_network_settings/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l3_firewall",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l3_firewall/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "57584287114e423c7d70a8d7071714ec3334698e84f1a88a2f382f3d11d9883f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l3_firewall/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_mtu",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l2_interface",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l2_interface/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5a8e9db5fc043cb55bff0f5d3540553265f7828aaab4cc7d10bc311de6ac4e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_third_party_vpn_peers",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_third_party_vpn_peers/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_third_party_vpn_peers/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "851b83d11acb9bea439caac42131aeb6af7c936acd9c26e0be5e3f277f21f3df",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_mx_third_party_vpn_peers/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_list",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_list/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_list/tasks/tests.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "efea3044d70f3c1b5264a45ab3de5ff279e47df5a38ff2f0c7229cd6839b8d74",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_list/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "53fa9852c9f50ce3ef61cdbfbd87831e062cfd27869b6607c27c7a41225148a7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_access_list/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_l3_interface",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_l3_interface/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_l3_interface/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2168b11d79e603877d3a009c00407323dca32b886ba11fa5b8650d14815c5a5a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_link_aggregation",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_link_aggregation/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "74e0d9200b5312394a7b8c4bcd99829311354dd057829adf3955ba56e501df79",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/meraki_ms_link_aggregation/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/target-prefixes.network",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b8a734712fe5c9c772fd93a16a62bc6239cc4ed638239fedf060285a4aa6a59c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/inventory.networking.template",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d54a2c3f4b1395efbc502ccedb40991e10a5f1498e31a1204daf4d90db63169",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/inventory.networking",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2b8976aa168b476b987037a55d7c28ed40c0896c2cc640ad9cbe8c0d2248f913",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.14.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8926bb803a207bd9e46c725ba1564c3494a88dd000f9a36be1e6f08456ea45d7",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.15.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8926bb803a207bd9e46c725ba1564c3494a88dd000f9a36be1e6f08456ea45d7",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.11.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0fc718cc654bc5af6ff38e411c7d03b93c757d03b62b009a5a7223ff7e164922",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.10.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8926bb803a207bd9e46c725ba1564c3494a88dd000f9a36be1e6f08456ea45d7",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.12.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8926bb803a207bd9e46c725ba1564c3494a88dd000f9a36be1e6f08456ea45d7",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.13.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8926bb803a207bd9e46c725ba1564c3494a88dd000f9a36be1e6f08456ea45d7",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.9.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "014a8541fd8acfd7617be7ec00780bd196fb955936f8ffb5077edc7582fdc61e",
+ "format": 1
+ },
+ {
+ "name": "meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "meta/execution-environment.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "49dd36232640221f2dc756d8ce001229352528be3d496a24959acf395c79f1ba",
+ "format": 1
+ },
+ {
+ "name": "meta/runtime.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1d055cae1300653a64eccd38971f55dda600c5fe9e610af8dfec8bed323dcaf7",
+ "format": 1
+ },
+ {
+ "name": "changelogs",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/config.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "262867ae8aa0f9fa90fe19a60e3178500f920dbf9f98442ec7c6c06e030a8ca0",
+ "format": 1
+ },
+ {
+ "name": "changelogs/.plugin-cache.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "28964b091c6aca7998c08021fe6afc6a011d60fe6133686e969f2ec866034aa7",
+ "format": 1
+ },
+ {
+ "name": "changelogs/changelog.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f5620aeb03e730433ea83c6b0e99c788d90a9d508f0f70f1a3e3557c127c5c9c",
+ "format": 1
+ },
+ {
+ "name": "README.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fc159262a41039c5db624bb502911d1dfac6a7fb8b87cede5ad9386cdd2aa533",
+ "format": 1
+ },
+ {
+ "name": "COPYING",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986",
+ "format": 1
+ },
+ {
+ "name": "contributing.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "675b7ca808057142806c4a3a745daa42fa5a73bbf4e70c8d3b39dd8f60992b8d",
+ "format": 1
+ },
+ {
+ "name": "scripts",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "scripts/sublime-build",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "scripts/sublime-build/build.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "569caf1737763a06d5da2b4260a035dce2c257c2cc3defcbe8923af940a86c57",
+ "format": 1
+ },
+ {
+ "name": "scripts/sublime-build/requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "275650a206a5612e29a6163f94f102a0d31fc341f34d2ad98250b861fc28e310",
+ "format": 1
+ },
+ {
+ "name": "scripts/sublime-build/build.py.generic",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "569caf1737763a06d5da2b4260a035dce2c257c2cc3defcbe8923af940a86c57",
+ "format": 1
+ },
+ {
+ "name": "CHANGELOG.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "970617ad4dfd95d65e3b603a0c2762b59bd113f5a9fd3792c1a979c975833ff0",
+ "format": 1
+ }
+ ],
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/meraki/MANIFEST.json b/ansible_collections/cisco/meraki/MANIFEST.json
new file mode 100644
index 000000000..7e894c984
--- /dev/null
+++ b/ansible_collections/cisco/meraki/MANIFEST.json
@@ -0,0 +1,36 @@
+{
+ "collection_info": {
+ "namespace": "cisco",
+ "name": "meraki",
+ "version": "2.15.1",
+ "authors": [
+ "Kevin Breit"
+ ],
+ "readme": "README.md",
+ "tags": [
+ "networking",
+ "wireless",
+ "firewall",
+ "switching",
+ "cisco"
+ ],
+ "description": "An Ansible collection for managing the Cisco Meraki Dashboard",
+ "license": [
+ "GPL-3.0-only"
+ ],
+ "license_file": null,
+ "dependencies": {},
+ "repository": "https://github.com/CiscoDevNet/ansible-meraki",
+ "documentation": "",
+ "homepage": "",
+ "issues": "https://github.com/CiscoDevNet/ansible-meraki/issues"
+ },
+ "file_manifest_file": {
+ "name": "FILES.json",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "89712211f5307f2913b4e3cf7b4896f3a8c36d7b6ab4dfa2ca3d172a59d8f184",
+ "format": 1
+ },
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/meraki/README.md b/ansible_collections/cisco/meraki/README.md
new file mode 100644
index 000000000..9539f1a1f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/README.md
@@ -0,0 +1,180 @@
+# cisco.meraki Ansible Collection
+
+Ansible collection for managing and automating Cisco Meraki network environments.
+
+See the CHANGELOG.md file for details of updates in each release.
+
+## Requirements
+
+* Ansible v2.10 or newer is required for collection support
+
+## What is Cisco Meraki?
+
+Cisco Meraki is an easy-to-use, cloud-based, network infrastructure platform for enterprise environments. While most network hardware uses command-line interfaces (CLIs) for configuration, Meraki uses an easy-to-use Dashboard hosted in the Meraki cloud. No on-premises management hardware or software is required - only the network infrastructure to run your business.
+
+### MS Switches
+
+Meraki MS switches come in multiple flavors and form factors. Meraki switches support 10/100/1000/10000 ports, as well as Cisco's mGig technology for 2.5/5/10Gbps copper connectivity. 8, 24, and 48 port flavors are available with PoE (802.3af/802.3at/UPoE) available on many models.
+
+### MX Firewalls
+
+Meraki's MX firewalls support full layer 3-7 deep packet inspection. MX firewalls are compatible with a variety of VPN technologies including IPSec, SSL VPN, and Meraki's easy-to-use AutoVPN.
+
+### MR Wireless Access Points
+
+MR access points are enterprise-class, high-performance access points for the enterprise. MR access points have MIMO technology and integrated beamforming built-in for high performance applications. BLE allows for advanced location applications to be developed with no on-premises analytics platforms.
+
+## Using the Meraki modules
+
+Meraki modules provide a user-friendly interface to manage your Meraki environment using Ansible. For example, details about SNMP settings for a particular organization can be discovered using the module `meraki_snmp <meraki_snmp_module>`.
+
+```
+ - name: Query SNMP settings
+ meraki_snmp:
+ api_key: abc123
+ org_name: AcmeCorp
+ state: query
+ delegate_to: localhost
+```
+
+Information about a particular object can be queried. For example, the `meraki_admin <meraki_admin_module>` module supports
+
+```
+ - name: Gather information about Jane Doe
+ meraki_admin:
+ api_key: abc123
+ org_name: AcmeCorp
+ state: query
+ email: janedoe@email.com
+ delegate_to: localhost
+```
+
+## Common Parameters
+
+All Ansible Meraki modules support the following parameters which affect communication with the Meraki Dashboard API. Most of these should only be used by Meraki developers and not the general public.
+
+* host
+ * Hostname or IP of Meraki Dashboard.
+
+* use_https
+ * Specifies whether communication should be over HTTPS. (Defaults to `yes`)
+
+* use_proxy
+ * Whether to use a proxy for any communication.
+
+* validate_certs
+ * Determine whether certificates should be validated or trusted. (Defaults to `yes`)
+
+These are the common parameters which are used for most every module.
+
+* org_name
+ * Name of organization to perform actions in.
+
+* org_id
+ * ID of organization to perform actions in.
+
+* net_name
+ * Name of network to perform actions in.
+
+* net_id
+ * ID of network to perform actions in.
+
+* state
+ * General specification of what action to take. `query` does lookups. `present` creates or edits. `absent` deletes.
+
+**Note:** Use the `org_id` and `net_id` parameters when possible. `org_name` and `net_name` require additional behind-the-scenes API calls to learn the ID values. `org_id` and `net_id` will perform faster.
+
+## Meraki Authentication
+
+All API access with the Meraki Dashboard requires an API key. An API key can be generated from the organization's settings page. Each play in a playbook requires the `api_key` parameter to be specified.
+
+The "Vault" feature of Ansible allows you to keep sensitive data such as passwords or keys in encrypted files, rather than as plain text in your playbooks or roles. These vault files can then be distributed or placed in source control. See [Using Vaults in playbooks](https://docs.ansible.com/ansible/latest/user_guide/playbooks_vault.html) for more information.
+
+Meraki's API returns a 404 error if the API key is not correct. It does not provide any specific error saying the key is incorrect. If you receive a 404 error, check the API key first.
+
+## Returned Data Structures
+
+Meraki and its related Ansible modules return most information in the form of a list. For example, this is returned information by `meraki_admin` querying administrators. It returns a list even though there's only one.
+
+```
+ [
+ {
+ "orgAccess": "full",
+ "name": "John Doe",
+ "tags": [],
+ "networks": [],
+ "email": "john@doe.com",
+ "id": "12345677890"
+ }
+ ]
+```
+
+## Handling Returned Data
+
+Since Meraki's response data uses lists instead of properly keyed dictionaries for responses, certain strategies should be used when querying data for particular information. For many situations, use the `selectattr()` Jinja2 function.
+
+## Merging Existing and New Data
+
+Ansible's Meraki modules do not allow for manipulating data. For example, you may need to insert a rule in the middle of a firewall ruleset. Ansible and the Meraki modules lack a way to directly merge to manipulate data. However, a playlist can use a few tasks to split the list where you need to insert a rule and then merge them together again with the new rule added. The steps involved are as follows:
+
+1. Create blank "front" and "back" lists.
+```
+ vars:
+ - front_rules: []
+ - back_rules: []
+```
+2. Get existing firewall rules from Meraki and create a new variable.
+```
+ - name: Get firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+ register: rules
+ - set_fact:
+ original_ruleset: '{{rules.data}}'
+```
+3. Write the new rule. The new rule needs to be in a list so it can be merged with other lists in an upcoming step. The blank `-` puts the rule in a list so it can be merged.
+```
+ - set_fact:
+ new_rule:
+ -
+ - comment: Block traffic to server
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dst_cidr: 192.0.1.2/32
+ dst_port: any
+ protocol: any
+ policy: deny
+```
+4. Split the rules into two lists. This assumes the existing ruleset is 2 rules long.
+```
+ - set_fact:
+ front_rules: '{{front_rules + [ original_ruleset[:1] ]}}'
+ - set_fact:
+ back_rules: '{{back_rules + [ original_ruleset[1:] ]}}'
+```
+5. Merge rules with the new rule in the middle.
+```
+ - set_fact:
+ new_ruleset: '{{front_rules + new_rule + back_rules}}'
+```
+6. Upload new ruleset to Meraki.
+```
+ - name: Set two firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ rules: '{{ new_ruleset }}'
+ delegate_to: localhost
+```
+
+## Error Handling
+
+Ansible's Meraki modules will often fail if improper or incompatible parameters are specified. However, there will likely be scenarios where the module accepts the information but the Meraki API rejects the data. If this happens, the error will be returned in the `body` field for HTTP status of 400 return code.
+
+Meraki's API returns a 404 error if the API key is not correct. It does not provide any specific error saying the key is incorrect. If you receive a 404 error, check the API key first. 404 errors can also occur if improper object IDs (ex. `org_id`) are specified.
diff --git a/ansible_collections/cisco/meraki/changelogs/.plugin-cache.yaml b/ansible_collections/cisco/meraki/changelogs/.plugin-cache.yaml
new file mode 100644
index 000000000..df22d4db1
--- /dev/null
+++ b/ansible_collections/cisco/meraki/changelogs/.plugin-cache.yaml
@@ -0,0 +1,234 @@
+objects:
+ role: {}
+plugins:
+ become: {}
+ cache: {}
+ callback: {}
+ cliconf: {}
+ connection: {}
+ filter: {}
+ httpapi: {}
+ inventory: {}
+ lookup: {}
+ module:
+ meraki_action_batch:
+ description: Manage Action Batch jobs within the Meraki Dashboard.
+ name: meraki_action_batch
+ namespace: ''
+ version_added: null
+ meraki_admin:
+ description: Manage administrators in the Meraki cloud
+ name: meraki_admin
+ namespace: ''
+ version_added: 1.0.0
+ meraki_alert:
+ description: Manage alerts in the Meraki cloud
+ name: meraki_alert
+ namespace: ''
+ version_added: 2.1.0
+ meraki_config_template:
+ description: Manage configuration templates in the Meraki cloud
+ name: meraki_config_template
+ namespace: ''
+ version_added: 1.0.0
+ meraki_device:
+ description: Manage devices in the Meraki cloud
+ name: meraki_device
+ namespace: ''
+ version_added: null
+ meraki_firewalled_services:
+ description: Edit firewall policies for administrative network services
+ name: meraki_firewalled_services
+ namespace: ''
+ version_added: null
+ meraki_management_interface:
+ description: Configure Meraki management interfaces
+ name: meraki_management_interface
+ namespace: ''
+ version_added: 1.1.0
+ meraki_mr_l3_firewall:
+ description: Manage MR access point layer 3 firewalls in the Meraki cloud
+ name: meraki_mr_l3_firewall
+ namespace: ''
+ version_added: null
+ meraki_mr_l7_firewall:
+ description: Manage MR access point layer 7 firewalls in the Meraki cloud
+ name: meraki_mr_l7_firewall
+ namespace: ''
+ version_added: null
+ meraki_mr_radio:
+ description: Manage device radio settings for Meraki wireless networks
+ name: meraki_mr_radio
+ namespace: ''
+ version_added: null
+ meraki_mr_rf_profile:
+ description: Manage RF profiles for Meraki wireless networks
+ name: meraki_mr_rf_profile
+ namespace: ''
+ version_added: null
+ meraki_mr_settings:
+ description: Manage general settings for Meraki wireless networks
+ name: meraki_mr_settings
+ namespace: ''
+ version_added: null
+ meraki_mr_ssid:
+ description: Manage wireless SSIDs in the Meraki cloud
+ name: meraki_mr_ssid
+ namespace: ''
+ version_added: null
+ meraki_ms_access_list:
+ description: Manage access lists for Meraki switches in the Meraki cloud
+ name: meraki_ms_access_list
+ namespace: ''
+ version_added: 0.1.0
+ meraki_ms_access_policies:
+ description: Manage Switch Access Policies in the Meraki cloud
+ name: meraki_ms_access_policies
+ namespace: ''
+ version_added: null
+ meraki_ms_l3_interface:
+ description: Manage routed interfaces on MS switches
+ name: meraki_ms_l3_interface
+ namespace: ''
+ version_added: null
+ meraki_ms_link_aggregation:
+ description: Manage link aggregations on MS switches
+ name: meraki_ms_link_aggregation
+ namespace: ''
+ version_added: 1.2.0
+ meraki_ms_ospf:
+ description: Manage OSPF configuration on MS switches
+ name: meraki_ms_ospf
+ namespace: ''
+ version_added: null
+ meraki_ms_stack:
+ description: Modify switch stacking configuration in Meraki.
+ name: meraki_ms_stack
+ namespace: ''
+ version_added: 1.3.0
+ meraki_ms_stack_l3_interface:
+ description: Manage routed interfaces on MS switches
+ name: meraki_ms_stack_l3_interface
+ namespace: ''
+ version_added: null
+ meraki_ms_storm_control:
+ description: Manage storm control configuration on a switch in the Meraki cloud
+ name: meraki_ms_storm_control
+ namespace: ''
+ version_added: 0.0.1
+ meraki_ms_switchport:
+ description: Manage switchports on a switch in the Meraki cloud
+ name: meraki_ms_switchport
+ namespace: ''
+ version_added: null
+ meraki_mx_content_filtering:
+ description: Edit Meraki MX content filtering policies
+ name: meraki_mx_content_filtering
+ namespace: ''
+ version_added: null
+ meraki_mx_intrusion_prevention:
+ description: Manage intrustion prevention in the Meraki cloud
+ name: meraki_mx_intrusion_prevention
+ namespace: ''
+ version_added: null
+ meraki_mx_l2_interface:
+ description: Configure MX layer 2 interfaces
+ name: meraki_mx_l2_interface
+ namespace: ''
+ version_added: 2.1.0
+ meraki_mx_l3_firewall:
+ description: Manage MX appliance layer 3 firewalls in the Meraki cloud
+ name: meraki_mx_l3_firewall
+ namespace: ''
+ version_added: null
+ meraki_mx_l7_firewall:
+ description: Manage MX appliance layer 7 firewalls in the Meraki cloud
+ name: meraki_mx_l7_firewall
+ namespace: ''
+ version_added: null
+ meraki_mx_malware:
+ description: Manage Malware Protection in the Meraki cloud
+ name: meraki_mx_malware
+ namespace: ''
+ version_added: null
+ meraki_mx_nat:
+ description: Manage NAT rules in Meraki cloud
+ name: meraki_mx_nat
+ namespace: ''
+ version_added: null
+ meraki_mx_network_vlan_settings:
+ description: Manage VLAN settings for Meraki Networks
+ name: meraki_mx_network_vlan_settings
+ namespace: ''
+ version_added: null
+ meraki_mx_site_to_site_firewall:
+ description: Manage MX appliance firewall rules for site-to-site VPNs
+ name: meraki_mx_site_to_site_firewall
+ namespace: ''
+ version_added: 1.0.0
+ meraki_mx_site_to_site_vpn:
+ description: Manage AutoVPN connections in Meraki
+ name: meraki_mx_site_to_site_vpn
+ namespace: ''
+ version_added: 1.1.0
+ meraki_mx_static_route:
+ description: Manage static routes in the Meraki cloud
+ name: meraki_mx_static_route
+ namespace: ''
+ version_added: null
+ meraki_mx_third_party_vpn_peers:
+ description: Manage third party (IPSec) VPN peers for MX devices
+ name: meraki_mx_third_party_vpn_peers
+ namespace: ''
+ version_added: null
+ meraki_mx_uplink_bandwidth:
+ description: Manage uplinks on Meraki MX appliances
+ name: meraki_mx_uplink_bandwidth
+ namespace: ''
+ version_added: 1.1.0
+ meraki_mx_vlan:
+ description: Manage VLANs in the Meraki cloud
+ name: meraki_mx_vlan
+ namespace: ''
+ version_added: null
+ meraki_network:
+ description: Manage networks in the Meraki cloud
+ name: meraki_network
+ namespace: ''
+ version_added: null
+ meraki_network_settings:
+ description: Manage the settings of networks in the Meraki cloud
+ name: meraki_network_settings
+ namespace: ''
+ version_added: null
+ meraki_organization:
+ description: Manage organizations in the Meraki cloud
+ name: meraki_organization
+ namespace: ''
+ version_added: null
+ meraki_snmp:
+ description: Manage organizations in the Meraki cloud
+ name: meraki_snmp
+ namespace: ''
+ version_added: null
+ meraki_syslog:
+ description: Manage syslog server settings in the Meraki cloud.
+ name: meraki_syslog
+ namespace: ''
+ version_added: null
+ meraki_webhook:
+ description: Manage webhooks configured in the Meraki cloud
+ name: meraki_webhook
+ namespace: ''
+ version_added: null
+ meraki_webhook_payload_template:
+ description: Manage webhook payload templates for a network in the Meraki cloud
+ name: meraki_webhook_payload_template
+ namespace: ''
+ version_added: null
+ netconf: {}
+ shell: {}
+ strategy: {}
+ test: {}
+ vars: {}
+version: 2.15.1
diff --git a/ansible_collections/cisco/meraki/changelogs/changelog.yaml b/ansible_collections/cisco/meraki/changelogs/changelog.yaml
new file mode 100644
index 000000000..9687e1524
--- /dev/null
+++ b/ansible_collections/cisco/meraki/changelogs/changelog.yaml
@@ -0,0 +1,410 @@
+ancestor: null
+releases:
+ 2.0.0:
+ changes:
+ breaking_changes:
+ - meraki_device - Changed tags from string to list
+ - meraki_device - Removed serial_lldp_cdp parameter
+ - meraki_device - Removed serial_uplink parameter
+ - meraki_intrusion_prevention - Rename whitedlisted_rules to allowed_rules
+ - meraki_mx_l3_firewall - Rule responses are now in a `rules` list
+ - meraki_mx_l7_firewall - Rename blacklisted_countries to blocked_countries
+ - meraki_mx_l7_firewall - Rename whitelisted_countries to allowed_countries
+ - meraki_network - Local and remote status page settings cannot be set during
+ network creation
+ - meraki_network - `disableRemoteStatusPage` response is now `remote_status_page_enabled`
+ - meraki_network - `disable_my_meraki_com` response is now `local_status_page_enabled`
+ - meraki_network - `disable_my_meraki` has been deprecated
+ - meraki_network - `enable_my_meraki` is now called `local_status_page_enabled`
+ - meraki_network - `enable_remote_status_page` is now called `remote_status_page_enabled`
+ - meraki_network - `enabled` response for VLAN status is now `vlans_enabled`
+ - meraki_network - `tags` and `type` now return a list
+ - meraki_snmp - peer_ips is now a list
+ - meraki_switchport - `access_policy_number` is now an int and not a string
+ - meraki_switchport - `tags` is now a list and not a string
+ - meraki_webhook - Querying test status now uses state of query.
+ bugfixes:
+ - Remove unnecessary files from the collection package, significantly reduces
+ package size
+ - meraki_admin - Fix error when adding network privileges to admin using network
+ name
+ - meraki_switch_stack - Fix situation where module may crash due to switch being
+ in or not in a stack already
+ - meraki_webhook - Proper response is shown when creating webhook test
+ major_changes:
+ - Rewrite requests method for version 1.0 API and improved readability
+ - meraki_mr_rf_profile - Configure wireless RF profiles.
+ - meraki_mr_settings - Configure network settings for wireless.
+ - meraki_ms_l3_interface - New module
+ - meraki_ms_ospf - Configure OSPF.
+ minor_changes:
+ - meraki - Add optional debugging for is_update_required() method.
+ - meraki_admin - Update endpoints for API v1
+ - meraki_alert - Manage network wide alert settings.
+ - meraki_device - Added query parameter
+ - meraki_intrusion_prevention - Change documentation to show proper way to clear
+ rules
+ - meraki_malware - Update documentation to show how to allow multiple URLs at
+ once.
+ - meraki_mx_l2_interface - Configure physical interfaces on MX appliances.
+ - meraki_mx_uplink - Renamed to meraki_mx_uplink_bandwidth
+ - meraki_ssid - Add `WPA3 Only` and `WPA3 Transition Mode`
+ - meraki_switchport - Add support for `access_policy_type` parameter
+ security_fixes:
+ - meraki_webhook - diff output may show data for values set to not display
+ fragments:
+ - add-build-ignore.yml
+ - diff_secret_fix.yml
+ - is_update_debug.yml
+ - malware_docs.yml
+ - malware_mx_l2_interface.yml
+ - meraki_admin.yml
+ - meraki_alert.yml
+ - meraki_content_filtering.yml
+ - meraki_device.yml
+ - meraki_firewalled_services.yml
+ - meraki_intrusion_prevention.yml
+ - meraki_malware.yml
+ - meraki_management_interface.yml
+ - meraki_mr_l3_firewall.yml
+ - meraki_mr_rf_profile.yml
+ - meraki_mr_settings.yml
+ - meraki_ms_l3_interface.yml
+ - meraki_ms_ospf.yml
+ - meraki_mx_l3_firewall.yml
+ - meraki_mx_l7_firewall.yml
+ - meraki_mx_uplink.yml
+ - meraki_nat.yml
+ - meraki_network_update.yml
+ - meraki_site_to_site_vpn.yml
+ - meraki_snmp.yml
+ - meraki_ssid.yml
+ - meraki_static_route.yml
+ - meraki_switch_stack.yml
+ - meraki_switch_storm_control.yml
+ - meraki_switchport.yml
+ - meraki_syslog.yml
+ - meraki_vlan.yml
+ - meraki_webhook.yml
+ - requests-rewrite.yml
+ - ssid-tags-fix.yml
+ - ssid_wpa3.yml
+ release_date: '2020-08-27'
+ 2.1.0:
+ modules:
+ - description: Manage alerts in the Meraki cloud
+ name: meraki_alert
+ namespace: ''
+ - description: Configure MX layer 2 interfaces
+ name: meraki_mx_l2_interface
+ namespace: ''
+ release_date: '2020-10-16'
+ 2.1.1:
+ changes:
+ bugfixes:
+ - meraki_management_interface - Fix crash when modifying a non-MX management
+ interface.
+ fragments:
+ - management_ms_fix.yml
+ release_date: '2020-11-02'
+ 2.1.2:
+ changes:
+ bugfixes:
+ - Remove test output as it made the collection, and Ansible, huge.
+ fragments:
+ - size_fix.yml
+ release_date: '2020-11-04'
+ 2.1.3:
+ changes:
+ bugfixes:
+ - meraki_device - Support pagination. This allows for more than 1,000 devices
+ to be listed at a time.
+ - meraki_network - Support pagination. This allows for more than 1,000 networks
+ to be listed at a time.
+ fragments:
+ - pagination.yml
+ release_date: '2020-12-16'
+ 2.10.0:
+ changes:
+ minor_changes:
+ - meraki_network - Add support for `copy_from_network_id`.
+ fragments:
+ - 335-net_copy.yml
+ - rename_method.yml
+ release_date: '2022-07-06'
+ 2.10.1:
+ changes:
+ minor_changes:
+ - Change shebang in Sublime utils to point to env instead of direct to the path
+ fragments:
+ - changelog-frags.yml
+ - sublime-utils.yml
+ release_date: '2022-07-16'
+ 2.11.0:
+ changes:
+ minor_changes:
+ - Add GPLv3 license. Always was GPLv3, but didn't have the file.
+ - Change shebang in Sublime utils to point to env instead of direct to the path
+ - meraki_alert - Change type for opbject to alert_type in examples
+ - meraki_ms_access_policies - New module to create, delete, update Access Policies
+ in the Switch settings
+ - meraki_ssid - Add support for `ap_availability_tags`.
+ - meraki_ssid - Add support for `available_on_all_aps`
+ - meraki_ssid - Add support for `lan_isolation_enabled`.
+ - meraki_ssid - Add support for `visible`.
+ fragments:
+ - add-meraki_ms_access_policy.yml
+ - alert_examples.yml
+ - amend_meraki_network.yml
+ - changelog-frags.yml
+ - license.yml
+ - meraki_mr_ssid.yaml
+ - meraki_mr_ssid_availability_tags.yaml
+ - meraki_mr_ssid_lan_isolation.yaml
+ - shebang.yml
+ - ssid_test_fix.yml
+ - sublime-utils.yml
+ release_date: '2022-09-12'
+ 2.12.0:
+ changes:
+ bugfixes:
+ - Update defaults in documentation for new sanity tests
+ - meraki_device - Fix URL for LLDP and CDP lookups
+ major_changes:
+ - meraki_webhook_payload_template - New module
+ fragments:
+ - 2.15-docs.yml
+ - 2.15-prep.yml
+ - device_lldp.yml
+ - fix-meraki-mr-radio-tests.yml
+ - meraki_ms_switch_pollicy_doc.yml
+ - meraki_webhook_payload_template.yml
+ - warnings-.yml
+ - warnings-meraki_action_batch.yml
+ - warnings-meraki_admin.yml
+ - warnings-meraki_alert.yml
+ - warnings-meraki_config_template.yml
+ - warnings-meraki_device.yml
+ - warnings-meraki_management_interface.yml
+ - warnings-meraki_mr_l3_firewall.yml
+ - warnings-meraki_mr_radio.yml
+ - warnings-meraki_mr_rf_profile.yml
+ release_date: '2022-11-21'
+ 2.13.0:
+ changes:
+ major_changes:
+ - meraki_mr_l7_firewall - New module
+ fragments:
+ - meraki_mr_l7_firewall.yml
+ release_date: '2022-11-21'
+ 2.14.0:
+ changes:
+ bugfixes:
+ - Fix checkmode on merak webhook payload template update
+ - meraki_webhook - First error when updating URL in a webhook
+ minor_changes:
+ - meraki_webhook - Add payload template parameter
+ fragments:
+ - bugfixes-meraki_webhook_payload_template.yml
+ - warnings-meraki_mr_settings.yml
+ - warnings-meraki_ms_access_list.yml
+ - warnings-meraki_ms_storm_control.yml
+ - webhook-diff.yml
+ - webhook-plus-payload-templates.yml
+ release_date: '2023-01-05'
+ 2.15.0:
+ changes:
+ bugfixes:
+ - Resolved issue
+ - Update pipeline to use newer version of action to detect changed files.
+ - meraki_alert - Fix situation where specifying emails may crash.
+ - meraki_mx_site_to_site_vpn - Check mode should no longer apply changes when
+ enabled.
+ known_issues:
+ - meraki_network - Updated documentation for `local_status_page_enabled` and
+ `remote_status_page_enabled` as these no longer work.
+ minor_changes:
+ - New module - meraki_network_settings - Configure detailed settings of a network.
+ fragments:
+ - 412-meraki_network-settings.yaml
+ - 412-network-settings.yaml
+ - 414-alert-emails.yaml
+ - issue-meraki_mx_content_filtering.yml
+ - mx-s2s.yaml
+ - pipeline-changelog.yaml
+ - warnings-meraki_ms_ospf.yml
+ - warnings-meraki_ms_stack.yml
+ - warnings-meraki_ms_switchport.yml
+ - warnings-meraki_mx_content_filtering.yml
+ - warnings-meraki_mx_intrusion_prevention.yml
+ - warnings-meraki_mx_l2_interface.yml
+ - warnings-meraki_ssid.yml
+ - workflow-ansible-lint.yml
+ release_date: '2023-01-24'
+ 2.15.1:
+ changes:
+ bugfixes:
+ - Corrects constraints applied to local and remote status page settings to align
+ with API behaviour (https://github.com/CiscoDevNet/ansible-meraki/issues/437)
+ - Enables meraki_network query by net_id (https://github.com/CiscoDevNet/ansible-meraki/issues/441)
+ - Resolved an issue where an empty response from the API triggered an exception
+ in module meraki_webhook (https://github.com/CiscoDevNet/ansible-meraki/issues/433)
+ - Resolves issues with meraki_webhook shared_secret defaulting to null; (https://github.com/CiscoDevNet/ansible-meraki/issues/439);
+ Also adds Test Coverage for shared secret idempotency and resolves test file
+ lint issues.
+ fragments:
+ - 433_Resolve_meraki_webhook_fail_when_none_defined.yml
+ - Resolve_local_and_remote_status_pages_constraint.yml
+ - Resolve_meraki_network_query_by_ID_returns_empty_data_object.yml
+ - Resolve_shared_secret_defaulting_to_null.yml
+ - warnings-meraki_ms_link_aggregation.yml
+ release_date: '2023-02-15'
+ 2.2.0:
+ changes:
+ bugfixes:
+ - meraki_network - Fix bug where local or remote settings always show changed.
+ minor_changes:
+ - meraki_network - Update documentation to show querying of local or remote
+ settings.
+ - meraki_ssid - Add Cisco ISE as a splash page option.
+ fragments:
+ - network_settings_changed.yml
+ - ssid_ise.yml
+ release_date: '2021-02-01'
+ 2.2.1:
+ changes:
+ bugfixes:
+ - meraki_mx_content_filtering - Fix crash with idempotent condition due to improper
+ sorting
+ fragments:
+ - 222.yml
+ release_date: '2021-02-22'
+ 2.3.0:
+ changes:
+ bugfixes:
+ - meraki_ms_switchport - access_policy_types choices are incorrect causing failures.
+ (https://github.com/CiscoDevNet/ansible-meraki/issues/227).
+ minor_changes:
+ - meraki_ms_switchport - Adding additional functionality to support the access_policy_types
+ "MAC allow list" and "Sticky MAC allow list" port security configuration options.
+ (https://github.com/CiscoDevNet/ansible-meraki/issues/227).
+ - meraki_mx_intrusion_prevention - Rename message to rule_message to avoid conflicts
+ with internal Ansible variables.
+ fragments:
+ - 228-switchport-module-additions.yml
+ - msg.yaml
+ release_date: '2021-05-12'
+ 2.3.1:
+ changes:
+ bugfixes:
+ - meraki_ms_switchport - link_negotiation choice for 100 Megabit Auto is incorrect
+ causing failures. (https://github.com/CiscoDevNet/ansible-meraki/issues/235).
+ fragments:
+ - CHANGELOG.yml
+ release_date: '2021-05-19'
+ 2.4.0:
+ changes:
+ bugfixes:
+ - Allow a state of absent in voice vlan to allow the value to be nulled out(https://github.com/CiscoDevNet/ansible-meraki/issues/238)
+ minor_changes:
+ - meraki_mx_switchport - Improve documentation for response
+ fragments:
+ - 0238-vvlan-allow-null.yml
+ - switchport-docs.yaml
+ release_date: '2021-06-07'
+ 2.4.2:
+ changes:
+ bugfixes:
+ - Fix some flake8 sanity errors as reported by Ansible Galaxy. Should be no
+ functional change.
+ fragments:
+ - sanity-fixes.yml
+ release_date: '2021-06-22'
+ 2.5.0:
+ changes:
+ minor_changes:
+ - meraki_mr_l3_firewall - Return each MR L3 firewall rule's values in lowercase.
+ - meraki_mr_ssid - Add support for radius_proxy_enabled SSID setting.
+ - meraki_mx_l3_firewall - Return each MX L3 firewall rule's values in lowercase.
+ - meraki_mx_vlan - Fix dhcp_boot_options_enabled parameter
+ fragments:
+ - dhcp_boot_options_enabled.yaml
+ - lowercase-firewall-rule-values.yml
+ - radius_proxy_enabled.yaml
+ release_date: '2021-10-14'
+ 2.6.0:
+ changes:
+ bugfixes:
+ - meraki_mr_ssid - Fix issue with SSID removal idempotency when ID doesn't exist
+ major_changes:
+ - meraki_mr_radio - New module
+ minor_changes:
+ - meraki_mx_l7_firewall - Allow passing an empty ruleset to delete all rules
+ - meraki_utils - Add debugging output for failed socket connections
+ fragments:
+ - delete-ssid-idempotency.yml
+ - docs-m-reference.yml
+ - meraki_mr_radio.yml
+ - mx_l7_firewall_empty_list.yaml
+ release_date: '2021-12-27'
+ 2.6.1:
+ changes:
+ bugfixes:
+ - meraki_mr_rf_profile - Fix issue with idempotency and creation of RF Profiles
+ by name only
+ - meraki_syslog - Improve reliability for multiple roles or capitalization.
+ minor_changes:
+ - meraki_ssid - Add support for enterprise_admin_access and splash_guest_sponsor_domains
+ with the latter required for creating a sponsor portal.
+ fragments:
+ - 288-syslog_idempotency.yml
+ - 290-sponsor.yml
+ - docs-contributing.yml
+ - rf-profile-create-idempotency.yml
+ - sanity_fixes.yaml
+ release_date: '2022-03-28'
+ 2.6.2:
+ changes:
+ bugfixes:
+ - meraki_alert - Updates now properly set default destination webhook
+ - meraki_syslog - Fix crash due to incorrect dictionary reference
+ minor_changes:
+ - Add execution-environment.yml in meta as the base to a Meraki ee
+ - meraki_network - Add Products to net_type list
+ fragments:
+ - 288-fix-dict.yml
+ - 310-alert-webhook.yml
+ - ee.yml
+ - net_type-list.yml
+ release_date: '2022-05-16'
+ 2.7.0:
+ changes:
+ bugfixes:
+ - meraki_mx_static_route - Add support for gateway_vlan_id otherwise requests
+ could error
+ minor_changes:
+ - meraki_mx_network_vlan_settings - New module to enable or disable VLANs on
+ a network
+ - meraki_mx_third_party_vpn_peers - New module for managing third party VPM
+ peers
+ fragments:
+ - meraki_mx_network_vlan_settings.yml
+ - mx_static_route.yaml
+ - s2s_peers.yaml
+ release_date: '2022-06-14'
+ 2.8.0:
+ changes:
+ minor_changes:
+ - meraki_action_batch - New module for CRUD operations on Meraki Action Batches
+ - meraki_switchport - Add support for flexible stacking
+ fragments:
+ - 327-switchport-flex.yaml
+ - action_batch.yaml
+ release_date: '2022-06-24'
+ 2.9.0:
+ changes:
+ bugfixes:
+ - meraki_switchport - Setting VLAN to 0 on trunk port clears the VLAN.
+ fragments:
+ - 331-switchport_clear_vlan.yml
+ release_date: '2022-07-04'
diff --git a/ansible_collections/cisco/meraki/changelogs/config.yaml b/ansible_collections/cisco/meraki/changelogs/config.yaml
new file mode 100644
index 000000000..33e2a3495
--- /dev/null
+++ b/ansible_collections/cisco/meraki/changelogs/config.yaml
@@ -0,0 +1,29 @@
+changelog_filename_template: ../CHANGELOG.rst
+changelog_filename_version_depth: 0
+changes_file: changelog.yaml
+changes_format: combined
+keep_fragments: false
+mention_ancestor: true
+new_plugins_after_name: removed_features
+notesdir: fragments
+prelude_section_name: release_summary
+prelude_section_title: Release Summary
+sections:
+- - major_changes
+ - Major Changes
+- - minor_changes
+ - Minor Changes
+- - breaking_changes
+ - Breaking Changes / Porting Guide
+- - deprecated_features
+ - Deprecated Features
+- - removed_features
+ - Removed Features (previously deprecated)
+- - security_fixes
+ - Security Fixes
+- - bugfixes
+ - Bugfixes
+- - known_issues
+ - Known Issues
+title: Cisco.Meraki
+trivial_section_name: trivial
diff --git a/ansible_collections/cisco/meraki/contributing.md b/ansible_collections/cisco/meraki/contributing.md
new file mode 100644
index 000000000..207fed6f9
--- /dev/null
+++ b/ansible_collections/cisco/meraki/contributing.md
@@ -0,0 +1,80 @@
+# Contributing
+
+Contributions are welcome, and they are greatly appreciated! This is a one man show
+so help is fantastic!
+
+You can contribute in many ways:
+
+## Types of Contributions
+
+### Report Bugs
+
+Report bugs at https://github.com/CiscoDevNet/ansible-meraki/issues.
+
+### Fix Bugs or Complete Enhancements
+
+Look through the GitHub issues for bugs. Anything without a pull request associated is
+open.
+
+### Submit Feedback
+
+Request new features at https://github.com/CiscoDevNet/ansible-meraki/issues
+
+If you are proposing a feature:
+
+- Explain in detail how it would work.
+- Keep the scope as narrow as possible, to make it easier to implement.
+- Remember that this is a volunteer-driven project, and that contributions are welcome :)
+
+## Get Started!
+
+Ready to contribute some code? Here's how to set up `cisco.meraki` for local development.
+
+1. Install Python 3.8 or higher, along with Ansible
+
+ Newer versions of Ansible require 3.8 so please target those versions.
+
+2. Fork the `cisco.meraki` repo on GitHub
+
+3. Clone your fork locally, using a special directory name so that Ansible understands it as a collection:
+
+```
+$ mkdir -p ansible_collections/meraki
+$ git clone https://github.com/your-username/ansible-meraki.git ansible_collections/cisco/meraki/
+```
+
+4. Create a branch for local development
+
+```
+$ cd ansible_collections/cisco/meraki
+$ git checkout -b name-of-your-bugfix-or-feature
+```
+
+5. Make your changes in the new branch
+
+ You can test any changes by developing integration tests. These are in the `tests/integration/targets/module_name` directory.
+
+6. Setup integration test Meraki variables template
+
+ If integration tests need to be run. Copy the `tests/integration/inventory.networking.template` file to `tests/integration/inventory.networking` and fill out the values. This should never be committed into git.
+
+7. Execute integration tests
+
+```
+$ ansible-test network-integration --allow-unsupported module_name
+```
+
+8. When you're done making changes, check that your changes pass `ansible-test sanity`:
+
+```
+$ ansible-test sanity --local
+```
+9. Commit your changes and push your branch to GitHub:
+
+```
+$ git add -A
+$ git commit -m "Your detailed description of your changes."
+$ git push origin name-of-your-bugfix-or-feature
+```
+
+10. Submit a pull request through the GitHub website.
diff --git a/ansible_collections/cisco/meraki/meta/execution-environment.yml b/ansible_collections/cisco/meraki/meta/execution-environment.yml
new file mode 100644
index 000000000..ae1d3a933
--- /dev/null
+++ b/ansible_collections/cisco/meraki/meta/execution-environment.yml
@@ -0,0 +1,3 @@
+---
+collections:
+ - name: cisco.meraki
diff --git a/ansible_collections/cisco/meraki/meta/runtime.yml b/ansible_collections/cisco/meraki/meta/runtime.yml
new file mode 100644
index 000000000..3ced16ad2
--- /dev/null
+++ b/ansible_collections/cisco/meraki/meta/runtime.yml
@@ -0,0 +1,29 @@
+requires_ansible: '>=2.10'
+plugin_routing:
+ modules:
+ meraki_vlan:
+ redirect: cisco.meraki.meraki_mx_vlan
+ meraki_switchport:
+ redirect: cisco.meraki.meraki_ms_switchport
+ meraki_content_filtering:
+ redirect: cisco.meraki.meraki_mx_content_filtering
+ meraki_intrusion_prevention:
+ redirect: cisco.meraki.meraki_mx_intrusion_prevention
+ meraki_malware:
+ redirect: cisco.meraki.meraki_mx_malware
+ meraki_mx_uplink:
+ redirect: cisco.meraki.meraki_mx_uplink_bandwidth
+ meraki_nat:
+ redirect: cisco.meraki.meraki_mx_nat
+ meraki_site_to_site_vpn:
+ redirect: cisco.meraki.meraki_mx_site_to_site_vpn
+ meraki_ssid:
+ redirect: cisco.meraki.meraki_mr_ssid
+ meraki_static_route:
+ redirect: cisco.meraki.meraki_mx_static_route
+ meraki_switch_access_list:
+ redirect: cisco.meraki.meraki_ms_access_list
+ meraki_switch_stack:
+ redirect: cisco.meraki.meraki_ms_stack
+ meraki_switch_storm_control:
+ redirect: cisco.meraki.meraki_ms_storm_control
diff --git a/ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py b/ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py
diff --git a/ansible_collections/cisco/meraki/plugins/doc_fragments/meraki.py b/ansible_collections/cisco/meraki/plugins/doc_fragments/meraki.py
new file mode 100644
index 000000000..d6d456f19
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/doc_fragments/meraki.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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
+
+
+class ModuleDocFragment(object):
+ # Standard files for documentation fragment
+ DOCUMENTATION = r'''
+notes:
+- More information about the Meraki API can be found at U(https://dashboard.meraki.com/api_docs).
+- Some of the options are likely only used for developers within Meraki.
+- As of Ansible 2.9, Meraki modules output keys as snake case. To use camel case, set the C(ANSIBLE_MERAKI_FORMAT) environment variable to C(camelcase).
+- Ansible's Meraki modules will stop supporting camel case output in Ansible 2.13. Please update your playbooks.
+- Check Mode downloads the current configuration from the dashboard, then compares changes against this download. Check Mode will report changed if
+ there are differences in the configurations, but does not submit changes to the API for validation of change.
+options:
+ auth_key:
+ description:
+ - Authentication key provided by the dashboard. Required if environmental variable C(MERAKI_KEY) is not set.
+ type: str
+ required: yes
+ host:
+ description:
+ - Hostname for Meraki dashboard.
+ - Can be used to access regional Meraki environments, such as China.
+ type: str
+ default: api.meraki.com
+ use_proxy:
+ description:
+ - If C(no), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
+ type: bool
+ default: False
+ use_https:
+ description:
+ - If C(no), it will use HTTP. Otherwise it will use HTTPS.
+ - Only useful for internal Meraki developers.
+ type: bool
+ default: yes
+ output_format:
+ description:
+ - Instructs module whether response keys should be snake case (ex. C(net_id)) or camel case (ex. C(netId)).
+ type: str
+ choices: [snakecase, camelcase]
+ default: snakecase
+ output_level:
+ description:
+ - Set amount of debug output during module execution.
+ type: str
+ choices: [ debug, normal ]
+ default: normal
+ timeout:
+ description:
+ - Time to timeout for HTTP requests.
+ type: int
+ default: 30
+ validate_certs:
+ description:
+ - Whether to validate HTTP certificates.
+ type: bool
+ default: yes
+ org_name:
+ description:
+ - Name of organization.
+ type: str
+ aliases: [ organization ]
+ org_id:
+ description:
+ - ID of organization.
+ type: str
+ rate_limit_retry_time:
+ description:
+ - Number of seconds to retry if rate limiter is triggered.
+ type: int
+ default: 165
+ internal_error_retry_time:
+ description:
+ - Number of seconds to retry if server returns an internal server error.
+ type: int
+ default: 60
+'''
diff --git a/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py b/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py
diff --git a/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py b/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py
new file mode 100644
index 000000000..4bb059629
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py
@@ -0,0 +1,539 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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
+
+import time
+import re
+from ansible.module_utils.basic import json, env_fallback
+from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict, snake_dict_to_camel_dict, recursive_diff
+from ansible.module_utils.urls import fetch_url
+from ansible.module_utils.six.moves.urllib.parse import urlencode
+from ansible.module_utils._text import to_native
+
+
+RATE_LIMIT_RETRY_MULTIPLIER = 3
+INTERNAL_ERROR_RETRY_MULTIPLIER = 3
+
+
+def meraki_argument_spec():
+ return dict(auth_key=dict(type='str', no_log=True, fallback=(env_fallback, ['MERAKI_KEY']), required=True),
+ host=dict(type='str', default='api.meraki.com'),
+ use_proxy=dict(type='bool', default=False),
+ use_https=dict(type='bool', default=True),
+ validate_certs=dict(type='bool', default=True),
+ output_format=dict(type='str', choices=['camelcase', 'snakecase'], default='snakecase', fallback=(env_fallback, ['ANSIBLE_MERAKI_FORMAT'])),
+ output_level=dict(type='str', default='normal', choices=['normal', 'debug']),
+ timeout=dict(type='int', default=30),
+ org_name=dict(type='str', aliases=['organization']),
+ org_id=dict(type='str'),
+ rate_limit_retry_time=dict(type='int', default=165),
+ internal_error_retry_time=dict(type='int', default=60)
+ )
+
+
+class RateLimitException(Exception):
+ def __init__(self, *args, **kwargs):
+ Exception.__init__(self, *args, **kwargs)
+
+
+class InternalErrorException(Exception):
+ def __init__(self, *args, **kwargs):
+ Exception.__init__(self, *args, **kwargs)
+
+
+class HTTPError(Exception):
+ def __init__(self, *args, **kwargs):
+ Exception.__init__(self, *args, **kwargs)
+
+
+class MerakiModule(object):
+
+ def __init__(self, module, function=None):
+ self.module = module
+ self.params = module.params
+ self.result = dict(changed=False)
+ self.headers = dict()
+ self.function = function
+ self.orgs = None
+ self.nets = None
+ self.org_id = None
+ self.net_id = None
+ self.check_mode = module.check_mode
+ self.key_map = {}
+ self.request_attempts = 0
+
+ # normal output
+ self.existing = None
+
+ # info output
+ self.config = dict()
+ self.original = None
+ self.proposed = dict()
+ self.merged = None
+ self.ignored_keys = ['id', 'organizationId']
+
+ # debug output
+ self.filter_string = ''
+ self.method = None
+ self.path = None
+ self.response = None
+ self.status = None
+ self.url = None
+ self.body = None
+
+ # rate limiting statistics
+ self.retry = 0
+ self.retry_time = 0
+
+ # If URLs need to be modified or added for specific purposes, use .update() on the url_catalog dictionary
+ self.get_urls = {'organizations': '/organizations',
+ 'network': '/organizations/{org_id}/networks',
+ 'admins': '/organizations/{org_id}/admins',
+ 'configTemplates': '/organizations/{org_id}/configTemplates',
+ 'samlymbols': '/organizations/{org_id}/samlRoles',
+ 'ssids': '/networks/{net_id}/ssids',
+ 'groupPolicies': '/networks/{net_id}/groupPolicies',
+ 'staticRoutes': '/networks/{net_id}/staticRoutes',
+ 'vlans': '/networks/{net_id}/vlans',
+ 'devices': '/networks/{net_id}/devices',
+ }
+
+ # Used to retrieve only one item
+ self.get_one_urls = {'organizations': '/organizations/{org_id}',
+ 'network': '/networks/{net_id}',
+ }
+
+ # Module should add URLs which are required by the module
+ self.url_catalog = {'get_all': self.get_urls,
+ 'get_one': self.get_one_urls,
+ 'create': None,
+ 'update': None,
+ 'delete': None,
+ 'misc': None,
+ }
+
+ if self.module._debug or self.params['output_level'] == 'debug':
+ self.module.warn('Enable debug output because ANSIBLE_DEBUG was set or output_level is set to debug.')
+
+ # TODO: This should be removed as org_name isn't always required
+ self.module.required_if = [('state', 'present', ['org_name']),
+ ('state', 'absent', ['org_name']),
+ ]
+ # self.module.mutually_exclusive = [('org_id', 'org_name'),
+ # ]
+ self.modifiable_methods = ['POST', 'PUT', 'DELETE']
+
+ self.headers = {'Content-Type': 'application/json',
+ 'Authorization': 'Bearer {key}'.format(key=module.params['auth_key']),
+ }
+
+ def define_protocol(self):
+ """Set protocol based on use_https parameters."""
+ if self.params['use_https'] is True:
+ self.params['protocol'] = 'https'
+ else:
+ self.params['protocol'] = 'http'
+
+ def sanitize_keys(self, data):
+ if isinstance(data, dict):
+ items = {}
+ for k, v in data.items():
+ try:
+ items[self.key_map[k]] = self.sanitize_keys(data[k])
+ except KeyError:
+ snake_k = re.sub('([a-z0-9])([A-Z])', r'\1_\2', k).lower()
+ # new = {snake_k: data[k]}
+ items[snake_k] = self.sanitize_keys(data[k])
+ return items
+ elif isinstance(data, list):
+ items = []
+ for i in data:
+ items.append(self.sanitize_keys(i))
+ return items
+ elif isinstance(data, int) or isinstance(data, str) or isinstance(data, float):
+ return data
+
+ def is_update_required(self, original, proposed, optional_ignore=None, force_include=None, debug=False):
+ ''' Compare two data-structures '''
+ self.ignored_keys.append('net_id')
+ if force_include is not None:
+ if force_include in self.ignored_keys:
+ self.ignored_keys.remove(force_include)
+ if optional_ignore is not None:
+ # self.fail_json(msg="Keys", ignored_keys=self.ignored_keys, optional=optional_ignore)
+ self.ignored_keys = self.ignored_keys + optional_ignore
+
+ if isinstance(original, list):
+ if len(original) != len(proposed):
+ if debug is True:
+ self.fail_json(msg="Length of lists don't match")
+ return True
+ for a, b in zip(original, proposed):
+ if self.is_update_required(a, b, debug=debug):
+ if debug is True:
+ self.fail_json(msg="List doesn't match", a=a, b=b)
+ return True
+ elif isinstance(original, dict):
+ try:
+ for k, v in proposed.items():
+ if k not in self.ignored_keys:
+ if k in original:
+ if self.is_update_required(original[k], proposed[k], debug=debug):
+ return True
+ else:
+ if debug is True:
+ self.fail_json(msg="Key not in original", k=k)
+ return True
+ except AttributeError:
+ return True
+ else:
+ if original != proposed:
+ if debug is True:
+ self.fail_json(msg="Fallback", original=original, proposed=proposed)
+ return True
+ return False
+
+ def generate_diff(self, before, after):
+ """Creates a diff based on two objects. Applies to the object and returns nothing.
+ """
+ try:
+ diff = recursive_diff(before, after)
+ self.result['diff'] = {'before': diff[0],
+ 'after': diff[1]}
+ except AttributeError: # Normally for passing a list instead of a dict
+ diff = recursive_diff({'data': before},
+ {'data': after})
+ self.result['diff'] = {'before': diff[0]['data'],
+ 'after': diff[1]['data']}
+
+ def get_orgs(self):
+ """Downloads all organizations for a user."""
+ response = self.request('/organizations', method='GET')
+ if self.status != 200:
+ self.fail_json(msg='Organization lookup failed')
+ self.orgs = response
+ return self.orgs
+
+ def is_org_valid(self, data, org_name=None, org_id=None):
+ """Checks whether a specific org exists and is duplicated.
+
+ If 0, doesn't exist. 1, exists and not duplicated. >1 duplicated.
+ """
+ org_count = 0
+ if org_name is not None:
+ for o in data:
+ if o['name'] == org_name:
+ org_count += 1
+ if org_id is not None:
+ for o in data:
+ if o['id'] == org_id:
+ org_count += 1
+ return org_count
+
+ def get_org_id(self, org_name):
+ """Returns an organization id based on organization name, only if unique.
+
+ If org_id is specified as parameter, return that instead of a lookup.
+ """
+ orgs = self.get_orgs()
+ # self.fail_json(msg='ogs', orgs=orgs)
+ if self.params['org_id'] is not None:
+ if self.is_org_valid(orgs, org_id=self.params['org_id']) is True:
+ return self.params['org_id']
+ org_count = self.is_org_valid(orgs, org_name=org_name)
+ if org_count == 0:
+ self.fail_json(msg='There are no organizations with the name {org_name}'.format(org_name=org_name))
+ if org_count > 1:
+ self.fail_json(msg='There are multiple organizations with the name {org_name}'.format(org_name=org_name))
+ elif org_count == 1:
+ for i in orgs:
+ if org_name == i['name']:
+ # self.fail_json(msg=i['id'])
+ return str(i['id'])
+
+ def get_nets(self, org_name=None, org_id=None):
+ """Downloads all networks in an organization."""
+ if org_name:
+ org_id = self.get_org_id(org_name)
+ path = self.construct_path('get_all', org_id=org_id, function='network', params={'perPage': '1000'})
+ r = self.request(path, method='GET', pagination_items=1000)
+ if self.status != 200:
+ self.fail_json(msg='Network lookup failed')
+ self.nets = r
+ templates = self.get_config_templates(org_id)
+ for t in templates:
+ self.nets.append(t)
+ return self.nets
+
+ def get_net(self, org_name, net_name=None, org_id=None, data=None, net_id=None):
+ ''' Return network information '''
+ if not data:
+ if not org_id:
+ org_id = self.get_org_id(org_name)
+ data = self.get_nets(org_id=org_id)
+ for n in data:
+ if net_id:
+ if n['id'] == net_id:
+ return n
+ elif net_name:
+ if n['name'] == net_name:
+ return n
+ return False
+
+ def get_net_id(self, org_name=None, net_name=None, data=None):
+ """Return network id from lookup or existing data."""
+ if data is None:
+ self.fail_json(msg='Must implement lookup')
+ for n in data:
+ if n['name'] == net_name:
+ return n['id']
+ self.fail_json(msg='No network found with the name {0}'.format(net_name))
+
+ def get_config_templates(self, org_id):
+ path = self.construct_path('get_all', function='configTemplates', org_id=org_id)
+ response = self.request(path, 'GET')
+ if self.status != 200:
+ self.fail_json(msg='Unable to get configuration templates')
+ return response
+
+ def get_template_id(self, name, data):
+ for template in data:
+ if name == template['name']:
+ return template['id']
+ self.fail_json(msg='No configuration template named {0} found'.format(name))
+
+ def convert_camel_to_snake(self, data):
+ """
+ Converts a dictionary or list to snake case from camel case
+ :type data: dict or list
+ :return: Converted data structure, if list or dict
+ """
+
+ if isinstance(data, dict):
+ return camel_dict_to_snake_dict(data, ignore_list=('tags', 'tag'))
+ elif isinstance(data, list):
+ return [camel_dict_to_snake_dict(item, ignore_list=('tags', 'tag')) for item in data]
+ else:
+ return data
+
+ def convert_snake_to_camel(self, data):
+ """
+ Converts a dictionary or list to camel case from snake case
+ :type data: dict or list
+ :return: Converted data structure, if list or dict
+ """
+
+ if isinstance(data, dict):
+ return snake_dict_to_camel_dict(data)
+ elif isinstance(data, list):
+ return [snake_dict_to_camel_dict(item) for item in data]
+ else:
+ return data
+
+ def construct_params_list(self, keys, aliases=None):
+ qs = {}
+ for key in keys:
+ if key in aliases:
+ qs[aliases[key]] = self.module.params[key]
+ else:
+ qs[key] = self.module.params[key]
+ return qs
+
+ def encode_url_params(self, params):
+ """Encodes key value pairs for URL"""
+ return "?{0}".format(urlencode(params))
+
+ def construct_path(self,
+ action,
+ function=None,
+ org_id=None,
+ net_id=None,
+ org_name=None,
+ custom=None,
+ params=None):
+ """Build a path from the URL catalog.
+ Uses function property from class for catalog lookup.
+ """
+ built_path = None
+ if function is None:
+ built_path = self.url_catalog[action][self.function]
+ else:
+ built_path = self.url_catalog[action][function]
+ if org_name:
+ org_id = self.get_org_id(org_name)
+ if custom:
+ built_path = built_path.format(org_id=org_id, net_id=net_id, **custom)
+ else:
+ built_path = built_path.format(org_id=org_id, net_id=net_id)
+ if params:
+ built_path += self.encode_url_params(params)
+ return built_path
+
+ def _set_url(self, path, method, params):
+ self.path = path
+ self.define_protocol()
+
+ if method is not None:
+ self.method = method
+
+ self.url = '{protocol}://{host}/api/v1/{path}'.format(path=self.path.lstrip('/'), **self.params)
+
+ @staticmethod
+ def _parse_pagination_header(link):
+ rels = {'first': None,
+ 'next': None,
+ 'prev': None,
+ 'last': None
+ }
+ for rel in link.split(','):
+ kv = rel.split('rel=')
+ rels[kv[1]] = kv[0].split('<')[1].split('>')[0].strip() # This should return just the URL for <url>
+ return rels
+
+ def _execute_request(self, path, method=None, payload=None, params=None):
+ """ Execute query """
+ try:
+ resp, info = fetch_url(self.module, self.url,
+ headers=self.headers,
+ data=payload,
+ method=self.method,
+ timeout=self.params['timeout'],
+ use_proxy=self.params['use_proxy'],
+ )
+ self.status = info['status']
+
+ if self.status == 429:
+ self.retry += 1
+ if self.retry <= 10:
+ # retry-after isn't returned for over 10 concurrent connections per IP
+ try:
+ self.module.warn("Rate limiter hit, retry {0}...pausing for {1} seconds".format(self.retry, info['Retry-After']))
+ time.sleep(info['Retry-After'])
+ except KeyError:
+ self.module.warn("Rate limiter hit, retry {0}...pausing for 5 seconds".format(self.retry))
+ time.sleep(5)
+ return self._execute_request(path, method=method, payload=payload, params=params)
+ else:
+ self.fail_json(msg="Rate limit retries failed for {url}".format(url=self.url))
+ elif self.status == 500:
+ self.retry += 1
+ self.module.warn("Internal server error 500, retry {0}".format(self.retry))
+ if self.retry <= 10:
+ self.retry_time += self.retry * INTERNAL_ERROR_RETRY_MULTIPLIER
+ time.sleep(self.retry_time)
+ return self._execute_request(path, method=method, payload=payload, params=params)
+ else:
+ # raise RateLimitException(e)
+ self.fail_json(msg="Rate limit retries failed for {url}".format(url=self.url))
+ elif self.status == 502:
+ self.module.warn("Internal server error 502, retry {0}".format(self.retry))
+ elif self.status == 400:
+ raise HTTPError("")
+ elif self.status >= 400:
+ self.fail_json(msg=self.status, url=self.url)
+ raise HTTPError("")
+ except HTTPError:
+ try:
+ self.fail_json(msg="HTTP error {0} - {1} - {2}".format(self.status, self.url, json.loads(info['body'])['errors'][0]))
+ except json.decoder.JSONDecodeError:
+ self.fail_json(msg="HTTP error {0} - {1}".format(self.status, self.url))
+ self.retry = 0 # Needs to reset in case of future retries
+ return resp, info
+
+ def request(self, path, method=None, payload=None, params=None, pagination_items=None):
+ """ Submit HTTP request to Meraki API """
+ self._set_url(path, method, params)
+
+ try:
+ # Gather the body (resp) and header (info)
+ resp, info = self._execute_request(path, method=method, payload=payload, params=params)
+ except HTTPError:
+ self.fail_json(msg="HTTP request to {url} failed with error code {code}".format(url=self.url, code=self.status))
+ self.response = info['msg']
+ self.status = info['status']
+ # This needs to be refactored as it's not very clean
+ # Looping process for pagination
+ if pagination_items is not None:
+ data = None
+ if 'body' in info:
+ self.body = info['body']
+ try:
+ data = json.loads(to_native(resp.read()))
+ except AttributeError:
+ self.fail_json(msg="Failure occurred during pagination",
+ response=self.response,
+ status=self.status,
+ body=self.body
+ )
+ header_link = self._parse_pagination_header(info['link'])
+ while header_link['next'] is not None:
+ self.url = header_link['next']
+ try:
+ # Gather the body (resp) and header (info)
+ resp, info = self._execute_request(header_link['next'], method=method, payload=payload, params=params)
+ except HTTPError:
+ self.fail_json(msg="HTTP request to {url} failed with error code {code}".format(url=self.url, code=self.status))
+ header_link = self._parse_pagination_header(info['link'])
+ try:
+ data.extend(json.loads(to_native(resp.read())))
+ except AttributeError:
+ self.fail_json(msg="Failure occurred during pagination",
+ response=self.response,
+ status=self.status,
+ body=self.body
+ )
+ return data
+ else: # Non-pagination
+ if 'body' in info:
+ self.body = info['body']
+ try:
+ return json.loads(to_native(resp.read()))
+ except json.decoder.JSONDecodeError:
+ return {}
+ except AttributeError:
+ self.fail_json(msg="Failure occurred",
+ response=self.response,
+ status=self.status,
+ body=self.body
+ )
+
+ def exit_json(self, **kwargs):
+ """Custom written method to exit from module."""
+ self.result['response'] = self.response
+ self.result['status'] = self.status
+ if self.retry > 0:
+ self.module.warn("Rate limiter triggered - retry count {0}".format(self.retry))
+ # Return the gory details when we need it
+ if self.params['output_level'] == 'debug':
+ self.result['method'] = self.method
+ self.result['url'] = self.url
+ self.result.update(**kwargs)
+ if self.params['output_format'] == 'camelcase':
+ self.module.deprecate("Update your playbooks to support snake_case format instead of camelCase format.",
+ date="2022-06-01",
+ collection_name="cisco.meraki")
+ else:
+ if 'data' in self.result:
+ try:
+ self.result['data'] = self.convert_camel_to_snake(self.result['data'])
+ self.result['diff'] = self.convert_camel_to_snake(self.result['diff'])
+ except (KeyError, AttributeError):
+ pass
+ self.module.exit_json(**self.result)
+
+ def fail_json(self, msg, **kwargs):
+ """Custom written method to return info on failure."""
+ self.result['response'] = self.response
+ self.result['status'] = self.status
+
+ if self.params['output_level'] == 'debug':
+ if self.url is not None:
+ self.result['method'] = self.method
+ self.result['url'] = self.url
+
+ self.result.update(**kwargs)
+ self.module.fail_json(msg=msg, **self.result)
diff --git a/ansible_collections/cisco/meraki/plugins/modules/__init__.py b/ansible_collections/cisco/meraki/plugins/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/__init__.py
diff --git a/ansible_collections/cisco/meraki/plugins/modules/meraki_action_batch.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_action_batch.py
new file mode 100644
index 000000000..3e0bed937
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_action_batch.py
@@ -0,0 +1,394 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_action_batch
+short_description: Manage Action Batch jobs within the Meraki Dashboard.
+description:
+- Allows for management of Action Batch jobs for Meraki.
+notes:
+- This module is in active development and the interface may change.
+options:
+ state:
+ description:
+ - Specifies whether to lookup, create, or delete an Action Batch job.
+ choices: ['query', 'present', 'absent']
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of network, if applicable.
+ type: str
+ net_id:
+ description:
+ - ID of network, if applicable.
+ type: str
+ action_batch_id:
+ description:
+ - ID of an existing Action Batch job.
+ type: str
+ confirmed:
+ description:
+ - Whether job is to be executed.
+ type: bool
+ default: False
+ synchronous:
+ description:
+ - Whether job is a synchronous or asynchronous job.
+ type: bool
+ default: True
+ actions:
+ description:
+ - List of actions the job should execute.
+ type: list
+ elements: dict
+ suboptions:
+ operation:
+ description:
+ - Operation type of action
+ type: str
+ choices: [
+ 'create',
+ 'destroy',
+ 'update',
+ 'claim',
+ 'bind',
+ 'split',
+ 'unbind',
+ 'combine',
+ 'update_order',
+ 'cycle',
+ 'swap',
+ 'assignSeats',
+ 'move',
+ 'moveSeats',
+ 'renewSeats'
+ ]
+ resource:
+ description:
+ - Path to Action Batch resource.
+ type: str
+ body:
+ description:
+ - Required body of action.
+ type: raw
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+
+EXAMPLES = r"""
+ - name: Query all Action Batches
+ meraki_action_batch:
+ auth_key: abc123
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+ - name: Query one Action Batch job
+ meraki_action_batch:
+ auth_key: abc123
+ org_name: YourOrg
+ state: query
+ action_batch_id: 12345
+ delegate_to: localhost
+
+ - name: Create an Action Batch job
+ meraki_action_batch:
+ auth_key: abc123
+ org_name: YourOrg
+ state: present
+ actions:
+ - resource: '/organizations/org_123/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch1'
+ productTypes:
+ - 'switch'
+ delegate_to: localhost
+
+ - name: Update Action Batch job
+ meraki_action_batch:
+ auth_key: abc123
+ org_name: YourOrg
+ state: present
+ action_batch_id: 12345
+ synchronous: false
+
+ - name: Create an Action Batch job with multiple actions
+ meraki_action_batch:
+ auth_key: abc123
+ org_name: YourOrg
+ state: present
+ actions:
+ - resource: '/organizations/org_123/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch2'
+ productTypes:
+ - 'switch'
+ - resource: '/organizations/org_123/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch3'
+ productTypes:
+ - 'switch'
+ delegate_to: localhost
+
+ - name: Delete an Action Batch job
+ meraki_action_batch:
+ auth_key: abc123
+ org_name: YourOrg
+ state: absent
+ action_batch_id: 12345
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: Information about action batch jobs.
+ type: complex
+ returned: always
+ contains:
+ id:
+ description: Unique ID of action batch job.
+ returned: success
+ type: str
+ sample: 123
+ organization_id:
+ description: Unique ID of organization which owns batch job.
+ returned: success
+ type: str
+ sample: 2930418
+ confirmed:
+ description: Whether action batch job was confirmed for execution.
+ returned: success
+ type: bool
+ synchronous:
+ description: Whether action batch job executes synchronously or asynchronously.
+ returned: success
+ type: bool
+ status:
+ description: Information about the action batch job state.
+ type: complex
+ contains:
+ completed:
+ description: Whether job has completed.
+ type: bool
+ returned: success
+ failed:
+ description: Whether execution of action batch job failed.
+ type: bool
+ returned: success
+ errors:
+ description: List of errors, if any, created during execution.
+ type: list
+ returned: success
+ created_resources:
+ description: List of resources created during execution.
+ type: list
+ returned: success
+ sample: [{"id": 100, "uri": "/networks/L_XXXXX/groupPolicies/100"}]
+ actions:
+ description: List of actions associated to job.
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def _construct_payload(meraki):
+ payload = dict()
+ payload["confirmed"] = meraki.params["confirmed"]
+ payload["synchronous"] = meraki.params["synchronous"]
+ if meraki.params["actions"] is not None: # No payload is specified for an update
+ payload["actions"] = list()
+ for action in meraki.params["actions"]:
+ action_detail = dict()
+ if action["resource"] is not None:
+ action_detail["resource"] = action["resource"]
+ if action["operation"] is not None:
+ action_detail["operation"] = action["operation"]
+ if action["body"] is not None:
+ action_detail["body"] = action["body"]
+ payload["actions"].append(action_detail)
+ return payload
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ actions_arg_spec = dict(
+ operation=dict(
+ type="str",
+ choices=[
+ "create",
+ "destroy",
+ "update",
+ "claim",
+ "bind",
+ "split",
+ "unbind",
+ "combine",
+ "update_order",
+ "cycle",
+ "swap",
+ "assignSeats",
+ "move",
+ "moveSeats",
+ "renewSeats",
+ ],
+ ),
+ resource=dict(type="str"),
+ body=dict(type="raw"),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ state=dict(
+ type="str", choices=["present", "query", "absent"], default="present"
+ ),
+ net_name=dict(type="str"),
+ net_id=dict(type="str"),
+ action_batch_id=dict(type="str", default=None),
+ confirmed=dict(type="bool", default=False),
+ synchronous=dict(type="bool", default=True),
+ actions=dict(
+ type="list", default=None, elements="dict", options=actions_arg_spec
+ ),
+ )
+
+ # 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="action_batch")
+ meraki.params["follow_redirects"] = "all"
+
+ query_urls = {"action_batch": "/organizations/{org_id}/actionBatches"}
+ query_one_urls = {
+ "action_batch": "/organizations/{org_id}/actionBatches/{action_batch_id}"
+ }
+ create_urls = {"action_batch": "/organizations/{org_id}/actionBatches"}
+ update_urls = {
+ "action_batch": "/organizations/{org_id}/actionBatches/{action_batch_id}"
+ }
+ delete_urls = {
+ "action_batch": "/organizations/{org_id}/actionBatches/{action_batch_id}"
+ }
+
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["get_one"].update(query_one_urls)
+ meraki.url_catalog["create"] = create_urls
+ meraki.url_catalog["update"] = update_urls
+ meraki.url_catalog["delete"] = delete_urls
+
+ payload = None
+
+ if not meraki.params["org_name"] and not meraki.params["org_id"]:
+ meraki.fail_json(msg="org_name or org_id is required")
+
+ org_id = meraki.params["org_id"]
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+
+ if meraki.params["state"] == "query":
+ if meraki.params["action_batch_id"] is None: # Get all Action Batches
+ path = meraki.construct_path("get_all", org_id=org_id)
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["action_batch_id"] is not None: # Query one Action Batch job
+ path = meraki.construct_path(
+ "get_one",
+ org_id=org_id,
+ custom={"action_batch_id": meraki.params["action_batch_id"]},
+ )
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["state"] == "present":
+ if meraki.params["action_batch_id"] is None: # Create a new Action Batch job
+ payload = _construct_payload(meraki)
+ path = meraki.construct_path("create", org_id=org_id)
+ response = meraki.request(path, method="POST", payload=json.dumps(payload))
+ if meraki.status == 201:
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["action_batch_id"] is not None:
+ path = meraki.construct_path(
+ "get_one",
+ org_id=org_id,
+ custom={"action_batch_id": meraki.params["action_batch_id"]},
+ )
+ current = meraki.request(path, method="GET")
+ payload = _construct_payload(meraki)
+ if (
+ meraki.params["actions"] is not None
+ ): # Cannot update the body once a job is submitted
+ meraki.fail_json(msg="Body cannot be updated on existing job.")
+ if (
+ meraki.is_update_required(current, payload) is True
+ ): # Job needs to be modified
+ path = meraki.construct_path(
+ "update",
+ org_id=org_id,
+ custom={"action_batch_id": meraki.params["action_batch_id"]},
+ )
+ response = meraki.request(
+ path, method="PUT", payload=json.dumps(payload)
+ )
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ else: # Idempotent response
+ meraki.result["data"] = current
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["state"] == "absent":
+ path = meraki.construct_path(
+ "delete",
+ org_id=org_id,
+ custom={"action_batch_id": meraki.params["action_batch_id"]},
+ )
+ response = meraki.request(path, method="DELETE")
+ if meraki.status == 204:
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_admin.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_admin.py
new file mode 100644
index 000000000..e554bb009
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_admin.py
@@ -0,0 +1,504 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_admin
+short_description: Manage administrators in the Meraki cloud
+version_added: '1.0.0'
+description:
+- Allows for creation, management, and visibility into administrators within Meraki.
+options:
+ name:
+ description:
+ - Name of the dashboard administrator.
+ - Required when creating a new administrator.
+ type: str
+ email:
+ description:
+ - Email address for the dashboard administrator.
+ - Email cannot be updated.
+ - Required when creating or editing an administrator.
+ type: str
+ org_access:
+ description:
+ - Privileges assigned to the administrator in the organization.
+ aliases: [ orgAccess ]
+ choices: [ full, none, read-only ]
+ type: str
+ tags:
+ description:
+ - Tags the administrator has privileges on.
+ - When creating a new administrator, C(org_name), C(network), or C(tags) must be specified.
+ - If C(none) is specified, C(network) or C(tags) must be specified.
+ type: list
+ elements: dict
+ suboptions:
+ tag:
+ description:
+ - Object tag which privileges should be assigned.
+ type: str
+ access:
+ description:
+ - The privilege of the dashboard administrator for the tag.
+ type: str
+ networks:
+ description:
+ - List of networks the administrator has privileges on.
+ - When creating a new administrator, C(org_name), C(network), or C(tags) must be specified.
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description:
+ - Network ID for which administrator should have privileges assigned.
+ type: str
+ network:
+ description:
+ - Network name for which administrator should have privileges assigned.
+ type: str
+ access:
+ description:
+ - The privilege of the dashboard administrator on the network.
+ - Valid options are C(full), C(read-only), or C(none).
+ type: str
+ state:
+ description:
+ - Create or modify, or delete an organization
+ - If C(state) is C(absent), name takes priority over email if both are specified.
+ choices: [ absent, present, query ]
+ required: true
+ type: str
+ org_name:
+ description:
+ - Name of organization.
+ - Used when C(name) should refer to another object.
+ - When creating a new administrator, C(org_name), C(network), or C(tags) must be specified.
+ aliases: ['organization']
+ type: str
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query information about all administrators associated to the organization
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+- name: Query information about a single administrator by name
+ meraki_admin:
+ auth_key: abc12345
+ org_id: 12345
+ state: query
+ name: Jane Doe
+
+- name: Query information about a single administrator by email
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ email: jane@doe.com
+
+- name: Create new administrator with organization access
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ name: Jane Doe
+ org_access: read-only
+ email: jane@doe.com
+
+- name: Create new administrator with organization access
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ name: Jane Doe
+ org_access: read-only
+ email: jane@doe.com
+
+- name: Create a new administrator with organization access
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ name: Jane Doe
+ org_access: read-only
+ email: jane@doe.com
+
+- name: Revoke access to an organization for an administrator
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: absent
+ email: jane@doe.com
+
+- name: Create a new administrator with full access to two tags
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ name: Jane Doe
+ orgAccess: read-only
+ email: jane@doe.com
+ tags:
+ - tag: tenant
+ access: full
+ - tag: corporate
+ access: read-only
+
+- name: Create a new administrator with full access to a network
+ meraki_admin:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ name: Jane Doe
+ orgAccess: read-only
+ email: jane@doe.com
+ networks:
+ - id: N_12345
+ access: full
+'''
+
+RETURN = r'''
+data:
+ description: List of administrators.
+ returned: success
+ type: complex
+ contains:
+ email:
+ description: Email address of administrator.
+ returned: success
+ type: str
+ sample: your@email.com
+ id:
+ description: Unique identification number of administrator.
+ returned: success
+ type: str
+ sample: 1234567890
+ name:
+ description: Given name of administrator.
+ returned: success
+ type: str
+ sample: John Doe
+ account_status:
+ description: Status of account.
+ returned: success
+ type: str
+ sample: ok
+ two_factor_auth_enabled:
+ description: Enabled state of two-factor authentication for administrator.
+ returned: success
+ type: bool
+ sample: false
+ has_api_key:
+ description: Defines whether administrator has an API assigned to their account.
+ returned: success
+ type: bool
+ sample: false
+ last_active:
+ description: Date and time of time the administrator was active within Dashboard.
+ returned: success
+ type: str
+ sample: 2019-01-28 14:58:56 -0800
+ networks:
+ description: List of networks administrator has access on.
+ returned: success
+ type: complex
+ contains:
+ id:
+ description: The network ID.
+ returned: when network permissions are set
+ type: str
+ sample: N_0123456789
+ access:
+ description: Access level of administrator. Options are 'full', 'read-only', or 'none'.
+ returned: when network permissions are set
+ type: str
+ sample: read-only
+ tags:
+ description: Tags the administrator has access on.
+ returned: success
+ type: complex
+ contains:
+ tag:
+ description: Tag name.
+ returned: when tag permissions are set
+ type: str
+ sample: production
+ access:
+ description: Access level of administrator. Options are 'full', 'read-only', or 'none'.
+ returned: when tag permissions are set
+ type: str
+ sample: full
+ org_access:
+ description: The privilege of the dashboard administrator on the organization. Options are 'full', 'read-only', or 'none'.
+ returned: success
+ type: str
+ sample: full
+
+'''
+
+import os
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def get_admins(meraki, org_id):
+ admins = meraki.request(
+ meraki.construct_path(
+ 'query',
+ function='admin',
+ org_id=org_id
+ ),
+ method='GET'
+ )
+ if meraki.status == 200:
+ return admins
+
+
+def get_admin_id(meraki, data, name=None, email=None):
+ admin_id = None
+ for a in data:
+ if meraki.params['name'] is not None:
+ if meraki.params['name'] == a['name']:
+ if admin_id is not None:
+ meraki.fail_json(msg='There are multiple administrators with the same name')
+ else:
+ admin_id = a['id']
+ elif meraki.params['email']:
+ if meraki.params['email'] == a['email']:
+ return a['id']
+ if admin_id is None:
+ meraki.fail_json(msg='No admin_id found')
+ return admin_id
+
+
+def get_admin(meraki, data, id):
+ for a in data:
+ if a['id'] == id:
+ return a
+ meraki.fail_json(msg='No admin found by specified name or email')
+
+
+def find_admin(meraki, data, email):
+ for a in data:
+ if a['email'] == email:
+ return a
+ return None
+
+
+def delete_admin(meraki, org_id, admin_id):
+ path = meraki.construct_path('revoke', 'admin', org_id=org_id) + admin_id
+ r = meraki.request(path,
+ method='DELETE'
+ )
+ if meraki.status == 204:
+ return r
+
+
+def network_factory(meraki, networks, nets):
+ networks_new = []
+ for n in networks:
+ if 'network' in n and n['network'] is not None:
+ networks_new.append({'id': meraki.get_net_id(org_name=meraki.params['org_name'],
+ net_name=n['network'],
+ data=nets),
+ 'access': n['access']
+ })
+ elif 'id' in n:
+ networks_new.append({'id': n['id'],
+ 'access': n['access']
+ })
+
+ return networks_new
+
+
+def create_admin(meraki, org_id, name, email):
+ payload = dict()
+ payload['name'] = name
+ payload['email'] = email
+
+ is_admin_existing = find_admin(meraki, get_admins(meraki, org_id), email)
+
+ if meraki.params['org_access'] is not None:
+ payload['orgAccess'] = meraki.params['org_access']
+ if meraki.params['tags'] is not None:
+ payload['tags'] = meraki.params['tags']
+ if meraki.params['networks'] is not None:
+ nets = meraki.get_nets(org_id=org_id)
+ networks = network_factory(meraki, meraki.params['networks'], nets)
+ payload['networks'] = networks
+ if is_admin_existing is None: # Create new admin
+ if meraki.module.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('create', function='admin', org_id=org_id)
+ r = meraki.request(path,
+ method='POST',
+ payload=json.dumps(payload)
+ )
+ if meraki.status == 201:
+ meraki.result['changed'] = True
+ return r
+ elif is_admin_existing is not None: # Update existing admin
+ if not meraki.params['tags']:
+ payload['tags'] = []
+ if not meraki.params['networks']:
+ payload['networks'] = []
+ if meraki.is_update_required(is_admin_existing, payload) is True:
+ if meraki.module.check_mode is True:
+ meraki.generate_diff(is_admin_existing, payload)
+ is_admin_existing.update(payload)
+ meraki.result['changed'] = True
+ meraki.result['data'] = payload
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', function='admin', org_id=org_id) + is_admin_existing['id']
+ r = meraki.request(path,
+ method='PUT',
+ payload=json.dumps(payload)
+ )
+ if meraki.status == 200:
+ meraki.result['changed'] = True
+ return r
+ else:
+ meraki.result['data'] = is_admin_existing
+ if meraki.module.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.exit_json(**meraki.result)
+ return -1
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ network_arg_spec = dict(id=dict(type='str'),
+ network=dict(type='str'),
+ access=dict(type='str'),
+ )
+
+ tag_arg_spec = dict(tag=dict(type='str'),
+ access=dict(type='str'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query', 'absent'], required=True),
+ name=dict(type='str'),
+ email=dict(type='str'),
+ org_access=dict(type='str', aliases=['orgAccess'], choices=['full', 'read-only', 'none']),
+ tags=dict(type='list', elements='dict', options=tag_arg_spec),
+ networks=dict(type='list', elements='dict', options=network_arg_spec),
+ org_name=dict(type='str', aliases=['organization']),
+ org_id=dict(type='str'),
+ )
+
+ # 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='admin')
+
+ meraki.function = 'admin'
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'admin': '/organizations/{org_id}/admins',
+ }
+ create_urls = {'admin': '/organizations/{org_id}/admins',
+ }
+ update_urls = {'admin': '/organizations/{org_id}/admins/',
+ }
+ revoke_urls = {'admin': '/organizations/{org_id}/admins/',
+ }
+
+ meraki.url_catalog['query'] = query_urls
+ meraki.url_catalog['create'] = create_urls
+ meraki.url_catalog['update'] = update_urls
+ meraki.url_catalog['revoke'] = revoke_urls
+
+ try:
+ meraki.params['auth_key'] = os.environ['MERAKI_KEY']
+ except KeyError:
+ pass
+
+ # if the user is working with this module in only check mode we do not
+ # want to make any changes to the environment, just return the current
+ # state with no modifications
+
+ # execute checks for argument completeness
+ if meraki.params['state'] == 'query':
+ meraki.mututally_exclusive = ['name', 'email']
+ if not meraki.params['org_name'] and not meraki.params['org_id']:
+ meraki.fail_json(msg='org_name or org_id required')
+ meraki.required_if = [(['state'], ['absent'], ['email']),
+ ]
+
+ # 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']
+ if not meraki.params['org_id']:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ if meraki.params['state'] == 'query':
+ admins = get_admins(meraki, org_id)
+ if not meraki.params['name'] and not meraki.params['email']: # Return all admins for org
+ meraki.result['data'] = admins
+ if meraki.params['name'] is not None: # Return a single admin for org
+ admin_id = get_admin_id(meraki, admins, name=meraki.params['name'])
+ meraki.result['data'] = admin_id
+ admin = get_admin(meraki, admins, admin_id)
+ meraki.result['data'] = admin
+ elif meraki.params['email'] is not None:
+ admin_id = get_admin_id(meraki, admins, email=meraki.params['email'])
+ meraki.result['data'] = admin_id
+ admin = get_admin(meraki, admins, admin_id)
+ meraki.result['data'] = admin
+ elif meraki.params['state'] == 'present':
+ r = create_admin(meraki,
+ org_id,
+ meraki.params['name'],
+ meraki.params['email'],
+ )
+ if r != -1:
+ meraki.result['data'] = r
+ elif meraki.params['state'] == 'absent':
+ if meraki.module.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ admin_id = get_admin_id(meraki,
+ get_admins(meraki, org_id),
+ email=meraki.params['email']
+ )
+ r = delete_admin(meraki, org_id, admin_id)
+
+ if r != -1:
+ meraki.result['data'] = r
+ meraki.result['changed'] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py
new file mode 100644
index 000000000..e9dfe0f12
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py
@@ -0,0 +1,395 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, 2019 Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_alert
+version_added: "2.1.0"
+short_description: Manage alerts in the Meraki cloud
+description:
+- Allows for creation, management, and visibility into alert settings within Meraki.
+options:
+ state:
+ description:
+ - Create or modify an alert.
+ choices: [ present, query ]
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [ name, network ]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ default_destinations:
+ description:
+ - Properties for destinations when alert specific destinations aren't specified.
+ type: dict
+ suboptions:
+ all_admins:
+ description:
+ - If true, all network admins will receive emails.
+ type: bool
+ snmp:
+ description:
+ - If true, then an SNMP trap will be sent if there is an SNMP trap server configured for this network.
+ type: bool
+ emails:
+ description:
+ - A list of emails that will recieve the alert(s).
+ type: list
+ elements: str
+ http_server_ids:
+ description:
+ - A list of HTTP server IDs to send a Webhook to.
+ type: list
+ elements: str
+ alerts:
+ description:
+ - Alert-specific configuration for each type.
+ type: list
+ elements: dict
+ suboptions:
+ alert_type:
+ description:
+ - The type of alert.
+ type: str
+ enabled:
+ description:
+ - A boolean depicting if the alert is turned on or off.
+ type: bool
+ filters:
+ description:
+ - A hash of specific configuration data for the alert. Only filters specific to the alert will be updated.
+ - No validation checks occur against C(filters).
+ type: raw
+ default: {}
+ alert_destinations:
+ description:
+ - A hash of destinations for this specific alert.
+ type: dict
+ suboptions:
+ all_admins:
+ description:
+ - If true, all network admins will receive emails.
+ type: bool
+ snmp:
+ description:
+ - If true, then an SNMP trap will be sent if there is an SNMP trap server configured for this network.
+ type: bool
+ emails:
+ description:
+ - A list of emails that will recieve the alert(s).
+ type: list
+ elements: str
+ http_server_ids:
+ description:
+ - A list of HTTP server IDs to send a Webhook to.
+ type: list
+ elements: str
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Update settings
+ meraki_alert:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ default_destinations:
+ emails:
+ - 'youremail@yourcorp'
+ - 'youremail2@yourcorp'
+ all_admins: yes
+ snmp: no
+ alerts:
+ - alert_type: "gatewayDown"
+ enabled: yes
+ filters:
+ timeout: 60
+ alert_destinations:
+ emails:
+ - 'youremail@yourcorp'
+ - 'youremail2@yourcorp'
+ all_admins: yes
+ snmp: no
+ - alert_type: "usageAlert"
+ enabled: yes
+ filters:
+ period: 1200
+ threshold: 104857600
+ alert_destinations:
+ emails:
+ - 'youremail@yourcorp'
+ - 'youremail2@yourcorp'
+ all_admins: yes
+ snmp: no
+
+- name: Query all settings
+ meraki_alert:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: Information about the created or manipulated object.
+ returned: info
+ type: complex
+ contains:
+ default_destinations:
+ description: Properties for destinations when alert specific destinations aren't specified.
+ returned: success
+ type: complex
+ contains:
+ all_admins:
+ description: If true, all network admins will receive emails.
+ type: bool
+ sample: true
+ returned: success
+ snmp:
+ description: If true, then an SNMP trap will be sent if there is an SNMP trap server configured for this network.
+ type: bool
+ sample: true
+ returned: success
+ emails:
+ description: A list of emails that will recieve the alert(s).
+ type: list
+ returned: success
+ http_server_ids:
+ description: A list of HTTP server IDs to send a Webhook to.
+ type: list
+ returned: success
+ alerts:
+ description: Alert-specific configuration for each type.
+ type: complex
+ contains:
+ alert_type:
+ description: The type of alert.
+ type: str
+ returned: success
+ enabled:
+ description: A boolean depicting if the alert is turned on or off.
+ type: bool
+ returned: success
+ filters:
+ description:
+ - A hash of specific configuration data for the alert. Only filters specific to the alert will be updated.
+ - No validation checks occur against C(filters).
+ type: complex
+ returned: success
+ alert_destinations:
+ description: A hash of destinations for this specific alert.
+ type: complex
+ contains:
+ all_admins:
+ description: If true, all network admins will receive emails.
+ type: bool
+ returned: success
+ snmp:
+ description: If true, then an SNMP trap will be sent if there is an SNMP trap server configured for this network.
+ type: bool
+ returned: success
+ emails:
+ description: A list of emails that will recieve the alert(s).
+ type: list
+ returned: success
+ http_server_ids:
+ description: A list of HTTP server IDs to send a Webhook to.
+ type: list
+ returned: success
+"""
+
+import copy
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def get_alert_by_type(type, meraki):
+ for alert in meraki.params["alerts"]:
+ if alert["alert_type"] == type:
+ return alert
+ return None
+
+
+def construct_payload(meraki, current):
+ payload = {}
+ if meraki.params["default_destinations"] is not None:
+ payload["defaultDestinations"] = {}
+ if meraki.params["default_destinations"]["all_admins"] is not None:
+ payload["defaultDestinations"]["allAdmins"] = meraki.params["default_destinations"]["all_admins"]
+ if meraki.params["default_destinations"]["snmp"] is not None:
+ payload["defaultDestinations"]["snmp"] = meraki.params["default_destinations"]["snmp"]
+ if meraki.params["default_destinations"]["emails"] is not None:
+ payload["defaultDestinations"]["emails"] = meraki.params["default_destinations"]["emails"]
+ if len(payload["defaultDestinations"]["emails"]) > 0 and payload["defaultDestinations"]["emails"][0] == "None":
+ # Ansible is setting the first item to be "None" so we need to clear this
+ # This happens when an empty list is provided to clear emails
+ del payload["defaultDestinations"]["emails"][0]
+ if meraki.params["default_destinations"]["http_server_ids"] is not None:
+ payload["defaultDestinations"]["httpServerIds"] = meraki.params["default_destinations"]["http_server_ids"]
+ if len(payload["defaultDestinations"]["httpServerIds"]) > 0 and payload["defaultDestinations"]["httpServerIds"][0] == "None":
+ # Ansible is setting the first item to be "None" so we need to clear this
+ # This happens when an empty list is provided to clear server IDs
+ del payload["defaultDestinations"]["httpServerIds"][0]
+ if meraki.params["alerts"] is not None:
+ payload["alerts"] = []
+ # All data should be resubmitted, otherwise it will clear the alert
+ # Also, the order matters so it should go in the same order as current
+ modified_types = [type["alert_type"] for type in meraki.params["alerts"]]
+
+ # for alert in meraki.params["alerts"]:
+ for current_alert in current["alerts"]:
+ if current_alert["type"] not in modified_types:
+ payload["alerts"].append(current_alert)
+ else:
+ alert = get_alert_by_type(current_alert["type"], meraki)
+ alert_temp = {"type": None}
+ if alert["alert_type"] is not None:
+ alert_temp["type"] = alert["alert_type"]
+ if alert["enabled"] is not None:
+ alert_temp["enabled"] = alert["enabled"]
+ if alert["filters"] is not None:
+ alert_temp["filters"] = alert["filters"]
+ if alert["alert_destinations"] is not None:
+ alert_temp["alertDestinations"] = dict()
+ if alert["alert_destinations"]["all_admins"] is not None:
+ alert_temp["alertDestinations"]["allAdmins"] = alert["alert_destinations"]["all_admins"]
+ if alert["alert_destinations"]["snmp"] is not None:
+ alert_temp["alertDestinations"]["snmp"] = alert["alert_destinations"]["snmp"]
+ if alert["alert_destinations"]["emails"] is not None:
+ alert_temp["alertDestinations"]["emails"] = alert["alert_destinations"]["emails"]
+ if len(alert_temp["alertDestinations"]["emails"]) > 0 and alert_temp["alertDestinations"]["emails"][0] == "None":
+ # Ansible is setting the first item to be "None" so we need to clear this
+ # This happens when an empty list is provided to clear emails
+ del alert_temp["defaultDestinations"]["emails"][0]
+ if alert["alert_destinations"]["http_server_ids"] is not None:
+ alert_temp["alertDestinations"]["httpServerIds"] = alert["alert_destinations"]["http_server_ids"]
+ if len(alert_temp["alertDestinations"]["httpServerIds"]) > 0 and alert_temp["alertDestinations"]["httpServerIds"][0] == "None":
+ # Ansible is setting the first item to be "None" so we need to clear this
+ # This happens when an empty list is provided to clear server IDs
+ del alert_temp["defaultDestinations"]["httpServerIds"][0]
+ payload["alerts"].append(alert_temp)
+ return payload
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ destinations_arg_spec = dict(
+ all_admins=dict(type="bool"),
+ snmp=dict(type="bool"),
+ emails=dict(type="list", elements="str"),
+ http_server_ids=dict(type="list", elements="str"),
+ )
+
+ alerts_arg_spec = dict(
+ alert_type=dict(type="str"),
+ enabled=dict(type="bool"),
+ alert_destinations=dict(
+ type="dict", default=None, options=destinations_arg_spec
+ ),
+ filters=dict(type="raw", default={}),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type="str"),
+ net_name=dict(type="str", aliases=["name", "network"]),
+ state=dict(type="str", choices=["present", "query"], default="present"),
+ default_destinations=dict(
+ type="dict", default=None, options=destinations_arg_spec
+ ),
+ alerts=dict(type="list", elements="dict", options=alerts_arg_spec),
+ )
+
+ # 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="alert")
+ module.params["follow_redirects"] = "all"
+
+ query_urls = {"alert": "/networks/{net_id}/alerts/settings"}
+ update_urls = {"alert": "/networks/{net_id}/alerts/settings"}
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["update"] = update_urls
+
+ # 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"]
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ net_id = meraki.params["net_id"]
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params["net_name"], data=nets)
+
+ if meraki.params["state"] == "query":
+ path = meraki.construct_path("get_all", net_id=net_id)
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["state"] == "present":
+ path = meraki.construct_path("get_all", net_id=net_id)
+ original = meraki.request(path, method="GET")
+ payload = construct_payload(meraki, original)
+ if meraki.is_update_required(original, payload):
+ if meraki.check_mode is True:
+ meraki.generate_diff(original, payload)
+ meraki.result["data"] = payload
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("update", net_id=net_id)
+ response = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.generate_diff(original, payload)
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result["data"] = original
+ meraki.exit_json(**meraki.result)
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_config_template.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_config_template.py
new file mode 100644
index 000000000..8438d41b2
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_config_template.py
@@ -0,0 +1,331 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_config_template
+short_description: Manage configuration templates in the Meraki cloud
+version_added: "1.0.0"
+description:
+- Allows for querying, deleting, binding, and unbinding of configuration templates.
+notes:
+- Module is not idempotent as the Meraki API is limited in what information it provides about configuration templates.
+- Meraki's API does not support creating new configuration templates.
+- To use the configuration template, simply pass its ID via C(net_id) parameters in Meraki modules.
+options:
+ state:
+ description:
+ - Specifies whether configuration template information should be queried, modified, or deleted.
+ choices: ['absent', 'query', 'present']
+ default: query
+ type: str
+ org_name:
+ description:
+ - Name of organization containing the configuration template.
+ type: str
+ org_id:
+ description:
+ - ID of organization associated to a configuration template.
+ type: str
+ config_template:
+ description:
+ - Name of the configuration template within an organization to manipulate.
+ aliases: ['name']
+ type: str
+ net_name:
+ description:
+ - Name of the network to bind or unbind configuration template to.
+ type: str
+ net_id:
+ description:
+ - ID of the network to bind or unbind configuration template to.
+ type: str
+ auto_bind:
+ description:
+ - Optional boolean indicating whether the network's switches should automatically bind to profiles of the same model.
+ - This option only affects switch networks and switch templates.
+ - Auto-bind is not valid unless the switch template has at least one profile and has at most one profile per switch model.
+ type: bool
+
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query configuration templates
+ meraki_config_template:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+- name: Bind a template from a network
+ meraki_config_template:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ config_template: DevConfigTemplate
+ delegate_to: localhost
+
+- name: Unbind a template from a network
+ meraki_config_template:
+ auth_key: abc123
+ state: absent
+ org_name: YourOrg
+ net_name: YourNet
+ config_template: DevConfigTemplate
+ delegate_to: localhost
+
+- name: Delete a configuration template
+ meraki_config_template:
+ auth_key: abc123
+ state: absent
+ org_name: YourOrg
+ config_template: DevConfigTemplate
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information about queried object.
+ returned: success
+ type: complex
+ contains:
+ id:
+ description: Unique identification number of organization.
+ returned: success
+ type: int
+ sample: L_2930418
+ name:
+ description: Name of configuration template.
+ returned: success
+ type: str
+ sample: YourTemplate
+ product_types:
+ description: List of products which can exist in the network.
+ returned: success
+ type: list
+ sample: [ "appliance", "switch" ]
+ time_zone:
+ description: Timezone applied to each associated network.
+ returned: success
+ type: str
+ sample: "America/Chicago"
+
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def get_config_templates(meraki, org_id):
+ path = meraki.construct_path('get_all', org_id=org_id)
+ response = meraki.request(path, 'GET')
+ if meraki.status != 200:
+ meraki.fail_json(msg='Unable to get configuration templates')
+ return response
+
+
+def get_template_id(meraki, name, data):
+ for template in data:
+ if name == template['name']:
+ return template['id']
+ meraki.fail_json(msg='No configuration template named {0} found'.format(name))
+
+
+def is_template_valid(meraki, nets, template_id):
+ for net in nets:
+ if net['id'] == template_id:
+ return True
+ return False
+
+
+def is_network_bound(meraki, nets, net_id, template_id):
+ for net in nets:
+ if net['id'] == net_id:
+ try:
+ if net['configTemplateId'] == template_id:
+ return True
+ except KeyError:
+ pass
+ return False
+
+
+def delete_template(meraki, org_id, name, data):
+ template_id = get_template_id(meraki, name, data)
+ path = meraki.construct_path('delete', org_id=org_id)
+ path = path + '/' + template_id
+ response = meraki.request(path, 'DELETE')
+ if meraki.status != 204:
+ meraki.fail_json(msg='Unable to remove configuration template')
+ return response
+
+
+def bind(meraki, net_id, template_id):
+ path = meraki.construct_path('bind', net_id=net_id)
+ payload = {'configTemplateId': template_id}
+ if meraki.params['auto_bind']:
+ payload['autoBind'] = meraki.params['auto_bind']
+ r = meraki.request(path, method='POST', payload=json.dumps(payload))
+ return r
+
+
+def unbind(meraki, net_id):
+ path = meraki.construct_path('unbind', net_id=net_id)
+ meraki.result['changed'] = True
+ return meraki.request(path, method='POST')
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'query', 'present'], default='query'),
+ config_template=dict(type='str', aliases=['name']),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ # config_template_id=dict(type='str', aliases=['id']),
+ auto_bind=dict(type='bool'),
+ )
+
+ # 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='config_template')
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'config_template': '/organizations/{org_id}/configTemplates'}
+ delete_urls = {'config_template': '/organizations/{org_id}/configTemplates'}
+ bind_urls = {'config_template': '/networks/{net_id}/bind'}
+ unbind_urls = {'config_template': '/networks/{net_id}/unbind'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['delete'] = delete_urls
+ meraki.url_catalog['bind'] = bind_urls
+ meraki.url_catalog['unbind'] = unbind_urls
+
+ # if the user is working with this module in only check mode we do not
+ # want to make any changes to the environment, just return the current
+ # state with no modifications
+
+ # execute checks for argument completeness
+
+ # 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']
+ if meraki.params['org_name']:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ nets = None
+ if net_id is None:
+ if meraki.params['net_name'] is not None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+ else:
+ nets = meraki.get_nets(org_id=org_id)
+
+ if meraki.params['state'] == 'query':
+ meraki.result['data'] = get_config_templates(meraki, org_id)
+ elif meraki.params['state'] == 'present':
+ template_id = get_template_id(meraki,
+ meraki.params['config_template'],
+ get_config_templates(meraki, org_id))
+ if nets is None:
+ nets = meraki.get_nets(org_id=org_id)
+ if is_network_bound(meraki, nets, net_id, template_id) is False: # Bind template
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ template_bind = bind(meraki,
+ net_id,
+ template_id)
+ if meraki.status != 200:
+ meraki.fail_json(msg='Unable to bind configuration template to network')
+ meraki.result['changed'] = True
+ meraki.result['data'] = template_bind
+ else: # Network is already bound, being explicit
+ if meraki.check_mode is True: # Include to be explicit
+ meraki.result['data'] = {}
+ meraki.result['changed'] = False
+ meraki.exit_json(**meraki.result)
+ meraki.result['data'] = {}
+ meraki.result['changed'] = False
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'absent':
+ template_id = get_template_id(meraki,
+ meraki.params['config_template'],
+ get_config_templates(meraki, org_id))
+ if not meraki.params['net_name'] and not meraki.params['net_id']: # Delete template
+ if is_template_valid(meraki, nets, template_id) is True:
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ meraki.result['data'] = delete_template(meraki,
+ org_id,
+ meraki.params['config_template'],
+ get_config_templates(meraki, org_id))
+ if meraki.status == 204:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ else:
+ meraki.fail_json(msg="No template named {0} found.".format(meraki.params['config_template']))
+ else: # Unbind template
+ if nets is None:
+ nets = meraki.get_nets(org_id=org_id)
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ if is_template_valid(meraki, nets, template_id) is True:
+ meraki.result['changed'] = True
+ else:
+ meraki.result['changed'] = False
+ meraki.exit_json(**meraki.result)
+ template_id = get_template_id(meraki,
+ meraki.params['config_template'],
+ get_config_templates(meraki, org_id))
+ if is_network_bound(meraki, nets, net_id, template_id) is True:
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ config_unbind = unbind(meraki,
+ net_id)
+ if meraki.status != 200:
+ meraki.fail_json(msg='Unable to unbind configuration template from network')
+ meraki.result['changed'] = True
+ meraki.result['data'] = config_unbind
+ else: # No network is bound, nothing to do
+ if meraki.check_mode is True: # Include to be explicit
+ meraki.result['data'] = {}
+ meraki.result['changed'] = False
+ meraki.exit_json(**meraki.result)
+ meraki.result['data'] = {}
+ meraki.result['changed'] = False
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_device.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_device.py
new file mode 100644
index 000000000..2152e4949
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_device.py
@@ -0,0 +1,431 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_device
+short_description: Manage devices in the Meraki cloud
+description:
+- Visibility into devices associated to a Meraki environment.
+notes:
+- This module does not support claiming of devices or licenses into a Meraki organization.
+- More information about the Meraki API can be found at U(https://dashboard.meraki.com/api_docs).
+- Some of the options are likely only used for developers within Meraki.
+options:
+ state:
+ description:
+ - Query an organization.
+ choices: [absent, present, query]
+ default: query
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of a network.
+ type: str
+ serial:
+ description:
+ - Serial number of a device to query.
+ type: str
+ hostname:
+ description:
+ - Hostname of network device to search for.
+ aliases: [name]
+ type: str
+ model:
+ description:
+ - Model of network device to search for.
+ type: str
+ tags:
+ description:
+ - Space delimited list of tags to assign to device.
+ type: list
+ elements: str
+ lat:
+ description:
+ - Latitude of device's geographic location.
+ - Use negative number for southern hemisphere.
+ aliases: [latitude]
+ type: float
+ lng:
+ description:
+ - Longitude of device's geographic location.
+ - Use negative number for western hemisphere.
+ aliases: [longitude]
+ type: float
+ address:
+ description:
+ - Postal address of device's location.
+ type: str
+ move_map_marker:
+ description:
+ - Whether or not to set the latitude and longitude of a device based on the new address.
+ - Only applies when C(lat) and C(lng) are not specified.
+ type: bool
+ lldp_cdp_timespan:
+ description:
+ - Timespan, in seconds, used to query LLDP and CDP information.
+ - Must be less than 1 month.
+ type: int
+ note:
+ description:
+ - Informational notes about a device.
+ - Limited to 255 characters.
+ type: str
+ query:
+ description:
+ - Specifies what information should be queried.
+ type: str
+ choices: [lldp_cdp, uplink]
+
+
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query all devices in an organization.
+ meraki_device:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+- name: Query all devices in a network.
+ meraki_device:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+
+- name: Query a device by serial number.
+ meraki_device:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ serial: ABC-123
+ state: query
+ delegate_to: localhost
+
+- name: Lookup uplink information about a device.
+ meraki_device:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ serial_uplink: ABC-123
+ state: query
+ delegate_to: localhost
+
+- name: Lookup LLDP and CDP information about devices connected to specified device.
+ meraki_device:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ serial_lldp_cdp: ABC-123
+ state: query
+ delegate_to: localhost
+
+- name: Lookup a device by hostname.
+ meraki_device:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ hostname: main-switch
+ state: query
+ delegate_to: localhost
+
+- name: Query all devices of a specific model.
+ meraki_device:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ model: MR26
+ state: query
+ delegate_to: localhost
+
+- name: Update information about a device.
+ meraki_device:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ serial: '{{serial}}'
+ name: mr26
+ address: 1060 W. Addison St., Chicago, IL
+ lat: 41.948038
+ lng: -87.65568
+ tags: recently-added
+ delegate_to: localhost
+
+- name: Claim a device into a network.
+ meraki_device:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ serial: ABC-123
+ state: present
+ delegate_to: localhost
+
+- name: Remove a device from a network.
+ meraki_device:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ serial: ABC-123
+ state: absent
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+response:
+ description: Data returned from Meraki dashboard.
+ type: dict
+ returned: info
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def is_device_valid(meraki, serial, data):
+ """ Parse a list of devices for a serial and return True if it's in the list """
+ for device in data:
+ if device['serial'] == serial:
+ return True
+ return False
+
+
+def get_org_devices(meraki, org_id):
+ """ Get all devices in an organization """
+ path = meraki.construct_path('get_all_org', org_id=org_id)
+ response = meraki.request(path, method='GET')
+ if meraki.status != 200:
+ meraki.fail_json(msg='Failed to query all devices belonging to the organization')
+ return response
+
+
+def get_net_devices(meraki, net_id):
+ """ Get all devices in a network """
+ path = meraki.construct_path('get_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ if meraki.status != 200:
+ meraki.fail_json(msg='Failed to query all devices belonging to the network')
+ return response
+
+
+def construct_payload(params):
+ """ Create payload based on inputs """
+ payload = {}
+ if params['hostname'] is not None:
+ payload['name'] = params['hostname']
+ if params['tags'] is not None:
+ payload['tags'] = params['tags']
+ if params['lat'] is not None:
+ payload['lat'] = params['lat']
+ if params['lng'] is not None:
+ payload['lng'] = params['lng']
+ if params['address'] is not None:
+ payload['address'] = params['address']
+ if params['move_map_marker'] is not None:
+ payload['moveMapMarker'] = params['move_map_marker']
+ if params['note'] is not None:
+ payload['notes'] = params['note']
+ return payload
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'present', 'query'], default='query'),
+ net_name=dict(type='str', aliases=['network']),
+ net_id=dict(type='str'),
+ serial=dict(type='str'),
+ lldp_cdp_timespan=dict(type='int'),
+ hostname=dict(type='str', aliases=['name']),
+ model=dict(type='str'),
+ tags=dict(type='list', elements='str', default=None),
+ lat=dict(type='float', aliases=['latitude'], default=None),
+ lng=dict(type='float', aliases=['longitude'], default=None),
+ address=dict(type='str', default=None),
+ move_map_marker=dict(type='bool', default=None),
+ note=dict(type='str', default=None),
+ query=dict(type='str', default=None, choices=['lldp_cdp', 'uplink'])
+ )
+
+ # 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=False,
+ )
+ meraki = MerakiModule(module, function='device')
+
+ if meraki.params['query'] is not None \
+ and meraki.params['query'] == 'lldp_cdp' \
+ and not meraki.params['lldp_cdp_timespan']:
+ meraki.fail_json(msg='lldp_cdp_timespan is required when querying LLDP and CDP information')
+ if meraki.params['net_name'] and meraki.params['net_id']:
+ meraki.fail_json(msg='net_name and net_id are mutually exclusive')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'device': '/networks/{net_id}/devices'}
+ query_org_urls = {'device': '/organizations/{org_id}/devices'}
+ query_device_urls = {'device': '/networks/{net_id}/devices/{serial}'}
+ query_device_lldp_urls = {'device': '/devices/{serial}/lldpCdp'}
+ claim_device_urls = {'device': '/networks/{net_id}/devices/claim'}
+ bind_org_urls = {'device': '/organizations/{org_id}/claim'}
+ update_device_urls = {'device': '/networks/{net_id}/devices/'}
+ delete_device_urls = {'device': '/networks/{net_id}/devices/{serial}/remove'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['get_all_org'] = query_org_urls
+ meraki.url_catalog['get_device'] = query_device_urls
+ meraki.url_catalog['get_device_uplink'] = query_device_urls
+ meraki.url_catalog['get_device_lldp'] = query_device_lldp_urls
+ meraki.url_catalog['create'] = claim_device_urls
+ meraki.url_catalog['bind_org'] = bind_org_urls
+ meraki.url_catalog['update'] = update_device_urls
+ meraki.url_catalog['delete'] = delete_device_urls
+
+ payload = None
+
+ # execute checks for argument completeness
+
+ # 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']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = None
+ if meraki.params['net_id'] or meraki.params['net_name']:
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ if meraki.params['net_name'] or meraki.params['net_id']:
+ device = []
+ if meraki.params['serial']:
+ path = meraki.construct_path('get_device', net_id=net_id, custom={'serial': meraki.params['serial']})
+ request = meraki.request(path, method='GET')
+ device.append(request)
+ meraki.result['data'] = device
+ if meraki.params['query'] == 'uplink':
+ path = meraki.construct_path('get_device_uplink', net_id=net_id, custom={'serial': meraki.params['serial']})
+ meraki.result['data'] = (meraki.request(path, method='GET'))
+ elif meraki.params['query'] == 'lldp_cdp':
+ if meraki.params['lldp_cdp_timespan'] > 2592000:
+ meraki.fail_json(msg='LLDP/CDP timespan must be less than a month (2592000 seconds)')
+ path = meraki.construct_path('get_device_lldp', net_id=net_id, custom={'serial': meraki.params['serial']})
+ path = path + '?timespan=' + str(meraki.params['lldp_cdp_timespan'])
+ device.append(meraki.request(path, method='GET'))
+ meraki.result['data'] = device
+ elif meraki.params['hostname']:
+ path = meraki.construct_path('get_all', net_id=net_id)
+ devices = meraki.request(path, method='GET')
+ for unit in devices:
+ try:
+ if unit['name'] == meraki.params['hostname']:
+ device.append(unit)
+ meraki.result['data'] = device
+ except KeyError:
+ pass
+ elif meraki.params['model']:
+ path = meraki.construct_path('get_all', net_id=net_id)
+ devices = meraki.request(path, method='GET')
+ device_match = []
+ for device in devices:
+ if device['model'] == meraki.params['model']:
+ device_match.append(device)
+ meraki.result['data'] = device_match
+ else:
+ path = meraki.construct_path('get_all', net_id=net_id)
+ request = meraki.request(path, method='GET')
+ meraki.result['data'] = request
+ else:
+ path = meraki.construct_path('get_all_org', org_id=org_id, params={'perPage': '1000'})
+ devices = meraki.request(path, method='GET', pagination_items=1000)
+ if meraki.params['serial']:
+ for device in devices:
+ if device['serial'] == meraki.params['serial']:
+ meraki.result['data'] = device
+ else:
+ meraki.result['data'] = devices
+ elif meraki.params['state'] == 'present':
+ device = []
+ if net_id is None: # Claim a device to an organization
+ device_list = get_org_devices(meraki, org_id)
+ if is_device_valid(meraki, meraki.params['serial'], device_list) is False:
+ payload = {'serial': meraki.params['serial']}
+ path = meraki.construct_path('bind_org', org_id=org_id)
+ created_device = []
+ created_device.append(meraki.request(path, method='POST', payload=json.dumps(payload)))
+ meraki.result['data'] = created_device
+ meraki.result['changed'] = True
+ else: # A device is assumed to be in an organization
+ device_list = get_net_devices(meraki, net_id)
+ if is_device_valid(meraki, meraki.params['serial'], device_list) is True: # Device is in network, update
+ query_path = meraki.construct_path('get_all', net_id=net_id)
+ if is_device_valid(meraki, meraki.params['serial'], device_list):
+ payload = construct_payload(meraki.params)
+ query_path = meraki.construct_path('get_device', net_id=net_id, custom={'serial': meraki.params['serial']})
+ device_data = meraki.request(query_path, method='GET')
+ ignore_keys = ['lanIp', 'serial', 'mac', 'model', 'networkId', 'moveMapMarker', 'wan1Ip', 'wan2Ip']
+ if meraki.is_update_required(device_data, payload, optional_ignore=ignore_keys):
+ path = meraki.construct_path('update', net_id=net_id) + meraki.params['serial']
+ updated_device = []
+ updated_device.append(meraki.request(path, method='PUT', payload=json.dumps(payload)))
+ meraki.result['data'] = updated_device
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = device_data
+ else: # Claim device into network
+ query_path = meraki.construct_path('get_all', net_id=net_id)
+ device_list = meraki.request(query_path, method='GET')
+ if is_device_valid(meraki, meraki.params['serial'], device_list) is False:
+ if net_id:
+ payload = {'serials': [meraki.params['serial']]}
+ path = meraki.construct_path('create', net_id=net_id)
+ created_device = []
+ created_device.append(meraki.request(path, method='POST', payload=json.dumps(payload)))
+ meraki.result['data'] = created_device
+ meraki.result['changed'] = True
+ elif meraki.params['state'] == 'absent':
+ device = []
+ query_path = meraki.construct_path('get_all', net_id=net_id)
+ device_list = meraki.request(query_path, method='GET')
+ if is_device_valid(meraki, meraki.params['serial'], device_list) is True:
+ path = meraki.construct_path('delete', net_id=net_id, custom={'serial': meraki.params['serial']})
+ request = meraki.request(path, method='POST')
+ meraki.result['changed'] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_firewalled_services.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_firewalled_services.py
new file mode 100644
index 000000000..ec78c068b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_firewalled_services.py
@@ -0,0 +1,233 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_firewalled_services
+short_description: Edit firewall policies for administrative network services
+description:
+- Allows for setting policy firewalled services for Meraki network devices.
+
+options:
+ auth_key:
+ description:
+ - Authentication key provided by the dashboard. Required if environmental variable MERAKI_KEY is not set.
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [ network ]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ org_name:
+ description:
+ - Name of organization associated to a network.
+ type: str
+ org_id:
+ description:
+ - ID of organization associated to a network.
+ type: str
+ state:
+ description:
+ - States that a policy should be created or modified.
+ choices: [present, query]
+ default: present
+ type: str
+ service:
+ description:
+ - Network service to query or modify.
+ choices: [ICMP, SNMP, web]
+ type: str
+ access:
+ description:
+ - Network service to query or modify.
+ choices: [blocked, restricted, unrestricted]
+ type: str
+ allowed_ips:
+ description:
+ - List of IP addresses allowed to access a service.
+ - Only used when C(access) is set to restricted.
+ type: list
+ elements: str
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Set icmp service to blocked
+ meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ service: ICMP
+ access: blocked
+ delegate_to: localhost
+
+- name: Set icmp service to restricted
+ meraki_firewalled_services:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ service: web
+ access: restricted
+ allowed_ips:
+ - 192.0.1.1
+ - 192.0.1.2
+ delegate_to: localhost
+
+- name: Query appliance services
+ meraki_firewalled_services:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ delegate_to: localhost
+
+- name: Query services
+ meraki_firewalled_services:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ service: ICMP
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: List of network services.
+ returned: info
+ type: complex
+ contains:
+ access:
+ description: Access assigned to a service type.
+ returned: success
+ type: str
+ sample: unrestricted
+ service:
+ description: Service to apply policy to.
+ returned: success
+ type: str
+ sample: ICMP
+ allowed_ips:
+ description: List of IP addresses to have access to service.
+ returned: success
+ type: str
+ sample: 192.0.1.0
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type='str'),
+ net_name=dict(type='str', aliases=['network']),
+ state=dict(type='str', default='present', choices=['query', 'present']),
+ service=dict(type='str', default=None, choices=['ICMP', 'SNMP', 'web']),
+ access=dict(type='str', choices=['blocked', 'restricted', 'unrestricted']),
+ allowed_ips=dict(type='list', elements='str'),
+ )
+
+ mutually_exclusive = [('net_name', 'net_id')]
+
+ # 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,
+ mutually_exclusive=mutually_exclusive
+ )
+
+ meraki = MerakiModule(module, function='firewalled_services')
+ module.params['follow_redirects'] = 'all'
+
+ net_services_urls = {'firewalled_services': '/networks/{net_id}/appliance/firewall/firewalledServices'}
+ services_urls = {'firewalled_services': '/networks/{net_id}/appliance/firewall/firewalledServices/{service}'}
+
+ meraki.url_catalog['network_services'] = net_services_urls
+ meraki.url_catalog['service'] = services_urls
+
+ # 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']
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'present':
+ if meraki.params['access'] != 'restricted' and meraki.params['allowed_ips'] is not None:
+ meraki.fail_json(msg="allowed_ips is only allowed when access is restricted.")
+ payload = {'access': meraki.params['access']}
+ if meraki.params['access'] == 'restricted':
+ payload['allowedIps'] = meraki.params['allowed_ips']
+
+ if meraki.params['state'] == 'query':
+ if meraki.params['service'] is None:
+ path = meraki.construct_path('network_services', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ else:
+ path = meraki.construct_path('service', net_id=net_id, custom={'service': meraki.params['service']})
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('service', net_id=net_id, custom={'service': meraki.params['service']})
+ original = meraki.request(path, method='GET')
+ if meraki.is_update_required(original, payload, optional_ignore=['service']):
+ if meraki.check_mode is True:
+ diff_payload = {'service': meraki.params['service']} # Need to add service as it's not in payload
+ diff_payload.update(payload)
+ meraki.generate_diff(original, diff_payload)
+ original.update(payload)
+ meraki.result['data'] = original
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('service', net_id=net_id, custom={'service': meraki.params['service']})
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.generate_diff(original, response)
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_management_interface.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_management_interface.py
new file mode 100644
index 000000000..c02978611
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_management_interface.py
@@ -0,0 +1,384 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_management_interface
+short_description: Configure Meraki management interfaces
+version_added: "1.1.0"
+description:
+- Allows for configuration of management interfaces on Meraki MX, MS, and MR devices.
+notes:
+- C(WAN2) parameter is only valid for MX appliances.
+- C(wan_enabled) should not be provided for non-MX devies.
+options:
+ state:
+ description:
+ - Specifies whether configuration template information should be queried, modified, or deleted.
+ choices: ['absent', 'query', 'present']
+ default: query
+ type: str
+ org_name:
+ description:
+ - Name of organization containing the configuration template.
+ type: str
+ org_id:
+ description:
+ - ID of organization associated to a configuration template.
+ type: str
+ net_name:
+ description:
+ - Name of the network to bind or unbind configuration template to.
+ type: str
+ net_id:
+ description:
+ - ID of the network to bind or unbind configuration template to.
+ type: str
+ serial:
+ description:
+ - serial number of the device to configure.
+ type: str
+ required: true
+ wan1:
+ description:
+ - Management interface details for management interface.
+ aliases: [mgmt1]
+ type: dict
+ suboptions:
+ wan_enabled:
+ description:
+ - States whether the management interface is enabled.
+ - Only valid for MX devices.
+ type: str
+ choices: [disabled, enabled, not configured]
+ using_static_ip:
+ description:
+ - Configures the interface to use static IP or DHCP.
+ type: bool
+ static_ip:
+ description:
+ - IP address assigned to Management interface.
+ - Valid only if C(using_static_ip) is C(True).
+ type: str
+ static_gateway_ip:
+ description:
+ - IP address for default gateway.
+ - Valid only if C(using_static_ip) is C(True).
+ type: str
+ static_subnet_mask:
+ description:
+ - Netmask for static IP address.
+ - Valid only if C(using_static_ip) is C(True).
+ type: str
+ static_dns:
+ description:
+ - DNS servers to use.
+ - Allows for a maximum of 2 addresses.
+ type: list
+ elements: str
+ vlan:
+ description:
+ - VLAN number to use for the management network.
+ type: int
+ wan2:
+ description:
+ - Management interface details for management interface.
+ type: dict
+ aliases: [mgmt2]
+ suboptions:
+ wan_enabled:
+ description:
+ - States whether the management interface is enabled.
+ - Only valid for MX devices.
+ type: str
+ choices: [disabled, enabled, not configured]
+ using_static_ip:
+ description:
+ - Configures the interface to use static IP or DHCP.
+ type: bool
+ static_ip:
+ description:
+ - IP address assigned to Management interface.
+ - Valid only if C(using_static_ip) is C(True).
+ type: str
+ static_gateway_ip:
+ description:
+ - IP address for default gateway.
+ - Valid only if C(using_static_ip) is C(True).
+ type: str
+ static_subnet_mask:
+ description:
+ - Netmask for static IP address.
+ - Valid only if C(using_static_ip) is C(True).
+ type: str
+ static_dns:
+ description:
+ - DNS servers to use.
+ - Allows for a maximum of 2 addresses.
+ type: list
+ elements: str
+ vlan:
+ description:
+ - VLAN number to use for the management network.
+ type: int
+
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Set WAN2 as static IP
+ meraki_management_interface:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_id: YourNetId
+ serial: AAAA-BBBB-CCCC
+ wan2:
+ wan_enabled: enabled
+ using_static_ip: yes
+ static_ip: 192.168.16.195
+ static_gateway_ip: 192.168.16.1
+ static_subnet_mask: 255.255.255.0
+ static_dns:
+ - 1.1.1.1
+ vlan: 1
+ delegate_to: localhost
+
+- name: Query management information
+ meraki_management_interface:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_id: YourNetId
+ serial: AAAA-BBBB-CCCC
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information about queried object.
+ returned: success
+ type: complex
+ contains:
+ wan1:
+ description: Management configuration for WAN1 interface
+ returned: success
+ type: complex
+ contains:
+ wan_enabled:
+ description: Enabled state of interface
+ returned: success
+ type: str
+ sample: enabled
+ using_static_ip:
+ description: Boolean value of whether static IP assignment is used on interface
+ returned: success
+ type: bool
+ sample: True
+ static_ip:
+ description: Assigned static IP
+ returned: only if static IP assignment is used
+ type: str
+ sample: 192.0.1.2
+ static_gateway_ip:
+ description: Assigned static gateway IP
+ returned: only if static IP assignment is used
+ type: str
+ sample: 192.0.1.1
+ static_subnet_mask:
+ description: Assigned netmask for static IP
+ returned: only if static IP assignment is used
+ type: str
+ sample: 255.255.255.0
+ static_dns:
+ description: List of DNS IP addresses
+ returned: only if static IP assignment is used
+ type: list
+ sample: ["1.1.1.1"]
+ vlan:
+ description: VLAN tag id of management VLAN
+ returned: success
+ type: int
+ sample: 2
+ wan2:
+ description: Management configuration for WAN1 interface
+ returned: success
+ type: complex
+ contains:
+ wan_enabled:
+ description: Enabled state of interface
+ returned: success
+ type: str
+ sample: enabled
+ using_static_ip:
+ description: Boolean value of whether static IP assignment is used on interface
+ returned: success
+ type: bool
+ sample: True
+ static_ip:
+ description: Assigned static IP
+ returned: only if static IP assignment is used
+ type: str
+ sample: 192.0.1.2
+ static_gateway_ip:
+ description: Assigned static gateway IP
+ returned: only if static IP assignment is used
+ type: str
+ sample: 192.0.1.1
+ static_subnet_mask:
+ description: Assigned netmask for static IP
+ returned: only if static IP assignment is used
+ type: str
+ sample: 255.255.255.0
+ static_dns:
+ description: List of DNS IP addresses
+ returned: only if static IP assignment is used
+ type: list
+ sample: ["1.1.1.1"]
+ vlan:
+ description: VLAN tag id of management VLAN
+ returned: success
+ type: int
+ sample: 2
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import recursive_diff
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ int_arg_spec = dict(wan_enabled=dict(type='str', choices=['enabled', 'disabled', 'not configured']),
+ using_static_ip=dict(type='bool'),
+ static_ip=dict(type='str'),
+ static_gateway_ip=dict(type='str'),
+ static_subnet_mask=dict(type='str'),
+ static_dns=dict(type='list', elements='str'),
+ vlan=dict(type='int'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'query', 'present'], default='query'),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ serial=dict(type='str', required=True),
+ wan1=dict(type='dict', default=None, options=int_arg_spec, aliases=['mgmt1']),
+ wan2=dict(type='dict', default=None, options=int_arg_spec, aliases=['mgmt2']),
+ )
+
+ # 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='management_interface')
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'management_interface': '/devices/{serial}/managementInterface'}
+
+ meraki.url_catalog['get_one'].update(query_urls)
+
+ if meraki.params['net_id'] and meraki.params['net_name']:
+ meraki.fail_json('net_id and net_name are mutually exclusive.')
+ if meraki.params['state'] == 'present':
+ interfaces = ('wan1', 'wan2')
+ for interface in interfaces:
+ if meraki.params[interface] is not None:
+ if meraki.params[interface]['using_static_ip'] is True:
+ if len(meraki.params[interface]['static_dns']) > 2:
+ meraki.fail_json("Maximum number of static DNS addresses is 2.")
+
+ payload = dict()
+
+ if meraki.params['state'] == 'present':
+ interfaces = ('wan1', 'wan2')
+ for interface in interfaces:
+ if meraki.params[interface] is not None:
+ wan_int = dict()
+ if meraki.params[interface]['wan_enabled'] is not None:
+ wan_int['wanEnabled'] = meraki.params[interface]['wan_enabled']
+ if meraki.params[interface]['using_static_ip'] is not None:
+ wan_int['usingStaticIp'] = meraki.params[interface]['using_static_ip']
+ if meraki.params[interface]['vlan'] is not None:
+ wan_int['vlan'] = meraki.params[interface]['vlan']
+ if meraki.params[interface]['using_static_ip'] is True:
+ wan_int['staticIp'] = meraki.params[interface]['static_ip']
+ wan_int['staticGatewayIp'] = meraki.params[interface]['static_gateway_ip']
+ wan_int['staticSubnetMask'] = meraki.params[interface]['static_subnet_mask']
+ wan_int['staticDns'] = meraki.params[interface]['static_dns']
+ payload[interface] = wan_int
+
+ # 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']
+ if meraki.params['org_name']:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_one', net_id=net_id, custom={'serial': meraki.params['serial']})
+ response = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('get_one', custom={'serial': meraki.params['serial']})
+ original = meraki.request(path, method='GET')
+ update_required = False
+ if 'wan1' in original:
+ if 'wanEnabled' in original['wan1']:
+ update_required = meraki.is_update_required(original, payload)
+ else:
+ update_required = meraki.is_update_required(original, payload, optional_ignore=['wanEnabled'])
+ if 'wan2' in original and update_required is False:
+ if 'wanEnabled' in original['wan2']:
+ update_required = meraki.is_update_required(original, payload)
+ else:
+ update_required = meraki.is_update_required(original, payload, optional_ignore=['wanEnabled'])
+ if update_required is True:
+ if meraki.check_mode is True:
+ diff = recursive_diff(original, payload)
+ original.update(payload)
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1]}
+ meraki.result['data'] = original
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ diff = recursive_diff(original, response)
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1]}
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py
new file mode 100644
index 000000000..d2a70052a
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py
@@ -0,0 +1,300 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_mr_l3_firewall
+short_description: Manage MR access point layer 3 firewalls in the Meraki cloud
+description:
+- Allows for creation, management, and visibility into layer 3 firewalls implemented on Meraki MR access points.
+- Module is not idempotent as of current release.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ type: str
+ choices: [ present, query ]
+ default: present
+ net_name:
+ description:
+ - Name of network containing access points.
+ type: str
+ net_id:
+ description:
+ - ID of network containing access points.
+ type: str
+ number:
+ description:
+ - Number of SSID to apply firewall rule to.
+ type: str
+ aliases: [ ssid_number ]
+ ssid_name:
+ description:
+ - Name of SSID to apply firewall rule to.
+ type: str
+ aliases: [ ssid ]
+ allow_lan_access:
+ description:
+ - Sets whether devices can talk to other devices on the same LAN.
+ type: bool
+ default: yes
+ rules:
+ description:
+ - List of firewall rules.
+ type: list
+ elements: dict
+ suboptions:
+ policy:
+ description:
+ - Specifies the action that should be taken when rule is hit.
+ type: str
+ choices: [ allow, deny ]
+ protocol:
+ description:
+ - Specifies protocol to match against.
+ type: str
+ choices: [ any, icmp, tcp, udp ]
+ dest_port:
+ description:
+ - Comma-seperated list of destination ports to match.
+ type: str
+ dest_cidr:
+ description:
+ - Comma-separated list of CIDR notation networks to match.
+ type: str
+ comment:
+ description:
+ - Optional comment describing the firewall rule.
+ type: str
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Create single firewall rule
+ meraki_mr_l3_firewall:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_id: 12345
+ number: 1
+ rules:
+ - comment: Integration test rule
+ policy: allow
+ protocol: tcp
+ dest_port: 80
+ dest_cidr: 192.0.2.0/24
+ allow_lan_access: no
+ delegate_to: localhost
+
+- name: Enable local LAN access
+ meraki_mr_l3_firewall:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_id: 123
+ number: 1
+ rules:
+ allow_lan_access: yes
+ delegate_to: localhost
+
+- name: Query firewall rules
+ meraki_mr_l3_firewall:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ number: 1
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def assemble_payload(meraki):
+ params_map = {'policy': 'policy',
+ 'protocol': 'protocol',
+ 'dest_port': 'destPort',
+ 'dest_cidr': 'destCidr',
+ 'comment': 'comment',
+ }
+ rules = []
+ for rule in meraki.params['rules']:
+ proposed_rule = dict()
+ for k, v in rule.items():
+ proposed_rule[params_map[k]] = v
+ rules.append(proposed_rule)
+ payload = {'rules': rules}
+ return payload
+
+
+def get_rules(meraki, net_id, number):
+ path = meraki.construct_path('get_all', net_id=net_id, custom={'number': number})
+ response = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ return normalize_rule_case(response)
+
+
+def normalize_rule_case(rules):
+ excluded = ['comment']
+ try:
+ for r in rules['rules']:
+ for k in r:
+ if k not in excluded:
+ r[k] = r[k].lower()
+ except KeyError:
+ return rules
+ return rules
+
+
+def get_ssid_number(name, data):
+ for ssid in data:
+ if name == ssid['name']:
+ return ssid['number']
+ return False
+
+
+def get_ssids(meraki, net_id):
+ path = meraki.construct_path('get_all', net_id=net_id)
+ return meraki.request(path, method='GET')
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ fw_rules = dict(policy=dict(type='str', choices=['allow', 'deny']),
+ protocol=dict(type='str', choices=['tcp', 'udp', 'icmp', 'any']),
+ dest_port=dict(type='str'),
+ dest_cidr=dict(type='str'),
+ comment=dict(type='str'),
+ )
+
+ 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'),
+ number=dict(type='str', aliases=['ssid_number']),
+ ssid_name=dict(type='str', aliases=['ssid']),
+ rules=dict(type='list', default=None, elements='dict', options=fw_rules),
+ allow_lan_access=dict(type='bool', default=True),
+ )
+
+ # 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='mr_l3_firewall')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'mr_l3_firewall': '/networks/{net_id}/wireless/ssids/{number}/firewall/l3FirewallRules'}
+ update_urls = {'mr_l3_firewall': '/networks/{net_id}/wireless/ssids/{number}/firewall/l3FirewallRules'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['update'] = update_urls
+
+ payload = None
+
+ # execute checks for argument completeness
+
+ # 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))
+ number = meraki.params['number']
+ if meraki.params['ssid_name']:
+ number = get_ssid_number(meraki.params['ssid_name'], get_ssids(meraki, net_id))
+
+ if meraki.params['state'] == 'query':
+ meraki.result['data'] = get_rules(meraki, net_id, number)
+ elif meraki.params['state'] == 'present':
+ rules = get_rules(meraki, net_id, number)
+ path = meraki.construct_path('get_all', net_id=net_id, custom={'number': number})
+ if meraki.params['rules']:
+ payload = assemble_payload(meraki)
+ else:
+ payload = dict()
+ update = False
+ try:
+ if len(rules) != len(payload['rules']): # Quick and simple check to avoid more processing
+ update = True
+ if update is False:
+ for r in range(len(rules) - 2):
+ if meraki.is_update_required(rules[r], payload[r]) is True:
+ update = True
+ except KeyError:
+ pass
+ # meraki.fail_json(msg=rules)
+ if rules['rules'][len(rules['rules']) - 2] != meraki.params['allow_lan_access']:
+ update = True
+ if update is True:
+ payload['allowLanAccess'] = meraki.params['allow_lan_access']
+ if meraki.check_mode is True:
+ # This code is disgusting, rework it at some point
+ if 'rules' in payload:
+ cleansed_payload = payload['rules']
+ cleansed_payload.append(rules['rules'][len(rules['rules']) - 1])
+ cleansed_payload.append(rules['rules'][len(rules['rules']) - 2])
+ if meraki.params['allow_lan_access'] is None:
+ cleansed_payload[len(cleansed_payload) - 2]['policy'] = rules['rules'][len(rules['rules']) - 2]['policy']
+ else:
+ if meraki.params['allow_lan_access'] is True:
+ cleansed_payload[len(cleansed_payload) - 2]['policy'] = 'allow'
+ else:
+ cleansed_payload[len(cleansed_payload) - 2]['policy'] = 'deny'
+ else:
+ if meraki.params['allow_lan_access'] is True:
+ rules['rules'][len(rules['rules']) - 2]['policy'] = 'allow'
+ else:
+ rules['rules'][len(rules['rules']) - 2]['policy'] = 'deny'
+ cleansed_payload = rules
+ meraki.result['data'] = cleansed_payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = rules
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l7_firewall.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l7_firewall.py
new file mode 100644
index 000000000..a9584091f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l7_firewall.py
@@ -0,0 +1,503 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Joshua Coronado (@joshuajcoronado) <joshua@coronado.io>
+# 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_mr_l7_firewall
+short_description: Manage MR access point layer 7 firewalls in the Meraki cloud
+description:
+- Allows for creation, management, and visibility into layer 7 firewalls implemented on Meraki MR access points.
+- 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 containing access points.
+ type: str
+ net_id:
+ description:
+ - ID of network containing access points.
+ type: str
+ number:
+ description:
+ - Number of SSID to apply firewall rule to.
+ type: str
+ aliases: [ ssid_number ]
+ ssid_name:
+ description:
+ - Name of SSID to apply firewall rule to.
+ type: str
+ aliases: [ ssid ]
+ rules:
+ description:
+ - List of layer 7 firewall rules.
+ type: list
+ elements: dict
+ 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,
+ host,
+ ip_range,
+ port]
+ type: str
+ application:
+ description:
+ - Application to filter.
+ type: dict
+ 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
+ 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
+ categories:
+ description:
+ - When C(True), specifies that applications and application categories should be queried instead of firewall rules.
+ type: bool
+author:
+- Joshua Coronado (@joshuajcoronado)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Query firewall rules
+ meraki_mr_l7_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ number: 1
+ delegate_to: localhost
+
+- name: Query applications and application categories
+ meraki_mr_l7_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ number: 1
+ categories: yes
+ state: query
+ delegate_to: localhost
+
+- name: Set firewall rules
+ meraki_mr_l7_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ number: 1
+ state: present
+ rules:
+ - 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 SSID.
+ 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: str
+ sample: deny
+ type:
+ description: Type of rule category.
+ returned: success
+ type: str
+ sample: applications
+ applications:
+ description: List of applications within a category.
+ type: list
+ contains:
+ id:
+ description: URI of application.
+ returned: success
+ type: str
+ sample: Gmail
+ name:
+ description: Descriptive name of application.
+ returned: success
+ type: str
+ 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: str
+ sample: Gmail
+ name:
+ description: Descriptive name of application.
+ returned: success
+ type: str
+ sample: meraki:layer7/application/4
+ port:
+ description: Port number in rule.
+ returned: success
+ type: str
+ sample: 23
+ ipRange:
+ description: Range of IP addresses in rule.
+ returned: success
+ type: str
+ sample: 1.1.1.0/23
+ 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: str
+ sample: Gmail
+ name:
+ description: Descriptive name of application.
+ returned: success
+ type: str
+ sample: meraki:layer7/application/4
+ id:
+ description: URI of application category.
+ returned: success
+ type: str
+ sample: Email
+ name:
+ description: Descriptive name of application category.
+ returned: success
+ type: str
+ sample: layer7/category/1
+"""
+
+import copy
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.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):
+ new_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"],
+ }
+ 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_ssid_number(name, data):
+ for ssid in data:
+ if name == ssid["name"]:
+ return ssid["number"]
+ return False
+
+
+def get_ssids(meraki, net_id):
+ path = meraki.construct_path("get_ssids", net_id=net_id)
+ return meraki.request(path, method="GET")
+
+
+def get_rules(meraki, net_id, number):
+ path = meraki.construct_path("get_all", net_id=net_id, custom={"number": number})
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ return response
+
+
+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", "host", "ip_range", "port"],
+ ),
+ ip_range=dict(type="str"),
+ application=dict(type="dict", default=None, options=application_arg_spec),
+ host=dict(type="str"),
+ port=dict(type="str"),
+ )
+
+ 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"),
+ number=dict(type="str", aliases=["ssid_number"]),
+ ssid_name=dict(type="str", aliases=["ssid"]),
+ rules=dict(type="list", default=None, elements="dict", options=rule_arg_spec),
+ categories=dict(type="bool"),
+ )
+
+ # 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="mr_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"] == "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.")
+
+ meraki.params["follow_redirects"] = "all"
+ query_ssids_urls = {"mr_l7_firewall": "/networks/{net_id}/wireless/ssids"}
+ query_urls = {
+ "mr_l7_firewall": "/networks/{net_id}/wireless/ssids/{number}/firewall/l7FirewallRules"
+ }
+ update_urls = {
+ "mr_l7_firewall": "/networks/{net_id}/wireless/ssids/{number}/firewall/l7FirewallRules"
+ }
+ query_category_urls = {
+ "mr_l7_firewall": "/networks/{net_id}/trafficShaping/applicationCategories"
+ }
+
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["get_categories"] = query_category_urls
+ meraki.url_catalog["get_ssids"] = query_ssids_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)
+ )
+ number = meraki.params["number"]
+ if meraki.params["ssid_name"]:
+ ssids = get_ssids(meraki, net_id)
+ number = get_ssid_number(meraki.params["ssid_name"], ssids)
+
+ 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, number)
+ )
+ elif meraki.params["state"] == "present":
+ rules = get_rules(meraki, net_id, number)
+ path = meraki.construct_path(
+ "get_all", net_id=net_id, custom={"number": number}
+ )
+
+ # Detect if no rules are given, special case
+ if len(meraki.params["rules"]) == 0:
+ # Conditionally wrap parameters in rules makes it comparable
+ if isinstance(meraki.params["rules"], list):
+ param_rules = {"rules": meraki.params["rules"]}
+ else:
+ param_rules = meraki.params["rules"]
+ if meraki.is_update_required(rules, param_rules):
+ if meraki.module.check_mode is True:
+ meraki.result["data"] = meraki.params["rules"]
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ payload = {"rules": []}
+ response = meraki.request(
+ path, method="PUT", payload=json.dumps(payload)
+ )
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result["data"] = param_rules
+ meraki.exit_json(**meraki.result)
+ if meraki.params["rules"]:
+ payload = {"rules": []}
+ for rule in meraki.params["rules"]:
+ payload["rules"].append(assemble_payload(meraki, net_id, rule))
+ else:
+ payload = dict()
+ if meraki.is_update_required(rules, payload, force_include="id"):
+ if meraki.module.check_mode is True:
+ response = restructure_response(payload)
+ meraki.generate_diff(restructure_response(rules), response)
+ 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:
+ meraki.generate_diff(restructure_response(rules), response)
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ else:
+ 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_radio.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_radio.py
new file mode 100644
index 000000000..c61f1e225
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_radio.py
@@ -0,0 +1,490 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Tyler Christiansen (@supertylerc) <code@tylerc.me>
+# 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_mr_radio
+short_description: Manage device radio settings for Meraki wireless networks
+description:
+- Allows for configuration of radio settings in Meraki MR wireless networks.
+options:
+ state:
+ description:
+ - Query or edit radio settings on a device.
+ type: str
+ choices: [present, query]
+ default: present
+ serial:
+ description:
+ - Serial number of a device to query.
+ type: str
+ rf_profile_id:
+ description:
+ - The ID of an RF profile to assign to the device.
+ - If the value of this parameter is null, the appropriate basic RF profile (indoor or outdoor) will be assigned to the device.
+ - Assigning an RF profile will clear ALL manually configured overrides on the device (channel width, channel, power).
+ type: str
+ rf_profile_name:
+ description:
+ - The name of an RF profile to assign to the device.
+ - Similar to ``rf_profile_id``, but requires ``net_id`` (preferred) or ``net_name``.
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of a network.
+ type: str
+ five_ghz_settings:
+ description:
+ - Manual radio settings for 5 GHz.
+ type: dict
+ default: {}
+ suboptions:
+ target_power:
+ description:
+ - Set a manual target power for 5 GHz.
+ - Can be between '8' or '30' or null for using auto power range.
+ type: int
+ channel_width:
+ description:
+ - Sets a manual channel for 5 GHz.
+ - Can be '0', '20', '40', or '80' or null for using auto channel width.
+ choices:
+ - auto
+ - '20'
+ - '40'
+ - '80'
+ type: str
+ channel:
+ description:
+ - Sets a manual channel for 5 GHz.
+ type: int
+ choices:
+ - 36
+ - 40
+ - 44
+ - 48
+ - 52
+ - 56
+ - 60
+ - 64
+ - 100
+ - 104
+ - 108
+ - 112
+ - 116
+ - 120
+ - 124
+ - 128
+ - 132
+ - 136
+ - 140
+ - 144
+ - 149
+ - 153
+ - 157
+ - 161
+ - 165
+ two_four_ghz_settings:
+ description:
+ - Manual radio settings for 2.4 GHz.
+ type: dict
+ default: {}
+ suboptions:
+ target_power:
+ description:
+ - Set a manual target power for 2.4 GHz.
+ - Can be between '5' or '30' or null for using auto power range.
+ type: int
+ channel:
+ description:
+ - Sets a manual channel for 2.4 GHz.
+ - Can be '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13' or '14' or null for using auto channel.
+ choices:
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+ - 6
+ - 7
+ - 8
+ - 9
+ - 10
+ - 11
+ - 12
+ - 13
+ - 14
+ type: int
+author:
+- Tyler Christiansen (@supertylerc)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Query a device's radio configuration
+ meraki_mr_radio:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ serial: YourSerialNumber
+ state: query
+ delegate_to: localhost
+- name: Configure a device's radios
+ meraki_mr_radio:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ serial: YourSerialNumber
+ state: present
+ five_ghz_settings:
+ channel: 56
+ channel_width: 20
+ target_power: 10
+ two_four_ghz_settings:
+ channel: 6
+ target_power: 12
+ rf_profile_name: Test Profile
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: RF settings configured on a specific device.
+ returned: success
+ type: complex
+ contains:
+ serial:
+ description:
+ - Serial number of the device that was configured.
+ type: str
+ returned: success
+ sample: xyz
+ rf_profile_id:
+ description:
+ - The ID of an RF profile assigned to the device.
+ - Null indicates the appropriate basic RF profile (indoor or outdoor) is assigned to the device.
+ type: str
+ returned: success
+ sample: null
+ five_ghz_settings:
+ description:
+ - Configured manual radio settings for 5 GHz.
+ type: dict
+ returned: success
+ contains:
+ target_power:
+ description:
+ - Configured manual target power for 5 GHz.
+ - Null indicates auto power.
+ type: int
+ sample: 25
+ channel_width:
+ description:
+ - Configured manual channel for 5 GHz.
+ - Null indicates auto channel width.
+ type: str
+ sample: 40
+ channel:
+ description:
+ - Configured manual channel for 5 GHz.
+ - Null indicates auto channel.
+ type: str
+ sample: 56
+ two_four_ghz_settings:
+ description:
+ - Configured manual radio settings for 2.4 GHz.
+ type: dict
+ returned: success
+ contains:
+ target_power:
+ description:
+ - Configured manual target power for 2.4 GHz.
+ - Null indicates auto power.
+ type: int
+ sample: 15
+ channel:
+ description:
+ - Configured manual channel for 2.4 GHz.
+ - Null indicates auto channel.
+ type: str
+ sample: 11
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+from re import sub
+
+
+def convert_to_camel_case(string):
+ """Convert "snake case" to "camel case"."""
+ string = sub(r"(_|-)+", " ", string).title().replace(" ", "")
+ return string[0].lower() + string[1:]
+
+
+def _construct_payload(params):
+ """Recursively convert key names.
+
+ This function recursively updates all dict key names from the
+ Ansible/Python-style "snake case" to the format the Meraki API expects
+ ("camel case").
+ """
+ payload = {}
+ for k, v in params.items():
+ if isinstance(v, dict):
+ v = _construct_payload(v)
+ payload[convert_to_camel_case(k)] = v
+ return payload
+
+
+def construct_payload(meraki):
+ """Construct API payload dict.
+
+ This function uses a ``valid_params`` variable to filter out keys from
+ ``meraki.params`` that aren't relevant to the Meraki API call.
+ """
+ params = {}
+ valid_params = [
+ "serial",
+ "rf_profile_id",
+ "five_ghz_settings",
+ "two_four_ghz_settings",
+ ]
+ for k, v in meraki.params.items():
+ if k not in valid_params:
+ continue
+ params[k] = v
+ return _construct_payload(params)
+
+
+# Ansible spec for the 'five_ghz_settings' param, based on Meraki API.
+FIVE_GHZ_SETTINGS_SPEC = {
+ "options": {
+ "target_power": {"type": "int"},
+ "channel_width": {"type": "str", "choices": ["auto", "20", "40", "80"]},
+ "channel": {
+ "type": "int",
+ "choices": [
+ 36,
+ 40,
+ 44,
+ 48,
+ 52,
+ 56,
+ 60,
+ 64,
+ 100,
+ 104,
+ 108,
+ 112,
+ 116,
+ 120,
+ 124,
+ 128,
+ 132,
+ 136,
+ 140,
+ 144,
+ 149,
+ 153,
+ 157,
+ 161,
+ 165,
+ ],
+ },
+ },
+ "default": {},
+}
+
+# Ansible spec for the 'two_four_ghz_settings' param, based on Meraki API.
+TWO_FOUR_GHZ_SETTINGS_SPEC = {
+ "options": {
+ "target_power": {"type": "int"},
+ "channel": {
+ "type": "int",
+ "choices": list(range(1, 15)),
+ },
+ },
+ "default": {},
+}
+
+
+def get_org_id(meraki):
+ """Get the Organization ID based on the Organization Name."""
+ org_id = meraki.params["org_id"]
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ return org_id
+
+
+def get_net_id(meraki):
+ """Get the Network ID based on a Network Name."""
+ net_id = meraki.params["net_id"]
+ if net_id is None:
+ org_id = get_org_id(meraki)
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params["net_name"], data=nets)
+ return net_id
+
+
+def get_rf_profile_id(meraki):
+ """Get the RF Profile ID for a given RF Profile Name."""
+ profile_id = meraki.params["rf_profile_id"]
+ profile_name = meraki.params["rf_profile_name"]
+ if profile_id is None and profile_name is not None:
+ net_id = get_net_id(meraki)
+ path = meraki.construct_path("get_all", "mr_rf_profile", net_id=net_id)
+ profiles = meraki.request(path, method="GET")
+ profile_id = next(
+ (
+ profile["id"]
+ for profile in profiles
+ if profile["name"] == meraki.params["rf_profile_name"]
+ ),
+ None,
+ )
+ return profile_id
+
+
+def meraki_get_radio_settings(meraki):
+ """Query the Meraki API for the current radio settings."""
+ path = meraki.construct_path("get_one", custom={"serial": meraki.params["serial"]})
+ return meraki.request(path, method="GET")
+
+
+def _meraki_run_query(meraki):
+ """Get the radio settings on the specified device."""
+ meraki.result["data"] = meraki_get_radio_settings(meraki)
+ meraki.exit_json(**meraki.result)
+
+
+def _meraki_run_present(meraki):
+ """Update / check radio settings for a specified device."""
+ original = meraki_get_radio_settings(meraki)
+ meraki.result["data"] = original
+ meraki.params["rf_profile_id"] = get_rf_profile_id(meraki)
+ payload = construct_payload(meraki)
+ if meraki.is_update_required(original, payload) is True:
+ if meraki.check_mode is True:
+ meraki.result["data"] = payload
+ meraki.result["changed"] = True
+ meraki.result["original"] = original
+ else:
+ path = meraki.construct_path(
+ "update", custom={"serial": meraki.params["serial"]}
+ )
+ response = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+
+
+def _meraki_run_func_lookup(state):
+ """Return the function that `meraki_run` will use based on `state`."""
+ return {
+ "query": _meraki_run_query,
+ "present": _meraki_run_present,
+ }[state]
+
+
+def meraki_run(meraki):
+ """Perform API calls and generate responses based on the 'state' param."""
+ meraki_run_func = _meraki_run_func_lookup(meraki.params["state"])
+ meraki_run_func(meraki)
+
+
+def update_url_catalog(meraki):
+ """Update the URL catalog available to the helper."""
+ query_urls = {"mr_radio": "/devices/{serial}/wireless/radio/settings"}
+ update_urls = {"mr_radio": "/devices/{serial}/wireless/radio/settings"}
+ query_all_urls = {"mr_rf_profile": "/networks/{net_id}/wireless/rfProfiles"}
+
+ meraki.url_catalog["get_one"].update(query_urls)
+ meraki.url_catalog["update"] = update_urls
+ meraki.url_catalog["get_all"].update(query_all_urls)
+
+
+def validate_params(params):
+ """Validate parameters passed to this Ansible module.
+
+ When ``rf_profile_name`` is passed, we need to lookup the ID as that's what
+ the API expects. To look up the RF Profile ID, we need the network ID,
+ which might be derived based on the network name, in which case we need the
+ org ID or org name to complete the process.
+ """
+ valid = True
+ msg = None
+
+ if (
+ params["rf_profile_name"] is not None
+ and params["rf_profile_id"] is None
+ and params["net_id"] is None
+ ):
+ if params["net_name"] is None:
+ valid = False
+ msg = "When specifying 'rf_profile_name', either 'net_id' (preferred) or 'net_name' is required."
+ elif params["org_id"] is None and params["org_name"] is None:
+ valid = False
+ msg = "When specifying 'rf_profile_name' and omitting 'net_id', either 'org_id' (preferred) or 'org_name' is required."
+ return (valid, msg)
+
+
+def main():
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ state=dict(type="str", choices=["present", "query"], default="present"),
+ org_name=dict(type="str", aliases=["organization"]),
+ org_id=dict(type="str"),
+ net_name=dict(type="str", aliases=["network"]),
+ net_id=dict(type="str"),
+ serial=dict(type="str"),
+ rf_profile_name=(dict(type="str")),
+ rf_profile_id=dict(type="str"),
+ five_ghz_settings=dict(
+ type="dict",
+ options=FIVE_GHZ_SETTINGS_SPEC["options"],
+ default=FIVE_GHZ_SETTINGS_SPEC["default"],
+ ),
+ two_four_ghz_settings=dict(
+ type="dict",
+ options=TWO_FOUR_GHZ_SETTINGS_SPEC["options"],
+ default=TWO_FOUR_GHZ_SETTINGS_SPEC["default"],
+ ),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+ meraki = MerakiModule(module, function="mr_radio")
+ meraki.params["follow_redirects"] = "all"
+ valid_params, msg = validate_params(meraki.params)
+ if not valid_params:
+ meraki.fail_json(msg=msg)
+
+ update_url_catalog(meraki)
+ meraki_run(meraki)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py
new file mode 100644
index 000000000..cd8d9c41e
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py
@@ -0,0 +1,662 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_mr_rf_profile
+short_description: Manage RF profiles for Meraki wireless networks
+description:
+- Allows for configuration of radio frequency (RF) profiles in Meraki MR wireless networks.
+options:
+ state:
+ description:
+ - Query, edit, or delete wireless RF profile settings.
+ type: str
+ choices: [ present, query, absent]
+ default: present
+ net_name:
+ description:
+ - Name of network.
+ type: str
+ net_id:
+ description:
+ - ID of network.
+ type: str
+ profile_id:
+ description:
+ - Unique identifier of existing RF profile.
+ type: str
+ aliases: [ id ]
+ band_selection_type:
+ description:
+ - Sets whether band selection is assigned per access point or SSID.
+ - This param is required on creation.
+ choices: [ ssid, ap ]
+ type: str
+ min_bitrate_type:
+ description:
+ - Type of minimum bitrate.
+ choices: [ band, ssid ]
+ type: str
+ name:
+ description:
+ - The unique name of the new profile.
+ - This param is required on creation.
+ type: str
+ client_balancing_enabled:
+ description:
+ - Steers client to best available access point.
+ type: bool
+ ap_band_settings:
+ description:
+ - Settings that will be enabled if selectionType is set to 'ap'.
+ type: dict
+ suboptions:
+ mode:
+ description:
+ - Sets which RF band the AP will support.
+ choices: [ 2.4ghz, 5ghz, dual ]
+ aliases: [ band_operation_mode ]
+ type: str
+ band_steering_enabled:
+ description:
+ - Steers client to most open band.
+ type: bool
+ five_ghz_settings:
+ description:
+ - Settings related to 5Ghz band.
+ type: dict
+ suboptions:
+ max_power:
+ description:
+ - Sets max power (dBm) of 5Ghz band.
+ - Can be integer between 8 and 30.
+ type: int
+ min_power:
+ description:
+ - Sets minmimum power (dBm) of 5Ghz band.
+ - Can be integer between 8 and 30.
+ type: int
+ min_bitrate:
+ description:
+ - Sets minimum bitrate (Mbps) of 5Ghz band.
+ choices: [ 6, 9, 12, 18, 24, 36, 48, 54 ]
+ type: int
+ rxsop:
+ description:
+ - The RX-SOP level controls the sensitivity of the radio.
+ - It is strongly recommended to use RX-SOP only after consulting a wireless expert.
+ - RX-SOP can be configured in the range of -65 to -95 (dBm).
+ type: int
+ channel_width:
+ description:
+ - Sets channel width (MHz) for 5Ghz band.
+ choices: [ auto, '20', '40', '80' ]
+ type: str
+ valid_auto_channels:
+ description:
+ - Sets valid auto channels for 5Ghz band.
+ type: list
+ elements: int
+ choices: [36,
+ 40,
+ 44,
+ 48,
+ 52,
+ 56,
+ 60,
+ 64,
+ 100,
+ 104,
+ 108,
+ 112,
+ 116,
+ 120,
+ 124,
+ 128,
+ 132,
+ 136,
+ 140,
+ 144,
+ 149,
+ 153,
+ 157,
+ 161,
+ 165]
+ two_four_ghz_settings:
+ description:
+ - Settings related to 2.4Ghz band
+ type: dict
+ suboptions:
+ max_power:
+ description:
+ - Sets max power (dBm) of 2.4Ghz band.
+ - Can be integer between 5 and 30.
+ type: int
+ min_power:
+ description:
+ - Sets minmimum power (dBm) of 2.4Ghz band.
+ - Can be integer between 5 and 30.
+ type: int
+ min_bitrate:
+ description:
+ - Sets minimum bitrate (Mbps) of 2.4Ghz band.
+ choices: [ 1, 2, 5.5, 6, 9, 11, 12, 18, 24, 36, 48, 54 ]
+ type: float
+ rxsop:
+ description:
+ - The RX-SOP level controls the sensitivity of the radio.
+ - It is strongly recommended to use RX-SOP only after consulting a wireless expert.
+ - RX-SOP can be configured in the range of -65 to -95 (dBm).
+ type: int
+ ax_enabled:
+ description:
+ - Determines whether ax radio on 2.4Ghz band is on or off.
+ type: bool
+ valid_auto_channels:
+ description:
+ - Sets valid auto channels for 2.4Ghz band.
+ choices: [ 1, 6, 11 ]
+ type: list
+ elements: int
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Create RF profile in check mode
+ meraki_mr_rf_profile:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ name: Test Profile
+ band_selection_type: ap
+ client_balancing_enabled: True
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 40
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+
+- name: Query all RF profiles
+ meraki_mr_rf_profile:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+
+- name: Query one RF profile by ID
+ meraki_mr_rf_profile:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ profile_id: '{{ profile_id }}'
+ delegate_to: localhost
+
+- name: Update profile
+ meraki_mr_rf_profile:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ profile_id: 12345
+ band_selection_type: ap
+ client_balancing_enabled: True
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -75
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+
+- name: Delete RF profile
+ meraki_mr_rf_profile:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: absent
+ profile_id: 12345
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: List of wireless RF profile settings.
+ returned: success
+ type: complex
+ contains:
+ id:
+ description:
+ - Unique identifier of existing RF profile.
+ type: str
+ returned: success
+ sample: 12345
+ band_selection_type:
+ description:
+ - Sets whether band selection is assigned per access point or SSID.
+ - This param is required on creation.
+ type: str
+ returned: success
+ sample: ap
+ min_bitrate_type:
+ description:
+ - Type of minimum bitrate.
+ type: str
+ returned: success
+ sample: ssid
+ name:
+ description:
+ - The unique name of the new profile.
+ - This param is required on creation.
+ type: str
+ returned: success
+ sample: Guest RF profile
+ client_balancing_enabled:
+ description:
+ - Steers client to best available access point.
+ type: bool
+ returned: success
+ sample: true
+ ap_band_settings:
+ description:
+ - Settings that will be enabled if selectionType is set to 'ap'.
+ type: complex
+ returned: success
+ contains:
+ mode:
+ description:
+ - Sets which RF band the AP will support.
+ type: str
+ returned: success
+ sample: dual
+ band_steering_enabled:
+ description:
+ - Steers client to most open band.
+ type: bool
+ returned: success
+ sample: true
+ five_ghz_settings:
+ description:
+ - Settings related to 5Ghz band.
+ type: complex
+ returned: success
+ contains:
+ max_power:
+ description:
+ - Sets max power (dBm) of 5Ghz band.
+ - Can be integer between 8 and 30.
+ type: int
+ returned: success
+ sample: 12
+ min_power:
+ description:
+ - Sets minmimum power (dBm) of 5Ghz band.
+ - Can be integer between 8 and 30.
+ type: int
+ returned: success
+ sample: 12
+ min_bitrate:
+ description:
+ - Sets minimum bitrate (Mbps) of 5Ghz band.
+ type: int
+ returned: success
+ sample: 6
+ rxsop:
+ description:
+ - The RX-SOP level controls the sensitivity of the radio.
+ type: int
+ returned: success
+ sample: -70
+ channel_width:
+ description:
+ - Sets channel width (MHz) for 5Ghz band.
+ type: str
+ returned: success
+ sample: auto
+ valid_auto_channels:
+ description:
+ - Sets valid auto channels for 5Ghz band.
+ type: list
+ returned: success
+ two_four_ghz_settings:
+ description:
+ - Settings related to 2.4Ghz band
+ type: complex
+ returned: success
+ contains:
+ max_power:
+ description:
+ - Sets max power (dBm) of 2.4Ghz band.
+ type: int
+ returned: success
+ sample: 12
+ min_power:
+ description:
+ - Sets minmimum power (dBm) of 2.4Ghz band.
+ type: int
+ returned: success
+ sample: 12
+ min_bitrate:
+ description:
+ - Sets minimum bitrate (Mbps) of 2.4Ghz band.
+ type: float
+ returned: success
+ sample: 5.5
+ rxsop:
+ description:
+ - The RX-SOP level controls the sensitivity of the radio.
+ type: int
+ returned: success
+ sample: -70
+ ax_enabled:
+ description:
+ - Determines whether ax radio on 2.4Ghz band is on or off.
+ type: bool
+ returned: success
+ sample: true
+ valid_auto_channels:
+ description:
+ - Sets valid auto channels for 2.4Ghz band.
+ type: list
+ returned: success
+ sample: 6
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def get_profile(meraki, profiles, name):
+ for profile in profiles:
+ if profile['name'] == name:
+ return profile
+ return None
+
+
+def construct_payload(meraki):
+ payload = {}
+ if meraki.params['name'] is not None:
+ payload['name'] = meraki.params['name']
+ if meraki.params['band_selection_type'] is not None:
+ payload['bandSelectionType'] = meraki.params['band_selection_type']
+ if meraki.params['min_bitrate_type'] is not None:
+ payload['minBitrateType'] = meraki.params['min_bitrate_type']
+ if meraki.params['client_balancing_enabled'] is not None:
+ payload['clientBalancingEnabled'] = meraki.params['client_balancing_enabled']
+ if meraki.params['ap_band_settings'] is not None:
+ payload['apBandSettings'] = {}
+ if meraki.params['ap_band_settings']['mode'] is not None:
+ payload['apBandSettings']['bandOperationMode'] = meraki.params['ap_band_settings']['mode']
+ if meraki.params['ap_band_settings']['band_steering_enabled'] is not None:
+ payload['apBandSettings']['bandSteeringEnabled'] = meraki.params['ap_band_settings']['band_steering_enabled']
+ if meraki.params['five_ghz_settings'] is not None:
+ payload['fiveGhzSettings'] = {}
+ if meraki.params['five_ghz_settings']['max_power'] is not None:
+ payload['fiveGhzSettings']['maxPower'] = meraki.params['five_ghz_settings']['max_power']
+ if meraki.params['five_ghz_settings']['min_bitrate'] is not None:
+ payload['fiveGhzSettings']['minBitrate'] = meraki.params['five_ghz_settings']['min_bitrate']
+ if meraki.params['five_ghz_settings']['min_power'] is not None:
+ payload['fiveGhzSettings']['minPower'] = meraki.params['five_ghz_settings']['min_power']
+ if meraki.params['five_ghz_settings']['rxsop'] is not None:
+ payload['fiveGhzSettings']['rxsop'] = meraki.params['five_ghz_settings']['rxsop']
+ if meraki.params['five_ghz_settings']['channel_width'] is not None:
+ payload['fiveGhzSettings']['channelWidth'] = meraki.params['five_ghz_settings']['channel_width']
+ if meraki.params['five_ghz_settings']['valid_auto_channels'] is not None:
+ payload['fiveGhzSettings']['validAutoChannels'] = meraki.params['five_ghz_settings']['valid_auto_channels']
+ if meraki.params['two_four_ghz_settings'] is not None:
+ payload['twoFourGhzSettings'] = {}
+ if meraki.params['two_four_ghz_settings']['max_power'] is not None:
+ payload['twoFourGhzSettings']['maxPower'] = meraki.params['two_four_ghz_settings']['max_power']
+ if meraki.params['two_four_ghz_settings']['min_bitrate'] is not None:
+ payload['twoFourGhzSettings']['minBitrate'] = meraki.params['two_four_ghz_settings']['min_bitrate']
+ if meraki.params['two_four_ghz_settings']['min_power'] is not None:
+ payload['twoFourGhzSettings']['minPower'] = meraki.params['two_four_ghz_settings']['min_power']
+ if meraki.params['two_four_ghz_settings']['rxsop'] is not None:
+ payload['twoFourGhzSettings']['rxsop'] = meraki.params['two_four_ghz_settings']['rxsop']
+ if meraki.params['two_four_ghz_settings']['ax_enabled'] is not None:
+ payload['twoFourGhzSettings']['axEnabled'] = meraki.params['two_four_ghz_settings']['ax_enabled']
+ if meraki.params['two_four_ghz_settings']['valid_auto_channels'] is not None:
+ payload['twoFourGhzSettings']['validAutoChannels'] = meraki.params['two_four_ghz_settings']['valid_auto_channels']
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ band_arg_spec = dict(mode=dict(type='str', aliases=['band_operation_mode'], choices=['2.4ghz', '5ghz', 'dual']),
+ band_steering_enabled=dict(type='bool'),
+ )
+
+ five_arg_spec = dict(max_power=dict(type='int'),
+ min_bitrate=dict(type='int', choices=[6, 9, 12, 18, 24, 36, 48, 54]),
+ min_power=dict(type='int'),
+ rxsop=dict(type='int'),
+ channel_width=dict(type='str', choices=['auto', '20', '40', '80']),
+ valid_auto_channels=dict(type='list', elements='int', choices=[36,
+ 40,
+ 44,
+ 48,
+ 52,
+ 56,
+ 60,
+ 64,
+ 100,
+ 104,
+ 108,
+ 112,
+ 116,
+ 120,
+ 124,
+ 128,
+ 132,
+ 136,
+ 140,
+ 144,
+ 149,
+ 153,
+ 157,
+ 161,
+ 165]),
+ )
+
+ two_arg_spec = dict(max_power=dict(type='int'),
+ min_bitrate=dict(type='float', choices=[1,
+ 2,
+ 5.5,
+ 6,
+ 9,
+ 11,
+ 12,
+ 18,
+ 24,
+ 36,
+ 48,
+ 54]),
+ min_power=dict(type='int'),
+ rxsop=dict(type='int'),
+ ax_enabled=dict(type='bool'),
+ valid_auto_channels=dict(type='list', elements='int', choices=[1, 6, 11]),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query', 'absent'], default='present'),
+ org_name=dict(type='str', aliases=['organization']),
+ org_id=dict(type='str'),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ profile_id=dict(type='str', aliases=['id']),
+ band_selection_type=dict(type='str', choices=['ssid', 'ap']),
+ min_bitrate_type=dict(type='str', choices=['band', 'ssid']),
+ name=dict(type='str'),
+ client_balancing_enabled=dict(type='bool'),
+ ap_band_settings=dict(type='dict', options=band_arg_spec),
+ five_ghz_settings=dict(type='dict', options=five_arg_spec),
+ two_four_ghz_settings=dict(type='dict', options=two_arg_spec),
+ )
+
+ # 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='mr_rf_profile')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_all_urls = {'mr_rf_profile': '/networks/{net_id}/wireless/rfProfiles'}
+ query_urls = {'mr_rf_profile': '/networks/{net_id}/wireless/rfProfiles/{profile_id}'}
+ create_urls = {'mr_rf_profile': '/networks/{net_id}/wireless/rfProfiles'}
+ update_urls = {'mr_rf_profile': '/networks/{net_id}/wireless/rfProfiles/{profile_id}'}
+ delete_urls = {'mr_rf_profile': '/networks/{net_id}/wireless/rfProfiles/{profile_id}'}
+
+ meraki.url_catalog['get_all'].update(query_all_urls)
+ meraki.url_catalog['get_one'].update(query_urls)
+ meraki.url_catalog['create'] = create_urls
+ meraki.url_catalog['update'] = update_urls
+ meraki.url_catalog['delete'] = delete_urls
+
+ if meraki.params['five_ghz_settings'] is not None:
+ if meraki.params['five_ghz_settings']['max_power'] is not None:
+ if meraki.params['five_ghz_settings']['max_power'] < 8 or meraki.params['five_ghz_settings']['max_power'] > 30:
+ meraki.fail_json(msg="5ghz max power must be between 8 and 30.")
+ if meraki.params['five_ghz_settings']['min_power'] is not None:
+ if meraki.params['five_ghz_settings']['min_power'] < 8 or meraki.params['five_ghz_settings']['min_power'] > 30:
+ meraki.fail_json(msg="5ghz min power must be between 8 and 30.")
+ if meraki.params['five_ghz_settings']['rxsop'] is not None:
+ if meraki.params['five_ghz_settings']['rxsop'] < -95 or meraki.params['five_ghz_settings']['rxsop'] > -65:
+ meraki.fail_json(msg="5ghz min power must be between 8 and 30.")
+ if meraki.params['two_four_ghz_settings'] is not None:
+ if meraki.params['two_four_ghz_settings']['max_power'] is not None:
+ if meraki.params['two_four_ghz_settings']['max_power'] < 5 or meraki.params['two_four_ghz_settings']['max_power'] > 30:
+ meraki.fail_json(msg="5ghz max power must be between 5 and 30.")
+ if meraki.params['two_four_ghz_settings']['min_power'] is not None:
+ if meraki.params['two_four_ghz_settings']['min_power'] < 5 or meraki.params['two_four_ghz_settings']['min_power'] > 30:
+ meraki.fail_json(msg="5ghz min power must be between 5 and 30.")
+ if meraki.params['two_four_ghz_settings']['rxsop'] is not None:
+ if meraki.params['two_four_ghz_settings']['rxsop'] < -95 or meraki.params['two_four_ghz_settings']['rxsop'] > -65:
+ meraki.fail_json(msg="5ghz min power must be between 8 and 30.")
+
+ org_id = meraki.params['org_id']
+ net_id = meraki.params['net_id']
+ profile_id = meraki.params['profile_id']
+ profile = None
+ profiles = None
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+ if profile_id is None:
+ path = meraki.construct_path('get_all', net_id=net_id)
+ profiles = meraki.request(path, method='GET')
+ # profile = get_profile(meraki, profiles, meraki.params['name'])
+ profile_id = next((profile['id'] for profile in profiles if profile['name'] == meraki.params['name']), None)
+
+ if meraki.params['state'] == 'query':
+ if profile_id is not None:
+ path = meraki.construct_path('get_one', net_id=net_id, custom={'profile_id': profile_id})
+ result = meraki.request(path, method='GET')
+ meraki.result['data'] = result
+ meraki.exit_json(**meraki.result)
+ if profiles is None:
+ path = meraki.construct_path('get_all', net_id=net_id)
+ profiles = meraki.request(path, method='GET')
+ meraki.result['data'] = profiles
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ payload = construct_payload(meraki)
+ if profile_id is None: # Create a new RF profile
+ if meraki.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('create', net_id=net_id)
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ path = meraki.construct_path('get_one', net_id=net_id, custom={'profile_id': profile_id})
+ original = meraki.request(path, method='GET')
+ if meraki.is_update_required(original, payload) is True:
+ if meraki.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id, custom={'profile_id': profile_id})
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result['data'] = original
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'absent':
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('delete', net_id=net_id, custom={'profile_id': profile_id})
+ response = meraki.request(path, method='DELETE')
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py
new file mode 100644
index 000000000..7858c208c
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py
@@ -0,0 +1,221 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_mr_settings
+short_description: Manage general settings for Meraki wireless networks
+description:
+- Allows for configuration of general settings in Meraki MR wireless networks.
+options:
+ state:
+ description:
+ - Query or edit wireless settings.
+ type: str
+ choices: [ present, query]
+ default: present
+ net_name:
+ description:
+ - Name of network.
+ type: str
+ net_id:
+ description:
+ - ID of network.
+ type: str
+ upgrade_strategy:
+ description:
+ - The upgrade strategy to apply to the network.
+ - Requires firmware version MR 26.8 or higher.
+ choices: [ minimize_upgrade_time, minimize_client_downtime ]
+ type: str
+ ipv6_bridge_enabled:
+ description:
+ - Toggle for enabling or disabling IPv6 bridging in a network.
+ - If enabled, SSIDs must also be configured to use bridge mode.
+ type: bool
+ led_lights_on:
+ description:
+ - Toggle for enabling or disabling LED lights on all APs in the network.
+ type: bool
+ location_analytics_enabled:
+ description:
+ - Toggle for enabling or disabling location analytics for your network.
+ type: bool
+ meshing_enabled:
+ description: Toggle for enabling or disabling meshing in a network.
+ type: bool
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query all settings
+ meraki_mr_settings:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+- name: Configure settings
+ meraki_mr_settings:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ upgrade_strategy: minimize_upgrade_time
+ ipv6_bridge_enabled: false
+ led_lights_on: true
+ location_analytics_enabled: true
+ meshing_enabled: true
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: List of wireless settings.
+ returned: success
+ type: complex
+ contains:
+ upgrade_strategy:
+ description:
+ - The upgrade strategy to apply to the network.
+ - Requires firmware version MR 26.8 or higher.
+ type: str
+ returned: success
+ sample: minimize_upgrade_time
+ ipv6_bridge_enabled:
+ description:
+ - Toggle for enabling or disabling IPv6 bridging in a network.
+ - If enabled, SSIDs must also be configured to use bridge mode.
+ type: bool
+ returned: success
+ sample: True
+ led_lights_on:
+ description:
+ - Toggle for enabling or disabling LED lights on all APs in the network.
+ type: bool
+ returned: success
+ sample: True
+ location_analytics_enabled:
+ description:
+ - Toggle for enabling or disabling location analytics for your network.
+ type: bool
+ returned: success
+ sample: True
+ meshing_enabled:
+ description: Toggle for enabling or disabling meshing in a network.
+ type: bool
+ returned: success
+ sample: True
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+from re import sub
+
+
+def convert_to_camel_case(string):
+ string = sub(r"(_|-)+", " ", string).title().replace(" ", "")
+ return string[0].lower() + string[1:]
+
+
+def construct_payload(meraki):
+ payload = {}
+ if meraki.params['upgrade_strategy'] is not None:
+ payload['upgradeStrategy'] = convert_to_camel_case(meraki.params['upgrade_strategy'])
+ if meraki.params['ipv6_bridge_enabled'] is not None:
+ payload['ipv6BridgeEnabled'] = meraki.params['ipv6_bridge_enabled']
+ if meraki.params['led_lights_on'] is not None:
+ payload['ledLightsOn'] = meraki.params['led_lights_on']
+ if meraki.params['location_analytics_enabled'] is not None:
+ payload['locationAnalyticsEnabled'] = meraki.params['location_analytics_enabled']
+ if meraki.params['meshing_enabled'] is not None:
+ payload['meshingEnabled'] = meraki.params['meshing_enabled']
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
+ org_name=dict(type='str', aliases=['organization']),
+ org_id=dict(type='str'),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ upgrade_strategy=dict(type='str', choices=['minimize_upgrade_time',
+ 'minimize_client_downtime']),
+ ipv6_bridge_enabled=dict(type='bool'),
+ led_lights_on=dict(type='bool'),
+ location_analytics_enabled=dict(type='bool'),
+ meshing_enabled=dict(type='bool'),
+ )
+
+ # 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='mr_settings')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'mr_settings': '/networks/{net_id}/wireless/settings'}
+ update_urls = {'mr_settings': '/networks/{net_id}/wireless/settings'}
+
+ meraki.url_catalog['get_one'].update(query_urls)
+ meraki.url_catalog['update'] = update_urls
+
+ org_id = meraki.params['org_id']
+ net_id = meraki.params['net_id']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_one', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('get_one', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ payload = construct_payload(meraki)
+ if meraki.is_update_required(original, payload) is True:
+ if meraki.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result['data'] = original
+ meraki.exit_json(**meraki.result)
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py
new file mode 100644
index 000000000..f6c242e0d
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py
@@ -0,0 +1,744 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_mr_ssid
+short_description: Manage wireless SSIDs in the Meraki cloud
+description:
+- Allows for management of SSIDs in a Meraki wireless environment.
+notes:
+- Deleting an SSID does not delete RADIUS servers.
+options:
+ state:
+ description:
+ - Specifies whether SNMP information should be queried or modified.
+ type: str
+ choices: [ absent, query, present ]
+ default: present
+ number:
+ description:
+ - SSID number within network.
+ type: int
+ aliases: [ssid_number]
+ name:
+ description:
+ - Name of SSID.
+ type: str
+ net_name:
+ description:
+ - Name of network.
+ type: str
+ net_id:
+ description:
+ - ID of network.
+ type: str
+ enabled:
+ description:
+ - Enable or disable SSID network.
+ type: bool
+ auth_mode:
+ description:
+ - Set authentication mode of network.
+ type: str
+ choices: [open, psk, open-with-radius, 8021x-meraki, 8021x-radius]
+ encryption_mode:
+ description:
+ - Set encryption mode of network.
+ type: str
+ choices: [wpa, eap, wpa-eap]
+ psk:
+ description:
+ - Password for wireless network.
+ - Requires auth_mode to be set to psk.
+ type: str
+ wpa_encryption_mode:
+ description:
+ - Encryption mode within WPA specification.
+ type: str
+ choices: [WPA1 and WPA2, WPA2 only, WPA3 Transition Mode, WPA3 only]
+ splash_page:
+ description:
+ - Set to enable splash page and specify type of splash.
+ type: str
+ choices: ['None',
+ 'Click-through splash page',
+ 'Billing',
+ 'Password-protected with Meraki RADIUS',
+ 'Password-protected with custom RADIUS',
+ 'Password-protected with Active Directory',
+ 'Password-protected with LDAP',
+ 'SMS authentication',
+ 'Systems Manager Sentry',
+ 'Facebook Wi-Fi',
+ 'Google OAuth',
+ 'Sponsored guest',
+ 'Cisco ISE']
+ radius_servers:
+ description:
+ - List of RADIUS servers.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description:
+ - IP address or hostname of RADIUS server.
+ type: str
+ required: true
+ port:
+ description:
+ - Port number RADIUS server is listening to.
+ type: int
+ secret:
+ description:
+ - RADIUS password.
+ - Setting password is not idempotent.
+ type: str
+ radius_proxy_enabled:
+ description:
+ - Enable or disable RADIUS Proxy on SSID.
+ type: bool
+ radius_coa_enabled:
+ description:
+ - Enable or disable RADIUS CoA (Change of Authorization) on SSID.
+ type: bool
+ radius_failover_policy:
+ description:
+ - Set client access policy in case RADIUS servers aren't available.
+ type: str
+ choices: [Deny access, Allow access]
+ radius_load_balancing_policy:
+ description:
+ - Set load balancing policy when multiple RADIUS servers are specified.
+ type: str
+ choices: [Strict priority order, Round robin]
+ radius_accounting_enabled:
+ description:
+ - Enable or disable RADIUS accounting.
+ type: bool
+ radius_accounting_servers:
+ description:
+ - List of RADIUS servers for RADIUS accounting.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description:
+ - IP address or hostname of RADIUS server.
+ type: str
+ required: true
+ port:
+ description:
+ - Port number RADIUS server is listening to.
+ type: int
+ secret:
+ description:
+ - RADIUS password.
+ - Setting password is not idempotent.
+ type: str
+ ip_assignment_mode:
+ description:
+ - Method of which SSID uses to assign IP addresses.
+ type: str
+ choices: ['NAT mode',
+ 'Bridge mode',
+ 'Layer 3 roaming',
+ 'Layer 3 roaming with a concentrator',
+ 'VPN']
+ lan_isolation_enabled:
+ description:
+ - Enable or disable Layer 2 Lan isolation.
+ - Requires C(ip_assignment_mode) to be C(Bridge mode).
+ type: bool
+ use_vlan_tagging:
+ description:
+ - Set whether to use VLAN tagging.
+ - Requires C(default_vlan_id) to be set.
+ type: bool
+ visible:
+ description:
+ - Enable or disable whether APs should broadcast this SSID.
+ type: bool
+ default_vlan_id:
+ description:
+ - Default VLAN ID.
+ - Requires C(ip_assignment_mode) to be C(Bridge mode) or C(Layer 3 roaming).
+ type: int
+ vlan_id:
+ description:
+ - ID number of VLAN on SSID.
+ - Requires C(ip_assignment_mode) to be C(ayer 3 roaming with a concentrator) or C(VPN).
+ type: int
+ ap_tags_vlan_ids:
+ description:
+ - List of VLAN tags.
+ - Requires C(ip_assignment_mode) to be C(Bridge mode) or C(Layer 3 roaming).
+ - Requires C(use_vlan_tagging) to be C(True).
+ type: list
+ elements: dict
+ suboptions:
+ tags:
+ description:
+ - List of AP tags.
+ type: list
+ elements: str
+ vlan_id:
+ description:
+ - Numerical identifier that is assigned to the VLAN.
+ type: int
+ walled_garden_enabled:
+ description:
+ - Enable or disable walled garden functionality.
+ type: bool
+ walled_garden_ranges:
+ description:
+ - List of walled garden ranges.
+ type: list
+ elements: str
+ available_on_all_aps:
+ description:
+ - Set whether all APs should broadcast the SSID or if it should be restricted to APs matching any availability tags.
+ - Requires C(ap_availability_tags) to be defined when set to C(False).
+ type: bool
+ ap_availability_tags:
+ description:
+ - Set whether SSID will be broadcast by APs with tags matching any of the tags in this list.
+ - Requires C(available_on_all_aps) to be C(false).
+ type: list
+ elements: str
+ min_bitrate:
+ description:
+ - Minimum bitrate (Mbps) allowed on SSID.
+ type: float
+ choices: [1, 2, 5.5, 6, 9, 11, 12, 18, 24, 36, 48, 54]
+ band_selection:
+ description:
+ - Set band selection mode.
+ type: str
+ choices: ['Dual band operation', '5 GHz band only', 'Dual band operation with Band Steering']
+ per_client_bandwidth_limit_up:
+ description:
+ - Maximum bandwidth in Mbps devices on SSID can upload.
+ type: int
+ per_client_bandwidth_limit_down:
+ description:
+ - Maximum bandwidth in Mbps devices on SSID can download.
+ type: int
+ concentrator_network_id:
+ description:
+ - The concentrator to use for 'Layer 3 roaming with a concentrator' or 'VPN'.
+ type: str
+ enterprise_admin_access:
+ description:
+ - Whether SSID is accessible by enterprise administrators.
+ type: str
+ choices: ['access disabled', 'access enabled']
+ splash_guest_sponsor_domains:
+ description:
+ - List of valid sponsor email domains for sponsored guest portal.
+ type: list
+ elements: str
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Enable and name SSID
+ meraki_ssid:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: WiFi
+ name: GuestSSID
+ enabled: true
+ visible: true
+ delegate_to: localhost
+
+- name: Set PSK with invalid encryption mode
+ meraki_ssid:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: WiFi
+ name: GuestSSID
+ auth_mode: psk
+ psk: abc1234
+ encryption_mode: eap
+ ignore_errors: yes
+ delegate_to: localhost
+
+- name: Configure RADIUS servers
+ meraki_ssid:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: WiFi
+ name: GuestSSID
+ auth_mode: open-with-radius
+ radius_servers:
+ - host: 192.0.1.200
+ port: 1234
+ secret: abc98765
+ delegate_to: localhost
+
+- name: Enable click-through splash page
+ meraki_ssid:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: WiFi
+ name: GuestSSID
+ splash_page: Click-through splash page
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: List of wireless SSIDs.
+ returned: success
+ type: complex
+ contains:
+ number:
+ description: Zero-based index number for SSIDs.
+ returned: success
+ type: int
+ sample: 0
+ name:
+ description:
+ - Name of wireless SSID.
+ - This value is what is broadcasted.
+ returned: success
+ type: str
+ sample: CorpWireless
+ enabled:
+ description: Enabled state of wireless network.
+ returned: success
+ type: bool
+ sample: true
+ splash_page:
+ description: Splash page to show when user authenticates.
+ returned: success
+ type: str
+ sample: Click-through splash page
+ ssid_admin_accessible:
+ description: Whether SSID is administratively accessible.
+ returned: success
+ type: bool
+ sample: true
+ auth_mode:
+ description: Authentication method.
+ returned: success
+ type: str
+ sample: psk
+ psk:
+ description: Secret wireless password.
+ returned: success
+ type: str
+ sample: SecretWiFiPass
+ encryption_mode:
+ description: Wireless traffic encryption method.
+ returned: success
+ type: str
+ sample: wpa
+ wpa_encryption_mode:
+ description: Enabled WPA versions.
+ returned: success
+ type: str
+ sample: WPA2 only
+ ip_assignment_mode:
+ description: Wireless client IP assignment method.
+ returned: success
+ type: str
+ sample: NAT mode
+ min_bitrate:
+ description: Minimum bitrate a wireless client can connect at.
+ returned: success
+ type: int
+ sample: 11
+ band_selection:
+ description: Wireless RF frequency wireless network will be broadcast on.
+ returned: success
+ type: str
+ sample: 5 GHz band only
+ per_client_bandwidth_limit_up:
+ description: Maximum upload bandwidth a client can use.
+ returned: success
+ type: int
+ sample: 1000
+ per_client_bandwidth_limit_down:
+ description: Maximum download bandwidth a client can use.
+ returned: success
+ type: int
+ sample: 0
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def get_available_number(data):
+ for item in data:
+ if "Unconfigured SSID" in item["name"]:
+ return item["number"]
+ return False
+
+
+def get_ssid_number(name, data):
+ for ssid in data:
+ if name == ssid["name"]:
+ return ssid["number"]
+ return False
+
+
+def get_ssids(meraki, net_id):
+ path = meraki.construct_path("get_all", net_id=net_id)
+ return meraki.request(path, method="GET")
+
+
+def construct_payload(meraki):
+ param_map = {
+ "name": "name",
+ "enabled": "enabled",
+ "authMode": "auth_mode",
+ "encryptionMode": "encryption_mode",
+ "psk": "psk",
+ "wpaEncryptionMode": "wpa_encryption_mode",
+ "splashPage": "splash_page",
+ "radiusServers": "radius_servers",
+ "radiusProxyEnabled": "radius_proxy_enabled",
+ "radiusCoaEnabled": "radius_coa_enabled",
+ "radiusFailoverPolicy": "radius_failover_policy",
+ "radiusLoadBalancingPolicy": "radius_load_balancing_policy",
+ "radiusAccountingEnabled": "radius_accounting_enabled",
+ "radiusAccountingServers": "radius_accounting_servers",
+ "ipAssignmentMode": "ip_assignment_mode",
+ "useVlanTagging": "use_vlan_tagging",
+ "visible": "visible",
+ "concentratorNetworkId": "concentrator_network_id",
+ "vlanId": "vlan_id",
+ "lanIsolationEnabled": "lan_isolation_enabled",
+ "availableOnAllAps": "available_on_all_aps",
+ "availabilityTags": "ap_availability_tags",
+ "defaultVlanId": "default_vlan_id",
+ "apTagsAndVlanIds": "ap_tags_vlan_ids",
+ "walledGardenEnabled": "walled_garden_enabled",
+ "walledGardenRanges": "walled_garden_ranges",
+ "minBitrate": "min_bitrate",
+ "bandSelection": "band_selection",
+ "perClientBandwidthLimitUp": "per_client_bandwidth_limit_up",
+ "perClientBandwidthLimitDown": "per_client_bandwidth_limit_down",
+ "enterpriseAdminAccess": "enterprise_admin_access",
+ "splashGuestSponsorDomains": "splash_guest_sponsor_domains",
+ }
+
+ payload = dict()
+ for k, v in param_map.items():
+ if meraki.params[v] is not None:
+ payload[k] = meraki.params[v]
+
+ if meraki.params["ap_tags_vlan_ids"] is not None:
+ for i in payload["apTagsAndVlanIds"]:
+ try:
+ i["vlanId"] = i["vlan_id"]
+ del i["vlan_id"]
+ except KeyError:
+ pass
+
+ return payload
+
+
+def per_line_to_str(data):
+ return data.replace("\n", " ")
+
+
+def main():
+ default_payload = {
+ "name": "Unconfigured SSID",
+ "auth_mode": "open",
+ "splashPage": "None",
+ "perClientBandwidthLimitUp": 0,
+ "perClientBandwidthLimitDown": 0,
+ "ipAssignmentMode": "NAT mode",
+ "enabled": False,
+ "bandSelection": "Dual band operation",
+ "minBitrate": 11,
+ }
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ radius_arg_spec = dict(
+ host=dict(type="str", required=True),
+ port=dict(type="int"),
+ secret=dict(type="str", no_log=True),
+ )
+ vlan_arg_spec = dict(
+ tags=dict(type="list", elements="str"),
+ vlan_id=dict(type="int"),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ state=dict(
+ type="str", choices=["absent", "present", "query"], default="present"
+ ),
+ number=dict(type="int", aliases=["ssid_number"]),
+ name=dict(type="str"),
+ org_name=dict(type="str", aliases=["organization"]),
+ org_id=dict(type="str"),
+ net_name=dict(type="str"),
+ net_id=dict(type="str"),
+ enabled=dict(type="bool"),
+ auth_mode=dict(
+ type="str",
+ choices=["open", "psk", "open-with-radius", "8021x-meraki", "8021x-radius"],
+ ),
+ encryption_mode=dict(type="str", choices=["wpa", "eap", "wpa-eap"]),
+ psk=dict(type="str", no_log=True),
+ wpa_encryption_mode=dict(
+ type="str",
+ choices=["WPA1 and WPA2", "WPA2 only", "WPA3 Transition Mode", "WPA3 only"],
+ ),
+ splash_page=dict(
+ type="str",
+ choices=[
+ "None",
+ "Click-through splash page",
+ "Billing",
+ "Password-protected with Meraki RADIUS",
+ "Password-protected with custom RADIUS",
+ "Password-protected with Active Directory",
+ "Password-protected with LDAP",
+ "SMS authentication",
+ "Systems Manager Sentry",
+ "Facebook Wi-Fi",
+ "Google OAuth",
+ "Sponsored guest",
+ "Cisco ISE",
+ ],
+ ),
+ radius_servers=dict(
+ type="list", default=None, elements="dict", options=radius_arg_spec
+ ),
+ radius_proxy_enabled=dict(type="bool"),
+ radius_coa_enabled=dict(type="bool"),
+ radius_failover_policy=dict(
+ type="str", choices=["Deny access", "Allow access"]
+ ),
+ radius_load_balancing_policy=dict(
+ type="str", choices=["Strict priority order", "Round robin"]
+ ),
+ radius_accounting_enabled=dict(type="bool"),
+ radius_accounting_servers=dict(
+ type="list", elements="dict", options=radius_arg_spec
+ ),
+ ip_assignment_mode=dict(
+ type="str",
+ choices=[
+ "NAT mode",
+ "Bridge mode",
+ "Layer 3 roaming",
+ "Layer 3 roaming with a concentrator",
+ "VPN",
+ ],
+ ),
+ use_vlan_tagging=dict(type="bool"),
+ visible=dict(type="bool"),
+ lan_isolation_enabled=dict(type="bool"),
+ available_on_all_aps=dict(type="bool"),
+ ap_availability_tags=dict(type="list", elements="str"),
+ concentrator_network_id=dict(type="str"),
+ vlan_id=dict(type="int"),
+ default_vlan_id=dict(type="int"),
+ ap_tags_vlan_ids=dict(
+ type="list", default=None, elements="dict", options=vlan_arg_spec
+ ),
+ walled_garden_enabled=dict(type="bool"),
+ walled_garden_ranges=dict(type="list", elements="str"),
+ min_bitrate=dict(
+ type="float", choices=[1, 2, 5.5, 6, 9, 11, 12, 18, 24, 36, 48, 54]
+ ),
+ band_selection=dict(
+ type="str",
+ choices=[
+ "Dual band operation",
+ "5 GHz band only",
+ "Dual band operation with Band Steering",
+ ],
+ ),
+ per_client_bandwidth_limit_up=dict(type="int"),
+ per_client_bandwidth_limit_down=dict(type="int"),
+ enterprise_admin_access=dict(
+ type="str", choices=["access disabled", "access enabled"]
+ ),
+ splash_guest_sponsor_domains=dict(type="list", elements="str"),
+ )
+
+ # 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="ssid")
+ meraki.params["follow_redirects"] = "all"
+
+ query_urls = {"ssid": "/networks/{net_id}/wireless/ssids"}
+ query_url = {"ssid": "/networks/{net_id}/wireless/ssids/{number}"}
+ update_url = {"ssid": "/networks/{net_id}/wireless/ssids/"}
+
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["get_one"].update(query_url)
+ meraki.url_catalog["update"] = update_url
+
+ payload = None
+
+ # execute checks for argument completeness
+ if meraki.params["psk"]:
+ if meraki.params["auth_mode"] != "psk":
+ meraki.fail_json(msg="PSK is only allowed when auth_mode is set to psk")
+ if meraki.params["encryption_mode"] != "wpa":
+ meraki.fail_json(msg="PSK requires encryption_mode be set to wpa")
+ if meraki.params["radius_servers"]:
+ if meraki.params["auth_mode"] not in ("open-with-radius", "8021x-radius"):
+ meraki.fail_json(
+ msg="radius_servers requires auth_mode to be open-with-radius or 8021x-radius"
+ )
+ if meraki.params["radius_accounting_enabled"] is True:
+ if meraki.params["auth_mode"] not in ("open-with-radius", "8021x-radius"):
+ meraki.fail_json(
+ msg="radius_accounting_enabled is only allowed when auth_mode is open-with-radius or 8021x-radius"
+ )
+ if meraki.params["radius_accounting_servers"] is True:
+ if (
+ meraki.params["auth_mode"] not in ("open-with-radius", "8021x-radius")
+ or meraki.params["radius_accounting_enabled"] is False
+ ):
+ meraki.fail_json(
+ msg="radius_accounting_servers is only allowed when auth_mode is open_with_radius or 8021x-radius and \
+ radius_accounting_enabled is true"
+ )
+ if meraki.params["use_vlan_tagging"] is True:
+ if meraki.params["default_vlan_id"] is None:
+ meraki.fail_json(
+ msg="default_vlan_id is required when use_vlan_tagging is True"
+ )
+ if meraki.params["lan_isolation_enabled"] is not None:
+ if meraki.params["ip_assignment_mode"] not in ("Bridge mode"):
+ meraki.fail_json(
+ msg="lan_isolation_enabled is only allowed when ip_assignment_mode is Bridge mode"
+ )
+ if meraki.params["available_on_all_aps"] is False:
+ if not meraki.params["ap_availability_tags"]:
+ meraki.fail_json(
+ msg="available_on_all_aps is only allowed to be false when ap_availability_tags is defined"
+ )
+ if meraki.params["ap_availability_tags"]:
+ if meraki.params["available_on_all_aps"] is not False:
+ meraki.fail_json(
+ msg="ap_availability_tags is only allowed when available_on_all_aps is false"
+ )
+ # 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"]
+ net_id = meraki.params["net_id"]
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params["net_name"], data=nets)
+
+ if meraki.params["state"] == "query":
+ if meraki.params["name"]:
+ ssid_id = get_ssid_number(meraki.params["name"], get_ssids(meraki, net_id))
+ path = meraki.construct_path(
+ "get_one", net_id=net_id, custom={"number": ssid_id}
+ )
+ meraki.result["data"] = meraki.request(path, method="GET")
+ elif meraki.params["number"] is not None:
+ path = meraki.construct_path(
+ "get_one", net_id=net_id, custom={"number": meraki.params["number"]}
+ )
+ meraki.result["data"] = meraki.request(path, method="GET")
+ else:
+ meraki.result["data"] = get_ssids(meraki, net_id)
+ elif meraki.params["state"] == "present":
+ payload = construct_payload(meraki)
+ ssids = get_ssids(meraki, net_id)
+ number = meraki.params["number"]
+ if number is None:
+ number = get_ssid_number(meraki.params["name"], ssids)
+ original = ssids[number]
+ if meraki.is_update_required(original, payload, optional_ignore=["secret"]):
+ ssid_id = meraki.params["number"]
+ if ssid_id is None: # Name should be used to lookup number
+ ssid_id = get_ssid_number(meraki.params["name"], ssids)
+ if ssid_id is False:
+ ssid_id = get_available_number(ssids)
+ if ssid_id is False:
+ meraki.fail_json(
+ msg="No unconfigured SSIDs are available. Specify a number."
+ )
+ if meraki.check_mode is True:
+ original.update(payload)
+ meraki.result["data"] = original
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("update", net_id=net_id) + str(ssid_id)
+ result = meraki.request(path, "PUT", payload=json.dumps(payload))
+ meraki.result["data"] = result
+ meraki.result["changed"] = True
+ else:
+ meraki.result["data"] = original
+ elif meraki.params["state"] == "absent":
+ ssids = get_ssids(meraki, net_id)
+ ssid_id = meraki.params["number"]
+ if ssid_id is None: # Name should be used to lookup number
+ ssid_id = get_ssid_number(meraki.params["name"], ssids)
+ if ssid_id is False:
+ # This will return True as long as there's an unclaimed SSID number!
+ ssid_id = get_available_number(ssids)
+ # There are no available SSIDs or SSID numbers
+ if ssid_id is False:
+ meraki.fail_json(
+ msg="No SSID found by specified name and no numbers unclaimed."
+ )
+ meraki.result["changed"] = False
+ meraki.result["data"] = {}
+ meraki.exit_json(**meraki.result)
+ if meraki.check_mode is True:
+ meraki.result["data"] = {}
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("update", net_id=net_id) + str(ssid_id)
+ payload = default_payload
+ payload["name"] = payload["name"] + " " + str(ssid_id + 1)
+ result = meraki.request(path, "PUT", payload=json.dumps(payload))
+ meraki.result["data"] = result
+ meraki.result["changed"] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_list.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_list.py
new file mode 100644
index 000000000..bd5e9205f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_list.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_access_list
+short_description: Manage access lists for Meraki switches in the Meraki cloud
+version_added: "0.1.0"
+description:
+- Configure and query information about access lists on Meraki switches within the Meraki cloud.
+notes:
+- Some of the options are likely only used for developers within Meraki.
+options:
+ state:
+ description:
+ - Specifies whether object should be queried, created/modified, or removed.
+ choices: [absent, present, query]
+ default: query
+ type: str
+ net_name:
+ description:
+ - Name of network which configuration is applied to.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of network which configuration is applied to.
+ type: str
+ rules:
+ description:
+ - List of access control rules.
+ type: list
+ elements: dict
+ suboptions:
+ comment:
+ description:
+ - Description of the rule.
+ type: str
+ policy:
+ description:
+ - Action to take on matching traffic.
+ choices: [allow, deny]
+ type: str
+ ip_version:
+ description:
+ - Type of IP packets to match.
+ choices: [any, ipv4, ipv6]
+ type: str
+ protocol:
+ description:
+ - Type of protocol to match.
+ choices: [any, tcp, udp]
+ type: str
+ src_cidr:
+ description:
+ - CIDR notation of source IP address to match.
+ type: str
+ src_port:
+ description:
+ - Port number of source port to match.
+ - May be a port number or 'any'.
+ type: str
+ dst_cidr:
+ description:
+ - CIDR notation of source IP address to match.
+ type: str
+ dst_port:
+ description:
+ - Port number of destination port to match.
+ - May be a port number or 'any'.
+ type: str
+ vlan:
+ description:
+ - Incoming traffic VLAN.
+ - May be any port between 1-4095 or 'any'.
+ type: str
+author:
+ Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Set access list
+ meraki_switch_access_list:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ rules:
+ - comment: Fake rule
+ policy: allow
+ ip_version: ipv4
+ protocol: udp
+ src_cidr: 192.0.1.0/24
+ src_port: "4242"
+ dst_cidr: 1.2.3.4/32
+ dst_port: "80"
+ vlan: "100"
+ delegate_to: localhost
+
+- name: Query access lists
+ meraki_switch_access_list:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: List of administrators.
+ returned: success
+ type: complex
+ contains:
+ rules:
+ description:
+ - List of access control rules.
+ type: list
+ contains:
+ comment:
+ description:
+ - Description of the rule.
+ type: str
+ sample: User rule
+ returned: success
+ policy:
+ description:
+ - Action to take on matching traffic.
+ type: str
+ sample: allow
+ returned: success
+ ip_version:
+ description:
+ - Type of IP packets to match.
+ type: str
+ sample: ipv4
+ returned: success
+ protocol:
+ description:
+ - Type of protocol to match.
+ type: str
+ sample: udp
+ returned: success
+ src_cidr:
+ description:
+ - CIDR notation of source IP address to match.
+ type: str
+ sample: 192.0.1.0/24
+ returned: success
+ src_port:
+ description:
+ - Port number of source port to match.
+ type: str
+ sample: 1234
+ returned: success
+ dst_cidr:
+ description:
+ - CIDR notation of source IP address to match.
+ type: str
+ sample: 1.2.3.4/32
+ returned: success
+ dst_port:
+ description:
+ - Port number of destination port to match.
+ type: str
+ sample: 80
+ returned: success
+ vlan:
+ description:
+ - Incoming traffic VLAN.
+ type: str
+ sample: 100
+ returned: success
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import recursive_diff
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+from copy import deepcopy
+
+
+def construct_payload(params):
+ payload = {'rules': []}
+ for rule in params['rules']:
+ new_rule = dict()
+ if 'comment' in rule:
+ new_rule['comment'] = rule['comment']
+ if 'policy' in rule:
+ new_rule['policy'] = rule['policy']
+ if 'ip_version' in rule:
+ new_rule['ipVersion'] = rule['ip_version']
+ if 'protocol' in rule:
+ new_rule['protocol'] = rule['protocol']
+ if 'src_cidr' in rule:
+ new_rule['srcCidr'] = rule['src_cidr']
+ if 'src_port' in rule:
+ try: # Need to convert to int for comparison later
+ new_rule['srcPort'] = int(rule['src_port'])
+ except ValueError:
+ pass
+ if 'dst_cidr' in rule:
+ new_rule['dstCidr'] = rule['dst_cidr']
+ if 'dst_port' in rule:
+ try: # Need to convert to int for comparison later
+ new_rule['dstPort'] = int(rule['dst_port'])
+ except ValueError:
+ pass
+ if 'vlan' in rule:
+ try: # Need to convert to int for comparison later
+ new_rule['vlan'] = int(rule['vlan'])
+ except ValueError:
+ pass
+ payload['rules'].append(new_rule)
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ rules_arg_spec = dict(comment=dict(type='str'),
+ policy=dict(type='str', choices=['allow', 'deny']),
+ ip_version=dict(type='str', choices=['ipv4', 'ipv6', 'any']),
+ protocol=dict(type='str', choices=['tcp', 'udp', 'any']),
+ src_cidr=dict(type='str'),
+ src_port=dict(type='str'),
+ dst_cidr=dict(type='str'),
+ dst_port=dict(type='str'),
+ vlan=dict(type='str'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'present', 'query'], default='query'),
+ net_name=dict(type='str', aliases=['network']),
+ net_id=dict(type='str'),
+ rules=dict(type='list', elements='dict', options=rules_arg_spec),
+ )
+
+ # 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='switch_access_list')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_url = {'switch_access_list': '/networks/{net_id}/switch/accessControlLists'}
+ update_url = {'switch_access_list': '/networks/{net_id}/switch/accessControlLists'}
+
+ meraki.url_catalog['get_all'].update(query_url)
+ meraki.url_catalog['update'] = update_url
+
+ org_id = meraki.params['org_id']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ result = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ meraki.result['data'] = result
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ payload = construct_payload(meraki.params)
+ comparable = deepcopy(original)
+ if len(comparable['rules']) > 1:
+ del comparable['rules'][len(comparable['rules']) - 1] # Delete the default rule for comparison
+ else:
+ del comparable['rules'][0]
+ if meraki.is_update_required(comparable, payload):
+ if meraki.check_mode is True:
+ default_rule = original['rules'][len(original['rules']) - 1]
+ payload['rules'].append(default_rule)
+ new_rules = {'rules': payload['rules']}
+ meraki.result['data'] = new_rules
+ meraki.result['changed'] = True
+ diff = recursive_diff(original, new_rules)
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1]}
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ diff = recursive_diff(original, payload)
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1]}
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_policies.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_policies.py
new file mode 100644
index 000000000..ebf35c35a
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_policies.py
@@ -0,0 +1,608 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022
+# Marcin Woźniak (@y0rune) <y0rune@aol.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 = r"""
+---
+module: meraki_ms_access_policies
+short_description: Manage Switch Access Policies in the Meraki cloud
+description: Module for managing a Switch Access Policies in the Meraki cloud
+options:
+ state:
+ description:
+ - Specifies whether SNMP information should be queried or modified.
+ type: str
+ choices: [ absent, query, present ]
+ default: present
+ number:
+ description:
+ - Number of the access_policy.
+ type: int
+ aliases: [access_policy_number]
+ name:
+ description:
+ - Name of Access Policy.
+ type: str
+ net_id:
+ description:
+ - ID of network.
+ type: str
+ org_id:
+ description:
+ - ID of organization associated to a network.
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [name, network]
+ type: str
+ auth_method:
+ description:
+ - Set authentication method in the policy.
+ type: str
+ choices: ["Meraki authentication", "my RADIUS server"]
+ guest_vlan:
+ description:
+ - Guest Vlan
+ type: int
+ access_policy_type:
+ description:
+ - Set type of the access policy
+ type: str
+ choices: ["802.1x", "MAC authentication bypass", "Hybrid authentication"]
+ systems_management_enrollment:
+ description:
+ - Set if the Systems Management Enrollemnt is enabled or disabled
+ type: bool
+ default: False
+ radius_servers:
+ description:
+ - List of RADIUS servers.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description:
+ - IP address or hostname of RADIUS server.
+ type: str
+ required: true
+ port:
+ description:
+ - Port number RADIUS server is listening to.
+ type: int
+ secret:
+ description:
+ - RADIUS password.
+ - Setting password is not idempotent.
+ type: str
+ radius_testing:
+ description:
+ - Set status of testing a radius.
+ type: bool
+ default: True
+ voice_vlan_clients:
+ description:
+ - If is enabled that means Voice VLAN client require authentication
+ type: bool
+ default: True
+ radius_coa_enabled:
+ description:
+ - Enable or disable RADIUS CoA (Change of Authorization).
+ type: bool
+ radius_accounting_enabled:
+ description:
+ - Enable or disable RADIUS accounting.
+ type: bool
+ radius_accounting_servers:
+ description:
+ - List of RADIUS servers for RADIUS accounting.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description:
+ - IP address or hostname of RADIUS server.
+ type: str
+ required: true
+ port:
+ description:
+ - Port number RADIUS server is listening to.
+ type: int
+ secret:
+ description:
+ - RADIUS password.
+ type: str
+ host_mode:
+ description:
+ - Choose the Host Mode for the access policy.
+ type: str
+ choices: ["Single-Host", "Multi-Domain", "Multi-Host", "Multi-Auth"]
+ data_vlan_id:
+ description:
+ - Set a Data VLAN ID for Critical Auth VLAN
+ type: int
+ voice_vlan_id:
+ description:
+ - Set a Voice VLAN ID for Critical Auth VLAN
+ type: int
+ suspend_port_bounce:
+ description:
+ - Enable or disable the Suspend Port Bounce when RADIUS servers are unreachable.
+ type: bool
+ default: False
+ radius_attribute_group_policy_name:
+ description:
+ - Enable that attribute for a RADIUS
+ type: str
+ choices: ["Filter-Id", ""]
+ default: ""
+author:
+- Marcin Woźniak (@y0rune)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Create access policy with auth_method is "Meraki authentication"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: abc123
+ state: present
+ name: "Meraki authentication policy"
+ auth_method: "Meraki authentication"
+ net_name: YourNet
+ org_name: YourOrg
+ delegate_to: localhost
+
+- name: Create access policy with auth_method is "my Radius Server"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: abc123
+ access_policy_type: "802.1x"
+ host_mode: "Single-Host"
+ state: present
+ name: "Meraki authentication policy"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ net_name: YourNet
+ org_name: YourOrg
+ radius_coa_enabled: False
+ radius_accounting_enabled: False
+ guest_vlan: 10
+ voice_vlan_clients: False
+"""
+
+RETURN = r"""
+data:
+ description: List of Access Policies
+ returned: success
+ type: complex
+ contains:
+ number:
+ description: Number of the Access Policy
+ returned: success
+ type: int
+ sample: 1
+ name:
+ description: Name of the Access Policy
+ returned: success
+ type: str
+ sample: Policy with 802.1x
+ access_policy_type:
+ description: Type of the access policy
+ returned: success
+ type: str
+ sample: 802.1x
+ guest_vlan_id:
+ description: ID of the Guest Vlan
+ returned: success
+ type: int
+ sample: 10
+ host_mode:
+ description: Choosen teh Host Mode for the access policy
+ returned: success
+ type: str
+ sample: Single-Host
+ radius:
+ description: List of radius specific list
+ returned: success
+ type: complex
+ contains:
+ critial_auth:
+ description: Critial Auth List
+ returned: success
+ type: complex
+ contains:
+ data_vlan_id:
+ description: VLAN ID for data
+ returned: success
+ type: int
+ sample: 10
+ suspend_port_bounce:
+ description: Enable or disable suspend port bounce
+ returned: success
+ type: bool
+ sample: False
+ voice_vlan_id:
+ description: VLAN ID for voice
+ returned: success
+ type: int
+ sample: 10
+ failed_auth_vlan_id:
+ description: VLAN ID when failed auth
+ returned: success
+ type: int
+ sample: 11
+ re_authentication_interval:
+ description: Interval of re-authentication
+ returned: success
+ type: int
+ sample:
+ radius_coa_enabled:
+ description:
+ - Enable or disable RADIUS CoA (Change of Authorization).
+ type: bool
+ radius_accounting_enabled:
+ description:
+ - Enable or disable RADIUS accounting.
+ type: bool
+ radius_accounting_servers:
+ description:
+ - List of RADIUS servers for RADIUS accounting.
+ type: list
+ elements: dict
+ radius_servers:
+ description:
+ - List of RADIUS servers.
+ type: list
+ elements: dict
+ radius_attribute_group_policy_name:
+ description: Enable the radius group attribute
+ returned: success
+ type: str
+ choices: [ "11", ""]
+ sample: 11
+ radius_testing_enabled:
+ description: Enable or disable Radius Testing
+ returned: success
+ type: bool
+ sample: True
+ voice_vlan_clients:
+ description: Enable or disable Voice Vlan Clients
+ returned: success
+ type: bool
+ sample: False
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import recursive_diff
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def convert_vlan_id(vlan_id):
+ if vlan_id == "":
+ return None
+ elif vlan_id == 0:
+ return None
+ elif vlan_id in range(1, 4094):
+ return vlan_id
+
+
+def convert_radius_attribute_group_policy_name(arg):
+ if arg == "Filter-Id":
+ return 11
+ else:
+ return ""
+
+
+def main():
+ argument_spec = meraki_argument_spec()
+
+ radius_arg_spec = dict(
+ host=dict(type="str", required=True),
+ port=dict(type="int"),
+ secret=dict(type="str", no_log=True),
+ )
+
+ argument_spec.update(
+ state=dict(
+ type="str",
+ choices=["present", "query", "absent"],
+ default="present",
+ ),
+ net_id=dict(type="str"),
+ net_name=dict(type="str", aliases=["network"]),
+ number=dict(type="int", aliases=["access_policy_number"]),
+ name=dict(type="str"),
+ auth_method=dict(
+ type="str",
+ choices=["Meraki authentication", "my RADIUS server"],
+ ),
+ guest_vlan=dict(type="int"),
+ access_policy_type=dict(
+ type="str",
+ choices=[
+ "802.1x",
+ "MAC authentication bypass",
+ "Hybrid authentication",
+ ],
+ ),
+ systems_management_enrollment=dict(type="bool", default=False),
+ radius_servers=dict(
+ type="list", default=None, elements="dict", options=radius_arg_spec
+ ),
+ radius_testing=dict(type="bool", default="True"),
+ voice_vlan_clients=dict(type="bool", default="True"),
+ radius_coa_enabled=dict(type="bool"),
+ radius_accounting_enabled=dict(type="bool"),
+ radius_accounting_servers=dict(
+ type="list", elements="dict", options=radius_arg_spec
+ ),
+ host_mode=dict(
+ type="str",
+ choices=["Single-Host", "Multi-Domain", "Multi-Host", "Multi-Auth"],
+ ),
+ data_vlan_id=dict(type="int"),
+ voice_vlan_id=dict(type="int"),
+ suspend_port_bounce=dict(type="bool", default="False"),
+ radius_attribute_group_policy_name=dict(
+ type="str", choices=["Filter-Id", ""], default=""
+ ),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+ meraki = MerakiModule(module, function="access_policies")
+
+ net_id = meraki.params["net_id"]
+ net_name = meraki.params["net_name"]
+
+ org_id = meraki.params["org_id"]
+ org_name = meraki.params["org_name"]
+
+ if meraki.params["net_name"] and meraki.params["net_id"]:
+ meraki.fail_json(msg="net_name and net_id are mutually exclusive")
+
+ if meraki.params["org_name"] and meraki.params["org_id"]:
+ meraki.fail_json(msg="org_name and org_id are mutually exclusive")
+
+ if net_id or net_name:
+ if net_id is None:
+ if org_id is None:
+ org_id = meraki.get_org_id(org_name)
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=net_name, data=nets)
+
+ query_urls = {"access_policies": "/networks/{net_id}/switch/accessPolicies"}
+ query_url = {
+ "access_policies": "/networks/{net_id}/switch/accessPolicies/{number}"
+ }
+ update_url = {
+ "access_policies": "/networks/{net_id}/switch/accessPolicies/{number}"
+ }
+ create_url = {"access_policies": "/networks/{net_id}/switch/accessPolicies"}
+
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["get_one"].update(query_url)
+ meraki.url_catalog["update"] = update_url
+ meraki.url_catalog["create"] = create_url
+
+ payload_auth = {
+ "name": meraki.params["name"],
+ "radiusServers": [],
+ "radiusTestingEnabled": False,
+ "radiusGroupAttribute": meraki.params[
+ "radius_attribute_group_policy_name"
+ ],
+ "radius": {
+ "criticalAuth": {
+ "dataVlanId": None,
+ "voiceVlanId": None,
+ "suspendPortBounce": False,
+ },
+ "failedAuthVlanId": None,
+ "reAuthenticationInterval": None,
+ },
+ "radiusCoaSupportEnabled": False,
+ "radiusAccountingEnabled": False,
+ "radiusAccountingServers": [],
+ "hostMode": "Single-Host",
+ "accessPolicyType": "802.1x",
+ "voiceVlanClients": True,
+ "systems_management_enrollment": meraki.params[
+ "systems_management_enrollment"
+ ],
+ "guestVlanId": meraki.params["guest_vlan"],
+ "urlRedirectWalledGardenEnabled": False,
+ }
+
+ payload_radius = {
+ "name": meraki.params["name"],
+ "radiusServers": meraki.params["radius_servers"],
+ "radiusTestingEnabled": meraki.params["radius_testing"],
+ "radiusGroupAttribute": convert_radius_attribute_group_policy_name(
+ meraki.params["radius_attribute_group_policy_name"]
+ ),
+ "radius": {
+ "criticalAuth": {
+ "dataVlanId": convert_vlan_id(meraki.params["data_vlan_id"]),
+ "voiceVlanId": convert_vlan_id(meraki.params["voice_vlan_id"]),
+ "suspendPortBounce": meraki.params["suspend_port_bounce"],
+ },
+ "failedAuthVlanId": None,
+ "reAuthenticationInterval": None,
+ },
+ "radiusCoaSupportEnabled": meraki.params["radius_coa_enabled"],
+ "hostMode": meraki.params["host_mode"],
+ "accessPolicyType": meraki.params["access_policy_type"],
+ "guestVlanId": meraki.params["guest_vlan"],
+ "voiceVlanClients": meraki.params["voice_vlan_clients"],
+ "urlRedirectWalledGardenEnabled": False,
+ "radiusAccountingEnabled": meraki.params["radius_accounting_enabled"],
+ "radiusAccountingServers": meraki.params["radius_accounting_servers"],
+ "systems_management_enrollment": meraki.params[
+ "systems_management_enrollment"
+ ],
+ }
+
+ if meraki.params["state"] == "query":
+ if meraki.params["number"]:
+ path = meraki.construct_path(
+ "get_one",
+ net_id=net_id,
+ custom={
+ "number": meraki.params["number"],
+ },
+ )
+ response = meraki.request(path, method="GET")
+ meraki.result["data"] = response
+ else:
+ path = meraki.construct_path("get_all", net_id=net_id)
+ response = meraki.request(path, method="GET")
+ meraki.result["data"] = response
+ elif meraki.params["state"] == "present":
+
+ query_path_all = meraki.construct_path(
+ "get_all",
+ net_id=net_id,
+ )
+
+ original_all = meraki.request(query_path_all, method="GET")
+
+ for i in original_all:
+ if i.get("name") == meraki.params["name"]:
+ meraki.params["number"] = i.get("accessPolicyNumber")
+
+ if meraki.params["number"] is None:
+ path = meraki.construct_path(
+ "create",
+ net_id=net_id,
+ )
+ if meraki.params["auth_method"] == "Meraki authentication":
+ response = meraki.request(
+ path, method="POST", payload=json.dumps(payload_auth)
+ )
+ meraki.result["changed"] = True
+ meraki.result["data"] = response
+ elif meraki.params["auth_method"] == "my RADIUS server":
+ response = meraki.request(
+ path, method="POST", payload=json.dumps(payload_radius)
+ )
+ meraki.result["changed"] = True
+ meraki.result["data"] = response
+ else:
+ query_path = meraki.construct_path(
+ "get_one",
+ net_id=net_id,
+ custom={
+ "number": meraki.params["number"],
+ },
+ )
+
+ update_path = meraki.construct_path(
+ "update",
+ net_id=net_id,
+ custom={
+ "number": meraki.params["number"],
+ },
+ )
+
+ proposed = ""
+
+ if meraki.params["auth_method"] == "Meraki authentication":
+ proposed = payload_auth.copy()
+ elif meraki.params["auth_method"] == "my RADIUS server":
+ proposed = payload_radius.copy()
+
+ original = meraki.request(query_path, method="GET")
+
+ ignored_parameters = [
+ "accessPolicyNumber",
+ "secret",
+ "systems_management_enrollment",
+ ]
+
+ if meraki.params["radius_accounting_enabled"]:
+ proposed.update(
+ {
+ "radiusAccountingServers": meraki.params[
+ "radius_accounting_servers"
+ ],
+ }
+ )
+ else:
+ proposed.update(
+ {
+ "radiusAccountingServers": [],
+ }
+ )
+
+ if meraki.params["radius_servers"]:
+ proposed.update(
+ {
+ "radiusServers": meraki.params["radius_servers"],
+ }
+ )
+ else:
+ proposed.update(
+ {
+ "radiusServers": [],
+ }
+ )
+
+ if meraki.is_update_required(
+ original,
+ proposed,
+ optional_ignore=ignored_parameters,
+ ):
+
+ if meraki.check_mode is True:
+ original.update(proposed)
+ meraki.result["data"] = original
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+
+ response = meraki.request(
+ update_path, method="PUT", payload=json.dumps(proposed)
+ )
+ meraki.result["changed"] = True
+ meraki.result["data"] = response
+ else:
+ meraki.result["data"] = original
+
+ elif meraki.params["state"] == "absent":
+ path = meraki.construct_path(
+ "update",
+ net_id=net_id,
+ custom={
+ "number": meraki.params["number"],
+ },
+ )
+
+ response = meraki.request(path, method="DELETE")
+ meraki.result["changed"] = True
+ meraki.result["data"] = response
+
+ meraki.exit_json(**meraki.result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_l3_interface.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_l3_interface.py
new file mode 100644
index 000000000..716ec8d9b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_l3_interface.py
@@ -0,0 +1,373 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_l3_interface
+short_description: Manage routed interfaces on MS switches
+description:
+- Allows for creation, management, and visibility into routed interfaces on Meraki MS switches.
+notes:
+- Once a layer 3 interface is created, the API does not allow updating the interface and specifying C(default_gateway).
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ type: str
+ choices: [ present, query, absent ]
+ default: present
+ serial:
+ description:
+ - Serial number of MS switch hosting the layer 3 interface.
+ type: str
+ vlan_id:
+ description:
+ - The VLAN this routed interface is on.
+ - VLAN must be between 1 and 4094.
+ type: int
+ default_gateway:
+ description:
+ - The next hop for any traffic that isn't going to a directly connected subnet or over a static route.
+ - This IP address must exist in a subnet with a routed interface.
+ type: str
+ interface_ip:
+ description:
+ - The IP address this switch will use for layer 3 routing on this VLAN or subnet.
+ - This cannot be the same as the switch's management IP.
+ type: str
+ interface_id:
+ description:
+ - Uniqiue identification number for layer 3 interface.
+ type: str
+ multicast_routing:
+ description:
+ - Enable multicast support if multicast routing between VLANs is required.
+ type: str
+ choices: [disabled, enabled, IGMP snooping querier]
+ name:
+ description:
+ - A friendly name or description for the interface or VLAN.
+ type: str
+ subnet:
+ description:
+ - The network that this routed interface is on, in CIDR notation.
+ type: str
+ ospf_settings:
+ description:
+ - The OSPF routing settings of the interface.
+ type: dict
+ suboptions:
+ cost:
+ description:
+ - The path cost for this interface.
+ type: int
+ area:
+ description:
+ - The OSPF area to which this interface should belong.
+ - Can be either 'disabled' or the identifier of an existing OSPF area.
+ type: str
+ is_passive_enabled:
+ description:
+ - When enabled, OSPF will not run on the interface, but the subnet will still be advertised.
+ type: bool
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query all l3 interfaces
+ meraki_ms_l3_interface:
+ auth_key: abc123
+ state: query
+ serial: aaa-bbb-ccc
+
+- name: Query one l3 interface
+ meraki_ms_l3_interface:
+ auth_key: abc123
+ state: query
+ serial: aaa-bbb-ccc
+ name: Test L3 interface
+
+- name: Create l3 interface
+ meraki_ms_l3_interface:
+ auth_key: abc123
+ state: present
+ serial: aaa-bbb-ccc
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 1
+ is_passive_enabled: true
+
+- name: Update l3 interface
+ meraki_ms_l3_interface:
+ auth_key: abc123
+ state: present
+ serial: aaa-bbb-ccc
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 2
+ is_passive_enabled: true
+
+- name: Delete l3 interface
+ meraki_ms_l3_interface:
+ auth_key: abc123
+ state: absent
+ serial: aaa-bbb-ccc
+ interface_id: abc123344566
+'''
+
+RETURN = r'''
+data:
+ description: Information about the layer 3 interfaces.
+ returned: success
+ type: complex
+ contains:
+ vlan_id:
+ description: The VLAN this routed interface is on.
+ returned: success
+ type: int
+ sample: 10
+ default_gateway:
+ description: The next hop for any traffic that isn't going to a directly connected subnet or over a static route.
+ returned: success
+ type: str
+ sample: 192.168.2.1
+ interface_ip:
+ description: The IP address this switch will use for layer 3 routing on this VLAN or subnet.
+ returned: success
+ type: str
+ sample: 192.168.2.2
+ interface_id:
+ description: Uniqiue identification number for layer 3 interface.
+ returned: success
+ type: str
+ sample: 62487444811111120
+ multicast_routing:
+ description: Enable multicast support if multicast routing between VLANs is required.
+ returned: success
+ type: str
+ sample: disabled
+ name:
+ description: A friendly name or description for the interface or VLAN.
+ returned: success
+ type: str
+ sample: L3 interface
+ subnet:
+ description: The network that this routed interface is on, in CIDR notation.
+ returned: success
+ type: str
+ sample: 192.168.2.0/24
+ ospf_settings:
+ description: The OSPF routing settings of the interface.
+ returned: success
+ type: complex
+ contains:
+ cost:
+ description: The path cost for this interface.
+ returned: success
+ type: int
+ sample: 1
+ area:
+ description: The OSPF area to which this interface should belong.
+ returned: success
+ type: str
+ sample: 0
+ is_passive_enabled:
+ description: When enabled, OSPF will not run on the interface, but the subnet will still be advertised.
+ returned: success
+ type: bool
+ sample: true
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def construct_payload(meraki):
+ payload = {}
+ if meraki.params['name'] is not None:
+ payload['name'] = meraki.params['name']
+ if meraki.params['subnet'] is not None:
+ payload['subnet'] = meraki.params['subnet']
+ if meraki.params['interface_ip'] is not None:
+ payload['interfaceIp'] = meraki.params['interface_ip']
+ if meraki.params['multicast_routing'] is not None:
+ payload['multicastRouting'] = meraki.params['multicast_routing']
+ if meraki.params['vlan_id'] is not None:
+ payload['vlanId'] = meraki.params['vlan_id']
+ if meraki.params['default_gateway'] is not None:
+ payload['defaultGateway'] = meraki.params['default_gateway']
+ if meraki.params['ospf_settings'] is not None:
+ payload['ospfSettings'] = {}
+ if meraki.params['ospf_settings']['area'] is not None:
+ payload['ospfSettings']['area'] = meraki.params['ospf_settings']['area']
+ if meraki.params['ospf_settings']['cost'] is not None:
+ payload['ospfSettings']['cost'] = meraki.params['ospf_settings']['cost']
+ if meraki.params['ospf_settings']['is_passive_enabled'] is not None:
+ payload['ospfSettings']['isPassiveEnabled'] = meraki.params['ospf_settings']['is_passive_enabled']
+ return payload
+
+
+def get_interface_id(meraki, data, name):
+ # meraki.fail_json(msg=data)
+ for interface in data:
+ if interface['name'] == name:
+ return interface['interfaceId']
+ return None
+
+
+def get_interface(interfaces, interface_id):
+ for interface in interfaces:
+ if interface['interfaceId'] == interface_id:
+ return interface
+ return None
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ ospf_arg_spec = dict(area=dict(type='str'),
+ cost=dict(type='int'),
+ is_passive_enabled=dict(type='bool'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query', 'absent'], default='present'),
+ serial=dict(type='str'),
+ name=dict(type='str'),
+ subnet=dict(type='str'),
+ interface_id=dict(type='str'),
+ interface_ip=dict(type='str'),
+ multicast_routing=dict(type='str', choices=['disabled', 'enabled', 'IGMP snooping querier']),
+ vlan_id=dict(type='int'),
+ default_gateway=dict(type='str'),
+ ospf_settings=dict(type='dict', default=None, options=ospf_arg_spec),
+ )
+
+ # 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='ms_l3_interfaces')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'ms_l3_interfaces': '/devices/{serial}/switch/routing/interfaces'}
+ query_one_urls = {'ms_l3_interfaces': '/devices/{serial}/switch/routing/interfaces'}
+ create_urls = {'ms_l3_interfaces': '/devices/{serial}/switch/routing/interfaces'}
+ update_urls = {'ms_l3_interfaces': '/devices/{serial}/switch/routing/interfaces/{interface_id}'}
+ delete_urls = {'ms_l3_interfaces': '/devices/{serial}/switch/routing/interfaces/{interface_id}'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['get_one'].update(query_one_urls)
+ meraki.url_catalog['create'] = create_urls
+ meraki.url_catalog['update'] = update_urls
+ meraki.url_catalog['delete'] = delete_urls
+
+ payload = None
+
+ if meraki.params['vlan_id'] is not None:
+ if meraki.params['vlan_id'] < 1 or meraki.params['vlan_id'] > 4094:
+ meraki.fail_json(msg='vlan_id must be between 1 and 4094')
+
+ interface_id = meraki.params['interface_id']
+ interfaces = None
+ if interface_id is None:
+ if meraki.params['name'] is not None:
+ path = meraki.construct_path('get_all', custom={'serial': meraki.params['serial']})
+ interfaces = meraki.request(path, method='GET')
+ interface_id = get_interface_id(meraki, interfaces, meraki.params['name'])
+
+ # manipulate or modify the state as needed (this is going to be the
+ # part where your module will do what it needs to do)
+
+ if meraki.params['state'] == 'query':
+ if interface_id is not None: # Query one interface
+ path = meraki.construct_path('get_one', custom={'serial': meraki.params['serial'],
+ 'interface_id': interface_id})
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ else: # Query all interfaces
+ path = meraki.construct_path('get_all', custom={'serial': meraki.params['serial']})
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ if interface_id is None: # Create a new interface
+ payload = construct_payload(meraki)
+ if meraki.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('create', custom={'serial': meraki.params['serial']})
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ if interfaces is None:
+ path = meraki.construct_path('get_all', custom={'serial': meraki.params['serial']})
+ interfaces = meraki.request(path, method='GET')
+ payload = construct_payload(meraki)
+ interface = get_interface(interfaces, interface_id)
+ if meraki.is_update_required(interface, payload):
+ if meraki.check_mode is True:
+ interface.update(payload)
+ meraki.result['data'] = interface
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', custom={'serial': meraki.params['serial'],
+ 'interface_id': interface_id})
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result['data'] = interface
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'absent':
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('delete', custom={'serial': meraki.params['serial'],
+ 'interface_id': meraki.params['interface_id']})
+ response = meraki.request(path, method='DELETE')
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_link_aggregation.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_link_aggregation.py
new file mode 100644
index 000000000..a38eda7dc
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_link_aggregation.py
@@ -0,0 +1,258 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_link_aggregation
+short_description: Manage link aggregations on MS switches
+version_added: "1.2.0"
+description:
+- Allows for management of MS switch link aggregations in a Meraki environment.
+notes:
+- Switch profile ports are not supported in this module.
+options:
+ state:
+ description:
+ - Specifies whether SNMP information should be queried or modified.
+ type: str
+ choices: [ absent, query, present ]
+ default: present
+ net_name:
+ description:
+ - Name of network.
+ type: str
+ net_id:
+ description:
+ - ID of network.
+ type: str
+ lag_id:
+ description:
+ - ID of lag to query or modify.
+ type: str
+ switch_ports:
+ description:
+ - List of switchports to include in link aggregation.
+ type: list
+ elements: dict
+ suboptions:
+ serial:
+ description:
+ - Serial number of switch to own link aggregation.
+ type: str
+ port_id:
+ description:
+ - Port number which should be included in link aggregation.
+ type: str
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Create LAG
+ meraki_ms_link_aggregation:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_switch_net_name}}'
+ switch_ports:
+ - serial: '{{serial_switch}}'
+ port_id: "1"
+ - serial: '{{serial_switch}}'
+ port_id: "2"
+ delegate_to: localhost
+
+- name: Update LAG
+ meraki_ms_link_aggregation:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_switch_net_name}}'
+ lag_id: '{{lag_id}}'
+ switch_ports:
+ - serial: '{{serial_switch}}'
+ port_id: "1"
+ - serial: '{{serial_switch}}'
+ port_id: "2"
+ - serial: '{{serial_switch}}'
+ port_id: "3"
+ - serial: '{{serial_switch}}'
+ port_id: "4"
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: List of aggregated links.
+ returned: success
+ type: complex
+ contains:
+ id:
+ description:
+ - ID of link aggregation.
+ returned: success
+ type: str
+ sample: "MTK3M4A2ZDdfM3=="
+ switch_ports:
+ description:
+ - List of switch ports to be included in link aggregation.
+ returned: success
+ type: complex
+ contains:
+ port_id:
+ description:
+ - Port number.
+ type: str
+ returned: success
+ sample: "1"
+ serial:
+ description:
+ - Serial number of switch on which port resides.
+ type: str
+ returned: success
+ sample: "ABCD-1234-WXYZ"
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def get_lags(meraki, net_id):
+ path = meraki.construct_path('get_all', net_id=net_id)
+ return meraki.request(path, method='GET')
+
+
+def is_lag_valid(lags, lag_id):
+ for lag in lags:
+ if lag['id'] == lag_id:
+ return lag
+ return False
+
+
+def construct_payload(meraki):
+ payload = dict()
+ if meraki.params['switch_ports'] is not None:
+ payload['switchPorts'] = []
+ for port in meraki.params['switch_ports']:
+ port_config = {'serial': port['serial'],
+ 'portId': port['port_id'],
+ }
+ payload['switchPorts'].append(port_config)
+ # if meraki.params['switch_profile_ports'] is not None:
+ # payload['switchProfilePorts'] = []
+ # for port in meraki.params['switch_profile_ports']:
+ # port_config = {'profile': port['profile'],
+ # 'portId': port['port_id'],
+ # }
+ # payload['switchProfilePorts'].append(port_config)
+ return payload
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ switch_ports_args = dict(serial=dict(type='str'),
+ port_id=dict(type='str'),
+ )
+
+ # switch_profile_ports_args = dict(profile=dict(type='str'),
+ # port_id=dict(type='str'),
+ # )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'present', 'query'], default='present'),
+ org_name=dict(type='str', aliases=['organization']),
+ org_id=dict(type='str'),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ lag_id=dict(type='str'),
+ switch_ports=dict(type='list', default=None, elements='dict', options=switch_ports_args),
+ # switch_profile_ports=dict(type='list', default=None, elements='dict', options=switch_profile_ports_args),
+ )
+
+ # 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='ms_link_aggregation')
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'ms_link_aggregation': '/networks/{net_id}/switch/linkAggregations'}
+ create_url = {'ms_link_aggregation': '/networks/{net_id}/switch/linkAggregations'}
+ update_url = {'ms_link_aggregation': '/networks/{net_id}/switch/linkAggregations/{lag_id}'}
+ delete_url = {'ms_link_aggregation': '/networks/{net_id}/switch/linkAggregations/{lag_id}'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['create'] = create_url
+ meraki.url_catalog['update'] = update_url
+ meraki.url_catalog['delete'] = delete_url
+
+ payload = None
+
+ # execute checks for argument completeness
+ # 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']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ if meraki.params['lag_id'] is not None: # Need to update
+ lag = is_lag_valid(get_lags(meraki, net_id), meraki.params['lag_id'])
+ if lag is not False: # Lag ID is valid
+ payload = construct_payload(meraki)
+ if meraki.is_update_required(lag, payload) is True:
+ path = meraki.construct_path('update', net_id=net_id, custom={'lag_id': meraki.params['lag_id']})
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['changed'] = True
+ meraki.result['data'] = response
+ else:
+ meraki.result['data'] = lag
+ else:
+ meraki.fail_json("Provided lag_id is not valid.")
+ else:
+ path = meraki.construct_path('create', net_id=net_id)
+ payload = construct_payload(meraki)
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ meraki.result['changed'] = True
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'absent':
+ path = meraki.construct_path('delete', net_id=net_id, custom={'lag_id': meraki.params['lag_id']})
+ response = meraki.request(path, method='DELETE')
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py
new file mode 100644
index 000000000..a8aaed005
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py
@@ -0,0 +1,323 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_ospf
+short_description: Manage OSPF configuration on MS switches
+description:
+- Configure OSPF for compatible Meraki MS switches.
+options:
+ state:
+ description:
+ - Read or edit OSPF settings.
+ type: str
+ choices: [ present, query ]
+ default: present
+ net_name:
+ description:
+ - Name of network containing OSPF configuration.
+ type: str
+ aliases: [ name, network ]
+ net_id:
+ description:
+ - ID of network containing OSPF configuration.
+ type: str
+ enabled:
+ description:
+ - Enable or disable OSPF on the network.
+ type: bool
+ hello_timer:
+ description:
+ - Time interval, in seconds, at which hello packets will be sent to OSPF neighbors to maintain connectivity.
+ - Value must be between 1 and 255.
+ - Default is 10 seconds.
+ type: int
+ dead_timer:
+ description:
+ - Time interval to determine when the peer will be declared inactive.
+ - Value must be between 1 and 65535.
+ type: int
+ md5_authentication_enabled:
+ description:
+ - Whether to enable or disable MD5 authentication.
+ type: bool
+ md5_authentication_key:
+ description:
+ - MD5 authentication credentials.
+ type: dict
+ suboptions:
+ id:
+ description:
+ - MD5 authentication key index.
+ - Must be between 1 and 255.
+ type: str
+ passphrase:
+ description:
+ - Plain text authentication passphrase
+ type: str
+ areas:
+ description:
+ - List of areas in OSPF network.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - OSPF area ID
+ type: int
+ aliases: [ id ]
+ area_name:
+ description:
+ - Descriptive name of OSPF area.
+ type: str
+ aliases: [ name ]
+ area_type:
+ description:
+ - OSPF area type.
+ choices: [normal, stub, nssa]
+ type: str
+ aliases: [ type ]
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+ - name: Query OSPF settings
+ meraki_ms_ospf:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+
+ - name: Enable OSPF with check mode
+ meraki_ms_ospf:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ enabled: true
+ hello_timer: 20
+ dead_timer: 60
+ areas:
+ - area_id: 0
+ area_name: Backbone
+ area_type: normal
+ - area_id: 1
+ area_name: Office
+ area_type: nssa
+ md5_authentication_enabled: false
+'''
+
+RETURN = r'''
+data:
+ description: Information about queried object.
+ returned: success
+ type: complex
+ contains:
+ enabled:
+ description:
+ - Enable or disable OSPF on the network.
+ type: bool
+ hello_timer_in_seconds:
+ description:
+ - Time interval, in seconds, at which hello packets will be sent to OSPF neighbors to maintain connectivity.
+ type: int
+ dead_timer_in_seconds:
+ description:
+ - Time interval to determine when the peer will be declared inactive.
+ type: int
+ areas:
+ description:
+ - List of areas in OSPF network.
+ type: complex
+ contains:
+ area_id:
+ description:
+ - OSPF area ID
+ type: int
+ area_name:
+ description:
+ - Descriptive name of OSPF area.
+ type: str
+ area_type:
+ description:
+ - OSPF area type.
+ type: str
+ md5_authentication_enabled:
+ description:
+ - Whether to enable or disable MD5 authentication.
+ type: bool
+ md5_authentication_key:
+ description:
+ - MD5 authentication credentials.
+ type: complex
+ contains:
+ id:
+ description:
+ - MD5 key index.
+ type: int
+ passphrase:
+ description:
+ - Passphrase for MD5 key.
+ type: str
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def construct_payload(meraki):
+ payload_key_mapping = {'enabled': 'enabled',
+ 'hello_timer': 'helloTimerInSeconds',
+ 'dead_timer': 'deadTimerInSeconds',
+ 'areas': 'areas',
+ 'area_id': 'areaId',
+ 'area_name': 'areaName',
+ 'area_type': 'areaType',
+ 'md5_authentication_enabled': 'md5AuthenticationEnabled',
+ 'md5_authentication_key': 'md5AuthenticationKey',
+ 'id': 'id',
+ 'passphrase': 'passphrase',
+ }
+ payload = {}
+
+ # This may need to be reworked to avoid overwiting
+ for snake, camel in payload_key_mapping.items():
+ try:
+ if meraki.params[snake] is not None:
+ payload[camel] = meraki.params[snake]
+ if snake == 'areas':
+ if meraki.params['areas'] is not None and len(meraki.params['areas']) > 0:
+ payload['areas'] = []
+ for area in meraki.params['areas']:
+ area_settings = {'areaName': area['area_name'],
+ 'areaId': area['area_id'],
+ 'areaType': area['area_type'],
+ }
+ payload['areas'].append(area_settings)
+ # TODO: Does this code below have a purpose?
+ # elif snake == 'md5_authentication_key':
+ # if meraki.params['md5_authentication_key'] is not None:
+ # md5_settings = {'id': meraki.params['md5_authentication_key']['id'],
+ # 'passphrase': meraki.params['md5_authentication_key']['passphrase'],
+ # }
+ except KeyError:
+ pass
+
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ areas_arg_spec = dict(area_id=dict(type='int', aliases=['id']),
+ area_name=dict(type='str', aliases=['name']),
+ area_type=dict(type='str', aliases=['type'], choices=['normal', 'stub', 'nssa']),
+ )
+
+ md5_auth_arg_spec = dict(id=dict(type='str'),
+ passphrase=dict(type='str', no_log=True),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
+ net_id=dict(type='str'),
+ net_name=dict(type='str', aliases=['name', 'network']),
+ enabled=dict(type='bool'),
+ hello_timer=dict(type='int'),
+ dead_timer=dict(type='int'),
+ areas=dict(type='list', default=None, elements='dict', options=areas_arg_spec),
+ md5_authentication_enabled=dict(type='bool'),
+ md5_authentication_key=dict(type='dict', default=None, options=md5_auth_arg_spec, no_log=True),
+ )
+
+ # 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='ms_ospf')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'ms_ospf': '/networks/{net_id}/switch/routing/ospf'}
+ update_urls = {'ms_ospf': '/networks/{net_id}/switch/routing/ospf'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['update'] = update_urls
+
+ payload = None
+
+ # execute checks for argument completeness
+
+ if meraki.params['dead_timer'] is not None:
+ if meraki.params['dead_timer'] < 1 or meraki.params['dead_timer'] > 65535:
+ meraki.fail_json(msg='dead_timer must be between 1 and 65535')
+ if meraki.params['hello_timer'] is not None:
+ if meraki.params['hello_timer'] < 1 or meraki.params['hello_timer'] > 255:
+ meraki.fail_json(msg='hello_timer must be between 1 and 65535')
+ if meraki.params['md5_authentication_enabled'] is False:
+ if meraki.params['md5_authentication_key'] is not None:
+ meraki.fail_json(msg='md5_authentication_key must not be configured when md5_authentication_enabled is false')
+
+ # 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']
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None and meraki.params['net_name']:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ original = meraki.request(meraki.construct_path('get_all', net_id=net_id), method='GET')
+ payload = construct_payload(meraki)
+ if meraki.is_update_required(original, payload) is True:
+ if meraki.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if 'md5_authentication_key' in response:
+ response['md5_authentication_key']['passphrase'] = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ if 'md5_authentication_key' in original:
+ original['md5_authentication_key']['passphrase'] = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
+ meraki.result['data'] = original
+ meraki.exit_json(**meraki.result)
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py
new file mode 100644
index 000000000..bbe6282ac
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py
@@ -0,0 +1,278 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_stack
+short_description: Modify switch stacking configuration in Meraki.
+version_added: "1.3.0"
+description:
+- Allows for modification of Meraki MS switch stacks.
+notes:
+- Not all actions are idempotent. Specifically, creating a new stack will error if any switch is already in a stack.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: ['present', 'query', 'absent']
+ 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
+ stack_id:
+ description:
+ - ID of stack which is to be modified or deleted.
+ type: str
+ serials:
+ description:
+ - List of switch serial numbers which should be included or removed from a stack.
+ type: list
+ elements: str
+ name:
+ description:
+ - Name of stack.
+ type: str
+
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Create new stack
+ meraki_switch_stack:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ name: Test stack
+ serials:
+ - "ABCD-1231-4579"
+ - "ASDF-4321-0987"
+
+- name: Add switch to stack
+ meraki_switch_stack:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ stack_id: ABC12340987
+ serials:
+ - "ABCD-1231-4579"
+
+- name: Remove switch from stack
+ meraki_switch_stack:
+ auth_key: abc123
+ state: absent
+ org_name: YourOrg
+ net_name: YourNet
+ stack_id: ABC12340987
+ serials:
+ - "ABCD-1231-4579"
+
+- name: Query one stack
+ meraki_switch_stack:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ stack_id: ABC12340987
+'''
+
+RETURN = r'''
+data:
+ description: VPN settings.
+ returned: success
+ type: complex
+ contains:
+ id:
+ description: ID of switch stack.
+ returned: always
+ type: str
+ sample: 7636
+ name:
+ description: Descriptive name of switch stack.
+ returned: always
+ type: str
+ sample: MyStack
+ serials:
+ description: List of serial numbers in switch stack.
+ returned: always
+ type: list
+ sample:
+ - "QBZY-XWVU-TSRQ"
+ - "QBAB-CDEF-GHIJ"
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+from copy import deepcopy
+
+
+def get_stacks(meraki, net_id):
+ path = meraki.construct_path('get_all', net_id=net_id)
+ return meraki.request(path, method='GET')
+
+
+def get_stack(stack_id, stacks):
+ for stack in stacks:
+ if stack_id == stack['id']:
+ return stack
+ return None
+
+
+def get_stack_id(meraki, net_id):
+ stacks = get_stacks(meraki, net_id)
+ for stack in stacks:
+ if stack['name'] == meraki.params['name']:
+ return stack['id']
+
+
+def does_stack_exist(meraki, stacks):
+ for stack in stacks:
+ have = set(meraki.params['serials'])
+ want = set(stack['serials'])
+ if have == want:
+ return stack
+ return False
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query', 'absent'], default='present'),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ stack_id=dict(type='str'),
+ serials=dict(type='list', elements='str', default=None),
+ name=dict(type='str'),
+ )
+
+ # 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='switch_stack')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'switch_stack': '/networks/{net_id}/switch/stacks'}
+ query_url = {'switch_stack': '/networks/{net_id}/switch/stacks/{stack_id}'}
+ add_urls = {'switch_stack': '/networks/{net_id}/switch/stacks/{stack_id}/add'}
+ remove_urls = {'switch_stack': '/networks/{net_id}/switch/stacks/{stack_id}/remove'}
+ create_urls = {'switch_stack': '/networks/{net_id}/switch/stacks'}
+ delete_urls = {'switch_stack': '/networks/{net_id}/switch/stacks/{stack_id}'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['get_one'].update(query_url)
+ meraki.url_catalog['add'] = add_urls
+ meraki.url_catalog['remove'] = remove_urls
+ meraki.url_catalog['create'] = create_urls
+ meraki.url_catalog['delete'] = delete_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']
+ 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:
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'],
+ data=meraki.get_nets(org_id=org_id))
+
+ # assign and lookup stack_id
+ stack_id = meraki.params['stack_id']
+ if stack_id is None and meraki.params['name'] is not None:
+ stack_id = get_stack_id(meraki, net_id)
+ path = meraki.construct_path('get_all', net_id=net_id)
+ stacks = meraki.request(path, method='GET')
+
+ if meraki.params['state'] == 'query':
+ if stack_id is None:
+ meraki.result['data'] = stacks
+ else:
+ meraki.result['data'] = get_stack(stack_id, stacks)
+ elif meraki.params['state'] == 'present':
+ if meraki.params['stack_id'] is None:
+ payload = {'serials': meraki.params['serials'],
+ 'name': meraki.params['name'],
+ }
+ path = meraki.construct_path('create', net_id=net_id)
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ if meraki.status == 201:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ payload = {'serial': meraki.params['serials'][0]}
+ original = get_stack(stack_id, stacks)
+ comparable = deepcopy(original)
+ comparable.update(payload)
+ if meraki.params['serials'][0] not in comparable['serials']:
+ comparable['serials'].append(meraki.params['serials'][0])
+ # meraki.fail_json(msg=comparable)
+ if meraki.is_update_required(original, comparable, optional_ignore=['serial']):
+ path = meraki.construct_path('add', net_id=net_id, custom={'stack_id': stack_id})
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+ elif meraki.params['state'] == 'absent':
+ if meraki.params['serials'] is None:
+ path = meraki.construct_path('delete', net_id=net_id, custom={'stack_id': stack_id})
+ response = meraki.request(path, method='DELETE')
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ else:
+ for serial in meraki.params['serials']:
+ payload = {'serial': serial}
+ original = get_stack(stack_id, stacks)
+ comparable = deepcopy(original)
+ comparable.update(payload)
+ if serial in comparable['serials']:
+ comparable['serials'].remove(serial)
+ if meraki.is_update_required(original, comparable, optional_ignore=['serial']):
+ path = meraki.construct_path('remove', net_id=net_id, custom={'stack_id': stack_id})
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack_l3_interface.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack_l3_interface.py
new file mode 100644
index 000000000..46291c314
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack_l3_interface.py
@@ -0,0 +1,395 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_stack_l3_interface
+short_description: Manage routed interfaces on MS switches
+description:
+- Allows for creation, management, and visibility into routed interfaces on Meraki MS switches.
+notes:
+- Once a layer 3 interface is created, the API does not allow updating the interface and specifying C(default_gateway).
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ type: str
+ choices: [ present, query, absent ]
+ default: present
+ net_name:
+ description:
+ - Name of network which configuration is applied to.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of network which configuration is applied to.
+ type: str
+ stack_id:
+ description:
+ - The unique identifier of the stack.
+ type: str
+ vlan_id:
+ description:
+ - The VLAN this routed interface is on.
+ - VLAN must be between 1 and 4094.
+ type: int
+ default_gateway:
+ description:
+ - The next hop for any traffic that isn't going to a directly connected subnet or over a static route.
+ - This IP address must exist in a subnet with a routed interface.
+ type: str
+ interface_ip:
+ description:
+ - The IP address this switch will use for layer 3 routing on this VLAN or subnet.
+ - This cannot be the same as the switch's management IP.
+ type: str
+ interface_id:
+ description:
+ - Uniqiue identification number for layer 3 interface.
+ type: str
+ multicast_routing:
+ description:
+ - Enable multicast support if multicast routing between VLANs is required.
+ type: str
+ choices: [disabled, enabled, IGMP snooping querier]
+ name:
+ description:
+ - A friendly name or description for the interface or VLAN.
+ type: str
+ subnet:
+ description:
+ - The network that this routed interface is on, in CIDR notation.
+ type: str
+ ospf_settings:
+ description:
+ - The OSPF routing settings of the interface.
+ type: dict
+ suboptions:
+ cost:
+ description:
+ - The path cost for this interface.
+ type: int
+ area:
+ description:
+ - The OSPF area to which this interface should belong.
+ - Can be either 'disabled' or the identifier of an existing OSPF area.
+ type: str
+ is_passive_enabled:
+ description:
+ - When enabled, OSPF will not run on the interface, but the subnet will still be advertised.
+ type: bool
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query all l3 interfaces
+ meraki_ms_stack_l3_interface:
+ auth_key: abc123
+ state: query
+ serial: aaa-bbb-ccc
+
+- name: Query one l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: abc123
+ state: query
+ serial: aaa-bbb-ccc
+ name: Test L3 interface
+
+- name: Create l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: abc123
+ state: present
+ serial: aaa-bbb-ccc
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 1
+ is_passive_enabled: true
+
+- name: Update l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: abc123
+ state: present
+ serial: aaa-bbb-ccc
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 2
+ is_passive_enabled: true
+
+- name: Delete l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: abc123
+ state: absent
+ serial: aaa-bbb-ccc
+ interface_id: abc123344566
+'''
+
+RETURN = r'''
+data:
+ description: Information about the layer 3 interfaces.
+ returned: success
+ type: complex
+ contains:
+ vlan_id:
+ description: The VLAN this routed interface is on.
+ returned: success
+ type: int
+ sample: 10
+ default_gateway:
+ description: The next hop for any traffic that isn't going to a directly connected subnet or over a static route.
+ returned: success
+ type: str
+ sample: 192.168.2.1
+ interface_ip:
+ description: The IP address this switch will use for layer 3 routing on this VLAN or subnet.
+ returned: success
+ type: str
+ sample: 192.168.2.2
+ interface_id:
+ description: Uniqiue identification number for layer 3 interface.
+ returned: success
+ type: str
+ sample: 62487444811111120
+ multicast_routing:
+ description: Enable multicast support if multicast routing between VLANs is required.
+ returned: success
+ type: str
+ sample: disabled
+ name:
+ description: A friendly name or description for the interface or VLAN.
+ returned: success
+ type: str
+ sample: L3 interface
+ subnet:
+ description: The network that this routed interface is on, in CIDR notation.
+ returned: success
+ type: str
+ sample: 192.168.2.0/24
+ ospf_settings:
+ description: The OSPF routing settings of the interface.
+ returned: success
+ type: complex
+ contains:
+ cost:
+ description: The path cost for this interface.
+ returned: success
+ type: int
+ sample: 1
+ area:
+ description: The OSPF area to which this interface should belong.
+ returned: success
+ type: str
+ sample: 0
+ is_passive_enabled:
+ description: When enabled, OSPF will not run on the interface, but the subnet will still be advertised.
+ returned: success
+ type: bool
+ sample: true
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def construct_payload(meraki):
+ payload = {}
+ if meraki.params['name'] is not None:
+ payload['name'] = meraki.params['name']
+ if meraki.params['subnet'] is not None:
+ payload['subnet'] = meraki.params['subnet']
+ if meraki.params['interface_ip'] is not None:
+ payload['interfaceIp'] = meraki.params['interface_ip']
+ if meraki.params['multicast_routing'] is not None:
+ payload['multicastRouting'] = meraki.params['multicast_routing']
+ if meraki.params['vlan_id'] is not None:
+ payload['vlanId'] = meraki.params['vlan_id']
+ if meraki.params['default_gateway'] is not None:
+ payload['defaultGateway'] = meraki.params['default_gateway']
+ if meraki.params['ospf_settings'] is not None:
+ payload['ospfSettings'] = {}
+ if meraki.params['ospf_settings']['area'] is not None:
+ payload['ospfSettings']['area'] = meraki.params['ospf_settings']['area']
+ if meraki.params['ospf_settings']['cost'] is not None:
+ payload['ospfSettings']['cost'] = meraki.params['ospf_settings']['cost']
+ if meraki.params['ospf_settings']['is_passive_enabled'] is not None:
+ payload['ospfSettings']['isPassiveEnabled'] = meraki.params['ospf_settings']['is_passive_enabled']
+ return payload
+
+
+def get_interface_id(meraki, data, name):
+ # meraki.fail_json(msg=data)
+ for interface in data:
+ if interface['name'] == name:
+ return interface['interfaceId']
+ return None
+
+
+def get_interface(interfaces, interface_id):
+ for interface in interfaces:
+ if interface['interfaceId'] == interface_id:
+ return interface
+ return None
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ ospf_arg_spec = dict(area=dict(type='str'),
+ cost=dict(type='int'),
+ is_passive_enabled=dict(type='bool'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query', 'absent'], default='present'),
+ net_id=dict(type='str'),
+ net_name=dict(type='str', aliases=['network']),
+ stack_id=dict(type='str'),
+ name=dict(type='str'),
+ subnet=dict(type='str'),
+ interface_id=dict(type='str'),
+ interface_ip=dict(type='str'),
+ multicast_routing=dict(type='str', choices=['disabled', 'enabled', 'IGMP snooping querier']),
+ vlan_id=dict(type='int'),
+ default_gateway=dict(type='str'),
+ ospf_settings=dict(type='dict', default=None, options=ospf_arg_spec),
+ )
+
+ # 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='ms_stack_l3_interfaces')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'ms_stack_l3_interfaces': '/networks/{net_id}/switch/stacks/{stack_id}/routing/interfaces'}
+ query_one_urls = {'ms_stack_l3_interfaces': '/networks/{net_id}/switch/stacks/{stack_id}/routing/interfaces'}
+ create_urls = {'ms_stack_l3_interfaces': '/networks/{net_id}/switch/stacks/{stack_id}/routing/interfaces'}
+ update_urls = {'ms_stack_l3_interfaces': '/networks/{net_id}/switch/stacks/{stack_id}/routing/interfaces/{interface_id}'}
+ delete_urls = {'ms_stack_l3_interfaces': '/networks/{net_id}/switch/stacks/{stack_id}/routing/interfaces/{interface_id}'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['get_one'].update(query_one_urls)
+ meraki.url_catalog['create'] = create_urls
+ meraki.url_catalog['update'] = update_urls
+ meraki.url_catalog['delete'] = delete_urls
+
+ payload = None
+
+ if meraki.params['vlan_id'] is not None:
+ if meraki.params['vlan_id'] < 1 or meraki.params['vlan_id'] > 4094:
+ meraki.fail_json(msg='vlan_id must be between 1 and 4094')
+
+ org_id = meraki.params['org_id']
+ 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:
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'],
+ data=meraki.get_nets(org_id=org_id))
+
+ interface_id = meraki.params['interface_id']
+ interfaces = None
+ if interface_id is None:
+ if meraki.params['name'] is not None:
+ path = meraki.construct_path('get_all', net_id=net_id, custom={'stack_id': meraki.params['stack_id']})
+ interfaces = meraki.request(path, method='GET')
+ interface_id = get_interface_id(meraki, interfaces, meraki.params['name'])
+
+ # manipulate or modify the state as needed (this is going to be the
+ # part where your module will do what it needs to do)
+
+ if meraki.params['state'] == 'query':
+ if interface_id is not None: # Query one interface
+ path = meraki.construct_path('get_one', net_id=net_id, custom={'stack_id': meraki.params['stack_id'],
+ 'interface_id': interface_id})
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ else: # Query all interfaces
+ path = meraki.construct_path('get_all', net_id=net_id, custom={'stack_id': meraki.params['stack_id']})
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ if interface_id is None: # Create a new interface
+ payload = construct_payload(meraki)
+ if meraki.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('create', net_id=net_id, custom={'stack_id': meraki.params['stack_id']})
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ if interfaces is None:
+ path = meraki.construct_path('get_all', net_id=net_id, custom={'stack_id': meraki.params['stack_id']})
+ interfaces = meraki.request(path, method='GET')
+ payload = construct_payload(meraki)
+ interface = get_interface(interfaces, interface_id)
+ if meraki.is_update_required(interface, payload):
+ if meraki.check_mode is True:
+ interface.update(payload)
+ meraki.result['data'] = interface
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id, custom={'stack_id': meraki.params['stack_id'],
+ 'interface_id': interface_id})
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result['data'] = interface
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'absent':
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('delete', net_id=net_id, custom={'stack_id': meraki.params['stack_id'],
+ 'interface_id': meraki.params['interface_id']})
+ response = meraki.request(path, method='DELETE')
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_storm_control.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_storm_control.py
new file mode 100644
index 000000000..2048ad5e9
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_storm_control.py
@@ -0,0 +1,201 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_storm_control
+short_description: Manage storm control configuration on a switch in the Meraki cloud
+version_added: "0.0.1"
+description:
+- Allows for management of storm control settings for Meraki MS switches.
+options:
+ state:
+ description:
+ - Specifies whether storm control configuration should be queried or modified.
+ choices: [query, present]
+ default: query
+ type: str
+ net_name:
+ description:
+ - Name of network.
+ type: str
+ net_id:
+ description:
+ - ID of network.
+ type: str
+ broadcast_threshold:
+ description:
+ - Percentage (1 to 99) of total available port bandwidth for broadcast traffic type.
+ - Default value 100 percent rate is to clear the configuration.
+ type: int
+ multicast_threshold:
+ description:
+ - Percentage (1 to 99) of total available port bandwidth for multicast traffic type.
+ - Default value 100 percent rate is to clear the configuration.
+ type: int
+ unknown_unicast_threshold:
+ description:
+ - Percentage (1 to 99) of total available port bandwidth for unknown unicast traffic type.
+ - Default value 100 percent rate is to clear the configuration.
+ type: int
+
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Set broadcast settings
+ meraki_switch_storm_control:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ broadcast_threshold: 75
+ multicast_threshold: 70
+ unknown_unicast_threshold: 65
+ delegate_to: localhost
+
+- name: Query storm control settings
+ meraki_switch_storm_control:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information queried or updated storm control configuration.
+ returned: success
+ type: complex
+ contains:
+ broadcast_threshold:
+ description:
+ - Percentage (1 to 99) of total available port bandwidth for broadcast traffic type.
+ - Default value 100 percent rate is to clear the configuration.
+ returned: success
+ type: int
+ sample: 42
+ multicast_threshold:
+ description:
+ - Percentage (1 to 99) of total available port bandwidth for multicast traffic type.
+ - Default value 100 percent rate is to clear the configuration.
+ returned: success
+ type: int
+ sample: 42
+ unknown_unicast_threshold:
+ description:
+ - Percentage (1 to 99) of total available port bandwidth for unknown unicast traffic type.
+ - Default value 100 percent rate is to clear the configuration.
+ returned: success
+ type: int
+ sample: 42
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import recursive_diff
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def construct_payload(params):
+ payload = dict()
+ if 'broadcast_threshold' in params:
+ payload['broadcastThreshold'] = params['broadcast_threshold']
+ if 'multicast_threshold' in params:
+ payload['multicastThreshold'] = params['multicast_threshold']
+ if 'unknown_unicast_threshold' in params:
+ payload['unknownUnicastThreshold'] = params['unknown_unicast_threshold']
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='query'),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ broadcast_threshold=dict(type='int'),
+ multicast_threshold=dict(type='int'),
+ unknown_unicast_threshold=dict(type='int'),
+ )
+
+ # 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='switch_storm_control')
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'switch_storm_control': '/networks/{net_id}/switch/stormControl'}
+ update_url = {'switch_storm_control': '/networks/{net_id}/switch/stormControl'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['update'] = update_url
+
+ payload = None
+
+ org_id = meraki.params['org_id']
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ # execute checks for argument completeness
+
+ # manipulate or modify the state as needed (this is going to be the
+ # part where your module will do what it needs to do)
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ payload = construct_payload(meraki.params)
+ if meraki.is_update_required(original, payload) is True:
+ diff = recursive_diff(original, payload)
+ if meraki.check_mode is True:
+ original.update(payload)
+ meraki.result['data'] = original
+ meraki.result['changed'] = True
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1]}
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1]}
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py
new file mode 100644
index 000000000..a8048bf24
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py
@@ -0,0 +1,680 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_ms_switchport
+short_description: Manage switchports on a switch in the Meraki cloud
+description:
+- Allows for management of switchports settings for Meraki MS switches.
+options:
+ state:
+ description:
+ - Specifies whether a switchport should be queried or modified.
+ choices: [query, present]
+ default: query
+ type: str
+ access_policy_type:
+ description:
+ - Type of access policy to apply to port.
+ type: str
+ choices: [Open, Custom access policy, MAC allow list, Sticky MAC allow list]
+ access_policy_number:
+ description:
+ - Number of the access policy to apply.
+ - Only applicable to access port types.
+ type: int
+ allowed_vlans:
+ description:
+ - List of VLAN numbers to be allowed on switchport.
+ default: all
+ type: list
+ elements: str
+ enabled:
+ description:
+ - Whether a switchport should be enabled or disabled.
+ type: bool
+ default: yes
+ isolation_enabled:
+ description:
+ - Isolation status of switchport.
+ default: no
+ type: bool
+ link_negotiation:
+ description:
+ - Link speed for the switchport.
+ default: Auto negotiate
+ choices: [Auto negotiate, 100 Megabit (auto), 100 Megabit full duplex (forced)]
+ type: str
+ name:
+ description:
+ - Switchport description.
+ aliases: [description]
+ type: str
+ number:
+ description:
+ - Port number.
+ type: str
+ poe_enabled:
+ description:
+ - Enable or disable Power Over Ethernet on a port.
+ type: bool
+ default: true
+ rstp_enabled:
+ description:
+ - Enable or disable Rapid Spanning Tree Protocol on a port.
+ type: bool
+ default: true
+ serial:
+ description:
+ - Serial nubmer of the switch.
+ type: str
+ required: true
+ stp_guard:
+ description:
+ - Set state of STP guard.
+ choices: [disabled, root guard, bpdu guard, loop guard]
+ default: disabled
+ type: str
+ tags:
+ description:
+ - List of tags to assign to a port.
+ type: list
+ elements: str
+ type:
+ description:
+ - Set port type.
+ choices: [access, trunk]
+ default: access
+ type: str
+ vlan:
+ description:
+ - VLAN number assigned to port.
+ - If a port is of type trunk, the specified VLAN is the native VLAN.
+ - Setting value to 0 on a trunk will clear the VLAN.
+ type: int
+ voice_vlan:
+ description:
+ - VLAN number assigned to a port for voice traffic.
+ - Only applicable to access port type.
+ - Only applicable if voice_vlan_state is set to present.
+ type: int
+ voice_vlan_state:
+ description:
+ - Specifies whether voice vlan configuration should be present or absent.
+ choices: [absent, present]
+ default: present
+ type: str
+ mac_allow_list:
+ description:
+ - MAC addresses list that are allowed on a port.
+ - Only applicable to access port type.
+ - Only applicable to access_policy_type "MAC allow list".
+ type: dict
+ suboptions:
+ state:
+ description:
+ - The state the configuration should be left in.
+ - Merged, MAC addresses provided will be added to the current allow list.
+ - Replaced, All MAC addresses are overwritten, only the MAC addresses provided with exist in the allow list.
+ - Deleted, Remove the MAC addresses provided from the current allow list.
+ type: str
+ choices: [merged, replaced, deleted]
+ default: replaced
+ macs:
+ description:
+ - List of MAC addresses to update with based on state option.
+ type: list
+ elements: str
+ sticky_mac_allow_list:
+ description:
+ - MAC addresses list that are allowed on a port.
+ - Only applicable to access port type.
+ - Only applicable to access_policy_type "Sticky MAC allow list".
+ type: dict
+ suboptions:
+ state:
+ description:
+ - The state the configuration should be left in.
+ - Merged, MAC addresses provided will be added to the current allow list.
+ - Replaced, All MAC addresses are overwritten, only the MAC addresses provided with exist in the allow list.
+ - Deleted, Remove the MAC addresses provided from the current allow list.
+ type: str
+ choices: ["merged", "replaced", "deleted"]
+ default: replaced
+ macs:
+ description:
+ - List of MAC addresses to update with based on state option.
+ type: list
+ elements: str
+ sticky_mac_allow_list_limit:
+ description:
+ - The number of MAC addresses allowed in the sticky port allow list.
+ - Only applicable to access port type.
+ - Only applicable to access_policy_type "Sticky MAC allow list".
+ - The value must be equal to or greater then the list size of sticky_mac_allow_list. Value will be checked for validity, during processing.
+ type: int
+ flexible_stacking_enabled:
+ description:
+ - Whether flexible stacking capabilities are supported on the port.
+ type: bool
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Query information about all switchports on a switch
+ meraki_switchport:
+ auth_key: abc12345
+ state: query
+ serial: ABC-123
+ delegate_to: localhost
+
+- name: Query information about all switchports on a switch
+ meraki_switchport:
+ auth_key: abc12345
+ state: query
+ serial: ABC-123
+ number: 2
+ delegate_to: localhost
+
+- name: Name switchport
+ meraki_switchport:
+ auth_key: abc12345
+ state: present
+ serial: ABC-123
+ number: 7
+ name: Test Port
+ delegate_to: localhost
+
+- name: Configure access port with voice VLAN
+ meraki_switchport:
+ auth_key: abc12345
+ state: present
+ serial: ABC-123
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ voice_vlan: 11
+ delegate_to: localhost
+
+- name: Check access port for idempotency
+ meraki_switchport:
+ auth_key: abc12345
+ state: present
+ serial: ABC-123
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ voice_vlan: 11
+ delegate_to: localhost
+
+- name: Configure trunk port with specific VLANs
+ meraki_switchport:
+ auth_key: abc12345
+ state: present
+ serial: ABC-123
+ number: 7
+ enabled: true
+ name: Server port
+ tags: server
+ type: trunk
+ allowed_vlans:
+ - 10
+ - 15
+ - 20
+ delegate_to: localhost
+
+- name: Configure access port with sticky MAC allow list and limit.
+ meraki_switchport:
+ auth_key: abc12345
+ state: present
+ serial: ABC-123
+ number: 5
+ sticky_mac_allow_limit: 3
+ sticky_mac_allow_list:
+ macs:
+ - aa:aa:bb:bb:cc:cc
+ - bb:bb:aa:aa:cc:cc
+ - 11:aa:bb:bb:cc:cc
+ state: replaced
+ delegate_to: localhost
+
+- name: Delete an existing MAC address from the sticky MAC allow list.
+ meraki_switchport:
+ auth_key: abc12345
+ state: present
+ serial: ABC-123
+ number: 5
+ sticky_mac_allow_list:
+ macs:
+ - aa:aa:bb:bb:cc:cc
+ state: deleted
+ delegate_to: localhost
+
+- name: Add a MAC address to sticky MAC allow list.
+ meraki_switchport:
+ auth_key: abc12345
+ state: present
+ serial: ABC-123
+ number: 5
+ sticky_mac_allow_list:
+ macs:
+ - 22:22:bb:bb:cc:cc
+ state: merged
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: Information queried or updated switchports.
+ returned: success
+ type: complex
+ contains:
+ access_policy_type:
+ description: Type of access policy assigned to port
+ returned: success, when assigned
+ type: str
+ sample: "MAC allow list"
+ allowed_vlans:
+ description: List of VLANs allowed on an access port
+ returned: success, when port is set as access
+ type: str
+ sample: all
+ number:
+ description: Number of port.
+ returned: success
+ type: int
+ sample: 1
+ name:
+ description: Human friendly description of port.
+ returned: success
+ type: str
+ sample: "Jim Phone Port"
+ tags:
+ description: List of tags assigned to port.
+ returned: success
+ type: list
+ sample: ['phone', 'marketing']
+ enabled:
+ description: Enabled state of port.
+ returned: success
+ type: bool
+ sample: true
+ poe_enabled:
+ description: Power Over Ethernet enabled state of port.
+ returned: success
+ type: bool
+ sample: true
+ type:
+ description: Type of switchport.
+ returned: success
+ type: str
+ sample: trunk
+ vlan:
+ description: VLAN assigned to port.
+ returned: success
+ type: int
+ sample: 10
+ voice_vlan:
+ description: VLAN assigned to port with voice VLAN enabled devices.
+ returned: success
+ type: int
+ sample: 20
+ isolation_enabled:
+ description: Port isolation status of port.
+ returned: success
+ type: bool
+ sample: true
+ rstp_enabled:
+ description: Enabled or disabled state of Rapid Spanning Tree Protocol (RSTP)
+ returned: success
+ type: bool
+ sample: true
+ stp_guard:
+ description: State of STP guard
+ returned: success
+ type: str
+ sample: "Root Guard"
+ access_policy_number:
+ description: Number of assigned access policy. Only applicable to access ports.
+ returned: success
+ type: int
+ sample: 1234
+ link_negotiation:
+ description: Link speed for the port.
+ returned: success
+ type: str
+ sample: "Auto negotiate"
+ sticky_mac_allow_list_limit:
+ description: Number of MAC addresses allowed on a sticky port.
+ returned: success
+ type: int
+ sample: 6
+ sticky_mac_allow_list:
+ description: List of MAC addresses currently allowed on a sticky port. Used with access_policy_type of Sticky MAC allow list.
+ returned: success
+ type: list
+ sample: ["11:aa:bb:bb:cc:cc", "22:aa:bb:bb:cc:cc", "33:aa:bb:bb:cc:cc"]
+ mac_allow_list:
+ description: List of MAC addresses currently allowed on a non-sticky port. Used with access_policy_type of MAC allow list.
+ returned: success
+ type: list
+ sample: ["11:aa:bb:bb:cc:cc", "22:aa:bb:bb:cc:cc", "33:aa:bb:bb:cc:cc"]
+ port_schedule_id:
+ description: Unique ID of assigned port schedule
+ returned: success
+ type: str
+ sample: null
+ udld:
+ description: Alert state of UDLD
+ returned: success
+ type: str
+ sample: "Alert only"
+ flexible_stacking_enabled:
+ description: Whether flexible stacking capabilities are enabled on the port.
+ returned: success
+ type: bool
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+param_map = {
+ "access_policy_number": "accessPolicyNumber",
+ "access_policy_type": "accessPolicyType",
+ "allowed_vlans": "allowedVlans",
+ "enabled": "enabled",
+ "isolation_enabled": "isolationEnabled",
+ "link_negotiation": "linkNegotiation",
+ "name": "name",
+ "number": "number",
+ "poe_enabled": "poeEnabled",
+ "rstp_enabled": "rstpEnabled",
+ "stp_guard": "stpGuard",
+ "tags": "tags",
+ "type": "type",
+ "vlan": "vlan",
+ "voice_vlan": "voiceVlan",
+ "mac_allow_list": "macAllowList",
+ "sticky_mac_allow_list": "stickyMacAllowList",
+ "sticky_mac_allow_list_limit": "stickyMacAllowListLimit",
+ "adaptive_policy_group_id": "adaptivePolicyGroupId",
+ "peer_sgt_capable": "peerSgtCapable",
+ "flexible_stacking_enabled": "flexibleStackingEnabled",
+}
+
+
+def sort_vlans(meraki, vlans):
+ converted = set()
+ for vlan in vlans:
+ converted.add(int(vlan))
+ vlans_sorted = sorted(converted)
+ vlans_str = []
+ for vlan in vlans_sorted:
+ vlans_str.append(str(vlan))
+ return ",".join(vlans_str)
+
+
+def assemble_payload(meraki):
+ payload = dict()
+ # if meraki.params['enabled'] is not None:
+ # payload['enabled'] = meraki.params['enabled']
+
+ for k, v in meraki.params.items():
+ try:
+ if meraki.params[k] is not None:
+ if k == "access_policy_number":
+ if meraki.params["access_policy_type"] is not None:
+ payload[param_map[k]] = v
+ else:
+ payload[param_map[k]] = v
+ except KeyError:
+ pass
+ return payload
+
+
+def get_mac_list(original_allowed, new_mac_list, state):
+ if state == "deleted":
+ return [entry for entry in original_allowed if entry not in new_mac_list]
+ if state == "merged":
+ return original_allowed + list(set(new_mac_list) - set(original_allowed))
+ return new_mac_list
+
+
+def clear_vlan(params, payload):
+ if params["vlan"] == 0:
+ payload["vlan"] = None
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ argument_spec = meraki_argument_spec()
+
+ policy_data_arg_spec = dict(
+ macs=dict(type="list", elements="str"),
+ state=dict(
+ type="str", choices=["merged", "replaced", "deleted"], default="replaced"
+ ),
+ )
+
+ argument_spec.update(
+ state=dict(type="str", choices=["present", "query"], default="query"),
+ serial=dict(type="str", required=True),
+ number=dict(type="str"),
+ name=dict(type="str", aliases=["description"]),
+ tags=dict(type="list", elements="str"),
+ enabled=dict(type="bool", default=True),
+ type=dict(type="str", choices=["access", "trunk"], default="access"),
+ vlan=dict(type="int"),
+ voice_vlan=dict(type="int"),
+ voice_vlan_state=dict(
+ type="str", choices=["present", "absent"], default="present"
+ ),
+ allowed_vlans=dict(type="list", elements="str", default="all"),
+ poe_enabled=dict(type="bool", default=True),
+ isolation_enabled=dict(type="bool", default=False),
+ rstp_enabled=dict(type="bool", default=True),
+ stp_guard=dict(
+ type="str",
+ choices=["disabled", "root guard", "bpdu guard", "loop guard"],
+ default="disabled",
+ ),
+ access_policy_type=dict(
+ type="str",
+ choices=[
+ "Open",
+ "Custom access policy",
+ "MAC allow list",
+ "Sticky MAC allow list",
+ ],
+ ),
+ access_policy_number=dict(type="int"),
+ link_negotiation=dict(
+ type="str",
+ choices=[
+ "Auto negotiate",
+ "100 Megabit (auto)",
+ "100 Megabit full duplex (forced)",
+ ],
+ default="Auto negotiate",
+ ),
+ mac_allow_list=dict(type="dict", options=policy_data_arg_spec),
+ sticky_mac_allow_list=dict(type="dict", options=policy_data_arg_spec),
+ sticky_mac_allow_list_limit=dict(type="int"),
+ # adaptive_policy_group_id=dict(type=str),
+ # peer_sgt_capable=dict(type=bool),
+ flexible_stacking_enabled=dict(type="bool"),
+ )
+
+ # 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="switchport")
+ if meraki.params.get("voice_vlan_state") == "absent" and meraki.params.get(
+ "voice_vlan"
+ ):
+ meraki.fail_json(
+ msg="voice_vlan_state cant be `absent` while voice_vlan is also defined."
+ )
+
+ meraki.params["follow_redirects"] = "all"
+
+ if meraki.params["type"] == "trunk":
+ if not meraki.params["allowed_vlans"]:
+ meraki.params["allowed_vlans"] = [
+ "all"
+ ] # Backdoor way to set default without conflicting on access
+
+ query_urls = {"switchport": "/devices/{serial}/switch/ports"}
+ query_url = {"switchport": "/devices/{serial}/switch/ports/{number}"}
+ update_url = {"switchport": "/devices/{serial}/switch/ports/{number}"}
+
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["get_one"].update(query_url)
+ meraki.url_catalog["update"] = update_url
+
+ # execute checks for argument completeness
+
+ # manipulate or modify the state as needed (this is going to be the
+ # part where your module will do what it needs to do)
+ if meraki.params["state"] == "query":
+ if meraki.params["number"]:
+ path = meraki.construct_path(
+ "get_one",
+ custom={
+ "serial": meraki.params["serial"],
+ "number": meraki.params["number"],
+ },
+ )
+ response = meraki.request(path, method="GET")
+ meraki.result["data"] = response
+ else:
+ path = meraki.construct_path(
+ "get_all", custom={"serial": meraki.params["serial"]}
+ )
+ response = meraki.request(path, method="GET")
+ meraki.result["data"] = response
+ elif meraki.params["state"] == "present":
+ payload = assemble_payload(meraki)
+ # meraki.fail_json(msg='payload', payload=payload)
+ allowed = set() # Use a set to remove duplicate items
+ if meraki.params["allowed_vlans"][0] == "all":
+ allowed.add("all")
+ else:
+ for vlan in meraki.params["allowed_vlans"]:
+ allowed.add(str(vlan))
+ if meraki.params["vlan"] is not None:
+ allowed.add(str(meraki.params["vlan"]))
+ if len(allowed) > 1: # Convert from list to comma separated
+ payload["allowedVlans"] = sort_vlans(meraki, allowed)
+ else:
+ payload["allowedVlans"] = next(iter(allowed))
+
+ # Exceptions need to be made for idempotency check based on how Meraki returns
+ if meraki.params["type"] == "access":
+ if not meraki.params[
+ "vlan"
+ ]: # VLAN needs to be specified in access ports, but can't default to it
+ payload["vlan"] = 1
+ query_path = meraki.construct_path(
+ "get_one",
+ custom={
+ "serial": meraki.params["serial"],
+ "number": meraki.params["number"],
+ },
+ )
+ original = meraki.request(query_path, method="GET")
+ # Check voiceVlan to see if state is absent to remove the vlan.
+ if meraki.params.get("voice_vlan_state"):
+ if meraki.params.get("voice_vlan_state") == "absent":
+ payload["voiceVlan"] = None
+ else:
+ payload["voiceVlan"] = meraki.params.get("voice_vlan")
+ if meraki.params.get("mac_allow_list"):
+ macs = get_mac_list(
+ original.get("macAllowList"),
+ meraki.params["mac_allow_list"].get("macs"),
+ meraki.params["mac_allow_list"].get("state"),
+ )
+ payload["macAllowList"] = macs
+ # Evaluate Sticky Limit whether it was passed in or what is currently configured and was returned in GET call.
+ if meraki.params.get("sticky_mac_allow_list_limit"):
+ sticky_mac_limit = meraki.params.get("sticky_mac_allow_list_limit")
+ else:
+ sticky_mac_limit = original.get("stickyMacAllowListLimit")
+ if meraki.params.get("sticky_mac_allow_list"):
+ macs = get_mac_list(
+ original.get("stickyMacAllowList"),
+ meraki.params["sticky_mac_allow_list"].get("macs"),
+ meraki.params["sticky_mac_allow_list"].get("state"),
+ )
+ if int(sticky_mac_limit) < len(macs):
+ meraki.fail_json(
+ msg="Stick MAC Allow List Limit must be equal to or greater than length of Sticky MAC Allow List."
+ )
+ payload["stickyMacAllowList"] = macs
+ payload["stickyMacAllowListLimit"] = sticky_mac_limit
+ payload = clear_vlan(meraki.params, payload)
+ proposed = payload.copy()
+ if meraki.params["type"] == "trunk":
+ proposed["voiceVlan"] = original[
+ "voiceVlan"
+ ] # API shouldn't include voice VLAN on a trunk port
+ # meraki.fail_json(msg='Compare', original=original, payload=payload)
+ if meraki.is_update_required(original, proposed, optional_ignore=["number"]):
+ if meraki.check_mode is True:
+ original.update(proposed)
+ meraki.result["data"] = original
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path(
+ "update",
+ custom={
+ "serial": meraki.params["serial"],
+ "number": meraki.params["number"],
+ },
+ )
+ response = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ else:
+ meraki.result["data"] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py
new file mode 100644
index 000000000..d189a4e44
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_content_filtering
+short_description: Edit Meraki MX content filtering policies
+description:
+- Allows for setting policy on content filtering.
+options:
+ auth_key:
+ description:
+ - Authentication key provided by the dashboard. Required if environmental variable MERAKI_KEY is not set.
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [ network ]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ state:
+ description:
+ - States that a policy should be created or modified.
+ choices: [present, query]
+ default: present
+ type: str
+ allowed_urls:
+ description:
+ - List of URL patterns which should be allowed.
+ type: list
+ elements: str
+ blocked_urls:
+ description:
+ - List of URL patterns which should be blocked.
+ type: list
+ elements: str
+ blocked_categories:
+ description:
+ - List of content categories which should be blocked.
+ - Use the C(meraki_content_filtering_facts) module for a full list of categories.
+ type: list
+ elements: str
+ category_list_size:
+ description:
+ - Determines whether a network filters fo rall URLs in a category or only the list of top blocked sites.
+ choices: [ top sites, full list ]
+ type: str
+ subset:
+ description:
+ - Display only certain facts.
+ choices: [categories, policy]
+ type: str
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+ - name: Set single allowed URL pattern
+ meraki_content_filtering:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourMXNet
+ allowed_urls:
+ - "http://www.ansible.com/*"
+
+ - name: Set blocked URL category
+ meraki_content_filtering:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourMXNet
+ state: present
+ category_list_size: full list
+ blocked_categories:
+ - "Adult and Pornography"
+
+ - name: Remove match patterns and categories
+ meraki_content_filtering:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourMXNet
+ state: present
+ category_list_size: full list
+ allowed_urls: []
+ blocked_urls: []
+'''
+
+RETURN = r'''
+data:
+ description: Information about the created or manipulated object.
+ returned: info
+ type: complex
+ contains:
+ categories:
+ description: List of available content filtering categories.
+ returned: query for categories
+ type: complex
+ contains:
+ id:
+ description: Unique ID of content filtering category.
+ returned: query for categories
+ type: str
+ sample: "meraki:contentFiltering/category/1"
+ name:
+ description: Name of content filtering category.
+ returned: query for categories
+ type: str
+ sample: "Real Estate"
+ allowed_url_patterns:
+ description: Explicitly permitted URL patterns
+ returned: query for policy
+ type: list
+ sample: ["http://www.ansible.com"]
+ blocked_url_patterns:
+ description: Explicitly denied URL patterns
+ returned: query for policy
+ type: list
+ sample: ["http://www.ansible.net"]
+ blocked_url_categories:
+ description: List of blocked URL categories
+ returned: query for policy
+ type: complex
+ contains:
+ id:
+ description: Unique ID of category to filter
+ returned: query for policy
+ type: list
+ sample: ["meraki:contentFiltering/category/1"]
+ name:
+ description: Name of category to filter
+ returned: query for policy
+ type: list
+ sample: ["Real Estate"]
+ url_cateogory_list_size:
+ description: Size of categories to cache on MX appliance
+ returned: query for policy
+ type: str
+ sample: "topSites"
+'''
+
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec, recursive_diff
+from copy import deepcopy
+
+
+def get_category_dict(meraki, full_list, category):
+ for i in full_list['categories']:
+ if i['name'] == category:
+ return i['id']
+ meraki.fail_json(msg="{0} is not a valid content filtering category".format(category))
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type='str'),
+ net_name=dict(type='str', aliases=['network']),
+ state=dict(type='str', default='present', choices=['present', 'query']),
+ allowed_urls=dict(type='list', elements='str'),
+ blocked_urls=dict(type='list', elements='str'),
+ blocked_categories=dict(type='list', elements='str'),
+ category_list_size=dict(type='str', choices=['top sites', 'full list']),
+ subset=dict(type='str', choices=['categories', 'policy']),
+ )
+
+ # 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='content_filtering')
+ module.params['follow_redirects'] = 'all'
+
+ category_urls = {'content_filtering': '/networks/{net_id}/appliance/contentFiltering/categories'}
+ policy_urls = {'content_filtering': '/networks/{net_id}/appliance/contentFiltering'}
+
+ meraki.url_catalog['categories'] = category_urls
+ meraki.url_catalog['policy'] = policy_urls
+
+ if meraki.params['net_name'] and meraki.params['net_id']:
+ meraki.fail_json(msg='net_name and net_id are mutually exclusive')
+
+ # 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']
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = None
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ if meraki.params['subset']:
+ if meraki.params['subset'] == 'categories':
+ path = meraki.construct_path('categories', net_id=net_id)
+ elif meraki.params['subset'] == 'policy':
+ path = meraki.construct_path('policy', net_id=net_id)
+ meraki.result['data'] = meraki.request(path, method='GET')
+ else:
+ response_data = {'categories': None,
+ 'policy': None,
+ }
+ path = meraki.construct_path('categories', net_id=net_id)
+ response_data['categories'] = meraki.request(path, method='GET')
+ path = meraki.construct_path('policy', net_id=net_id)
+ response_data['policy'] = meraki.request(path, method='GET')
+ meraki.result['data'] = response_data
+ if module.params['state'] == 'present':
+ payload = dict()
+ if meraki.params['allowed_urls']:
+ if meraki.params['allowed_urls'] == ['None']: # Corner case for resetting
+ payload['allowedUrlPatterns'] = []
+ else:
+ payload['allowedUrlPatterns'] = meraki.params['allowed_urls']
+ if meraki.params['blocked_urls']:
+ if meraki.params['blocked_urls'] == ['None']: # Corner case for resetting
+ payload['blockedUrlPatterns'] = []
+ else:
+ payload['blockedUrlPatterns'] = meraki.params['blocked_urls']
+ if meraki.params['blocked_categories']:
+ if meraki.params['blocked_categories'] == ['None']: # Corner case for resetting
+ payload['blockedUrlCategories'] = []
+ else:
+ category_path = meraki.construct_path('categories', net_id=net_id)
+ categories = meraki.request(category_path, method='GET')
+ payload['blockedUrlCategories'] = []
+ for category in meraki.params['blocked_categories']:
+ payload['blockedUrlCategories'].append(get_category_dict(meraki,
+ categories,
+ category))
+ if meraki.params['category_list_size']:
+ if meraki.params['category_list_size'].lower() == 'top sites':
+ payload['urlCategoryListSize'] = "topSites"
+ elif meraki.params['category_list_size'].lower() == 'full list':
+ payload['urlCategoryListSize'] = "fullList"
+ path = meraki.construct_path('policy', net_id=net_id)
+ current = meraki.request(path, method='GET')
+ proposed = current.copy()
+ proposed.update(payload)
+ temp_current = deepcopy(current)
+ temp_blocked = []
+ for blocked_category in current['blockedUrlCategories']:
+ temp_blocked.append(blocked_category['id'])
+ temp_current.pop('blockedUrlCategories')
+ if 'blockedUrlCategories' in payload:
+ payload['blockedUrlCategories'].sort()
+ temp_current['blockedUrlCategories'] = temp_blocked
+ if meraki.is_update_required(temp_current, payload) is True:
+ if module.check_mode:
+ meraki.generate_diff(current, payload)
+ current.update(payload)
+ meraki.result['changed'] = True
+ meraki.result['data'] = current
+ meraki.exit_json(**meraki.result)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ if recursive_diff(current, response) is None:
+ meraki.result['changed'] = False
+ meraki.result['data'] = str(recursive_diff(current, response))
+ else:
+ meraki.result['changed'] = True
+ meraki.generate_diff(current, response)
+ else:
+ meraki.result['data'] = current
+ if module.check_mode:
+ meraki.result['data'] = current
+ meraki.exit_json(**meraki.result)
+ meraki.result['data'] = current
+ meraki.exit_json(**meraki.result)
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py
new file mode 100644
index 000000000..0acd6beaa
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py
@@ -0,0 +1,371 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019 Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_intrusion_prevention
+short_description: Manage intrustion prevention in the Meraki cloud
+description:
+- Allows for management of intrusion prevention rules networks within Meraki MX networks.
+
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: [ absent, present, query ]
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [ name, network ]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ mode:
+ description:
+ - Operational mode of Intrusion Prevention system.
+ choices: [ detection, disabled, prevention ]
+ type: str
+ ids_rulesets:
+ description:
+ - Ruleset complexity setting.
+ choices: [ connectivity, balanced, security ]
+ type: str
+ allowed_rules:
+ description:
+ - List of IDs related to rules which are allowed for the organization.
+ type: list
+ elements: dict
+ suboptions:
+ rule_id:
+ description:
+ - ID of rule as defined by Snort.
+ type: str
+ rule_message:
+ description:
+ - Description of rule.
+ - This is overwritten by the API.
+ - Formerly C(message) which was deprecated but still maintained as an alias.
+ type: str
+ aliases: [ message ]
+ version_added: "2.3.0"
+ protected_networks:
+ description:
+ - Set included/excluded networks for Intrusion Prevention.
+ type: dict
+ suboptions:
+ use_default:
+ description:
+ - Whether to use special IPv4 addresses per RFC 5735.
+ type: bool
+ included_cidr:
+ description:
+ - List of network IP ranges to include in scanning.
+ type: list
+ elements: str
+ excluded_cidr:
+ description:
+ - List of network IP ranges to exclude from scanning.
+ type: list
+ elements: str
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Set whitelist for organization
+ meraki_intrusion_prevention:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ allowed_rules:
+ - rule_id: "meraki:intrusion/snort/GID/01/SID/5805"
+ rule_message: Test rule
+ delegate_to: localhost
+
+- name: Query IPS info for organization
+ meraki_intrusion_prevention:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ delegate_to: localhost
+ register: query_org
+
+- name: Set full ruleset with check mode
+ meraki_intrusion_prevention:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - IPS'
+ mode: prevention
+ ids_rulesets: security
+ protected_networks:
+ use_default: true
+ included_cidr:
+ - 192.0.1.0/24
+ excluded_cidr:
+ - 10.0.1.0/24
+ delegate_to: localhost
+
+- name: Clear rules from organization
+ meraki_intrusion_prevention:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ allowed_rules: []
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information about the Threat Protection settings.
+ returned: success
+ type: complex
+ contains:
+ whitelistedRules:
+ description: List of whitelisted IPS rules.
+ returned: success, when organization is queried or modified
+ type: complex
+ contains:
+ ruleId:
+ description: A rule identifier for an IPS rule.
+ returned: success, when organization is queried or modified
+ type: str
+ sample: "meraki:intrusion/snort/GID/01/SID/5805"
+ rule_message:
+ description: Description of rule.
+ returned: success, when organization is queried or modified
+ type: str
+ sample: "MALWARE-OTHER Trackware myway speedbar runtime detection - switch engines"
+ mode:
+ description: Enabled setting of intrusion prevention.
+ returned: success, when network is queried or modified
+ type: str
+ sample: enabled
+ idsRulesets:
+ description: Setting of selected ruleset.
+ returned: success, when network is queried or modified
+ type: str
+ sample: balanced
+ protectedNetworks:
+ description: Networks protected by IPS.
+ returned: success, when network is queried or modified
+ type: complex
+ contains:
+ useDefault:
+ description: Whether to use special IPv4 addresses.
+ returned: success, when network is queried or modified
+ type: bool
+ sample: true
+ includedCidr:
+ description: List of CIDR notiation networks to protect.
+ returned: success, when network is queried or modified
+ type: str
+ sample: 192.0.1.0/24
+ excludedCidr:
+ description: List of CIDR notiation networks to exclude from protection.
+ returned: success, when network is queried or modified
+ type: str
+ sample: 192.0.1.0/24
+
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+param_map = {'allowed_rules': 'allowedrules',
+ 'rule_id': 'ruleId',
+ 'rule_message': 'message',
+ 'mode': 'mode',
+ 'protected_networks': 'protectedNetworks',
+ 'use_default': 'useDefault',
+ 'included_cidr': 'includedCidr',
+ }
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ allowedrules_arg_spec = dict(rule_id=dict(type='str'),
+ rule_message=dict(type='str',
+ aliases=['message'],
+ deprecated_aliases=[dict(name='message', version='3.0.0', collection_name='cisco.meraki')]),
+ )
+
+ protected_nets_arg_spec = dict(use_default=dict(type='bool'),
+ included_cidr=dict(type='list', elements='str'),
+ excluded_cidr=dict(type='list', elements='str'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type='str'),
+ net_name=dict(type='str', aliases=['name', 'network']),
+ state=dict(type='str', choices=['absent', 'present', 'query'], default='present'),
+ allowed_rules=dict(type='list', default=None, elements='dict', options=allowedrules_arg_spec),
+ mode=dict(type='str', choices=['detection', 'disabled', 'prevention']),
+ ids_rulesets=dict(type='str', choices=['connectivity', 'balanced', 'security']),
+ protected_networks=dict(type='dict', default=None, options=protected_nets_arg_spec),
+ )
+
+ # 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='intrusion_prevention')
+ module.params['follow_redirects'] = 'all'
+ payload = None
+
+ query_org_urls = {'intrusion_prevention': '/organizations/{org_id}/appliance/security/intrusion'}
+ query_net_urls = {'intrusion_prevention': '/networks/{net_id}/appliance/security/intrusion'}
+ set_org_urls = {'intrusion_prevention': '/organizations/{org_id}/appliance/security/intrusion'}
+ set_net_urls = {'intrusion_prevention': '/networks/{net_id}/appliance/security/intrusion'}
+ meraki.url_catalog['query_org'] = query_org_urls
+ meraki.url_catalog['query_net'] = query_net_urls
+ meraki.url_catalog['set_org'] = set_org_urls
+ meraki.url_catalog['set_net'] = set_net_urls
+
+ if not meraki.params['org_name'] and not meraki.params['org_id']:
+ meraki.fail_json(msg='org_name or org_id parameters are required')
+ if meraki.params['net_name'] and meraki.params['net_id']:
+ meraki.fail_json(msg='net_name and net_id are mutually exclusive')
+ if meraki.params['net_name'] is None and meraki.params['net_id'] is None: # Organization param check
+ if meraki.params['state'] == 'present':
+ if meraki.params['allowed_rules'] is None:
+ meraki.fail_json(msg='allowed_rules is required when state is present and no network is specified.')
+ if meraki.params['net_name'] or meraki.params['net_id']: # Network param check
+ if meraki.params['state'] == 'present':
+ if meraki.params['protected_networks'] is not None:
+ if meraki.params['protected_networks']['use_default'] is False and meraki.params['protected_networks']['included_cidr'] is None:
+ meraki.fail_json(msg="included_cidr is required when use_default is False.")
+ if meraki.params['protected_networks']['use_default'] is False and meraki.params['protected_networks']['excluded_cidr'] is None:
+ meraki.fail_json(msg="excluded_cidr is required when use_default is False.")
+
+ org_id = meraki.params['org_id']
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None and meraki.params['net_name']:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ # Assemble payload
+ if meraki.params['state'] == 'present':
+ if net_id is None: # Create payload for organization
+ rules = []
+ for rule in meraki.params['allowed_rules']:
+ rules.append({'ruleId': rule['rule_id'],
+ 'message': rule['rule_message'],
+ })
+ payload = {'allowedRules': rules}
+ else: # Create payload for network
+ payload = dict()
+ if meraki.params['mode']:
+ payload['mode'] = meraki.params['mode']
+ if meraki.params['ids_rulesets']:
+ payload['idsRulesets'] = meraki.params['ids_rulesets']
+ if meraki.params['protected_networks']:
+ payload['protectedNetworks'] = {}
+ if meraki.params['protected_networks']['use_default']:
+ payload['protectedNetworks'].update({'useDefault': meraki.params['protected_networks']['use_default']})
+ if meraki.params['protected_networks']['included_cidr']:
+ payload['protectedNetworks'].update({'includedCidr': meraki.params['protected_networks']['included_cidr']})
+ if meraki.params['protected_networks']['excluded_cidr']:
+ payload['protectedNetworks'].update({'excludedCidr': meraki.params['protected_networks']['excluded_cidr']})
+ elif meraki.params['state'] == 'absent':
+ if net_id is None: # Create payload for organization
+ payload = {'allowedRules': []}
+
+ if meraki.params['state'] == 'query':
+ if net_id is None: # Query settings for organization
+ path = meraki.construct_path('query_org', org_id=org_id)
+ data = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ meraki.result['data'] = data
+ else: # Query settings for network
+ path = meraki.construct_path('query_net', net_id=net_id)
+ data = meraki.request(path, method='GET')
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('query_org', org_id=org_id)
+ original = meraki.request(path, method='GET')
+ if net_id is None: # Set configuration for organization
+ if meraki.is_update_required(original, payload, optional_ignore=['message']):
+ if meraki.module.check_mode is True:
+ original.update(payload)
+ meraki.result['data'] = original
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('set_org', org_id=org_id)
+ data = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = data
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+ meraki.result['changed'] = False
+ else: # Set configuration for network
+ path = meraki.construct_path('query_net', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ if meraki.is_update_required(original, payload):
+ if meraki.module.check_mode is True:
+ payload.update(original)
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('set_net', net_id=net_id)
+ data = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = data
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+ meraki.result['changed'] = False
+ elif meraki.params['state'] == 'absent':
+ if net_id is None:
+ path = meraki.construct_path('query_org', org_id=org_id)
+ original = meraki.request(path, method='GET')
+ if meraki.is_update_required(original, payload):
+ if meraki.module.check_mode is True:
+ payload.update(original)
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('set_org', org_id=org_id)
+ data = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = data
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+ meraki.result['changed'] = False
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py
new file mode 100644
index 000000000..48e48642b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py
@@ -0,0 +1,272 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_l2_interface
+short_description: Configure MX layer 2 interfaces
+version_added: "2.1.0"
+description:
+- Allows for management and visibility of Merkai MX layer 2 ports.
+
+options:
+ state:
+ description:
+ - Modify or query an port.
+ choices: [present, query]
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [name, network]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ org_id:
+ description:
+ - ID of organization associated to a network.
+ type: str
+ number:
+ description:
+ - ID number of MX port.
+ aliases: [port, port_id]
+ type: int
+ vlan:
+ description:
+ - Native VLAN when the port is in Trunk mode.
+ - Access VLAN when the port is in Access mode.
+ type: int
+ access_policy:
+ description:
+ - The name of the policy. Only applicable to access ports.
+ choices: [open, 8021x-radius, mac-radius, hybris-radius]
+ type: str
+ allowed_vlans:
+ description:
+ - Comma-delimited list of the VLAN ID's allowed on the port, or 'all' to permit all VLAN's on the port.
+ type: str
+ port_type:
+ description:
+ - Type of port.
+ choices: [access, trunk]
+ type: str
+ drop_untagged_traffic:
+ description:
+ - Trunk port can Drop all Untagged traffic. When true, no VLAN is required.
+ - Access ports cannot have dropUntaggedTraffic set to true.
+ type: bool
+ enabled:
+ description:
+ - Enabled state of port.
+ type: bool
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query layer 2 interface settings
+ meraki_mx_l2_interface:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+
+- name: Query a single layer 2 interface settings
+ meraki_mx_l2_interface:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ number: 2
+ delegate_to: localhost
+
+- name: Update interface configuration
+ meraki_mx_l2_interface:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ number: 2
+ port_type: access
+ vlan: 10
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information about the created or manipulated object.
+ returned: success
+ type: complex
+ contains:
+ number:
+ description:
+ - ID number of MX port.
+ type: int
+ returned: success
+ sample: 4
+ vlan:
+ description:
+ - Native VLAN when the port is in Trunk mode.
+ - Access VLAN when the port is in Access mode.
+ type: int
+ returned: success
+ sample: 1
+ access_policy:
+ description:
+ - The name of the policy. Only applicable to access ports.
+ type: str
+ returned: success
+ sample: guestUsers
+ allowed_vlans:
+ description:
+ - Comma-delimited list of the VLAN ID's allowed on the port, or 'all' to permit all VLAN's on the port.
+ type: str
+ returned: success
+ sample: 1,5,10
+ type:
+ description:
+ - Type of port.
+ type: str
+ returned: success
+ sample: access
+ drop_untagged_traffic:
+ description:
+ - Trunk port can Drop all Untagged traffic. When true, no VLAN is required.
+ - Access ports cannot have dropUntaggedTraffic set to true.
+ type: bool
+ returned: success
+ sample: true
+ enabled:
+ description:
+ - Enabled state of port.
+ type: bool
+ returned: success
+ sample: true
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def construct_payload(meraki):
+ payload = {}
+ if meraki.params['vlan'] is not None:
+ payload['vlan'] = meraki.params['vlan']
+ if meraki.params['access_policy'] is not None:
+ payload['accessPolicy'] = meraki.params['access_policy']
+ if meraki.params['allowed_vlans'] is not None:
+ payload['allowedVlans'] = meraki.params['allowed_vlans']
+ if meraki.params['port_type'] is not None:
+ payload['type'] = meraki.params['port_type']
+ if meraki.params['drop_untagged_traffic'] is not None:
+ payload['dropUntaggedTraffic'] = meraki.params['drop_untagged_traffic']
+ if meraki.params['enabled'] is not None:
+ payload['enabled'] = meraki.params['enabled']
+ return payload
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type='str'),
+ net_name=dict(type='str', aliases=['name', 'network']),
+ state=dict(type='str', choices=['present', 'query'], default='present'),
+ number=dict(type='int', aliases=['port', 'port_id']),
+ vlan=dict(type='int'),
+ access_policy=dict(type='str', choices=['open', '8021x-radius', 'mac-radius', 'hybris-radius']),
+ allowed_vlans=dict(type='str'),
+ port_type=dict(type='str', choices=['access', 'trunk']),
+ drop_untagged_traffic=dict(type='bool'),
+ enabled=dict(type='bool'),
+ )
+
+ # 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_l2_interface')
+ module.params['follow_redirects'] = 'all'
+
+ get_all_urls = {'mx_l2_interface': '/networks/{net_id}/appliance/ports'}
+ get_one_urls = {'mx_l2_interface': '/networks/{net_id}/appliance/ports/{port_id}'}
+ update_urls = {'mx_l2_interface': '/networks/{net_id}/appliance/ports/{port_id}'}
+ meraki.url_catalog['query_all'] = get_all_urls
+ meraki.url_catalog['query_one'] = get_one_urls
+ meraki.url_catalog['update'] = update_urls
+
+ if meraki.params['net_name'] and meraki.params['net_id']:
+ meraki.fail_json(msg='net_name and net_id are mutually exclusive.')
+ if meraki.params['port_type'] == 'access':
+ if meraki.params['allowed_vlans'] is not None:
+ meraki.meraki.fail_json(msg='allowed_vlans is mutually exclusive with port type trunk.')
+
+ org_id = meraki.params['org_id']
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ if meraki.params['number'] is not None:
+ path = meraki.construct_path('query_one', net_id=net_id, custom={'port_id': meraki.params['number']})
+ else:
+ path = meraki.construct_path('query_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('query_one', net_id=net_id, custom={'port_id': meraki.params['number']})
+ original = meraki.request(path, method='GET')
+ payload = construct_payload(meraki)
+ if meraki.is_update_required(original, payload):
+ meraki.generate_diff(original, payload)
+ if meraki.check_mode is True:
+ original.update(payload)
+ meraki.result['data'] = original
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id, custom={'port_id': meraki.params['number']})
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result['data'] = original
+ meraki.exit_json(**meraki.result)
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py
new file mode 100644
index 000000000..f8fa3f919
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py
@@ -0,0 +1,377 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_l3_firewall
+short_description: Manage MX appliance layer 3 firewalls in the Meraki cloud
+description:
+- Allows for creation, management, and visibility into layer 3 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:
+ - Create or modify an organization.
+ 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 firewall rules.
+ type: list
+ elements: dict
+ suboptions:
+ policy:
+ description:
+ - Policy to apply if rule is hit.
+ choices: [allow, deny]
+ type: str
+ protocol:
+ description:
+ - Protocol to match against.
+ choices: [any, icmp, tcp, udp]
+ type: str
+ dest_port:
+ description:
+ - Comma separated list of destination port numbers to match against.
+ - C(Any) must be capitalized.
+ type: str
+ dest_cidr:
+ description:
+ - Comma separated list of CIDR notation destination networks.
+ - C(Any) must be capitalized.
+ type: str
+ src_port:
+ description:
+ - Comma separated list of source port numbers to match against.
+ - C(Any) must be capitalized.
+ type: str
+ src_cidr:
+ description:
+ - Comma separated list of CIDR notation source networks.
+ - C(Any) must be capitalized.
+ type: str
+ comment:
+ description:
+ - Optional comment to describe the firewall rule.
+ type: str
+ syslog_enabled:
+ description:
+ - Whether to log hints against the firewall rule.
+ - Only applicable if a syslog server is specified against the network.
+ type: bool
+ default: False
+ syslog_default_rule:
+ description:
+ - Whether to log hits against the default firewall rule.
+ - Only applicable if a syslog server is specified against the network.
+ - This is not shown in response from Meraki. Instead, refer to the C(syslog_enabled) value in the default rule.
+ type: bool
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+
+- name: Set two firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ rules:
+ - comment: Block traffic to server
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.2/32
+ dest_port: any
+ protocol: any
+ policy: deny
+ - comment: Allow traffic to group of servers
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.0/24
+ dest_port: any
+ protocol: any
+ policy: allow
+ delegate_to: localhost
+
+- name: Set one firewall rule and enable logging of the default rule
+ meraki_mx_l3_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ rules:
+ - comment: Block traffic to server
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.2/32
+ dest_port: any
+ protocol: any
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Firewall rules associated to network.
+ returned: success
+ type: complex
+ contains:
+ rules:
+ description: List of firewall rules.
+ returned: success
+ type: complex
+ contains:
+ comment:
+ description: Comment to describe the firewall rule.
+ returned: always
+ type: str
+ sample: Block traffic to server
+ src_cidr:
+ description: Comma separated list of CIDR notation source networks.
+ returned: always
+ type: str
+ sample: 192.0.1.1/32,192.0.1.2/32
+ src_port:
+ description: Comma separated list of source ports.
+ returned: always
+ type: str
+ sample: 80,443
+ dest_cidr:
+ description: Comma separated list of CIDR notation destination networks.
+ returned: always
+ type: str
+ sample: 192.0.1.1/32,192.0.1.2/32
+ dest_port:
+ description: Comma separated list of destination ports.
+ returned: always
+ type: str
+ sample: 80,443
+ protocol:
+ description: Network protocol for which to match against.
+ returned: always
+ type: str
+ sample: tcp
+ policy:
+ description: Action to take when rule is matched.
+ returned: always
+ type: str
+ syslog_enabled:
+ description: Whether to log to syslog when rule is matched.
+ returned: always
+ type: bool
+ sample: true
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def assemble_payload(meraki):
+ params_map = {'policy': 'policy',
+ 'protocol': 'protocol',
+ 'dest_port': 'destPort',
+ 'dest_cidr': 'destCidr',
+ 'src_port': 'srcPort',
+ 'src_cidr': 'srcCidr',
+ 'syslog_enabled': 'syslogEnabled',
+ 'comment': 'comment',
+ }
+ rules = []
+ for rule in meraki.params['rules']:
+ proposed_rule = dict()
+ for k, v in rule.items():
+ proposed_rule[params_map[k]] = v
+ rules.append(proposed_rule)
+ payload = {'rules': rules}
+ return payload
+
+
+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 normalize_rule_case(response)
+
+
+def normalize_rule_case(rules):
+ excluded = ['comment', 'syslogEnabled']
+ try:
+ for r in rules['rules']:
+ for k in r:
+ if k not in excluded:
+ r[k] = r[k].lower()
+ except KeyError:
+ return rules
+ return rules
+
+
+def normalize_case(rule):
+ any = ['any', 'Any', 'ANY']
+ if 'srcPort' in rule:
+ if rule['srcPort'] in any:
+ rule['srcPort'] = 'Any'
+ if 'srcCidr' in rule:
+ if rule['srcCidr'] in any:
+ rule['srcCidr'] = 'Any'
+ if 'destPort' in rule:
+ if rule['destPort'] in any:
+ rule['destPort'] = 'Any'
+ if 'destCidr' in rule:
+ if rule['destCidr'] in any:
+ rule['destCidr'] = 'Any'
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ fw_rules = dict(policy=dict(type='str', choices=['allow', 'deny']),
+ protocol=dict(type='str', choices=['tcp', 'udp', 'icmp', 'any']),
+ dest_port=dict(type='str'),
+ dest_cidr=dict(type='str'),
+ src_port=dict(type='str'),
+ src_cidr=dict(type='str'),
+ comment=dict(type='str'),
+ syslog_enabled=dict(type='bool', default=False),
+ )
+
+ 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=fw_rules),
+ syslog_default_rule=dict(type='bool'),
+ )
+
+ # 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_l3_firewall')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'mx_l3_firewall': '/networks/{net_id}/appliance/firewall/l3FirewallRules/'}
+ update_urls = {'mx_l3_firewall': '/networks/{net_id}/appliance/firewall/l3FirewallRules/'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['update'] = update_urls
+
+ payload = None
+
+ # execute checks for argument completeness
+
+ # 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']
+ 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:
+ 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':
+ meraki.result['data'] = 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'] is not None:
+ payload = assemble_payload(meraki)
+ else:
+ payload = dict()
+ update = False
+ if meraki.params['syslog_default_rule'] is not None:
+ payload['syslogDefaultRule'] = meraki.params['syslog_default_rule']
+ try:
+ if meraki.params['rules'] is not None:
+ if len(rules['rules']) - 1 != len(payload['rules']): # Quick and simple check to avoid more processing
+ update = True
+ if meraki.params['syslog_default_rule'] is not None:
+ if rules['rules'][len(rules['rules']) - 1]['syslogEnabled'] != meraki.params['syslog_default_rule']:
+ update = True
+ if update is False:
+ default_rule = rules['rules'][len(rules['rules']) - 1].copy()
+ del rules['rules'][len(rules['rules']) - 1] # Remove default rule for comparison
+ if len(rules['rules']) - 1 == 0: # There is only a single rule
+ normalize_case(rules['rules'][0])
+ normalize_case(payload['rules'][0])
+ if meraki.is_update_required(rules['rules'][0], payload['rules'][0]) is True:
+ update = True
+ else:
+ for r in range(len(rules['rules']) - 1):
+ normalize_case(rules[r])
+ normalize_case(payload['rules'][r])
+ if meraki.is_update_required(rules[r], payload['rules'][r]) is True:
+ update = True
+ rules['rules'].append(default_rule)
+ except KeyError:
+ pass
+ if update is True:
+ if meraki.check_mode is True:
+ if meraki.params['rules'] is not None:
+ data = payload['rules']
+ data.append(rules['rules'][len(rules['rules']) - 1]) # Append the default rule
+ if meraki.params['syslog_default_rule'] is not None:
+ data[len(payload) - 1]['syslog_enabled'] = meraki.params['syslog_default_rule']
+ else:
+ if meraki.params['syslog_default_rule'] is not None:
+ data = rules
+ data['rules'][len(data['rules']) - 1]['syslogEnabled'] = meraki.params['syslog_default_rule']
+ meraki.result['data'] = data
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = rules
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py
new file mode 100644
index 000000000..efe4bda88
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py
@@ -0,0 +1,475 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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
+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
+ elements: dict
+ 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,
+ blocked_countries,
+ host,
+ ip_range,
+ port,
+ allowed_countries]
+ type: str
+ application:
+ description:
+ - Application to filter.
+ type: dict
+ 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
+ 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
+ elements: str
+ 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: cisco.meraki.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: allowed_countries
+ countries:
+ - US
+ - FR
+ - type: blocked_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: str
+ sample: deny
+ type:
+ description: Type of rule category.
+ returned: success
+ type: str
+ sample: applications
+ applications:
+ description: List of applications within a category.
+ type: list
+ contains:
+ id:
+ description: URI of application.
+ returned: success
+ type: str
+ sample: Gmail
+ name:
+ description: Descriptive name of application.
+ returned: success
+ type: str
+ 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: str
+ sample: Gmail
+ name:
+ description: Descriptive name of application.
+ returned: success
+ type: str
+ sample: meraki:layer7/application/4
+ port:
+ description: Port number in rule.
+ returned: success
+ type: str
+ sample: 23
+ ipRange:
+ description: Range of IP addresses in rule.
+ returned: success
+ type: str
+ sample: 1.1.1.0/23
+ allowedCountries:
+ description: Countries to be allowed.
+ returned: success
+ type: str
+ sample: CA
+ blockedCountries:
+ description: Countries to be blacklisted.
+ returned: success
+ type: str
+ 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: str
+ sample: Gmail
+ name:
+ description: Descriptive name of application.
+ returned: success
+ type: str
+ sample: meraki:layer7/application/4
+ id:
+ description: URI of application category.
+ returned: success
+ type: str
+ sample: Email
+ name:
+ description: Descriptive name of application category.
+ returned: success
+ type: str
+ sample: layer7/category/1
+'''
+
+import copy
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.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'] == 'blocked_countries':
+ new_rule = {'policy': rule['policy'],
+ 'type': 'blockedCountries',
+ 'value': rule['countries']
+ }
+ elif rule['type'] == 'allowed_countries':
+ new_rule = {'policy': rule['policy'],
+ 'type': 'allowedCountries',
+ '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 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',
+ 'blocked_countries',
+ 'host',
+ 'ip_range',
+ 'port',
+ 'allowed_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', elements='str'),
+ )
+
+ 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'),
+ )
+
+ # 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'] == 'blocked_countries' and rule['countries'] is None:
+ meraki.fail_json(msg="countries argument is required when type is blocked_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'] == 'allowed_countries' and rule['countries'] is None:
+ meraki.fail_json(msg="countries argument is required when type is allowed_countries.")
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'mx_l7_firewall': '/networks/{net_id}/appliance/firewall/l7FirewallRules/'}
+ query_category_urls = {'mx_l7_firewall': '/networks/{net_id}/appliance/firewall/l7FirewallRules/applicationCategories'}
+ update_urls = {'mx_l7_firewall': '/networks/{net_id}/appliance/firewall/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)
+
+ # Detect if no rules are given, special case
+ if len(meraki.params['rules']) == 0:
+ # Conditionally wrap parameters in rules makes it comparable
+ if isinstance(meraki.params['rules'], list):
+ param_rules = {'rules': meraki.params['rules']}
+ else:
+ param_rules = meraki.params['rules']
+ if meraki.is_update_required(rules, param_rules):
+ if meraki.module.check_mode is True:
+ meraki.result['data'] = meraki.params['rules']
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ payload = {'rules': []}
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result['data'] = param_rules
+ meraki.exit_json(**meraki.result)
+ if meraki.params['rules']:
+ payload = {'rules': []}
+ for rule in meraki.params['rules']:
+ payload['rules'].append(assemble_payload(meraki, net_id, rule))
+ else:
+ payload = dict()
+ if meraki.is_update_required(rules, payload, force_include='id'):
+ if meraki.module.check_mode is True:
+ response = restructure_response(payload)
+ meraki.generate_diff(restructure_response(rules), response)
+ 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:
+ meraki.generate_diff(restructure_response(rules), response)
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_malware.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_malware.py
new file mode 100644
index 000000000..1cbf7e680
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_malware.py
@@ -0,0 +1,264 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_malware
+short_description: Manage Malware Protection in the Meraki cloud
+description:
+- Fully configure malware protection in a Meraki environment.
+notes:
+- Some of the options are likely only used for developers within Meraki.
+options:
+ state:
+ description:
+ - Specifies whether object should be queried, created/modified, or removed.
+ choices: [absent, present, query]
+ default: query
+ type: str
+ net_name:
+ description:
+ - Name of network which configuration is applied to.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of network which configuration is applied to.
+ type: str
+ allowed_urls:
+ description:
+ - List of URLs to whitelist.
+ type: list
+ elements: dict
+ suboptions:
+ url:
+ description:
+ - URL string to allow.
+ type: str
+ comment:
+ description:
+ - Human readable information about URL.
+ type: str
+ allowed_files:
+ description:
+ - List of files to whitelist.
+ type: list
+ elements: dict
+ suboptions:
+ sha256:
+ description:
+ - 256-bit hash of file.
+ type: str
+ aliases: [ hash ]
+ comment:
+ description:
+ - Human readable information about file.
+ type: str
+ mode:
+ description:
+ - Enabled or disabled state of malware protection.
+ choices: [disabled, enabled]
+ type: str
+
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+ - name: Enable malware protection
+ meraki_malware:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ mode: enabled
+ delegate_to: localhost
+
+ - name: Set whitelisted url
+ meraki_malware:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ mode: enabled
+ allowed_urls:
+ - url: www.ansible.com
+ comment: Ansible
+ - url: www.google.com
+ comment: Google
+ delegate_to: localhost
+
+ - name: Set whitelisted file
+ meraki_malware:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ mode: enabled
+ allowed_files:
+ - sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
+ comment: random zip
+ delegate_to: localhost
+
+ - name: Get malware settings
+ meraki_malware:
+ auth_key: abc123
+ state: query
+ org_name: YourNet
+ net_name: YourOrg
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: List of administrators.
+ returned: success
+ type: complex
+ contains:
+ mode:
+ description: Mode to enable or disable malware scanning.
+ returned: success
+ type: str
+ sample: enabled
+ allowed_files:
+ description: List of files which are whitelisted.
+ returned: success
+ type: complex
+ contains:
+ sha256:
+ description: sha256 hash of whitelisted file.
+ returned: success
+ type: str
+ sample: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
+ comment:
+ description: Comment about the whitelisted entity
+ returned: success
+ type: str
+ sample: TPS report
+ allowed_urls:
+ description: List of URLs which are whitelisted.
+ returned: success
+ type: complex
+ contains:
+ url:
+ description: URL of whitelisted site.
+ returned: success
+ type: str
+ sample: site.com
+ comment:
+ description: Comment about the whitelisted entity
+ returned: success
+ type: str
+ sample: Corporate HQ
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ urls_arg_spec = dict(url=dict(type='str'),
+ comment=dict(type='str'),
+ )
+
+ files_arg_spec = dict(sha256=dict(type='str', aliases=['hash']),
+ comment=dict(type='str'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'present', 'query'], default='query'),
+ net_name=dict(type='str', aliases=['network']),
+ net_id=dict(type='str'),
+ mode=dict(type='str', choices=['enabled', 'disabled']),
+ allowed_urls=dict(type='list', default=None, elements='dict', options=urls_arg_spec),
+ allowed_files=dict(type='list', default=None, elements='dict', options=files_arg_spec),
+ )
+
+ # 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='malware')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_url = {'malware': '/networks/{net_id}/appliance/security/malware'}
+ update_url = {'malware': '/networks/{net_id}/appliance/security/malware'}
+
+ meraki.url_catalog['get_one'].update(query_url)
+ meraki.url_catalog['update'] = update_url
+
+ org_id = meraki.params['org_id']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ # Check for argument completeness
+ if meraki.params['state'] == 'present':
+ if meraki.params['allowed_files'] is not None or meraki.params['allowed_urls'] is not None:
+ if meraki.params['mode'] is None:
+ meraki.fail_json(msg="mode must be set when allowed_files or allowed_urls is set.")
+
+ # Assemble payload
+ if meraki.params['state'] == 'present':
+ payload = dict()
+ if meraki.params['mode'] is not None:
+ payload['mode'] = meraki.params['mode']
+ if meraki.params['allowed_urls'] is not None:
+ payload['allowedUrls'] = meraki.params['allowed_urls']
+ if meraki.params['allowed_files'] is not None:
+ payload['allowedFiles'] = meraki.params['allowed_files']
+
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_one', net_id=net_id)
+ data = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ meraki.result['data'] = data
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('get_one', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ if meraki.is_update_required(original, payload):
+ if meraki.module.check_mode is True:
+ meraki.generate_diff(original, payload)
+ original.update(payload)
+ meraki.result['data'] = original
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id)
+ data = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.generate_diff(original, data)
+ meraki.result['data'] = data
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_nat.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_nat.py
new file mode 100644
index 000000000..0844d4c1f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_nat.py
@@ -0,0 +1,679 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_nat
+short_description: Manage NAT rules in Meraki cloud
+description:
+- Allows for creation, management, and visibility of NAT rules (1:1, 1:many, port forwarding) within Meraki.
+
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: [present, query]
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [name, network]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ org_id:
+ description:
+ - ID of organization associated to a network.
+ type: str
+ subset:
+ description:
+ - Specifies which NAT components to query.
+ choices: ['1:1', '1:many', all, port_forwarding]
+ default: all
+ type: list
+ elements: str
+ one_to_one:
+ description:
+ - List of 1:1 NAT rules.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - A descriptive name for the rule.
+ type: str
+ public_ip:
+ description:
+ - The IP address that will be used to access the internal resource from the WAN.
+ type: str
+ lan_ip:
+ description:
+ - The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN.
+ type: str
+ uplink:
+ description:
+ - The physical WAN interface on which the traffic will arrive.
+ choices: [both, internet1, internet2]
+ type: str
+ allowed_inbound:
+ description:
+ - The ports this mapping will provide access on, and the remote IPs that will be allowed access to the resource.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - Protocol to apply NAT rule to.
+ choices: [any, icmp-ping, tcp, udp]
+ type: str
+ default: any
+ destination_ports:
+ description:
+ - List of ports or port ranges that will be forwarded to the host on the LAN.
+ type: list
+ elements: str
+ allowed_ips:
+ description:
+ - ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges, or 'any'.
+ type: list
+ elements: str
+ one_to_many:
+ description:
+ - List of 1:many NAT rules.
+ type: list
+ elements: dict
+ suboptions:
+ public_ip:
+ description:
+ - The IP address that will be used to access the internal resource from the WAN.
+ type: str
+ uplink:
+ description:
+ - The physical WAN interface on which the traffic will arrive.
+ choices: [both, internet1, internet2]
+ type: str
+ port_rules:
+ description:
+ - List of associated port rules.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - A description of the rule.
+ type: str
+ protocol:
+ description:
+ - Protocol to apply NAT rule to.
+ choices: [tcp, udp]
+ type: str
+ public_port:
+ description:
+ - Destination port of the traffic that is arriving on the WAN.
+ type: str
+ local_ip:
+ description:
+ - Local IP address to which traffic will be forwarded.
+ type: str
+ local_port:
+ description:
+ - Destination port of the forwarded traffic that will be sent from the MX to the specified host on the LAN.
+ - If you simply wish to forward the traffic without translating the port, this should be the same as the Public port.
+ type: str
+ allowed_ips:
+ description:
+ - Remote IP addresses or ranges that are permitted to access the internal resource via this port forwarding rule, or 'any'.
+ type: list
+ elements: str
+ port_forwarding:
+ description:
+ - List of port forwarding rules.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - A descriptive name for the rule.
+ type: str
+ lan_ip:
+ description:
+ - The IP address of the server or device that hosts the internal resource that you wish to make available on the WAN.
+ type: str
+ uplink:
+ description:
+ - The physical WAN interface on which the traffic will arrive.
+ choices: [both, internet1, internet2]
+ type: str
+ public_port:
+ description:
+ - A port or port ranges that will be forwarded to the host on the LAN.
+ type: int
+ local_port:
+ description:
+ - A port or port ranges that will receive the forwarded traffic from the WAN.
+ type: int
+ allowed_ips:
+ description:
+ - List of ranges of WAN IP addresses that are allowed to make inbound connections on the specified ports or port ranges (or any).
+ type: list
+ elements: str
+ protocol:
+ description:
+ - Protocol to forward traffic for.
+ choices: [tcp, udp]
+ type: str
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query all NAT rules
+ meraki_nat:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ subset: all
+ delegate_to: localhost
+
+- name: Query 1:1 NAT rules
+ meraki_nat:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ subset: '1:1'
+ delegate_to: localhost
+
+- name: Create 1:1 rule
+ meraki_nat:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ one_to_one:
+ - name: Service behind NAT
+ public_ip: 1.2.1.2
+ lan_ip: 192.168.128.1
+ uplink: internet1
+ allowed_inbound:
+ - protocol: tcp
+ destination_ports:
+ - 80
+ allowed_ips:
+ - 10.10.10.10
+ delegate_to: localhost
+
+- name: Create 1:many rule
+ meraki_nat:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ one_to_many:
+ - public_ip: 1.1.1.1
+ uplink: internet1
+ port_rules:
+ - name: Test rule
+ protocol: tcp
+ public_port: 10
+ local_ip: 192.168.128.1
+ local_port: 11
+ allowed_ips:
+ - any
+ delegate_to: localhost
+
+- name: Create port forwarding rule
+ meraki_nat:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ port_forwarding:
+ - name: Test map
+ lan_ip: 192.168.128.1
+ uplink: both
+ protocol: tcp
+ allowed_ips:
+ - 1.1.1.1
+ public_port: 10
+ local_port: 11
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information about the created or manipulated object.
+ returned: success
+ type: complex
+ contains:
+ one_to_one:
+ description: Information about 1:1 NAT object.
+ returned: success, when 1:1 NAT object is in task
+ type: complex
+ contains:
+ rules:
+ description: List of 1:1 NAT rules.
+ returned: success, when 1:1 NAT object is in task
+ type: complex
+ contains:
+ name:
+ description: Name of NAT object.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: Web server behind NAT
+ lanIp:
+ description: Local IP address to be mapped.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: 192.168.128.22
+ publicIp:
+ description: Public IP address to be mapped.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: 148.2.5.100
+ uplink:
+ description: Internet port where rule is applied.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: internet1
+ allowedInbound:
+ description: List of inbound forwarding rules.
+ returned: success, when 1:1 NAT object is in task
+ type: complex
+ contains:
+ protocol:
+ description: Protocol to apply NAT rule to.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: tcp
+ destinationPorts:
+ description: Ports to apply NAT rule to.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: 80
+ allowedIps:
+ description: List of IP addresses to be forwarded.
+ returned: success, when 1:1 NAT object is in task
+ type: list
+ example: 10.80.100.0/24
+ one_to_many:
+ description: Information about 1:many NAT object.
+ returned: success, when 1:many NAT object is in task
+ type: complex
+ contains:
+ rules:
+ description: List of 1:many NAT rules.
+ returned: success, when 1:many NAT object is in task
+ type: complex
+ contains:
+ publicIp:
+ description: Public IP address to be mapped.
+ returned: success, when 1:many NAT object is in task
+ type: str
+ example: 148.2.5.100
+ uplink:
+ description: Internet port where rule is applied.
+ returned: success, when 1:many NAT object is in task
+ type: str
+ example: internet1
+ portRules:
+ description: List of NAT port rules.
+ returned: success, when 1:many NAT object is in task
+ type: complex
+ contains:
+ name:
+ description: Name of NAT object.
+ returned: success, when 1:many NAT object is in task
+ type: str
+ example: Web server behind NAT
+ protocol:
+ description: Protocol to apply NAT rule to.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: tcp
+ publicPort:
+ description: Destination port of the traffic that is arriving on WAN.
+ returned: success, when 1:1 NAT object is in task
+ type: int
+ example: 9443
+ localIp:
+ description: Local IP address traffic will be forwarded.
+ returned: success, when 1:1 NAT object is in task
+ type: str
+ example: 192.0.2.10
+ localPort:
+ description: Destination port to be forwarded to.
+ returned: success, when 1:1 NAT object is in task
+ type: int
+ example: 443
+ allowedIps:
+ description: List of IP addresses to be forwarded.
+ returned: success, when 1:1 NAT object is in task
+ type: list
+ example: 10.80.100.0/24
+ port_forwarding:
+ description: Information about port forwarding rules.
+ returned: success, when port forwarding is in task
+ type: complex
+ contains:
+ rules:
+ description: List of port forwarding rules.
+ returned: success, when port forwarding is in task
+ type: complex
+ contains:
+ lanIp:
+ description: Local IP address to be mapped.
+ returned: success, when port forwarding is in task
+ type: str
+ example: 192.168.128.22
+ allowedIps:
+ description: List of IP addresses to be forwarded.
+ returned: success, when port forwarding is in task
+ type: list
+ example: 10.80.100.0/24
+ name:
+ description: Name of NAT object.
+ returned: success, when port forwarding is in task
+ type: str
+ example: Web server behind NAT
+ protocol:
+ description: Protocol to apply NAT rule to.
+ returned: success, when port forwarding is in task
+ type: str
+ example: tcp
+ publicPort:
+ description: Destination port of the traffic that is arriving on WAN.
+ returned: success, when port forwarding is in task
+ type: int
+ example: 9443
+ localPort:
+ description: Destination port to be forwarded to.
+ returned: success, when port forwarding is in task
+ type: int
+ example: 443
+ uplink:
+ description: Internet port where rule is applied.
+ returned: success, when port forwarding is in task
+ type: str
+ example: internet1
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import recursive_diff
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+key_map = {'name': 'name',
+ 'public_ip': 'publicIp',
+ 'lan_ip': 'lanIp',
+ 'uplink': 'uplink',
+ 'allowed_inbound': 'allowedInbound',
+ 'protocol': 'protocol',
+ 'destination_ports': 'destinationPorts',
+ 'allowed_ips': 'allowedIps',
+ 'port_rules': 'portRules',
+ 'public_port': 'publicPort',
+ 'local_ip': 'localIp',
+ 'local_port': 'localPort',
+ }
+
+
+def construct_payload(params):
+ if isinstance(params, list):
+ items = []
+ for item in params:
+ items.append(construct_payload(item))
+ return items
+ elif isinstance(params, dict):
+ info = {}
+ for param in params:
+ info[key_map[param]] = construct_payload(params[param])
+ return info
+ elif isinstance(params, str) or isinstance(params, int):
+ return params
+
+
+def list_int_to_str(data):
+ return [str(item) for item in data]
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ one_to_one_allowed_inbound_spec = dict(protocol=dict(type='str', choices=['tcp', 'udp', 'icmp-ping', 'any'], default='any'),
+ destination_ports=dict(type='list', elements='str'),
+ allowed_ips=dict(type='list', elements='str'),
+ )
+
+ one_to_many_port_inbound_spec = dict(protocol=dict(type='str', choices=['tcp', 'udp']),
+ name=dict(type='str'),
+ local_ip=dict(type='str'),
+ local_port=dict(type='str'),
+ allowed_ips=dict(type='list', elements='str'),
+ public_port=dict(type='str'),
+ )
+
+ one_to_one_spec = dict(name=dict(type='str'),
+ public_ip=dict(type='str'),
+ lan_ip=dict(type='str'),
+ uplink=dict(type='str', choices=['internet1', 'internet2', 'both']),
+ allowed_inbound=dict(type='list', elements='dict', options=one_to_one_allowed_inbound_spec),
+ )
+
+ one_to_many_spec = dict(public_ip=dict(type='str'),
+ uplink=dict(type='str', choices=['internet1', 'internet2', 'both']),
+ port_rules=dict(type='list', elements='dict', options=one_to_many_port_inbound_spec),
+ )
+
+ port_forwarding_spec = dict(name=dict(type='str'),
+ lan_ip=dict(type='str'),
+ uplink=dict(type='str', choices=['internet1', 'internet2', 'both']),
+ protocol=dict(type='str', choices=['tcp', 'udp']),
+ public_port=dict(type='int'),
+ local_port=dict(type='int'),
+ allowed_ips=dict(type='list', elements='str'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type='str'),
+ net_name=dict(type='str', aliases=['name', 'network']),
+ state=dict(type='str', choices=['present', 'query'], default='present'),
+ subset=dict(type='list', elements='str', choices=['1:1', '1:many', 'all', 'port_forwarding'], default='all'),
+ one_to_one=dict(type='list', elements='dict', options=one_to_one_spec),
+ one_to_many=dict(type='list', elements='dict', options=one_to_many_spec),
+ port_forwarding=dict(type='list', elements='dict', options=port_forwarding_spec),
+ )
+
+ # 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='nat')
+ module.params['follow_redirects'] = 'all'
+
+ one_to_one_payload = None
+ one_to_many_payload = None
+ port_forwarding_payload = None
+ if meraki.params['state'] == 'present':
+ if meraki.params['one_to_one'] is not None:
+ rules = []
+ for i in meraki.params['one_to_one']:
+ data = {'name': i['name'],
+ 'publicIp': i['public_ip'],
+ 'uplink': i['uplink'],
+ 'lanIp': i['lan_ip'],
+ 'allowedInbound': construct_payload(i['allowed_inbound'])
+ }
+ for inbound in data['allowedInbound']:
+ inbound['destinationPorts'] = list_int_to_str(inbound['destinationPorts'])
+ rules.append(data)
+ one_to_one_payload = {'rules': rules}
+ if meraki.params['one_to_many'] is not None:
+ rules = []
+ for i in meraki.params['one_to_many']:
+ data = {'publicIp': i['public_ip'],
+ 'uplink': i['uplink'],
+ }
+ port_rules = []
+ for port_rule in i['port_rules']:
+ rule = {'name': port_rule['name'],
+ 'protocol': port_rule['protocol'],
+ 'publicPort': str(port_rule['public_port']),
+ 'localIp': port_rule['local_ip'],
+ 'localPort': str(port_rule['local_port']),
+ 'allowedIps': port_rule['allowed_ips'],
+ }
+ port_rules.append(rule)
+ data['portRules'] = port_rules
+ rules.append(data)
+ one_to_many_payload = {'rules': rules}
+ if meraki.params['port_forwarding'] is not None:
+ port_forwarding_payload = {'rules': construct_payload(meraki.params['port_forwarding'])}
+ for rule in port_forwarding_payload['rules']:
+ rule['localPort'] = str(rule['localPort'])
+ rule['publicPort'] = str(rule['publicPort'])
+
+ onetomany_urls = {'nat': '/networks/{net_id}/appliance/firewall/oneToManyNatRules'}
+ onetoone_urls = {'nat': '/networks/{net_id}/appliance/firewall/oneToOneNatRules'}
+ port_forwarding_urls = {'nat': '/networks/{net_id}/appliance/firewall/portForwardingRules'}
+ meraki.url_catalog['1:many'] = onetomany_urls
+ meraki.url_catalog['1:1'] = onetoone_urls
+ meraki.url_catalog['port_forwarding'] = port_forwarding_urls
+
+ if meraki.params['net_name'] and meraki.params['net_id']:
+ meraki.fail_json(msg='net_name and net_id are mutually exclusive')
+
+ org_id = meraki.params['org_id']
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ if meraki.params['subset'][0] == 'all':
+ path = meraki.construct_path('1:many', net_id=net_id)
+ data = {'1:many': meraki.request(path, method='GET')}
+ path = meraki.construct_path('1:1', net_id=net_id)
+ data['1:1'] = meraki.request(path, method='GET')
+ path = meraki.construct_path('port_forwarding', net_id=net_id)
+ data['port_forwarding'] = meraki.request(path, method='GET')
+ meraki.result['data'] = data
+ else:
+ for subset in meraki.params['subset']:
+ path = meraki.construct_path(subset, net_id=net_id)
+ data = {subset: meraki.request(path, method='GET')}
+ try:
+ meraki.result['data'][subset] = data
+ except KeyError:
+ meraki.result['data'] = {subset: data}
+ elif meraki.params['state'] == 'present':
+ meraki.result['data'] = dict()
+ if one_to_one_payload is not None:
+ path = meraki.construct_path('1:1', net_id=net_id)
+ current = meraki.request(path, method='GET')
+ if meraki.is_update_required(current, one_to_one_payload):
+ if meraki.module.check_mode is True:
+ diff = recursive_diff(current, one_to_one_payload)
+ current.update(one_to_one_payload)
+ if 'diff' not in meraki.result:
+ meraki.result['diff'] = {'before': {}, 'after': {}}
+ meraki.result['diff']['before'].update({'one_to_one': diff[0]})
+ meraki.result['diff']['after'].update({'one_to_one': diff[1]})
+ meraki.result['data'] = {'one_to_one': current}
+ meraki.result['changed'] = True
+ else:
+ r = meraki.request(path, method='PUT', payload=json.dumps(one_to_one_payload))
+ if meraki.status == 200:
+ diff = recursive_diff(current, one_to_one_payload)
+ if 'diff' not in meraki.result:
+ meraki.result['diff'] = {'before': {}, 'after': {}}
+ meraki.result['diff']['before'].update({'one_to_one': diff[0]})
+ meraki.result['diff']['after'].update({'one_to_one': diff[1]})
+ meraki.result['data'] = {'one_to_one': r}
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data']['one_to_one'] = current
+ if one_to_many_payload is not None:
+ path = meraki.construct_path('1:many', net_id=net_id)
+ current = meraki.request(path, method='GET')
+ if meraki.is_update_required(current, one_to_many_payload):
+ if meraki.module.check_mode is True:
+ diff = recursive_diff(current, one_to_many_payload)
+ current.update(one_to_many_payload)
+ if 'diff' not in meraki.result:
+ meraki.result['diff'] = {'before': {}, 'after': {}}
+ meraki.result['diff']['before'].update({'one_to_many': diff[0]})
+ meraki.result['diff']['after'].update({'one_to_many': diff[1]})
+ meraki.result['data']['one_to_many'] = current
+ meraki.result['changed'] = True
+ else:
+ r = meraki.request(path, method='PUT', payload=json.dumps(one_to_many_payload))
+ if meraki.status == 200:
+ diff = recursive_diff(current, one_to_many_payload)
+ if 'diff' not in meraki.result:
+ meraki.result['diff'] = {'before': {}, 'after': {}}
+ meraki.result['diff']['before'].update({'one_to_many': diff[0]})
+ meraki.result['diff']['after'].update({'one_to_many': diff[1]})
+ meraki.result['data'].update({'one_to_many': r})
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data']['one_to_many'] = current
+ if port_forwarding_payload is not None:
+ path = meraki.construct_path('port_forwarding', net_id=net_id)
+ current = meraki.request(path, method='GET')
+ if meraki.is_update_required(current, port_forwarding_payload):
+ if meraki.module.check_mode is True:
+ diff = recursive_diff(current, port_forwarding_payload)
+ current.update(port_forwarding_payload)
+ if 'diff' not in meraki.result:
+ meraki.result['diff'] = {'before': {}, 'after': {}}
+ meraki.result['diff']['before'].update({'port_forwarding': diff[0]})
+ meraki.result['diff']['after'].update({'port_forwarding': diff[1]})
+ meraki.result['data']['port_forwarding'] = current
+ meraki.result['changed'] = True
+ else:
+ r = meraki.request(path, method='PUT', payload=json.dumps(port_forwarding_payload))
+ if meraki.status == 200:
+ if 'diff' not in meraki.result:
+ meraki.result['diff'] = {'before': {}, 'after': {}}
+ diff = recursive_diff(current, port_forwarding_payload)
+ meraki.result['diff']['before'].update({'port_forwarding': diff[0]})
+ meraki.result['diff']['after'].update({'port_forwarding': diff[1]})
+ meraki.result['data'].update({'port_forwarding': r})
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data']['port_forwarding'] = current
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_network_vlan_settings.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_network_vlan_settings.py
new file mode 100644
index 000000000..105114e54
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_network_vlan_settings.py
@@ -0,0 +1,161 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022 Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_network_vlan_settings
+short_description: Manage VLAN settings for Meraki Networks
+description:
+- Edits VLAN enabled status on a network within Meraki.
+options:
+ state:
+ description:
+ - Create or modify an alert.
+ choices: [ present, query ]
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [ name, network ]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ vlans_enabled:
+ description:
+ - Whether VLANs are enabled on the network.
+ type: bool
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Update settings
+ meraki_mx_network_vlan_settings:
+ auth_key: abc123
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ vlans_enabled: true
+"""
+
+RETURN = r"""
+data:
+ description: Information about the created or manipulated object.
+ returned: info
+ type: complex
+ contains:
+ vlans_enabled:
+ description: Whether VLANs are enabled for this network.
+ returned: success
+ type: bool
+"""
+
+import copy
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def construct_payload(meraki):
+ payload = {"vlansEnabled": meraki.params["vlans_enabled"]}
+ return payload
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type="str"),
+ net_name=dict(type="str", aliases=["name", "network"]),
+ state=dict(type="str", choices=["query", "present"]),
+ vlans_enabled=dict(type="bool"),
+ )
+
+ # 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="network_vlan_settings")
+ module.params["follow_redirects"] = "all"
+
+ query_urls = {
+ "network_vlan_settings": "/networks/{net_id}/appliance/vlans/settings"
+ }
+ update_urls = {
+ "network_vlan_settings": "/networks/{net_id}/appliance/vlans/settings"
+ }
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["update"] = update_urls
+
+ # 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"]
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ net_id = meraki.params["net_id"]
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params["net_name"], data=nets)
+
+ if meraki.params["state"] == "query":
+ path = meraki.construct_path("get_all", net_id=net_id)
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["state"] == "present":
+ path = meraki.construct_path("get_all", net_id=net_id)
+ original = meraki.request(path, method="GET")
+ payload = construct_payload(meraki)
+ if meraki.is_update_required(original, payload):
+ if meraki.check_mode is True:
+ meraki.generate_diff(original, payload)
+ meraki.result["data"] = payload
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("update", net_id=net_id)
+ response = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.generate_diff(original, payload)
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result["data"] = original
+ meraki.exit_json(**meraki.result)
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_firewall.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_firewall.py
new file mode 100644
index 000000000..f81ac3a3c
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_firewall.py
@@ -0,0 +1,330 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_site_to_site_firewall
+short_description: Manage MX appliance firewall rules for site-to-site VPNs
+version_added: "1.0.0"
+description:
+- Allows for creation, management, and visibility into firewall rules for site-to-site VPNs implemented on Meraki MX firewalls.
+notes:
+- Module assumes a complete list of firewall rules are passed as a parameter.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: ['present', 'query']
+ default: present
+ type: str
+ rules:
+ description:
+ - List of firewall rules.
+ type: list
+ elements: dict
+ suboptions:
+ policy:
+ description:
+ - Policy to apply if rule is hit.
+ choices: [allow, deny]
+ type: str
+ protocol:
+ description:
+ - Protocol to match against.
+ choices: [any, icmp, tcp, udp]
+ type: str
+ dest_port:
+ description:
+ - Comma separated list of destination port numbers to match against.
+ - C(Any) must be capitalized.
+ type: str
+ dest_cidr:
+ description:
+ - Comma separated list of CIDR notation destination networks.
+ - C(Any) must be capitalized.
+ type: str
+ src_port:
+ description:
+ - Comma separated list of source port numbers to match against.
+ - C(Any) must be capitalized.
+ type: str
+ src_cidr:
+ description:
+ - Comma separated list of CIDR notation source networks.
+ - C(Any) must be capitalized.
+ type: str
+ comment:
+ description:
+ - Optional comment to describe the firewall rule.
+ type: str
+ syslog_enabled:
+ description:
+ - Whether to log hints against the firewall rule.
+ - Only applicable if a syslog server is specified against the network.
+ type: bool
+ default: False
+ syslog_default_rule:
+ description:
+ - Whether to log hits against the default firewall rule.
+ - Only applicable if a syslog server is specified against the network.
+ - This is not shown in response from Meraki. Instead, refer to the C(syslog_enabled) value in the default rule.
+ type: bool
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query firewall rules
+ meraki_mx_site_to_site_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+- name: Set two firewall rules
+ meraki_mx_site_to_site_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ state: present
+ rules:
+ - comment: Block traffic to server
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.2/32
+ dest_port: any
+ protocol: any
+ policy: deny
+ - comment: Allow traffic to group of servers
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.0/24
+ dest_port: any
+ protocol: any
+ policy: permit
+ delegate_to: localhost
+
+- name: Set one firewall rule and enable logging of the default rule
+ meraki_mx_site_to_site_firewall:
+ auth_key: abc123
+ org_name: YourOrg
+ state: present
+ rules:
+ - comment: Block traffic to server
+ src_cidr: 192.0.1.0/24
+ src_port: any
+ dest_cidr: 192.0.2.2/32
+ dest_port: any
+ protocol: any
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Firewall rules associated to network.
+ returned: success
+ type: complex
+ contains:
+ rules:
+ description: List of firewall rules associated to network.
+ returned: success
+ type: complex
+ contains:
+ comment:
+ description: Comment to describe the firewall rule.
+ returned: always
+ type: str
+ sample: Block traffic to server
+ src_cidr:
+ description: Comma separated list of CIDR notation source networks.
+ returned: always
+ type: str
+ sample: 192.0.1.1/32,192.0.1.2/32
+ src_port:
+ description: Comma separated list of source ports.
+ returned: always
+ type: str
+ sample: 80,443
+ dest_cidr:
+ description: Comma separated list of CIDR notation destination networks.
+ returned: always
+ type: str
+ sample: 192.0.1.1/32,192.0.1.2/32
+ dest_port:
+ description: Comma separated list of destination ports.
+ returned: always
+ type: str
+ sample: 80,443
+ protocol:
+ description: Network protocol for which to match against.
+ returned: always
+ type: str
+ sample: tcp
+ policy:
+ description: Action to take when rule is matched.
+ returned: always
+ type: str
+ syslog_enabled:
+ description: Whether to log to syslog when rule is matched.
+ returned: always
+ type: bool
+ sample: true
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def assemble_payload(meraki):
+ params_map = {'policy': 'policy',
+ 'protocol': 'protocol',
+ 'dest_port': 'destPort',
+ 'dest_cidr': 'destCidr',
+ 'src_port': 'srcPort',
+ 'src_cidr': 'srcCidr',
+ 'syslog_enabled': 'syslogEnabled',
+ 'comment': 'comment',
+ }
+ rules = []
+ for rule in meraki.params['rules']:
+ proposed_rule = dict()
+ for k, v in rule.items():
+ proposed_rule[params_map[k]] = v
+ rules.append(proposed_rule)
+ payload = {'rules': rules}
+ return payload
+
+
+def get_rules(meraki, org_id):
+ path = meraki.construct_path('get_all', org_id=org_id)
+ response = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ return response
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ fw_rules = dict(policy=dict(type='str', choices=['allow', 'deny']),
+ protocol=dict(type='str', choices=['tcp', 'udp', 'icmp', 'any']),
+ dest_port=dict(type='str'),
+ dest_cidr=dict(type='str'),
+ src_port=dict(type='str'),
+ src_cidr=dict(type='str'),
+ comment=dict(type='str'),
+ syslog_enabled=dict(type='bool', default=False),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
+ rules=dict(type='list', default=None, elements='dict', options=fw_rules),
+ syslog_default_rule=dict(type='bool'),
+ )
+
+ # 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_site_to_site_firewall')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'mx_site_to_site_firewall': '/organizations/{org_id}/appliance/vpn/vpnFirewallRules/'}
+ update_urls = {'mx_site_to_site_firewall': '/organizations/{org_id}/appliance/vpn/vpnFirewallRules/'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['update'] = update_urls
+
+ payload = None
+
+ # execute checks for argument completeness
+
+ # 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']
+
+ if meraki.params['state'] == 'query':
+ meraki.result['data'] = get_rules(meraki, org_id)
+ elif meraki.params['state'] == 'present':
+ rules = get_rules(meraki, org_id)
+ path = meraki.construct_path('get_all', org_id=org_id)
+ if meraki.params['rules'] is not None:
+ payload = assemble_payload(meraki)
+ else:
+ payload = dict()
+ update = False
+ if meraki.params['syslog_default_rule'] is not None:
+ payload['syslogDefaultRule'] = meraki.params['syslog_default_rule']
+ try:
+ if meraki.params['rules'] is not None:
+ if len(rules['rules']) - 1 != len(payload['rules']): # Quick and simple check to avoid more processing
+ update = True
+ if meraki.params['syslog_default_rule'] is not None:
+ if rules['rules'][len(rules['rules']) - 1]['syslogEnabled'] != meraki.params['syslog_default_rule']:
+ update = True
+ if update is False:
+ default_rule = rules['rules'][len(rules['rules']) - 1].copy()
+ # meraki.fail_json(msg=update)
+ del rules['rules'][len(rules['rules']) - 1] # Remove default rule for comparison
+ if len(rules['rules']) - 1 == 0:
+ if meraki.is_update_required(rules['rules'][0], payload['rules'][0]) is True:
+ update = True
+ else:
+ for r in range(len(rules) - 1):
+ if meraki.is_update_required(rules['rules'][r], payload['rules'][r]) is True:
+ update = True
+ rules['rules'].append(default_rule)
+ except KeyError:
+ pass
+ if update is True:
+ if meraki.check_mode is True:
+ if meraki.params['rules'] is not None:
+ data = payload
+ data['rules'].append(rules['rules'][len(rules['rules']) - 1]) # Append the default rule
+ if meraki.params['syslog_default_rule'] is not None:
+ data['rules'][len(payload['rules']) - 1]['syslog_enabled'] = meraki.params['syslog_default_rule']
+ else:
+ if meraki.params['syslog_default_rule'] is not None:
+ data = rules
+ data['rules'][len(data['rules']) - 1]['syslogEnabled'] = meraki.params['syslog_default_rule']
+ meraki.result['data'] = data
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = rules
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py
new file mode 100644
index 000000000..2c1711adc
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py
@@ -0,0 +1,270 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_site_to_site_vpn
+short_description: Manage AutoVPN connections in Meraki
+version_added: "1.1.0"
+description:
+- Allows for creation, management, and visibility into AutoVPNs implemented on Meraki MX firewalls.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ 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
+ mode:
+ description:
+ - Set VPN mode for network
+ choices: ['none', 'hub', 'spoke']
+ type: str
+ hubs:
+ description:
+ - List of hubs to assign to a spoke.
+ type: list
+ elements: dict
+ suboptions:
+ hub_id:
+ description:
+ - Network ID of hub
+ type: str
+ use_default_route:
+ description:
+ - Indicates whether deafult troute traffic should be sent to this hub.
+ - Only valid in spoke mode.
+ type: bool
+ subnets:
+ description:
+ - List of subnets to advertise over VPN.
+ type: list
+ elements: dict
+ suboptions:
+ local_subnet:
+ description:
+ - CIDR formatted subnet.
+ type: str
+ use_vpn:
+ description:
+ - Whether to advertise over VPN.
+ type: bool
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Set hub mode
+ meraki_site_to_site_vpn:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: hub_network
+ mode: hub
+ delegate_to: localhost
+ register: set_hub
+
+- name: Set spoke mode
+ meraki_site_to_site_vpn:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: spoke_network
+ mode: spoke
+ hubs:
+ - hub_id: N_1234
+ use_default_route: false
+ delegate_to: localhost
+ register: set_spoke
+
+- name: Add subnet to hub for VPN. Hub is required.
+ meraki_site_to_site_vpn:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: hub_network
+ mode: hub
+ hubs:
+ - hub_id: N_1234
+ use_default_route: false
+ subnets:
+ - local_subnet: 192.168.1.0/24
+ use_vpn: true
+ delegate_to: localhost
+ register: set_hub
+
+- name: Query rules for hub
+ meraki_site_to_site_vpn:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: hub_network
+ delegate_to: localhost
+ register: query_all_hub
+'''
+
+RETURN = r'''
+data:
+ description: VPN settings.
+ returned: success
+ type: complex
+ contains:
+ mode:
+ description: Mode assigned to network.
+ returned: always
+ type: str
+ sample: spoke
+ hubs:
+ description: Hub networks to associate to.
+ returned: always
+ type: complex
+ contains:
+ hub_id:
+ description: ID of hub network.
+ returned: always
+ type: complex
+ sample: N_12345
+ use_default_route:
+ description: Whether to send all default route traffic over VPN.
+ returned: always
+ type: bool
+ sample: true
+ subnets:
+ description: List of subnets to advertise over VPN.
+ returned: always
+ type: complex
+ contains:
+ local_subnet:
+ description: CIDR formatted subnet.
+ returned: always
+ type: str
+ sample: 192.168.1.0/24
+ use_vpn:
+ description: Whether subnet should use the VPN.
+ returned: always
+ type: bool
+ sample: true
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+from copy import deepcopy
+
+
+def assemble_payload(meraki):
+ payload = {'mode': meraki.params['mode']}
+ if meraki.params['hubs'] is not None:
+ payload['hubs'] = meraki.params['hubs']
+ for hub in payload['hubs']:
+ hub['hubId'] = hub.pop('hub_id')
+ hub['useDefaultRoute'] = hub.pop('use_default_route')
+ if meraki.params['subnets'] is not None:
+ payload['subnets'] = meraki.params['subnets']
+ for subnet in payload['subnets']:
+ subnet['localSubnet'] = subnet.pop('local_subnet')
+ subnet['useVpn'] = subnet.pop('use_vpn')
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ hubs_args = dict(hub_id=dict(type='str'),
+ use_default_route=dict(type='bool'),
+ )
+ subnets_args = dict(local_subnet=dict(type='str'),
+ use_vpn=dict(type='bool'),
+ )
+
+ 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'),
+ hubs=dict(type='list', default=None, elements='dict', options=hubs_args),
+ subnets=dict(type='list', default=None, elements='dict', options=subnets_args),
+ mode=dict(type='str', choices=['none', 'hub', 'spoke']),
+ )
+
+ # 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='site_to_site_vpn')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'site_to_site_vpn': '/networks/{net_id}/appliance/vpn/siteToSiteVpn/'}
+ update_urls = {'site_to_site_vpn': '/networks/{net_id}/appliance/vpn/siteToSiteVpn/'}
+
+ meraki.url_catalog['get_all'].update(query_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']
+ 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:
+ 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':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ payload = assemble_payload(meraki)
+ comparable = deepcopy(original)
+ comparable.update(payload)
+ if meraki.is_update_required(original, payload):
+ if meraki.check_mode is True:
+ meraki.result['changed'] = True
+ meraki.result['data'] = payload
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['changed'] = True
+ meraki.result['data'] = response
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py
new file mode 100644
index 000000000..51aef2656
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py
@@ -0,0 +1,438 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, 2019 Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_static_route
+short_description: Manage static routes in the Meraki cloud
+description:
+- Allows for creation, management, and visibility into static routes within Meraki.
+
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: [ absent, query, present ]
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ name:
+ description:
+ - Descriptive name of the static route.
+ type: str
+ subnet:
+ description:
+ - CIDR notation based subnet for static route.
+ type: str
+ gateway_ip:
+ description:
+ - IP address of the gateway for the subnet.
+ type: str
+ route_id:
+ description:
+ - Unique ID of static route.
+ type: str
+ gateway_vlan_id:
+ description:
+ - The gateway IP (next hop) VLAN ID of the static route.
+ type: int
+ fixed_ip_assignments:
+ description:
+ - List of fixed MAC to IP bindings for DHCP.
+ type: list
+ elements: dict
+ suboptions:
+ mac:
+ description:
+ - MAC address of endpoint.
+ type: str
+ ip:
+ description:
+ - IP address of endpoint.
+ type: str
+ name:
+ description:
+ - Hostname of endpoint.
+ type: str
+ reserved_ip_ranges:
+ description:
+ - List of IP ranges reserved for static IP assignments.
+ type: list
+ elements: dict
+ suboptions:
+ start:
+ description:
+ - First IP address of reserved range.
+ type: str
+ end:
+ description:
+ - Last IP address of reserved range.
+ type: str
+ comment:
+ description:
+ - Human readable description of reservation range.
+ type: str
+ enabled:
+ description:
+ - Indicates whether static route is enabled within a network.
+ type: bool
+
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Create static_route
+ meraki_static_route:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ name: Test Route
+ subnet: 192.0.1.0/24
+ gateway_ip: 192.168.128.1
+ delegate_to: localhost
+
+- name: Update static route with fixed IP assignment
+ meraki_static_route:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ route_id: d6fa4821-1234-4dfa-af6b-ae8b16c20c39
+ fixed_ip_assignments:
+ - mac: aa:bb:cc:dd:ee:ff
+ ip: 192.0.1.11
+ comment: Server
+ delegate_to: localhost
+
+- name: Query static routes
+ meraki_static_route:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ delegate_to: localhost
+
+- name: Delete static routes
+ meraki_static_route:
+ auth_key: abc123
+ state: absent
+ org_name: YourOrg
+ net_name: YourNet
+ route_id: '{{item}}'
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: Information about the created or manipulated object.
+ returned: info
+ type: complex
+ contains:
+ id:
+ description: Unique identification string assigned to each static route.
+ returned: success
+ type: str
+ sample: d6fa4821-1234-4dfa-af6b-ae8b16c20c39
+ net_id:
+ description: Identification string of network.
+ returned: query or update
+ type: str
+ sample: N_12345
+ name:
+ description: Name of static route.
+ returned: success
+ type: str
+ sample: Data Center static route
+ subnet:
+ description: CIDR notation subnet for static route.
+ returned: success
+ type: str
+ sample: 192.0.1.0/24
+ gatewayIp:
+ description: Next hop IP address.
+ returned: success
+ type: str
+ sample: 192.1.1.1
+ enabled:
+ description: Enabled state of static route.
+ returned: query or update
+ type: bool
+ sample: True
+ reservedIpRanges:
+ description: List of IP address ranges which are reserved for static assignment.
+ returned: query or update
+ type: complex
+ contains:
+ start:
+ description: First address in reservation range, inclusive.
+ returned: query or update
+ type: str
+ sample: 192.0.1.2
+ end:
+ description: Last address in reservation range, inclusive.
+ returned: query or update
+ type: str
+ sample: 192.0.1.10
+ comment:
+ description: Human readable description of range.
+ returned: query or update
+ type: str
+ sample: Server range
+ fixedIpAssignments:
+ description: List of static MAC to IP address bindings.
+ returned: query or update
+ type: complex
+ contains:
+ mac:
+ description: Key is MAC address of endpoint.
+ returned: query or update
+ type: complex
+ contains:
+ ip:
+ description: IP address to be bound to the endpoint.
+ returned: query or update
+ type: str
+ sample: 192.0.1.11
+ name:
+ description: Hostname given to the endpoint.
+ returned: query or update
+ type: str
+ sample: JimLaptop
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def fixed_ip_factory(meraki, data):
+ fixed_ips = dict()
+ for item in data:
+ fixed_ips[item["mac"]] = {"ip": item["ip"], "name": item["name"]}
+ return fixed_ips
+
+
+def get_static_routes(meraki, net_id):
+ path = meraki.construct_path("get_all", net_id=net_id)
+ r = meraki.request(path, method="GET")
+ return r
+
+
+def get_static_route(meraki, net_id, route_id):
+ path = meraki.construct_path(
+ "get_one", net_id=net_id, custom={"route_id": route_id}
+ )
+ r = meraki.request(path, method="GET")
+ return r
+
+
+def does_route_exist(name, routes):
+ for route in routes:
+ if name == route["name"]:
+ return route
+ return None
+
+
+def update_dict(original, proposed):
+ for k, v in proposed.items():
+ if v is not None:
+ original[k] = v
+ return original
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ fixed_ip_arg_spec = dict(
+ mac=dict(type="str"),
+ ip=dict(type="str"),
+ name=dict(type="str"),
+ )
+
+ reserved_ip_arg_spec = dict(
+ start=dict(type="str"),
+ end=dict(type="str"),
+ comment=dict(type="str"),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type="str"),
+ net_name=dict(type="str"),
+ name=dict(type="str"),
+ subnet=dict(type="str"),
+ gateway_ip=dict(type="str"),
+ state=dict(
+ type="str", default="present", choices=["absent", "present", "query"]
+ ),
+ fixed_ip_assignments=dict(
+ type="list", elements="dict", options=fixed_ip_arg_spec
+ ),
+ reserved_ip_ranges=dict(
+ type="list", elements="dict", options=reserved_ip_arg_spec
+ ),
+ route_id=dict(type="str"),
+ enabled=dict(type="bool"),
+ gateway_vlan_id=dict(type="int"),
+ )
+
+ # 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="static_route")
+ module.params["follow_redirects"] = "all"
+ payload = None
+
+ query_urls = {"static_route": "/networks/{net_id}/appliance/staticRoutes"}
+ query_one_urls = {
+ "static_route": "/networks/{net_id}/appliance/staticRoutes/{route_id}"
+ }
+ create_urls = {"static_route": "/networks/{net_id}/appliance/staticRoutes/"}
+ update_urls = {
+ "static_route": "/networks/{net_id}/appliance/staticRoutes/{route_id}"
+ }
+ delete_urls = {
+ "static_route": "/networks/{net_id}/appliance/staticRoutes/{route_id}"
+ }
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["get_one"].update(query_one_urls)
+ meraki.url_catalog["create"] = create_urls
+ meraki.url_catalog["update"] = update_urls
+ meraki.url_catalog["delete"] = delete_urls
+
+ if not meraki.params["org_name"] and not meraki.params["org_id"]:
+ meraki.fail_json(
+ msg="Parameters 'org_name' or 'org_id' parameters are required"
+ )
+ if not meraki.params["net_name"] and not meraki.params["net_id"]:
+ meraki.fail_json(
+ msg="Parameters 'net_name' or 'net_id' parameters are required"
+ )
+ if meraki.params["net_name"] and meraki.params["net_id"]:
+ meraki.fail_json(msg="'net_name' and 'net_id' are mutually exclusive")
+
+ # Construct payload
+ if meraki.params["state"] == "present":
+ payload = dict()
+ if meraki.params["net_name"]:
+ payload["name"] = meraki.params["net_name"]
+
+ # 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"]
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ net_id = meraki.params["net_id"]
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params["net_name"], data=nets)
+
+ if meraki.params["state"] == "query":
+ if meraki.params["route_id"] is not None:
+ meraki.result["data"] = get_static_route(
+ meraki, net_id, meraki.params["route_id"]
+ )
+ else:
+ meraki.result["data"] = get_static_routes(meraki, net_id)
+ elif meraki.params["state"] == "present":
+ payload = {
+ "name": meraki.params["name"],
+ "subnet": meraki.params["subnet"],
+ "gatewayIp": meraki.params["gateway_ip"],
+ }
+ if meraki.params["fixed_ip_assignments"] is not None:
+ payload["fixedIpAssignments"] = fixed_ip_factory(
+ meraki, meraki.params["fixed_ip_assignments"]
+ )
+ if meraki.params["reserved_ip_ranges"] is not None:
+ payload["reservedIpRanges"] = meraki.params["reserved_ip_ranges"]
+ if meraki.params["enabled"] is not None:
+ payload["enabled"] = meraki.params["enabled"]
+ if meraki.params["gateway_vlan_id"] is not None:
+ payload["gatewayVlanId"] = meraki.params["gateway_vlan_id"]
+
+ route_id = meraki.params["route_id"]
+ if meraki.params["name"] is not None and route_id is None:
+ route_status = does_route_exist(
+ meraki.params["name"], get_static_routes(meraki, net_id)
+ )
+ if route_status is not None: # Route exists, assign route_id
+ route_id = route_status["id"]
+
+ if route_id is not None:
+ existing_route = get_static_route(meraki, net_id, route_id)
+ original = existing_route.copy()
+ payload = update_dict(existing_route, payload)
+ if module.check_mode:
+ meraki.result["data"] = payload
+ meraki.exit_json(**meraki.result)
+ if meraki.is_update_required(original, payload, optional_ignore=["id"]):
+ path = meraki.construct_path(
+ "update", net_id=net_id, custom={"route_id": route_id}
+ )
+ meraki.result["data"] = meraki.request(
+ path, method="PUT", payload=json.dumps(payload)
+ )
+ meraki.result["changed"] = True
+ else:
+ meraki.result["data"] = original
+ else:
+ if module.check_mode:
+ meraki.result["data"] = payload
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("create", net_id=net_id)
+ meraki.result["data"] = meraki.request(
+ path, method="POST", payload=json.dumps(payload)
+ )
+ meraki.result["changed"] = True
+ elif meraki.params["state"] == "absent":
+ if module.check_mode:
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path(
+ "delete", net_id=net_id, custom={"route_id": meraki.params["route_id"]}
+ )
+ meraki.result["data"] = meraki.request(path, method="DELETE")
+ meraki.result["changed"] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_third_party_vpn_peers.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_third_party_vpn_peers.py
new file mode 100644
index 000000000..c504911c0
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_third_party_vpn_peers.py
@@ -0,0 +1,493 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_third_party_vpn_peers
+short_description: Manage third party (IPSec) VPN peers for MX devices
+description:
+- Create, edit, query, or delete third party VPN peers in a Meraki environment.
+options:
+ state:
+ description:
+ - Specifies whether object should be queried, created/modified, or removed.
+ choices: [absent, present, query]
+ default: query
+ type: str
+ peers:
+ description:
+ - The list of VPN peers.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VPN peer.
+ - Required when state is present.
+ type: str
+ public_ip:
+ description:
+ - The public IP of the VPN peer.
+ - Required when state is present.
+ type: str
+ secret:
+ description:
+ - The shared secret with the VPN peer.
+ - Required when state is present.
+ type: str
+ private_subnets:
+ description:
+ - The list of the private subnets of the VPN peer.
+ - Required when state is present.
+ type: list
+ elements: str
+ ike_version:
+ description:
+ - The IKE version to be used for the IPsec VPN peer configuration.
+ default: "1"
+ type: str
+ choices: ["1", "2"]
+ ipsec_policies_preset:
+ description:
+ - Specifies IPsec preset values. If this is provided, the 'ipsecPolicies' parameter is ignored.
+ type: str
+ choices: ["default", "aws", "azure"]
+ remote_id:
+ description:
+ - The remote ID is used to identify the connecting VPN peer. This can either be a valid IPv4 Address, FQDN or User FQDN.
+ type: str
+ network_tags:
+ description:
+ - A list of network tags that will connect with this peer. If not included, the default is ['all'].
+ type: list
+ elements: str
+ ipsec_policies:
+ description:
+ - Custom IPSec policies for the VPN peer. If not included and a preset has not been chosen, the default preset for IPSec policies will be used.
+ type: dict
+ suboptions:
+ child_lifetime:
+ description:
+ - The lifetime of the Phase 2 SA in seconds.
+ type: int
+ ike_lifetime:
+ description:
+ - The lifetime of the Phase 1 SA in seconds.
+ type: int
+ child_auth_algo:
+ description:
+ - This is the authentication algorithms to be used in Phase 2.
+ type: list
+ elements: str
+ choices: ['sha256', 'sha1', 'md5']
+ child_cipher_algo:
+ description:
+ - This is the cipher algorithms to be used in Phase 2.
+ choices: ['aes256', 'aes192', 'aes128', 'tripledes', 'des', 'null']
+ type: list
+ elements: str
+ child_pfs_group:
+ description:
+ - This is the Diffie-Hellman group to be used for Perfect Forward Secrecy in Phase 2.
+ type: list
+ elements: str
+ choices: ['disabled','group14', 'group5', 'group2', 'group1']
+ ike_auth_algo:
+ description:
+ - This is the authentication algorithm to be used in Phase 1.
+ type: list
+ elements: str
+ choices: ['sha256', 'sha1', 'md5']
+ ike_cipher_algo:
+ description:
+ - This is the cipher algorithm to be used in Phase 1.
+ type: list
+ elements: str
+ choices: ['aes256', 'aes192', 'aes128', 'tripledes', 'des']
+ ike_diffie_hellman_group:
+ description:
+ - This is the Diffie-Hellman group to be used in Phase 1.
+ type: list
+ elements: str
+ choices: ['group14', 'group5', 'group2', 'group1']
+ ike_prf_algo:
+ description:
+ - This is the pseudo-random function to be used in IKE_SA.
+ type: list
+ elements: str
+ choices: ['prfsha256', 'prfsha1', 'prfmd5', 'default']
+
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Query all VPN peers
+ meraki_mx_third_party_vpn_peers:
+ auth_key: abc123
+ state: query
+ org_name: orgName
+
+- name: Create VPN peer with an IPsec policy
+ meraki_mx_third_party_vpn_peers:
+ auth_key: abc123
+ state: present
+ org_name: orgName
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ network_tags:
+ - none
+ remote_id: "192.0.2.0"
+ ipsec_policies:
+ child_lifetime: 600
+ ike_lifetime: 600
+ child_auth_algo:
+ - "md5"
+ child_cipher_algo:
+ - "tripledes"
+ - "aes192"
+ child_pfs_group:
+ - "disabled"
+ ike_auth_algo:
+ - "sha256"
+ ike_cipher_algo:
+ - "tripledes"
+ ike_diffie_hellman_group:
+ - "group2"
+ ike_prf_algo:
+ - "prfmd5"
+"""
+
+RETURN = r"""
+
+response:
+ description: Information about the organization which was created or modified
+ returned: success
+ type: complex
+ contains:
+ appliance_ip:
+ description: IP address of Meraki appliance in the VLAN
+ returned: success
+ type: str
+ sample: 192.0.1.1
+ dnsnamservers:
+ description: IP address or Meraki defined DNS servers which VLAN should use by default
+ returned: success
+ type: str
+ sample: upstream_dns
+ peers:
+ description: The list of VPN peers.
+ returned: success
+ type: complex
+ contains:
+ ike_version:
+ description: The IKE version to be used for the IPsec VPN peer configuration.
+ returned: success
+ type: str
+ sample: "1"
+ ipsec_policies_preset:
+ description: Preconfigured IPsec settings.
+ returned: success
+ type: str
+ sample: "aws"
+ name:
+ description: The name of the VPN peer.
+ returned: success
+ type: str
+ sample: "MyVPNPeer"
+ public_ip:
+ description: The public IP of the VPN peer.
+ returned: success
+ type: str
+ sample: "198.51.100.1"
+ remote_id:
+ description: "The remote ID is used to identify the connecting VPN peer."
+ returned: success
+ type: str
+ sample: "s3cret"
+ network_tags:
+ description: A list of network tags that will connect with this peer.
+ returned: success
+ type: list
+ sample: ["all"]
+ private_subnets:
+ description: The list of the private subnets of the VPN peer.
+ returned: success
+ type: list
+ sample: ["192.0.2.0/24"]
+ ipsec_policies:
+ description: Custom IPSec policies for the VPN peer.
+ returned: success
+ type: complex
+ contains:
+ child_lifetime:
+ description: The lifetime of the Phase 2 SA in seconds.
+ returned: success
+ type: str
+ sample: "60"
+ ike_lifetime:
+ description: The lifetime of the Phase 1 SA in seconds.
+ returned: success
+ type: str
+ sample: "60"
+ child_auth_algo:
+ description: This is the authentication algorithms to be used in Phase 2.
+ returned: success
+ type: list
+ sample: ["sha1"]
+ child_cipher_algo:
+ description: This is the cipher algorithms to be used in Phase 2.
+ returned: success
+ type: list
+ sample: ["aes192"]
+ child_pfs_group:
+ description: This is the Diffie-Hellman group to be used for Perfect Forward Secrecy in Phase 2.
+ returned: success
+ type: list
+ sample: ["group14"]
+ ike_auth_algo:
+ description: This is the authentication algorithm to be used in Phase 1.
+ returned: success
+ type: list
+ sample: ["sha1"]
+ ike_cipher_algo:
+ description: This is the cipher algorithm to be used in Phase 1.
+ returned: success
+ type: list
+ sample: ["aes128"]
+ ike_diffie_hellman_group:
+ description: This is the Diffie-Hellman group to be used in Phase 1.
+ returned: success
+ type: list
+ sample: ["group14"]
+ ike_prf_algo:
+ description: This is the pseudo-random function to be used in IKE_SA.
+ returned: success
+ type: list
+ sample: ["prfmd5"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+import json
+
+
+def validate_payload(meraki):
+ for peer in meraki.params["peers"]:
+ if peer["name"] is None:
+ meraki.fail_json(msg="Peer name must be specified")
+ elif peer["public_ip"] is None:
+ meraki.fail_json(msg="Peer public IP must be specified")
+ elif peer["secret"] is None:
+ meraki.fail_json(msg="Peer secret must be specified")
+ elif peer["private_subnets"] is None:
+ meraki.fail_json(msg="Peer private subnets must be specified")
+
+
+def construct_payload(meraki):
+ validate_payload(meraki)
+ peer_list = []
+ for peer in meraki.params["peers"]:
+ current_peer = dict()
+ current_peer["name"] = peer["name"]
+ current_peer["publicIp"] = peer["public_ip"]
+ current_peer["secret"] = peer["secret"]
+ current_peer["privateSubnets"] = peer["private_subnets"]
+ if peer["ike_version"] is not None:
+ current_peer["ikeVersion"] = peer["ike_version"]
+ if peer["ipsec_policies_preset"] is not None:
+ current_peer["ipsecPoliciesPreset"] = peer["ipsec_policies_preset"]
+ if peer["remote_id"] is not None:
+ current_peer["remoteId"] = peer["remote_id"]
+ if peer["network_tags"] is not None:
+ current_peer["networkTags"] = peer["network_tags"]
+ if peer["ipsec_policies"] is not None:
+ current_peer["ipsecPolicies"] = dict()
+ if peer["ipsec_policies"]["child_lifetime"] is not None:
+ current_peer["ipsecPolicies"]["childLifetime"] = peer["ipsec_policies"][
+ "child_lifetime"
+ ]
+ if peer["ipsec_policies"]["ike_lifetime"] is not None:
+ current_peer["ipsecPolicies"]["ikeLifetime"] = peer["ipsec_policies"][
+ "ike_lifetime"
+ ]
+ if peer["ipsec_policies"]["child_auth_algo"] is not None:
+ current_peer["ipsecPolicies"]["childAuthAlgo"] = peer["ipsec_policies"][
+ "child_auth_algo"
+ ]
+ if peer["ipsec_policies"]["child_cipher_algo"] is not None:
+ current_peer["ipsecPolicies"]["childCipherAlgo"] = peer[
+ "ipsec_policies"
+ ]["child_cipher_algo"]
+ if peer["ipsec_policies"]["child_pfs_group"] is not None:
+ current_peer["ipsecPolicies"]["childPfsGroup"] = peer["ipsec_policies"][
+ "child_pfs_group"
+ ]
+ if peer["ipsec_policies"]["ike_auth_algo"] is not None:
+ current_peer["ipsecPolicies"]["ikeAuthAlgo"] = peer["ipsec_policies"][
+ "ike_auth_algo"
+ ]
+ if peer["ipsec_policies"]["ike_cipher_algo"] is not None:
+ current_peer["ipsecPolicies"]["ikeCipherAlgo"] = peer["ipsec_policies"][
+ "ike_cipher_algo"
+ ]
+ if peer["ipsec_policies"]["ike_diffie_hellman_group"] is not None:
+ current_peer["ipsecPolicies"]["ikeDiffieHellmanGroup"] = peer[
+ "ipsec_policies"
+ ]["ike_diffie_hellman_group"]
+ if peer["ipsec_policies"]["ike_prf_algo"] is not None:
+ current_peer["ipsecPolicies"]["ikePrfAlgo"] = peer["ipsec_policies"][
+ "ike_prf_algo"
+ ]
+
+ peer_list.append(current_peer)
+ payload = {"peers": peer_list}
+ return payload
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ ipsec_policies_arg_spec = dict(
+ child_lifetime=dict(type="int", default=None),
+ ike_lifetime=dict(type="int", default=None),
+ child_auth_algo=dict(
+ type="list", elements="str", default=None, choices=["sha256", "sha1", "md5"]
+ ),
+ child_cipher_algo=dict(
+ type="list",
+ elements="str",
+ default=None,
+ choices=["aes256", "aes192", "aes128", "tripledes", "des", "null"],
+ ),
+ child_pfs_group=dict(
+ type="list",
+ elements="str",
+ default=None,
+ choices=["disabled", "group14", "group5", "group2", "group1"],
+ ),
+ ike_auth_algo=dict(
+ type="list", elements="str", default=None, choices=["sha256", "sha1", "md5"]
+ ),
+ ike_cipher_algo=dict(
+ type="list",
+ elements="str",
+ default=None,
+ choices=["aes256", "aes192", "aes128", "tripledes", "des"],
+ ),
+ ike_diffie_hellman_group=dict(
+ type="list",
+ elements="str",
+ default=None,
+ choices=["group14", "group5", "group2", "group1"],
+ ),
+ ike_prf_algo=dict(
+ type="list",
+ elements="str",
+ default=None,
+ choices=["prfsha256", "prfsha1", "prfmd5", "default"],
+ ),
+ )
+
+ peers_arg_spec = dict(
+ name=dict(type="str"),
+ public_ip=dict(type="str"),
+ secret=dict(type="str", no_log=True),
+ private_subnets=dict(type="list", elements="str"),
+ ike_version=dict(type="str", choices=["1", "2"], default="1"),
+ ipsec_policies_preset=dict(
+ type="str", choices=["default", "aws", "azure"], default=None
+ ),
+ remote_id=dict(type="str", default=None),
+ network_tags=dict(type="list", elements="str", default=None),
+ ipsec_policies=dict(type="dict", options=ipsec_policies_arg_spec, default=None),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ state=dict(type="str", choices=["absent", "present", "query"], default="query"),
+ peers=dict(type="list", elements="dict", options=peers_arg_spec),
+ )
+
+ # 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="third_party_vpn_peer")
+
+ meraki.params["follow_redirects"] = "all"
+
+ query_urls = {
+ "third_party_vpn_peer": "/organizations/{org_id}/appliance/vpn/thirdPartyVPNPeers"
+ }
+ update_url = {
+ "third_party_vpn_peer": "/organizations/{org_id}/appliance/vpn/thirdPartyVPNPeers"
+ }
+
+ meraki.url_catalog["get_all"].update(query_urls)
+ meraki.url_catalog["update"] = update_url
+
+ payload = None
+ if meraki.params["org_id"] is None and meraki.params["org_name"] is None:
+ meraki.fail_json(msg="Organization must be specified via org_name or org_id")
+
+ org_id = meraki.params["org_id"]
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+
+ if meraki.params["state"] == "query":
+ path = meraki.construct_path("get_all", org_id=org_id)
+ response = meraki.request(path, "GET")
+ meraki.result["data"] = response
+ elif meraki.params["state"] == "present":
+ payload = construct_payload(meraki)
+ have = meraki.request(meraki.construct_path("get_all", org_id=org_id), "GET")
+ # meraki.fail_json(msg="Compare", have=have, payload=payload)
+ if meraki.is_update_required(have, payload):
+ meraki.generate_diff(have, payload)
+ path = meraki.construct_path("update", org_id=org_id)
+ if meraki.module.check_mode is False:
+ response = meraki.request(path, "PUT", payload=json.dumps(payload))
+ meraki.result["data"] = response
+ else:
+ meraki.result["data"] = payload
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ meraki.result["data"] = have
+ elif meraki.params["state"] == "absent":
+ return
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink_bandwidth.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink_bandwidth.py
new file mode 100644
index 000000000..cb2ef07e5
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink_bandwidth.py
@@ -0,0 +1,325 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_uplink_bandwidth
+short_description: Manage uplinks on Meraki MX appliances
+version_added: "1.1.0"
+description:
+- Configure and query information about uplinks on Meraki MX appliances.
+notes:
+- Some of the options are likely only used for developers within Meraki.
+- Module was formerly named M(cisco.meraki.meraki_mx_uplink).
+options:
+ state:
+ description:
+ - Specifies whether object should be queried, created/modified, or removed.
+ choices: [absent, present, query]
+ default: query
+ type: str
+ org_name:
+ description:
+ - Name of organization associated to a network.
+ type: str
+ org_id:
+ description:
+ - ID of organization associated to a network.
+ type: str
+ net_name:
+ description:
+ - Name of network which VLAN is in or should be in.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of network which VLAN is in or should be in.
+ type: str
+ wan1:
+ description:
+ - Configuration of WAN1 uplink
+ type: dict
+ suboptions:
+ bandwidth_limits:
+ description:
+ - Structure for configuring bandwidth limits
+ type: dict
+ suboptions:
+ limit_up:
+ description:
+ - Maximum upload speed for interface
+ type: int
+ limit_down:
+ description:
+ - Maximum download speed for interface
+ type: int
+ wan2:
+ description:
+ - Configuration of WAN2 uplink
+ type: dict
+ suboptions:
+ bandwidth_limits:
+ description:
+ - Structure for configuring bandwidth limits
+ type: dict
+ suboptions:
+ limit_up:
+ description:
+ - Maximum upload speed for interface
+ type: int
+ limit_down:
+ description:
+ - Maximum download speed for interface
+ type: int
+ cellular:
+ description:
+ - Configuration of cellular uplink
+ type: dict
+ suboptions:
+ bandwidth_limits:
+ description:
+ - Structure for configuring bandwidth limits
+ type: dict
+ suboptions:
+ limit_up:
+ description:
+ - Maximum upload speed for interface
+ type: int
+ limit_down:
+ description:
+ - Maximum download speed for interface
+ type: int
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Set MX uplink settings
+ meraki_mx_uplink_bandwidth:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ wan1:
+ bandwidth_limits:
+ limit_down: 1000000
+ limit_up: 1000
+ cellular:
+ bandwidth_limits:
+ limit_down: 0
+ limit_up: 0
+ delegate_to: localhost
+
+- name: Query MX uplink settings
+ meraki_mx_uplink_bandwidth:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ delegate_to: localhost
+
+'''
+
+RETURN = r'''
+
+data:
+ description: Information about the organization which was created or modified
+ returned: success
+ type: complex
+ contains:
+ wan1:
+ description: WAN1 interface
+ returned: success
+ type: complex
+ contains:
+ bandwidth_limits:
+ description: Structure for uplink bandwidth limits
+ returned: success
+ type: complex
+ contains:
+ limit_up:
+ description: Upload bandwidth limit
+ returned: success
+ type: int
+ limit_down:
+ description: Download bandwidth limit
+ returned: success
+ type: int
+ wan2:
+ description: WAN2 interface
+ returned: success
+ type: complex
+ contains:
+ bandwidth_limits:
+ description: Structure for uplink bandwidth limits
+ returned: success
+ type: complex
+ contains:
+ limit_up:
+ description: Upload bandwidth limit
+ returned: success
+ type: int
+ limit_down:
+ description: Download bandwidth limit
+ returned: success
+ type: int
+ cellular:
+ description: cellular interface
+ returned: success
+ type: complex
+ contains:
+ bandwidth_limits:
+ description: Structure for uplink bandwidth limits
+ returned: success
+ type: complex
+ contains:
+ limit_up:
+ description: Upload bandwidth limit
+ returned: success
+ type: int
+ limit_down:
+ description: Download bandwidth limit
+ returned: success
+ type: int
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import recursive_diff
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+INT_NAMES = ('wan1', 'wan2', 'cellular')
+
+
+def clean_custom_format(data):
+ for interface in data:
+ if data[interface]['bandwidth_limits']['limit_up'] is None:
+ data[interface]['bandwidth_limits']['limit_up'] = 0
+ if data[interface]['bandwidth_limits']['limit_down'] is None:
+ data[interface]['bandwidth_limits']['limit_down'] = 0
+ return data
+
+
+def meraki_struct_to_custom_format(data):
+ new_struct = {}
+ for interface in INT_NAMES:
+ if interface in data['bandwidthLimits']:
+ new_struct[interface] = {'bandwidth_limits': {'limit_up': data['bandwidthLimits'][interface]['limitUp'],
+ 'limit_down': data['bandwidthLimits'][interface]['limitDown'],
+ }
+ }
+ # return snake_dict_to_camel_dict(new_struct)
+ return new_struct
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ bandwidth_arg_spec = dict(limit_up=dict(type='int'),
+ limit_down=dict(type='int'),
+ )
+
+ interface_arg_spec = dict(bandwidth_limits=dict(type='dict', default=None, options=bandwidth_arg_spec),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'present', 'query'], default='query'),
+ net_name=dict(type='str', aliases=['network']),
+ net_id=dict(type='str'),
+ wan1=dict(type='dict', default=None, options=interface_arg_spec),
+ wan2=dict(type='dict', default=None, options=interface_arg_spec),
+ cellular=dict(type='dict', default=None, options=interface_arg_spec),
+ )
+
+ # 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_uplink')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'mx_uplink': '/networks/{net_id}/appliance/trafficShaping/uplinkBandwidth'}
+ update_bw_url = {'mx_uplink': '/networks/{net_id}/appliance/trafficShaping/uplinkBandwidth'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['update_bw'] = update_bw_url
+
+ payload = dict()
+
+ org_id = meraki.params['org_id']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ data = clean_custom_format(meraki_struct_to_custom_format(response))
+ meraki.result['data'] = data
+ elif meraki.params['state'] == 'present':
+ path = meraki.construct_path('get_all', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ payload = {'bandwidthLimits': {}}
+ for interface in INT_NAMES:
+ if meraki.params[interface] is not None:
+ if meraki.params[interface]['bandwidth_limits'] is not None:
+ payload['bandwidthLimits'][interface] = None
+ payload['bandwidthLimits'][interface] = {'limitUp': meraki.params[interface]['bandwidth_limits']['limit_up'],
+ 'limitDown': meraki.params[interface]['bandwidth_limits']['limit_down'],
+ }
+ if payload['bandwidthLimits'][interface]['limitUp'] == 0:
+ payload['bandwidthLimits'][interface]['limitUp'] = None
+ if payload['bandwidthLimits'][interface]['limitDown'] == 0:
+ payload['bandwidthLimits'][interface]['limitDown'] = None
+ if meraki.is_update_required(original, payload):
+ if meraki.module.check_mode is True:
+ diff = recursive_diff(clean_custom_format(meraki_struct_to_custom_format(original)),
+ clean_custom_format(meraki_struct_to_custom_format(payload)))
+ original.update(payload)
+ meraki.result['data'] = clean_custom_format(meraki_struct_to_custom_format(original))
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1],
+ }
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update_bw', net_id=net_id)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ formatted_original = clean_custom_format(meraki_struct_to_custom_format(original))
+ formatted_response = clean_custom_format(meraki_struct_to_custom_format(response))
+ diff = recursive_diff(formatted_original, formatted_response)
+ meraki.result['diff'] = {'before': diff[0],
+ 'after': diff[1],
+ }
+ meraki.result['data'] = formatted_response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = clean_custom_format(meraki_struct_to_custom_format(original))
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py
new file mode 100644
index 000000000..2b6c8adc6
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py
@@ -0,0 +1,585 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_vlan
+short_description: Manage VLANs in the Meraki cloud
+description:
+- Create, edit, query, or delete VLANs in a Meraki environment.
+notes:
+- Meraki's API will return an error if VLANs aren't enabled on a network. VLANs are returned properly if VLANs are enabled on a network.
+- Some of the options are likely only used for developers within Meraki.
+- Meraki's API defaults to networks having VLAN support disabled and there is no way to enable VLANs support in the API. VLAN support must be enabled manually.
+options:
+ state:
+ description:
+ - Specifies whether object should be queried, created/modified, or removed.
+ choices: [absent, present, query]
+ default: query
+ type: str
+ net_name:
+ description:
+ - Name of network which VLAN is in or should be in.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of network which VLAN is in or should be in.
+ type: str
+ vlan_id:
+ description:
+ - ID number of VLAN.
+ - ID should be between 1-4096.
+ type: int
+ name:
+ description:
+ - Name of VLAN.
+ aliases: [vlan_name]
+ type: str
+ subnet:
+ description:
+ - CIDR notation of network subnet.
+ type: str
+ appliance_ip:
+ description:
+ - IP address of appliance.
+ - Address must be within subnet specified in C(subnet) parameter.
+ type: str
+ dns_nameservers:
+ description:
+ - Semi-colon delimited list of DNS IP addresses.
+ - Specify one of the following options for preprogrammed DNS entries opendns, google_dns, upstream_dns
+ type: str
+ reserved_ip_range:
+ description:
+ - IP address ranges which should be reserve and not distributed via DHCP.
+ type: list
+ elements: dict
+ suboptions:
+ start:
+ description: First IP address of reserved IP address range, inclusive.
+ type: str
+ end:
+ description: Last IP address of reserved IP address range, inclusive.
+ type: str
+ comment:
+ description: Description of IP addresses reservation
+ type: str
+ vpn_nat_subnet:
+ description:
+ - The translated VPN subnet if VPN and VPN subnet translation are enabled on the VLAN.
+ type: str
+ fixed_ip_assignments:
+ description:
+ - Static IP address assignments to be distributed via DHCP by MAC address.
+ type: list
+ elements: dict
+ suboptions:
+ mac:
+ description: MAC address for fixed IP assignment binding.
+ type: str
+ ip:
+ description: IP address for fixed IP assignment binding.
+ type: str
+ name:
+ description: Descriptive name of IP assignment binding.
+ type: str
+ dhcp_handling:
+ description:
+ - How to handle DHCP packets on network.
+ type: str
+ choices: ['Run a DHCP server',
+ 'Relay DHCP to another server',
+ 'Do not respond to DHCP requests',
+ 'none',
+ 'server',
+ 'relay']
+ dhcp_relay_server_ips:
+ description:
+ - IP addresses to forward DHCP packets to.
+ type: list
+ elements: str
+ dhcp_lease_time:
+ description:
+ - DHCP lease timer setting
+ type: str
+ choices: ['30 minutes',
+ '1 hour',
+ '4 hours',
+ '12 hours',
+ '1 day',
+ '1 week']
+ dhcp_boot_options_enabled:
+ description:
+ - Enable DHCP boot options
+ type: bool
+ dhcp_boot_next_server:
+ description:
+ - DHCP boot option to direct boot clients to the server to load boot file from.
+ type: str
+ dhcp_boot_filename:
+ description:
+ - Filename to boot from for DHCP boot
+ type: str
+ dhcp_options:
+ description:
+ - List of DHCP option values
+ type: list
+ elements: dict
+ suboptions:
+ code:
+ description:
+ - DHCP option number.
+ type: int
+ type:
+ description:
+ - Type of value for DHCP option.
+ type: str
+ choices: ['text', 'ip', 'hex', 'integer']
+ value:
+ description:
+ - Value for DHCP option.
+ type: str
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query all VLANs in a network.
+ meraki_vlan:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ state: query
+ delegate_to: localhost
+
+- name: Query information about a single VLAN by ID.
+ meraki_vlan:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ vlan_id: 2
+ state: query
+ delegate_to: localhost
+
+- name: Create a VLAN.
+ meraki_vlan:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.0.1.0/24
+ appliance_ip: 192.0.1.1
+ delegate_to: localhost
+
+- name: Update a VLAN.
+ meraki_vlan:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.0.1.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: opendns
+ delegate_to: localhost
+
+- name: Enable DHCP on VLAN with options
+ meraki_vlan:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ dhcp_handling: server
+ dhcp_lease_time: 1 hour
+ dhcp_boot_options_enabled: false
+ dhcp_options:
+ - code: 5
+ type: ip
+ value: 192.0.1.1
+ delegate_to: localhost
+
+- name: Delete a VLAN.
+ meraki_vlan:
+ auth_key: abc12345
+ org_name: YourOrg
+ net_name: YourNet
+ state: absent
+ vlan_id: 2
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+
+response:
+ description: Information about the organization which was created or modified
+ returned: success
+ type: complex
+ contains:
+ appliance_ip:
+ description: IP address of Meraki appliance in the VLAN
+ returned: success
+ type: str
+ sample: 192.0.1.1
+ dnsnamservers:
+ description: IP address or Meraki defined DNS servers which VLAN should use by default
+ returned: success
+ type: str
+ sample: upstream_dns
+ fixed_ip_assignments:
+ description: List of MAC addresses which have IP addresses assigned.
+ returned: success
+ type: complex
+ contains:
+ macaddress:
+ description: MAC address which has IP address assigned to it. Key value is the actual MAC address.
+ returned: success
+ type: complex
+ contains:
+ ip:
+ description: IP address which is assigned to the MAC address.
+ returned: success
+ type: str
+ sample: 192.0.1.4
+ name:
+ description: Descriptive name for binding.
+ returned: success
+ type: str
+ sample: fixed_ip
+ reserved_ip_ranges:
+ description: List of IP address ranges which are reserved for static assignment.
+ returned: success
+ type: complex
+ contains:
+ comment:
+ description: Description for IP address reservation.
+ returned: success
+ type: str
+ sample: reserved_range
+ end:
+ description: Last IP address in reservation range.
+ returned: success
+ type: str
+ sample: 192.0.1.10
+ start:
+ description: First IP address in reservation range.
+ returned: success
+ type: str
+ sample: 192.0.1.5
+ id:
+ description: VLAN ID number.
+ returned: success
+ type: int
+ sample: 2
+ name:
+ description: Descriptive name of VLAN.
+ returned: success
+ type: str
+ sample: TestVLAN
+ networkId:
+ description: ID number of Meraki network which VLAN is associated to.
+ returned: success
+ type: str
+ sample: N_12345
+ subnet:
+ description: CIDR notation IP subnet of VLAN.
+ returned: success
+ type: str
+ sample: "192.0.1.0/24"
+ dhcp_handling:
+ description: Status of DHCP server on VLAN.
+ returned: success
+ type: str
+ sample: Run a DHCP server
+ dhcp_lease_time:
+ description: DHCP lease time when server is active.
+ returned: success
+ type: str
+ sample: 1 day
+ dhcp_boot_options_enabled:
+ description: Whether DHCP boot options are enabled.
+ returned: success
+ type: bool
+ sample: no
+ dhcp_boot_next_server:
+ description: DHCP boot option to direct boot clients to the server to load the boot file from.
+ returned: success
+ type: str
+ sample: 192.0.1.2
+ dhcp_boot_filename:
+ description: Filename for boot file.
+ returned: success
+ type: str
+ sample: boot.txt
+ dhcp_options:
+ description: DHCP options.
+ returned: success
+ type: complex
+ contains:
+ code:
+ description:
+ - Code for DHCP option.
+ - Integer between 2 and 254.
+ returned: success
+ type: int
+ sample: 43
+ type:
+ description:
+ - Type for DHCP option.
+ - Choices are C(text), C(ip), C(hex), C(integer).
+ returned: success
+ type: str
+ sample: text
+ value:
+ description: Value for the DHCP option.
+ returned: success
+ type: str
+ sample: 192.0.1.2
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+import json
+
+
+def fixed_ip_factory(meraki, data):
+ fixed_ips = dict()
+ for item in data:
+ fixed_ips[item['mac']] = {'ip': item['ip'], 'name': item['name']}
+ return fixed_ips
+
+
+def get_vlans(meraki, net_id):
+ path = meraki.construct_path('get_all', net_id=net_id)
+ return meraki.request(path, method='GET')
+
+
+# TODO: Allow method to return actual item if True to reduce number of calls needed
+def is_vlan_valid(meraki, net_id, vlan_id):
+ vlans = get_vlans(meraki, net_id)
+ for vlan in vlans:
+ if vlan_id == vlan['id']:
+ return True
+ return False
+
+
+def construct_payload(meraki):
+ payload = {'id': meraki.params['vlan_id'],
+ 'name': meraki.params['name'],
+ 'subnet': meraki.params['subnet'],
+ 'applianceIp': meraki.params['appliance_ip'],
+ }
+ if meraki.params['dns_nameservers']:
+ if meraki.params['dns_nameservers'] not in ('opendns', 'google_dns', 'upstream_dns'):
+ payload['dnsNameservers'] = format_dns(meraki.params['dns_nameservers'])
+ else:
+ payload['dnsNameservers'] = meraki.params['dns_nameservers']
+ if meraki.params['fixed_ip_assignments']:
+ payload['fixedIpAssignments'] = fixed_ip_factory(meraki, meraki.params['fixed_ip_assignments'])
+ if meraki.params['reserved_ip_range']:
+ payload['reservedIpRanges'] = meraki.params['reserved_ip_range']
+ if meraki.params['vpn_nat_subnet']:
+ payload['vpnNatSubnet'] = meraki.params['vpn_nat_subnet']
+ if meraki.params['dhcp_handling']:
+ payload['dhcpHandling'] = normalize_dhcp_handling(meraki.params['dhcp_handling'])
+ if meraki.params['dhcp_relay_server_ips']:
+ payload['dhcpRelayServerIps'] = meraki.params['dhcp_relay_server_ips']
+ if meraki.params['dhcp_lease_time']:
+ payload['dhcpLeaseTime'] = meraki.params['dhcp_lease_time']
+ if meraki.params['dhcp_boot_options_enabled']:
+ payload['dhcpBootOptionsEnabled'] = meraki.params['dhcp_boot_options_enabled']
+ if meraki.params['dhcp_boot_next_server']:
+ payload['dhcpBootNextServer'] = meraki.params['dhcp_boot_next_server']
+ if meraki.params['dhcp_boot_filename']:
+ payload['dhcpBootFilename'] = meraki.params['dhcp_boot_filename']
+ if meraki.params['dhcp_options']:
+ payload['dhcpOptions'] = meraki.params['dhcp_options']
+ # if meraki.params['dhcp_handling']:
+ # meraki.fail_json(payload)
+
+ return payload
+
+
+def format_dns(nameservers):
+ return nameservers.replace(';', '\n')
+
+
+def normalize_dhcp_handling(parameter):
+ if parameter == 'none':
+ return 'Do not respond to DHCP requests'
+ elif parameter == 'server':
+ return 'Run a DHCP server'
+ elif parameter == 'relay':
+ return 'Relay DHCP to another server'
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ fixed_ip_arg_spec = dict(mac=dict(type='str'),
+ ip=dict(type='str'),
+ name=dict(type='str'),
+ )
+
+ reserved_ip_arg_spec = dict(start=dict(type='str'),
+ end=dict(type='str'),
+ comment=dict(type='str'),
+ )
+
+ dhcp_options_arg_spec = dict(code=dict(type='int'),
+ type=dict(type='str', choices=['text', 'ip', 'hex', 'integer']),
+ value=dict(type='str'),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['absent', 'present', 'query'], default='query'),
+ net_name=dict(type='str', aliases=['network']),
+ net_id=dict(type='str'),
+ vlan_id=dict(type='int'),
+ name=dict(type='str', aliases=['vlan_name']),
+ subnet=dict(type='str'),
+ appliance_ip=dict(type='str'),
+ fixed_ip_assignments=dict(type='list', default=None, elements='dict', options=fixed_ip_arg_spec),
+ reserved_ip_range=dict(type='list', default=None, elements='dict', options=reserved_ip_arg_spec),
+ vpn_nat_subnet=dict(type='str'),
+ dns_nameservers=dict(type='str'),
+ dhcp_handling=dict(type='str', choices=['Run a DHCP server',
+ 'Relay DHCP to another server',
+ 'Do not respond to DHCP requests',
+ 'none',
+ 'server',
+ 'relay'],
+ ),
+ dhcp_relay_server_ips=dict(type='list', default=None, elements='str'),
+ dhcp_lease_time=dict(type='str', choices=['30 minutes',
+ '1 hour',
+ '4 hours',
+ '12 hours',
+ '1 day',
+ '1 week']),
+ dhcp_boot_options_enabled=dict(type='bool'),
+ dhcp_boot_next_server=dict(type='str'),
+ dhcp_boot_filename=dict(type='str'),
+ dhcp_options=dict(type='list', default=None, elements='dict', options=dhcp_options_arg_spec),
+ )
+
+ # 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='vlan')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'vlan': '/networks/{net_id}/appliance/vlans'}
+ query_url = {'vlan': '/networks/{net_id}/appliance/vlans/{vlan_id}'}
+ create_url = {'vlan': '/networks/{net_id}/appliance/vlans'}
+ update_url = {'vlan': '/networks/{net_id}/appliance/vlans/'}
+ delete_url = {'vlan': '/networks/{net_id}/appliance/vlans/'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['get_one'].update(query_url)
+ meraki.url_catalog['create'] = create_url
+ meraki.url_catalog['update'] = update_url
+ meraki.url_catalog['delete'] = delete_url
+
+ payload = None
+
+ org_id = meraki.params['org_id']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'query':
+ if not meraki.params['vlan_id']:
+ meraki.result['data'] = get_vlans(meraki, net_id)
+ else:
+ path = meraki.construct_path('get_one', net_id=net_id, custom={'vlan_id': meraki.params['vlan_id']})
+ response = meraki.request(path, method='GET')
+ meraki.result['data'] = response
+ elif meraki.params['state'] == 'present':
+ payload = construct_payload(meraki)
+ if is_vlan_valid(meraki, net_id, meraki.params['vlan_id']) is False: # Create new VLAN
+ if meraki.module.check_mode is True:
+ meraki.result['data'] = payload
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('create', net_id=net_id)
+ response = meraki.request(path, method='POST', payload=json.dumps(payload))
+ meraki.result['changed'] = True
+ meraki.result['data'] = response
+ else: # Update existing VLAN
+ path = meraki.construct_path('get_one', net_id=net_id, custom={'vlan_id': meraki.params['vlan_id']})
+ original = meraki.request(path, method='GET')
+ ignored = ['networkId']
+ if meraki.is_update_required(original, payload, optional_ignore=ignored):
+ meraki.generate_diff(original, payload)
+ if meraki.module.check_mode is True:
+ original.update(payload)
+ meraki.result['changed'] = True
+ meraki.result['data'] = original
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('update', net_id=net_id) + str(meraki.params['vlan_id'])
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ meraki.result['changed'] = True
+ meraki.result['data'] = response
+ meraki.generate_diff(original, response)
+ else:
+ if meraki.module.check_mode is True:
+ meraki.result['data'] = original
+ meraki.exit_json(**meraki.result)
+ meraki.result['data'] = original
+ elif meraki.params['state'] == 'absent':
+ if is_vlan_valid(meraki, net_id, meraki.params['vlan_id']):
+ if meraki.module.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('delete', net_id=net_id) + str(meraki.params['vlan_id'])
+ response = meraki.request(path, 'DELETE')
+ meraki.result['changed'] = True
+ meraki.result['data'] = response
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_network.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_network.py
new file mode 100644
index 000000000..84e5a2fc9
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_network.py
@@ -0,0 +1,469 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, 2019 Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_network
+short_description: Manage networks in the Meraki cloud
+description:
+- Allows for creation, management, and visibility into networks within Meraki.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: [ absent, present, query ]
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [ name, network ]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ type:
+ description:
+ - Type of network device network manages.
+ - Required when creating a network.
+ - As of Ansible 2.8, C(combined) type is no longer accepted.
+ - As of Ansible 2.8, changes to this parameter are no longer idempotent.
+ choices: [ appliance, switch, wireless, sensor, systemsManager, camera, cellularGateway ]
+ aliases: [ net_type ]
+ type: list
+ elements: str
+ tags:
+ type: list
+ elements: str
+ description:
+ - List of tags to assign to network.
+ - C(tags) name conflicts with the tags parameter in Ansible. Indentation problems may cause unexpected behaviors.
+ - Ansible 2.8 converts this to a list from a comma separated list.
+ timezone:
+ description:
+ - Timezone associated to network.
+ - See U(https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for a list of valid timezones.
+ type: str
+ enable_vlans:
+ description:
+ - Boolean value specifying whether VLANs should be supported on a network.
+ - Requires C(net_name) or C(net_id) to be specified.
+ type: bool
+ local_status_page_enabled:
+ description: >
+ - This no longer works and will likely be moved to a separate module.
+ - Enables the local device status pages (U[my.meraki.com](my.meraki.com), U[ap.meraki.com](ap.meraki.com), U[switch.meraki.com](switch.meraki.com),
+ U[wired.meraki.com](wired.meraki.com)).
+ - Only can be specified on its own or with C(remote_status_page_enabled).
+ type: bool
+ remote_status_page_enabled:
+ description:
+ - This no longer works and will likely be moved to a separate module.
+ - Enables access to the device status page (U(http://device LAN IP)).
+ - Can only be set if C(local_status_page_enabled:) is set to C(yes).
+ - Only can be specified on its own or with C(local_status_page_enabled).
+ type: bool
+ copy_from_network_id:
+ description:
+ - New network inherits properties from this network ID.
+ - Other provided parameters will override the copied configuration.
+ - Type which must match this network's type exactly.
+ type: str
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- delegate_to: localhost
+ block:
+ - name: List all networks associated to the YourOrg organization
+ meraki_network:
+ auth_key: abc12345
+ state: query
+ org_name: YourOrg
+ - name: Query network named MyNet in the YourOrg organization
+ meraki_network:
+ auth_key: abc12345
+ state: query
+ org_name: YourOrg
+ net_name: MyNet
+ - name: Create network named MyNet in the YourOrg organization
+ meraki_network:
+ auth_key: abc12345
+ state: present
+ org_name: YourOrg
+ net_name: MyNet
+ type: switch
+ timezone: America/Chicago
+ tags: production, chicago
+ - name: Create combined network named MyNet in the YourOrg organization
+ meraki_network:
+ auth_key: abc12345
+ state: present
+ org_name: YourOrg
+ net_name: MyNet
+ type:
+ - switch
+ - appliance
+ timezone: America/Chicago
+ tags: production, chicago
+ - name: Create new network based on an existing network
+ meraki_network:
+ auth_key: abc12345
+ state: present
+ org_name: YourOrg
+ net_name: MyNet
+ type:
+ - switch
+ - appliance
+ copy_from_network_id: N_1234
+ - name: Enable VLANs on a network
+ meraki_network:
+ auth_key: abc12345
+ state: query
+ org_name: YourOrg
+ net_name: MyNet
+ enable_vlans: yes
+ - name: Modify local status page enabled state
+ meraki_network:
+ auth_key: abc12345
+ state: query
+ org_name: YourOrg
+ net_name: MyNet
+ local_status_page_enabled: yes
+"""
+
+RETURN = r"""
+data:
+ description: Information about the created or manipulated object.
+ returned: info
+ type: complex
+ contains:
+ id:
+ description: Identification string of network.
+ returned: success
+ type: str
+ sample: N_12345
+ name:
+ description: Written name of network.
+ returned: success
+ type: str
+ sample: YourNet
+ organization_id:
+ description: Organization ID which owns the network.
+ returned: success
+ type: str
+ sample: 0987654321
+ tags:
+ description: Space delimited tags assigned to network.
+ returned: success
+ type: list
+ sample: ['production']
+ time_zone:
+ description: Timezone where network resides.
+ returned: success
+ type: str
+ sample: America/Chicago
+ type:
+ description: Functional type of network.
+ returned: success
+ type: list
+ sample: ['switch']
+ local_status_page_enabled:
+ description: States whether U(my.meraki.com) and other device portals should be enabled.
+ returned: success
+ type: bool
+ sample: true
+ remote_status_page_enabled:
+ description: Enables access to the device status page.
+ returned: success
+ type: bool
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def is_net_valid(data, net_name=None, net_id=None):
+ if net_name is None and net_id is None:
+ return False
+ for n in data:
+ if net_name:
+ if n["name"] == net_name:
+ return True
+ elif net_id:
+ if n["id"] == net_id:
+ return True
+ return False
+
+
+def get_network_settings(meraki, net_id):
+ path = meraki.construct_path("get_settings", net_id=net_id)
+ response = meraki.request(path, method="GET")
+ return response
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type="str"),
+ type=dict(
+ type="list",
+ elements="str",
+ choices=[
+ "wireless",
+ "switch",
+ "appliance",
+ "sensor",
+ "systemsManager",
+ "camera",
+ "cellularGateway",
+ ],
+ aliases=["net_type"],
+ ),
+ tags=dict(type="list", elements="str"),
+ timezone=dict(type="str"),
+ net_name=dict(type="str", aliases=["name", "network"]),
+ state=dict(
+ type="str", choices=["present", "query", "absent"], default="present"
+ ),
+ enable_vlans=dict(type="bool"),
+ local_status_page_enabled=dict(type="bool"),
+ remote_status_page_enabled=dict(type="bool"),
+ copy_from_network_id=dict(type="str"),
+ )
+
+ # 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="network")
+ module.params["follow_redirects"] = "all"
+ payload = None
+
+ create_urls = {"network": "/organizations/{org_id}/networks"}
+ update_urls = {"network": "/networks/{net_id}"}
+ delete_urls = {"network": "/networks/{net_id}"}
+ update_settings_urls = {"network": "/networks/{net_id}/settings"}
+ get_settings_urls = {"network": "/networks/{net_id}/settings"}
+ enable_vlans_urls = {"network": "/networks/{net_id}/appliance/vlans/settings"}
+ get_vlan_status_urls = {"network": "/networks/{net_id}/appliance/vlans/settings"}
+ meraki.url_catalog["create"] = create_urls
+ meraki.url_catalog["update"] = update_urls
+ meraki.url_catalog["update_settings"] = update_settings_urls
+ meraki.url_catalog["get_settings"] = get_settings_urls
+ meraki.url_catalog["delete"] = delete_urls
+ meraki.url_catalog["enable_vlans"] = enable_vlans_urls
+ meraki.url_catalog["status_vlans"] = get_vlan_status_urls
+
+ if not meraki.params["org_name"] and not meraki.params["org_id"]:
+ meraki.fail_json(msg="org_name or org_id parameters are required")
+ if meraki.params["state"] != "query":
+ if not meraki.params["net_name"] and not meraki.params["net_id"]:
+ meraki.fail_json(
+ msg="net_name or net_id is required for present or absent states"
+ )
+ if meraki.params["net_name"] and meraki.params["net_id"]:
+ meraki.fail_json(msg="net_name and net_id are mutually exclusive")
+ if not meraki.params["net_name"] and not meraki.params["net_id"]:
+ if meraki.params["enable_vlans"]:
+ meraki.fail_json(
+ msg="The parameter 'enable_vlans' requires 'net_name' or 'net_id' to be specified"
+ )
+ if (
+ meraki.params["local_status_page_enabled"] is False
+ and meraki.params["remote_status_page_enabled"] is True
+ ):
+ meraki.fail_json(
+ msg="local_status_page_enabled must be true when setting remote_status_page_enabled"
+ )
+
+ # Construct payload
+ if meraki.params["state"] == "present":
+ payload = dict()
+ if meraki.params["net_name"]:
+ payload["name"] = meraki.params["net_name"]
+ if meraki.params["type"]:
+ payload["productTypes"] = meraki.params["type"]
+ if meraki.params["tags"]:
+ payload["tags"] = meraki.params["tags"]
+ if meraki.params["timezone"]:
+ payload["timeZone"] = meraki.params["timezone"]
+ if meraki.params["local_status_page_enabled"] is not None:
+ payload["localStatusPageEnabled"] = meraki.params[
+ "local_status_page_enabled"
+ ]
+ if meraki.params["remote_status_page_enabled"] is not None:
+ payload["remoteStatusPageEnabled"] = meraki.params[
+ "remote_status_page_enabled"
+ ]
+ if meraki.params["copy_from_network_id"] is not None:
+ payload["copyFromNetworkId"] = meraki.params["copy_from_network_id"]
+
+ # 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"]
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.params["net_id"]
+ net_exists = False
+ if net_id is not None:
+ if is_net_valid(nets, net_id=net_id) is False:
+ meraki.fail_json(msg="Network specified by net_id does not exist.")
+ net_exists = True
+ elif meraki.params["net_name"]:
+ if is_net_valid(nets, net_name=meraki.params["net_name"]) is True:
+ net_id = meraki.get_net_id(net_name=meraki.params["net_name"], data=nets)
+ net_exists = True
+
+ if meraki.params["state"] == "query":
+ if not meraki.params["net_name"] and not meraki.params["net_id"]:
+ meraki.result["data"] = nets
+ elif meraki.params["net_name"] or meraki.params["net_id"] is not None:
+ if (
+ meraki.params["local_status_page_enabled"] is not None
+ or meraki.params["remote_status_page_enabled"] is not None
+ ):
+ meraki.result["data"] = get_network_settings(meraki, net_id)
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result["data"] = meraki.get_net(
+ meraki.params["org_name"], net_name=meraki.params["net_name"], data=nets, net_id=meraki.params["net_id"],
+ )
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["state"] == "present":
+ if net_exists is False: # Network needs to be created
+ if "type" not in meraki.params or meraki.params["type"] is None:
+ meraki.fail_json(
+ msg="type parameter is required when creating a network."
+ )
+ if meraki.check_mode is True:
+ data = payload
+ data["id"] = "N_12345"
+ data["organization_id"] = org_id
+ meraki.result["data"] = data
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("create", org_id=org_id)
+ r = meraki.request(path, method="POST", payload=json.dumps(payload))
+ if meraki.status == 201:
+ meraki.result["data"] = r
+ meraki.result["changed"] = True
+ else: # Network exists, make changes
+ if meraki.params["enable_vlans"] is not None: # Modify VLANs configuration
+ status_path = meraki.construct_path("status_vlans", net_id=net_id)
+ status = meraki.request(status_path, method="GET")
+ payload = {"vlansEnabled": meraki.params["enable_vlans"]}
+ if meraki.is_update_required(status, payload):
+ if meraki.check_mode is True:
+ data = {
+ "vlansEnabled": meraki.params["enable_vlans"],
+ "network_id": net_id,
+ }
+ meraki.result["data"] = data
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("enable_vlans", net_id=net_id)
+ r = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result["data"] = r
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result["data"] = status
+ meraki.exit_json(**meraki.result)
+ elif (
+ meraki.params["local_status_page_enabled"] is not None
+ or meraki.params["remote_status_page_enabled"] is not None
+ ):
+ path = meraki.construct_path("get_settings", net_id=net_id)
+ original = meraki.request(path, method="GET")
+ payload = {}
+ if meraki.params["local_status_page_enabled"] is not None:
+ payload["localStatusPageEnabled"] = meraki.params[
+ "local_status_page_enabled"
+ ]
+ if meraki.params["remote_status_page_enabled"] is not None:
+ payload["remoteStatusPageEnabled"] = meraki.params[
+ "remote_status_page_enabled"
+ ]
+ if meraki.is_update_required(original, payload):
+ if meraki.check_mode is True:
+ original.update(payload)
+ meraki.result["data"] = original
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("update_settings", net_id=net_id)
+ response = meraki.request(
+ path, method="PUT", payload=json.dumps(payload)
+ )
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ else:
+ meraki.result["data"] = original
+ meraki.exit_json(**meraki.result)
+ net = meraki.get_net(meraki.params["org_name"], net_id=net_id, data=nets)
+ if meraki.is_update_required(net, payload):
+ if meraki.check_mode is True:
+ data = net
+ net.update(payload)
+ meraki.result["data"] = net
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("update", net_id=net_id)
+ r = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result["data"] = r
+ meraki.result["changed"] = True
+ else:
+ meraki.result["data"] = net
+ elif meraki.params["state"] == "absent":
+ if is_net_valid(nets, net_id=net_id) is True:
+ if meraki.check_mode is True:
+ meraki.result["data"] = {}
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("delete", net_id=net_id)
+ r = meraki.request(path, method="DELETE")
+ if meraki.status == 204:
+ meraki.result["changed"] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_network_settings.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_network_settings.py
new file mode 100644
index 000000000..098b7729d
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_network_settings.py
@@ -0,0 +1,337 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023 Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_network_settings
+short_description: Manage the settings of networks in the Meraki cloud
+description:
+- Allows for management of settings of networks within Meraki.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ choices: [present, query]
+ type: str
+ default: query
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [ name, network ]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ local_status_page_enabled:
+ description: >
+ - Enables the local device status pages (U[my.meraki.com](my.meraki.com), U[ap.meraki.com](ap.meraki.com), U[switch.meraki.com](switch.meraki.com),
+ U[wired.meraki.com](wired.meraki.com)).
+ - Only can be specified on its own or with C(remote_status_page_enabled).
+ type: bool
+ remote_status_page_enabled:
+ description:
+ - Enables access to the device status page (U(http://device LAN IP)).
+ - Can only be set if C(local_status_page_enabled:) is set to C(yes).
+ - Only can be specified on its own or with C(local_status_page_enabled).
+ type: bool
+ local_status_page:
+ description:
+ - Configuration stanza of the local status page.
+ type: dict
+ suboptions:
+ authentication:
+ description:
+ - Local status page authentication settings.
+ type: dict
+ suboptions:
+ enabled:
+ description:
+ - Set whether local status page authentication is enabled.
+ type: bool
+ password:
+ description:
+ - Set password on local status page.
+ type: str
+ secure_port:
+ description:
+ - Configuration of SecureConnect options applied to the network.
+ type: dict
+ suboptions:
+ enabled:
+ description:
+ - Set whether SecureConnect is enabled on the network.
+ type: bool
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+ - name: Get network settings
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ delegate_to: localhost
+
+ - name: Update network settings
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: false
+ delegate_to: localhost
+
+ - name: Enable password on local page
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: true
+ local_status_page:
+ authentication:
+ enabled: true
+ password: abc123
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: Information about the created or manipulated object.
+ returned: info
+ type: complex
+ contains:
+ local_status_page_enabled:
+ description: States whether U(my.meraki.com) and other device portals should be enabled.
+ returned: success
+ type: bool
+ sample: true
+ remote_status_page_enabled:
+ description: Enables access to the device status page.
+ returned: success
+ type: bool
+ sample: true
+ expire_data_older_than:
+ description: The number of days, weeks, or months in Epoch time to expire the data before
+ returned: success
+ type: int
+ sample: 1234
+ fips:
+ description: A hash of FIPS options applied to the Network.
+ returned: success
+ type: complex
+ contains:
+ enabled:
+ description: Enables/disables FIPS on the network.
+ returned: success
+ type: bool
+ sample: true
+ local_status_page:
+ description: A hash of Local Status Page(s) authentication options applied to the Network.
+ returned: success
+ type: complex
+ contains:
+ authentication:
+ description: A hash of Local Status Pages' authentication options applied to the Network.
+ type: complex
+ contains:
+ username:
+ description: The username used for Local Status Pages.
+ type: str
+ returned: success
+ sample: admin
+ enabled:
+ description: Enables/Disables the authenticaiton on Local Status Pages.
+ type: bool
+ returned: success
+ sample: true
+ secure_port:
+ description: A hash of SecureConnect options applied to the Network.
+ type: complex
+ contains:
+ enabled:
+ description: Enables/disables SecureConnect on the network.
+ type: bool
+ returned: success
+ sample: true
+ named_vlans:
+ description: A hash of Named VLANs options applied to the Network.
+ type: complex
+ contains:
+ enabled:
+ description: Enables/disables Named VLANs on the network.
+ type: bool
+ returned: success
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def is_net_valid(data, net_name=None, net_id=None):
+ if net_name is None and net_id is None:
+ return False
+ for n in data:
+ if net_name:
+ if n["name"] == net_name:
+ return True
+ elif net_id:
+ if n["id"] == net_id:
+ return True
+ return False
+
+
+def get_network_settings(meraki, net_id):
+ path = meraki.construct_path("get_settings", net_id=net_id)
+ response = meraki.request(path, method="GET")
+ return response
+
+
+def construct_payload(params):
+ payload = dict()
+ if params["local_status_page_enabled"] is not None:
+ payload["localStatusPageEnabled"] = params["local_status_page_enabled"]
+ if params["remote_status_page_enabled"] is not None:
+ payload["remoteStatusPageEnabled"] = params["remote_status_page_enabled"]
+ if params["local_status_page"] is not None:
+ payload["localStatusPage"] = dict()
+ if params["local_status_page"]["authentication"] is not None:
+ payload["localStatusPage"]["authentication"] = {}
+ if params["local_status_page"]["authentication"]["enabled"] is not None:
+ payload["localStatusPage"]["authentication"]["enabled"] = params["local_status_page"]["authentication"]["enabled"]
+ if params["local_status_page"]["authentication"]["password"] is not None:
+ payload["localStatusPage"]["authentication"]["password"] = params["local_status_page"]["authentication"]["password"]
+ if params["secure_port"] is not None:
+ payload["securePort"] = dict()
+ if params["secure_port"]["enabled"] is not None:
+ payload["securePort"]["enabled"] = params["secure_port"]["enabled"]
+ return payload
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ auth_args = dict(
+ enabled=dict(type="bool"),
+ password=dict(type="str", no_log=True),
+ )
+
+ local_status_page_args = dict(
+ authentication=dict(type="dict", default=None, options=auth_args),
+ )
+
+ secure_port_args = dict(
+ enabled=dict(type="bool"),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ state=dict(type="str", choices=["query", "present"], default="query"),
+ net_name=dict(type="str", aliases=["name", "network"]),
+ net_id=dict(type="str"),
+ local_status_page_enabled=dict(type="bool"),
+ remote_status_page_enabled=dict(type="bool"),
+ local_status_page=dict(type="dict", default=None, options=local_status_page_args),
+ secure_port=dict(type="dict", default=None, options=secure_port_args)
+ )
+
+ # 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="network")
+ module.params["follow_redirects"] = "all"
+ payload = None
+
+ update_settings_urls = {"network": "/networks/{net_id}/settings"}
+ get_settings_urls = {"network": "/networks/{net_id}/settings"}
+ meraki.url_catalog["update_settings"] = update_settings_urls
+ meraki.url_catalog["get_settings"] = get_settings_urls
+
+ if not meraki.params["org_name"] and not meraki.params["org_id"]:
+ meraki.fail_json(msg="org_name or org_id parameters are required")
+ if meraki.params["state"] != "query":
+ if not meraki.params["net_name"] and not meraki.params["net_id"]:
+ meraki.fail_json(
+ msg="net_name or net_id is required for present or absent states"
+ )
+ if meraki.params["net_name"] and meraki.params["net_id"]:
+ meraki.fail_json(msg="net_name and net_id are mutually exclusive")
+ if (
+ meraki.params["local_status_page_enabled"] is False
+ and meraki.params["remote_status_page_enabled"] is True
+ ):
+ meraki.fail_json(
+ msg="local_status_page_enabled must be true when setting remote_status_page_enabled"
+ )
+
+ # 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"]
+ net_id = meraki.params["net_id"]
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params["net_name"], data=nets)
+
+ if meraki.params["state"] == "query":
+ path = meraki.construct_path("get_settings", net_id=net_id)
+ meraki.result["data"] = meraki.request(path, method="GET")
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["state"] == "present":
+ path = meraki.construct_path("get_settings", net_id=net_id)
+ current = meraki.request(path, method="GET")
+ payload = construct_payload(meraki.params)
+ if meraki.is_update_required(current, payload, optional_ignore=["password"]):
+ if meraki.check_mode is True:
+ try:
+ del payload["local_status_page"]["authentication"]["password"]
+ except KeyError:
+ pass
+ meraki.result["data"] = payload
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("update_settings", net_id=net_id)
+ response = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.result["changed"] = True
+ meraki.result["data"] = response
+ meraki.exit_json(**meraki.result)
+ meraki.result["data"] = current
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_organization.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_organization.py
new file mode 100644
index 000000000..45148e89a
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_organization.py
@@ -0,0 +1,242 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_organization
+short_description: Manage organizations in the Meraki cloud
+description:
+- Allows for creation, management, and visibility into organizations within Meraki.
+options:
+ state:
+ description:
+ - Create or modify an organization.
+ - C(org_id) must be specified if multiple organizations of the same name exist.
+ - C(absent) WILL DELETE YOUR ENTIRE ORGANIZATION, AND ALL ASSOCIATED OBJECTS, WITHOUT CONFIRMATION. USE WITH CAUTION.
+ choices: ['absent', 'present', 'query']
+ default: present
+ type: str
+ clone:
+ description:
+ - Organization to clone to a new organization.
+ type: str
+ org_name:
+ description:
+ - Name of organization.
+ - If C(clone) is specified, C(org_name) is the name of the new organization.
+ aliases: [ name, organization ]
+ type: str
+ org_id:
+ description:
+ - ID of organization.
+ aliases: [ id ]
+ type: str
+ delete_confirm:
+ description:
+ - ID of organization required for confirmation before deletion.
+ type: str
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Create a new organization named YourOrg
+ meraki_organization:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ delegate_to: localhost
+
+- name: Delete an organization named YourOrg
+ meraki_organization:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: absent
+ delegate_to: localhost
+
+- name: Query information about all organizations associated to the user
+ meraki_organization:
+ auth_key: abc12345
+ state: query
+ delegate_to: localhost
+
+- name: Query information about a single organization named YourOrg
+ meraki_organization:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+- name: Rename an organization to RenamedOrg
+ meraki_organization:
+ auth_key: abc12345
+ org_id: 987654321
+ org_name: RenamedOrg
+ state: present
+ delegate_to: localhost
+
+- name: Clone an organization named Org to a new one called ClonedOrg
+ meraki_organization:
+ auth_key: abc12345
+ clone: Org
+ org_name: ClonedOrg
+ state: present
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information about the organization which was created or modified
+ returned: success
+ type: complex
+ contains:
+ id:
+ description: Unique identification number of organization
+ returned: success
+ type: int
+ sample: 2930418
+ name:
+ description: Name of organization
+ returned: success
+ type: str
+ sample: YourOrg
+
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def get_org(meraki, org_id, data):
+ # meraki.fail_json(msg=str(org_id), data=data, oid0=data[0]['id'], oid1=data[1]['id'])
+ for o in data:
+ # meraki.fail_json(msg='o', data=o['id'], type=str(type(o['id'])))
+ if o['id'] == org_id:
+ return o
+ return -1
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(clone=dict(type='str'),
+ state=dict(type='str', choices=['absent', 'present', 'query'], default='present'),
+ org_name=dict(type='str', aliases=['name', 'organization']),
+ org_id=dict(type='str', aliases=['id']),
+ delete_confirm=dict(type='str'),
+ )
+
+ # 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='organizations')
+
+ meraki.params['follow_redirects'] = 'all'
+
+ create_urls = {'organizations': '/organizations'}
+ update_urls = {'organizations': '/organizations/{org_id}'}
+ delete_urls = {'organizations': '/organizations/{org_id}'}
+ clone_urls = {'organizations': '/organizations/{org_id}/clone'}
+
+ meraki.url_catalog['create'] = create_urls
+ meraki.url_catalog['update'] = update_urls
+ meraki.url_catalog['clone'] = clone_urls
+ meraki.url_catalog['delete'] = delete_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)
+ orgs = meraki.get_orgs()
+ if meraki.params['state'] == 'query':
+ if meraki.params['org_name']: # Query by organization name
+ module.warn('All matching organizations will be returned, even if there are duplicate named organizations')
+ for o in orgs:
+ if o['name'] == meraki.params['org_name']:
+ meraki.result['data'] = o
+ elif meraki.params['org_id']:
+ for o in orgs:
+ if o['id'] == meraki.params['org_id']:
+ meraki.result['data'] = o
+ else: # Query all organizations, no matter what
+ meraki.result['data'] = orgs
+ elif meraki.params['state'] == 'present':
+ if meraki.params['clone']: # Cloning
+ payload = {'name': meraki.params['org_name']}
+ response = meraki.request(meraki.construct_path('clone',
+ org_name=meraki.params['clone']
+ ),
+ payload=json.dumps(payload),
+ method='POST')
+ if meraki.status != 201:
+ meraki.fail_json(msg='Organization clone failed')
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ elif not meraki.params['org_id'] and meraki.params['org_name']: # Create new organization
+ payload = {'name': meraki.params['org_name']}
+ response = meraki.request(meraki.construct_path('create'),
+ method='POST',
+ payload=json.dumps(payload))
+ if meraki.status == 201:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ elif meraki.params['org_id'] and meraki.params['org_name']: # Update an existing organization
+ payload = {'name': meraki.params['org_name'],
+ 'id': meraki.params['org_id'],
+ }
+ original = get_org(meraki, meraki.params['org_id'], orgs)
+ if meraki.is_update_required(original, payload, optional_ignore=['url']):
+ response = meraki.request(meraki.construct_path('update',
+ org_id=meraki.params['org_id']
+ ),
+ method='PUT',
+ payload=json.dumps(payload))
+ if meraki.status != 200:
+ meraki.fail_json(msg='Organization update failed')
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+ elif meraki.params['state'] == 'absent':
+ if meraki.params['org_name'] is not None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ elif meraki.params['org_id'] is not None:
+ org_id = meraki.params['org_id']
+ if meraki.params['delete_confirm'] != org_id:
+ meraki.fail_json(msg="delete_confirm must match the network ID of the network to be deleted.")
+ if meraki.check_mode is True:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path('delete', org_id=org_id)
+ response = meraki.request(path, method='DELETE')
+ if meraki.status == 204:
+ meraki.result['data'] = {}
+ meraki.result['changed'] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_snmp.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_snmp.py
new file mode 100644
index 000000000..0240a168b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_snmp.py
@@ -0,0 +1,387 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_snmp
+short_description: Manage organizations in the Meraki cloud
+description:
+- Allows for management of SNMP settings for Meraki.
+options:
+ state:
+ description:
+ - Specifies whether SNMP information should be queried or modified.
+ choices: ['query', 'present']
+ default: present
+ type: str
+ v2c_enabled:
+ description:
+ - Specifies whether SNMPv2c is enabled.
+ type: bool
+ v3_enabled:
+ description:
+ - Specifies whether SNMPv3 is enabled.
+ type: bool
+ v3_auth_mode:
+ description:
+ - Sets authentication mode for SNMPv3.
+ choices: ['MD5', 'SHA']
+ type: str
+ v3_auth_pass:
+ description:
+ - Authentication password for SNMPv3.
+ - Must be at least 8 characters long.
+ type: str
+ v3_priv_mode:
+ description:
+ - Specifies privacy mode for SNMPv3.
+ choices: ['DES', 'AES128']
+ type: str
+ v3_priv_pass:
+ description:
+ - Privacy password for SNMPv3.
+ - Must be at least 8 characters long.
+ type: str
+ peer_ips:
+ description:
+ - List of IP addresses which can perform SNMP queries.
+ type: list
+ elements: str
+ net_name:
+ description:
+ - Name of network.
+ type: str
+ net_id:
+ description:
+ - ID of network.
+ type: str
+ access:
+ description:
+ - Type of SNMP access.
+ choices: [community, none, users]
+ type: str
+ community_string:
+ description:
+ - SNMP community string.
+ - Only relevant if C(access) is set to C(community).
+ type: str
+ users:
+ description:
+ - Information about users with access to SNMP.
+ - Only relevant if C(access) is set to C(users).
+ type: list
+ elements: dict
+ suboptions:
+ username:
+ description: Username of user with access.
+ type: str
+ passphrase:
+ description: Passphrase for user SNMP access.
+ type: str
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+'''
+
+EXAMPLES = r'''
+- name: Query SNMP values
+ meraki_snmp:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+- name: Enable SNMPv2
+ meraki_snmp:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ v2c_enabled: yes
+ delegate_to: localhost
+
+- name: Disable SNMPv2
+ meraki_snmp:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ v2c_enabled: no
+ delegate_to: localhost
+
+- name: Enable SNMPv3
+ meraki_snmp:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: present
+ v3_enabled: true
+ v3_auth_mode: SHA
+ v3_auth_pass: ansiblepass
+ v3_priv_mode: AES128
+ v3_priv_pass: ansiblepass
+ peer_ips: 192.0.1.1;192.0.1.2
+ delegate_to: localhost
+
+- name: Set network access type to community string
+ meraki_snmp:
+ auth_key: abc1235
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ access: community
+ community_string: abc123
+ delegate_to: localhost
+
+- name: Set network access type to username
+ meraki_snmp:
+ auth_key: abc1235
+ org_name: YourOrg
+ net_name: YourNet
+ state: present
+ access: users
+ users:
+ - username: ansibleuser
+ passphrase: ansiblepass
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+data:
+ description: Information about SNMP settings.
+ type: complex
+ returned: always
+ contains:
+ hostname:
+ description: Hostname of SNMP server.
+ returned: success and no network specified.
+ type: str
+ sample: n1.meraki.com
+ peer_ips:
+ description: Semi-colon delimited list of IPs which can poll SNMP information.
+ returned: success and no network specified.
+ type: str
+ sample: 192.0.1.1
+ port:
+ description: Port number of SNMP.
+ returned: success and no network specified.
+ type: str
+ sample: 16100
+ v2c_enabled:
+ description: Shows enabled state of SNMPv2c
+ returned: success and no network specified.
+ type: bool
+ sample: true
+ v3_enabled:
+ description: Shows enabled state of SNMPv3
+ returned: success and no network specified.
+ type: bool
+ sample: true
+ v3_auth_mode:
+ description: The SNMP version 3 authentication mode either MD5 or SHA.
+ returned: success and no network specified.
+ type: str
+ sample: SHA
+ v3_priv_mode:
+ description: The SNMP version 3 privacy mode DES or AES128.
+ returned: success and no network specified.
+ type: str
+ sample: AES128
+ v2_community_string:
+ description: Automatically generated community string for SNMPv2c.
+ returned: When SNMPv2c is enabled and no network specified.
+ type: str
+ sample: o/8zd-JaSb
+ v3_user:
+ description: Automatically generated username for SNMPv3.
+ returned: When SNMPv3c is enabled and no network specified.
+ type: str
+ sample: o/8zd-JaSb
+ access:
+ description: Type of SNMP access.
+ type: str
+ returned: success, when network specified
+ community_string:
+ description: SNMP community string. Only relevant if C(access) is set to C(community).
+ type: str
+ returned: success, when network specified
+ users:
+ description: Information about users with access to SNMP. Only relevant if C(access) is set to C(users).
+ type: complex
+ contains:
+ username:
+ description: Username of user with access.
+ type: str
+ returned: success, when network specified
+ passphrase:
+ description: Passphrase for user SNMP access.
+ type: str
+ returned: success, when network specified
+'''
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
+
+
+def get_snmp(meraki, org_id):
+ path = meraki.construct_path('get_all', org_id=org_id)
+ r = meraki.request(path,
+ method='GET',
+ )
+ if meraki.status == 200:
+ return r
+
+
+def set_snmp(meraki, org_id):
+ payload = dict()
+ if meraki.params['v2c_enabled'] is not None:
+ payload = {'v2cEnabled': meraki.params['v2c_enabled'],
+ }
+ if meraki.params['v3_enabled'] is True:
+ if len(meraki.params['v3_auth_pass']) < 8 or len(meraki.params['v3_priv_pass']) < 8:
+ meraki.fail_json(msg='v3_auth_pass and v3_priv_pass must both be at least 8 characters long.')
+ if meraki.params['v3_auth_mode'] is None or \
+ meraki.params['v3_auth_pass'] is None or \
+ meraki.params['v3_priv_mode'] is None or \
+ meraki.params['v3_priv_pass'] is None:
+ meraki.fail_json(msg='v3_auth_mode, v3_auth_pass, v3_priv_mode, and v3_auth_pass are required')
+ payload = {'v3Enabled': meraki.params['v3_enabled'],
+ 'v3AuthMode': meraki.params['v3_auth_mode'].upper(),
+ 'v3AuthPass': meraki.params['v3_auth_pass'],
+ 'v3PrivMode': meraki.params['v3_priv_mode'].upper(),
+ 'v3PrivPass': meraki.params['v3_priv_pass'],
+ }
+ if meraki.params['peer_ips'] is not None:
+ payload['peerIps'] = meraki.params['peer_ips']
+ elif meraki.params['v3_enabled'] is False:
+ payload = {'v3Enabled': False}
+ full_compare = snake_dict_to_camel_dict(payload)
+ path = meraki.construct_path('create', org_id=org_id)
+ snmp = get_snmp(meraki, org_id)
+ ignored_parameters = ['v3AuthPass', 'v3PrivPass', 'hostname', 'port', 'v2CommunityString', 'v3User']
+ if meraki.is_update_required(snmp, full_compare, optional_ignore=ignored_parameters):
+ if meraki.module.check_mode is True:
+ meraki.generate_diff(snmp, full_compare)
+ snmp.update(payload)
+ meraki.result['data'] = snmp
+ meraki.result['changed'] = True
+ meraki.exit_json(**meraki.result)
+ r = meraki.request(path,
+ method='PUT',
+ payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.generate_diff(snmp, r)
+ meraki.result['changed'] = True
+ return r
+ else:
+ return snmp
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ user_arg_spec = dict(username=dict(type='str'),
+ passphrase=dict(type='str', no_log=True),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
+ v2c_enabled=dict(type='bool'),
+ v3_enabled=dict(type='bool'),
+ v3_auth_mode=dict(type='str', choices=['SHA', 'MD5']),
+ v3_auth_pass=dict(type='str', no_log=True),
+ v3_priv_mode=dict(type='str', choices=['DES', 'AES128']),
+ v3_priv_pass=dict(type='str', no_log=True),
+ peer_ips=dict(type='list', default=None, elements='str'),
+ access=dict(type='str', choices=['none', 'community', 'users']),
+ community_string=dict(type='str', no_log=True),
+ users=dict(type='list', default=None, elements='dict', options=user_arg_spec),
+ net_name=dict(type='str'),
+ net_id=dict(type='str'),
+ )
+
+ # 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='snmp')
+ meraki.params['follow_redirects'] = 'all'
+
+ query_urls = {'snmp': '/organizations/{org_id}/snmp'}
+ query_net_urls = {'snmp': '/networks/{net_id}/snmp'}
+ update_urls = {'snmp': '/organizations/{org_id}/snmp'}
+ update_net_urls = {'snmp': '/networks/{net_id}/snmp'}
+
+ meraki.url_catalog['get_all'].update(query_urls)
+ meraki.url_catalog['query_net_all'] = query_net_urls
+ meraki.url_catalog['create'] = update_urls
+ meraki.url_catalog['create_net'] = update_net_urls
+
+ payload = None
+
+ if not meraki.params['org_name'] and not meraki.params['org_id']:
+ meraki.fail_json(msg='org_name or org_id is required')
+
+ org_id = meraki.params['org_id']
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params['org_name'])
+ net_id = meraki.params['net_id']
+ if net_id is None and meraki.params['net_name']:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
+
+ if meraki.params['state'] == 'present':
+ if net_id is not None:
+ payload = {'access': meraki.params['access']}
+ if meraki.params['community_string'] is not None:
+ payload['communityString'] = meraki.params['community_string']
+ elif meraki.params['users'] is not None:
+ payload['users'] = meraki.params['users']
+
+ if meraki.params['state'] == 'query':
+ if net_id is None:
+ meraki.result['data'] = get_snmp(meraki, org_id)
+ else:
+ path = meraki.construct_path('query_net_all', net_id=net_id)
+ response = meraki.request(path, method='GET')
+ if meraki.status == 200:
+ meraki.result['data'] = response
+ elif meraki.params['state'] == 'present':
+ if net_id is None:
+ meraki.result['data'] = set_snmp(meraki, org_id)
+ else:
+ path = meraki.construct_path('query_net_all', net_id=net_id)
+ original = meraki.request(path, method='GET')
+ if meraki.is_update_required(original, payload):
+ path = meraki.construct_path('create_net', net_id=net_id)
+ response = meraki.request(path, method='PUT', payload=json.dumps(payload))
+ if meraki.status == 200:
+ if response['access'] == 'none':
+ meraki.result['data'] = {}
+ else:
+ meraki.result['data'] = response
+ meraki.result['changed'] = True
+ else:
+ meraki.result['data'] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py
new file mode 100644
index 000000000..e94238750
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py
@@ -0,0 +1,317 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_syslog
+short_description: Manage syslog server settings in the Meraki cloud.
+description:
+- Allows for creation and management of Syslog servers within Meraki.
+notes:
+- Changes to existing syslog servers replaces existing configuration. If you need to add to an
+ existing configuration set state to query to gather the existing configuration and then modify or add.
+options:
+ auth_key:
+ description:
+ - Authentication key provided by the dashboard. Required if environmental variable MERAKI_KEY is not set.
+ type: str
+ state:
+ description:
+ - Query or edit syslog servers
+ - To delete a syslog server, do not include server in list of servers
+ choices: [present, query]
+ default: present
+ type: str
+ net_name:
+ description:
+ - Name of a network.
+ aliases: [name, network]
+ type: str
+ net_id:
+ description:
+ - ID number of a network.
+ type: str
+ servers:
+ description:
+ - List of syslog server settings
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description:
+ - IP address or hostname of Syslog server.
+ type: str
+ port:
+ description:
+ - Port number Syslog server is listening on.
+ default: "514"
+ type: int
+ roles:
+ description:
+ - List of applicable Syslog server roles.
+ - Choices can be one of Wireless Event log, Appliance event log, Switch event log, Air Marshal events, Flows, URLs, IDS alerts, Security events
+ type: list
+ elements: str
+
+author:
+ - Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Query syslog configurations on network named MyNet in the YourOrg organization
+ meraki_syslog:
+ auth_key: abc12345
+ state: query
+ org_name: YourOrg
+ net_name: MyNet
+ delegate_to: localhost
+
+- name: Add single syslog server with Appliance event log role
+ meraki_syslog:
+ auth_key: abc12345
+ state: present
+ org_name: YourOrg
+ net_name: MyNet
+ servers:
+ - host: 192.0.1.2
+ port: 514
+ roles:
+ - Appliance event log
+ delegate_to: localhost
+
+- name: Add multiple syslog servers
+ meraki_syslog:
+ auth_key: abc12345
+ state: present
+ org_name: YourOrg
+ net_name: MyNet
+ servers:
+ - host: 192.0.1.2
+ port: 514
+ roles:
+ - Appliance event log
+ - host: 192.0.1.3
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: Information about the created or manipulated object.
+ returned: info
+ type: complex
+ contains:
+ servers:
+ description: List of syslog servers.
+ returned: info
+ type: complex
+ contains:
+ host:
+ description: Hostname or IP address of syslog server.
+ returned: success
+ type: str
+ sample: 192.0.1.1
+ port:
+ description: Port number for syslog communication.
+ returned: success
+ type: str
+ sample: 443
+ roles:
+ description: List of roles assigned to syslog server.
+ returned: success
+ type: list
+ sample: "Wireless event log, URLs"
+"""
+
+from copy import deepcopy
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def sort_roles(syslog_servers):
+ """Accept a full payload and sort roles"""
+ # sorted_servers = list()
+ for i, server in enumerate(syslog_servers):
+ syslog_servers["servers"][i]["roles"] = sorted(
+ syslog_servers["servers"][i]["roles"]
+ )
+ # return sorted_servers
+
+
+def validate_role_choices(meraki, servers):
+ choices = [
+ "Wireless event log",
+ "Appliance event log",
+ "Switch event log",
+ "Air Marshal events",
+ "Flows",
+ "URLs",
+ "IDS alerts",
+ "Security events",
+ ]
+
+ # Change all choices to lowercase for comparison
+ for i in range(len(choices)):
+ choices[i] = choices[i].lower()
+ for server in range(len(servers)):
+ for role in servers[server]["roles"]:
+ if role.lower() not in choices:
+ meraki.fail_json(
+ msg="Invalid role found in {0}.".format(servers[server]["host"])
+ )
+
+
+def normalize_roles(meraki, servers):
+ if len(servers["servers"]) > 0:
+ for server in range(len(servers)):
+ for role in range(len(servers["servers"][server]["roles"])):
+ servers["servers"][server]["roles"][role] = servers["servers"][server][
+ "roles"
+ ][role].lower()
+ return servers
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ server_arg_spec = dict(
+ host=dict(type="str"),
+ port=dict(type="int", default="514"),
+ roles=dict(
+ type="list",
+ elements="str",
+ ),
+ )
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ net_id=dict(type="str"),
+ servers=dict(type="list", elements="dict", options=server_arg_spec),
+ state=dict(type="str", choices=["present", "query"], default="present"),
+ net_name=dict(type="str", aliases=["name", "network"]),
+ )
+
+ # 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="syslog")
+ module.params["follow_redirects"] = "all"
+ payload = None
+
+ syslog_urls = {"syslog": "/networks/{net_id}/syslogServers"}
+ meraki.url_catalog["query_update"] = syslog_urls
+
+ if not meraki.params["org_name"] and not meraki.params["org_id"]:
+ meraki.fail_json(msg="org_name or org_id parameters are required")
+ if meraki.params["state"] != "query":
+ if not meraki.params["net_name"] and not meraki.params["net_id"]:
+ meraki.fail_json(
+ msg="net_name or net_id is required for present or absent states"
+ )
+ if meraki.params["net_name"] and meraki.params["net_id"]:
+ meraki.fail_json(msg="net_name and net_id are mutually exclusive")
+
+ # if the user is working with this module in only check mode we do not
+ # want to make any changes to the environment, just return the current
+ # state with no modifications
+
+ # 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"]
+ if not org_id:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ net_id = meraki.params["net_id"]
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params["net_name"], data=nets)
+
+ if meraki.params["state"] == "query":
+ path = meraki.construct_path("query_update", net_id=net_id)
+ r = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = r
+ elif meraki.params["state"] == "present":
+ # Validate roles
+ validate_role_choices(meraki, meraki.params["servers"])
+
+ # Construct payload
+ payload = dict()
+ payload["servers"] = meraki.params["servers"]
+
+ # Convert port numbers to string for idempotency checks
+ for server in payload["servers"]:
+ if server["port"]:
+ server["port"] = str(server["port"])
+ path = meraki.construct_path("query_update", net_id=net_id)
+ r = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ original = r
+
+ # Roles must be sorted since out of order responses may break idempotency check
+ # Need to check to make sure servers are actually defined
+ if original is not None:
+ if len(original["servers"]) > 0:
+ sort_roles(original)
+ if payload is not None:
+ if len(payload["servers"]) > 0:
+ sort_roles(payload)
+
+ # Sanitize roles for comparison
+ sanitized_original = normalize_roles(meraki, deepcopy(original))
+ sanitized_payload = normalize_roles(meraki, deepcopy(payload))
+
+ if meraki.is_update_required(sanitized_original, sanitized_payload):
+ if meraki.module.check_mode is True:
+ meraki.generate_diff(original, payload)
+ original.update(payload)
+ meraki.result["data"] = original
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("query_update", net_id=net_id)
+ r = meraki.request(path, method="PUT", payload=json.dumps(payload))
+ if meraki.status == 200:
+ meraki.generate_diff(original, r)
+ meraki.result["data"] = r
+ meraki.result["changed"] = True
+ else:
+ if meraki.module.check_mode is True:
+ meraki.result["data"] = original
+ meraki.exit_json(**meraki.result)
+ meraki.result["data"] = original
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py
new file mode 100644
index 000000000..9b8ce2f67
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py
@@ -0,0 +1,431 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
+# 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_webhook
+short_description: Manage webhooks configured in the Meraki cloud
+description:
+- Configure and query information about webhooks within the Meraki cloud.
+notes:
+- Some of the options are likely only used for developers within Meraki.
+options:
+ state:
+ description:
+ - Specifies whether object should be queried, created/modified, or removed.
+ choices: [absent, present, query]
+ default: query
+ type: str
+ net_name:
+ description:
+ - Name of network which configuration is applied to.
+ aliases: [network]
+ type: str
+ net_id:
+ description:
+ - ID of network which configuration is applied to.
+ type: str
+ name:
+ description:
+ - Name of webhook.
+ type: str
+ shared_secret:
+ description:
+ - Secret password to use when accessing webhook.
+ type: str
+ url:
+ description:
+ - URL to access when calling webhook.
+ type: str
+ webhook_id:
+ description:
+ - Unique ID of webhook.
+ type: str
+ payload_template_name:
+ description:
+ - The name of the payload template
+ type: str
+ payload_template_id:
+ description:
+ - The ID of the payload template. Overrides payload_template_name if passed too.
+ type: str
+ test:
+ description:
+ - Indicates whether to test or query status.
+ type: str
+ choices: [test]
+ test_id:
+ description:
+ - ID of webhook test query.
+ type: str
+author:
+- Kevin Breit (@kbreit)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Create webhook
+ meraki_webhook:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ name: Test_Hook
+ url: https://webhook.url/
+ shared_secret: shhhdonttellanyone
+ payload_template_name: 'Slack (included)'
+ delegate_to: localhost
+
+- name: Query one webhook
+ meraki_webhook:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ name: Test_Hook
+ delegate_to: localhost
+
+- name: Query all webhooks
+ meraki_webhook:
+ auth_key: abc123
+ state: query
+ org_name: YourOrg
+ net_name: YourNet
+ delegate_to: localhost
+
+- name: Delete webhook
+ meraki_webhook:
+ auth_key: abc123
+ state: absent
+ org_name: YourOrg
+ net_name: YourNet
+ name: Test_Hook
+ delegate_to: localhost
+
+- name: Test webhook
+ meraki_webhook:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ test: test
+ url: https://webhook.url/abc123
+ delegate_to: localhost
+
+- name: Get webhook status
+ meraki_webhook:
+ auth_key: abc123
+ state: present
+ org_name: YourOrg
+ net_name: YourNet
+ test: status
+ test_id: abc123531234
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: List of administrators.
+ returned: success
+ type: complex
+ contains:
+ id:
+ description: Unique ID of webhook.
+ returned: success
+ type: str
+ sample: aHR0cHM6Ly93ZWJob22LnvpdGUvOGViNWI3NmYtYjE2Ny00Y2I4LTlmYzQtND32Mj3F5NzIaMjQ0
+ name:
+ description: Descriptive name of webhook.
+ returned: success
+ type: str
+ sample: Test_Hook
+ networkId:
+ description: ID of network containing webhook object.
+ returned: success
+ type: str
+ sample: N_12345
+ shared_secret:
+ description: Password for webhook.
+ returned: success
+ type: str
+ sample: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ url:
+ description: URL of webhook endpoint.
+ returned: success
+ type: str
+ sample: https://webhook.url/abc123
+ status:
+ description: Status of webhook test.
+ returned: success, when testing webhook
+ type: str
+ sample: enqueued
+ payloadTemplate:
+ description: The payload template used when posting data to the HTTP server.
+ returned: success
+ type: str
+ sample:
+ payloadTemplateId: wpt_00001
+ name: Meraki (included)
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def get_webhook_id(name, webhooks):
+ for webhook in webhooks:
+ if name == webhook["name"]:
+ return webhook["id"]
+ return None
+
+
+def get_all_webhooks(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 sanitize_no_log_values(meraki):
+ try:
+ meraki.result["diff"]["before"][
+ "shared_secret"
+ ] = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ except KeyError:
+ pass
+ try:
+ for i in meraki.result['data']:
+ i[
+ "shared_secret"
+ ] = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ except (KeyError, TypeError):
+ pass
+ try:
+ meraki.result["data"]["shared_secret"] = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ except (KeyError, TypeError):
+ pass
+
+
+def get_webhook_payload_templates(meraki, net_id):
+ path = meraki.construct_path("get_payload_templates", net_id=net_id)
+ response = meraki.request(path, "GET")
+ if meraki.status != 200:
+ meraki.fail_json(msg="Unable to get webhook payload templates")
+ return response
+
+
+def get_webhook_payload_template_id(meraki, templates, name):
+ for template in templates:
+ if template["name"] == name:
+ return template["payloadTemplateId"]
+ meraki.fail_json(msg="No payload template found with the name {0}".format(name))
+
+
+def main():
+ # define the available arguments/parameters that a user can pass to
+ # the module
+
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ state=dict(type="str", choices=["absent", "present", "query"], default="query"),
+ net_name=dict(type="str", aliases=["network"]),
+ net_id=dict(type="str"),
+ name=dict(type="str"),
+ url=dict(type="str"),
+ shared_secret=dict(type="str", no_log=True),
+ webhook_id=dict(type="str"),
+ test=dict(type="str", choices=["test"]),
+ test_id=dict(type="str"),
+ payload_template_name=dict(type="str"),
+ payload_template_id=dict(type="str"),
+ )
+
+ # 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="webhooks")
+
+ meraki.params["follow_redirects"] = "all"
+
+ query_url = {"webhooks": "/networks/{net_id}/webhooks/httpServers"}
+ query_one_url = {"webhooks": "/networks/{net_id}/webhooks/httpServers/{hookid}"}
+ create_url = {"webhooks": "/networks/{net_id}/webhooks/httpServers"}
+ update_url = {"webhooks": "/networks/{net_id}/webhooks/httpServers/{hookid}"}
+ delete_url = {"webhooks": "/networks/{net_id}/webhooks/httpServers/{hookid}"}
+ test_url = {"webhooks": "/networks/{net_id}/webhooks/webhookTests"}
+ test_status_url = {"webhooks": "/networks/{net_id}/webhooks/webhookTests/{testid}"}
+ query_payload_templates = {
+ "webhooks": "/networks/{net_id}/webhooks/payloadTemplates"
+ }
+ meraki.url_catalog["get_all"].update(query_url)
+ meraki.url_catalog["get_one"].update(query_one_url)
+ meraki.url_catalog["create"] = create_url
+ meraki.url_catalog["update"] = update_url
+ meraki.url_catalog["delete"] = delete_url
+ meraki.url_catalog["test"] = test_url
+ meraki.url_catalog["test_status"] = test_status_url
+ meraki.url_catalog["get_payload_templates"] = query_payload_templates
+
+ org_id = meraki.params["org_id"]
+ if org_id is None:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+
+ net_id = meraki.params["net_id"]
+ if net_id is None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(net_name=meraki.params["net_name"], data=nets)
+
+ templates = get_webhook_payload_templates(meraki, net_id)
+ payload_template_id = meraki.params["payload_template_id"]
+ if (
+ payload_template_id is None
+ and meraki.params["payload_template_name"] is not None
+ ):
+ payload_template_id = get_webhook_payload_template_id(
+ meraki, templates, name=meraki.params["payload_template_name"]
+ )
+
+ webhook_id = meraki.params["webhook_id"]
+ webhooks = None
+ if webhook_id is None and meraki.params["name"]:
+ webhooks = get_all_webhooks(meraki, net_id)
+ webhook_id = get_webhook_id(meraki.params["name"], webhooks)
+
+ if meraki.params["state"] == "present" and meraki.params["test"] is None:
+ payload = {
+ "name": meraki.params["name"],
+ "url": meraki.params["url"],
+ }
+ if meraki.params["shared_secret"] is not None:
+ payload["sharedSecret"] = meraki.params["shared_secret"]
+ if payload_template_id is not None:
+ payload["payloadTemplate"] = {"payloadTemplateId": payload_template_id}
+
+ if meraki.params["state"] == "query":
+ if webhook_id is not None: # Query a single webhook
+ path = meraki.construct_path(
+ "get_one", net_id=net_id, custom={"hookid": webhook_id}
+ )
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ sanitize_no_log_values(meraki)
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["test_id"] is not None:
+ path = meraki.construct_path(
+ "test_status",
+ net_id=net_id,
+ custom={"testid": meraki.params["test_id"]},
+ )
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ sanitize_no_log_values(meraki)
+ meraki.exit_json(**meraki.result)
+ else:
+ path = meraki.construct_path("get_all", net_id=net_id)
+ response = meraki.request(path, method="GET")
+ if meraki.status == 200:
+ meraki.result["data"] = response
+ # meraki.fail_json(msg=meraki.result)
+ sanitize_no_log_values(meraki)
+ meraki.exit_json(**meraki.result)
+ elif meraki.params["state"] == "present":
+ if meraki.params["test"] == "test":
+ payload = {"url": meraki.params["url"]}
+ path = meraki.construct_path("test", net_id=net_id)
+ response = meraki.request(path, method="POST", payload=json.dumps(payload))
+ if meraki.status == 201:
+ meraki.result["data"] = response
+ meraki.exit_json(**meraki.result)
+ if webhook_id is None: # New webhook needs to be created
+ if meraki.check_mode is True:
+ meraki.result["data"] = payload
+ meraki.result["data"]["networkId"] = net_id
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path("create", net_id=net_id)
+ response = meraki.request(path, method="POST", payload=json.dumps(payload))
+ if meraki.status == 201:
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ else: # Need to update
+ path = meraki.construct_path(
+ "get_one", net_id=net_id, custom={"hookid": webhook_id}
+ )
+ original = meraki.request(path, method="GET")
+ if meraki.is_update_required(original, payload):
+ if meraki.check_mode is True:
+ meraki.generate_diff(original, payload)
+ sanitize_no_log_values(meraki)
+ original.update(payload)
+ meraki.result["data"] = original
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path(
+ "update", net_id=net_id, custom={"hookid": webhook_id}
+ )
+ response = meraki.request(
+ path, method="PUT", payload=json.dumps(payload)
+ )
+ if meraki.status == 200:
+ # Not all fields are included so it needs to be checked to avoid comparing same thing
+ if meraki.is_update_required(original, response):
+ meraki.generate_diff(original, response)
+ sanitize_no_log_values(meraki)
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+ else:
+ meraki.result["data"] = original
+ elif meraki.params["state"] == "absent":
+ if webhook_id is None: # Make sure it is downloaded
+ if webhooks is None:
+ webhooks = get_all_webhooks(meraki, net_id)
+ webhook_id = get_webhook_id(meraki.params["name"], webhooks)
+ if webhook_id is None:
+ meraki.fail_json(
+ msg="There is no webhook with the name {0}".format(
+ meraki.params["name"]
+ )
+ )
+ if webhook_id: # Test to see if it exists
+ if meraki.module.check_mode is True:
+ meraki.result["data"] = None
+ meraki.result["changed"] = True
+ meraki.exit_json(**meraki.result)
+ path = meraki.construct_path(
+ "delete", net_id=net_id, custom={"hookid": webhook_id}
+ )
+ response = meraki.request(path, method="DELETE")
+ if meraki.status == 204:
+ meraki.result["data"] = response
+ meraki.result["changed"] = True
+
+ # 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/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook_payload_template.py b/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook_payload_template.py
new file mode 100644
index 000000000..d7cc5a190
--- /dev/null
+++ b/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook_payload_template.py
@@ -0,0 +1,352 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Joshua Coronado (@joshuajcoronado) <joshua@coronado.io>
+# 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_webhook_payload_template
+short_description: Manage webhook payload templates for a network in the Meraki cloud
+description:
+- Allows for querying, deleting, creating, and updating of webhook payload templates.
+options:
+ state:
+ description:
+ - Specifies whether payload template should be queried, created, modified, or deleted.
+ choices: ['absent', 'query', 'present']
+ default: query
+ type: str
+ name:
+ description:
+ - Name of the template.
+ type: str
+ net_name:
+ description:
+ - Name of network containing access points.
+ type: str
+ net_id:
+ description:
+ - ID of network containing access points.
+ type: str
+ body:
+ description:
+ - The liquid template used for the body of the webhook message.
+ type: str
+ headers:
+ description:
+ - List of the liquid templates used with the webhook headers.
+ type: list
+ elements: dict
+ default: []
+ suboptions:
+ name:
+ description:
+ - The name of the header template.
+ type: str
+ template:
+ description:
+ - The liquid template for the headers
+ type: str
+author:
+- Joshua Coronado (@joshuajcoronado)
+extends_documentation_fragment: cisco.meraki.meraki
+"""
+
+EXAMPLES = r"""
+- name: Query all configuration templates
+ meraki_webhook_payload_template:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ delegate_to: localhost
+
+- name: Query specific configuration templates
+ meraki_webhook_payload_template:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ name: Twitter
+ delegate_to: localhost
+
+- name: Create payload template
+ meraki_webhook_payload_template:
+ auth_key: abc12345
+ org_name: YourOrg
+ state: query
+ name: TestTemplate
+ body: Testbody
+ headers:
+ - name: testheader
+ template: testheadertemplate
+ delegate_to: localhost
+
+- name: Delete a configuration template
+ meraki_config_template:
+ auth_key: abc123
+ state: absent
+ org_name: YourOrg
+ name: TestTemplate
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+data:
+ description: Information about queried object.
+ returned: success
+ type: complex
+ contains:
+ name:
+ description:
+ - The name of the template
+ returned: success
+ type: str
+ sample: testTemplate
+ body:
+ description:
+ - The liquid template used for the body of the webhook message.
+ returned: success
+ type: str
+ sample: {"event_type":"{{alertTypeId}}","client_payload":{"text":"{{alertData}}"}}
+ headers:
+ description: List of the liquid templates used with the webhook headers.
+ returned: success
+ type: list
+ contains:
+ name:
+ description:
+ - The name of the template
+ returned: success
+ type: str
+ sample: testTemplate
+ template:
+ description:
+ - The liquid template for the header
+ returned: success
+ type: str
+ sample: "Bearer {{sharedSecret}}"
+"""
+
+from ansible.module_utils.basic import AnsibleModule, json
+from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import (
+ MerakiModule,
+ meraki_argument_spec,
+)
+
+
+def get_webhook_payload_templates(meraki, net_id):
+ path = meraki.construct_path("get_all", net_id=net_id)
+ response = meraki.request(path, "GET")
+ if meraki.status != 200:
+ meraki.fail_json(msg="Unable to get webhook payload templates")
+ return response
+
+
+def delete_template(meraki, net_id, template_id):
+ changed = True
+ if meraki.check_mode:
+ return {}, changed
+ else:
+ path = meraki.construct_path(
+ "update", net_id=net_id, custom={"template_id": template_id}
+ )
+ response = meraki.request(path, method="DELETE")
+ if meraki.status != 204:
+ meraki.fail_json(msg="Unable to remove webhook payload templates")
+ return response, changed
+
+
+def create_template(meraki, net_id, template):
+ changed = True
+
+ if meraki.check_mode:
+ return template, changed
+ else:
+ path = meraki.construct_path("get_all", net_id=net_id)
+ response = meraki.request(path, "POST", payload=json.dumps(template))
+ if meraki.status != 201:
+ meraki.fail_json(msg="Unable to create webhook payload template")
+ return response, changed
+
+
+def update_template(meraki, net_id, template, payload):
+ changed = False
+
+ if template["body"] != payload["body"]:
+ changed = True
+
+ if meraki.is_update_required(template["headers"], payload["headers"]):
+ changed = True
+
+ if changed:
+ meraki.generate_diff(template, payload)
+ if meraki.check_mode:
+ return payload, changed
+ else:
+ path = meraki.construct_path(
+ "update",
+ net_id=net_id,
+ custom={"template_id": template["payloadTemplateId"]},
+ )
+ response = meraki.request(
+ path, method="PUT", payload=json.dumps(payload)
+ )
+ if meraki.status != 200:
+ meraki.fail_json(
+ msg="Unable to update webhook payload template"
+ )
+ return response, changed
+
+ return template, changed
+
+
+def main():
+
+ # define the available arguments/parameters that a user can pass to
+ # the module
+ argument_spec = meraki_argument_spec()
+ argument_spec.update(
+ state=dict(
+ type="str", choices=["absent", "query", "present"], default="query"
+ ),
+ name=dict(type="str", default=None),
+ net_name=dict(type="str"),
+ net_id=dict(type="str"),
+ body=dict(type="str", default=None),
+ headers=dict(
+ type="list",
+ default=[],
+ elements="dict",
+ options=dict(
+ name=dict(type="str"),
+ template=dict(type="str"),
+ ),
+ ),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+ meraki = MerakiModule(module, function="webhook_payload_template")
+ meraki.params["follow_redirects"] = "all"
+
+ query_all_urls = {
+ "webhook_payload_template": "/networks/{net_id}/webhooks/payloadTemplates"
+ }
+ update_urls = {
+ "webhook_payload_template": "/networks/{net_id}/webhooks/payloadTemplates/{template_id}"
+ }
+
+ meraki.url_catalog["get_all"].update(query_all_urls)
+ meraki.url_catalog["update"] = update_urls
+
+ org_id = meraki.params["org_id"]
+ if meraki.params["org_name"]:
+ org_id = meraki.get_org_id(meraki.params["org_name"])
+ net_id = meraki.params["net_id"]
+
+ if net_id is None:
+ if meraki.params["net_name"] is not None:
+ nets = meraki.get_nets(org_id=org_id)
+ net_id = meraki.get_net_id(
+ net_name=meraki.params["net_name"], data=nets
+ )
+
+ templates = {
+ template["name"]: template
+ for template in get_webhook_payload_templates(meraki, net_id)
+ }
+
+ if meraki.params["state"] == "query":
+ meraki.result["changed"] = False
+
+ if meraki.params["name"]:
+ if meraki.params["name"] in templates:
+ meraki.result["data"] = templates[meraki.params["name"]]
+ else:
+ meraki.fail_json(
+ msg="Unable to get webhook payload template named: {0}".format(
+ meraki.params["name"]
+ )
+ )
+ else:
+ meraki.result["data"] = templates
+
+ elif meraki.params["state"] == "present":
+ if meraki.params["name"] is None:
+ meraki.fail_json(msg="name is a required parameter")
+
+ if meraki.params["body"] is None:
+ meraki.fail_json(
+ msg="body is a required parameter when state is present"
+ )
+
+ headers = []
+
+ for header in meraki.params["headers"]:
+ for key in ["name", "template"]:
+ if key not in header:
+ meraki.fail_json(
+ msg="{0} is a required parameter for a header".format(
+ key
+ )
+ )
+ if not header[key]:
+ meraki.fail_json(
+ msg="{0} in header must be a string".format(key)
+ )
+ headers.append(
+ dict(name=header["name"], template=header["template"])
+ )
+
+ payload = {
+ "name": meraki.params["name"],
+ "body": meraki.params["body"],
+ "headers": meraki.params["headers"],
+ }
+
+ if meraki.params["name"] in templates:
+ (
+ meraki.result["data"],
+ meraki.result["changed"],
+ ) = update_template(
+ meraki, net_id, templates[meraki.params["name"]], payload
+ )
+ else:
+ (
+ meraki.result["data"],
+ meraki.result["changed"],
+ ) = create_template(meraki, net_id, payload)
+
+ elif meraki.params["state"] == "absent":
+ if meraki.params["name"] in templates:
+ (
+ meraki.result["data"],
+ meraki.result["changed"],
+ ) = delete_template(
+ meraki,
+ net_id,
+ templates[meraki.params["name"]]["payloadTemplateId"],
+ )
+ else:
+ meraki.result["changed"] = False
+ meraki.result["data"] = {}
+
+ # 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/ansible_collections/cisco/meraki/scripts/sublime-build/build.py b/ansible_collections/cisco/meraki/scripts/sublime-build/build.py
new file mode 100644
index 000000000..53acd432b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/scripts/sublime-build/build.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+import subprocess
+import sys
+import pathlib
+
+
+def is_path_module(path) -> bool:
+ path = pathlib.Path(path)
+ parts = path.parts
+ if "plugins" in parts and "modules" in parts:
+ # print(f"This is module {str(parts[-1])}")
+ return True
+ return False
+
+
+def is_path_integration_test(path) -> bool:
+ path = pathlib.Path(path)
+ parts = path.parts
+ if "integration" in parts and "tests" in parts:
+ return True
+ return False
+
+
+def get_module_name_from_module(path) -> str:
+ path = pathlib.Path(path)
+ parts = path.parts
+ return parts[-1].split(".")[0]
+
+
+def get_module_name_from_test(path) -> str:
+ path = pathlib.Path(path)
+ parts = path.parts
+ return parts[-3]
+
+
+def execute_tests(module_name, ansible_test_path=None) -> None:
+ if ansible_test_path is not None:
+ if ansible_test_path[-1] != "/":
+ ansible_test_path = f"{ansible_test_path}/"
+ with subprocess.Popen(
+ [
+ f"{ansible_test_path}ansible-test",
+ "network-integration",
+ "--allow-unsupported",
+ module_name,
+ ],
+ ) as process:
+ process.communicate()
+ else:
+ with subprocess.Popen(
+ [
+ "ansible-test",
+ "network-integration",
+ "--allow-unsupported",
+ module_name,
+ ],
+ ) as process:
+ process.communicate()
+
+
+def main():
+ if len(sys.argv) == 1:
+ sys.exit("File path must be passed as an argument.")
+ if is_path_module(sys.argv[1]) is True:
+ module_name = get_module_name_from_module(sys.argv[1])
+ if is_path_integration_test(sys.argv[1]) is True:
+ module_name = get_module_name_from_test(sys.argv[1])
+ if len(sys.argv) == 3: # Specify ansible-test path
+ execute_tests(module_name, sys.argv[2])
+ else:
+ execute_tests(module_name)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/meraki/scripts/sublime-build/build.py.generic b/ansible_collections/cisco/meraki/scripts/sublime-build/build.py.generic
new file mode 100755
index 000000000..53acd432b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/scripts/sublime-build/build.py.generic
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+import subprocess
+import sys
+import pathlib
+
+
+def is_path_module(path) -> bool:
+ path = pathlib.Path(path)
+ parts = path.parts
+ if "plugins" in parts and "modules" in parts:
+ # print(f"This is module {str(parts[-1])}")
+ return True
+ return False
+
+
+def is_path_integration_test(path) -> bool:
+ path = pathlib.Path(path)
+ parts = path.parts
+ if "integration" in parts and "tests" in parts:
+ return True
+ return False
+
+
+def get_module_name_from_module(path) -> str:
+ path = pathlib.Path(path)
+ parts = path.parts
+ return parts[-1].split(".")[0]
+
+
+def get_module_name_from_test(path) -> str:
+ path = pathlib.Path(path)
+ parts = path.parts
+ return parts[-3]
+
+
+def execute_tests(module_name, ansible_test_path=None) -> None:
+ if ansible_test_path is not None:
+ if ansible_test_path[-1] != "/":
+ ansible_test_path = f"{ansible_test_path}/"
+ with subprocess.Popen(
+ [
+ f"{ansible_test_path}ansible-test",
+ "network-integration",
+ "--allow-unsupported",
+ module_name,
+ ],
+ ) as process:
+ process.communicate()
+ else:
+ with subprocess.Popen(
+ [
+ "ansible-test",
+ "network-integration",
+ "--allow-unsupported",
+ module_name,
+ ],
+ ) as process:
+ process.communicate()
+
+
+def main():
+ if len(sys.argv) == 1:
+ sys.exit("File path must be passed as an argument.")
+ if is_path_module(sys.argv[1]) is True:
+ module_name = get_module_name_from_module(sys.argv[1])
+ if is_path_integration_test(sys.argv[1]) is True:
+ module_name = get_module_name_from_test(sys.argv[1])
+ if len(sys.argv) == 3: # Specify ansible-test path
+ execute_tests(module_name, sys.argv[2])
+ else:
+ execute_tests(module_name)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/meraki/scripts/sublime-build/requirements.txt b/ansible_collections/cisco/meraki/scripts/sublime-build/requirements.txt
new file mode 100644
index 000000000..dca9a9096
--- /dev/null
+++ b/ansible_collections/cisco/meraki/scripts/sublime-build/requirements.txt
@@ -0,0 +1 @@
+click
diff --git a/ansible_collections/cisco/meraki/tests/integration/inventory.networking b/ansible_collections/cisco/meraki/tests/integration/inventory.networking
new file mode 100644
index 000000000..ab8958954
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/inventory.networking
@@ -0,0 +1,42 @@
+# This is the configuration template for ansible-test network-integration tests.
+#
+# You do not need this template if you are:
+#
+# 1) Running integration tests without using ansible-test.
+# 2) Using the `--platform` option to provision temporary network instances on EC2.
+#
+# If you do not want to use the automatically provisioned temporary network instances,
+# fill in the @VAR placeholders below and save this file without the .template extension.
+#
+# NOTE: Automatic provisioning of network instances on EC2 requires an ansible-core-ci API key.
+
+[meraki]
+localhost ansible_connection="local"
+
+[meraki:vars]
+auth_key=b4aa1133085792857ae08a22ed01bc99b7e9c97d
+test_org_id=133277
+test_org_name=kbreit@insight.com
+test_net_name=Home - Appliance
+test_net_id=N_624874448297678702
+test_switch_net_name=Home - Switch
+test_wireless_net_name=Home - Wireless
+test_appliance_net_name=Home - Appliance
+test_template_name=AnsibleTemplate
+test_template_id=L_624874448297661409
+email_prefix=meraki
+email_domain=kevinbreit.net
+serial=Q2BN-MCH8-VEL5
+serial_switch=Q2HP-2C6E-GTLD
+serial_wireless=Q2KD-83UG-JL8X
+serial_switch_l3=QBSB-VLNE-E299
+serial_appliance=Q2BN-MCH8-VEL5
+# serial=QBSC-HJSL-A64F
+# serial=Q2HD-DPPR-QTAE
+# serial=Q2HP-2C6E-GTLD
+
+###
+# Example
+#
+# [vyos]
+# vyos01.example.net ansible_connection=local ansible_network_os="vyos" ansible_user=admin ansible_ssh_pass=mypassword
diff --git a/ansible_collections/cisco/meraki/tests/integration/inventory.networking.template b/ansible_collections/cisco/meraki/tests/integration/inventory.networking.template
new file mode 100644
index 000000000..3057b7f2f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/inventory.networking.template
@@ -0,0 +1,37 @@
+# This is the configuration template for ansible-test network-integration tests.
+#
+# You do not need this template if you are:
+#
+# 1) Running integration tests without using ansible-test.
+# 2) Using the `--platform` option to provision temporary network instances on EC2.
+#
+# If you do not want to use the automatically provisioned temporary network instances,
+# fill in the @VAR placeholders below and save this file without the .template extension.
+#
+# NOTE: Automatic provisioning of network instances on EC2 requires an ansible-core-ci API key.
+
+[meraki]
+localhost ansible_connection="local"
+
+[meraki:vars]
+auth_key=abc1234567890
+test_org_id=12345
+test_org_name=YourOrg
+test_net_name=YourNet
+test_net_id=54321
+test_switch_net_name=Switch Net
+test_wireless_net_name=Wireless Net
+test_template_name=YourTemplate
+test_template_id=L_12345
+email_prefix=YourEmail
+email_domain=YourDomain
+serial=YourSerial
+serial_switch=YourSwitchSerial
+serial_switch_l3=YourL3SwitchSerial
+
+
+###
+# Example
+#
+# [vyos]
+# vyos01.example.net ansible_connection=local ansible_network_os="vyos" ansible_user=admin ansible_ssh_pass=mypassword
diff --git a/ansible_collections/cisco/meraki/tests/integration/target-prefixes.network b/ansible_collections/cisco/meraki/tests/integration/target-prefixes.network
new file mode 100644
index 000000000..2516cd48f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/target-prefixes.network
@@ -0,0 +1 @@
+meraki
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/tasks/main.yml
new file mode 100644
index 000000000..768e1d251
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_action_batch/tasks/main.yml
@@ -0,0 +1,325 @@
+# 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: Block of tasks
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Query all Action Batches
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: query
+ delegate_to: localhost
+ register: query_all
+
+ - name: Query all Action Batches
+ ansible.builtin.set_fact:
+ batch_id: '{{ query_all.data[0].id }}'
+
+ - name: Asserting a values query_all.data and query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data is defined
+ - query_all is not changed
+
+ - name: Query one Action Batch job
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: query
+ action_batch_id: '{{ batch_id }}'
+ delegate_to: localhost
+ register: query_one
+
+ - name: Debug query_one
+ ansible.builtin.debug:
+ var: query_one | length
+
+ - name: Assert if query_one.data has been changed
+ ansible.builtin.assert:
+ that:
+ - query_one.data is defined
+
+ - name: Create an Action Batch job
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ actions:
+ - resource: '/organizations/{{ test_org_id }}/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch1'
+ productTypes:
+ - 'switch'
+ delegate_to: localhost
+ register: create_one
+
+ - name: Assert a various values
+ ansible.builtin.assert:
+ that:
+ - create_one.data.id is defined
+ - create_one.data.status.completed == false
+ - create_one.data.actions.0.resource == '/organizations/{{ test_org_id }}/networks'
+ - create_one.data.actions.0.operation == 'create'
+ - create_one.data.actions.0.body.name == 'AnsibleActionBatch1'
+ - create_one.data.actions[0].body.product_types[0] == 'switch'
+
+ - name: Set fact new_batch_id
+ ansible.builtin.set_fact:
+ new_batch_id: '{{ create_one.data.id }}'
+
+ - name: Create an Action Batch job to be deleted later
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ actions:
+ - resource: '/organizations/{{ test_org_id }}/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch100'
+ productTypes:
+ - 'switch'
+ delegate_to: localhost
+ register: batch_delete_id
+
+ - name: Set fact deletable_batch_id
+ ansible.builtin.set_fact:
+ deletable_batch_id: '{{ batch_delete_id.data.id }}'
+
+ - name: Update Action Batch job with a body
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ action_batch_id: '{{ new_batch_id }}'
+ actions:
+ - resource: '/organizations/{{ test_org_id }}/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch1'
+ productTypes:
+ - 'switch'
+ delegate_to: localhost
+ register: create_one_idempotent
+ failed_when: false
+ changed_when: false
+
+ - name: Asset a value create_one_idempotent.msg
+ ansible.builtin.assert:
+ that:
+ - create_one_idempotent.msg == 'Body cannot be updated on existing job.'
+
+ - name: Update Action Batch job
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ action_batch_id: '{{ new_batch_id }}'
+ synchronous: false
+ delegate_to: localhost
+ register: update_one
+
+ - name: Assert a value update_one
+ ansible.builtin.assert:
+ that:
+ - update_one.data.id is defined
+ - update_one is changed
+
+ - name: Update Action Batch job with idempotentcy
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ action_batch_id: '{{ new_batch_id }}'
+ synchronous: false
+ delegate_to: localhost
+ register: update_one_idempotent
+
+ - name: Assert value update_one_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_one_idempotent.data.id is defined
+ - update_one_idempotent is not changed
+
+ - name: Create an Action Batch job with multiple actions
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ actions:
+ - resource: '/organizations/{{ test_org_id }}/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch2'
+ productTypes:
+ - 'switch'
+ - resource: '/organizations/{{ test_org_id }}/networks'
+ operation: 'create'
+ body:
+ name: 'AnsibleActionBatch3'
+ productTypes:
+ - 'switch'
+ delegate_to: localhost
+ register: create_two
+
+ - name: Set fact create_two_batch_id
+ ansible.builtin.set_fact:
+ create_two_batch_id: '{{ create_two.data.id }}'
+
+ - name: Set single action job as confirmed
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ action_batch_id: '{{ new_batch_id }}'
+ confirmed: true
+ delegate_to: localhost
+ register: confirm_one
+
+ - name: Assert confirm_one
+ ansible.builtin.assert:
+ that:
+ - confirm_one.data.id is defined
+ - confirm_one is changed
+
+ - name: Set two action job as confirmed
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ action_batch_id: '{{ create_two_batch_id }}'
+ confirmed: true
+ delegate_to: localhost
+ register: confirm_two
+
+ - name: Assert confirm_two
+ ansible.builtin.assert:
+ that:
+ - confirm_two.data.id is defined
+ - confirm_two is changed
+
+ - name: Gather newly created networks
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: query
+ action_batch_id: '{{ create_two_batch_id }}'
+ delegate_to: localhost
+ register: gather_networks
+
+ - name: If value is not equal Validation failed...
+ ansible.builtin.assert:
+ that:
+ - gather_networks.data.actions.0.status.errors.0 != 'Validation failed\: Name has already been taken'
+
+ - name: Set fact new_networks
+ ansible.builtin.set_fact:
+ new_networks: "{{ gather_networks.data.status.created_resources | selectattr('uri', 'defined') | map(attribute='uri') | list | unique }}"
+
+ - name: Initialize an empty list
+ ansible.builtin.set_fact:
+ delete_actions: []
+
+ - name: Set fact delete_actions
+ ansible.builtin.set_fact:
+ delete_actions: "{{ delete_actions + [{'resource': item, 'operation': 'destroy'}] }}"
+ loop: '{{ new_networks }}'
+
+ - name: Create an Action Batch job to delete newly created networks
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ actions: '{{ delete_actions }}'
+ delegate_to: localhost
+ register: delete_two
+
+ - name: Set fact deletable_bat
+ ansible.builtin.set_fact:
+ deletable_batch_id: '{{ delete_two.data.id }}'
+
+ - name: Delete an Action Batch job
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: absent
+ action_batch_id: '{{ deletable_batch_id }}'
+ actions: '{{ delete_actions }}'
+ delegate_to: localhost
+ register: delete_two_networks
+
+ always:
+ - name: Get network ID for AnsibleActionBatch2
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: query
+ net_name: AnsibleActionBatch2
+ delegate_to: localhost
+ register: aab2
+
+ - name: Set value aab2_id
+ ansible.builtin.set_fact:
+ aab2_id: '{{ aab2.data.id }}'
+ failed_when: false
+ changed_when: false
+
+ - name: Get network ID for AnsibleActionBatch3
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: query
+ net_name: AnsibleActionBatch3
+ delegate_to: localhost
+ register: aab3
+
+ - name: Set value aab3_id
+ ansible.builtin.set_fact:
+ aab3_id: '{{ aab3.data.id }}'
+ failed_when: false
+ changed_when: false
+
+ - name: Delete new networks before other tests
+ meraki_action_batch:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: present
+ confirmed: true
+ actions:
+ - resource: '/networks/{{ aab2_id }}'
+ operation: 'destroy'
+ - resource: '/networks/{{ aab3_id }}'
+ operation: 'destroy'
+ delegate_to: localhost
+ register: delete_networks
+ when:
+ - aab2 is defined
+ - aab3 is defined
+
+ # - name: Get all action batch jobs
+ # meraki_action_batch:
+ # auth_key: '{{ auth_key }}'
+ # org_name: '{{ test_org_name }}'
+ # state: query
+ # delegate_to: localhost
+ # register: all_jobs
+
+ # - ansible.builtin.debug:
+ # var: all_jobs
+
+ # - name: Delete all action batch jobs
+ # meraki_action_batch:
+ # auth_key: '{{ auth_key }}'
+ # org_name: '{{ test_org_name }}'
+ # state: absent
+ # action_batch_id: '{{ item.id }}'
+ # loop: '{{ all_jobs.data }}'
+ # delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/tasks/main.yml
new file mode 100644
index 000000000..89374c0b4
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/tasks/main.yml
@@ -0,0 +1,428 @@
+# Test code for the Meraki Admin 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: Block of tests
+ block:
+ - name: Create new administrator in check mode
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jane Doe
+ email: '{{ email_prefix }}+janedoe@{{ email_domain }}'
+ org_access: read-only
+ delegate_to: localhost
+ check_mode: true
+ register: create_org_check
+
+ - name: Create new admin check mode assertion
+ ansible.builtin.assert:
+ that:
+ - create_org_check is changed
+ - 'create_org_check.data.name == "Jane Doe"'
+
+ - name: Create new administrator
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jane Doe
+ email: '{{ email_prefix }}+janedoe@{{ email_domain }}'
+ org_access: read-only
+ delegate_to: localhost
+ register: create_orgaccess
+
+ - name: Create new admin assertion
+ ansible.builtin.assert:
+ that:
+ - create_orgaccess.changed == true
+ - 'create_orgaccess.data.name == "Jane Doe"'
+
+ - name: Delete recently created administrator with check mode
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ email: '{{ email_prefix }}+janedoe@{{ email_domain }}'
+ delegate_to: localhost
+ register: delete_one_check
+ check_mode: true
+
+ - name: Assert if delete_one_check has been changed
+ ansible.builtin.assert:
+ that:
+ - delete_one_check is changed
+
+ - name: Delete recently created administrator
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ email: '{{ email_prefix }}+janedoe@{{ email_domain }}'
+ delegate_to: localhost
+ register: delete_one
+
+ - name: Create new administrator with org_id
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ name: Jane Doe
+ email: '{{ email_prefix }}+janedoe@{{ email_domain }}'
+ orgAccess: read-only
+ delegate_to: localhost
+ register: create_orgaccess_id
+
+ - name: Create new admin assertion
+ ansible.builtin.assert:
+ that:
+ - create_orgaccess_id.changed == true
+ - 'create_orgaccess_id.data.name == "Jane Doe"'
+
+ - name: Create administrator with tags with check mode
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: John Doe
+ email: '{{ email_prefix }}+johndoe@{{ email_domain }}'
+ orgAccess: none
+ tags:
+ - tag: production
+ access: read-only
+ - tag: beta
+ access: full
+ delegate_to: localhost
+ register: create_tags_check
+ check_mode: true
+
+ - name: Assert if create_tags_check has been changed
+ ansible.builtin.assert:
+ that:
+ - create_tags_check is changed
+ - create_tags_check.data.name == "John Doe"
+ - create_tags_check.data.tags | length == 2
+
+ - name: Create administrator with tags
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: John Doe
+ email: '{{ email_prefix }}+johndoe@{{ email_domain }}'
+ orgAccess: none
+ tags:
+ - tag: production
+ access: read-only
+ - tag: beta
+ access: full
+ delegate_to: localhost
+ register: create_tags
+
+ - name: Assert if create_tags has been changed
+ ansible.builtin.assert:
+ that:
+ - create_tags.changed == true
+ - create_tags.data.name == "John Doe"
+ - create_tags.data.tags | length == 2
+
+ - name: Create administrator with invalid tags
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jake Doe
+ email: '{{ email_prefix }}+jakedoe@{{ email_domain }}'
+ orgAccess: none
+ tags:
+ - tag: production
+ access: read-only
+ - tag: alpha
+ access: invalid
+ delegate_to: localhost
+ register: create_tags_invalid
+ failed_when: false
+ changed_when: false
+
+ - name: Assert if create_tags_invalid
+ ansible.builtin.assert:
+ that:
+ - '"400" in create_tags_invalid.msg'
+ - '"must be one of" in create_tags_invalid.msg'
+
+ - name: Create administrator with invalid tag permission
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jake Doe
+ email: '{{ email_prefix }}+jakedoe@{{ email_domain }}'
+ orgAccess: none
+ tags:
+ - tag: production
+ access: read-only
+ - tag: beta
+ access: invalid
+ delegate_to: localhost
+ register: create_tags_invalid_permission
+ failed_when: false
+ changed_when: false
+
+ - name: Assert if create_tags_invalid_permission
+ ansible.builtin.assert:
+ that:
+ - '"400" in create_tags_invalid_permission.msg'
+ - '"must be one of" in create_tags_invalid_permission.msg'
+
+ - name: Make sure TestNet and TestNet2 are created
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ item }}'
+ type: switch
+ register: new_networks
+ loop:
+ - TestNet
+ - TestNet2
+
+ - name: Set facts testnet_id, testnet2_id
+ ansible.builtin.set_fact:
+ testnet_id: new_networks.results[0].data.id
+ testnet2_id: new_networks.results[1].data.id
+
+ - name: Create administrator with networks with check mode
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jim Doe
+ email: '{{ email_prefix }}+jimdoe@{{ email_domain }}'
+ orgAccess: none
+ networks:
+ - network: TestNet
+ access: read-only
+ - id: testnet2_id
+ access: full
+ delegate_to: localhost
+ register: create_network_check
+ check_mode: true
+
+ - name: Assert if create_network_check has been changed
+ ansible.builtin.assert:
+ that:
+ - create_network_check is changed
+ - create_network_check.data.name == "Jim Doe"
+ - create_network_check.data.networks | length == 2
+
+ - name: Create administrator with networks
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jim Doe
+ email: '{{ email_prefix }}+jimdoe@{{ email_domain }}'
+ orgAccess: none
+ networks:
+ - network: TestNet
+ access: read-only
+ - network: TestNet2
+ access: full
+ delegate_to: localhost
+ register: create_network
+
+ - name: Assert if create_network has been changed
+ ansible.builtin.assert:
+ that:
+ - create_network.changed == true
+ - create_network.data.name == "Jim Doe"
+ - create_network.data.networks | length == 2
+
+ - name: Update administrator with check mode
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jim Doe
+ email: '{{ email_prefix }}+jimdoe@{{ email_domain }}'
+ orgAccess: none
+ networks:
+ - network: TestNet
+ access: full
+ delegate_to: localhost
+ register: update_network_check
+ check_mode: true
+
+ - name: Debug update_network_check
+ ansible.builtin.debug:
+ var: update_network_check
+
+ - name: Assert if update_network_check has been changed
+ ansible.builtin.assert:
+ that:
+ - update_network_check is changed
+ - update_network_check.data.networks.0.access == "full"
+ - update_network_check.data.networks | length == 1
+
+ - name: Update administrator
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jim Doe
+ email: '{{ email_prefix }}+jimdoe@{{ email_domain }}'
+ orgAccess: none
+ networks:
+ - network: TestNet
+ access: full
+ delegate_to: localhost
+ register: update_network
+
+ - name: Assert if update_network has been changed
+ ansible.builtin.assert:
+ that:
+ - update_network.changed == true
+ - update_network.data.networks.0.access == "full"
+ - update_network.data.networks | length == 1
+
+ - name: Update administrator for idempotency check with check mode
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jim Doe
+ email: '{{ email_prefix }}+jimdoe@{{ email_domain }}'
+ orgAccess: none
+ networks:
+ - network: TestNet
+ access: full
+ delegate_to: localhost
+ register: update_network_idempotent_check
+ check_mode: true
+
+ - name: Debug update_network_idempotent_check
+ ansible.builtin.debug:
+ var: update_network_idempotent_check
+
+ - name: Assert if update_network_idempotent_check has not changed
+ ansible.builtin.assert:
+ that:
+ - update_network_idempotent_check is not changed
+
+ - name: Update administrator for idempotency
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: Jim Doe
+ email: '{{ email_prefix }}+jimdoe@{{ email_domain }}'
+ orgAccess: none
+ networks:
+ - network: TestNet
+ access: full
+ delegate_to: localhost
+ register: update_network_idempotent
+
+ - name: Assert if update_network_idempotent has been not changed
+ ansible.builtin.assert:
+ that:
+ - update_network_idempotent.changed == false
+ - update_network_idempotent.data is defined
+
+ - name: Create administrator with invalid network
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ name: John Doe
+ email: '{{ email_prefix }}+John@{{ email_domain }}'
+ orgAccess: none
+ networks:
+ - network: TestNetFake
+ access: read-only
+ delegate_to: localhost
+ register: create_network_invalid
+ failed_when: false
+ changed_when: false
+
+ - name: Assert if message in create_network_invalid
+ ansible.builtin.assert:
+ that:
+ - '"No network found with the name" in create_network_invalid.msg'
+ # - '"400" in create_network_invalid.msg'
+
+ - name: Query all administrators
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ delegate_to: localhost
+ register: query_all
+
+ - name: Debug query_all
+ ansible.builtin.debug:
+ var: query_all
+
+ - name: Assert query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data | length == 4
+ - query_all.changed == False
+
+ - name: Query admin by name
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ name: Jane Doe
+ delegate_to: localhost
+ register: query_name
+
+ - name: Query admin by email
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ email: '{{ email_prefix }}+janedoe@{{ email_domain }}'
+ delegate_to: localhost
+ register: query_email
+
+ - name: Check the name and email of query_name, query_email
+ ansible.builtin.assert:
+ that:
+ - query_name.data.name == "Jane Doe"
+ - 'query_email.data.email == "{{ email_prefix }}+janedoe@{{ email_domain }}"'
+
+ always:
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+ - name: Delete administrators
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ email: '{{ item }}'
+ delegate_to: localhost
+ register: delete_all
+ failed_when: false
+ changed_when: false
+ loop:
+ - '{{ email_prefix }}+janedoe@{{ email_domain }}'
+ - '{{ email_prefix }}+johndoe@{{ email_domain }}'
+ - '{{ email_prefix }}+jimdoe@{{ email_domain }}'
+
+ - name: Query all administrators
+ cisco.meraki.meraki_admin:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ delegate_to: localhost
+ register: query_all_deleted
+
+ - name: Assert query_all_deleted
+ ansible.builtin.assert:
+ that:
+ - query_all_deleted.data | length == 1
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/aliases
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/aliases
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/tasks/main.yml
new file mode 100644
index 000000000..de9d20524
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/tasks/main.yml
@@ -0,0 +1,356 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2020, 2023 Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Create network with type wireless
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ type: wireless
+ timezone: America/Chicago
+ delegate_to: localhost
+
+ - name: Create webhook
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ name: Test webhook
+ url: "https://webhook.site/f113313e-fbad-464d-9fbe-ed2392e1ee8c"
+ shared_secret: "abc123"
+ delegate_to: localhost
+ register: webhook
+
+ - name: Update settings with check mode
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: false
+ snmp: false
+ http_server_ids:
+ - "aHR0cHM6Ly93ZWJob29rLnNpdGUvZjExMzMxM2UtZmJhZC00NjRkLTlmYmUtZWQyMzkyZTFlZThjCg=="
+ alerts:
+ - alert_type: "gatewayDown"
+ enabled: true
+ filters:
+ timeout: 60
+ alert_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: false
+ snmp: false
+ - alert_type: "usageAlert"
+ enabled: true
+ filters:
+ period: 1200
+ threshold: 104857600
+ alert_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: true
+ snmp: false
+ delegate_to: localhost
+ register: update_check
+ check_mode: true
+
+ - name: Assert update_check
+ ansible.builtin.assert:
+ that:
+ - update_check.data is defined
+ - update_check.diff is defined
+ - update_check is changed
+ - update_check.data.default_destinations.http_server_ids | length == 1
+
+ - name: Update settings
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: false
+ snmp: false
+ alerts:
+ - alert_type: "gatewayDown"
+ enabled: true
+ filters:
+ timeout: 60
+ alert_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: false
+ snmp: false
+ - alert_type: "usageAlert"
+ enabled: true
+ filters:
+ period: 1200
+ threshold: 104857600
+ alert_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: true
+ snmp: false
+ delegate_to: localhost
+ register: update
+
+ - name: Assert update
+ ansible.builtin.assert:
+ that:
+ - update.data is defined
+ - update.diff is defined
+ - update is changed
+ - update.data.default_destinations.emails | length == 2
+
+ - name: Update settings idempotent
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: false
+ snmp: false
+ alerts:
+ - alert_type: "gatewayDown"
+ enabled: true
+ filters:
+ timeout: 60
+ alert_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: false
+ snmp: false
+ - alert_type: "usageAlert"
+ enabled: true
+ filters:
+ period: 1200
+ threshold: 104857600
+ alert_destinations:
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ all_admins: true
+ snmp: false
+ delegate_to: localhost
+ register: update_idempotent
+
+ - name: Assert update_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_idempotent.data is defined
+ - update_idempotent is not changed
+
+ - name: Remove Email from settings
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ all_admins: false
+ snmp: false
+ emails: []
+ alerts:
+ - alert_type: "gatewayDown"
+ enabled: true
+ filters:
+ timeout: 60
+ alert_destinations:
+ all_admins: false
+ snmp: false
+ emails: []
+ - alert_type: "usageAlert"
+ enabled: true
+ filters:
+ period: 1200
+ threshold: 104857600
+ alert_destinations:
+ all_admins: true
+ snmp: false
+ delegate_to: localhost
+ register: removeemailupdate
+
+ - name: Assert remove email update
+ ansible.builtin.assert:
+ that:
+ - removeemailupdate.data is defined
+ - removeemailupdate.diff is defined
+ - removeemailupdate is changed
+ - removemeailupdate.data.default_destinations.email is not defined
+
+ - name: Remove email settings idempotent
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ all_admins: false
+ snmp: false
+ alerts:
+ - alert_type: "gatewayDown"
+ enabled: true
+ filters:
+ timeout: 60
+ alert_destinations:
+ all_admins: false
+ snmp: false
+ emails: []
+ - alert_type: "usageAlert"
+ enabled: true
+ filters:
+ period: 1200
+ threshold: 104857600
+ alert_destinations:
+ all_admins: true
+ snmp: false
+ delegate_to: localhost
+ register: removeemailupdate_idempotent
+
+ - name: Remove Email Assert update_idempotent
+ ansible.builtin.assert:
+ that:
+ - removeemailupdate_idempotent.data is defined
+ - removeemailupdate_idempotent is not changed
+
+ - name: Set default destination email address and HTTP server ID
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ all_admins: false
+ snmp: false
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ http_server_ids:
+ - '{{ webhook.data.id }}'
+ delegate_to: localhost
+ register: set_default_lists
+
+ - name: Assert default destination items
+ ansible.builtin.assert:
+ that:
+ - set_default_lists.data is defined
+ - set_default_lists is changed
+ - set_default_lists.data.default_destinations.emails | length == 2
+ - set_default_lists.data.default_destinations.http_server_ids| length == 1
+
+ - name: Update default destinations without HTTP server IDs for testing purposes
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ all_admins: false
+ snmp: false
+ emails:
+ - '{{ email_prefix }}@{{ email_domain }}'
+ - '{{ email_prefix }}2@{{ email_domain }}'
+ delegate_to: localhost
+
+ - name: Query all settings
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: query
+ delegate_to: localhost
+ register: query_for_webhook
+
+ - name: Check for HTTP server IDs
+ ansible.builtin.assert:
+ that:
+ - query_for_webhook.data.default_destinations.http_server_ids | length == 1
+
+ - name: Clear default destination email addresses
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ all_admins: false
+ snmp: false
+ emails:
+ -
+ delegate_to: localhost
+ register: clear_emails
+
+ - name: Assert emails were cleared
+ ansible.builtin.assert:
+ that:
+ - clear_emails.data.default_destinations.emails | length == 0
+
+ - name: Clear default destination HTTP server IDs
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ default_destinations:
+ all_admins: false
+ snmp: false
+ http_server_ids:
+ -
+ delegate_to: localhost
+ register: clear_http_ids
+
+ - name: Assert that HTTP servers were cleared
+ ansible.builtin.assert:
+ that:
+ - clear_http_ids.data.default_destinations.http_server_ids | length == 0
+
+ - name: Query all settings
+ cisco.meraki.meraki_alert:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: query
+ delegate_to: localhost
+ register: query_all
+
+ - name: Asset query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data is defined
+
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+
+ always:
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/tasks/main.yml
new file mode 100644
index 000000000..9fd5b0dba
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/tasks/main.yml
@@ -0,0 +1,210 @@
+# 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: Block of test
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Query all configuration templates
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ register: get_all
+
+ - name: Delete non-existant configuration template
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ config_template: FakeConfigTemplate
+ register: deleted
+ failed_when: false
+ changed_when: false
+
+ - name: Assert a delated.msg
+ ansible.builtin.assert:
+ that:
+ - '"No configuration template named" in deleted.msg'
+
+ - name: Create a network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ type: appliance
+ delegate_to: localhost
+ register: net_info
+
+ - name: Set fact net_id
+ ansible.builtin.set_fact:
+ net_id: '{{ net_info.data.id }}'
+
+ - name: Bind a template to a network with check mode
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ config_template: '{{ test_template_name }}'
+ check_mode: true
+ register: bind_check
+
+ - name: Bind a template to a network
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ config_template: '{{ test_template_name }}'
+ register: bind
+
+ - name: Assert bind
+ ansible.builtin.assert:
+ that:
+ bind.changed == True
+
+ - name: Assert if bind_check has been changed
+ ansible.builtin.assert:
+ that:
+ bind_check is changed
+
+ - name: Bind a template to a network when it's already bound
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ config_template: '{{ test_template_name }}'
+ register: bind_invalid
+ failed_when: false
+ changed_when: false
+
+ - name: Assert if bind_invalid has been not changed
+ ansible.builtin.assert:
+ that:
+ - bind_invalid.changed == False
+
+ - name: Unbind a template from a network
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ config_template: '{{ test_template_name }}'
+ register: unbind
+
+ - name: Assert if unbind has been changed
+ ansible.builtin.assert:
+ that:
+ unbind.changed == True
+
+ - name: Unbind a template from a network when it's not bound
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ config_template: '{{ test_template_name }}'
+ register: unbind_invalid
+
+ - name: Assert if unbind_invalid has been not changed
+ ansible.builtin.assert:
+ that:
+ unbind_invalid.changed == False
+
+ - name: Bind a template to a network via id
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ config_template: '{{ test_template_name }}'
+ register: bind_id
+
+ - name: Assert if bind_id has been changed
+ ansible.builtin.assert:
+ that:
+ bind_id.changed == True
+
+ - name: Bind a template to a network via id for idempotency
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ config_template: '{{ test_template_name }}'
+ register: bind_id_idempotent
+
+ - name: Assert bind_id_idempotent
+ ansible.builtin.assert:
+ that:
+ - bind_id_idempotent.changed == False
+ - bind_id_idempotent.data is defined
+
+ - name: Unbind a template from a network via id with check mode
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ config_template: '{{ test_template_name }}'
+ check_mode: true
+ register: unbind_id_check
+
+ - name: Assert if unbind_id_check has been changed
+ ansible.builtin.assert:
+ that:
+ unbind_id_check is changed
+
+ - name: Unbind a template from a network via id
+ cisco.meraki.meraki_config_template:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ config_template: '{{ test_template_name }}'
+ register: unbind_id
+
+ - name: Assert if unbind_id_check has been changed
+ ansible.builtin.assert:
+ that:
+ unbind_id.changed == True
+
+ # This is disabled by default since they can't be created via API
+ # - name: Delete sacrificial template with check mode
+ # cisco.meraki.meraki_config_template:
+ # auth_key: '{{ auth_key }}'
+ # state: absent
+ # org_name: '{{ test_org_name }}'
+ # config_template: sacrificial_template
+ # check_mode: true
+ # register: delete_template_check
+
+ # This is disabled by default since they can't be created via API
+ # - name: Delete sacrificial template
+ # cisco.meraki.meraki_config_template:
+ # auth_key: '{{ auth_key }}'
+ # state: absent
+ # org_name: '{{ test_org_name }}'
+ # config_template: sacrificial_template
+ # output_level: debug
+ # register: delete_template
+
+ # - ansible.builtin.debug:
+ # var: delete_template
+
+ always:
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/aliases
new file mode 100644
index 000000000..89aea537d
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/aliases
@@ -0,0 +1 @@
+unsupported \ No newline at end of file
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/tasks/main.yml
new file mode 100644
index 000000000..17d5407c9
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/tasks/main.yml
@@ -0,0 +1,265 @@
+---
+- name: Block of tests
+ block:
+ # This is commented out because a device cannot be unclaimed via API
+ # - name: Claim a device into an organization
+ # cisco.meraki.meraki_device:
+ # auth_key: '{{ auth_key }}'
+ # org_name: '{{ test_org_name }}'
+ # serial: '{{ serial }}'
+ # state: present
+ # delegate_to: localhost
+ # register: claim_device_org
+
+ # - ansible.builtin.assert:
+ # that:
+ # - claim_device_org.changed == true
+
+ - name: Create network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ type: appliance
+ state: present
+ register: net_info
+
+ - name: Set fact net_id
+ ansible.builtin.set_fact:
+ net_id: '{{ net_info.data.id }}'
+
+ - name: Query status of all devices in an organization
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ state: query
+ delegate_to: localhost
+ register: query_device_org
+
+ - name: Debug query_device_org
+ ansible.builtin.debug:
+ msg: '{{ query_device_org }}'
+
+ - name: Claim a device into a network
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ state: present
+ delegate_to: localhost
+ register: claim_device
+
+ - name: Debug claim_device
+ ansible.builtin.debug:
+ msg: '{{ claim_device }}'
+
+ - name: Assert if claim_device has been changed
+ ansible.builtin.assert:
+ that:
+ - claim_device.changed == true
+
+ - name: Query all devices in one network by network ID
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ state: query
+ delegate_to: localhost
+ register: query_one_net_id
+
+ - name: Debug query_one_net_id
+ ansible.builtin.debug:
+ msg: '{{ query_one_net_id }}'
+
+ - name: Query all devices in one network
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: query
+ delegate_to: localhost
+ register: query_one_net
+
+ - name: Debug query_one_net
+ ansible.builtin.debug:
+ msg: '{{ query_one_net }}'
+
+ - name: Query device by serial
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ serial: '{{ serial }}'
+ state: query
+ delegate_to: localhost
+ register: query_serial_no_net
+
+ - name: Debug query_serial_no_net
+ ansible.builtin.debug:
+ msg: '{{ query_serial_no_net }}'
+
+ - name: Query device by serial
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ state: query
+ delegate_to: localhost
+ register: query_serial
+
+ - name: Debug query_serial
+ ansible.builtin.debug:
+ msg: '{{ query_serial }}'
+
+ - name: Assert if query_serial has been not changed
+ ansible.builtin.assert:
+ that:
+ - query_serial.changed == False
+
+ - name: Query uplink information for a device
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ state: query
+ query: uplink
+ delegate_to: localhost
+ register: query_serial_uplink
+
+ - name: Debug query_serial_uplink
+ ansible.builtin.debug:
+ msg: '{{ query_serial_uplink }}'
+
+ - name: Query LLDP/CDP information about a device
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ lldp_cdp_timespan: 6000
+ state: query
+ query: lldp_cdp
+ delegate_to: localhost
+ register: query_serial_lldp_cdp
+
+ - name: Debug query_serial_lldp_cdp
+ ansible.builtin.debug:
+ msg: '{{ query_serial_lldp_cdp }}'
+
+ - name: Query a device by hostname
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ hostname: test-hostname
+ state: query
+ delegate_to: localhost
+ register: query_hostname
+
+ - name: Debug query_hostname
+ ansible.builtin.debug:
+ msg: '{{ query_hostname }}'
+
+ - name: Query a device by model
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ model: MR26
+ state: query
+ delegate_to: localhost
+ register: query_model
+
+ - name: Debug query_model
+ ansible.builtin.debug:
+ msg: '{{ query_model }}'
+
+ - name: Change device name for future test
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ hostname: mx60-temporary
+ address: 1060 W. Addison St., Chicago, IL
+ lat: 41.948038
+ lng: -87.65568
+ tags: recently-added
+ state: present
+ move_map_marker: true
+ note: Test device notes
+ delegate_to: localhost
+ register: update_device
+
+ - name: Update a device
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ hostname: mx60
+ address: 1060 W. Addison St., Chicago, IL
+ lat: 41.948038
+ lng: -87.65568
+ tags: recently-added
+ state: present
+ move_map_marker: true
+ note: Test device notes
+ delegate_to: localhost
+ register: update_device
+
+ - name: Assert update_device
+ ansible.builtin.assert:
+ that:
+ - update_device.changed == true
+ - update_device.data.0.notes == "Test device notes"
+ - '"1060 W. Addison St., Chicago, IL" in update_device.data.0.address'
+
+ - name: Update a device with idempotency
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ name: mx60
+ address: 1060 W. Addison St., Chicago, IL
+ lat: 41.948038
+ lng: -87.65568
+ tags: recently-added
+ state: present
+ move_map_marker: true
+ note: Test device notes
+ delegate_to: localhost
+ register: update_device_idempotent
+
+ - name: Debug update_device_idempotent
+ ansible.builtin.debug:
+ msg: '{{ update_device_idempotent }}'
+
+ - name: Assert update_device_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_device_idempotent.changed == False
+ - update_device_idempotent.data is defined
+
+ always:
+ - name: Remove a device from a network
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ serial: '{{ serial }}'
+ state: absent
+ delegate_to: localhost
+ register: delete_device
+
+ - name: Debug delete_device
+ ansible.builtin.debug:
+ msg: '{{ delete_device }}'
+
+ - name: Assert if delete_device has been changed
+ ansible.builtin.assert:
+ that:
+ - delete_device.changed == true
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/main.yml
new file mode 100644
index 000000000..60aa04a96
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/main.yml
@@ -0,0 +1,7 @@
+# Test code for the Meraki Firewalled Services 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
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/tests.yml
new file mode 100644
index 000000000..bcb329e3a
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/tests.yml
@@ -0,0 +1,234 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Create network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ type: appliance
+ register: create
+
+ - name: Set fact net_id
+ ansible.builtin.set_fact:
+ net_id: '{{ create.data.id }}'
+
+ - name: Set icmp service to blocked with check mode
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: ICMP
+ access: blocked
+ register: icmp_blocked_check
+ check_mode: true
+
+ - name: Debug icmp_blocked_check
+ ansible.builtin.debug:
+ var: icmp_blocked_check
+
+ - name: Assert if icmp_blocked_check has been changed
+ ansible.builtin.assert:
+ that:
+ - icmp_blocked_check.data is defined
+ - icmp_blocked_check is changed
+
+ - name: Set icmp service to blocked
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: ICMP
+ access: blocked
+ register: icmp_blocked
+
+ - name: Debug icmp_blocked
+ ansible.builtin.debug:
+ var: icmp_blocked
+
+ - name: Assert ig icmp_blocked has been changed
+ ansible.builtin.assert:
+ that:
+ - icmp_blocked.data is defined
+ - icmp_blocked is changed
+
+ - name: Set icmp service to blocked with idempotency
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: ICMP
+ access: blocked
+ register: icmp_blocked_idempotent
+
+ - name: Debug icmp_blocked_idempotent
+ ansible.builtin.debug:
+ var: icmp_blocked_idempotent
+
+ - name: Assert icmp_blocked_idempotent
+ ansible.builtin.assert:
+ that:
+ - icmp_blocked_idempotent.data is defined
+ - icmp_blocked_idempotent is not changed
+
+ - name: Set icmp service to restricted with check mode
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: web
+ access: restricted
+ allowed_ips:
+ - 192.0.1.1
+ - 192.0.1.2
+ check_mode: true
+ register: web_restricted_check
+
+ - name: Debug web_restricted_check
+ ansible.builtin.debug:
+ var: web_restricted_check
+
+ - name: Assert web_restricted_check
+ ansible.builtin.assert:
+ that:
+ - web_restricted_check.data is defined
+ - web_restricted_check is changed
+
+ - name: Set icmp service to restricted
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: web
+ access: restricted
+ allowed_ips:
+ - 192.0.1.1
+ - 192.0.1.2
+ register: web_restricted
+
+ - name: Debug web_restricted
+ ansible.builtin.debug:
+ var: web_restricted
+
+ - name: Assert web_restricted
+ ansible.builtin.assert:
+ that:
+ - web_restricted.data is defined
+ - web_restricted is changed
+
+ - name: Set icmp service to restricted with idempotency
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: web
+ access: restricted
+ allowed_ips:
+ - 192.0.1.1
+ - 192.0.1.2
+ register: web_restricted_idempotent
+
+ - name: Debug web_restricted_idempotent
+ ansible.builtin.debug:
+ var: web_restricted_idempotent
+
+ - name: Assert web_restricted_idempotent
+ ansible.builtin.assert:
+ that:
+ - web_restricted_idempotent.data is defined
+ - web_restricted_idempotent is not changed
+
+ - name: Test error for access restricted and allowed_ips
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: web
+ access: unrestricted
+ allowed_ips:
+ - 192.0.1.1
+ - 192.0.1.2
+ register: access_error
+ failed_when: false
+ changed_when: false
+
+ - name: Assert access_error
+ ansible.builtin.assert:
+ that:
+ - 'access_error.msg == "allowed_ips is only allowed when access is restricted."'
+
+ - name: Query appliance services with net_id
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ register: query_appliance_id
+
+ - name: Debug query_appliance_id
+ ansible.builtin.debug:
+ var: query_appliance_id
+
+ - name: Assert query_appliance_id
+ ansible.builtin.assert:
+ that:
+ - query_appliance_id.data is defined
+
+
+ - name: Query appliance services
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ register: query_appliance
+
+ - name: Debug query_appliance
+ ansible.builtin.debug:
+ var: query_appliance
+
+ - name: Assert query_appliance
+ ansible.builtin.assert:
+ that:
+ - query_appliance.data is defined
+
+ - name: Query services
+ cisco.meraki.meraki_firewalled_services:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ service: ICMP
+ register: query_service
+
+ - name: Debug query_service
+ ansible.builtin.debug:
+ var: query_service
+
+ - name: Assert query_service
+ ansible.builtin.assert:
+ that:
+ - query_service.data is defined
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+ always:
+ - name: Delete all networks
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/tasks/main.yml
new file mode 100644
index 000000000..56c98e50f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/tasks/main.yml
@@ -0,0 +1,298 @@
+# Test code for the Meraki Management Interface module
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tasks
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Set fact net_name
+ ansible.builtin.set_fact:
+ net_name: TestNet - Appliance
+
+ - name: 'Create test network {{ net_name }}'
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_name: '{{ net_name }}'
+ type: appliance
+ delegate_to: localhost
+ register: net
+
+ - name: Set fact net_id
+ ansible.builtin.set_fact:
+ net_id: '{{ net.data.id }}'
+
+ - name: Test providing wan_enabled to an MS network
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ test_switch_net_name }}'
+ serial: '{{ serial_switch }}'
+ wan1:
+ wan_enabled: enabled
+ using_static_ip: false
+ delegate_to: localhost
+ register: ms_not_configured
+
+ - name: Debug ms_not_configured
+ ansible.builtin.debug:
+ var: ms_not_configured
+
+ - name: Assert ms_not_configured
+ ansible.builtin.assert:
+ that:
+ - ms_not_configured.data is defined
+
+ - name: Set management interface on switch
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ test_switch_net_name }}'
+ serial: '{{ serial_switch }}'
+ wan1:
+ using_static_ip: false
+ vlan: 3
+ delegate_to: localhost
+ register: set_switch_mgmt
+
+ - name: Debug set_switch_mgmt
+ ansible.builtin.debug:
+ var: set_switch_mgmt
+
+ - name: Assert set_switch_mgmt has been defined
+ ansible.builtin.assert:
+ that:
+ - set_switch_mgmt.data is defined
+
+ - name: Query non-MX network
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ test_switch_net_name }}'
+ serial: '{{ serial_switch }}'
+ delegate_to: localhost
+ register: non_mx_network
+
+ - name: Debug non_mx_network
+ ansible.builtin.debug:
+ var: non_mx_network
+
+ - name: Assert non_mx_network
+ ansible.builtin.assert:
+ that:
+ - non_mx_network.data is defined
+
+ - name: Reset management interface on switch
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ test_switch_net_name }}'
+ serial: '{{ serial_switch }}'
+ wan1:
+ using_static_ip: false
+ vlan: 1
+ delegate_to: localhost
+ register: reset_switch_mgmt
+
+ - name: Debug reset_switch_mgmt
+ ansible.builtin.debug:
+ var: reset_switch_mgmt
+
+ - name: Assert reset_switch_mgmt
+ ansible.builtin.assert:
+ that:
+ - reset_switch_mgmt.data is defined
+
+ - name: Set WAN1 as DHCP in check mode
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ serial }}'
+ wan1:
+ wan_enabled: enabled
+ using_static_ip: false
+ vlan: 1
+ delegate_to: localhost
+ register: set_wan1_check
+ check_mode: true
+
+ - name: Debug set_wan1_check
+ ansible.builtin.debug:
+ var: set_wan1_check
+
+ - name: Assert set_wan1_check
+ ansible.builtin.assert:
+ that:
+ - set_wan1_check is changed
+ - set_wan1_check.data is defined
+
+ - name: Set WAN1 as DHCP
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ serial }}'
+ wan1:
+ wan_enabled: enabled
+ using_static_ip: false
+ vlan: 1
+ delegate_to: localhost
+ register: set_wan1
+
+ - name: Debug set_wan1
+ ansible.builtin.debug:
+ var: set_wan1
+
+ - name: Assert set_wan1
+ ansible.builtin.assert:
+ that:
+ - set_wan1 is changed
+ - set_wan1.data is defined
+
+ - name: Set WAN1 as DHCP with idempotency
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ serial }}'
+ wan1:
+ wan_enabled: enabled
+ using_static_ip: false
+ vlan: 1
+ delegate_to: localhost
+ register: set_wan1_idempotent
+
+ - name: Debug set_wan1_idempotent
+ ansible.builtin.debug:
+ var: set_wan1_idempotent
+
+ - name: Assert set_wan1_idempotent
+ ansible.builtin.assert:
+ that:
+ - set_wan1_idempotent is not changed
+ - set_wan1_idempotent.data is defined
+
+ - name: Set WAN2 as static IP
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ serial }}'
+ wan2:
+ wan_enabled: enabled
+ using_static_ip: true
+ static_ip: 192.168.16.195
+ static_gateway_ip: 192.168.16.1
+ static_subnet_mask: 255.255.255.0
+ static_dns:
+ - 1.1.1.1
+ vlan: 1
+ delegate_to: localhost
+ register: set_wan2
+
+ - name: Debug set_wan2
+ ansible.builtin.debug:
+ var: set_wan2
+
+ - name: Assert set_wan2
+ ansible.builtin.assert:
+ that:
+ - set_wan2 is changed
+ - set_wan2.data is defined
+
+ - name: Test too many DNS servers
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ serial }}'
+ wan1:
+ wan_enabled: enabled
+ using_static_ip: true
+ static_ip: 192.0.1.2
+ static_gateway_ip: 192.0.1.1
+ static_subnet_mask: 255.255.255.0
+ static_dns:
+ - 1.1.1.1
+ - 8.8.8.8
+ - 4.4.4.4
+ vlan: 1
+ delegate_to: localhost
+ register: too_many_dns
+ failed_when: false
+ changed_when: false
+
+ - name: Debug too_many_dns
+ ansible.builtin.debug:
+ var: too_many_dns
+
+ - name: Assert too_many_dns
+ ansible.builtin.assert:
+ that:
+ - 'too_many_dns.msg == "Maximum number of static DNS addresses is 2."'
+
+ - name: Query management information
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ serial }}'
+ delegate_to: localhost
+ register: query_mx
+
+ - name: Debug query_mx
+ ansible.builtin.debug:
+ var: query_mx
+
+ - name: Assert query_mx
+ ansible.builtin.assert:
+ that:
+ - query_mx.data is defined
+
+ always:
+ ############################################################################
+ # Tear down starts here
+ ############################################################################
+ - name: Reset settings for all interfaces
+ cisco.meraki.meraki_management_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{ test_org_id }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ serial }}'
+ wan1:
+ wan_enabled: disabled
+ using_static_ip: false
+ wan2:
+ wan_enabled: enabled
+ using_static_ip: false
+ delegate_to: localhost
+ failed_when: false
+ changed_when: false
+
+ # Network deletion is commented out since this requires a device in a network
+ # - name: Delete network
+ # cisco.meraki.meraki_network:
+ # auth_key: '{{ auth_key }}'
+ # state: absent
+ # org_name: '{{ test_org_name }}'
+ # net_name: '{{ net_name }}'
+ # delegate_to: localhost
+ # register: delete_network
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml
new file mode 100644
index 000000000..1390c2ecc
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml
@@ -0,0 +1,212 @@
+# Test code for the Meraki modules
+# 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: Block of tasks
+ block:
+ - name: Create wireless network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetWireless
+ type: wireless
+ delegate_to: localhost
+ register: new_net
+
+ - name: Set fact new_net
+ ansible.builtin.set_fact:
+ net: '{{ new_net.data.id }}'
+
+ - name: Check rule values are lowercase
+ cisco.meraki.meraki_mr_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetWireless
+ state: query
+ number: 0
+ delegate_to: localhost
+ register: query
+
+ - name: Debug query
+ ansible.builtin.debug:
+ var: query
+
+ - name: Assert query
+ ansible.builtin.assert:
+ that:
+ - query.data.rules.0.policy == 'deny'
+ - query.data.rules.0.protocol == 'any'
+ - query.data.rules.0.dest_port == 'any'
+ - query.data.rules.0.dest_cidr == 'local lan'
+ - query.data.rules.0.comment == 'Wireless clients accessing LAN'
+ - query.data.rules.1.policy == 'allow'
+ - query.data.rules.1.protocol == 'any'
+ - query.data.rules.1.dest_port == 'any'
+ - query.data.rules.1.dest_cidr == 'any'
+ - query.data.rules.1.comment == 'Default rule'
+
+ - name: Create single firewall rule with check mode
+ cisco.meraki.meraki_mr_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ number: 1
+ rules:
+ - comment: Integration test rule
+ policy: allow
+ protocol: tcp
+ dest_port: 80
+ dest_cidr: 192.0.2.0/24
+ allow_lan_access: false
+ delegate_to: localhost
+ check_mode: true
+ register: create_one_check
+
+ - name: Debug create_one_check
+ ansible.builtin.debug:
+ msg: '{{ create_one_check }}'
+
+ - name: Assert create_one_check
+ ansible.builtin.assert:
+ that:
+ - create_one_check.data.0.comment == 'Integration test rule'
+ - create_one_check.data.1.policy == 'deny'
+ - create_one_check.data is defined
+ - create_one_check is changed
+
+ - name: Create single firewall rule
+ cisco.meraki.meraki_mr_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ number: 1
+ rules:
+ - comment: Integration test rule
+ policy: allow
+ protocol: tcp
+ dest_port: 80
+ dest_cidr: 192.0.2.0/24
+ allow_lan_access: false
+ delegate_to: localhost
+ register: create_one
+
+ - name: Debug create_one
+ ansible.builtin.debug:
+ msg: '{{ create_one }}'
+
+ - name: Assert create_one
+ ansible.builtin.assert:
+ that:
+ - create_one.data.rules.0.comment == 'Integration test rule'
+ - create_one.data.rules.1.policy == 'deny'
+ - create_one.data is defined
+
+ - name: Enable local LAN access with check mode
+ cisco.meraki.meraki_mr_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ number: 1
+ allow_lan_access: true
+ delegate_to: localhost
+ check_mode: true
+ register: enable_lan_check
+
+ - name: Debug enable_lan_check
+ ansible.builtin.debug:
+ var: enable_lan_check
+
+ - name: Assert enable_lan_check
+ ansible.builtin.assert:
+ that:
+ - enable_lan_check.data.rules.1.policy == 'allow'
+ - enable_lan_check is changed
+
+ - name: Enable local LAN access
+ cisco.meraki.meraki_mr_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ number: 1
+ allow_lan_access: true
+ delegate_to: localhost
+ register: enable_lan
+
+ - name: Assert enable_lan
+ ansible.builtin.assert:
+ that:
+ - enable_lan.data.rules.1.policy == 'allow'
+
+ - name: Update rules without specifying LAN access with check mode
+ cisco.meraki.meraki_mr_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ number: 1
+ rules:
+ - comment: Integration test rule
+ policy: allow
+ protocol: tcp
+ dest_port: 8080
+ dest_cidr: 192.0.2.0/24
+ delegate_to: localhost
+ register: update_one_check
+
+ - name: Debug update_one_check
+ ansible.builtin.debug:
+ msg: '{{ update_one_check }}'
+
+ - name: Assert update_one_check
+ ansible.builtin.assert:
+ that:
+ - update_one_check.data.rules.0.comment == 'Integration test rule'
+ - update_one_check.data is defined
+
+ - name: Query firewall rules
+ cisco.meraki.meraki_mr_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ number: 1
+ delegate_to: localhost
+ register: query
+
+ - name: Debug query
+ ansible.builtin.debug:
+ msg: '{{ query }}'
+
+ - name: Assert query
+ ansible.builtin.assert:
+ that:
+ - query.data.rules.1.comment == 'Wireless clients accessing LAN'
+ - query.data.rules.2.comment == 'Default rule'
+ - query.changed == False
+
+############################################################################
+# Tear down starts here
+############################################################################
+ always:
+ - name: Delete wireless SSID
+ cisco.meraki.meraki_ssid:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ number: 1
+ delegate_to: localhost
+
+ - name: Delete wireless network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/main.yml
new file mode 100644
index 000000000..878b2a45b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/main.yml
@@ -0,0 +1,7 @@
+# Test code for the Meraki modules
+
+# Copyright: (c) 2022, Joshua Coronado (@joshuajcoronado) <joshua@coronado.io>
+# 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
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/tests.yml
new file mode 100644
index 000000000..3cb74be6f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l7_firewall/tasks/tests.yml
@@ -0,0 +1,520 @@
+# Test code for the Meraki modules
+
+# Copyright: (c) 2022, Joshua Coronado (@joshuajcoronado) <joshua@coronado.io>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tasks
+ block:
+ - name: Create network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: TestNet
+ state: present
+ type: wireless
+ register: new_net
+
+ - name: Query existing network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: TestNet
+ state: present
+ type: wireless
+ register: new_net
+
+ - name: Set fact new_net
+ ansible.builtin.set_fact:
+ net: '{{ new_net.data.id }}'
+
+ - name: Create wireless SSID
+ cisco.meraki.meraki_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ name: TestSSID
+
+ - name: Query firewall rules
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: query
+ ssid: TestSSID
+ register: query
+
+ - name: Assert query.data is defined
+ ansible.builtin.assert:
+ that:
+ - query.data is defined
+
+ - name: Query firewall application categories
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: query
+ categories: true
+ register: query_categories
+
+ - name: Assert query_categories.data is defined
+ ansible.builtin.assert:
+ that:
+ - query_categories.data is defined
+
+ - name: Set firewall rules to empty array
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ rules: []
+ ssid: TestSSID
+ register: empty_list
+
+ - name: Debug query
+ ansible.builtin.debug:
+ var: query
+
+ - name: Assert the rules are empty
+ ansible.builtin.assert:
+ that:
+ - empty_list.data.rules | length == 0
+
+ - name: Set firewall rules to empty array with idempotency
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ rules: []
+ ssid: TestSSID
+ register: empty_list_idempotent
+
+ - name: Debug empty_list_idempotent
+ ansible.builtin.debug:
+ var: empty_list_idempotent
+
+ - name: Assert rules list is still empty
+ ansible.builtin.assert:
+ that:
+ - empty_list_idempotent.data.rules | length == 0
+ - empty_list_idempotent is not changed
+
+ - name: Create firewall rule for IP range in check mode
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.0/24
+ register: create_ip_range_check
+ check_mode: true
+
+ - name: Debug create_ip_range_check
+ ansible.builtin.debug:
+ var: create_ip_range_check
+
+ - name: Assert create_ip_range_check is changed
+ ansible.builtin.assert:
+ that:
+ - create_ip_range_check is changed
+
+ - name: Create firewall rule for IP range
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.0/24
+ register: create_ip_range
+
+ - name: Debug create_ip_range
+ ansible.builtin.debug:
+ var: create_ip_range
+
+ - name: Assert firewalls rules have a single rule
+ ansible.builtin.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
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.0/24
+ register: create_ip_range_idempotent_check
+ check_mode: true
+
+ - name: Assert create_ip_range_idempotent_check is not changed
+ ansible.builtin.assert:
+ that:
+ - create_ip_range_idempotent_check is not changed
+
+ - name: Create firewall rule for IP range with idempotency
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.0/24
+ register: create_ip_range_idempotent
+
+ - name: Assert create_ip_range_idempotent is not changed
+ ansible.builtin.assert:
+ that:
+ - create_ip_range_idempotent is not changed
+
+ - name: Create firewall rule for IP and port
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.1:23
+ register: create_ip_range_port
+
+ - name: Debug create_ip_range_port
+ ansible.builtin.debug:
+ var: create_ip_range_port
+
+ - name: Assert create_ip_range_port is changed
+ ansible.builtin.assert:
+ that:
+ - create_ip_range_port is changed
+
+ - name: Create firewall rule for IP range
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.0/24
+ register: create_ip_range
+
+ - name: Debug create_ip_range
+ ansible.builtin.debug:
+ var: create_ip_range
+
+ - name: Assert create_ip_range has changed and has a rule
+ ansible.builtin.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
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.0/24
+ register: create_ip_range_idempotent_check
+ check_mode: true
+
+ - name: Assert create_ip_range_idempotent_check is not changed
+ ansible.builtin.assert:
+ that:
+ - create_ip_range_idempotent_check is not changed
+
+ - name: Create firewall rule for IP range with idempotency
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: ip_range
+ ip_range: 10.11.12.0/24
+ register: create_ip_range_idempotent
+
+ - name: Assert create_ip_range_idempotent is not changed
+ ansible.builtin.assert:
+ that:
+ - create_ip_range_idempotent is not changed
+
+ - name: Create firewall rule for application
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: application
+ application:
+ name: facebook
+ register: application_rule
+
+ - name: Assert application_rule is changed
+ ansible.builtin.assert:
+ that:
+ - application_rule is changed
+ - application_rule.data.rules is defined
+
+ - name: Create firewall rule for application via ID
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: application
+ application:
+ id: meraki:layer7/application/205
+ register: application_rule_id
+
+ - name: Assert application_rule_id is changed
+ ansible.builtin.assert:
+ that:
+ - application_rule_id is changed
+
+ - name: Create firewall rule for invalid application
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: application
+ application:
+ name: ansible
+ register: application_rule_invalid
+ ignore_errors: true
+
+ - name: Create firewall rule for application category
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: application_category
+ application:
+ name: Advertising
+ register: application_category_rule
+
+ - name: Debug application_category_rule
+ ansible.builtin.debug:
+ var: application_category_rule
+
+ - name: Assert application_category_rule is changed
+ ansible.builtin.assert:
+ that:
+ - application_category_rule is changed
+
+ - name: Create firewall rule for application category with ID and conflict
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: application_category
+ application:
+ id: meraki:layer7/category/27
+ register: application_category_rule_id_conflict
+
+ - name: Assert application_category_rule_id_conflict is not changed
+ ansible.builtin.assert:
+ that:
+ - application_category_rule_id_conflict is not changed
+
+ - name: Create firewall rule for application category with ID
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: application_category
+ application:
+ id: meraki:layer7/category/24
+ register: application_category_rule_id
+
+ - name: Assert application_category_rule_id is changed
+ ansible.builtin.assert:
+ that:
+ - application_category_rule_id is changed
+
+ - name: Create firewall rule for host
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: host
+ host: asdf.com
+ register: host_rule
+
+ - name: Assert host_rule is changed
+ ansible.builtin.assert:
+ that:
+ - host_rule is changed
+
+ - name: Create firewall rule for port
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ ssid: TestSSID
+ rules:
+ - type: port
+ port: 1234
+ register: port_rule
+
+ - name: Assert port_rule is changed
+ ansible.builtin.assert:
+ that:
+ - port_rule is changed
+
+ - name: Create multiple firewall rules
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: application_category
+ application:
+ id: meraki:layer7/category/27
+ - policy: deny
+ type: port
+ port: 8080
+ register: multiple_rules
+
+ - name: Debug multiple_rules
+ ansible.builtin.debug:
+ var: multiple_rules
+
+ - name: Assert multiple_rules.data.rules | length == 2
+ ansible.builtin.assert:
+ that:
+ - multiple_rules.data.rules | length == 2
+ - multiple_rules is changed
+
+ #########################################
+ ## Tests for argument completeness ##
+ #########################################
+
+ - name: Test application_category incomplete arguments
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: application_category
+ register: error_app_cat
+ ignore_errors: true
+
+ - name: Assert application argument is required when type is application_category
+ ansible.builtin.assert:
+ that:
+ - 'error_app_cat.msg == "application argument is required when type is application_category."'
+
+ - name: Test application incomplete arguments
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: application
+ register: error_app_cat
+ ignore_errors: true
+
+ - name: Assert application argument is required when type is application
+ ansible.builtin.assert:
+ that:
+ - 'error_app_cat.msg == "application argument is required when type is application."'
+
+ - name: Test host incomplete arguments
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: host
+ register: error_app_cat
+ ignore_errors: true
+
+ - name: Assert host argument is required when type is host
+ ansible.builtin.assert:
+ that:
+ - 'error_app_cat.msg == "host argument is required when type is host."'
+
+ - name: Test port incomplete arguments
+ cisco.meraki.meraki_mr_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ ssid: TestSSID
+ state: present
+ rules:
+ - type: port
+ register: error_app_cat
+ ignore_errors: true
+
+ - name: Assert port argument is required when type is port
+ ansible.builtin.assert:
+ that:
+ - 'error_app_cat.msg == "port argument is required when type is port."'
+
+############################################################################
+# Tear down starts here
+############################################################################
+ always:
+ - name: Delete wireless SSID
+ cisco.meraki.meraki_ssid:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ name: TestSSID
+
+ - name: Delete wireless network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/aliases
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/aliases
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/tasks/main.yml
new file mode 100644
index 000000000..987d099c4
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_radio/tasks/main.yml
@@ -0,0 +1,166 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2021, Tyler Christiansen (@supertylerc)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tasks
+ block:
+ - name: Create network with type wireless
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkWireless
+ type: wireless
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: created_network
+
+ - name: Setting the net_id
+ ansible.builtin.set_fact:
+ created_net_id: '{{ created_network.data.id }}'
+
+ - name: Add access points to network
+ cisco.meraki.meraki_device:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_id: '{{ created_net_id }}'
+ serial: "{{ serial_wireless }}"
+ delegate_to: localhost
+
+ - name: Create RF profile
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_id: '{{ created_net_id }}'
+ state: present
+ name: Test Profile
+ band_selection_type: ap
+ client_balancing_enabled: true
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 40
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+ register: create
+
+ - name: Set fact create
+ ansible.builtin.set_fact:
+ profile_id: "{{ create.data.id }}"
+
+ - name: Configure AP radios in check mode
+ cisco.meraki.meraki_mr_radio:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_id: '{{ created_net_id }}'
+ state: present
+ serial: "{{ serial_wireless }}"
+ five_ghz_settings:
+ channel: 40
+ channel_width: 20
+ target_power: 10
+ two_four_ghz_settings:
+ channel: 1
+ target_power: 10
+ rf_profile_name: Test Profile
+ delegate_to: localhost
+ register: create_check
+ check_mode: true
+
+ - name: Assert create_check
+ ansible.builtin.assert:
+ that:
+ - create_check.data is defined
+ - create_check is changed
+
+ - name: Query a device radio
+ cisco.meraki.meraki_mr_radio:
+ auth_key: '{{ auth_key }}'
+ serial: "{{ serial_wireless }}"
+ state: query
+ delegate_to: localhost
+ register: query_one
+
+ - name: Assert query_one
+ ansible.builtin.assert:
+ that:
+ - query_one.data is defined
+
+ - name: Configure AP radios
+ cisco.meraki.meraki_mr_radio:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_id: '{{ created_net_id }}'
+ state: present
+ serial: "{{ serial_wireless }}"
+ five_ghz_settings:
+ channel: 100
+ channel_width: 20
+ target_power: 10
+ two_four_ghz_settings:
+ channel: 1
+ target_power: 10
+ rf_profile_name: Test Profile
+ delegate_to: localhost
+ register: update
+
+ - name: Assert update
+ ansible.builtin.assert:
+ that:
+ - update.data is defined
+ - update is changed
+
+ - name: Configure AP radios
+ cisco.meraki.meraki_mr_radio:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_id: '{{ created_net_id }}'
+ state: present
+ serial: "{{ serial_wireless }}"
+ five_ghz_settings:
+ channel: 100
+ channel_width: 20
+ target_power: 10
+ two_four_ghz_settings:
+ channel: 1
+ target_power: 10
+ rf_profile_name: Test Profile
+ delegate_to: localhost
+ register: update_idempotent
+
+ - name: Assert update_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_idempotent.data is defined
+ - update_idempotent is not changed
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+
+ always:
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ created_net_id }}'
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/aliases
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/aliases
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml
new file mode 100644
index 000000000..ad5f9b6e2
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml
@@ -0,0 +1,342 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2020, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tasks
+ block:
+ - name: Create network with type wireless
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ type: wireless
+ timezone: America/Chicago
+ delegate_to: localhost
+
+ - name: Create RF profile in check mode
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ name: Test Profile
+ band_selection_type: ap
+ client_balancing_enabled: true
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 40
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+ register: create_check
+ check_mode: true
+
+ - name: Assert create_check
+ ansible.builtin.assert:
+ that:
+ - create_check.data is defined
+ - create_check is changed
+
+ - name: Create RF profile
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ name: Test Profile
+ band_selection_type: ap
+ client_balancing_enabled: true
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 40
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+ register: create
+
+ - name: Set fact profile_id
+ ansible.builtin.set_fact:
+ profile_id: '{{ create.data.id }}'
+
+ - name: Query all RF profiles
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: query
+ delegate_to: localhost
+ register: query_one
+
+ - name: Assert query_one
+ ansible.builtin.assert:
+ that:
+ - query_one.data is defined
+
+ - name: Update profile with check mode
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ profile_id: '{{ profile_id }}'
+ band_selection_type: ap
+ client_balancing_enabled: true
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -75
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+ register: update_check
+ check_mode: true
+
+ - name: Assert update_check
+ ansible.builtin.assert:
+ that:
+ - update_check.data is defined
+ - update_check is changed
+
+ - name: Update profile
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ profile_id: '{{ profile_id }}'
+ band_selection_type: ap
+ client_balancing_enabled: true
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -75
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+ register: update
+
+ - name: Assert update
+ ansible.builtin.assert:
+ that:
+ - update.data is defined
+ - update is changed
+
+ - name: Update profile with idempotency
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ profile_id: '{{ profile_id }}'
+ band_selection_type: ap
+ client_balancing_enabled: true
+ ap_band_settings:
+ mode: dual
+ band_steering_enabled: true
+ five_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -65
+ channel_width: 20
+ valid_auto_channels:
+ - 36
+ - 44
+ two_four_ghz_settings:
+ max_power: 10
+ min_bitrate: 12
+ min_power: 8
+ rxsop: -75
+ ax_enabled: false
+ valid_auto_channels:
+ - 1
+ delegate_to: localhost
+ register: update_idempotent
+
+ - name: Assert update_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_idempotent.data is defined
+ - update_idempotent is not changed
+
+ - name: Query one RF profile by ID
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: query
+ profile_id: '{{ profile_id }}'
+ delegate_to: localhost
+ register: query_one_id
+
+ - name: Assert query_one_id
+ ansible.builtin.assert:
+ that:
+ - query_one_id.data is defined
+
+ - name: Query one RF profile by name
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: query
+ name: Test Profile
+ delegate_to: localhost
+ register: query_one_name
+
+ - name: Assert query_one_name
+ ansible.builtin.assert:
+ that:
+ - query_one_name.data is defined
+
+ - name: Delete RF profile
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: absent
+ profile_id: '{{ profile_id }}'
+ delegate_to: localhost
+ register: delete
+
+ - name: Assert delete
+ ansible.builtin.assert:
+ that:
+ - delete.data is defined
+ - delete is changed
+
+- name: "Test RFProfile Bugfix from !281"
+ block:
+ - name: Create RF Profile
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ name: "RF Profile - !281"
+ band_selection_type: ap
+ ap_band_settings:
+ mode: 'dual'
+ five_ghz_settings:
+ channel_width: 40
+ two_four_ghz_settings:
+ ax_enabled: 'no'
+ state: present
+ register: create_281
+
+ - name: Assert create_281
+ ansible.builtin.assert:
+ that:
+ - create_281.data is defined
+ - create_281 is changed
+
+ - name: Create RF Profile - Idempotent
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ name: "RF Profile - !281"
+ band_selection_type: ap
+ ap_band_settings:
+ mode: 'dual'
+ five_ghz_settings:
+ channel_width: 40
+ two_four_ghz_settings:
+ ax_enabled: 'no'
+ state: present
+ register: idempotent_281
+
+ - name: Assert idempotent_281
+ ansible.builtin.assert:
+ that:
+ - idempotent_281.data is defined
+ - idempotent_281 is not changed
+
+ - name: Clean Up RF Profile
+ cisco.meraki.meraki_mr_rf_profile:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ name: "RF Profile - !281"
+ state: absent
+ register: delete_281
+
+ - name: Assert delete_281
+ ansible.builtin.assert:
+ that:
+ - delete_281.data is defined
+ - delete_281 is changed
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+
+ always:
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/aliases
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/aliases
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/tasks/main.yml
new file mode 100644
index 000000000..3010d421f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/tasks/main.yml
@@ -0,0 +1,108 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2020, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Create network with type wireless
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ type: wireless
+ timezone: America/Chicago
+ delegate_to: localhost
+
+ - name: Query all settings
+ cisco.meraki.meraki_mr_settings:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: query
+ delegate_to: localhost
+ register: query_all
+
+ - name: Debug query_all
+ ansible.builtin.debug:
+ var: query_all
+
+ - name: Assert query_all.data
+ ansible.builtin.assert:
+ that:
+ - query_all.data is defined
+
+ - name: Configure settings with check mode
+ cisco.meraki.meraki_mr_settings:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ upgrade_strategy: minimize_upgrade_time
+ ipv6_bridge_enabled: false
+ led_lights_on: true
+ location_analytics_enabled: true
+ meshing_enabled: true
+ delegate_to: localhost
+ register: settings_check
+ check_mode: true
+
+ - name: Assert settings_check
+ ansible.builtin.assert:
+ that:
+ - settings_check.data is defined
+ - settings_check is changed
+
+ - name: Configure settings
+ cisco.meraki.meraki_mr_settings:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ upgrade_strategy: minimize_upgrade_time
+ ipv6_bridge_enabled: false
+ led_lights_on: true
+ location_analytics_enabled: true
+ meshing_enabled: true
+ delegate_to: localhost
+ register: settings
+
+ - name: Assert settings.data
+ ansible.builtin.assert:
+ that:
+ - settings.data is defined
+ - settings is changed
+
+ - name: Configure settings with idempotency
+ cisco.meraki.meraki_mr_settings:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ state: present
+ upgrade_strategy: minimize_upgrade_time
+ ipv6_bridge_enabled: false
+ led_lights_on: true
+ location_analytics_enabled: true
+ meshing_enabled: true
+ delegate_to: localhost
+ register: settings_idempotent
+
+ - name: Assert settings_idempotent
+ ansible.builtin.assert:
+ that:
+ - settings_idempotent.data is defined
+ - settings_idempotent is not changed
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+
+ always:
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkWireless
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/main.yml
new file mode 100644
index 000000000..f671fc928
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/main.yml
@@ -0,0 +1,7 @@
+# Test code for the Meraki Webhooks 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
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/tests.yml
new file mode 100644
index 000000000..8c92d9c11
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_list/tasks/tests.yml
@@ -0,0 +1,132 @@
+# Test code for the Meraki Webhook module
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Create test network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ type: switch
+
+ - name: Set access list in check mode
+ meraki_switch_access_list:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ rules:
+ - comment: Fake rule
+ policy: allow
+ ip_version: ipv4
+ protocol: udp
+ src_cidr: 192.0.1.0/24
+ src_port: "4242"
+ dst_cidr: 1.2.3.4/32
+ dst_port: "80"
+ vlan: "100"
+ register: create_check
+ check_mode: true
+
+ - name: Assert create_check
+ ansible.builtin.assert:
+ that:
+ - create_check is changed
+ - create_check.data is defined
+
+ - name: Debug create_check
+ ansible.builtin.debug:
+ var: create_check
+
+ - name: Set access list
+ meraki_switch_access_list:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ rules:
+ - comment: Fake rule
+ policy: allow
+ ip_version: ipv4
+ protocol: udp
+ src_cidr: 192.0.1.0/24
+ src_port: "4242"
+ dst_cidr: 1.2.3.4/32
+ dst_port: "80"
+ vlan: "100"
+ register: create
+
+ - name: Assert create
+ ansible.builtin.assert:
+ that:
+ - create is changed
+ - create.data is defined
+
+ - name: Debug create
+ ansible.builtin.debug:
+ var: create
+
+ - name: Set access list with idempotency
+ meraki_switch_access_list:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ rules:
+ - comment: Fake rule
+ policy: allow
+ ip_version: ipv4
+ protocol: udp
+ src_cidr: 192.0.1.0/24
+ src_port: 4242
+ dst_cidr: 1.2.3.4/32
+ dst_port: 80
+ vlan: 100
+ register: create_idempotent
+
+ - name: Debug create_idempotent
+ ansible.builtin.debug:
+ var: create_idempotent
+
+ - name: Assert create_idempotent
+ ansible.builtin.assert:
+ that:
+ - create_idempotent is not changed
+ - create_idempotent.data is defined
+
+ - name: Query access lists
+ meraki_switch_access_list:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ register: query
+
+ - name: Debug query
+ ansible.builtin.debug:
+ var: query
+
+ - name: Assert query
+ ansible.builtin.assert:
+ that:
+ - query.data is defined
+
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+ always:
+ - name: Delete test network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_policies/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_policies/tasks/main.yml
new file mode 100644
index 000000000..9ba1d64dc
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_access_policies/tasks/main.yml
@@ -0,0 +1,561 @@
+# Test code for the Meraki Organization module
+# Copyright: (c) 2022, Marcin Woźniak (@y0rune) <y0rune@aol.com>
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+- name: Testing - created "Meraki authentication"
+ block:
+ - name: Query all access polices
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: query_all
+
+ - name: Create access policy with auth_method is "Meraki authentication"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ name: "Meraki authentication policy"
+ auth_method: "Meraki authentication"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: create_access_policy
+
+ - name: Query one access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right name of policy
+ ansible.builtin.assert:
+ that:
+ - create_access_policy is changed
+ - query_one.data.name == "Meraki authentication policy"
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "Meraki authentication" with guest_vlan
+ block:
+ - name: Create access policy with auth_method is "Meraki authentication" with guest_vlan
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ name: "Meraki authentication policy"
+ auth_method: "Meraki authentication"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ guest_vlan: 10
+ delegate_to: localhost
+ register: create_access_policy_guest_vlan
+
+ - name: Query one access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right guest_vlan
+ ansible.builtin.assert:
+ that:
+ - query_one.data.guest_vlan_id == 10
+ - create_access_policy_guest_vlan is changed
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "my RADIUS Server" and updated the same policy
+ block:
+ - name: Create access policy with auth_method is "my RADIUS Server"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ access_policy_type: "802.1x"
+ host_mode: "Single-Host"
+ state: present
+ name: "Meraki authentication policy"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: false
+ guest_vlan: 10
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: create_access_policy_my_radius_server
+
+ - name: Update the same access policy with auth_method is "my RADIUS Server"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ access_policy_type: "802.1x"
+ host_mode: "Single-Host"
+ state: present
+ name: "Meraki authentication policy"
+ number: 1
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: false
+ guest_vlan: 10
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: update_access_policy_my_radius_server
+
+ - name: Query one access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right host in radius_server
+ ansible.builtin.assert:
+ that:
+ - query_one.data.radius_servers.0.host == '192.0.1.18'
+ - query_one.data.guest_vlan_id == 10
+ - create_access_policy_my_radius_server is changed
+ - update_access_policy_my_radius_server is not changed
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "my RADIUS Server" with accounting
+ block:
+ - name: Create access policy with auth_method is "my RADIUS Server" with accounting
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ name: "Meraki authentication policy"
+ host_mode: "Single-Host"
+ access_policy_type: "802.1x"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: true
+ radius_accounting_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: create_access_policy_my_radius_server_accounting
+
+ - name: Query one access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right host in accounting servers
+ ansible.builtin.assert:
+ that:
+ - query_one.data.radius_accounting_servers.0.host == '192.0.1.18'
+ - create_access_policy_my_radius_server_accounting is changed
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "my RADIUS Server" with accounting and removed accounting
+ block:
+ - name: Create access policy with auth_method is "my RADIUS Server" with accounting
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ name: "Meraki authentication policy"
+ host_mode: "Single-Host"
+ access_policy_type: "802.1x"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: true
+ radius_accounting_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: create_access_policy_my_radius_server_accounting
+
+ - name: Update access policy with auth_method is "my RADIUS Server" with accounting
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ number: 1
+ name: "Updated Meraki authentication policy"
+ host_mode: "Single-Host"
+ access_policy_type: "802.1x"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.20
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: false
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: update_access_policy_my_radius_server_accounting
+
+ - name: Query one access policy when was updated
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right host in accounting servers
+ ansible.builtin.assert:
+ that:
+ - query_one.data.radius_servers.0.host == '192.0.1.20'
+ - update_access_policy_my_radius_server_accounting is changed
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "my RADIUS Server" and added accounting
+ block:
+ - name: Create access policy with auth_method is "my RADIUS Server"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ access_policy_type: "802.1x"
+ host_mode: "Single-Host"
+ state: present
+ name: "Meraki authentication policy"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: false
+ guest_vlan: 10
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: create_access_policy_my_radius_server
+
+ - name: Update access policy with auth_method is "my RADIUS Server" with accounting
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ name: "Meraki authentication policy"
+ host_mode: "Single-Host"
+ access_policy_type: "802.1x"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: true
+ radius_accounting_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: update_access_policy_my_radius_server_accounting
+
+ - name: Query one access policy when was updated
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right host in accounting servers
+ ansible.builtin.assert:
+ that:
+ - query_one.data.radius_servers.0.host == '192.0.1.18'
+ - query_one.data.radius_accounting_servers.0.host == '192.0.1.18'
+ - update_access_policy_my_radius_server_accounting is changed
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "my RADIUS Server" with accounting and removed accounting
+ block:
+ - name: Create access policy with auth_method is "my RADIUS Server" with accounting
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ name: "Meraki authentication policy"
+ host_mode: "Single-Host"
+ access_policy_type: "802.1x"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: true
+ radius_accounting_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: create_access_policy_my_radius_server_accounting
+
+ - name: Update access policy with auth_method is "my RADIUS Server"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ access_policy_type: "802.1x"
+ host_mode: "Single-Host"
+ state: present
+ name: "Meraki authentication policy"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: false
+ guest_vlan: 10
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: update_access_policy_my_radius_server
+
+ - name: Query one access policy when was updated
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right host in accounting servers
+ ansible.builtin.assert:
+ that:
+ - query_one.data.radius_servers.0.host == '192.0.1.18'
+ - update_access_policy_my_radius_server is changed
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "Meraki authentication" and changed it into "my RADIUS Server"
+ block:
+ - name: Create access policy with auth_method is "Meraki authentication"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: present
+ name: "Authentication policy"
+ auth_method: "Meraki authentication"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: create_access_policy
+
+ - name: Update access policy with auth_method is "my RADIUS Server"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ access_policy_type: "802.1x"
+ host_mode: "Single-Host"
+ state: present
+ name: "Authentication policy"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: false
+ guest_vlan: 10
+ voice_vlan_clients: false
+ delegate_to: localhost
+ register: update_access_policy_my_radius_server
+
+ - name: Query one access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right name of policy
+ ansible.builtin.assert:
+ that:
+ - update_access_policy_my_radius_server is changed
+ - query_one.data.radius_servers.0.host == '192.0.1.18'
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
+
+- name: Testing - created "Meraki authentication" with RADIUS attribute specyfing group policy
+ block:
+ - name: Create access policy with auth_method is "my RADIUS Server"
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ access_policy_type: "802.1x"
+ host_mode: "Single-Host"
+ state: present
+ name: "Authentication policy"
+ auth_method: "my RADIUS server"
+ radius_servers:
+ - host: 192.0.1.18
+ port: 7890
+ secret: secret123
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ radius_coa_enabled: false
+ radius_accounting_enabled: false
+ guest_vlan: 10
+ voice_vlan_clients: false
+ radius_attribute_group_policy_name: "Filter-Id"
+ delegate_to: localhost
+ register: create_access_policy_my_radius_server
+
+ - name: Query one access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+ - name: Checking if the query one access policy returns right name of policy
+ ansible.builtin.assert:
+ that:
+ - create_access_policy_my_radius_server is changed
+ - query_one.data.radius_group_attribute == '11'
+
+ always:
+ - name: Delete access policy
+ cisco.meraki.meraki_ms_access_policies:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ number: 1
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ delegate_to: localhost
+ register: delete_access_policy
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_l3_interface/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_l3_interface/tasks/main.yml
new file mode 100644
index 000000000..b32135cd1
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_l3_interface/tasks/main.yml
@@ -0,0 +1,236 @@
+# Test code for the Meraki modules
+# 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: Create network with type switch
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkSwitch
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_switch
+
+ - name: Claim a device into a network
+ meraki_device:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkSwitch
+ serial: '{{ serial_switch_l3 }}'
+ state: present
+ delegate_to: localhost
+
+ - name: Query all l3 interfaces
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: query
+ serial: '{{ serial_switch_l3 }}'
+ delegate_to: localhost
+ register: query_all
+
+ - set_fact:
+ interface_id_1: '{{query_all.data.1.interface_id}}'
+ ignore_errors: true
+
+ - assert:
+ that:
+ - query_all.data is defined
+ - query_all.data | length > 0
+
+ - name: Query one l3 interface
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: query
+ serial: '{{ serial_switch_l3 }}'
+ name: Test L3 interface
+ delegate_to: localhost
+ register: query_one
+
+ - assert:
+ that:
+ - query_one.data is defined
+
+ - name: Create l3 interface in check mode
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch_l3 }}'
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ # default_gateway: "192.168.2.1"
+ ospf_settings:
+ area: 0
+ cost: 1
+ is_passive_enabled: true
+ delegate_to: localhost
+ check_mode: true
+ register: create_check
+
+ - assert:
+ that:
+ - create_check.data is defined
+ - create_check is changed
+
+ - name: Create l3 interface
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch_l3 }}'
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ # default_gateway: "192.168.2.1"
+ ospf_settings:
+ area: 0
+ cost: 1
+ is_passive_enabled: true
+ delegate_to: localhost
+ register: create
+
+ - set_fact:
+ interface_id_1: '{{create.data.interface_id}}'
+
+ - assert:
+ that:
+ - create.data is defined
+ - create is changed
+
+ - name: Update l3 interface with check mode
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch_l3 }}'
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 2
+ is_passive_enabled: false
+ delegate_to: localhost
+ register: update_check
+ check_mode: true
+
+ - assert:
+ that:
+ - update_check.data is defined
+ - update_check is changed
+
+ - name: Update l3 interface
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch_l3 }}'
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 2
+ is_passive_enabled: false
+ delegate_to: localhost
+ register: update
+
+ - assert:
+ that:
+ - update.data is defined
+ - update is changed
+
+ - name: Update l3 interface idempotent
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch_l3 }}'
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 2
+ is_passive_enabled: false
+ delegate_to: localhost
+ register: update_idempotent
+
+ - assert:
+ that:
+ update_idempotent is not changed
+
+ - name: Update l3 interface idempotent with check mode
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch_l3 }}'
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ ospf_settings:
+ area: 0
+ cost: 2
+ is_passive_enabled: false
+ delegate_to: localhost
+ register: update_idempotent_check
+ check_mode: true
+
+ - assert:
+ that:
+ update_idempotent_check is not changed
+
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+
+ always:
+ - name: Delete l3 interface with check mode
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ serial: '{{ serial_switch_l3 }}'
+ interface_id: '{{ item }}'
+ delegate_to: localhost
+ check_mode: true
+ register: delete_check
+ loop:
+ - '{{interface_id_1}}'
+
+ - assert:
+ that:
+ - delete_check is changed
+
+ - name: Delete l3 interface
+ meraki_ms_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ serial: '{{ serial_switch_l3 }}'
+ interface_id: '{{ interface_id_1 }}'
+ delegate_to: localhost
+ register: delete
+
+ - assert:
+ that:
+ - delete is changed
+
+ # - name: Delete network
+ # meraki_network:
+ # auth_key: '{{ auth_key }}'
+ # state: absent
+ # org_name: '{{test_org_name}}'
+ # net_name: IntTestNetworkSwitch
+ # delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml
new file mode 100644
index 000000000..848f57b40
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml
@@ -0,0 +1,130 @@
+# Test code for the Meraki MS Link Aggregation 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: Block of tests
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Create test network
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_switch_net_name }}"
+ type: switch
+ register: test_net
+
+ - name: Add device to network
+ cisco.meraki.meraki_device:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_switch_net_name }}"
+ serial: "{{ serial_switch }}"
+ delegate_to: localhost
+
+ - name: Create LAG
+ cisco.meraki.meraki_ms_link_aggregation:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_switch_net_name }}"
+ switch_ports:
+ - serial: "{{ serial_switch }}"
+ port_id: "1"
+ - serial: "{{ serial_switch }}"
+ port_id: "2"
+ delegate_to: localhost
+ register: create_ports
+
+ - name: Debug create_ports
+ ansible.builtin.debug:
+ var: create_ports
+
+ - name: Set fact create_ports
+ ansible.builtin.set_fact:
+ lag_id: "{{ create_ports.data.id }}"
+
+ - name: Update LAG
+ cisco.meraki.meraki_ms_link_aggregation:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_switch_net_name }}"
+ lag_id: "{{ lag_id }}"
+ switch_ports:
+ - serial: "{{ serial_switch }}"
+ port_id: "1"
+ - serial: "{{ serial_switch }}"
+ port_id: "2"
+ - serial: "{{ serial_switch }}"
+ port_id: "3"
+ - serial: "{{ serial_switch }}"
+ port_id: "4"
+ delegate_to: localhost
+ register: update_ports
+
+ - name: Assert update_ports
+ ansible.builtin.assert:
+ that:
+ - update_ports is changed
+
+ - name: Update LAG with idempotency
+ cisco.meraki.meraki_ms_link_aggregation:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_switch_net_name }}"
+ lag_id: "{{ lag_id }}"
+ switch_ports:
+ - serial: "{{ serial_switch }}"
+ port_id: "1"
+ - serial: "{{ serial_switch }}"
+ port_id: "2"
+ - serial: "{{ serial_switch }}"
+ port_id: "3"
+ - serial: "{{ serial_switch }}"
+ port_id: "4"
+ delegate_to: localhost
+ register: update_ports_idempotent
+
+ - name: Assert update_ports_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_ports_idempotent is not changed
+
+ - name: Query all LAGs
+ cisco.meraki.meraki_ms_link_aggregation:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_switch_net_name }}"
+ delegate_to: localhost
+ register: query_all
+
+ - name: Debug query_all
+ ansible.builtin.debug:
+ var: query_all
+
+ always:
+ - name: Delete LAG
+ cisco.meraki.meraki_ms_link_aggregation:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_switch_net_name }}"
+ lag_id: "{{ lag_id }}"
+ delegate_to: localhost
+
+ # - name: Delete test network
+ # meraki_network:
+ # auth_key: '{{ auth_key }}'
+ # state: absent
+ # org_name: '{{ test_org_name }}'
+ # net_name: '{{ test_switch_net_name }}'
+ # register: delete_net
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_ospf/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_ospf/tasks/main.yml
new file mode 100644
index 000000000..f7b0f92b5
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_ospf/tasks/main.yml
@@ -0,0 +1,141 @@
+# Test code for the Meraki modules
+# 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: Block of tests
+ block:
+ - name: Create network with type switch
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkSwitch
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_switch
+
+ - name: Claim a device into a network
+ cisco.meraki.meraki_device:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkSwitch
+ serial: "{{ serial_switch_l3 }}"
+ state: present
+ delegate_to: localhost
+
+ - name: Query OSPF settings
+ cisco.meraki.meraki_ms_ospf:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkSwitch
+ state: query
+ delegate_to: localhost
+ register: query_all
+
+ - name: Assert query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data is defined
+
+ - name: Enable OSPF with check mode
+ cisco.meraki.meraki_ms_ospf:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkSwitch
+ state: present
+ enabled: true
+ hello_timer: 20
+ dead_timer: 60
+ areas:
+ - area_id: 0
+ area_name: Backbone
+ area_type: normal
+ - area_id: 1
+ area_name: Office
+ area_type: nssa
+ md5_authentication_enabled: true
+ md5_authentication_key:
+ id: 1
+ passphrase: "secretpass"
+ check_mode: true
+ register: enable_check_mode
+
+ - name: Assert enable_check_mode
+ ansible.builtin.assert:
+ that:
+ - enable_check_mode.data is defined
+
+ - name: Enable OSPF
+ cisco.meraki.meraki_ms_ospf:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkSwitch
+ state: present
+ enabled: true
+ hello_timer: 20
+ dead_timer: 60
+ areas:
+ - area_id: 0
+ area_name: Backbone
+ area_type: normal
+ - area_id: 1
+ area_name: Office
+ area_type: nssa
+ md5_authentication_enabled: true
+ md5_authentication_key:
+ id: 1
+ passphrase: "secretpass"
+ register: enable
+
+ - name: Assert enable
+ ansible.builtin.assert:
+ that:
+ - enable.data is defined
+
+ - name: Enable OSPF with idempotency
+ cisco.meraki.meraki_ms_ospf:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkSwitch
+ state: present
+ enabled: true
+ hello_timer: 20
+ dead_timer: 60
+ areas:
+ - area_id: 0
+ area_name: Backbone
+ area_type: normal
+ - area_id: 1
+ area_name: Office
+ area_type: nssa
+ md5_authentication_enabled: true
+ md5_authentication_key:
+ id: 1
+ passphrase: "secretpass"
+ delegate_to: localhost
+ register: enable_ospf_idempotent
+
+ - name: Debug enable_ospf_idempotent
+ ansible.builtin.debug:
+ var: enable_ospf_idempotent
+
+ - name: Assert enable_ospf_idempotent
+ ansible.builtin.assert:
+ that:
+ - enable_ospf_idempotent is not changed
+ - enable_ospf_idempotent.data is defined
+
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+
+ always:
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ net_name: IntTestNetworkSwitch
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/tasks/main.yml
new file mode 100644
index 000000000..7362d39c9
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack/tasks/main.yml
@@ -0,0 +1,215 @@
+# Test code for the Meraki Switch Stack module
+# Copyright: (c) 2020, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Create network with type switch
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_switch
+
+ - name: Set fact create_net_switch
+ ansible.builtin.set_fact:
+ net_id: '{{ create_net_switch.data.id }}'
+
+ - name: Claim devices into network
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net_id }}'
+ serial: '{{ item }}'
+ state: present
+ delegate_to: localhost
+ register: claim_device
+ loop:
+ - "QBSB-BPR6-PRER"
+ - "QBSB-VLNE-E299"
+ - "QBSB-D75G-PXCG"
+
+ - name: Create new stack
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ name: Test stack
+ serials:
+ - "QBSB-BPR6-PRER"
+ - "QBSB-VLNE-E299"
+ register: create
+
+ - name: Debug create
+ ansible.builtin.debug:
+ var: create
+
+ - name: Set fact create
+ ansible.builtin.set_fact:
+ stack_id: '{{ create.data.id }}'
+
+ - name: Assert create
+ ansible.builtin.assert:
+ that:
+ - create.data is defined
+ - create is changed
+
+ - name: Add switch to stack
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ stack_id: '{{ stack_id }}'
+ serials:
+ - "QBSB-D75G-PXCG"
+ register: add_stack
+
+ - name: Assert add_stack
+ ansible.builtin.assert:
+ that:
+ - add_stack.data is defined
+ - add_stack.data.serials | length == 3
+ - add_stack is changed
+
+ - name: Add switch to stack idempotent
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ stack_id: '{{ stack_id }}'
+ serials:
+ - "QBSB-D75G-PXCG"
+ register: add_stack_idempotent
+
+ - name: Assert add_stack_idempotent
+ ansible.builtin.assert:
+ that:
+ - add_stack_idempotent.data is defined
+ - add_stack_idempotent.data.serials | length == 3
+ - add_stack_idempotent is not changed
+
+ - name: Remove switch from stack
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ stack_id: '{{ stack_id }}'
+ serials:
+ - "QBSB-D75G-PXCG"
+ register: remove_from_stack
+
+ - name: Debug remove_from_stack
+ ansible.builtin.debug:
+ var: remove_from_stack
+
+ - name: Assert remove_from_stack
+ ansible.builtin.assert:
+ that:
+ - remove_from_stack.data is defined
+ - remove_from_stack.data.serials | length == 2
+ - remove_from_stack is changed
+
+ - name: Remove switch from stack idempotent
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ stack_id: '{{ stack_id }}'
+ serials:
+ - "QBSB-D75G-PXCG"
+ register: remove_from_stack_idempotent
+
+ - name: Debug remove_from_stack_idempotent
+ ansible.builtin.debug:
+ var: remove_from_stack_idempotent
+
+ - name: Assert remove_from_stack_idempotent
+ ansible.builtin.assert:
+ that:
+ - remove_from_stack_idempotent.data is defined
+ - remove_from_stack_idempotent.data.serials | length == 2
+ - remove_from_stack_idempotent is not changed
+
+ - name: Query all stacks in the network
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ register: query_all
+
+ - name: Debug query_all
+ ansible.builtin.debug:
+ var: query_all
+
+ - name: Assert query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data is defined
+ - query_all is not changed
+
+ - name: Query one stack
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ stack_id: '{{ stack_id }}'
+ register: query_one
+
+ - name: Debug query_one
+ ansible.builtin.debug:
+ var: query_one
+
+ - name: Assert query_one
+ ansible.builtin.assert:
+ that:
+ - query_one.data is defined
+ - query_one is not changed
+
+ - name: Query one stack using name
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ name: Test stack
+ register: query_one_name
+
+ - name: Debug query_one_name
+ ansible.builtin.debug:
+ var: query_one_name
+
+ - name: Assert query_one_name
+ ansible.builtin.assert:
+ that:
+ - query_one_name.data is defined
+ - query_one_name is not changed
+
+ always:
+ - name: Delete stack
+ cisco.meraki.meraki_ms_stack:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ stack_id: '{{ stack_id }}'
+ register: delete
+
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_switch_net_name }}'
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack_l3_interface/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack_l3_interface/tasks/main.yml
new file mode 100644
index 000000000..04bdaf015
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_stack_l3_interface/tasks/main.yml
@@ -0,0 +1,281 @@
+# Test code for the Meraki modules
+# 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: Create network with type switch
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkSwitch
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_switch
+
+ - set_fact:
+ net_id: '{{create_net_switch.data.id}}'
+
+ - name: Create new stack
+ meraki_switch_stack:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ name: Test stack
+ serials:
+ - "QBSB-BPR6-PRER"
+ - "QBSB-VLNE-E299"
+ - "QBSB-D75G-PXCG"
+ register: stack
+
+ - set_fact:
+ stack_id: '{{stack.data.id}}'
+
+ - name: Create l3 interface with check mode
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ default_gateway: "192.168.3.1"
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ delegate_to: localhost
+ register: create_check
+ check_mode: true
+
+ - assert:
+ that:
+ - create_check.data is defined
+ - create_check is changed
+
+ - name: Create l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ default_gateway: "192.168.3.1"
+ name: "Test L3 interface 2"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ delegate_to: localhost
+ register: create
+
+ - assert:
+ that:
+ - create.data is defined
+ - create is changed
+
+ - name: Query all l3 interfaces
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ state: query
+ stack_id: '{{ stack_id }}'
+ delegate_to: localhost
+ register: query_l3
+
+ - set_fact:
+ interface_id_1: '{{create.data.interface_id}}'
+
+ - name: Query one l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ state: query
+ stack_id: '{{ stack_id }}'
+ interface_id: '{{interface_id_1}}'
+ delegate_to: localhost
+ register: query_one
+
+ - debug:
+ var: query_one
+
+ - name: Update l3 interface with check mode
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ name: "Test L3 interface 3"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ interface_id: '{{interface_id_1}}'
+ multicast_routing: disabled
+ vlan_id: 11
+ delegate_to: localhost
+ register: update_check
+ check_mode: true
+
+ - assert:
+ that:
+ - update_check.data is defined
+ - update_check is changed
+
+ - name: Update l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ name: "Test L3 interface 3"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ interface_id: '{{interface_id_1}}'
+ multicast_routing: disabled
+ vlan_id: 11
+ delegate_to: localhost
+ register: update
+
+ - assert:
+ that:
+ - update.data is defined
+ - update is changed
+
+ - name: Update l3 interface idempotent
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ name: "Test L3 interface 3"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ delegate_to: localhost
+ register: update_idempotent
+
+ - assert:
+ that:
+ update_idempotent is not changed
+
+ - name: Update l3 interface idempotent with check mode
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ name: "Test L3 interface 3"
+ subnet: "192.168.3.0/24"
+ interface_ip: "192.168.3.2"
+ multicast_routing: disabled
+ vlan_id: 11
+ delegate_to: localhost
+ register: update_idempotent_check
+ check_mode: true
+
+ - assert:
+ that:
+ update_idempotent_check is not changed
+
+
+ - name: Query all l3 interfaces
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ state: query
+ stack_id: '{{ stack_id }}'
+ delegate_to: localhost
+ register: query_l3
+
+ - debug:
+ var: query_l3
+
+ - set_fact:
+ interface_id_1: '{{query_l3.data[0].interface_id}}'
+ ignore_errors: true
+
+ - assert:
+ that:
+ - query_l3.data is defined
+ - query_l3.data | length > 0
+
+ - name: Query one l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{ stack_id }}'
+ name: Test L3 interface
+ delegate_to: localhost
+ register: query_one
+
+ - assert:
+ that:
+ - query_one.data is defined
+
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+
+ always:
+ - name: Delete l3 interface with check mode
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ interface_id: '{{ item }}'
+ delegate_to: localhost
+ check_mode: true
+ register: delete_check
+ loop:
+ - '{{interface_id_1}}'
+
+ - assert:
+ that:
+ - delete_check is changed
+
+ - name: Delete l3 interface
+ meraki_ms_stack_l3_interface:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ stack_id: '{{stack_id}}'
+ interface_id: '{{ interface_id_1 }}'
+ delegate_to: localhost
+ register: delete
+
+ - assert:
+ that:
+ - delete is changed
+
+ - name: Remove switches from stack
+ meraki_switch_stack:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ name: Test stack
+ register: stack
+
+ # - name: Delete network
+ # meraki_network:
+ # auth_key: '{{ auth_key }}'
+ # state: absent
+ # org_name: '{{test_org_name}}'
+ # net_name: IntTestNetworkSwitch
+ # delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/tasks/main.yml
new file mode 100644
index 000000000..6c09875ee
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_storm_control/tasks/main.yml
@@ -0,0 +1,113 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Create switch network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: Home - Switch
+ timezone: America/Chicago
+ type: switch
+ delegate_to: localhost
+ register: net
+
+ - name: Set fact net
+ ansible.builtin.set_fact:
+ net_id: '{{ net.data.id }}'
+
+ - name: Add device to network
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: Home - Switch
+ serial: '{{ serial_switch_l3 }}'
+ delegate_to: localhost
+
+ - name: Reset data
+ cisco.meraki.meraki_ms_storm_control:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: Home - Switch
+ broadcast_threshold: 100
+ multicast_threshold: 100
+ unknown_unicast_threshold: 100
+ delegate_to: localhost
+
+ - name: Set broadcast settings
+ cisco.meraki.meraki_ms_storm_control:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: Home - Switch
+ broadcast_threshold: 75
+ multicast_threshold: 70
+ unknown_unicast_threshold: 65
+ delegate_to: localhost
+ register: set_all
+
+ - name: Assert set_all
+ ansible.builtin.assert:
+ that:
+ - set_all is changed
+ - set_all.data is defined
+ - set_all.diff is defined
+ - set_all.diff.before is defined
+ - set_all.diff.after is defined
+ - '{{ set_all.data.broadcast_threshold }} == 75'
+ - '{{ set_all.data.multicast_threshold }} == 70'
+ - '{{ set_all.data.unknown_unicast_threshold }} == 65'
+
+ - name: Set broadcast settings with idempotency
+ cisco.meraki.meraki_ms_storm_control:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: Home - Switch
+ broadcast_threshold: 75
+ multicast_threshold: 70
+ unknown_unicast_threshold: 65
+ delegate_to: localhost
+ register: set_all_idempotent
+
+ - name: Assert set_all_idempotent
+ ansible.builtin.assert:
+ that:
+ - set_all_idempotent is not changed
+ - set_all_idempotent.data is defined
+ - '{{ set_all_idempotent.data.broadcast_threshold }} == 75'
+ - '{{ set_all_idempotent.data.multicast_threshold }} == 70'
+ - '{{ set_all_idempotent.data.unknown_unicast_threshold }} == 65'
+
+ - name: Query storm control settings
+ cisco.meraki.meraki_ms_storm_control:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: Home - Switch
+ delegate_to: localhost
+ register: query_all
+
+ - name: Debug query_all
+ ansible.builtin.debug:
+ var: query_all
+
+ - name: Assert query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data is defined
+
+ # always:
+ # - name: Delete switch network
+ # cisco.meraki.meraki_network:
+ # auth_key: '{{ auth_key }}'
+ # state: absent
+ # org_name: '{{ test_org_name }}'
+ # net_name: Home - Switch
+ # delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/tasks/main.yml
new file mode 100644
index 000000000..3ebd43ec3
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_switchport/tasks/main.yml
@@ -0,0 +1,782 @@
+# 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: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+- name: Query all switchports
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: query
+ serial: '{{ serial_switch }}'
+ delegate_to: localhost
+ register: query_all
+
+- name: Debug query_all
+ ansible.builtin.debug:
+ msg: '{{ query_all }}'
+
+- name: Query one switchport
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: query
+ serial: '{{ serial_switch }}'
+ number: 1
+ delegate_to: localhost
+ register: query_one
+
+- name: Debug query_all
+ ansible.builtin.debug:
+ msg: '{{ query_one }}'
+
+- name: Enable switchport with check mode
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Temporary
+ delegate_to: localhost
+ check_mode: true
+ register: update_port_true_check
+
+- name: Debug update_port_true_check
+ ansible.builtin.debug:
+ msg: '{{ update_port_true_check }}'
+
+- name: Assert update_port_true_check
+ ansible.builtin.assert:
+ that:
+ - update_port_true_check is changed
+ - update_port_true_check.data is defined
+
+- name: Enable switchport
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ delegate_to: localhost
+ register: update_port_true
+
+- name: Debug update_port_true
+ ansible.builtin.debug:
+ msg: '{{ update_port_true }}'
+
+- name: Assert update_port_true
+ ansible.builtin.assert:
+ that:
+ - update_port_true.data.enabled == True
+
+- name: Disable switchport
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: false
+ delegate_to: localhost
+ register: update_port_false
+
+- name: Debug update_port_false
+ ansible.builtin.debug:
+ msg: '{{ update_port_false }}'
+
+- name: Assert update_port_false
+ ansible.builtin.assert:
+ that:
+ - update_port_false.data.enabled == False
+
+- name: Name switchport
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ name: Test Port
+ delegate_to: localhost
+ register: update_port_name
+
+- name: Debug update_port_name
+ ansible.builtin.debug:
+ msg: '{{ update_port_name }}'
+
+- name: Assert update_port_name
+ ansible.builtin.assert:
+ that:
+ - update_port_name.data.name == 'Test Port'
+
+- name: Configure access port with check mode
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ delegate_to: localhost
+ check_mode: true
+ register: update_access_port_changed
+
+- name: Debug update_access_port_changed
+ ansible.builtin.debug:
+ msg: '{{ update_access_port_changed }}'
+
+- name: Assert update_access_port_changed
+ ansible.builtin.assert:
+ that:
+ - update_access_port_changed is changed
+ - update_access_port_changed.data is defined
+
+- name: Configure access port
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ delegate_to: localhost
+ register: update_access_port
+
+- name: Debug update_access_port
+ ansible.builtin.debug:
+ msg: '{{ update_access_port }}'
+
+- name: Assert update_access_port
+ ansible.builtin.assert:
+ that:
+ - update_access_port.data.vlan == 10
+
+- name: Configure flexible stacking
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch_l3 }}'
+ number: 7
+ enabled: true
+ flexible_stacking_enabled: true
+ delegate_to: localhost
+ register: flex_stacking_enabled
+
+- name: Debug flex_stacking_enabled
+ ansible.builtin.debug:
+ msg: '{{ flex_stacking_enabled }}'
+
+- name: Assert flex_stacking_enabled
+ ansible.builtin.assert:
+ that:
+ - flex_stacking_enabled.data.flexible_stacking_enabled == true
+
+# - name: Configure adaptive policy and SGT
+# cisco.meraki.meraki_ms_switchport:
+# auth_key: '{{ auth_key }}'
+# state: present
+# serial: '{{ serial_switch_l3 }}'
+# number: 7
+# enabled: true
+# adaptive_policy_group_id: "1234"
+# peer_sgt_capable: true
+# delegate_to: localhost
+# register: adaptive_policy
+
+# - ansible.builtin.debug:
+# msg: '{{adaptive_policy}}'
+
+# - ansible.builtin.assert:
+# that:
+# - adaptive_policy.data.adaptive_policy_group_id == "1234"
+# - adaptive_policy.data.peer_sgt_capable == true
+
+- name: Configure port as trunk
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 8
+ enabled: true
+ name: Test Port
+ type: trunk
+ vlan: 10
+ allowed_vlans: 10, 100, 200
+ delegate_to: localhost
+
+- name: Remove VLAN on switchport
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 8
+ enabled: true
+ type: trunk
+ vlan: 0
+ delegate_to: localhost
+ register: remove_vlan
+
+- name: Debug remove_vlan
+ ansible.builtin.debug:
+ msg: '{{ remove_vlan }}'
+
+- name: Assert remove_vlan
+ ansible.builtin.assert:
+ that:
+ - remove_vlan.data.vlan == None
+
+- name: Convert trunk port to access
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 8
+ enabled: true
+ name: Test Port
+ type: access
+ vlan: 10
+ delegate_to: localhost
+
+- name: Test converted port for idempotency
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 8
+ enabled: true
+ name: Test Port
+ type: access
+ vlan: 10
+ delegate_to: localhost
+ register: convert_idempotent
+
+- name: Assert convert_idempotent
+ ansible.builtin.assert:
+ that:
+ - convert_idempotent.changed == False
+
+- name: Change voice VLAN for next task
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ voice_vlan: 12
+ delegate_to: localhost
+ register: update_port_vvlan
+
+- name: Configure access port with voice VLAN
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ voice_vlan: 11
+ delegate_to: localhost
+ register: update_port_vvlan
+
+- name: Debug update_port_vvlan
+ ansible.builtin.debug:
+ msg: '{{ update_port_vvlan }}'
+
+- name: Assert update_port_vvlan
+ ansible.builtin.assert:
+ that:
+ - update_port_vvlan.data.voice_vlan == 11
+ - update_port_vvlan.changed == True
+
+- name: Check access port for idempotenty
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ voice_vlan: 11
+ delegate_to: localhost
+ register: update_port_access_idempotent
+
+- name: Debug update_port_access_idempotent
+ ansible.builtin.debug:
+ msg: '{{ update_port_access_idempotent }}'
+
+- name: Assert update_port_access_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_port_access_idempotent.changed == False
+ - update_port_access_idempotent.data is defined
+
+- name: Configure access port removing voice VLAN
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Test Port
+ tags: desktop
+ type: access
+ vlan: 10
+ voice_vlan_state: absent
+ delegate_to: localhost
+ register: update_port_remove_vvlan
+
+- name: Debut update_port_remove_vvlan
+ ansible.builtin.debug:
+ msg: '{{ update_port_remove_vvlan }}'
+
+- name: Assert
+ ansible.builtin.assert:
+ that:
+ - update_port_remove_vvlan.data.voice_vlan == None
+ - update_port_remove_vvlan.changed == True
+
+- name: Configure trunk port
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Server port
+ tags: server
+ type: trunk
+ allowed_vlans: all
+ vlan: 8
+ delegate_to: localhost
+ register: update_trunk
+
+- name: Debug update_trunk
+ ansible.builtin.debug:
+ msg: '{{ update_trunk }}'
+
+- name: Assert update_trunk
+ ansible.builtin.assert:
+ that:
+ - update_trunk.data.tags.0 == 'server'
+ - update_trunk.data.type == 'trunk'
+ - update_trunk.data.allowed_vlans == 'all'
+
+- name: Configure trunk port with specific VLANs
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Server port
+ tags: server
+ type: trunk
+ vlan: 8
+ allowed_vlans:
+ - 10
+ - 15
+ - 20
+ delegate_to: localhost
+ register: update_trunk
+
+- name: Debug update_trunk
+ ansible.builtin.debug:
+ msg: '{{ update_trunk }}'
+
+- name: Assert update_trunk
+ ansible.builtin.assert:
+ that:
+ - update_trunk.data.tags.0 == 'server'
+ - update_trunk.data.type == 'trunk'
+ - update_trunk.data.allowed_vlans == '8,10,15,20'
+
+- name: Configure trunk port with specific VLANs and native VLAN
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Server port
+ tags: server
+ type: trunk
+ vlan: 2
+ allowed_vlans:
+ - 10
+ - 15
+ - 20
+ delegate_to: localhost
+ register: update_trunk
+
+- name: Debug update_trunk
+ ansible.builtin.debug:
+ msg: '{{ update_trunk }}'
+
+- name: Assert update_trunk
+ ansible.builtin.assert:
+ that:
+ - update_trunk.data.tags.0 == 'server'
+ - update_trunk.data.type == 'trunk'
+ - update_trunk.data.allowed_vlans == '2,10,15,20'
+
+- name: Check for idempotency on trunk port
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ name: Server port
+ tags: server
+ type: trunk
+ vlan: 2
+ allowed_vlans:
+ - 10
+ - 15
+ - 20
+ delegate_to: localhost
+ register: update_trunk_idempotent
+
+- name: Debug update_trunk_idempotent
+ ansible.builtin.debug:
+ msg: '{{ update_trunk_idempotent }}'
+
+- name: Assert update_trunk_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_trunk_idempotent.changed == False
+ - update_trunk_idempotent.data is defined
+
+# - name: Enable Sticky MAC Using Replace
+# cisco.meraki.meraki_ms_switchport:
+# auth_key: '{{ auth_key }}'
+# state: present
+# serial: '{{ serial_switch }}'
+# number: 7
+# enabled: true
+# type: access
+# access_policy_type: "Sticky MAC allow list"
+# sticky_mac_allow_list:
+# macs:
+# - "aa:aa:bb:bb:cc:cc"
+# state: replaced
+# sticky_mac_allow_list_limit: 1
+# delegate_to: localhost
+# register: sticky_mac_allow_replace
+
+# - name: Debug sticky_mac_allow_replace
+# ansible.builtin.debug:
+# msg: '{{ sticky_mac_allow_replace }}'
+
+# - name: Assert sticky_mac_allow_replace
+# ansible.builtin.assert:
+# that:
+# - sticky_mac_allow_replace.data.sticky_mac_allow_list_limit == 1
+# - sticky_mac_allow_replace.data.sticky_mac_allow_list == ["aa:aa:bb:bb:cc:cc"]
+
+- name: Enable Sticky MAC Using Replace Idempotent
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "Sticky MAC allow list"
+ sticky_mac_allow_list:
+ macs:
+ - "aa:aa:bb:bb:cc:cc"
+ state: replaced
+ sticky_mac_allow_list_limit: 1
+ delegate_to: localhost
+ register: idempotent_sticky_mac_allow_replace
+
+- name: Debug idempotent_sticky_mac_allow_replace
+ ansible.builtin.debug:
+ msg: '{{ idempotent_sticky_mac_allow_replace }}'
+
+- name: Assert idempotent_mac_allow_replace
+ ansible.builtin.assert:
+ that:
+ - idempotent_sticky_mac_allow_replace.changed == False
+ - idempotent_sticky_mac_allow_replace.data is defined
+
+- name: Enable Sticky MAC Using Merge
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "Sticky MAC allow list"
+ sticky_mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: merged
+ sticky_mac_allow_list_limit: 2
+ delegate_to: localhost
+ register: sticky_mac_allow_merge
+
+- name: Debug sticky_mac_allow_merge
+ ansible.builtin.debug:
+ msg: '{{ sticky_mac_allow_merge }}'
+
+- name: Assert sticky_mac_allow_merge
+ ansible.builtin.assert:
+ that:
+ - sticky_mac_allow_merge.data.sticky_mac_allow_list_limit == 2
+ - sticky_mac_allow_merge.data.sticky_mac_allow_list == ["aa:aa:bb:bb:cc:cc", "11:aa:bb:bb:cc:cc"]
+
+- name: Enable Sticky MAC Using Merge Idempotent
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "Sticky MAC allow list"
+ sticky_mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: merged
+ sticky_mac_allow_list_limit: 2
+ delegate_to: localhost
+ register: idempotent_sticky_mac_allow_merge
+
+- name: Debug idempotent_sticky_mac_allow_merge
+ ansible.builtin.debug:
+ msg: '{{ idempotent_sticky_mac_allow_merge }}'
+
+- name: Assert idempotent_sticky_mac_allow_merge
+ ansible.builtin.assert:
+ that:
+ - idempotent_sticky_mac_allow_merge.changed == False
+ - idempotent_sticky_mac_allow_merge.data is defined
+
+- name: Delete Sticky MAC From Allow List
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "Sticky MAC allow list"
+ sticky_mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: deleted
+ sticky_mac_allow_list_limit: 2
+ delegate_to: localhost
+ register: delete_sticky_mac
+
+- name: Debug delete_sticky_mac
+ ansible.builtin.debug:
+ msg: '{{ delete_sticky_mac }}'
+
+- name: Assert delete_sticky_mac
+ ansible.builtin.assert:
+ that:
+ - delete_sticky_mac.data.sticky_mac_allow_list_limit == 2
+ - delete_sticky_mac.data.sticky_mac_allow_list == ["aa:aa:bb:bb:cc:cc"]
+
+- name: Delete Sticky MAC Idempotent
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "Sticky MAC allow list"
+ sticky_mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: deleted
+ sticky_mac_allow_list_limit: 2
+ delegate_to: localhost
+ register: idempotent_delete_sticky_mac
+
+- name: Debug idempotent_delete_sticky_mac
+ ansible.builtin.debug:
+ msg: '{{ idempotent_delete_sticky_mac }}'
+
+- name: Assert idempotent_delete_sticky_mac
+ ansible.builtin.assert:
+ that:
+ - idempotent_delete_sticky_mac.changed == False
+ - idempotent_delete_sticky_mac.data is defined
+
+- name: Enable MAC Allow Using Replace
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "MAC allow list"
+ mac_allow_list:
+ macs:
+ - "aa:aa:bb:bb:cc:cc"
+ state: replaced
+ delegate_to: localhost
+ register: mac_allow_replace
+
+- name: Debug mac_allow_replace
+ ansible.builtin.debug:
+ msg: '{{ mac_allow_replace }}'
+
+- name: Assert mac_allow_replace
+ ansible.builtin.assert:
+ that:
+ - mac_allow_replace.data.mac_allow_list == ["aa:aa:bb:bb:cc:cc"]
+
+- name: Enable MAC Allow Using Replace Idempotent
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "MAC allow list"
+ mac_allow_list:
+ macs:
+ - "aa:aa:bb:bb:cc:cc"
+ state: replaced
+ delegate_to: localhost
+ register: idempotent_mac_allow_replace
+
+- name: Debug idempotent_mac_allow_replace
+ ansible.builtin.debug:
+ msg: '{{ idempotent_mac_allow_replace }}'
+
+- name: Assert idempotent_mac_allow_replace
+ ansible.builtin.assert:
+ that:
+ - idempotent_mac_allow_replace.changed == False
+ - idempotent_mac_allow_replace.data is defined
+
+- name: Enable MAC Allow Using Merge
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "MAC allow list"
+ mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: merged
+ delegate_to: localhost
+ register: mac_allow_merge
+
+- name: Debug mac_allow_merge
+ ansible.builtin.debug:
+ msg: '{{ mac_allow_merge }}'
+
+- name: Assert mac_allow_merge
+ ansible.builtin.assert:
+ that:
+ - mac_allow_merge.data.mac_allow_list == ["aa:aa:bb:bb:cc:cc", "11:aa:bb:bb:cc:cc"]
+
+- name: Enable MAC Allow Using Merge Idempotent
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "MAC allow list"
+ mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: merged
+ delegate_to: localhost
+ register: idempotent_mac_allow_merge
+
+- name: Debug idempotent_mac_allow_merge
+ ansible.builtin.debug:
+ msg: '{{ idempotent_mac_allow_merge }}'
+
+- name: Assert idempotent_mac_allow_merge
+ ansible.builtin.assert:
+ that:
+ - idempotent_mac_allow_merge.changed == False
+ - idempotent_mac_allow_merge.data is defined
+
+- name: Delete MAC From Allow List
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "MAC allow list"
+ mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: deleted
+ delegate_to: localhost
+ register: delete_mac
+
+- name: Debug delete_mac
+ ansible.builtin.debug:
+ msg: '{{ delete_mac }}'
+
+- name: Assert delete_mac
+ ansible.builtin.assert:
+ that:
+ - delete_mac.data.mac_allow_list == ["aa:aa:bb:bb:cc:cc"]
+
+- name: Delete MAC Idempotent
+ cisco.meraki.meraki_ms_switchport:
+ auth_key: '{{ auth_key }}'
+ state: present
+ serial: '{{ serial_switch }}'
+ number: 7
+ enabled: true
+ type: access
+ access_policy_type: "MAC allow list"
+ mac_allow_list:
+ macs:
+ - "11:aa:bb:bb:cc:cc"
+ state: deleted
+ delegate_to: localhost
+ register: idempotent_delete_mac
+
+- name: Debug idempotent_delete_mac
+ ansible.builtin.debug:
+ msg: '{{ idempotent_delete_mac }}'
+
+- name: Assert idempotent_delete_mac
+ ansible.builtin.assert:
+ that:
+ - idempotent_delete_mac.changed == False
+ - idempotent_delete_mac.data is defined
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/tasks/main.yml
new file mode 100644
index 000000000..b139ede74
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_content_filtering/tasks/main.yml
@@ -0,0 +1,309 @@
+# Test code for the Meraki Content Filteringmodule
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Create network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ type: appliance
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_appliance
+
+ - name: Test net_name and id exclusivity
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ net_id: 12345
+ state: present
+ allowed_urls:
+ - "http://www.ansible.com/*"
+ register: net_exclusive
+ failed_when: false
+ changed_when: false
+
+ - name: Assert net_exclusive
+ ansible.builtin.assert:
+ that:
+ - 'net_exclusive.msg == "net_name and net_id are mutually exclusive"'
+
+ - name: Set blocked URL categories for idempotency test
+ cisco.meraki.meraki_mx_content_filtering:
+ state: present
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ category_list_size: full list
+ blocked_categories:
+ - "Adult"
+ - "Advertisements"
+ - "Alcohol"
+ delegate_to: localhost
+
+ # - name: Set blocked URL categories with idempotency
+ # cisco.meraki.meraki_mx_content_filtering:
+ # state: present
+ # auth_key: "{{ auth_key }}"
+ # org_name: "{{ test_org_name }}"
+ # net_name: "{{ test_net_name }}"
+ # category_list_size: full list
+ # blocked_categories:
+ # - "Adult"
+ # - "Advertisements"
+ # - "Alcohol"
+ # delegate_to: localhost
+ # register: category_idempotent
+
+ # - name: Assert category_idempotent
+ # ansible.builtin.assert:
+ # that:
+ # - category_idempotent is not changed
+ # - category_idempotent.data is defined
+
+ - name: Set single allowed URL pattern with check mode
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ allowed_urls:
+ - "http://www.ansible.com/*"
+ register: single_allowed_check
+ check_mode: true
+
+ - name: Assert single_allowed_check
+ ansible.builtin.assert:
+ that:
+ - single_allowed_check.data.allowed_url_patterns | length == 1
+ - single_allowed_check is changed
+
+ - name: Set single allowed URL pattern
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ allowed_urls:
+ - "http://www.ansible.com/*"
+ register: single_allowed
+
+ - name: Assert single_allowed
+ ansible.builtin.assert:
+ that:
+ - single_allowed.data.allowed_url_patterns | length == 1
+
+ - name: Set single allowed URL pattern for idempotency with check mode
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ allowed_urls:
+ - "http://www.ansible.com/*"
+ register: single_allowed_idempotent_check
+ check_mode: true
+
+ - name: Debug single_allowed_idempotent_check
+ ansible.builtin.debug:
+ var: single_allowed_idempotent_check
+
+ - name: Assert single_allowed and single_allowed_idempotent_check
+ ansible.builtin.assert:
+ that:
+ - single_allowed_idempotent_check is not changed
+ - single_allowed.data.allowed_url_patterns | length == 1
+
+ - name: Set single allowed URL pattern for idempotency
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ allowed_urls:
+ - "http://www.ansible.com/*"
+ register: single_allowed_idempotent
+
+ - name: Debug single_allowed_idempotent
+ ansible.builtin.debug:
+ var: single_allowed_idempotent
+
+ - name: Assert single_allowed_idempotent
+ ansible.builtin.assert:
+ that:
+ - single_allowed_idempotent.changed == False
+ - single_allowed_idempotent.data is defined
+
+ - name: Set single blocked URL pattern
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ blocked_urls:
+ - "http://www.ansible.com/*"
+ register: single_blocked
+
+ - name: Debug single_blocked
+ ansible.builtin.debug:
+ var: single_blocked
+
+ - name: Assert single_blocked
+ ansible.builtin.assert:
+ that:
+ - single_blocked.data.blocked_url_patterns | length == 1
+
+ - name: Set two allowed URL pattern
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ allowed_urls:
+ - "http://www.ansible.com/*"
+ - "http://www.redhat.com"
+ register: two_allowed
+
+ - name: Debug two_allowed
+ ansible.builtin.debug:
+ var: two_allowed
+
+ - name: Debug two_allowed
+ ansible.builtin.assert:
+ that:
+ - two_allowed.changed == True
+ - two_allowed.data.allowed_url_patterns | length == 2
+
+ - name: Set blocked URL category
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ category_list_size: full list
+ blocked_categories:
+ - "Adult"
+ register: blocked_category
+
+ - name: Debug blocked_category
+ ansible.builtin.debug:
+ var: blocked_category
+
+ - name: Assert blocked_category
+ ansible.builtin.assert:
+ that:
+ - blocked_category.changed == True
+ - blocked_category.data.blocked_url_categories | length == 1
+ # - blocked_category.data.url_category_list_size == "fullList"
+
+ # - name: Set blocked URL category with top sites
+ # cisco.meraki.meraki_mx_content_filtering:
+ # auth_key: '{{ auth_key }}'
+ # org_name: '{{ test_org_name }}'
+ # net_name: '{{ test_net_name }}'
+ # state: present
+ # category_list_size: top sites
+ # blocked_categories:
+ # - "Adult"
+ # register: blocked_category
+
+ # - name: Debug blocked_category
+ # ansible.builtin.debug:
+ # var: blocked_category
+
+ # - name: Assert blocked_category
+ # ansible.builtin.assert:
+ # that:
+ # - blocked_category.changed == True
+ # - blocked_category.data.blocked_url_categories | length == 1
+ # # - blocked_category.data.url_category_list_size == "topSites"
+
+ - name: Query all content filtering information
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: query
+ delegate_to: localhost
+ register: query_all
+
+ - name: Debug query_all
+ ansible.builtin.debug:
+ var: query_all
+
+ - name: Query all content filtering assertion
+ ansible.builtin.assert:
+ that:
+ - query_all.data.categories is defined
+ - query_all.data.policy is defined
+
+ - name: Query categories
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: query
+ subset: categories
+ delegate_to: localhost
+ register: query_categories
+
+ - name: Debug query_categories
+ ansible.builtin.debug:
+ var: query_categories
+
+ - name: Query categories assertion
+ ansible.builtin.assert:
+ that:
+ - query_categories.data is defined
+
+ - name: Query content filtering policies
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ subset: policy
+ state: query
+ delegate_to: localhost
+ register: query_policy
+
+ - name: Debug query_policy
+ ansible.builtin.debug:
+ var: query_policy
+
+ - name: Query contnet filtering policy assertion
+ ansible.builtin.assert:
+ that:
+ - query_policy.data is defined
+
+ always:
+ - name: Reset policies
+ cisco.meraki.meraki_mx_content_filtering:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ category_list_size: full list
+ allowed_urls:
+ -
+ blocked_urls:
+ -
+ blocked_categories:
+ -
+
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: absent
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/main.yml
new file mode 100644
index 000000000..c04238da9
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/main.yml
@@ -0,0 +1,7 @@
+# Test code for the Meraki Webhooks 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
+ ansible.builtin.include_tasks: tests.yml
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/tests.yml
new file mode 100644
index 000000000..704738ccd
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_intrusion_prevention/tasks/tests.yml
@@ -0,0 +1,330 @@
+# Test code for the Meraki IPS module
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tests
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Create test network
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ type: appliance
+ register: net
+
+ - name: Debug net
+ ansible.builtin.debug:
+ var: net
+
+ - name: Set allowed rules for organization in check mode
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ allowed_rules:
+ - rule_id: "meraki:intrusion/snort/GID/01/SID/5805"
+ rule_message: Test rule
+ check_mode: true
+ register: create_org_check
+
+ - name: Assert create_org_check
+ ansible.builtin.assert:
+ that:
+ - create_org_check is changed
+ - create_org_check.data is defined
+
+ - name: Set allowed rules for organization
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_id: "{{ test_org_id }}"
+ allowed_rules:
+ - rule_id: "meraki:intrusion/snort/GID/01/SID/5805"
+ rule_message: Test rule
+ register: create_org
+
+ - name: Assert create_org
+ ansible.builtin.assert:
+ that:
+ - create_org is changed
+ - create_org.data is defined
+ - create_org.data.allowed_rules | length == 1
+
+ - name: Set allowed rules for organization with idempotency
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_id: "{{ test_org_id }}"
+ allowed_rules:
+ - rule_id: "meraki:intrusion/snort/GID/01/SID/5805"
+ rule_message: Test rule
+ register: create_org_idempotent
+
+ - name: Assert create_org_idempotent
+ ansible.builtin.assert:
+ that:
+ - create_org_idempotent is not changed
+ - create_org_idempotent.data is defined
+
+ - name: Query IPS info for organization
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ register: query_org
+
+ - name: Assert query_org
+ ansible.builtin.assert:
+ that:
+ - query_org.data.allowed_rules is defined
+
+ - name: Set mode to prevention with check mode
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ mode: prevention
+ check_mode: true
+ register: mode_check
+
+ - name: Assert mode_check
+ ansible.builtin.assert:
+ that:
+ - mode_check is changed
+ - mode_check.data is defined
+
+ - name: Set mode to prevention
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ mode: prevention
+ register: mode
+
+ - name: Debug mode
+ ansible.builtin.debug:
+ var: mode
+
+ - name: Assert mode
+ ansible.builtin.assert:
+ that:
+ - mode is changed
+ - mode.data.mode is defined
+
+ - name: Set mode to prevention with idempotency
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ mode: prevention
+ register: mode_idempotent
+
+ - name: Debug mode_idempotent
+ ansible.builtin.debug:
+ var: mode_idempotent
+
+ - name: Assert mode_idempotent
+ ansible.builtin.assert:
+ that:
+ - mode_idempotent is not changed
+ - mode_idempotent.data.mode is defined
+
+ - name: Set full ruleset with check mode
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ mode: prevention
+ ids_rulesets: security
+ protected_networks:
+ use_default: true
+ included_cidr:
+ - 192.0.1.0/24
+ excluded_cidr:
+ - 10.0.1.0/24
+ check_mode: true
+ register: full_check
+
+ - name: Debug full_check
+ ansible.builtin.debug:
+ var: full_check
+
+ - name: Assert full_check
+ ansible.builtin.assert:
+ that:
+ - full_check is changed
+ - full_check.data.mode is defined
+
+ # Meraki says protectedNetworks isn't supported on layer 3 firewalls
+ # Passthrough mode cannot be set via API
+ # - name: Set full ruleset
+ # cisco.meraki.meraki_mx_intrusion_prevention:
+ # auth_key: '{{ auth_key }}'
+ # state: present
+ # org_name: '{{ test_org_name }}'
+ # net_name: '{{ test_net_name }} - IPS'
+ # mode: detection
+ # ids_rulesets: security
+ # protected_networks:
+ # use_default: true
+ # included_cidr:
+ # - 192.0.1.0/24
+ # excluded_cidr:
+ # - 10.0.1.0/24
+ # delegate_to: localhost
+ # register: full
+
+ # - ansible.builtin.debug:
+ # var: full
+
+ # - ansible.builtin.assert:
+ # that:
+ # - full is changed
+ # - full.data.mode is defined
+
+ # Meraki says protectedNetworks isn't supported on layer 3 firewalls
+ # Passthrough mode cannot be set via API
+ # - name: Set full ruleset with idempotency
+ # cisco.meraki.meraki_mx_intrusion_prevention:
+ # auth_key: '{{ auth_key }}'
+ # state: present
+ # org_name: '{{ test_org_name }}'
+ # net_name: '{{ test_net_name }} - IPS'
+ # mode: prevention
+ # ids_rulesets: security
+ # protected_networks:
+ # use_default: true
+ # included_cidr:
+ # - 192.0.1.0/24
+ # excluded_cidr:
+ # - 10.0.1.0/24
+ # delegate_to: localhost
+ # register: full_idempotent
+
+ # - ansible.builtin.debug:
+ # var: full_idempotent
+
+ # - ansible.builtin.assert:
+ # that:
+ # - full_idempotent is not changed
+ # - full_idempotent.data.mode is defined
+
+ - name: Query IPS info for network
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: query
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ register: query_net
+
+ - name: Assert query_net
+ ansible.builtin.assert:
+ that:
+ - query_net is defined
+
+ - name: Test use_default error with included_cidr
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ mode: prevention
+ ids_rulesets: security
+ protected_networks:
+ use_default: false
+ excluded_cidr:
+ - 10.0.1.0/24
+ check_mode: true
+ register: included_missing
+ ignore_errors: true
+
+ - name: Assert included_missing
+ ansible.builtin.assert:
+ that:
+ - 'included_missing.msg == "included_cidr is required when use_default is False."'
+
+ - name: Test use_default error with included_cidr
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ mode: prevention
+ ids_rulesets: security
+ protected_networks:
+ use_default: false
+ included_cidr:
+ - 10.0.1.0/24
+ check_mode: true
+ register: excluded_missing
+ ignore_errors: true
+
+ - name: Assert excluded_missing
+ ansible.builtin.assert:
+ that:
+ - 'excluded_missing.msg == "excluded_cidr is required when use_default is False."'
+
+ # #############################################################################
+ # # Tear down starts here
+ # #############################################################################
+ always:
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
+ register: delete_net
+
+ - name: Clear rules from organization with check mode
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ allowed_rules: []
+ check_mode: true
+ register: cleared
+
+ - name: Assert cleared
+ ansible.builtin.assert:
+ that:
+ - cleared is changed
+ - cleared.data is defined
+
+ - name: Clear rules from organization
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ allowed_rules: []
+ - name: Clear rules from organization with idempotency
+ cisco.meraki.meraki_mx_intrusion_prevention:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ allowed_rules: []
+ register: del_idempotent
+
+ - name: Assert del_idempotent
+ ansible.builtin.assert:
+ that:
+ - del_idempotent is not changed
+ - del_idempotent.data is defined
+
+ - name: Delete test network
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }} - IPS"
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml
new file mode 100644
index 000000000..36b7a2fb2
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml
@@ -0,0 +1,161 @@
+# Test code for the Meraki modules
+# 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: Block of tasks
+ block:
+ - name: Create network with type appliance
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ type: appliance
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net
+
+ - name: Claim a device into a network
+ cisco.meraki.meraki_device:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ serial: "{{ serial_appliance }}"
+ state: present
+ delegate_to: localhost
+
+ - name: Enable VLAN support on appliance network
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ enable_vlans: true
+ delegate_to: localhost
+
+ - name: Create a VLAN
+ cisco.meraki.meraki_mx_vlan:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ state: present
+ vlan_id: 10
+ name: TestVLAN
+ subnet: 192.0.10.0/24
+ appliance_ip: 192.0.10.1
+ delegate_to: localhost
+
+ - name: Query layer 2 interface settings
+ cisco.meraki.meraki_mx_l2_interface:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ state: query
+ delegate_to: localhost
+ register: query_all
+
+ - name: Assert query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data is defined
+
+ - name: Query a single layer 2 interface settings
+ cisco.meraki.meraki_mx_l2_interface:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ state: query
+ number: 2
+ delegate_to: localhost
+ register: query_one
+
+ - name: Assert query_one
+ ansible.builtin.assert:
+ that:
+ - query_one.data is defined
+
+ - name: Update interface configuration with check mode
+ cisco.meraki.meraki_mx_l2_interface:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ state: present
+ number: 2
+ port_type: access
+ vlan: 10
+ delegate_to: localhost
+ register: update_check
+ check_mode: true
+
+ - name: Debug update_check
+ ansible.builtin.debug:
+ var: update_check
+
+ - name: Assert update_check
+ ansible.builtin.assert:
+ that:
+ - update_check.data is defined
+ - update_check.data.vlan == 10
+ - update_check is changed
+
+ - name: Update interface configuration
+ cisco.meraki.meraki_mx_l2_interface:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ state: present
+ number: 2
+ port_type: access
+ vlan: 10
+ delegate_to: localhost
+ register: update
+
+ - name: Assert update
+ ansible.builtin.assert:
+ that:
+ - update.diff is defined
+ - update.data is defined
+ - update.data.vlan == 10
+ - update is changed
+
+ - name: Update interface configuration with idempotency
+ cisco.meraki.meraki_mx_l2_interface:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ state: present
+ number: 2
+ port_type: access
+ vlan: 10
+ delegate_to: localhost
+ register: update_idempotent
+
+ - name: Assert update_idempotent
+ ansible.builtin.assert:
+ that:
+ - update_idempotent.data is defined
+ - update_idempotent.data.vlan == 10
+ - update_idempotent is not changed
+
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+
+ always:
+ - name: Remove device from network
+ cisco.meraki.meraki_device:
+ auth_key: "{{ auth_key }}"
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ serial: "{{ serial_appliance }}"
+ state: absent
+ delegate_to: localhost
+
+ - name: Delete network
+ cisco.meraki.meraki_network:
+ auth_key: "{{ auth_key }}"
+ state: absent
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_appliance_net_name }}"
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml
new file mode 100644
index 000000000..c8dbd1718
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml
@@ -0,0 +1,369 @@
+# 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
+ delegate_to: localhost
+
+ - name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - assert:
+ that:
+ - query.data|length == 1
+
+ - name: Check rule values are lowercase
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - assert:
+ that:
+ - query.data.rules.0.policy == 'allow'
+ - query.data.rules.0.protocol == 'any'
+ - query.data.rules.0.src_port == 'any'
+ - query.data.rules.0.src_cidr == 'any'
+ - query.data.rules.0.dest_port == 'any'
+ - query.data.rules.0.dest_cidr == 'any'
+
+ - name: Set one firewall rule with check mode
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ check_mode: yes
+ register: create_one_check
+
+ - debug:
+ var: create_one_check
+
+ - assert:
+ that:
+ - create_one_check.data|length == 2
+ - create_one_check.data.0.dest_cidr == '192.0.1.1/32'
+ - create_one_check.data.0.protocol == 'tcp'
+ - create_one_check.data.0.policy == 'deny'
+ - create_one_check is changed
+ - create_one_check.data is defined
+
+ - name: Set one firewall rule
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_id: '{{test_org_id}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: create_one
+
+ - debug:
+ var: create_one
+
+ - assert:
+ that:
+ - create_one.data.rules|length == 2
+ - create_one.data.rules.0.dest_cidr == '192.0.1.1/32'
+ - create_one.data.rules.0.protocol == 'tcp'
+ - create_one.data.rules.0.policy == 'deny'
+ - create_one.changed == True
+ - create_one.data is defined
+
+ - name: Check for idempotency
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: create_one_idempotent
+
+ - debug:
+ msg: '{{create_one_idempotent}}'
+
+ - assert:
+ that:
+ - create_one_idempotent.changed == False
+ - create_one_idempotent.data is defined
+
+ - name: Update one existing rule
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny all documentation addresses
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32,192.0.1.2/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: update_one
+
+ - debug:
+ msg: '{{update_one}}'
+
+ - assert:
+ that:
+ - update_one.changed == True
+ - update_one.data is defined
+
+ - name: Create syslog in network
+ meraki_syslog:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ servers:
+ - host: 192.0.2.10
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ delegate_to: localhost
+
+ - name: Enable syslog for default rule with check mode
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+ check_mode: yes
+ register: default_syslog_check
+
+ - debug:
+ msg: '{{default_syslog_check}}'
+
+ - assert:
+ that:
+ - default_syslog_check.data is defined
+ - default_syslog_check.data.1.syslog_enabled == True
+
+
+ - name: Enable syslog for default rule
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+ register: default_syslog
+
+ - debug:
+ msg: '{{default_syslog}}'
+
+ - assert:
+ that:
+ - default_syslog.data is defined
+
+ - name: Set protocol to any for idempotency check
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: any
+ src_cidr: any
+ dest_port: any
+ dest_cidr: 192.0.1.1/32
+ protocol: any
+ policy: deny
+ delegate_to: localhost
+
+ - name: Check for protocol any idempotency
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: any
+ src_cidr: any
+ dest_port: any
+ dest_cidr: 192.0.1.1/32
+ protocol: any
+ policy: deny
+ delegate_to: localhost
+ register: any_idempotency
+
+ - assert:
+ that:
+ - any_idempotency is not changed
+
+ - name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - assert:
+ that:
+ - query.data.rules.1.syslog_enabled == True
+ - default_syslog.changed == True
+
+ - name: Disable syslog for default rule
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ syslog_default_rule: no
+ delegate_to: localhost
+ register: disable_syslog
+
+ - debug:
+ msg: '{{disable_syslog}}'
+
+ - assert:
+ that:
+ - disable_syslog.data is defined
+
+ - name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ msg: '{{query}}'
+
+ - name: Enable syslog for default rule with no rules and with check mode
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ syslog_default_rule: yes
+ delegate_to: localhost
+ check_mode: yes
+ register: enable_syslog_only_check
+
+ - debug:
+ msg: '{{enable_syslog_only_check}}'
+
+ - assert:
+ that:
+ - enable_syslog_only_check.data.rules.1.syslog_enabled == True
+ - enable_syslog_only_check is changed
+
+ - name: Query firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ msg: '{{query.data.rules.1}}'
+
+ - assert:
+ that:
+ - query.data.rules.1.syslog_enabled == False
+ - disable_syslog.changed == True
+
+ always:
+ - name: Delete all firewall rules
+ meraki_mx_l3_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules: []
+ delegate_to: localhost
+ register: delete_all
+
+ - name: Delete network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: absent
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/aliases
new file mode 100644
index 000000000..06fe32bc6
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/aliases
@@ -0,0 +1,2 @@
+unsupported
+
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/main.yml
new file mode 100644
index 000000000..bb4c6fc59
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/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/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml
new file mode 100644
index 000000000..875f24520
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml
@@ -0,0 +1,524 @@
+# 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
+
+ # - 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: Set firewall rules to empty array
+ # meraki_mx_l7_firewall:
+ # auth_key: '{{ auth_key }}'
+ # org_name: '{{test_org_name}}'
+ # net_name: TestNetAppliance
+ # state: present
+ # rules: []
+ # register: empty_list
+
+ # - debug:
+ # var: empty_list
+
+ # - assert:
+ # that:
+ # - empty_list.data.rules | length == 0
+
+ - name: Set firewall rules to empty array with idempotency
+ meraki_mx_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules: []
+ register: empty_list_idempotent
+
+ - debug:
+ var: empty_list_idempotent
+
+ - assert:
+ that:
+ - empty_list_idempotent.data.rules | length == 0
+ - empty_list_idempotent is not changed
+
+ - 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 denied countries
+ meraki_mx_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - type: blocked_countries
+ countries:
+ - CA
+ - AX
+ register: blocked_countries
+
+ - assert:
+ that:
+ - blocked_countries is changed
+
+ - name: Create firewall rule for allowed countries
+ meraki_mx_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - type: blocked_countries
+ countries:
+ - US
+ - FR
+ register: allowed_countries
+
+ - assert:
+ that:
+ - allowed_countries is changed
+
+ - name: Create firewall rule for allowed countries with idempotency
+ meraki_mx_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - type: blocked_countries
+ countries:
+ - US
+ - FR
+ register: allowed_countries_idempotent
+
+ - assert:
+ that:
+ - allowed_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: blocked_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 blocked_countries incomplete arguments
+ meraki_mx_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - type: blocked_countries
+ register: error_allowed
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - 'error_allowed.msg == "countries argument is required when type is blocked_countries."'
+
+ - name: Test blocked_countries incomplete arguments
+ meraki_mx_l7_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ rules:
+ - type: blocked_countries
+ register: error_denied
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - 'error_denied.msg == "countries argument is required when type is blocked_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
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/tasks/main.yml
new file mode 100644
index 000000000..bab98cd0c
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_malware/tasks/main.yml
@@ -0,0 +1,253 @@
+# Test code for the Meraki VLAN 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 test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ type: appliance
+ delegate_to: localhost
+ register: net
+
+ - set_fact:
+ net_id: '{{net.data.id}}'
+
+ - name: Enable malware protection with check mode
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ delegate_to: localhost
+ check_mode: yes
+ register: get_malware_check
+
+ - assert:
+ that:
+ - get_malware_check is changed
+ - get_malware_check.data is defined
+
+ - name: Enable malware protection
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ delegate_to: localhost
+ register: get_malware
+
+ - debug:
+ var: get_malware
+
+ - assert:
+ that:
+ - get_malware is changed
+ - get_malware.data.mode is defined
+
+ - name: Enable malware protection with idempotency
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ delegate_to: localhost
+ register: get_malware_idempotent
+
+ - debug:
+ var: get_malware_idempotent
+
+ - assert:
+ that:
+ - get_malware_idempotent is not changed
+ - get_malware_idempotent.data is defined
+
+ - name: Test error when mode is not set
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ allowed_files:
+ - sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
+ comment: random zip
+ delegate_to: localhost
+ register: test_mode_err
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - test_mode_err.msg == "mode must be set when allowed_files or allowed_urls is set."
+
+ - name: Set whitelisted file with check mode
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ allowed_files:
+ - sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
+ comment: random zip
+ delegate_to: localhost
+ check_mode: yes
+ register: set_file_check
+
+ - debug:
+ var:
+ set_file_check
+
+ - assert:
+ that:
+ - set_file_check is changed
+ - set_file_check.data is defined
+
+ - name: Set whitelisted file
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ mode: enabled
+ allowed_files:
+ - sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
+ comment: random zip
+ delegate_to: localhost
+ register: set_file
+
+ - debug:
+ var: set_file
+
+ - assert:
+ that:
+ - set_file is changed
+ - set_file.data.mode is defined
+
+ - name: Set whitelisted file with idempotency
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ allowed_files:
+ - sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
+ comment: random zip
+ delegate_to: localhost
+ register: set_file_idempotent
+
+ - debug:
+ var: set_file_idempotent
+
+ - assert:
+ that:
+ - set_file_idempotent is not changed
+ - set_file_idempotent.data is defined
+
+ - name: Set whitelisted url with check mode
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ allowed_urls:
+ - url: www.ansible.com
+ comment: Ansible
+ - url: www.google.com
+ comment: Google
+ delegate_to: localhost
+ check_mode: yes
+ register: set_url_check
+
+ - debug:
+ var:
+ set_url_check
+
+ - assert:
+ that:
+ - set_url_check is changed
+ - set_url_check.data is defined
+
+ - name: Set whitelisted url
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ allowed_urls:
+ - url: www.ansible.com
+ comment: Ansible
+ - url: www.google.com
+ comment: Google
+ delegate_to: localhost
+ register: set_url
+
+ - debug:
+ var: set_url
+
+ - assert:
+ that:
+ - set_url is changed
+ - set_url.data.mode is defined
+
+ - name: Set whitelisted url with idempotency
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ mode: enabled
+ allowed_urls:
+ - url: www.ansible.com
+ comment: Ansible
+ - url: www.google.com
+ comment: Google
+ delegate_to: localhost
+ register: set_url_idempotent
+
+ - debug:
+ var: set_url_idempotent
+
+ - assert:
+ that:
+ - set_url_idempotent is not changed
+ - set_url_idempotent.data is defined
+
+ - name: Get malware settings
+ meraki_malware:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ delegate_to: localhost
+ register: get_malware
+
+ - assert:
+ that:
+ - get_malware.data is defined
+
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+ always:
+ - name: Delete test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Malware'
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/tasks/main.yml
new file mode 100644
index 000000000..721a93007
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/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
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/tasks/tests.yml
new file mode 100644
index 000000000..11193d135
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_nat/tasks/tests.yml
@@ -0,0 +1,363 @@
+# Test code for the Meraki NAT module
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- block:
+ - name: Create test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ type: appliance
+
+ - name: Create 1:1 rule with check mode
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ one_to_one:
+ - name: Service behind NAT
+ public_ip: 1.2.1.2
+ lan_ip: 192.168.128.1
+ uplink: internet1
+ allowed_inbound:
+ - protocol: tcp
+ destination_ports:
+ - 80
+ allowed_ips:
+ - 10.10.10.10
+ register: create_one_one_check
+ check_mode: yes
+
+ - debug:
+ var: create_one_one_check
+
+ - assert:
+ that:
+ - create_one_one_check is changed
+
+ - name: Create 1:1 rule
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ one_to_one:
+ - name: Service behind NAT
+ public_ip: 1.2.1.2
+ lan_ip: 192.168.128.1
+ uplink: internet1
+ allowed_inbound:
+ - protocol: tcp
+ destination_ports:
+ - 80
+ allowed_ips:
+ - 10.10.10.10
+ register: create_one_one
+
+ - debug:
+ var: create_one_one
+
+ - assert:
+ that:
+ - create_one_one is changed
+
+ - name: Create 1:1 rule with idempotency
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ one_to_one:
+ - name: Service behind NAT
+ public_ip: 1.2.1.2
+ lan_ip: 192.168.128.1
+ uplink: internet1
+ allowed_inbound:
+ - protocol: tcp
+ destination_ports:
+ - 80
+ allowed_ips:
+ - 10.10.10.10
+ register: create_one_one_idempotent
+
+ - debug:
+ var: create_one_one_idempotent
+
+ - assert:
+ that:
+ - create_one_one_idempotent is not changed
+
+ - name: Create 1:many rule with check mode
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ one_to_many:
+ - public_ip: 1.1.1.1
+ uplink: internet1
+ port_rules:
+ - name: Test rule
+ protocol: tcp
+ public_port: 10
+ local_ip: 192.168.128.1
+ local_port: 11
+ allowed_ips:
+ - any
+ register: create_one_many_check
+ check_mode: yes
+
+ - debug:
+ var: create_one_many_check
+
+ - assert:
+ that:
+ - create_one_many_check is changed
+
+ - name: Create 1:many rule
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ one_to_many:
+ - public_ip: 1.1.1.1
+ uplink: internet1
+ port_rules:
+ - name: Test rule
+ protocol: tcp
+ public_port: 10
+ local_ip: 192.168.128.1
+ local_port: 11
+ allowed_ips:
+ - any
+ register: create_one_many
+
+ - debug:
+ var: create_one_many
+
+ - assert:
+ that:
+ - create_one_many is changed
+
+ - name: Create 1:many rule with idempotency
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ one_to_many:
+ - public_ip: 1.1.1.1
+ uplink: internet1
+ port_rules:
+ - name: Test rule
+ protocol: tcp
+ public_port: 10
+ local_ip: 192.168.128.1
+ local_port: 11
+ allowed_ips:
+ - any
+ register: create_one_many_idempotent
+
+ - debug:
+ var: create_one_many_idempotent
+
+ - assert:
+ that:
+ - create_one_many_idempotent is not changed
+
+ - name: Create port forwarding rule with check mode
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ port_forwarding:
+ - name: Test map
+ lan_ip: 192.168.128.1
+ uplink: both
+ protocol: tcp
+ allowed_ips:
+ - 1.1.1.1
+ public_port: 10
+ local_port: 11
+ register: create_pf_check
+ check_mode: yes
+
+ - debug:
+ var: create_pf_check
+
+ - assert:
+ that:
+ - create_pf_check is changed
+
+ - name: Create port forwarding rule
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ port_forwarding:
+ - name: Test map
+ lan_ip: 192.168.128.1
+ uplink: both
+ protocol: tcp
+ allowed_ips:
+ - 1.1.1.1
+ public_port: 10
+ local_port: 11
+ register: create_pf
+
+ - debug:
+ var: create_pf
+
+ - assert:
+ that:
+ - create_pf is changed
+
+ - name: Create port forwarding rule with idempotency
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ port_forwarding:
+ - name: Test map
+ lan_ip: 192.168.128.1
+ uplink: both
+ protocol: tcp
+ allowed_ips:
+ - 1.1.1.1
+ public_port: 10
+ local_port: 11
+ register: create_pf_idempotent
+
+ - debug:
+ var: create_pf_idempotent
+
+ - assert:
+ that:
+ - create_pf_idempotent is not changed
+ - create_pf_idempotent.data.port_forwarding is defined
+
+ - name: Create multiple rules
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ port_forwarding:
+ - name: Test map
+ lan_ip: 192.168.128.1
+ uplink: both
+ protocol: tcp
+ allowed_ips:
+ - 1.1.1.2
+ public_port: 10
+ local_port: 11
+ one_to_many:
+ - public_ip: 1.1.1.3
+ uplink: internet1
+ port_rules:
+ - name: Test rule
+ protocol: tcp
+ public_port: 10
+ local_ip: 192.168.128.1
+ local_port: 11
+ allowed_ips:
+ - any
+ register: create_multiple
+
+ - debug:
+ var: create_multiple
+
+ - assert:
+ that:
+ - create_multiple is changed
+ - create_multiple.data.one_to_many is defined
+ - create_multiple.data.port_forwarding is defined
+
+ - assert:
+ that:
+ - create_multiple is changed
+ - create_multiple.data.one_to_many is defined
+ - create_multiple.data.port_forwarding is defined
+ - create_multiple.diff.before.one_to_many is defined
+ - create_multiple.diff.before.port_forwarding is defined
+ - create_multiple.diff.after.one_to_many is defined
+ - create_multiple.diff.after.port_forwarding is defined
+
+ - name: Query all NAT rules
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: query
+ subset: all
+ register: query_all
+
+ - debug:
+ var: query_all
+
+ - name: Query 1:1 NAT rules
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: query
+ subset: '1:1'
+ register: query_1to1
+
+ - debug:
+ var: query_1to1
+
+ - name: Query 1:many NAT rules
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: query
+ subset: '1:many'
+ register: query_1tomany
+
+ - debug:
+ var: query_1tomany
+
+ - name: Query port forwarding rules
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: query
+ subset: port_forwarding
+ register: query_pf
+
+ - debug:
+ var: query_pf
+
+ - name: Query multiple rules
+ meraki_nat:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: query
+ subset:
+ - '1:1'
+ - '1:many'
+ register: query_multiple
+
+ - debug:
+ var: query_multiple
+
+ always:
+ - name: Delete test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: absent
+ \ No newline at end of file
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/aliases
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/aliases
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/tasks/main.yml
new file mode 100644
index 000000000..4fd4b863a
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_network_vlan_settings/tasks/main.yml
@@ -0,0 +1,95 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2020, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- block:
+ - name: Create network with type appliance
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ type: appliance
+ timezone: America/Chicago
+ delegate_to: localhost
+
+ - name: Get VLAN settings
+ meraki_mx_network_vlan_settings:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ delegate_to: localhost
+ register: query_all
+
+ - debug:
+ var: query_all
+
+ - name: Enable VLANs on network with check mode
+ meraki_mx_network_vlan_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ vlans_enabled: yes
+ delegate_to: localhost
+ register: enable_vlans_check
+ check_mode: yes
+
+ - debug:
+ var: enable_vlans_check
+
+ - assert:
+ that:
+ - enable_vlans_check is changed
+ - enable_vlans_check.data.vlans_enabled == true
+
+ - name: Enable VLANs on network
+ meraki_mx_network_vlan_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ vlans_enabled: yes
+ delegate_to: localhost
+ register: enable_vlans
+
+ - debug:
+ var: enable_vlans
+
+ - assert:
+ that:
+ - enable_vlans is changed
+ - enable_vlans.data.vlans_enabled == true
+
+ - name: Enable VLANs on network idempotent
+ meraki_mx_network_vlan_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ vlans_enabled: yes
+ delegate_to: localhost
+ register: enable_vlans_idempotent
+
+ - debug:
+ var: enable_vlans_idempotent
+
+ - assert:
+ that:
+ - enable_vlans_idempotent is not changed
+ - enable_vlans_idempotent.data.vlans_enabled == true
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+
+ always:
+ - name: Delete network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/tasks/main.yml
new file mode 100644
index 000000000..df0b91644
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/tasks/main.yml
@@ -0,0 +1,306 @@
+# 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
+ delegate_to: localhost
+
+ - name: Query firewall rules
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ var: query
+
+ # - assert:
+ # that:
+ # - query.data|length == 1
+
+ - name: Set one firewall rule with check mode
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ check_mode: yes
+ register: create_one_check
+
+ - debug:
+ var: create_one_check
+
+ - assert:
+ that:
+ - create_one_check.data.rules|length == 2
+ - create_one_check.data.rules.0.dest_cidr == '192.0.1.1/32'
+ - create_one_check.data.rules.0.protocol == 'tcp'
+ - create_one_check.data.rules.0.policy == 'deny'
+ - create_one_check is changed
+ - create_one_check.data is defined
+
+ - name: Set one firewall rule
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: create_one
+
+ - debug:
+ var: create_one
+
+ - assert:
+ that:
+ - create_one.data.rules|length == 2
+ - create_one.data.rules.0.dest_cidr == '192.0.1.1/32'
+ - create_one.data.rules.0.protocol == 'tcp'
+ - create_one.data.rules.0.policy == 'deny'
+ - create_one.changed == True
+ - create_one.data is defined
+
+ - name: Check for idempotency
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: create_one_idempotent
+
+ - debug:
+ msg: '{{create_one_idempotent}}'
+
+ - assert:
+ that:
+ - create_one_idempotent.changed == False
+ - create_one_idempotent.data is defined
+
+ - name: Update one existing rule
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules:
+ - comment: Deny all documentation addresses
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32,192.0.1.2/32
+ protocol: tcp
+ policy: deny
+ delegate_to: localhost
+ register: update_one
+
+ - debug:
+ msg: '{{update_one}}'
+
+ - assert:
+ that:
+ - update_one.changed == True
+ - update_one.data is defined
+
+ - name: Create syslog in network
+ meraki_syslog:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: present
+ servers:
+ - host: 192.0.2.10
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ delegate_to: localhost
+
+ - name: Enable syslog for default rule with check mode
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+ check_mode: yes
+ register: default_syslog_check
+
+ - debug:
+ msg: '{{default_syslog_check}}'
+
+ - assert:
+ that:
+ - default_syslog_check.data is defined
+ - default_syslog_check.data.rules.1.syslog_enabled == True
+
+ - name: Enable syslog for default rule
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ syslog_default_rule: yes
+ delegate_to: localhost
+ register: default_syslog
+
+ - debug:
+ msg: '{{default_syslog}}'
+
+ - assert:
+ that:
+ - default_syslog.data is defined
+
+ - name: Query firewall rules
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ msg: '{{query.data.rules.1}}'
+
+ - assert:
+ that:
+ - query.data.rules.1.syslog_enabled == True
+ - default_syslog.changed == True
+
+ - name: Disable syslog for default rule
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules:
+ - comment: Deny to documentation address
+ src_port: Any
+ src_cidr: Any
+ dest_port: 80,443
+ dest_cidr: 192.0.1.1/32
+ protocol: tcp
+ policy: deny
+ syslog_default_rule: no
+ delegate_to: localhost
+ register: disable_syslog
+
+ - debug:
+ msg: '{{disable_syslog}}'
+
+ - assert:
+ that:
+ - disable_syslog.data is defined
+
+ - name: Query firewall rules
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ msg: '{{query}}'
+
+ - name: Enable syslog for default rule with no rules and with check mode
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ syslog_default_rule: yes
+ delegate_to: localhost
+ check_mode: yes
+ register: enable_syslog_only_check
+
+ - debug:
+ msg: '{{enable_syslog_only_check}}'
+
+ - assert:
+ that:
+ - enable_syslog_only_check.data.rules.1.syslog_enabled == True
+ - enable_syslog_only_check is changed
+
+ - name: Query firewall rules
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: query
+ delegate_to: localhost
+ register: query
+
+ - debug:
+ msg: '{{query.data.rules.1}}'
+
+ - assert:
+ that:
+ - query.data.rules.1.syslog_enabled == False
+ - disable_syslog.changed == True
+
+ always:
+ - name: Delete all firewall rules
+ meraki_mx_site_to_site_firewall:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ state: present
+ rules: []
+ delegate_to: localhost
+ register: delete_all
+
+ - name: Delete network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{test_org_name}}'
+ net_name: TestNetAppliance
+ state: absent
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/tasks/main.yml
new file mode 100644
index 000000000..514100c0c
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_vpn/tasks/main.yml
@@ -0,0 +1,188 @@
+# Test code for the Meraki Admin 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: Create hub network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ type: appliance
+ delegate_to: localhost
+ register: net_hub
+
+ - name: Create spoke network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_spoke'
+ type: appliance
+ delegate_to: localhost
+ register: net_spoke
+
+ - set_fact:
+ net_spoke_id: '{{net_spoke.data.id}}'
+ net_hub_id: '{{net_hub.data.id}}'
+
+
+ - name: Set hub mode in check mode
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ mode: hub
+ subnets:
+ - local_subnet: '192.168.129.0/24'
+ use_vpn: no
+ delegate_to: localhost
+ register: set_hub_check
+ check_mode: true
+
+ - assert:
+ that:
+ - set_hub_check is changed
+ - set_hub_check.data is defined
+
+ - name: Set hub mode in check mode
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ delegate_to: localhost
+ register: query_result
+
+ - debug:
+ var: query_result
+
+ - name: Assert check mode made no changes
+ ansible.builtin.assert:
+ that:
+ - query_result.data.subnets[0].local_subnet == "192.168.128.0/24"
+
+ - name: Create static_route
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ name: Test Route
+ subnet: 192.0.1.0/24
+ gateway_ip: 192.168.128.1
+ delegate_to: localhost
+
+ - name: Set hub mode
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ mode: hub
+ subnets:
+ - local_subnet: '192.0.1.0/24'
+ use_vpn: no
+ delegate_to: localhost
+ register: set_hub
+
+ - assert:
+ that:
+ - set_hub is changed
+ - set_hub.data is defined
+
+ - name: Set hub mode with idempotency
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ mode: hub
+ delegate_to: localhost
+ register: set_hub_idempotent
+
+ - assert:
+ that:
+ - set_hub_idempotent is not changed
+ - set_hub_idempotent.data is defined
+
+ - name: Enable subnet on hub network
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ mode: hub
+ subnets:
+ - local_subnet: '192.168.128.0/24'
+ use_vpn: yes
+ delegate_to: localhost
+ register: enable_hub_subnet
+
+ - assert:
+ that:
+ - enable_hub_subnet is changed
+ - enable_hub_subnet.data is defined
+
+ - name: Set spoke mode
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_spoke'
+ mode: spoke
+ hubs:
+ - hub_id: '{{net_hub_id}}'
+ use_default_route: false
+ delegate_to: localhost
+ register: set_spoke
+
+ - debug:
+ var: set_spoke
+
+ - name: Set spoke configuration
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_spoke'
+ mode: spoke
+ hubs:
+ - hub_id: '{{net_hub_id}}'
+ use_default_route: false
+ subnets:
+ - local_subnet: '192.168.169.0/24'
+ use_vpn: true
+ delegate_to: localhost
+ register: set_spoke
+
+ - debug:
+ var: set_spoke
+
+ - name: Query rules for hub
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_hub'
+ delegate_to: localhost
+ register: query_all_hub
+
+ - debug:
+ var: query_all_hub
+
+ - name: Query rules for spoke
+ meraki_site_to_site_vpn:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}_spoke'
+ delegate_to: localhost
+ register: query_all_spoke
+
+ - debug:
+ var: query_all_spoke
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/tasks/main.yml
new file mode 100644
index 000000000..ed902a3d1
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_static_route/meraki_static_route/tasks/main.yml
@@ -0,0 +1,210 @@
+# Test code for the Meraki modules
+# 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: Create appliance network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ timezone: America/Chicago
+ type: appliance
+ delegate_to: localhost
+ register: net
+
+ - name: Enable VLANs on network
+ meraki_mx_network_vlan_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ vlans_enabled: yes
+ delegate_to: localhost
+
+ - set_fact:
+ net_id: '{{net.data.id}}'
+
+ - name: Initialize static route id list
+ set_fact:
+ route_ids: []
+
+ - name: Create VLAN
+ meraki_mx_vlan:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ name: "Test VLAN"
+ vlan_id: 2
+ subnet: "192.168.129.0/24"
+ appliance_ip: "192.168.129.2"
+ delegate_to: localhost
+
+ - name: Create static_route
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ name: Test Route
+ subnet: 192.0.1.0/24
+ gateway_ip: 192.168.128.1
+ delegate_to: localhost
+ register: create_route
+
+ - set_fact:
+ route_ids: "{{ route_ids + [create_route.data.id] }}"
+
+ - name: Create static_route with idempotency
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ name: Test Route
+ subnet: 192.0.1.0/24
+ gateway_ip: 192.168.128.1
+ delegate_to: localhost
+ register: create_route_idempotent
+
+ - assert:
+ that:
+ - create_route_idempotent is not changed
+
+ - name: Create second static_route
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ name: Test Route 2
+ subnet: 192.0.2.0/24
+ gateway_ip: 192.168.128.1
+ delegate_to: localhost
+ register: second_create
+
+ - set_fact:
+ route_ids: "{{ route_ids + [second_create.data.id] }}"
+
+ - assert:
+ that:
+ - create_route.changed == True
+ - create_route.data.id is defined
+
+ - name: Update static route
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ route_id: '{{create_route.data.id}}'
+ subnet: 192.0.3.0/24
+ enabled: yes
+ gateway_vlan_id: 1
+ delegate_to: localhost
+ register: update
+
+ - assert:
+ that:
+ - update is changed
+ - update.data.subnet == "192.0.3.0/24"
+ - update.data.gateway_vlan_id == 1
+
+ - name: Query static routes
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ delegate_to: localhost
+ register: query_all
+
+ - debug:
+ var: query_all
+
+ - assert:
+ that:
+ - query_all.data | length >= 2
+
+ - name: Update static route with idempotency
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ route_id: '{{create_route.data.id}}'
+ name: Test Route
+ gateway_ip: 192.168.128.1
+ subnet: 192.0.3.0/24
+ enabled: yes
+ delegate_to: localhost
+ register: update_idempotent
+
+ - assert:
+ that:
+ - update_idempotent.changed == False
+ - update_idempotent.data is defined
+
+ - name: Update static route with fixed IP assignment and reservation
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ route_id: '{{create_route.data.id}}'
+ fixed_ip_assignments:
+ - mac: aa:bb:cc:dd:ee:ff
+ ip: 192.0.3.11
+ name: WebServer
+ reserved_ip_ranges:
+ - start: 192.168.3.2
+ end: 192.168.3.10
+ comment: Printers
+ delegate_to: localhost
+ register: fixed_ip
+
+ - debug:
+ var: fixed_ip
+
+ - assert:
+ that:
+ - fixed_ip.data.fixed_ip_assignments | length == 1
+ - fixed_ip.data.reserved_ip_ranges | length == 1
+
+
+ - name: Query single static route
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ route_id: '{{create_route.data.id}}'
+ delegate_to: localhost
+ register: query_one
+
+ - assert:
+ that:
+ - query_one.data.name == "Test Route"
+
+ - name: Delete static routes
+ meraki_static_route:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ route_id: '{{item}}'
+ delegate_to: localhost
+ loop: '{{route_ids}}'
+ register: delete_all
+
+ always:
+ - name: Delete appliance network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/tasks/main.yml
new file mode 100644
index 000000000..9a08b473d
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_third_party_vpn_peers/tasks/main.yml
@@ -0,0 +1,319 @@
+# Test code for the Meraki Third Party VPN Peer module
+# Copyright: (c) 2022, 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: Query all VPN peers with no org_id or org_name
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: query
+ register: query_all
+ ignore_errors: yes
+
+ - assert:
+ that: query_all.msg == "Organization must be specified via org_name or org_id"
+
+ - name: Query all VPN peers
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ register: query_all
+
+ - assert:
+ that: query_all.data.peers is defined
+
+ - name: Add a VPN peer without name
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ register: create_one_no_name
+ ignore_errors: yes
+
+ - assert:
+ that: create_one_no_name.msg == "Peer name must be specified"
+
+ - name: Add a VPN peer with check mode
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ ipsec_policies_preset: default
+ register: create_one_check
+ check_mode: yes
+
+ - debug:
+ var: create_one_check
+
+ - assert:
+ that:
+ - create_one_check is changed
+ - create_one_check.data is defined
+ - create_one_check.data.peers.0.name == "Test peer"
+ - create_one_check.data.peers.0.public_ip == "198.51.100.1"
+ - create_one_check.data.peers.0.private_subnets.0 == "192.0.2.0/24"
+ - create_one_check.data.peers.0.ike_version == "2"
+ - create_one_check.data.peers.0.ipsec_policies_preset == "default"
+
+ - name: Add a VPN peer
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ ipsec_policies_preset: default
+ register: create_one
+
+ - assert:
+ that:
+ - create_one is changed
+ - create_one.data is defined
+ - create_one.data.peers.0.name == "Test peer"
+ - create_one.data.peers.0.public_ip == "198.51.100.1"
+ - create_one.data.peers.0.private_subnets.0 == "192.0.2.0/24"
+ - create_one.data.peers.0.ike_version == "2"
+ - create_one.data.peers.0.ipsec_policies_preset == "default"
+
+ - name: Add a VPN peer with idempotency
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ ipsec_policies_preset: default
+ register: create_one_idempotent
+
+ - assert:
+ that:
+ - create_one_idempotent is not changed
+ - create_one_idempotent.data is defined
+ - create_one_idempotent.data.peers.0.name == "Test peer"
+ - create_one_idempotent.data.peers.0.public_ip == "198.51.100.1"
+ - create_one_idempotent.data.peers.0.private_subnets.0 == "192.0.2.0/24"
+ - create_one_idempotent.data.peers.0.ike_version == "2"
+ - create_one_idempotent.data.peers.0.ipsec_policies_preset == "default"
+
+ - name: Change peer to use network tags
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ network_tags:
+ - none
+ ipsec_policies_preset: default
+ register: add_network_tags
+
+ - assert:
+ that:
+ - add_network_tags is changed
+ - add_network_tags.data.peers.0.network_tags.0 == "none"
+
+ - name: Change peer to use network tags with idempotency
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ network_tags:
+ - none
+ ipsec_policies_preset: default
+ register: add_network_tags_idempotent
+
+ - assert:
+ that:
+ - add_network_tags_idempotent is not changed
+ - add_network_tags_idempotent.data.peers.0.network_tags.0 == "none"
+
+
+ - name: Set IPsec policies
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ network_tags:
+ - none
+ remote_id: "192.0.2.0"
+ ipsec_policies:
+ child_lifetime: 600
+ ike_lifetime: 600
+ child_auth_algo:
+ - "md5"
+ child_cipher_algo:
+ - "tripledes"
+ - "aes192"
+ child_pfs_group:
+ - "disabled"
+ ike_auth_algo:
+ - "sha256"
+ ike_cipher_algo:
+ - "tripledes"
+ ike_diffie_hellman_group:
+ - "group2"
+ ike_prf_algo:
+ - "prfmd5"
+ register: set_ipsec_policies
+
+ - assert:
+ that:
+ - set_ipsec_policies is changed
+ - set_ipsec_policies.data.peers.0.ipsec_policies.child_lifetime == 600
+ - set_ipsec_policies.data.peers.0.ipsec_policies.ike_lifetime == 600
+ - set_ipsec_policies.data.peers.0.ipsec_policies.child_auth_algo.0 == "md5"
+ - set_ipsec_policies.data.peers.0.ipsec_policies.child_cipher_algo.0 == "tripledes"
+ - set_ipsec_policies.data.peers.0.ipsec_policies.child_pfs_group.0 == "disabled"
+ - set_ipsec_policies.data.peers.0.ipsec_policies.ike_auth_algo.0 == "sha256"
+ - set_ipsec_policies.data.peers.0.ipsec_policies.ike_cipher_algo.0 == "tripledes"
+ - set_ipsec_policies.data.peers.0.ipsec_policies.ike_diffie_hellman_group.0 == "group2"
+ - set_ipsec_policies.data.peers.0.ipsec_policies.ike_prf_algo.0 == "prfmd5"
+
+ - name: Set IPsec policies with idempotency
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ network_tags:
+ - none
+ remote_id: "192.0.2.0"
+ ipsec_policies:
+ child_lifetime: 600
+ ike_lifetime: 600
+ child_auth_algo:
+ - "md5"
+ child_cipher_algo:
+ - "tripledes"
+ - "aes192"
+ child_pfs_group:
+ - "disabled"
+ ike_auth_algo:
+ - "sha256"
+ ike_cipher_algo:
+ - "tripledes"
+ ike_diffie_hellman_group:
+ - "group2"
+ ike_prf_algo:
+ - "prfmd5"
+ register: set_ipsec_policies_idempotent
+
+ - assert:
+ that:
+ - set_ipsec_policies_idempotent is not changed
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.child_lifetime == 600
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.ike_lifetime == 600
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.child_auth_algo.0 == "md5"
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.child_cipher_algo.0 == "tripledes"
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.child_pfs_group.0 == "disabled"
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.ike_auth_algo.0 == "sha256"
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.ike_cipher_algo.0 == "tripledes"
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.ike_diffie_hellman_group.0 == "group2"
+ - set_ipsec_policies_idempotent.data.peers.0.ipsec_policies.ike_prf_algo.0 == "prfmd5"
+
+
+ - name: Add a second VPN peer
+ meraki_mx_third_party_vpn_peers:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ peers:
+ - name: "Test peer"
+ public_ip: "198.51.100.1"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.2.0/24"
+ ike_version: "2"
+ ipsec_policies_preset: default
+ - name: "Test peer 2"
+ public_ip: "198.51.100.2"
+ secret: "s3cret"
+ private_subnets:
+ - "192.0.3.0/24"
+ ike_version: "2"
+ ipsec_policies_preset: default
+ register: create_second
+
+ - assert:
+ that:
+ - create_second is changed
+ - create_second.data.peers | length == 2
+ - create_second.data is defined
+ - create_second.data.peers.0.name == "Test peer"
+ - create_second.data.peers.0.public_ip == "198.51.100.1"
+ - create_second.data.peers.0.private_subnets.0 == "192.0.2.0/24"
+ - create_second.data.peers.0.ike_version == "2"
+ - create_second.data.peers.0.ipsec_policies_preset == "default"
+ - create_second.data is defined
+ - create_second.data.peers.1.name == "Test peer 2"
+ - create_second.data.peers.1.public_ip == "198.51.100.2"
+ - create_second.data.peers.1.private_subnets.0 == "192.0.3.0/24"
+ - create_second.data.peers.1.ike_version == "2"
+ - create_second.data.peers.1.ipsec_policies_preset == "default"
+
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+ # always:
+ # - name: Delete test network
+ # meraki_network:
+ # auth_key: '{{auth_key}}'
+ # state: absent
+ # org_name: '{{test_org_name}}'
+ # net_name: '{{test_net_name}}'
+ # delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/tasks/main.yml
new file mode 100644
index 000000000..1e055256b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink_bandwidth/tasks/main.yml
@@ -0,0 +1,125 @@
+# Test code for the Meraki MX Uplink module
+# Copyright: (c) 2019, 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 test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ type: appliance
+ delegate_to: localhost
+
+ - name: Set MX uplink settings with check mode
+ meraki_mx_uplink:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ wan1:
+ bandwidth_limits:
+ limit_down: 1000000
+ limit_up: 100
+ cellular:
+ bandwidth_limits:
+ limit_down: 0
+ limit_up: 0
+ delegate_to: localhost
+ register: set_bw_check
+ check_mode: yes
+
+ - debug:
+ var: set_bw_check
+
+ - assert:
+ that:
+ - set_bw_check is changed
+ - set_bw_check.data is defined
+ - set_bw_check.diff is defined
+
+ - name: Set MX uplink settings
+ meraki_mx_uplink_bandwidth:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ wan1:
+ bandwidth_limits:
+ limit_down: 1000000
+ limit_up: 1000
+ cellular:
+ bandwidth_limits:
+ limit_down: 0
+ limit_up: 0
+ delegate_to: localhost
+ register: set_bw
+
+ - debug:
+ var: set_bw
+
+ - assert:
+ that:
+ - set_bw is changed
+ - set_bw.data is defined
+
+ - name: Set MX uplink settings with idempotency
+ meraki_mx_uplink_bandwidth:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ wan1:
+ bandwidth_limits:
+ limit_down: 1000000
+ limit_up: 1000
+ cellular:
+ bandwidth_limits:
+ limit_down: 0
+ limit_up: 0
+ delegate_to: localhost
+ register: set_bw_idempotent
+
+ - debug:
+ var: set_bw_idempotent
+
+ - assert:
+ that:
+ - set_bw_idempotent is not changed
+ - set_bw_idempotent.data is defined
+
+ - name: Query MX uplink settings
+ meraki_mx_uplink:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ delegate_to: localhost
+ register: query_all
+
+ - debug:
+ var: query_all
+
+ - assert:
+ that:
+ - query_all.data is defined
+
+ always:
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+
+ - name: Delete test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}} - Uplink'
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/tasks/main.yml
new file mode 100644
index 000000000..13c36564e
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_vlan/tasks/main.yml
@@ -0,0 +1,475 @@
+# Test code for the Meraki VLAN 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 test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_name: '{{test_net_name}}'
+ type: appliance
+ delegate_to: localhost
+ register: new_net
+
+ - set_fact:
+ test_net_id: '{{ new_net.data.id }}'
+
+ - name: Enable VLANs on network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ enable_vlans: yes
+ delegate_to: localhost
+
+ - name: Create VLAN in check mode
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.1
+ delegate_to: localhost
+ register: create_vlan_check
+ check_mode: yes
+
+ - debug:
+ var: create_vlan_check
+
+ - assert:
+ that:
+ - create_vlan_check is changed
+
+ - name: Create VLAN
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.1
+ delegate_to: localhost
+ register: create_vlan
+ environment:
+ ANSIBLE_MERAKI_FORMAT: camelcase
+
+ - debug:
+ msg: '{{create_vlan}}'
+
+ - assert:
+ that:
+ - create_vlan.data.id == 2
+ - create_vlan.changed == True
+ - create_vlan.data.networkId is defined
+
+ - name: Update VLAN with check mode
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: opendns
+ delegate_to: localhost
+ register: update_vlan_check
+ check_mode: yes
+
+ - debug:
+ var: update_vlan_check
+
+ - assert:
+ that:
+ - update_vlan_check is changed
+
+ - name: Update VLAN
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: opendns
+ delegate_to: localhost
+ register: update_vlan
+
+ - debug:
+ msg: '{{update_vlan}}'
+
+ - assert:
+ that:
+ - update_vlan.data.appliance_ip == '192.168.250.2'
+ - update_vlan.changed == True
+
+ - name: Update VLAN with idempotency and check mode
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: opendns
+ delegate_to: localhost
+ register: update_vlan_idempotent_check
+ check_mode: yes
+
+ - debug:
+ var: update_vlan_idempotent_check
+
+ - assert:
+ that:
+ - update_vlan_idempotent_check is not changed
+
+ - name: Update VLAN with idempotency
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: opendns
+ delegate_to: localhost
+ register: update_vlan_idempotent
+
+ - debug:
+ msg: '{{update_vlan_idempotent}}'
+
+ - assert:
+ that:
+ - update_vlan_idempotent.changed == False
+ - update_vlan_idempotent.data is defined
+
+ - name: Add IP assignments and reserved IP ranges
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ - mac: "12:34:56:78:90:12"
+ ip: 192.168.250.11
+ name: another_fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ - start: 192.168.250.100
+ end: 192.168.250.120
+ comment: reserved_range_high
+ dns_nameservers: opendns
+ delegate_to: localhost
+ register: update_vlan_add_ip
+
+ - debug:
+ msg: '{{update_vlan_add_ip}}'
+
+ - assert:
+ that:
+ - update_vlan_add_ip.changed == True
+ - update_vlan_add_ip.data.fixed_ip_assignments | length == 2
+ - update_vlan_add_ip.data.reserved_ip_ranges | length == 2
+
+ - name: Remove IP assignments and reserved IP ranges
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: opendns
+ delegate_to: localhost
+ register: update_vlan_remove_ip
+
+ - debug:
+ msg: '{{update_vlan_remove_ip}}'
+
+ - assert:
+ that:
+ - update_vlan_remove_ip.changed == True
+ - update_vlan_remove_ip.data.fixed_ip_assignments | length == 1
+ - update_vlan_remove_ip.data.reserved_ip_ranges | length == 1
+
+ - name: Update VLAN with idempotency
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: opendns
+ delegate_to: localhost
+ register: update_vlan_idempotent
+
+ - debug:
+ msg: '{{update_vlan_idempotent}}'
+
+ - assert:
+ that:
+ - update_vlan_idempotent.changed == False
+ - update_vlan_idempotent.data is defined
+
+ - name: Update VLAN with list of DNS entries
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ fixed_ip_assignments:
+ - mac: "13:37:de:ad:be:ef"
+ ip: 192.168.250.10
+ name: fixed_ip
+ reserved_ip_range:
+ - start: 192.168.250.10
+ end: 192.168.250.20
+ comment: reserved_range
+ dns_nameservers: 1.1.1.1;8.8.8.8
+ delegate_to: localhost
+ register: update_vlan_dns_list
+
+ - debug:
+ msg: '{{update_vlan_dns_list}}'
+
+ - assert:
+ that:
+ - '"1.1.1.1" in update_vlan_dns_list.data.dns_nameservers'
+ - update_vlan_dns_list.changed == True
+
+ - name: Enable DHCP Relay on VLAN
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ dhcp_handling: relay
+ dhcp_relay_server_ips:
+ - 192.168.100.1
+ delegate_to: localhost
+ register: enable_relay
+
+ - debug:
+ var: enable_relay
+
+ - assert:
+ that:
+ - enable_relay is changed
+ - enable_relay.data.dhcp_handling == 'Relay DHCP to another server'
+ - enable_relay.data.dhcp_relay_server_ips[0] == '192.168.100.1'
+
+ - name: Enable DHCP on VLAN
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ dhcp_handling: server
+ delegate_to: localhost
+ register: enable_dhcp
+
+ - debug:
+ var: enable_dhcp
+
+ - assert:
+ that:
+ - enable_dhcp is changed
+ - enable_dhcp.data.dhcp_handling == 'Run a DHCP server'
+
+ - name: Enable DHCP on VLAN with options
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ name: TestVLAN
+ subnet: 192.168.250.0/24
+ appliance_ip: 192.168.250.2
+ dhcp_handling: server
+ dhcp_lease_time: 1 hour
+ dhcp_boot_options_enabled: false
+ dhcp_options:
+ - code: 5
+ type: ip
+ value: 192.0.1.1
+ delegate_to: localhost
+ register: enable_dhcp_options
+
+ - assert:
+ that:
+ - enable_dhcp_options is changed
+ - enable_dhcp_options.data.dhcp_handling == 'Run a DHCP server'
+ - enable_dhcp_options.data.dhcp_options.0.code == '5'
+
+ - name: Query all VLANs in network
+ meraki_vlan:
+ auth_key: '{{ auth_key }}'
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ state: query
+ delegate_to: localhost
+ register: query_vlans
+
+ - debug:
+ msg: '{{query_vlans}}'
+
+ - assert:
+ that:
+ - query_vlans.data | length >= 2
+ - query_vlans.data.1.id == 2
+ - query_vlans.changed == False
+
+ - name: Query single VLAN
+ meraki_vlan:
+ auth_key: '{{ auth_key }}'
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ state: query
+ output_level: debug
+ delegate_to: localhost
+ register: query_vlan
+
+ - debug:
+ msg: '{{query_vlan}}'
+
+ - assert:
+ that:
+ - query_vlan.data.id == 2
+ - query_vlan.changed == False
+
+ always:
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+ - name: Delete VLAN with check mode
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ delegate_to: localhost
+ register: delete_vlan_check
+ check_mode: yes
+
+ - assert:
+ that:
+ delete_vlan_check is changed
+
+ - name: Delete VLAN
+ meraki_vlan:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ vlan_id: 2
+ delegate_to: localhost
+ register: delete_vlan
+
+ - debug:
+ msg: '{{delete_vlan}}'
+
+ - name: Delete test network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: absent
+ org_id: '{{test_org_id}}'
+ net_id: '{{test_net_id}}'
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/tasks/main.yml
new file mode 100644
index 000000000..cc078952f
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/tasks/main.yml
@@ -0,0 +1,550 @@
+# Test code for the Meraki modules
+# 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: Create network without type
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetwork
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_no_type
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - create_net_no_type.msg == 'type parameter is required when creating a network.'
+
+ - name: Create network without organization
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ net_name: IntTestNetwork
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_no_org
+ ignore_errors: yes
+
+ - name: Create network with type switch with check mode
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkSwitch
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ check_mode: yes
+ register: create_net_switch_check
+
+ - assert:
+ that:
+ - create_net_switch_check is changed
+ - create_net_switch_check.data is defined
+ - create_net_switch_check.data.organization_id == "{{test_org_id |string}}"
+
+ - name: Create network with type switch
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkSwitch
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_switch
+
+ - set_fact:
+ switch_net_id: '{{ create_net_switch.data.id }}'
+
+ - name: Copy network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkSwitchCopied
+ type: switch
+ copy_from_network_id: '{{ switch_net_id }}'
+ delegate_to: localhost
+ register: copy_from_net_id
+
+ - assert:
+ that:
+ - copy_from_net_id is defined
+ - copy_from_net_id is changed
+ - copy_from_net_id.data.id != create_net_switch.data.id
+
+ - name: Create network with type switch by org ID
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_id: '{{test_org_id}}'
+ net_name: IntTestNetworkSwitchOrgID
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_switch_org_id
+
+ - name: Create network with type appliance and no timezone
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkAppliance
+ type: appliance
+ delegate_to: localhost
+ register: create_net_appliance_no_tz
+
+ - name: Enable VLAN support on appliance network with check mode
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ enable_vlans: yes
+ delegate_to: localhost
+ check_mode: yes
+ register: enable_vlan_check
+
+ - assert:
+ that:
+ - enable_vlan_check.data.vlans_enabled == True
+ - enable_vlan_check is changed
+
+ - name: Enable VLAN support on appliance network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ enable_vlans: yes
+ delegate_to: localhost
+ register: enable_vlan
+
+ - assert:
+ that:
+ - enable_vlan.data.vlans_enabled == True
+
+ - name: Enable VLAN support on appliance network with idempotency
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ enable_vlans: yes
+ delegate_to: localhost
+ register: enable_vlan_idempotent
+
+ - assert:
+ that:
+ - enable_vlan_idempotent is not changed
+ - enable_vlan_idempotent.data is defined
+
+ - name: Disable VLAN support on appliance network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ enable_vlans: no
+ delegate_to: localhost
+ register: disable_vlan
+
+ - assert:
+ that:
+ - disable_vlan.data.vlans_enabled == False
+
+ - name: Disable VLAN support on appliance network with idempotency
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ enable_vlans: no
+ delegate_to: localhost
+ register: disable_vlan_idempotent
+
+ - assert:
+ that:
+ - disable_vlan_idempotent is not changed
+ - disable_vlan_idempotent.data is defined
+
+ - name: Change local page settings with check mode
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ local_status_page_enabled: False
+ delegate_to: localhost
+ check_mode: True
+ register: disable_local_page_check
+
+ - assert:
+ that:
+ - disable_local_page_check is changed
+
+ - name: Change local page settings
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ local_status_page_enabled: False
+ delegate_to: localhost
+ register: disable_local_page
+
+ - assert:
+ that:
+ - disable_local_page is changed
+ - disable_local_page.data is defined
+
+ - name: Change local page settings with idempotency
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkAppliance
+ local_status_page_enabled: False
+ delegate_to: localhost
+ register: disable_local_page_idempotent
+
+ - assert:
+ that:
+ - disable_local_page_idempotent is not changed
+ - disable_local_page_idempotent.data is defined
+
+ - name: Create network with type wireless
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkWireless
+ type: wireless
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_wireless
+
+ - assert:
+ that:
+ - '"IntTestNetworkWireless" in create_net_wireless.data.name'
+
+ - name: Create network with type wireless and check for idempotency
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkWireless
+ type: wireless
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_wireless_idempotent
+
+ - assert:
+ that:
+ - create_net_wireless_idempotent.changed == False
+ - create_net_wireless_idempotent.data is defined
+
+ - name: Create network with type combined
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: IntTestNetworkCombined
+ type:
+ - appliance
+ - switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_combined
+
+ - assert:
+ that:
+ - create_net_combined.data.product_types | length > 1
+
+ - name: Test status pages - local disabled remote disabled
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkCombined
+ local_status_page_enabled: no
+ remote_status_page_enabled: no
+ delegate_to: localhost
+ register: status_pages_1
+
+ - assert:
+ that:
+ - status_pages_1.changed == True
+ - status_pages_1['data']['local_status_page_enabled'] == False
+ - status_pages_1['data']['remote_status_page_enabled'] == False
+
+ - name: Test status pages - local enabled remote disabled
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkCombined
+ local_status_page_enabled: yes
+ remote_status_page_enabled: no
+ delegate_to: localhost
+ register: status_pages_2
+
+ - assert:
+ that:
+ - status_pages_2.changed == True
+ - status_pages_2['data']['local_status_page_enabled'] == True
+ - status_pages_2['data']['remote_status_page_enabled'] == False
+
+ - name: Test status pages - local enabled remote enabled
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkCombined
+ local_status_page_enabled: yes
+ remote_status_page_enabled: yes
+ delegate_to: localhost
+ register: status_pages_3
+
+ - assert:
+ that:
+ - status_pages_3.changed == True
+ - status_pages_3['data']['local_status_page_enabled'] == True
+ - status_pages_3['data']['remote_status_page_enabled'] == True
+
+
+ - name: Test status pages - local disabled remote enabled - expected to fail
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkCombined
+ local_status_page_enabled: no
+ remote_status_page_enabled: yes
+ delegate_to: localhost
+ register: status_pages_4
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - status_pages_4.changed == False
+ - '"must be true when setting" in status_pages_4.msg'
+
+ - name: Create network with one tag
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkTag
+ type: switch
+ timezone: America/Chicago
+ tags: first_tag
+ delegate_to: localhost
+ register: create_net_tag
+
+ - name: Create network with two tags
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkTags
+ type: switch
+ timezone: America/Chicago
+ tags:
+ - first_tag
+ - second_tag
+ delegate_to: localhost
+ register: create_net_tags
+
+ - set_fact:
+ tag_net_id: '{{create_net_tags.data.id}}'
+
+ - name: Modify network by net_id
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_id: '{{tag_net_id}}'
+ type: switch
+ timezone: America/Chicago
+ tags:
+ - first_tag
+ - second_tag
+ - third_tag
+ delegate_to: localhost
+ register: create_net_modified
+
+ - name: Modify network with idempotency
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkTags
+ type: switch
+ timezone: America/Chicago
+ tags:
+ - first_tag
+ - second_tag
+ - third_tag
+ delegate_to: localhost
+ register: create_net_modified_idempotent
+
+ - assert:
+ that:
+ - create_net_modified_idempotent.data is defined
+
+ - name: Present assertions
+ assert:
+ that:
+ - '"org_name or org_id parameters are required" in create_net_no_org.msg'
+ - '"IntTestNetworkAppliance" in create_net_appliance_no_tz.data.name'
+ - create_net_appliance_no_tz.changed == True
+ - '"IntTestNetworkSwitch" in create_net_switch.data.name'
+ - '"IntTestNetworkSwitchOrgID" in create_net_switch_org_id.data.name'
+ - '"first_tag" in create_net_tag.data.tags'
+ - '"second_tag" in create_net_tags.data.tags'
+ - '"third_tag" in create_net_modified.data.tags'
+ - create_net_modified.changed == True
+ - create_net_modified_idempotent.changed == False
+ - create_net_modified_idempotent.data is defined
+
+ - name: Query templated network settings
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_template_name}}'
+ local_status_page_enabled: yes
+ delegate_to: localhost
+ register: query_settings
+
+ - assert:
+ that:
+ - query_settings.data is defined
+
+ - name: Query all networks
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ delegate_to: localhost
+ register: net_query_all
+
+ - name: Query a configuration template
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_template_name}}'
+ delegate_to: localhost
+ register: query_config_template
+
+ - name: Query one network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: IntTestNetworkSwitch
+ delegate_to: localhost
+ register: net_query_one
+
+ - name: Query one network - assert
+ assert:
+ that:
+ - 'net_query_one.data.name == "IntTestNetworkSwitch"'
+ - 'query_config_template.data.name == "{{ test_template_name }}"'
+
+ - name: Set net_id
+ ansible.builtin.set_fact:
+ net_id: "{{ net_query_one.data.id }}"
+
+ - name: Query one network by ID
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_id: "{{ net_id}}"
+ delegate_to: localhost
+ register: net_query_one_id
+
+ - name: Query one network by ID - debug
+ ansible.builtin.debug:
+ var: net_query_one_id
+
+ - name: Query one network by ID - assert
+ ansible.builtin.assert:
+ that:
+ - 'net_query_one_id.data.name == "IntTestNetworkSwitch"'
+ - 'query_config_template.data.name == "{{ test_template_name }}"'
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+ always:
+ - name: Delete network without org
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ net_name: IntTestNetworkSwitch
+ delegate_to: localhost
+ register: delete_all_no_org
+ ignore_errors: yes
+
+ - name: Delete network by org ID and check mode
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_id: '{{test_org_id}}'
+ net_name: IntTestNetworkSwitchOrgID
+ delegate_to: localhost
+ check_mode: yes
+ register: delete_net_org_id_check
+
+ - assert:
+ that:
+ - delete_net_org_id_check is changed
+ - delete_net_org_id_check.data is defined
+
+ - name: Delete network by org ID
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_id: '{{test_org_id}}'
+ net_name: IntTestNetworkSwitchOrgID
+ delegate_to: localhost
+ register: delete_net_org_id
+
+ - name: Query after delete with org ID
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ delegate_to: localhost
+ register: query_deleted_org_id
+
+ - name: Delete all networks
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: '{{ item }}'
+ delegate_to: localhost
+ register: delete_all
+ ignore_errors: yes
+ loop:
+ - IntTestNetworkSwitch
+ - IntTestNetworkSwitchCopied
+ - IntTestNetworkWireless
+ - IntTestNetworkAppliance
+ - IntTestNetworkCombined
+ - IntTestNetworkTag
+ - IntTestNetworkTags
+
+ - assert:
+ that:
+ - 'delete_all_no_org.msg == "org_name or org_id parameters are required"'
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/tasks/main.yml
new file mode 100644
index 000000000..9d3783cc6
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network_settings/tasks/main.yml
@@ -0,0 +1,294 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2023, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- block:
+ - name: Create network with type switch
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ type: switch
+ timezone: America/Chicago
+ delegate_to: localhost
+ register: create_net_switch
+
+ - name: Get network settings
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ delegate_to: localhost
+ register: query_net
+
+ - name: Update network settings with check mode
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: false
+ delegate_to: localhost
+ register: set_local_check
+ check_mode: true
+
+ - name: Assert settings local status page enabled
+ ansible.builtin.assert:
+ that:
+ - set_local_check is changed
+ - set_local_check.data is defined
+ - set_local_check.data.local_status_page_enabled == false
+ - set_local_check.data.password is not defined
+
+ - name: Update network settings
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: false
+ delegate_to: localhost
+ register: set_local
+
+ - name: Assert settings local status page enabled
+ ansible.builtin.assert:
+ that:
+ - set_local is changed
+ - set_local.data is defined
+ - set_local.data.local_status_page_enabled == false
+
+ - name: Update network settings with idempotency
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: false
+ delegate_to: localhost
+ register: set_local_idempotent
+
+ - name: Assert settings local status page enabled with idempotency
+ ansible.builtin.assert:
+ that:
+ - set_local_idempotent is not changed
+ - set_local_idempotent.data is defined
+ - set_local_idempotent.data.local_status_page_enabled == false
+
+ - name: Enable password on local page with check mode
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: true
+ local_status_page:
+ authentication:
+ enabled: true
+ password: abc123
+ delegate_to: localhost
+ register: set_auth_check
+ check_mode: true
+
+ - name: Assert auth settings with check mode
+ ansible.builtin.assert:
+ that:
+ - set_auth_check is changed
+ - set_auth_check.data is defined
+ - set_auth_check.data.local_status_page_enabled == true
+ - set_auth_check.data.local_status_page.authentication.enabled == true
+
+ - name: Enable password on local page
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: true
+ local_status_page:
+ authentication:
+ enabled: true
+ password: abc123
+ delegate_to: localhost
+ register: set_auth
+
+ - debug:
+ var: set_auth
+
+ - name: Assert auth settings
+ ansible.builtin.assert:
+ that:
+ - set_auth is changed
+ - set_auth.data is defined
+ - set_auth.data.local_status_page_enabled == true
+ - set_auth.data.local_status_page.authentication.enabled == true
+
+ - name: Enable password on local page with idempotency
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: true
+ local_status_page:
+ authentication:
+ enabled: true
+ password: abc123
+ delegate_to: localhost
+ register: set_auth_idempotent
+
+ - name: Assert auth settings with idempotency
+ ansible.builtin.assert:
+ that:
+ - set_auth_idempotent is not changed
+ - set_auth_idempotent.data is defined
+ - set_auth_idempotent.data.local_status_page_enabled == true
+ - set_auth_idempotent.data.local_status_page.authentication.enabled == true
+
+ - name: Enable secure port with check mode
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ secure_port:
+ enabled: true
+ delegate_to: localhost
+ register: set_port_check
+ check_mode: true
+
+ - name: Assert secure port settings with check mode
+ ansible.builtin.assert:
+ that:
+ - set_port_check is changed
+ - set_port_check.data is defined
+ - set_port_check.data.secure_port.enabled == true
+
+ - name: Enable secure port
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ secure_port:
+ enabled: true
+ delegate_to: localhost
+ register: sec_port
+
+ - debug:
+ var: sec_port
+
+ - name: Assert secure port settings
+ ansible.builtin.assert:
+ that:
+ - sec_port is changed
+ - sec_port.data is defined
+ - sec_port.data.secure_port.enabled == true
+
+ - name: Enable secure port with idempotency
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ secure_port:
+ enabled: true
+ delegate_to: localhost
+ register: sec_port_idempotent
+
+ - debug:
+ var: sec_port_idempotent
+
+ - name: Assert secure port settings with idempotency
+ ansible.builtin.assert:
+ that:
+ - sec_port_idempotent is not changed
+ - sec_port_idempotent.data is defined
+ - sec_port_idempotent.data.secure_port.enabled == true
+
+ - name: Test status pages - local disabled remote disabled
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: no
+ remote_status_page_enabled: no
+ delegate_to: localhost
+ register: status_pages_1
+
+ - name: Assert status pages - local disabled remote disabled
+ ansible.builtin.assert:
+ that:
+ - status_pages_1.changed == True
+ - status_pages_1['data']['local_status_page_enabled'] == False
+ - status_pages_1['data']['remote_status_page_enabled'] == False
+
+ - name: Test status pages - local enabled remote disabled
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: yes
+ remote_status_page_enabled: no
+ delegate_to: localhost
+ register: status_pages_2
+
+ - name: Assert status pages - local enabled remote disabled
+ ansible.builtin.assert:
+ that:
+ - status_pages_2.changed == True
+ - status_pages_2['data']['local_status_page_enabled'] == True
+ - status_pages_2['data']['remote_status_page_enabled'] == False
+
+ - name: Test status pages - local enabled remote enabled
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: yes
+ remote_status_page_enabled: yes
+ delegate_to: localhost
+ register: status_pages_3
+
+ - name: Assert status pages - local enabled remote enabled
+ ansible.builtin.assert:
+ that:
+ - status_pages_3.changed == True
+ - status_pages_3['data']['local_status_page_enabled'] == True
+ - status_pages_3['data']['remote_status_page_enabled'] == True
+
+
+ - name: Test status pages - local disabled remote enabled - expected to fail
+ cisco.meraki.meraki_network_settings:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ local_status_page_enabled: no
+ remote_status_page_enabled: yes
+ delegate_to: localhost
+ register: status_pages_4
+ ignore_errors: yes
+
+ - name: Assert status pages - local disabled remote enabled
+ ansible.builtin.assert:
+ that:
+ - status_pages_4.changed == False
+ - '"must be true when setting" in status_pages_4.msg'
+
+#############################################################################
+# Tear down starts here
+#############################################################################
+ always:
+ - name: Delete network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: NetworkSettingsTestNet
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/main.yml
new file mode 100644
index 000000000..e7ad65b81
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/main.yml
@@ -0,0 +1,8 @@
+# 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/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/tests.yml
new file mode 100644
index 000000000..28ebce01e
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/tests.yml
@@ -0,0 +1,149 @@
+# 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 a new organization named IntTestOrg
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ org_name: IntTestOrg
+ state: present
+ output_level: debug
+ register: new_org
+
+ - debug:
+ msg: '{{new_org}}'
+
+ - name: Clone IntTestOrg
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ clone: IntTestOrg
+ org_name: IntTestOrgCloned
+ state: present
+ register: cloned_org
+
+ - debug:
+ msg: '{{cloned_org}}'
+
+ - set_fact:
+ cloned_net_id: '{{cloned_org.data.id}}'
+
+ - name: Rename IntTestOrg
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ org_name: IntTestOrgRenamed
+ org_id: '{{ new_org.data.id }}'
+ state: present
+ register: modify_org
+
+ - debug:
+ msg: '{{ modify_org }}'
+
+ - set_fact:
+ renamed_org_id: '{{modify_org.data.id}}'
+
+ - name: Rename IntTestOrg idempotent
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ org_name: IntTestOrgRenamed
+ org_id: '{{ new_org.data.id }}'
+ state: present
+ register: modify_org_idempotent
+
+ - name: Present assertions
+ assert:
+ that:
+ - '"https" in new_org.url'
+ - new_org.changed == True
+ - new_org.data.id is defined
+ - cloned_org.changed == True
+ - cloned_org.data.id is defined
+ - modify_org.changed == True
+ - 'modify_org.data.name == "IntTestOrgRenamed"'
+ - modify_org_idempotent.changed == False
+ - modify_org_idempotent.data is defined
+
+ - name: List all organizations
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ state: query
+ register: query_all
+
+ - name: Query information about a single organization named IntTestOrg
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ org_name: IntTestOrgRenamed
+ state: query
+ register: query_org
+
+ - name: Query information about IntTestOrg by organization ID
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ org_id: '{{ query_org.data.id }}'
+ state: query
+ register: query_org_id
+
+ - name: Query assertions
+ assert:
+ that:
+ - query_org.data.id is defined
+ - query_all.changed == False
+ - query_all.data | length >= 1
+ - 'query_org.data.name == "IntTestOrgRenamed"'
+ - 'query_org_id.data.id == query_org.data.id'
+
+ - name: Delete without confirmation code
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: IntTestOrgCloned
+ register: delete_no_confirm
+ ignore_errors: yes
+
+ - assert:
+ that:
+ 'delete_no_confirm.msg == "delete_confirm must match the network ID of the network to be deleted."'
+
+ always:
+ # - name: Pause playbook for more reliable deletion
+ # pause:
+ # minutes: 1
+
+ - name: Delete cloned organizations with check mode
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: IntTestOrgCloned
+ delete_confirm: '{{cloned_net_id}}'
+ register: deleted_org_check
+ check_mode: yes
+
+ - assert:
+ that:
+ - deleted_org_check is changed
+
+ - name: Delete cloned organizations
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: IntTestOrgCloned
+ delete_confirm: '{{cloned_net_id}}'
+ register: deleted_org
+
+ - name: Delete renamed organization by id
+ meraki_organization:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_id: '{{renamed_org_id}}'
+ delete_confirm: '{{renamed_org_id}}'
+ register: deleted_org_id
+
+ - assert:
+ that:
+ - deleted_org_id is changed
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/tasks/main.yml
new file mode 100644
index 000000000..ed0868a40
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/tasks/main.yml
@@ -0,0 +1,290 @@
+# 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 SNMP network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ type: appliance
+ delegate_to: localhost
+ register: new_net
+
+ - set_fact:
+ net_id: new_net.data.id
+
+ - name: Query all SNMP settings
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: query
+ delegate_to: localhost
+ register: snmp_query
+
+ - debug:
+ msg: '{{snmp_query}}'
+
+ - name: Enable SNMPv2c
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v2c_enabled: true
+ delegate_to: localhost
+ register: snmp_v2_enable
+
+ - debug:
+ msg: '{{snmp_v2_enable}}'
+
+ - assert:
+ that:
+ - snmp_v2_enable.data.v2_community_string is defined
+ - snmp_v2_enable.data.v2c_enabled == true
+
+ - name: Disable SNMPv2c
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v2c_enabled: False
+ delegate_to: localhost
+ register: snmp_v2_disable
+
+ - assert:
+ that:
+ - snmp_v2_disable.data.v2_community_string is not defined
+ - snmp_v2_disable.data.v2c_enabled == False
+
+ - name: Enable SNMPv2c with org_id
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_id: '{{test_org_id}}'
+ state: present
+ v2c_enabled: true
+ delegate_to: localhost
+ register: snmp_v2_enable_id
+
+ - debug:
+ msg: '{{snmp_v2_enable_id}}'
+
+ - assert:
+ that:
+ - snmp_v2_enable_id.data.v2_community_string is defined
+ - snmp_v2_enable_id.data.v2c_enabled == true
+
+ - name: Disable SNMPv2c with org_id
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_id: '{{test_org_id}}'
+ state: present
+ v2c_enabled: False
+ delegate_to: localhost
+ register: snmp_v2_disable_id
+
+ - assert:
+ that:
+ - snmp_v2_disable_id.data.v2_community_string is not defined
+ - snmp_v2_disable_id.data.v2c_enabled == False
+
+ - name: Enable SNMPv3 with check mode
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v3_enabled: true
+ v3_auth_mode: SHA
+ v3_auth_pass: ansiblepass
+ v3_priv_mode: AES128
+ v3_priv_pass: ansiblepass
+ delegate_to: localhost
+ check_mode: yes
+ register: snmp_v3_enable_check
+
+ - assert:
+ that:
+ - snmp_v3_enable_check.data.v3_enabled == True
+ - snmp_v3_enable_check.changed == True
+
+ - name: Enable SNMPv3
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v3_enabled: true
+ v3_auth_mode: SHA
+ v3_auth_pass: ansiblepass
+ v3_priv_mode: AES128
+ v3_priv_pass: ansiblepass
+ delegate_to: localhost
+ register: snmp_v3_enable
+
+ - assert:
+ that:
+ - snmp_v3_enable.data.v3_enabled == True
+ - snmp_v3_enable.changed == True
+
+ - name: Check for idempotency
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v3_enabled: true
+ v3_auth_mode: SHA
+ v3_auth_pass: ansiblepass
+ v3_priv_mode: AES128
+ v3_priv_pass: ansiblepass
+ delegate_to: localhost
+ register: snmp_idempotent
+
+ - debug:
+ msg: '{{snmp_idempotent}}'
+
+ - assert:
+ that:
+ - snmp_idempotent.changed == False
+ - snmp_idempotent.data is defined
+
+ - name: Add peer IPs
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v3_enabled: true
+ v3_auth_mode: SHA
+ v3_auth_pass: ansiblepass
+ v3_priv_mode: AES128
+ v3_priv_pass: ansiblepass
+ peer_ips:
+ - 1.1.1.1
+ - 2.2.2.2
+ delegate_to: localhost
+ register: peers
+
+ - debug:
+ msg: '{{peers}}'
+
+ - assert:
+ that:
+ - peers.data.peer_ips is defined
+
+ - name: Set short password
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v3_enabled: true
+ v3_auth_mode: SHA
+ v3_auth_pass: ansible
+ v3_priv_mode: AES128
+ v3_priv_pass: ansible
+ delegate_to: localhost
+ register: short_password
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - '"at least 8" in short_password.msg'
+
+ - name: Set network access type to community string
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ access: community
+ community_string: abc123
+ delegate_to: localhost
+ register: set_net_community
+
+ - debug:
+ var: set_net_community
+
+ - assert:
+ that:
+ - set_net_community is changed
+ - set_net_community.data is defined
+
+ - name: Set network access type to username
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ access: users
+ users:
+ - username: ansibleuser
+ passphrase: ansiblepass
+ delegate_to: localhost
+ register: set_net_user
+
+ - debug:
+ var: set_net_user
+
+ - assert:
+ that:
+ - set_net_user is changed
+ - set_net_user.data is defined
+
+ - name: Set network access type to none
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ access: none
+ delegate_to: localhost
+ register: set_net_none
+
+ - debug:
+ var: set_net_none
+
+ - assert:
+ that:
+ - set_net_none is changed
+ - set_net_none.data is defined
+
+ - name: Query network SNMP settings
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: query
+ delegate_to: localhost
+ register: get_net
+
+ - debug:
+ var: get_net
+
+ - assert:
+ that:
+ - get_net.data is defined
+
+ always:
+ - name: Disable SNMPv3
+ meraki_snmp:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ state: present
+ v3_enabled: no
+ v3_auth_mode: SHA
+ v3_auth_pass: ansiblepass
+ v3_priv_mode: AES128
+ v3_priv_pass: ansiblepass
+ delegate_to: localhost
+
+ - name: Delete SNMP network
+ meraki_network:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: absent
+ delegate_to: localhost
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/tasks/main.yml
new file mode 100644
index 000000000..1f934f6fd
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/tasks/main.yml
@@ -0,0 +1,747 @@
+# Test code for the Meraki SSID 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: Block of the test
+ block:
+ - name: Set fact wpa_enable to false
+ ansible.builtin.set_fact:
+ wpa_enabled: false
+
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Create test network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ type: wireless
+ register: test_net
+
+ - name: Debug test_net
+ ansible.builtin.debug:
+ msg: '{{ test_net }}'
+
+ - name: Add access points to network
+ cisco.meraki.meraki_device:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ serial: '{{ serial_wireless }}'
+ delegate_to: localhost
+
+ - name: Query all SSIDs
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ delegate_to: localhost
+ register: query_all
+
+ - name: Enable and name SSID with check mode
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ enabled: true
+ delegate_to: localhost
+ register: enable_name_ssid_check
+ check_mode: true
+
+ - name: Assert enable_name_ssid_check
+ ansible.builtin.assert:
+ that:
+ - enable_name_ssid_check is changed
+ - enable_name_ssid_check.data is defined
+
+ - name: Enable and name SSID
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ enabled: true
+ delegate_to: localhost
+ register: enable_name_ssid
+
+ - name: Debug enable_name_ssid
+ ansible.builtin.debug:
+ msg: '{{ enable_name_ssid }}'
+
+ - name: Assert query_all
+ ansible.builtin.assert:
+ that:
+ - query_all.data | length == 15
+ - query_all.data.0.name == 'TestNetSSID WiFi'
+ - enable_name_ssid.data.name == 'AnsibleSSID'
+
+ - name: Check for idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ enabled: true
+ delegate_to: localhost
+ register: enable_name_ssid_idempotent
+
+ - name: Debug enable_name_ssid_idempotent
+ ansible.builtin.debug:
+ msg: '{{ enable_name_ssid_idempotent }}'
+
+ - name: Assert enable_name_ssid_idempotent
+ ansible.builtin.assert:
+ that:
+ - enable_name_ssid_idempotent.changed == False
+ - enable_name_ssid_idempotent.data is defined
+
+ - name: Set splash portal with sponsor domain
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ splash_page: Sponsored guest
+ splash_guest_sponsor_domains:
+ - "gmail.com"
+ delegate_to: localhost
+ register: splash_guest_sponsor_domain
+
+ - name: Debug splash_guest_sponsor_domain
+ ansible.builtin.debug:
+ msg: '{{ splash_guest_sponsor_domain }}'
+
+ - name: Assert splash_guest_sponsor_domain
+ ansible.builtin.assert:
+ that:
+ - splash_guest_sponsor_domain.changed == true
+ - splash_guest_sponsor_domain.data is defined
+
+ - name: Query one SSIDs
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ delegate_to: localhost
+ register: query_one
+
+ - name: Debug query_one
+ ansible.builtin.debug:
+ msg: '{{ query_one }}'
+
+ - name: Assert query_one
+ ansible.builtin.assert:
+ that:
+ - query_one.data.name == 'AnsibleSSID'
+
+ - name: Query one SSID with number
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ number: 1
+ delegate_to: localhost
+ register: query_one_number
+
+ - name: Debug query_one_number
+ ansible.builtin.debug:
+ msg: '{{ query_one_number }}'
+
+ - name: Assert query_one_number
+ ansible.builtin.assert:
+ that:
+ - query_one_number.data.name == 'AnsibleSSID'
+
+ - name: Disable SSID without specifying number
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ enabled: false
+ delegate_to: localhost
+ register: disable_ssid
+
+ - name: Debug disable_ssid
+ ansible.builtin.debug:
+ msg: '{{ disable_ssid.data.enabled }}'
+
+ - name: Assert disable_ssid
+ ansible.builtin.assert:
+ that:
+ - disable_ssid.data.enabled == False
+
+ - name: Enable SSID with number
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ number: 1
+ enabled: true
+ delegate_to: localhost
+ register: enable_ssid_number
+
+ - name: Debug enable_ssid_number
+ ansible.builtin.debug:
+ msg: '{{ enable_ssid_number.data.enabled }}'
+
+ - name: Assert enable_ssid_number
+ ansible.builtin.assert:
+ that:
+ - enable_ssid_number.data.enabled == true
+
+ - name: Configure sponsor portal
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ number: 1
+ splash_page: Sponsored guest
+
+ - name: Set VLAN arg spec
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ number: 1
+ use_vlan_tagging: true
+ visible: true
+ ip_assignment_mode: Bridge mode
+ default_vlan_id: 1
+ ap_tags_vlan_ids:
+ - tags: wifi
+ vlan_id: 2
+ delegate_to: localhost
+ register: set_vlan_arg
+
+ - name: Debug set_vlan_org
+ ansible.builtin.debug:
+ var: set_vlan_arg
+
+ - name: Assert set_vlan_arg
+ ansible.builtin.assert:
+ that: set_vlan_arg is changed
+
+ - name: Set VLAN arg spec
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ number: 1
+ use_vlan_tagging: true
+ visible: true
+ ip_assignment_mode: Bridge mode
+ default_vlan_id: 1
+ ap_tags_vlan_ids:
+ - tags: wifi
+ vlan_id: 2
+ delegate_to: localhost
+ register: set_vlan_arg_idempotent
+
+ - name: Debug set_vlan_arg_idempotent
+ ansible.builtin.debug:
+ var: set_vlan_arg_idempotent
+
+ - name: Assert set_vlan_org_idempotent
+ ansible.builtin.assert:
+ that: set_vlan_arg_idempotent is not changed
+
+ - name: Set PSK
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: psk
+ psk: abc1234567890
+ encryption_mode: wpa
+ delegate_to: localhost
+ register: psk
+
+ - name: Debug psk
+ ansible.builtin.debug:
+ msg: '{{ psk }}'
+
+ - name: Assert psk
+ ansible.builtin.assert:
+ that:
+ - psk.data.auth_mode == 'psk'
+ - psk.data.encryption_mode == 'wpa'
+ - psk.data.wpa_encryption_mode == 'WPA2 only'
+
+ - name: Set PSK with idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: psk
+ psk: abc1234567890
+ encryption_mode: wpa
+ delegate_to: localhost
+ register: psk_idempotent
+
+ - name: Debug psk_idempotent
+ ansible.builtin.debug:
+ msg: '{{ psk_idempotent }}'
+
+ - name: Assert psk_idempotent
+ ansible.builtin.assert:
+ that:
+ - psk_idempotent is not changed
+
+ #
+ # Check WPA3 Transition Mode
+ #
+ - name: Set WPA3 Transition Mode
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: psk
+ psk: abc1234567890
+ encryption_mode: wpa
+ wpa_encryption_mode: WPA3 Transition Mode
+ delegate_to: localhost
+ register: psk_wpa3_transition_mode
+ when: wpa_enabled
+
+ - name: Debug psk_wpa3_transition_mode
+ ansible.builtin.debug:
+ msg: '{{ psk_wpa3_transition_mode }}'
+ when: wpa_enabled
+
+ - name: Assert psk_wpa3_transition_mode
+ ansible.builtin.assert:
+ that:
+ - psk_wpa3_transition_mode.data.auth_mode == 'psk'
+ - psk_wpa3_transition_mode.data.encryption_mode == 'wpa'
+ - psk_wpa3_transition_mode.data.wpa_encryption_mode == 'WPA3 Transition Mode'
+ when: wpa_enabled
+
+ - name: Set WPA3 Transition Mode with Idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: psk
+ psk: abc1234567890
+ encryption_mode: wpa
+ wpa_encryption_mode: WPA3 Transition Mode
+ delegate_to: localhost
+ register: psk_wpa3_transition_mode_idempotent
+ when: wpa_enabled
+
+ - name: Debug wpa_enabled
+ ansible.builtin.debug:
+ msg: '{{ psk_wpa3_transition_mode_idempotent }}'
+ when: wpa_enabled
+
+ - name: Assert psk_wpa3_transition_mode_idempotent
+ ansible.builtin.assert:
+ that:
+ - psk_wpa3_transition_mode_idempotent is not changed
+ when: wpa_enabled
+
+ - name: Enable click-through splash page
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ splash_page: Click-through splash page
+ delegate_to: localhost
+ register: splash_click
+
+ - name: Debug splash_click
+ ansible.builtin.debug:
+ msg: '{{ splash_click }}'
+
+ - name: Assert splash_click
+ ansible.builtin.assert:
+ that:
+ - splash_click.data.splash_page == 'Click-through splash page'
+
+ - name: Set walled garden
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ walled_garden_enabled: true
+ walled_garden_ranges:
+ - 192.168.0.0/24
+ - '*.ansible.com'
+ delegate_to: localhost
+ register: walled_garden
+
+ - name: Debug walled_garden
+ ansible.builtin.debug:
+ msg: '{{ walled_garden }}'
+
+ - name: Assert walled_garden
+ ansible.builtin.assert:
+ that:
+ - 'walled_garden.data.walled_garden_enabled == true'
+ - walled_garden is changed
+
+ - name: Set walled garden with idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ walled_garden_enabled: true
+ walled_garden_ranges:
+ - 192.168.0.0/24
+ - '*.ansible.com'
+ delegate_to: localhost
+ register: walled_garden_idempotent
+
+ - name: Debug walled_garden_idempotent
+ ansible.builtin.debug:
+ msg: '{{ walled_garden_idempotent }}'
+
+ - name: Assert walled_garden_idempotent
+ ansible.builtin.assert:
+ that:
+ - walled_garden_idempotent.data is defined
+ - walled_garden_idempotent is not changed
+
+ - name: Configure RADIUS servers
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: open-with-radius
+ radius_servers:
+ - host: 192.0.1.200
+ port: 1234
+ secret: abc98765
+ delegate_to: localhost
+ register: set_radius_server
+
+ - name: Debug set_radius_server
+ ansible.builtin.debug:
+ msg: '{{ set_radius_server }}'
+
+ - name: Assert set_radius_server
+ ansible.builtin.assert:
+ that:
+ - set_radius_server.data.radius_servers.0.host == '192.0.1.200'
+
+ - name: Configure RADIUS servers with idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: open-with-radius
+ radius_servers:
+ - host: 192.0.1.200
+ port: 1234
+ secret: abc98765
+ delegate_to: localhost
+ register: set_radius_server_idempotent
+
+ - name: Debug set_radius_server_idempotent
+ ansible.builtin.debug:
+ var: set_radius_server_idempotent
+
+ - name: Assert set_radius_server_idempotent
+ ansible.builtin.assert:
+ that:
+ - set_radius_server_idempotent is not changed
+
+ #################
+ # Error testing #
+ #################
+ - name: Set available on all aps to false without also setting availability tags
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ available_on_all_aps: false
+ delegate_to: localhost
+ register: no_availability_tags
+ failed_when: false
+ changed_when: false
+
+ - name: Debug no_availability_tags
+ ansible.builtin.debug:
+ msg: '{{ no_availability_tags }}'
+
+ - name: Assert no_availability_tags
+ ansible.builtin.assert:
+ that:
+ - no_availability_tags.msg == 'available_on_all_aps is only allowed to be false when ap_availability_tags is defined'
+
+ - name: Set ap availability tags without setting available on all aps to false
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ available_on_all_aps: true
+ ap_availability_tags:
+ - "test"
+ delegate_to: localhost
+ register: available_on_all_aps_true_with_tags
+ failed_when: false
+ changed_when: false
+
+ - name: Debug available_on_all_aps_true_with_tags
+ ansible.builtin.debug:
+ msg: '{{ available_on_all_aps_true_with_tags }}'
+
+ - name: Assert available_on_all_aps_true_with_tags
+ ansible.builtin.assert:
+ that:
+ - available_on_all_aps_true_with_tags.msg == 'ap_availability_tags is only allowed when available_on_all_aps is false'
+
+ - name: Set LAN isolation without Bridge mode
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ ip_assignment_mode: "NAT mode"
+ lan_isolation_enabled: true
+ delegate_to: localhost
+ register: lan_isolation_invalid
+ failed_when: false
+ changed_when: false
+
+ - name: Debug lan_isolation_invalid
+ ansible.builtin.debug:
+ msg: '{{ lan_isolation_invalid }}'
+
+ - name: Assert lan_isolation_invalid
+ ansible.builtin.assert:
+ that:
+ - lan_isolation_invalid.msg == 'lan_isolation_enabled is only allowed when ip_assignment_mode is Bridge mode'
+
+ - name: Set PSK with wrong mode
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: open
+ psk: abc1234
+ delegate_to: localhost
+ register: psk_invalid
+ failed_when: false
+ changed_when: false
+
+ - name: Debug psk_invalid
+ ansible.builtin.debug:
+ msg: '{{ psk_invalid }}'
+
+ - name: Assert psk_invalid
+ ansible.builtin.assert:
+ that:
+ - psk_invalid.msg == 'PSK is only allowed when auth_mode is set to psk'
+
+ - name: Set PSK with invalid encryption mode
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: psk
+ psk: abc1234
+ encryption_mode: eap
+ delegate_to: localhost
+ register: psk_invalid_mode
+ failed_when: false
+ changed_when: false
+
+ - name: Debug psk_invalid_mode
+ ansible.builtin.debug:
+ msg: '{{ psk_invalid_mode }}'
+
+ - name: Assert psk_invalid_mode
+ ansible.builtin.assert:
+ that:
+ - psk_invalid_mode.msg == 'PSK requires encryption_mode be set to wpa'
+
+ - name: Error for PSK and RADIUS servers
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ auth_mode: psk
+ radius_servers:
+ - host: 192.0.1.200
+ port: 1234
+ secret: abc98765
+ delegate_to: localhost
+ register: err_radius_server_psk
+ failed_when: false
+ changed_when: false
+
+ - name: Debug err_radius_server_psk
+ ansible.builtin.debug:
+ var: err_radius_server_psk
+
+ - name: Assert err_radius_server_psk.msg
+ ansible.builtin.assert:
+ that:
+ - 'err_radius_server_psk.msg == "radius_servers requires auth_mode to be open-with-radius or 8021x-radius"'
+
+ - name: Set VLAN arg without default VLAN error
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ number: 1
+ use_vlan_tagging: true
+ visible: true
+ ip_assignment_mode: Bridge mode
+ ap_tags_vlan_ids:
+ - tags: wifi
+ vlan_id: 2
+ delegate_to: localhost
+ register: set_vlan_arg_err
+ failed_when: false
+ changed_when: false
+
+ - name: Debug set_vlan_arg_err
+ ansible.builtin.debug:
+ var: set_vlan_arg_err
+
+ - name: Assert set_vlan_arg_err.msg
+ ansible.builtin.assert:
+ that:
+ - 'set_vlan_arg_err.msg == "default_vlan_id is required when use_vlan_tagging is True"'
+
+ - name: Create SSID to Test SSID Delete Idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleIdempotentDeleteSSID
+ enabled: true
+ delegate_to: localhost
+
+ - name: Remove SSID to Test SSID Delete Idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleIdempotentDeleteSSID
+ delegate_to: localhost
+ register: deleted_ssid
+
+ - name: Assert deleted_ssid
+ ansible.builtin.assert:
+ that:
+ - deleted_ssid is changed
+
+ - name: Remove SSID to Test SSID Delete Idempotency
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleIdempotentDeleteSSID
+ delegate_to: localhost
+ register: deleted_ssid
+
+ - name: Assert deleted_ssid
+ ansible.builtin.assert:
+ that:
+ - deleted_ssid is not changed
+
+ always:
+ - name: Delete SSID with check mode
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ delegate_to: localhost
+ register: delete_ssid_check
+ check_mode: true
+
+ - name: Assert delete_ssid_check
+ ansible.builtin.assert:
+ that:
+ - delete_ssid_check is changed
+ - delete_ssid_check.data is defined
+
+ - name: Delete SSID
+ cisco.meraki.meraki_mr_ssid:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ name: AnsibleSSID
+ delegate_to: localhost
+ register: delete_ssid
+
+ - name: Debug delete_ssid
+ ansible.builtin.debug:
+ msg: '{{ delete_ssid }}'
+
+ - name: Assert delete_ssid
+ ansible.builtin.assert:
+ that:
+ - delete_ssid.data.name == 'Unconfigured SSID 2'
+
+ - name: Delete test network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: TestNetSSID
+ register: delete_net
+
+ - name: Debug delete_net
+ ansible.builtin.debug:
+ msg: '{{ delete_net }}'
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/tasks/main.yml
new file mode 100644
index 000000000..8bf518472
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/tasks/main.yml
@@ -0,0 +1,228 @@
+# 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
+
+ - set_fact:
+ syslog_test_net_name: 'syslog_{{test_net_name}}'
+
+ - name: Create network with type appliance and switch
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ type:
+ - appliance
+ - wireless
+ delegate_to: localhost
+ register: new_net
+
+ - set_fact:
+ net_id: '{{new_net.data.id}}'
+
+ - name: Query syslog settings
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: query
+ delegate_to: localhost
+ register: query_all
+
+ - name: Set syslog server
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ servers:
+ - host: 192.0.1.2
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ delegate_to: localhost
+ register: create_server
+
+ - assert:
+ that:
+ - create_server['data']['servers'][0]['host'] == "192.0.1.2"
+ - create_server is changed
+
+ - name: Set syslog server with idempotency
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ servers:
+ - host: 192.0.1.2
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ delegate_to: localhost
+ register: create_server_idempotency
+
+ - assert:
+ that:
+ - create_server_idempotency.changed == False
+ - create_server_idempotency.data is defined
+
+ - name: Set syslog server with wireless Event log
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ servers:
+ - host: 192.0.1.2
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ - wireless Event log
+ delegate_to: localhost
+ register: create_server
+
+ - assert:
+ that:
+ - create_server['data']['servers'][0]['host'] == "192.0.1.2"
+ - create_server is changed
+
+ - name: Set syslog server with wireless Event log with idempotency
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ servers:
+ - host: 192.0.1.2
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ - wireless event log
+ delegate_to: localhost
+ register: create_server_idempotency
+
+ - assert:
+ that:
+ - create_server_idempotency.changed == False
+ - create_server_idempotency.data is defined
+
+ - name: Set syslog servers to address bug 288
+ meraki_syslog:
+ auth_key: "{{ auth_key }}"
+ state: present
+ org_name: "{{ test_org_name }}"
+ net_name: "{{ test_net_name }}"
+ servers:
+ - host: "10.3.15.11"
+ port: 604
+ roles:
+ - Appliance event log
+ - Security events
+ - host: "10.5.15.11"
+ port: 604
+ roles:
+ - Appliance event log
+ - Security events
+ - host: "10.9.15.11"
+ port: 604
+ roles:
+ - Appliance event log
+ - Security events
+ - host: "10.5.250.12"
+ port: 514
+ roles:
+ - Appliance event log
+ - Security events
+
+ - name: Set multiple syslog servers
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_id: '{{net_id}}'
+ state: present
+ servers:
+ - host: 192.0.1.3
+ port: 514
+ roles:
+ - Appliance event log
+ - host: 192.0.1.4
+ port: 514
+ roles:
+ - Appliance event log
+ - Flows
+ - host: 192.0.1.5
+ port: 514
+ roles:
+ - Flows
+ delegate_to: localhost
+ register: create_multiple_servers
+
+ - assert:
+ that:
+ - create_multiple_servers['data']['servers'][0]['host'] == "192.0.1.3"
+ - create_multiple_servers['data']['servers'][1]['host'] == "192.0.1.4"
+ - create_multiple_servers['data']['servers'][2]['host'] == "192.0.1.5"
+ - create_multiple_servers['data']['servers'] | length == 3
+
+ - name: Create syslog server with bad name
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ servers:
+ - host: 192.0.1.6
+ port: 514
+ roles:
+ - Invalid role
+ delegate_to: localhost
+ register: invalid_role
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - '"Invalid role found in" in invalid_role.msg'
+
+ - name: Add role to existing syslog server # Adding doesn't work, just creation
+ meraki_syslog:
+ auth_key: '{{auth_key}}'
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ state: present
+ servers:
+ - host: 192.0.1.2
+ port: 514
+ roles:
+ - Flows
+ delegate_to: localhost
+ register: add_role
+
+ - debug:
+ msg: '{{add_role.data}}'
+
+ - assert:
+ that:
+ - add_role.data.servers.0.roles.0 == 'Flows'
+
+ always:
+ - name: Delete syslog test network
+ meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{test_org_name}}'
+ net_name: '{{test_net_name}}'
+ delegate_to: localhost
+ register: delete_all
+ ignore_errors: yes
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/aliases
new file mode 100644
index 000000000..ad7ccf7ad
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/aliases
@@ -0,0 +1 @@
+unsupported
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/main.yml
new file mode 100644
index 000000000..f671fc928
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/main.yml
@@ -0,0 +1,7 @@
+# Test code for the Meraki Webhooks 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
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/tests.yml
new file mode 100644
index 000000000..5455336e8
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/tests.yml
@@ -0,0 +1,426 @@
+# Test code for the Meraki Webhook module
+# Copyright: (c) 2019, Kevin Breit (@kbreit)
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Test Cases for meraki_webhook module
+ block:
+ - name: Test an API key is provided
+ ansible.builtin.fail:
+ msg: Please define an API key
+ when: auth_key is not defined
+
+ - name: Create test network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ type: appliance
+
+ - name: Query for any webhooks expecting None
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ register: query_none
+
+ - name: Query for any webhooks expecting None - debug
+ ansible.builtin.debug:
+ var: query_none
+
+ - name: Query for any webhooks expecting None - assert
+ ansible.builtin.assert:
+ that:
+ - query_none is not changed
+ - query_none.data[0] is not defined
+
+ - name: Create webhook with check mode
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ shared_secret: shhhdonttellanyone
+ check_mode: true
+ register: create_one_check
+
+ - name: Create webhook with check mode - debug
+ ansible.builtin.debug:
+ var: create_one_check
+
+ - name: Create webhook with check mode - assert
+ ansible.builtin.assert:
+ that:
+ - create_one_check is changed
+ - create_one_check.data is defined
+
+ - name: Create webhook
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ shared_secret: shhhdonttellanyone
+ register: create_one
+
+ - name: Create webhook - debug
+ ansible.builtin.debug:
+ var: create_one
+
+ - name: Create webhook - assert
+ ansible.builtin.assert:
+ that:
+ - create_one is changed
+ - create_one.data is defined
+
+ - name: Set webhook_id for future use
+ ansible.builtin.set_fact:
+ webhook_id: '{{ create_one.data.id }}'
+
+ - name: Query all webhooks expecting 1
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ register: query_one
+
+ - name: Query all webhooks expecting 1 - debug
+ ansible.builtin.debug:
+ var: query_one
+
+ - name: Query all webhooks expecting 1 - assert
+ ansible.builtin.assert:
+ that:
+ - query_one.data is defined
+ - query_one.data[0] is defined
+ - query_one.data[1] is not defined
+
+ - name: Query one webhook
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ register: query_one
+
+ - name: Query one webhook - debug
+ ansible.builtin.debug:
+ var: query_one
+
+ - name: Query one webhook - assert
+ ansible.builtin.assert:
+ that:
+ - query_one.data is defined
+
+ - name: Query one webhook with id
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ webhook_id: '{{ webhook_id }}'
+ register: query_one_id
+
+ - name: Query one webhook with id - debug
+ ansible.builtin.debug:
+ var: query_one_id
+
+ - name: Query one webhook with id - assert
+ ansible.builtin.assert:
+ that:
+ - query_one_id.data is defined
+
+ - name: Update webhook with check mode
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ shared_secret: shhhdonttellanyonehere
+ check_mode: true
+ register: update_check
+
+ - name: Update webhook with check mode - assert
+ ansible.builtin.assert:
+ that:
+ - update_check is changed
+ - update_check.data is defined
+
+ - name: Update webhook
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ shared_secret: shhhdonttellanyonehere
+ register: update
+
+ - name: Update webhook - debug
+ ansible.builtin.debug:
+ var: update
+
+ - name: Update webhook - assert
+ ansible.builtin.assert:
+ that:
+ - update is changed
+ - update.data is defined
+ - update.data.shared_secret is not defined
+
+ - name: Update webhook with idempotency with shared secret
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ shared_secret: shhhdonttellanyonehere
+ register: update_idempotent
+
+ - name: Update webhook with idempotency with shared secret - debug
+ ansible.builtin.debug:
+ var: update_idempotent
+
+ # response will always be "changed" since shared secret is not sent back in the response.
+ - name: Update webhook with idempotency with shared secret - assert
+ ansible.builtin.assert:
+ that:
+ - update_idempotent is changed
+ - update_idempotent.data is defined
+ - update_idempotent.data.shared_secret is not defined
+
+ - name: Update webhook with idempotency without shared secret
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ register: update_idempotent2
+
+ - name: Update webhook with idempotency without shared secret - debug
+ ansible.builtin.debug:
+ var: update_idempotent2
+
+ - name: Update webhook with idempotency without shared secret - assert
+ ansible.builtin.assert:
+ that:
+ - update_idempotent2 is not changed
+ - update_idempotent2.data is defined
+ - update_idempotent2.data.shared_secret is not defined
+
+ - name: Update webhook with id
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ webhook_id: '{{ webhook_id }}'
+ name: Test_Hook
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ shared_secret: shhhdonttellanyonehereid
+ register: update_id
+
+ - name: Update webhook with id - debug
+ ansible.builtin.debug:
+ var: update_id
+
+ # response will always be "changed" since shared secret is not sent back in the response.
+ - name: Update webhook with id - assert
+ ansible.builtin.assert:
+ that:
+ - update_id is changed
+ - update_id.data is defined
+
+ - name: Create webhook payload template for a webhook
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: present
+ name: TestPayloadTemplate
+ body: "a fake body"
+ register: payload_template
+
+ - name: Debug payload_template
+ ansible.builtin.debug:
+ var: payload_template
+
+ - name: Create webhook with a payload template
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ payload_template_name: TestPayloadTemplate
+ name: Test_Hook_with_template
+ url: https://webhook.site/8eb5b76f-b167
+ shared_secret: shhhdonttellanyone
+ register: webhook_with_template
+
+ - name: Create webhook with a payload template - debug
+ ansible.builtin.debug:
+ var: webhook_with_template
+
+ - name: Create webhook with a payload template - assert
+ ansible.builtin.assert:
+ that:
+ - webhook_with_template is changed
+ - webhook_with_template.data is defined
+ - webhook_with_template.data.payload_template.name == "TestPayloadTemplate"
+
+ - name: Delete webhook with payload template
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook_with_template
+ register: delete_hook_with_template
+
+ - name: Delete webhook with payload template - debug
+ ansible.builtin.debug:
+ var: delete_hook_with_template
+
+ - name: Delete webhook with payload template - assert
+ ansible.builtin.assert:
+ that:
+ - delete_hook_with_template is changed
+
+ - name: Create test webhook
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: present
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ test: test
+ url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
+ register: webhook_test
+
+ - name: Set test_id for future use
+ ansible.builtin.set_fact:
+ test_id: '{{ webhook_test.data.id }}'
+
+ - name: Create test webhook - debug
+ ansible.builtin.debug:
+ var: test_id
+
+ - name: Get webhook status
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ test_id: '{{ test_id }}'
+ register: webhook_test_status
+
+ - name: Get webhook status - debug
+ ansible.builtin.debug:
+ var: webhook_test_status
+
+ - name: Get webhook status - assert
+ ansible.builtin.assert:
+ that:
+ - webhook_test_status.data is defined
+
+ - name: Query all webhooks
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: query
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ register: query_all
+
+ - name: Query all webhooks - debug
+ ansible.builtin.debug:
+ var: query_all
+
+ - name: Delete webhook invalid webhook
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook_Invalid
+ check_mode: true
+ register: delete_invalid
+ ignore_errors: true
+
+ - name: Delete webhook invalid webhook - debug
+ ansible.builtin.debug:
+ var: delete_invalid
+
+ - name: Delete webhook invalid webhook - assert
+ ansible.builtin.assert:
+ that:
+ - 'delete_invalid.msg == "There is no webhook with the name Test_Hook_Invalid"'
+
+ - name: Delete webhook in check mode
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ check_mode: true
+ register: delete_check
+
+ - name: Delete webhook in check mode - debug
+ ansible.builtin.debug:
+ var: delete_check
+
+ - name: Delete webhook in check mode - assert
+ ansible.builtin.assert:
+ that:
+ - delete_check is changed
+
+ - name: Delete webhook
+ cisco.meraki.meraki_webhook:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ name: Test_Hook
+ register: delete
+
+ - name: Delete webhook - debug
+ ansible.builtin.debug:
+ var: delete
+
+ - name: Delete webhook - assert
+ ansible.builtin.assert:
+ that:
+ - delete is changed
+
+ #############################################################################
+ # Tear down starts here
+ #############################################################################
+ always:
+ - name: Delete webhook payload template for a webhook
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
+ state: absent
+ name: TestPayloadTemplate
+
+ - name: Delete test network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_name: '{{ test_net_name }}'
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/aliases b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/aliases
new file mode 100644
index 000000000..06fe32bc6
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/aliases
@@ -0,0 +1,2 @@
+unsupported
+
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/main.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/main.yml
new file mode 100644
index 000000000..878b2a45b
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/main.yml
@@ -0,0 +1,7 @@
+# Test code for the Meraki modules
+
+# Copyright: (c) 2022, Joshua Coronado (@joshuajcoronado) <joshua@coronado.io>
+# 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
diff --git a/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/tests.yml b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/tests.yml
new file mode 100644
index 000000000..8353fb27d
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook_payload_template/tasks/tests.yml
@@ -0,0 +1,270 @@
+# Test code for the Meraki modules
+# Copyright: (c) 2022, Joshua Coronado (@joshuajcoronado) <joshua@coronado.io>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: Block of tasks
+ block:
+ - name: Create network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_name: TestNet
+ state: present
+ type: wireless
+ register: new_net
+
+ - name: Set fact new_net
+ ansible.builtin.set_fact:
+ net: '{{ new_net.data.id }}'
+
+ - name: Query all webhook payload templates
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: query
+ register: query
+
+ - name: Assert query.data is defined
+ ansible.builtin.assert:
+ that:
+ - query.data is defined
+ - query.data | length > 1
+
+ - name: Create payload template with check mode
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ name: TestTemplate
+ body: "a fake body"
+ check_mode: yes
+ register: create_one_check
+
+ - ansible.builtin.debug:
+ var: create_one_check
+
+ - ansible.builtin.assert:
+ that:
+ - create_one_check is changed
+ - create_one_check.data is defined
+
+ - name: Create webhook payload template
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ name: TestTemplate
+ body: "a fake body"
+ register: basic_template
+
+ - name: Debug basic_template
+ ansible.builtin.debug:
+ var: basic_template
+
+ - name: Assert things were changed
+ ansible.builtin.assert:
+ that:
+ - basic_template.changed
+
+ - name: Query basic template
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: query
+ name: TestTemplate
+ register: query
+
+ - name: Debug query
+ ansible.builtin.debug:
+ var: query
+
+ - name: Assert basic template was created
+ ansible.builtin.assert:
+ that:
+ - query.data is defined
+ - not query.changed
+ - query.data.body == "a fake body"
+
+ - name: Update webhook payload template
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ name: TestTemplate
+ body: "a fake body"
+ headers:
+ - name: header1
+ template: "fake header1"
+ register: update_basic_template
+
+ - name: Debug update_basic_template
+ ansible.builtin.debug:
+ var: update_basic_template
+
+ - name: Assert update happened
+ ansible.builtin.assert:
+ that:
+ - update_basic_template.data is defined
+ - update_basic_template.changed
+ - update_basic_template.data.headers | length == 1
+
+ - name: Test idempotency by updating webhook payload template
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ name: TestTemplate
+ body: "a fake body"
+ headers:
+ - name: header1
+ template: "fake header1"
+ register: idempotent_update_basic_template
+
+ - name: Debug update_basic_template
+ ansible.builtin.debug:
+ var: idempotent_update_basic_template
+
+ - name: Assert nothing changed
+ ansible.builtin.assert:
+ that:
+ - not idempotent_update_basic_template.changed
+
+ - name: Delete webhook payload template in check mode
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: absent
+ name: TestTemplate
+ check_mode: yes
+ register: delete_check
+
+ - ansible.builtin.debug:
+ var: delete_check
+
+ - ansible.builtin.assert:
+ that:
+ - delete_check is changed
+
+ - name: Delete basic template
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: absent
+ name: TestTemplate
+ register: delete_template
+
+ - name: Debug delete_template
+ ansible.builtin.debug:
+ var: delete_template
+
+ - name: Assert the thing was deleted and changed
+ ansible.builtin.assert:
+ that:
+ - delete_template.changed
+
+ - name: Delete basic template idempotent
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: absent
+ name: TestTemplate
+ register: idempotent_delete_template
+
+ - name: Debug delete_template
+ ansible.builtin.debug:
+ var: idempotent_delete_template
+
+ - name: Assert nothing was changed
+ ansible.builtin.assert:
+ that:
+ - not idempotent_delete_template.changed
+
+ - name: Query deleted basic template
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: query
+ name: TestTemplate
+ register: deleted_template
+ ignore_errors: true
+
+ - name: Debug deleted_template
+ ansible.builtin.debug:
+ var: deleted_template
+
+ - name: Assert we hit an error
+ ansible.builtin.assert:
+ that:
+ - 'deleted_template.msg == "Unable to get webhook payload template named: TestTemplate"'
+
+ #########################################
+ ## Tests for argument completeness ##
+ #########################################
+ - name: Test body check
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ name: TestTemplate
+ register: error_no_body
+ ignore_errors: true
+
+ - name: Assert body is required when creating/updating a template
+ ansible.builtin.assert:
+ that:
+ - 'error_no_body.msg == "body is a required parameter when state is present"'
+
+ - name: Test name check
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ register: error_no_name
+ ignore_errors: true
+
+ - name: Assert name is required when creating/updating/deleting a template
+ ansible.builtin.assert:
+ that:
+ - 'error_no_name.msg == "name is a required parameter"'
+
+ - name: Test headers args
+ cisco.meraki.meraki_webhook_payload_template:
+ auth_key: '{{ auth_key }}'
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
+ state: present
+ name: Test
+ body: "fake body"
+ headers:
+ - name: test
+ register: header_args
+ ignore_errors: true
+
+ - name: Assert headers args are there
+ ansible.builtin.assert:
+ that:
+ - 'header_args.msg == "template in header must be a string"'
+
+ ############################################################################
+ # Tear down starts here
+ ############################################################################
+ always:
+
+ - name: Delete wireless network
+ cisco.meraki.meraki_network:
+ auth_key: '{{ auth_key }}'
+ state: absent
+ org_name: '{{ test_org_name }}'
+ net_id: '{{ net }}'
diff --git a/ansible_collections/cisco/meraki/tests/sanity/ignore-2.10.txt b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.10.txt
new file mode 100644
index 000000000..d6f190be0
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.10.txt
@@ -0,0 +1 @@
+plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:invalid-argument-name
diff --git a/ansible_collections/cisco/meraki/tests/sanity/ignore-2.11.txt b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.11.txt
new file mode 100644
index 000000000..0e9a3ead8
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.11.txt
@@ -0,0 +1,2 @@
+plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:invalid-argument-name
+plugins/modules/meraki_intrusion_prevention.py validate-modules:invalid-argument-name
diff --git a/ansible_collections/cisco/meraki/tests/sanity/ignore-2.12.txt b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.12.txt
new file mode 100644
index 000000000..d6f190be0
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.12.txt
@@ -0,0 +1 @@
+plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:invalid-argument-name
diff --git a/ansible_collections/cisco/meraki/tests/sanity/ignore-2.13.txt b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.13.txt
new file mode 100644
index 000000000..d6f190be0
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.13.txt
@@ -0,0 +1 @@
+plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:invalid-argument-name
diff --git a/ansible_collections/cisco/meraki/tests/sanity/ignore-2.14.txt b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.14.txt
new file mode 100644
index 000000000..d6f190be0
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.14.txt
@@ -0,0 +1 @@
+plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:invalid-argument-name
diff --git a/ansible_collections/cisco/meraki/tests/sanity/ignore-2.15.txt b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.15.txt
new file mode 100644
index 000000000..d6f190be0
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.15.txt
@@ -0,0 +1 @@
+plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:invalid-argument-name
diff --git a/ansible_collections/cisco/meraki/tests/sanity/ignore-2.9.txt b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.9.txt
new file mode 100644
index 000000000..00914499a
--- /dev/null
+++ b/ansible_collections/cisco/meraki/tests/sanity/ignore-2.9.txt
@@ -0,0 +1,2 @@
+plugins/modules/meraki_intrusion_prevention.py invalid-argument-name # ignore missing argument
+plugins/modules/meraki_intrusion_prevention.py nonexistent-parameter-documented # ignore missing argument