diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/cisco/meraki | |
parent | Initial commit. (diff) | |
download | ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/cisco/meraki')
140 files changed, 29621 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/CHANGELOG.md b/collections-debian-merged/ansible_collections/cisco/meraki/CHANGELOG.md new file mode 100644 index 00000000..87be8681 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/CHANGELOG.md @@ -0,0 +1,101 @@ +# Changelog + +## v1.3.1 + +### Bugfixes +* meraki_device - Rewrite module to be more reliable when hostname is specified +* meraki_admin - Fix crash when specifying networks parameter + +## v1.2.1 + +### Bugfixes +* meraki_site_to_site_vpn - Enable idempotency and changed statuses + +## v1.2.0 + +### New Modules +* meraki_ms_link_aggregation + +## v1.1.0 + +### New Modules +* meraki_management_interface +* meraki_mx_uplink +* meraki_site_to_site_vpn + +### New Modules +* meraki_management_interface + +### Features +* meraki_vlan - Add full DHCP server configuration support + +## Bugfixes +* meraki_mx_l3_firewall - Fix idempotency when 'any' is passed as a parameter + +## v1.0.3 + +### Miscellaneous +* Remove accidentally added meraki_site_to_site_vpn module which shouldn't have been published + +## v1.0.2 + +### Bugfixes +* meraki_static_route - Fix idempotency bugs triggered with certain parameters +* meraki_mx_l3_firewall - Remove unnecessary org lookup which may crash + +## v1.0.1 + +### Bugfixes +* meraki_mx_l3_firewall - Fix condition where firewall rules wouldn't update + +## v1.0.0 + +### Enhancements +* meraki_organzation - A confirmation is needed to delete an organization since it can be a catastrophic change +* meraki_network - Add support for check mode +* meraki_mr_l3_firewall - Add check mode +* meraki_mx_l3_firewall - Add check mode +* meraki_ssid - Add support for check mode +* meraki_switchport - Add check mode +* Add template for inventory.networking + +### New Modules +* meraki_intrusion_prevention + +## v0.1.1 + +### Bugfixes +** Fix some sanity errors + +## v0.1.0 + +### Enhancements +* diff generation now uses a centralized method instead of per module code + +### Bugfixes +* diff now returns as snakecase instead of camelcase +* Modules which don't support check mode will now error if check mode is requested + + +## v0.0.1 + +### New Modules +* meraki_switch_storm_control + +### Documentation +* Improve type documentation for module parameters +* Improve HTTP error reporting for 400 errors + +### Bugfixes +* meraki_ssid - Properly formats the walled garden payload to Meraki +* Fix most linting errors (issue #13) +* Restructure tests directory +* Fix comparison check to not crash when comparing a dict to a non-dict (issue #6) +* Enable integration tests within collection +* Fix linting errors +* Fix crash when unbinding a template in check mode with net_id (issue #19) +* meraki_firewalled_services recognizes net_id +* Content filtering integration test deletes network after completion + +## v0.0.0 +* Initial commit of collection into Ansible Galaxy diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/FILES.json b/collections-debian-merged/ansible_collections/cisco/meraki/FILES.json new file mode 100644 index 00000000..7fe16085 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/FILES.json @@ -0,0 +1,1594 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "CHANGELOG.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c83c95fb547a78342f87bac741a721df22149a61856afcdded44d90054fdaec1", + "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/action", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/inventory", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "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": "905531f7d6139bb858aa2bca096dd469e274a7635416bc8dbfc9dbd57063699d", + "format": 1 + }, + { + "name": "plugins/filter", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "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_mr_settings.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cd379307311c8e6f95476bd2ed51129a1a9cbce9f1b9d1d1b1e1623daf4bb244", + "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": "8efc38d649629e8c5c32787237d3e94d6fac00889dbf3cfe0112920f8a9b7a4f", + "format": 1 + }, + { + "name": "plugins/modules/meraki_ms_ospf.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "def1149cc70ceb281e3ed3dc2ed41ca6cd1e71a0d4ec97b8e5d17773e163a73a", + "format": 1 + }, + { + "name": "plugins/modules/meraki_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cd61c9c511dd88eb90dc8a223a1dc81a1f4f620443cd106e3223b192d9c0a992", + "format": 1 + }, + { + "name": "plugins/modules/meraki_content_filtering.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7f69e043fec7f62efa5b3c7c47003c6c958fefcec67e3849fd3df66861ed5193", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_site_to_site_vpn.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9b23b84064ebe3f7920c67db264514a9f1f68cd145ebabd5ddbcacb76c84da02", + "format": 1 + }, + { + "name": "plugins/modules/meraki_ms_switchport.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af59fa3575c0e71e7849821baffe9513836401046ec32a8cc5bcca633408f2e7", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mr_l3_firewall.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d9cb2ad75cf6686a7bd28f0ee500c8367c20f5f04e8c7830523cc3cbecb0bf90", + "format": 1 + }, + { + "name": "plugins/modules/meraki_switchport.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af59fa3575c0e71e7849821baffe9513836401046ec32a8cc5bcca633408f2e7", + "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_site_to_site_vpn.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9b23b84064ebe3f7920c67db264514a9f1f68cd145ebabd5ddbcacb76c84da02", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_l3_firewall.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "488f84bfd1aa9e7e20c196ccf54328fe07c4870692b8af66217bebfe78caa57d", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_content_filtering.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7f69e043fec7f62efa5b3c7c47003c6c958fefcec67e3849fd3df66861ed5193", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_uplink_bandwidth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e29a7945e7b10813907279a8dbd3339f7587e0832f98020f24be9ab72942f3c", + "format": 1 + }, + { + "name": "plugins/modules/meraki_vlan.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5fd455b481345d9f72d54413b359e930bf503dcfc79c9786a7412be3d1068880", + "format": 1 + }, + { + "name": "plugins/modules/meraki_nat.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6c5169c46925e2b277c46180f28196b0f9c2b69336af33b85ac925c3b5b4f97d", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mr_ssid.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "59e7e02a397282220190241c018c22be5b128950c4c3ba9ac4985160db3f3636", + "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": "27df2cb35b32c28ea4210d6538ce21959df46edb23f0f74eefdd2ff83bae9201", + "format": 1 + }, + { + "name": "plugins/modules/meraki_static_route.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "592d352c99d01cf25c8f99888186476c7a12002fc573d07011daee7b976a5645", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mr_rf_profile.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ae1fa0cc6f98d3075bcebd68f3a64145b77eecb6c7cefb4b44ea9165b4b4cf1e", + "format": 1 + }, + { + "name": "plugins/modules/meraki_switch_storm_control.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a46f5a974df824bb1f719940c0528ede43ccecaba64fbf50bdb3375413850d57", + "format": 1 + }, + { + "name": "plugins/modules/meraki_switch_stack.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4b69c1ff83701c17956c9e4bd7587df0efb58e6709294a1c2ad11775eadd6599", + "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": "5fd455b481345d9f72d54413b359e930bf503dcfc79c9786a7412be3d1068880", + "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": "ba4c8a9de8457e845400412755e8911045369daa7abdaf4d1cb3d4583d78f03e", + "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": "dcb05aa9c1bd4a4abf88fdfa9192f16bed91cc63aa8eb2d7e6fee96bf01b7cc1", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_nat.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6c5169c46925e2b277c46180f28196b0f9c2b69336af33b85ac925c3b5b4f97d", + "format": 1 + }, + { + "name": "plugins/modules/meraki_intrusion_prevention.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27df2cb35b32c28ea4210d6538ce21959df46edb23f0f74eefdd2ff83bae9201", + "format": 1 + }, + { + "name": "plugins/modules/meraki_management_interface.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b9ac21a825768548919f086c14f49e4073199f9f3f1c971370cac6007c345961", + "format": 1 + }, + { + "name": "plugins/modules/meraki_alert.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e2e47b4839ce1c567c86a94ebc68d50b2e0e27805a10d7c08f3c86babdfdd784", + "format": 1 + }, + { + "name": "plugins/modules/meraki_ssid.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "59e7e02a397282220190241c018c22be5b128950c4c3ba9ac4985160db3f3636", + "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_switch_access_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8de5fc940e2c036bbffbc5f5fb818066ae1ed7cc3b9c6d5f720d861dfd7a863", + "format": 1 + }, + { + "name": "plugins/modules/meraki_ms_stack.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4b69c1ff83701c17956c9e4bd7587df0efb58e6709294a1c2ad11775eadd6599", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_l7_firewall.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "189278d6458dd473653ec7a5b213e4ab26ea607f40445e87291fb17e65b2c076", + "format": 1 + }, + { + "name": "plugins/modules/meraki_malware.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fbe45461689899351b41f387d612000f0ebe03e9666537a1abcc16d2831a9351", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_uplink.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e29a7945e7b10813907279a8dbd3339f7587e0832f98020f24be9ab72942f3c", + "format": 1 + }, + { + "name": "plugins/modules/meraki_syslog.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "db74a7554b581166b2820a415444643c69a729ec8bf8223d17640d0f086f6f8c", + "format": 1 + }, + { + "name": "plugins/modules/meraki_mx_static_route.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "592d352c99d01cf25c8f99888186476c7a12002fc573d07011daee7b976a5645", + "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/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_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": "0bd45ebf7ef1554f5d8d42a6f93b1471813480ea4a806b61c582327cd719dacb", + "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": "e077a449cf928a4baf84344e39d4e71540186f245849a75e6551791526dd6d96", + "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_auth_user", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_nat", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_nat/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_nat/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "efa894c594a21000062c96fc37d06a7f545ade22c3f3068f28d2ebb477765890", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_nat/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7d721fb7675ced2f9f23e2b731c75c625e3d550de22eed6a6e188fb8bcc20591", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_nat/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_malware", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_malware/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_malware/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ea3aa66ab9f472e5a48fc0285d52cd41baa5daa9c8b2559996b3ae10020860c2", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_malware/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "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": "396d3b72bd49936caeff21f9f5863943365b4cf422db0ac73db7b565e517bfbb", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_syslog/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_mx_uplink", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_mx_uplink/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_mx_uplink/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9d4abc0313199c76186bb2b4494019521d34531934cf2400edcb1511a703ddb1", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_mx_uplink/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_site_to_site_vpn", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_site_to_site_vpn/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_site_to_site_vpn/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ca03fd940cd053bb8b79f6d2c9677448d3aa55ac99b74063606d4a396e5815c2", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_site_to_site_vpn/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_ms_static_route", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "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": "2acb8ab430d12a826537a3e3f8acbd1d1d60b16f73a0136de2939d6ccd956684", + "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": "dd291984ad73a82bbef5e1cb490e75a109992535e6618349002ac83009453f7e", + "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_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_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": "264ac4e354d4f5f7c45a652937ea1fea7111c406c851573f2b24ce1edffb0be0", + "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": "aec1451cd54fc2d2ff8f5f37ef4d583c76dafd7df45bdb06398350c80df32091", + "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": "f7778b3c15b3b552efbabd5e1d762541ce7e2a4171e9d5562b093c181517e344", + "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": "1fe0fde3236e8e0fe832eb3ff51e4cfc9b3a7da727def5b1b0e48cd0d09f7aac", + "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": "534c208490c7f02c8d3bd63e240cef528bb606285542825ec298daddcdfb96f4", + "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_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_switchport", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switchport/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switchport/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a42ba87d0fb00912ebe929ab1c003dca4fb8f679356795a6bc33b25703e6d0c", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switchport/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_static_route", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_static_route/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_static_route/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f5c658d00c7ac906a17f450a9417aa0ff575af1c211a6b36ee3f128b16a13177", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_static_route/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_storm_control", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_storm_control/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_storm_control/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "66e4a90781fc59e9db5d6003754cb727f843f87fc17b0e4d60f5d3766d14a155", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_storm_control/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": "f227a0e44f07b10893e4b7476ba8f02e830103c546e3a10c052faea44132dc11", + "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_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": "9f03a8444ca9c18df03441f4e043a0b4fefe7a144695cba1615b76506f3bbf4d", + "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": "88e8cdc137a58234a4ae6687871a67f2fbcfb5035db5b005026775af89418210", + "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_switch_access_list", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_access_list/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_access_list/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5da21e4a9a67b88be6d4f6f0def23e3506c53ee69d44d96138624b703b026645", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_access_list/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "53fa9852c9f50ce3ef61cdbfbd87831e062cfd27869b6607c27c7a41225148a7", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_access_list/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": "5eb485ea77acb835322cb954909342675e5878316e4781b84fff0c0b2408cc5b", + "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": "b031201c016a6706efdadbb335dc8a3206de12e19cf8fd13a3019c555136347a", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_device/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60945d49535300be8e42108658dba31fcd5d665fc40d6f186798e7e0682320ae", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_intrusion_prevention", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_intrusion_prevention/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_intrusion_prevention/tasks/tests.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6d4b7af6eed8d499a60504e8fa4bccbc1ca43a246d6a57317cdcc630fc15a7c5", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_intrusion_prevention/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "53fa9852c9f50ce3ef61cdbfbd87831e062cfd27869b6607c27c7a41225148a7", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_intrusion_prevention/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": "ca7817bbb168bcaac393b58d656fe042a8b8d7cac03406308c17886fe602b4e5", + "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_switch_stack", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_stack/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_stack/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cb6fc23b6d89d0a10d49c6cc00c7594884cb4b0cfe100312058156dedd4d9d96", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_switch_stack/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "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": "df7c05d463fd653894e69eb0a96898c53b13f0b5978705750b172210e7b98918", + "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_content_filtering", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_content_filtering/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_content_filtering/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7d5668f94f2c547c1069abcb680c90d8b738db067a5359611f34d01608de1444", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_content_filtering/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "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/meraki_network", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_ms_link_aggregation/meraki_network/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_ms_link_aggregation/meraki_network/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "54d47981fa1b84efbdf3aab9b2dc728b66c7534ecf913b16b63953ae4621c96f", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_ms_link_aggregation/meraki_network/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "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": "1637bcfe452de66b0d7f54e541fcd5a8e12c116bf13dabcfae23baf2c116a01f", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_ms_link_aggregation/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f13674351f8231c9e60fc923652b1ce94b62357ee00fedaf23ad3af677aff656", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_vlan", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_vlan/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_vlan/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "48618057e0b60e10fbabea94ed357002f581400bf2fd9e4423a8290f3b21dcc8", + "format": 1 + }, + { + "name": "tests/integration/targets/meraki_vlan/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.11.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b6aae5e472e01a9829db003c84a4f5ecd68431996f6a0743640e7fa8b8753ea5", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.10.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bbb19107052bb4115830018b69467adc94fdc34e21bb44aa25e6a75647df4687", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.9.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "014a8541fd8acfd7617be7ec00780bd196fb955936f8ffb5077edc7582fdc61e", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8d2d74737a0995d2004ac140be5c94cc8e120c98b32bf00744c7d5508419d2e9", + "format": 1 + }, + { + "name": "changelogs/.plugin-cache.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9e1f01774da475c03b643a81b0fb1c288776329729609b768259dcd0d2f741a6", + "format": 1 + }, + { + "name": "changelogs/CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f42d5eca0a08b0d1cea0432954f5bb1e30b54f0d2ab6c5ee2f524bb12e492197", + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4394bcef845d9c0b5d31c0de9abb3bd0c1f6e850723ab72afc671f62ea291d88", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "237d6096aa38511049994468eb8b4001b0f9d3aec0a862dcb361d1a18bdc97f7", + "format": 1 + }, + { + "name": "bugfix", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/MANIFEST.json b/collections-debian-merged/ansible_collections/cisco/meraki/MANIFEST.json new file mode 100644 index 00000000..6760b338 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/MANIFEST.json @@ -0,0 +1,36 @@ +{ + "collection_info": { + "namespace": "cisco", + "name": "meraki", + "version": "2.2.0", + "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": "21c75b35ac7d0c6dd953a97de11aa76cebcf0a7e09e6985a1ad20568db0f0b0b", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/README.md b/collections-debian-merged/ansible_collections/cisco/meraki/README.md new file mode 100644 index 00000000..fdfb88ee --- /dev/null +++ b/collections-debian-merged/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.9 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/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/.plugin-cache.yaml b/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/.plugin-cache.yaml new file mode 100644 index 00000000..dd6fad55 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/.plugin-cache.yaml @@ -0,0 +1,185 @@ +plugins: + become: {} + cache: {} + callback: {} + cliconf: {} + connection: {} + httpapi: {} + inventory: {} + lookup: {} + module: + 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_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_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_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_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_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_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 + netconf: {} + shell: {} + strategy: {} + vars: {} +version: 2.2.0 diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/CHANGELOG.rst b/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/CHANGELOG.rst new file mode 100644 index 00000000..57e43bcb --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/CHANGELOG.rst @@ -0,0 +1,116 @@ +========================== +Cisco.Meraki Release Notes +========================== + +.. contents:: Topics + + +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/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/changelog.yaml b/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/changelog.yaml new file mode 100644 index 00000000..5d955614 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/changelog.yaml @@ -0,0 +1,138 @@ +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.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' diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/config.yaml b/collections-debian-merged/ansible_collections/cisco/meraki/changelogs/config.yaml new file mode 100644 index 00000000..99379224 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/plugins/doc_fragments/meraki.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/doc_fragments/meraki.py new file mode 100644 index 00000000..d6d456f1 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py new file mode 100644 index 00000000..53b14caf --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py @@ -0,0 +1,517 @@ +# -*- 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: + new = {self.key_map[k]: data[k]} + 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, debug=False): + ''' Compare two data-structures ''' + self.ignored_keys.append('net_id') + 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'] + data = json.loads(to_native(resp.read())) + 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']) + data.extend(json.loads(to_native(resp.read()))) + 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 {} + + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/__init__.py diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_admin.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_admin.py new file mode 100644 index 00000000..e554bb00 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py new file mode 100644 index 00000000..481d8652 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py @@ -0,0 +1,337 @@ +#!/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 + 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: + - type: "gatewayDown" + enabled: yes + filters: + timeout: 60 + alert_destinations: + emails: + - 'youremail@yourcorp' + - 'youremail2@yourcorp' + all_admins: yes + snmp: no + - 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: + 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 construct_payload(meraki, original): + payload = copy.deepcopy(original) + if meraki.params['default_destinations'] is not None: + payload['defaultDestinations'].update(meraki.params['default_destinations']) + payload['defaultDestinations']['allAdmins'] = meraki.params['default_destinations']['all_admins'] + del payload['defaultDestinations']['all_admins'] + del payload['defaultDestinations']['http_server_ids'] + if meraki.params['alerts'] is not None: + for alert in meraki.params['alerts']: + alert.update(meraki.convert_snake_to_camel(alert)) + del alert['alert_destinations'] + for alert_want in meraki.params['alerts']: + for alert_have in payload['alerts']: + if alert_want['alert_type'] == alert_have['type']: + alert_have.update(alert_want) + del alert_have['alert_type'] + del alert_have['alertType'] + 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', default=[]), + ) + + 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) + # meraki.fail_json(msg="Compare", original=original, payload=payload) + # meraki.fail_json(msg=payload) + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_config_template.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_config_template.py new file mode 100644 index 00000000..8438d41b --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_content_filtering.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_content_filtering.py new file mode 100644 index 00000000..5bc6b934 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_content_filtering.py @@ -0,0 +1,282 @@ +#!/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 + + +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']: + payload['allowedUrlPatterns'] = meraki.params['allowed_urls'] + if meraki.params['blocked_urls']: + payload['blockedUrlPatterns'] = meraki.params['blocked_urls'] + if meraki.params['blocked_categories']: + if len(meraki.params['blocked_categories']) == 0: # 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) + if meraki.is_update_required(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 + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_device.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_device.py new file mode 100644 index 00000000..ddbd0301 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_device.py @@ -0,0 +1,432 @@ +#!/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_uplink_urls = {'device': '/networks/{net_id}/devices/{serial}/uplink'} + query_device_lldp_urls = {'device': '/networks/{net_id}/devices/{serial}/lldp_cdp'} + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_firewalled_services.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_firewalled_services.py new file mode 100644 index 00000000..ec78c068 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_intrusion_prevention.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_intrusion_prevention.py new file mode 100644 index 00000000..c5d0213c --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_intrusion_prevention.py @@ -0,0 +1,366 @@ +#!/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 + message: + description: + - Description of rule. + - This is overwritten by the API. + type: str + 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" + 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" + 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', + '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'), + message=dict(type='str'), + ) + + 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['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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_malware.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_malware.py new file mode 100644 index 00000000..1cbf7e68 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_management_interface.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_management_interface.py new file mode 100644 index 00000000..c0297861 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py new file mode 100644 index 00000000..274dbb15 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py @@ -0,0 +1,288 @@ +#!/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 response + + +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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py new file mode 100644 index 00000000..3a31e825 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py @@ -0,0 +1,663 @@ +#!/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.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 +from re import sub + + +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']) + + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py new file mode 100644 index 00000000..0d0c8897 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py @@ -0,0 +1,222 @@ +#!/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.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 +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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py new file mode 100644 index 00000000..c9101290 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py @@ -0,0 +1,614 @@ +#!/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_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'] + use_vlan_tagging: + description: + - Set whether to use VLAN tagging. + - Requires C(default_vlan_id) to be set. + 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 + 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 +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 + 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', + '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', + 'concentratorNetworkId': 'concentrator_network_id', + 'vlanId': 'vlan_id', + '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', + } + + 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_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'), + 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'), + ) + + # 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.fails_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") + + # 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: + ssid_id = get_available_number(ssids) + if ssid_id is False: + meraki.fail_json(msg='No SSID found by specified name and no number was referenced.') + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_list.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_list.py new file mode 100644 index 00000000..bd5e9205 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_l3_interface.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_l3_interface.py new file mode 100644 index 00000000..716ec8d9 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_link_aggregation.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_link_aggregation.py new file mode 100644 index 00000000..a38eda7d --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py new file mode 100644 index 00000000..f071b3f8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py @@ -0,0 +1,322 @@ +#!/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) + 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), + ) + + # 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py new file mode 100644 index 00000000..fb0729a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py @@ -0,0 +1,277 @@ +#!/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: + payload = {'serial': meraki.params['serials'][0]} + original = get_stack(stack_id, stacks) + comparable = deepcopy(original) + comparable.update(payload) + if meraki.params['serials'][0] in comparable['serials']: + comparable['serials'].remove(meraki.params['serials'][0]) + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_storm_control.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_storm_control.py new file mode 100644 index 00000000..2048ad5e --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py new file mode 100644 index 00000000..f119c71a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py @@ -0,0 +1,424 @@ +#!/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 whitelist, Sticky MAC whitelist] + 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, 100Megabit (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. + type: int + voice_vlan: + description: + - VLAN number assigned to a port for voice traffic. + - Only applicable to access port type. + type: int + +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 +''' + +RETURN = r''' +data: + description: Information queried or updated switchports. + returned: success + type: complex + contains: + 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" +''' + +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', + } + + +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 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'), + 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'), + 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 whitelist', 'Sticky MAC whitelist']), + access_policy_number=dict(type='int'), + link_negotiation=dict(type='str', + choices=['Auto negotiate', '100Megabit (auto)', '100 Megabit full duplex (forced)'], + default='Auto negotiate'), + ) + + # 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') + 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 + + proposed = payload.copy() + query_path = meraki.construct_path('get_one', custom={'serial': meraki.params['serial'], + 'number': meraki.params['number'], + }) + original = meraki.request(query_path, method='GET') + 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'], + }) + # meraki.fail_json(msg=payload) + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py new file mode 100644 index 00000000..5bc6b934 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py @@ -0,0 +1,282 @@ +#!/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 + + +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']: + payload['allowedUrlPatterns'] = meraki.params['allowed_urls'] + if meraki.params['blocked_urls']: + payload['blockedUrlPatterns'] = meraki.params['blocked_urls'] + if meraki.params['blocked_categories']: + if len(meraki.params['blocked_categories']) == 0: # 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) + if meraki.is_update_required(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 + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py new file mode 100644 index 00000000..c5d0213c --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py @@ -0,0 +1,366 @@ +#!/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 + message: + description: + - Description of rule. + - This is overwritten by the API. + type: str + 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" + 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" + 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', + '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'), + message=dict(type='str'), + ) + + 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['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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py new file mode 100644 index 00000000..65944b87 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py @@ -0,0 +1,273 @@ +#!/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.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(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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py new file mode 100644 index 00000000..b9b1e9b0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py @@ -0,0 +1,365 @@ +#!/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: permit + 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 response + + +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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py new file mode 100644 index 00000000..31c9af1c --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py @@ -0,0 +1,479 @@ +#!/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 rename_id_to_appid(rules): + for rule in rules['rules']: + if rule['type'] == 'application' or rule['type'] == 'applicationCategory': + rule['value']['appId'] = rule['value'].pop('id') + return rules + + +def rename_appid_to_id(rules): + for rule in rules['rules']: + if rule['type'] == 'application' or rule['type'] == 'applicationCategory': + rule['value']['id'] = rule['value'].pop('appId') + return rules + + +def main(): + # define the available arguments/parameters that a user can pass to + # the module + + application_arg_spec = dict(id=dict(type='str'), + name=dict(type='str'), + ) + + rule_arg_spec = dict(policy=dict(type='str', choices=['deny'], default='deny'), + type=dict(type='str', choices=['application', + 'application_category', + '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) + if meraki.params['rules']: + payload = {'rules': []} + for rule in meraki.params['rules']: + payload['rules'].append(assemble_payload(meraki, net_id, rule)) + else: + payload = dict() + + ''' + The rename_* functions are needed because the key is id and + is_update_required() by default ignores id. + ''' + rules = rename_id_to_appid(rules) + payload = rename_id_to_appid(payload) + if meraki.is_update_required(rules, payload): + rules = rename_appid_to_id(rules) + payload = rename_appid_to_id(payload) + if meraki.module.check_mode is True: + response = restructure_response(payload) + 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: + rules = rename_appid_to_id(rules) + payload = rename_appid_to_id(payload) + if meraki.module.check_mode is True: + meraki.result['data'] = rules + meraki.result['changed'] = False + meraki.exit_json(**meraki.result) + meraki.result['data'] = payload + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + meraki.exit_json(**meraki.result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_malware.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_malware.py new file mode 100644 index 00000000..1cbf7e68 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_nat.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_nat.py new file mode 100644 index 00000000..0844d4c1 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_firewall.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_firewall.py new file mode 100644 index 00000000..f81ac3a3 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py new file mode 100644 index 00000000..57a7211d --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py @@ -0,0 +1,250 @@ +#!/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: 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): + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py new file mode 100644 index 00000000..927d4230 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py @@ -0,0 +1,392 @@ +#!/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 + 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'), + ) + + # 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'] + + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink.py new file mode 100644 index 00000000..fd66fc9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink.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(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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink_bandwidth.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink_bandwidth.py new file mode 100644 index 00000000..fd66fc9b --- /dev/null +++ b/collections-debian-merged/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(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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py new file mode 100644 index 00000000..92a608fe --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py @@ -0,0 +1,583 @@ +#!/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_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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_nat.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_nat.py new file mode 100644 index 00000000..0844d4c1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_network.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_network.py new file mode 100644 index 00000000..af583f0a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_network.py @@ -0,0 +1,412 @@ +#!/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 ] + 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: > + - 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 + +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: 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'], 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'), + ) + + # 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 True and meraki.params['remote_status_page_enabled'] is False: + 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'] + + # 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'], + meraki.params['net_name'], + data=nets + ) + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_organization.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_organization.py new file mode 100644 index 00000000..45148e89 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_site_to_site_vpn.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_site_to_site_vpn.py new file mode 100644 index 00000000..57a7211d --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_site_to_site_vpn.py @@ -0,0 +1,250 @@ +#!/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: 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): + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_snmp.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_snmp.py new file mode 100644 index 00000000..0240a168 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ssid.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ssid.py new file mode 100644 index 00000000..c9101290 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ssid.py @@ -0,0 +1,614 @@ +#!/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_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'] + use_vlan_tagging: + description: + - Set whether to use VLAN tagging. + - Requires C(default_vlan_id) to be set. + 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 + 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 +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 + 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', + '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', + 'concentratorNetworkId': 'concentrator_network_id', + 'vlanId': 'vlan_id', + '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', + } + + 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_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'), + 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'), + ) + + # 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.fails_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") + + # 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: + ssid_id = get_available_number(ssids) + if ssid_id is False: + meraki.fail_json(msg='No SSID found by specified name and no number was referenced.') + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_static_route.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_static_route.py new file mode 100644 index 00000000..927d4230 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_static_route.py @@ -0,0 +1,392 @@ +#!/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 + 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'), + ) + + # 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'] + + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_access_list.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_access_list.py new file mode 100644 index 00000000..bd5e9205 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_stack.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_stack.py new file mode 100644 index 00000000..fb0729a1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_stack.py @@ -0,0 +1,277 @@ +#!/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: + payload = {'serial': meraki.params['serials'][0]} + original = get_stack(stack_id, stacks) + comparable = deepcopy(original) + comparable.update(payload) + if meraki.params['serials'][0] in comparable['serials']: + comparable['serials'].remove(meraki.params['serials'][0]) + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_storm_control.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_storm_control.py new file mode 100644 index 00000000..2048ad5e --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switchport.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switchport.py new file mode 100644 index 00000000..f119c71a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switchport.py @@ -0,0 +1,424 @@ +#!/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 whitelist, Sticky MAC whitelist] + 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, 100Megabit (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. + type: int + voice_vlan: + description: + - VLAN number assigned to a port for voice traffic. + - Only applicable to access port type. + type: int + +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 +''' + +RETURN = r''' +data: + description: Information queried or updated switchports. + returned: success + type: complex + contains: + 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" +''' + +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', + } + + +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 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'), + 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'), + 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 whitelist', 'Sticky MAC whitelist']), + access_policy_number=dict(type='int'), + link_negotiation=dict(type='str', + choices=['Auto negotiate', '100Megabit (auto)', '100 Megabit full duplex (forced)'], + default='Auto negotiate'), + ) + + # 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') + 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 + + proposed = payload.copy() + query_path = meraki.construct_path('get_one', custom={'serial': meraki.params['serial'], + 'number': meraki.params['number'], + }) + original = meraki.request(query_path, method='GET') + 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'], + }) + # meraki.fail_json(msg=payload) + 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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py new file mode 100644 index 00000000..7304199e --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py @@ -0,0 +1,260 @@ +#!/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: ['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 + status: 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 + status: query + 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 + status: query + 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 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 + + server_arg_spec = dict(host=dict(type='str'), + port=dict(type='int', default="514"), + roles=dict(type='list', elements='str', choices=['Wireless Event log', + 'Appliance event log', + 'Switch event log', + 'Air Marshal events', + 'Flows', + 'URLs', + 'IDS alerts', + 'Security events', + ]), + ) + + 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': + # 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 + + 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('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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_vlan.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_vlan.py new file mode 100644 index 00000000..92a608fe --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_vlan.py @@ -0,0 +1,583 @@ +#!/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_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/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py new file mode 100644 index 00000000..ec5574ad --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py @@ -0,0 +1,347 @@ +#!/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 + 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 + 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 +''' + +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: + meraki.result['data'][0]['shared_secret'] = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER' + except KeyError: + pass + try: + meraki.result['data']['shared_secret'] = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER' + except (KeyError, TypeError) as e: + pass + + +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'), + ) + + # 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}'} + + 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 + + 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) + 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'], + 'sharedSecret': meraki.params['shared_secret']} + + 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: + 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/inventory.networking b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/inventory.networking new file mode 100644 index 00000000..ab895895 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/inventory.networking.template b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/inventory.networking.template new file mode 100644 index 00000000..3057b7f2 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/target-prefixes.network b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/target-prefixes.network new file mode 100644 index 00000000..2516cd48 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/target-prefixes.network @@ -0,0 +1 @@ +meraki diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/tasks/main.yml new file mode 100644 index 00000000..336f9e4f --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_admin/tasks/main.yml @@ -0,0 +1,404 @@ +# 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 new administrator in check mode + 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: yes + register: create_org_check + + - name: Create new admin check mode assertion + assert: + that: + - create_org_check is changed + - 'create_org_check.data.name == "Jane Doe"' + + - name: Create new administrator + 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 + assert: + that: + - create_orgaccess.changed == true + - 'create_orgaccess.data.name == "Jane Doe"' + + - name: Delete recently created administrator with check mode + 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: yes + + - assert: + that: + - delete_one_check is changed + + - name: Delete recently created administrator + 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 + 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 + assert: + that: + - create_orgaccess_id.changed == true + - 'create_orgaccess_id.data.name == "Jane Doe"' + + - name: Create administrator with tags with check mode + 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: yes + + - 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 + 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 + + - assert: + that: + - create_tags.changed == true + - create_tags.data.name == "John Doe" + - create_tags.data.tags | length == 2 + + - name: Create administrator with invalid tags + 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 + ignore_errors: yes + + - assert: + that: + - '"400" in create_tags_invalid.msg' + - '"must be one of" in create_tags_invalid.msg' + + - name: Create administrator with invalid tag permission + 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 + ignore_errors: yes + + - 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 + meraki_network: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: '{{item}}' + type: switch + register: new_networks + loop: + - TestNet + - TestNet2 + + - 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 + 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: yes + + - 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 + 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 + + - assert: + that: + - create_network.changed == true + - create_network.data.name == "Jim Doe" + - create_network.data.networks | length == 2 + + - name: Update administrator with check mode + 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: yes + + - debug: + var: update_network_check + + - 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 + 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 + + - 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 + 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: yes + + - debug: + var: update_network_idempotent_check + + - assert: + that: + - update_network_idempotent_check is not changed + + - name: Update administrator for idempotency + 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 + + - assert: + that: + - update_network_idempotent.changed == false + - update_network_idempotent.data is defined + + - name: Create administrator with invalid network + 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 + ignore_errors: yes + + - assert: + that: + - '"No network found with the name" in create_network_invalid.msg' + # - '"400" in create_network_invalid.msg' + + - name: Query all administrators + meraki_admin: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + delegate_to: localhost + register: query_all + + - debug: + var: query_all + + - assert: + that: + - query_all.data | length == 4 + - query_all.changed == False + + - name: Query admin by name + 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 + 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 + + - 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 + meraki_admin: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + email: '{{item}}' + delegate_to: localhost + register: delete_all + ignore_errors: yes + loop: + - '{{email_prefix}}+janedoe@{{email_domain}}' + - '{{email_prefix}}+johndoe@{{email_domain}}' + - '{{email_prefix}}+jimdoe@{{email_domain}}' + + - name: Query all administrators + meraki_admin: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + delegate_to: localhost + register: query_all_deleted + + - assert: + that: + - query_all_deleted.data | length == 1
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/aliases new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/aliases diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/tasks/main.yml new file mode 100644 index 00000000..baf2a081 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_alert/tasks/main.yml @@ -0,0 +1,174 @@ +# 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 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 + + - name: Update settings with check mode + 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: no + snmp: no + alerts: + - alert_type: "gatewayDown" + enabled: yes + filters: + timeout: 60 + alert_destinations: + emails: + - '{{ email_prefix }}@{{ email_domain }}' + - '{{ email_prefix }}2@{{ email_domain }}' + all_admins: no + snmp: no + - alert_type: "usageAlert" + enabled: yes + filters: + period: 1200 + threshold: 104857600 + alert_destinations: + emails: + - '{{ email_prefix }}@{{ email_domain }}' + - '{{ email_prefix }}2@{{ email_domain }}' + all_admins: yes + snmp: no + delegate_to: localhost + register: update_check + check_mode: yes + + - assert: + that: + - update_check.data is defined + - update_check.diff is defined + - update_check is changed + + - name: Update settings + 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: no + snmp: no + alerts: + - alert_type: "gatewayDown" + enabled: yes + filters: + timeout: 60 + alert_destinations: + emails: + - '{{ email_prefix }}@{{ email_domain }}' + - '{{ email_prefix }}2@{{ email_domain }}' + all_admins: no + snmp: no + - alert_type: "usageAlert" + enabled: yes + filters: + period: 1200 + threshold: 104857600 + alert_destinations: + emails: + - '{{ email_prefix }}@{{ email_domain }}' + - '{{ email_prefix }}2@{{ email_domain }}' + all_admins: yes + snmp: no + delegate_to: localhost + register: update + + - assert: + that: + - update.data is defined + - update.diff is defined + - update is changed + + - name: Update settings idempotent + 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: no + snmp: no + alerts: + - alert_type: "gatewayDown" + enabled: yes + filters: + timeout: 60 + alert_destinations: + emails: + - '{{ email_prefix }}@{{ email_domain }}' + - '{{ email_prefix }}2@{{ email_domain }}' + all_admins: no + snmp: no + - alert_type: "usageAlert" + enabled: yes + filters: + period: 1200 + threshold: 104857600 + alert_destinations: + emails: + - '{{ email_prefix }}@{{ email_domain }}' + - '{{ email_prefix }}2@{{ email_domain }}' + all_admins: yes + snmp: no + delegate_to: localhost + register: update_idempotent + + - assert: + that: + - update_idempotent.data is defined + - update_idempotent is not changed + + - name: Query all settings + meraki_alert: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: IntTestNetworkWireless + state: query + delegate_to: localhost + register: query_all + + - debug: + var: query_all + + - assert: + that: + - query_all.data is defined + + +############################################################################# +# Tear down starts here +############################################################################# + + always: + - name: Delete network + meraki_network: + auth_key: '{{ auth_key }}' + state: absent + org_name: '{{test_org_name}}' + net_name: IntTestNetworkWireless + delegate_to: localhost diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/tasks/main.yml new file mode 100644 index 00000000..7bfc5e0b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_config_template/tasks/main.yml @@ -0,0 +1,196 @@ +# 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: Query all configuration templates + meraki_config_template: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + register: get_all + + - name: Delete non-existant configuration template + meraki_config_template: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + config_template: FakeConfigTemplate + register: deleted + ignore_errors: yes + + - assert: + that: + - '"No configuration template named" in deleted.msg' + + - name: Create a network + 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 + + - set_fact: + net_id: '{{net_info.data.id}}' + + - name: Bind a template to a network with check mode + 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: yes + register: bind_check + + - name: Bind a template to a network + 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 + + - assert: + that: + bind.changed == True + + - assert: + that: + bind_check is changed + + - name: Bind a template to a network when it's already bound + 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 + ignore_errors: yes + + - assert: + that: + - bind_invalid.changed == False + + - name: Unbind a template from a network + 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 + + - assert: + that: + unbind.changed == True + + - name: Unbind a template from a network when it's not bound + 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 + + - assert: + that: + unbind_invalid.changed == False + + - name: Bind a template to a network via id + 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 + + - assert: + that: + bind_id.changed == True + + - name: Bind a template to a network via id for idempotency + 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 + + - 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 + 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: yes + register: unbind_id_check + + - assert: + that: + unbind_id_check is changed + + - name: Unbind a template from a network via id + 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 + + - 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 + # meraki_config_template: + # auth_key: '{{auth_key}}' + # state: absent + # org_name: '{{test_org_name}}' + # config_template: sacrificial_template + # check_mode: yes + # register: delete_template_check + + # This is disabled by default since they can't be created via API + # - name: Delete sacrificial template + # meraki_config_template: + # auth_key: '{{auth_key}}' + # state: absent + # org_name: '{{test_org_name}}' + # config_template: sacrificial_template + # output_level: debug + # register: delete_template + + # - debug: + # var: delete_template + + always: + - name: Delete 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_content_filtering/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_content_filtering/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_content_filtering/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_content_filtering/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_content_filtering/tasks/main.yml new file mode 100644 index 00000000..a3a5210f --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_content_filtering/tasks/main.yml @@ -0,0 +1,254 @@ +# 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) +--- +- 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 }}' + 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 + meraki_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 + ignore_errors: yes + + - assert: + that: + - 'net_exclusive.msg == "net_name and net_id are mutually exclusive"' + + - name: Set single allowed URL pattern with check mode + meraki_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: yes + + - assert: + that: + - single_allowed_check.data.allowed_url_patterns | length == 1 + - single_allowed_check is changed + + - name: Set single allowed URL pattern + meraki_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 + + - assert: + that: + - single_allowed.data.allowed_url_patterns | length == 1 + + - name: Set single allowed URL pattern for idempotency with check mode + meraki_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: yes + + - debug: + var: single_allowed_idempotent_check + + - 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 + meraki_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 + + - debug: + var: single_allowed_idempotent + + - assert: + that: + - single_allowed_idempotent.changed == False + - single_allowed_idempotent.data is defined + + - name: Set single blocked URL pattern + meraki_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 + + - debug: + var: single_blocked + + - assert: + that: + - single_blocked.data.blocked_url_patterns | length == 1 + + - name: Set two allowed URL pattern + meraki_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 + + - debug: + var: two_allowed + + - assert: + that: + - two_allowed.changed == True + - two_allowed.data.allowed_url_patterns | length == 2 + + - name: Set blocked URL category + meraki_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 and Pornography" + register: blocked_category + + - debug: + var: blocked_category + + - 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 + meraki_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 and Pornography" + register: blocked_category + + - debug: + var: blocked_category + + - 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 + meraki_content_filtering: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + state: query + delegate_to: localhost + register: query_all + + - debug: + var: query_all + + - name: Query all content filtering assertion + assert: + that: + - query_all.data.categories is defined + - query_all.data.policy is defined + + - name: Query categories + meraki_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 + + - debug: + var: query_categories + + - name: Query categories assertion + assert: + that: + - query_categories.data is defined + + - name: Query content filtering policies + meraki_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 + + - debug: + var: query_policy + + - name: Query contnet filtering policy assertion + assert: + that: + - query_policy.data is defined + + always: + - name: Reset policies + meraki_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: + - + + - name: Delete network + meraki_network: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + state: absent diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/aliases new file mode 100644 index 00000000..89aea537 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/aliases @@ -0,0 +1 @@ +unsupported
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/tasks/main.yml new file mode 100644 index 00000000..4b999c41 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_device/tasks/main.yml @@ -0,0 +1,246 @@ +--- +- block: + # This is commented out because a device cannot be unclaimed via API + # - name: Claim a device into an organization + # meraki_device: + # auth_key: '{{auth_key}}' + # org_name: '{{test_org_name}}' + # serial: '{{serial}}' + # state: present + # delegate_to: localhost + # register: claim_device_org + + # - assert: + # that: + # - claim_device_org.changed == true + + - name: Create network + meraki_network: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + type: appliance + state: present + register: net_info + + - set_fact: + net_id: '{{net_info.data.id}}' + + - name: Query status of all devices in an organization + meraki_device: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + state: query + delegate_to: localhost + register: query_device_org + + - debug: + msg: '{{query_device_org}}' + + - name: Claim a device into a network + 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 + + - debug: + msg: '{{claim_device}}' + + - assert: + that: + - claim_device.changed == true + + - name: Query all devices in one network by network ID + 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 + + - debug: + msg: '{{query_one_net_id}}' + + - name: Query all devices in one network + 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 + + - debug: + msg: '{{query_one_net}}' + + - name: Query device by serial + meraki_device: + auth_key: '{{auth_key}}' + org_name: '{{test_org_name}}' + serial: '{{serial}}' + state: query + delegate_to: localhost + register: query_serial_no_net + + - debug: + msg: '{{query_serial_no_net}}' + + - name: Query device by serial + 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 + + - debug: + msg: '{{query_serial}}' + + - assert: + that: + - query_serial.changed == False + + - name: Query uplink information for a device + 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 + + - debug: + msg: '{{query_serial_uplink}}' + + - name: Query LLDP/CDP information about a device + 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 + + - debug: + msg: '{{query_serial_lldp_cdp}}' + + - name: Query a device by hostname + 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 + + - debug: + msg: '{{query_hostname}}' + + - name: Query a device by model + 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 + + - debug: + msg: '{{query_model}}' + + - name: Change device name for future test + 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 + 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 + + - 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 + 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 + + - debug: + msg: '{{update_device_idempotent}}' + + - assert: + that: + - update_device_idempotent.changed == False + - update_device_idempotent.data is defined + + always: + - name: Remove a device from a network + 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 + + - debug: + msg: '{{delete_device}}' + + - assert: + that: + - delete_device.changed == true
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/main.yml new file mode 100644 index 00000000..60aa04a9 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/tests.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/tests.yml new file mode 100644 index 00000000..29a41f5f --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_firewalled_services/tasks/tests.yml @@ -0,0 +1,212 @@ +# 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) +--- +- block: + - name: Create network + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkAppliance + type: appliance + register: create + + - set_fact: + net_id: '{{create.data.id}}' + + - name: Set icmp service to blocked with check mode + 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: yes + + - debug: + var: icmp_blocked_check + + - assert: + that: + - icmp_blocked_check.data is defined + - icmp_blocked_check is changed + + - 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 + register: icmp_blocked + + - debug: + var: icmp_blocked + + - assert: + that: + - icmp_blocked.data is defined + - icmp_blocked is changed + + - name: Set icmp service to blocked with idempotency + 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 + + - debug: + var: icmp_blocked_idempotent + + - assert: + that: + - icmp_blocked_idempotent.data is defined + - icmp_blocked_idempotent is not changed + + - name: Set icmp service to restricted with check mode + 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: yes + register: web_restricted_check + + - debug: + var: web_restricted_check + + - assert: + that: + - web_restricted_check.data is defined + - web_restricted_check is changed + + - name: Set icmp service to restricted + 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 + + - debug: + var: web_restricted + + - assert: + that: + - web_restricted.data is defined + - web_restricted is changed + + - name: Set icmp service to restricted with idempotency + 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 + + - debug: + var: web_restricted_idempotent + + - assert: + that: + - web_restricted_idempotent.data is defined + - web_restricted_idempotent is not changed + + - name: Test error for access restricted and allowed_ips + 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 + ignore_errors: yes + + - assert: + that: + - 'access_error.msg == "allowed_ips is only allowed when access is restricted."' + + - name: Query appliance services with net_id + meraki_firewalled_services: + auth_key: '{{ auth_key }}' + state: query + org_name: '{{test_org_name}}' + net_id: '{{net_id}}' + register: query_appliance_id + + - debug: + var: query_appliance_id + + - assert: + that: + - query_appliance_id.data is defined + + + - name: Query appliance services + meraki_firewalled_services: + auth_key: '{{ auth_key }}' + state: query + org_name: '{{test_org_name}}' + net_name: IntTestNetworkAppliance + register: query_appliance + + - debug: + var: query_appliance + + - assert: + that: + - query_appliance.data is defined + + - name: Query services + meraki_firewalled_services: + auth_key: '{{ auth_key }}' + state: query + org_name: '{{test_org_name}}' + net_name: IntTestNetworkAppliance + service: ICMP + register: query_service + + - debug: + var: query_service + + - assert: + that: + - query_service.data is defined + +############################################################################# +# Tear down starts here +############################################################################# + always: + - name: Delete all networks + meraki_network: + auth_key: '{{ auth_key }}' + state: absent + org_name: '{{test_org_name}}' + net_name: IntTestNetworkAppliance diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/tasks/main.yml new file mode 100644 index 00000000..f671fc92 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_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 + include: tests.yml ansible_connection=local diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/tasks/tests.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/tasks/tests.yml new file mode 100644 index 00000000..3ac1933e --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_intrusion_prevention/tasks/tests.yml @@ -0,0 +1,312 @@ +# 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) +--- +- 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}} - IPS' + type: appliance + register: net + + - debug: + var: net + + - name: Set allowed rules for organization in check mode + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + allowed_rules: + - rule_id: "meraki:intrusion/snort/GID/01/SID/5805" + message: Test rule + check_mode: yes + register: create_org_check + + - assert: + that: + - create_org_check is changed + - create_org_check.data is defined + + - name: Set allowed rules 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" + message: Test rule + register: create_org + + - 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 + 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" + message: Test rule + register: create_org_idempotent + + - assert: + that: + - create_org_idempotent is not changed + - create_org_idempotent.data is defined + + - name: Query IPS info for organization + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + register: query_org + + - assert: + that: + - query_org.data.allowed_rules is defined + + - name: Set mode to prevention 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 + check_mode: yes + register: mode_check + + - assert: + that: + - mode_check is changed + - mode_check.data is defined + + - name: Set mode to prevention + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}} - IPS' + mode: prevention + register: mode + + - debug: + var: mode + + - assert: + that: + - mode is changed + - mode.data.mode is defined + + - name: Set mode to prevention with idempotency + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}} - IPS' + mode: prevention + register: mode_idempotent + + - debug: + var: mode_idempotent + + - assert: + that: + - mode_idempotent is not changed + - mode_idempotent.data.mode is defined + + - 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 + check_mode: yes + register: full_check + + - debug: + var: full_check + + - 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 + # meraki_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 + + # - debug: + # var: full + + # - 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 + # 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 + # register: full_idempotent + + # - debug: + # var: full_idempotent + + # - assert: + # that: + # - full_idempotent is not changed + # - full_idempotent.data.mode is defined + + - name: Query IPS info for network + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}} - IPS' + register: query_net + + - assert: + that: + - query_net is defined + + - name: Test use_default error with included_cidr + 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: false + excluded_cidr: + - 10.0.1.0/24 + check_mode: yes + register: included_missing + ignore_errors: yes + + - assert: + that: + - 'included_missing.msg == "included_cidr is required when use_default is False."' + + - name: Test use_default error with included_cidr + 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: false + included_cidr: + - 10.0.1.0/24 + check_mode: yes + register: excluded_missing + ignore_errors: yes + + - assert: + that: + - 'excluded_missing.msg == "excluded_cidr is required when use_default is False."' + + # ############################################################################# + # # Tear down starts here + # ############################################################################# + always: + - name: Delete network + 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 + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + allowed_rules: [] + check_mode: yes + register: cleared + + - assert: + that: + - cleared is changed + - cleared.data is defined + + - name: Clear rules from organization + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + allowed_rules: [] + - name: Clear rules from organization with idempotency + meraki_intrusion_prevention: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + allowed_rules: [] + register: del_idempotent + + - assert: + that: + - del_idempotent is not changed + - del_idempotent.data is defined + + - name: Delete test network + meraki_network: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}} - IPS' diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_malware/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_malware/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_malware/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_malware/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_malware/tasks/main.yml new file mode 100644 index 00000000..bab98cd0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/tasks/main.yml new file mode 100644 index 00000000..28337707 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_management_interface/tasks/main.yml @@ -0,0 +1,273 @@ +# 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) +--- +- block: + - name: Test an API key is provided + fail: + msg: Please define an API key + when: auth_key is not defined + + - set_fact: + net_name: TestNet - Appliance + + - name: 'Create test network {{net_name}}' + meraki_network: + auth_key: '{{auth_key}}' + state: present + org_id: '{{test_org_id}}' + net_name: '{{net_name}}' + type: appliance + delegate_to: localhost + register: net + + - set_fact: + net_id: '{{net.data.id}}' + + - name: Test providing wan_enabled to an MS network + 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 + + - debug: + var: ms_not_configured + + - assert: + that: + - ms_not_configured.data is defined + + - name: Set management interface on switch + 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: no + vlan: 3 + delegate_to: localhost + register: set_switch_mgmt + + - debug: + var: set_switch_mgmt + + - assert: + that: + - set_switch_mgmt.data is defined + + - name: Query non-MX network + 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 + + - debug: + var: non_mx_network + + - assert: + that: + - non_mx_network.data is defined + + - name: Reset management interface on switch + 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: no + vlan: 1 + delegate_to: localhost + register: reset_switch_mgmt + + - debug: + var: reset_switch_mgmt + + - assert: + that: + - reset_switch_mgmt.data is defined + + - name: Set WAN1 as DHCP in check mode + 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: no + vlan: 1 + delegate_to: localhost + register: set_wan1_check + check_mode: yes + + - debug: + var: set_wan1_check + + - assert: + that: + - set_wan1_check is changed + - set_wan1_check.data is defined + + - name: Set WAN1 as DHCP + 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: no + vlan: 1 + delegate_to: localhost + register: set_wan1 + + - debug: + var: set_wan1 + + - assert: + that: + - set_wan1 is changed + - set_wan1.data is defined + + - name: Set WAN1 as DHCP with idempotency + 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: no + vlan: 1 + delegate_to: localhost + register: set_wan1_idempotent + + - debug: + var: set_wan1_idempotent + + - assert: + that: + - set_wan1_idempotent is not changed + - set_wan1_idempotent.data is defined + + - name: Set WAN2 as static IP + 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: 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 + register: set_wan2 + + - debug: + var: set_wan2 + + - assert: + that: + - set_wan2 is changed + - set_wan2.data is defined + + - name: Test too many DNS servers + 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: yes + 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 + ignore_errors: yes + + - debug: + var: too_many_dns + + - assert: + that: + - 'too_many_dns.msg == "Maximum number of static DNS addresses is 2."' + + - name: Query management information + 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 + + - debug: + var: query_mx + + - assert: + that: + - query_mx.data is defined + + always: + ############################################################################ + # Tear down starts here + ############################################################################ + - name: Reset settings for all interfaces + 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: no + wan2: + wan_enabled: enabled + using_static_ip: no + delegate_to: localhost + ignore_errors: yes + + # Network deletion is commented out since this requires a device in a network + # - name: Delete network + # 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml new file mode 100644 index 00000000..a267e15f --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_l3_firewall/tasks/main.yml @@ -0,0 +1,171 @@ +# 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 wireless network + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: TestNetWireless + type: wireless + delegate_to: localhost + register: new_net + + - set_fact: + net: '{{new_net.data.id}}' + + - name: Create single firewall rule with check mode + 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: no + delegate_to: localhost + check_mode: yes + register: create_one_check + + - debug: + msg: '{{create_one_check}}' + + - 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 + 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: no + delegate_to: localhost + register: create_one + + - debug: + msg: '{{create_one}}' + + - 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 + meraki_mr_l3_firewall: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_id: '{{net}}' + number: 1 + allow_lan_access: yes + delegate_to: localhost + check_mode: yes + register: enable_lan_check + + - debug: + var: enable_lan_check + + - assert: + that: + - enable_lan_check.data.rules.1.policy == 'allow' + - enable_lan_check is changed + + - name: Enable local LAN access + meraki_mr_l3_firewall: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_id: '{{net}}' + number: 1 + allow_lan_access: yes + delegate_to: localhost + register: enable_lan + + - assert: + that: + - enable_lan.data.rules.1.policy == 'allow' + + - name: Update rules without specifying LAN access with check mode + 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 + + - debug: + msg: '{{update_one_check}}' + + - assert: + that: + - update_one_check.data.rules.0.comment == 'Integration test rule' + - update_one_check.data is defined + + - name: Query firewall rules + 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 + + - debug: + msg: '{{query}}' + + - 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 + 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 + meraki_network: + auth_key: '{{ auth_key }}' + state: absent + org_name: '{{test_org_name}}' + net_id: '{{net}}' + delegate_to: localhost diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/aliases new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/aliases diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml new file mode 100644 index 00000000..3c78022b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_rf_profile/tasks/main.yml @@ -0,0 +1,271 @@ +# 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 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 + + - name: Create RF profile in check mode + 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 + + - assert: + that: + - create_check.data is defined + - create_check is changed + + - name: Create RF profile + 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 + + - set_fact: + profile_id: '{{ create.data.id }}' + + - name: Query all RF profiles + meraki_mr_rf_profile: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: IntTestNetworkWireless + state: query + delegate_to: localhost + register: query_one + + - assert: + that: + - query_one.data is defined + + - name: Update profile with check mode + 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 + + - assert: + that: + - update_check.data is defined + - update_check is changed + + - name: Update profile + 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 + + - assert: + that: + - update.data is defined + - update is changed + + - name: Update profile with idempotency + 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 + + - assert: + that: + - update_idempotent.data is defined + - update_idempotent is not changed + + - name: Query one RF profile by ID + 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 + + - assert: + that: + - query_one_id.data is defined + + - name: Query one RF profile by name + 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 + + - assert: + that: + - query_one_name.data is defined + + - name: Delete RF profile + 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 + + - assert: + that: + - delete.data is defined + - delete is changed + +############################################################################# +# Tear down starts here +############################################################################# + + always: + - name: Delete network + meraki_network: + auth_key: '{{ auth_key }}' + state: absent + org_name: '{{test_org_name}}' + net_name: IntTestNetworkWireless + delegate_to: localhost diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/aliases new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/aliases diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/tasks/main.yml new file mode 100644 index 00000000..c2cfed8d --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mr_settings/tasks/main.yml @@ -0,0 +1,102 @@ +# 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 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 + + - name: Query all settings + meraki_mr_settings: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: IntTestNetworkWireless + state: query + delegate_to: localhost + register: query_all + + - debug: + var: query_all + + - assert: + that: + - query_all.data is defined + + - name: Configure settings with check mode + 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 + + - assert: + that: + - settings_check.data is defined + - settings_check is changed + + - name: Configure settings + 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 + + - assert: + that: + - settings.data is defined + - settings is changed + + - name: Configure settings with idempotency + 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 + + - assert: + that: + - settings_idempotent.data is defined + - settings_idempotent is not changed + +############################################################################# +# Tear down starts here +############################################################################# + + always: + - name: Delete network + meraki_network: + auth_key: '{{ auth_key }}' + state: absent + org_name: '{{test_org_name}}' + net_name: IntTestNetworkWireless + delegate_to: localhost diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_l3_interface/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_l3_interface/tasks/main.yml new file mode 100644 index 00000000..b32135cd --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/meraki_network/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/meraki_network/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/meraki_network/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/meraki_network/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/meraki_network/tasks/main.yml new file mode 100644 index 00000000..37264abb --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/meraki_network/tasks/main.yml @@ -0,0 +1,473 @@ +# 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 + + - debug: + var: create_net_switch_check + + - debug: + var: create_net_switch_check.data.organization_id + + - 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 + + - 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.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.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 + + - debug: + var: 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.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: Create network with type wireless and disable my.meraki.com + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkWireless + type: wireless + timezone: America/Chicago + disable_my_meraki: yes + delegate_to: localhost + register: create_net_wireless + + - name: Create network with type wireless, disable my.meraki.com, 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 + disable_my_meraki: yes + delegate_to: localhost + register: create_net_wireless_idempotent + + - assert: + that: + - create_net_wireless_idempotent.data is defined + + - name: Create network with type combined and disable my.meraki.com + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{ test_org_name }}' + net_name: IntTestNetworkCombined + type: + - appliance + - switch + timezone: America/Chicago + enable_my_meraki: no + delegate_to: localhost + register: create_net_combined + + - name: Reenable my.meraki.com with check mode + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkCombined + enable_my_meraki: yes + delegate_to: localhost + register: enable_meraki_com_check + check_mode: yes + + - assert: + that: + - enable_meraki_com_check is changed + - enable_meraki_com_check.data is defined + + - name: Reenable my.meraki.com + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkCombined + enable_my_meraki: yes + delegate_to: localhost + register: enable_meraki_com + + - name: Disable my.meraki.com for next test + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkCombined + enable_my_meraki: no + delegate_to: localhost + + - name: Enable remote status page + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkCombined + enable_remote_status_page: yes + delegate_to: localhost + register: disable_remote_status + + - debug: + msg: '{{disable_remote_status}}' + + - assert: + that: + - disable_remote_status.data.disable_remote_status_page == False + + - name: Disable remote status page + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkCombined + enable_remote_status_page: no + delegate_to: localhost + register: enable_remote_status + + - debug: + msg: '{{enable_remote_status}}' + + - assert: + that: + - enable_remote_status.data.disable_remote_status_page == True + + - name: Test status pages are mutually exclusive when on + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: IntTestNetworkCombined + enable_my_meraki: yes + enable_remote_status_page: no + delegate_to: localhost + register: status_exclusivity + ignore_errors: yes + + - assert: + that: + - '"must be true when setting" in status_exclusivity.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: + - create_net_combined.data.type == 'combined' + - create_net_combined.data.disable_my_meraki_com == True + - enable_meraki_com.data.disable_my_meraki_com == False + - '"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' + - '"IntTestNetworkWireless" in create_net_wireless.data.name' + - create_net_wireless.data.disable_my_meraki_com == True + - create_net_wireless_idempotent.changed == False + - create_net_wireless_idempotent.data is defined + - '"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 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 assertions + assert: + that: + - 'net_query_one.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 + - IntTestNetworkWireless + - IntTestNetworkAppliance + - IntTestNetworkCombined + - IntTestNetworkTag + - IntTestNetworkTags + + - assert: + that: + - 'delete_all_no_org.msg == "org_name or org_id parameters are required"' diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml new file mode 100644 index 00000000..e7e3b96b --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_link_aggregation/tasks/main.yml @@ -0,0 +1,124 @@ +# 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) +--- +- 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_switch_net_name}}' + type: switch + register: test_net + + - name: Add device to network + 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 + 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 + + - debug: + var: create_ports + + - set_fact: + lag_id: '{{create_ports.data.id}}' + + - 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 + register: update_ports + + - assert: + that: + - update_ports is changed + + - name: Update LAG with idempotency + 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 + + - assert: + that: + - update_ports_idempotent is not changed + + - name: Query all LAGs + 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 + + - debug: + var: query_all + + always: + - name: Delete LAG + 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_ospf/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_ospf/tasks/main.yml new file mode 100644 index 00000000..c941f85c --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ms_ospf/tasks/main.yml @@ -0,0 +1,136 @@ +# 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 OSPF settings + meraki_ms_ospf: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: IntTestNetworkSwitch + state: query + delegate_to: localhost + register: query_all + + + - assert: + that: + - query_all.data is defined + + - name: Enable OSPF with check mode + 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 + + - assert: + that: + - enable_check_mode.data is defined + + - name: Enable OSPF + 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 + + - assert: + that: + - enable.data is defined + + - name: Enable OSPF with idempotency + 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 + + - debug: + var: enable_ospf_idempotent + + - assert: + that: + - enable_ospf_idempotent is not changed + - enable_ospf_idempotent.data is defined + +############################################################################# +# Tear down starts here +############################################################################# + + # always: + # - 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml new file mode 100644 index 00000000..3a142b2a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l2_interface/tasks/main.yml @@ -0,0 +1,155 @@ +# 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 appliance + 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 + 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 + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{ test_org_name }}' + net_name: '{{test_appliance_net_name}}' + enable_vlans: yes + delegate_to: localhost + + - name: Create a VLAN + meraki_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 + 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 + + - assert: + that: + - query_all.data is defined + + - name: Query a single layer 2 interface settings + 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 + + - assert: + that: + - query_one.data is defined + + - name: Update interface configuration with check mode + 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: yes + + - debug: + var: update_check + + - assert: + that: + - update_check.data is defined + - update_check.data.vlan == 10 + - update_check is changed + + - name: Update interface configuration + 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 + + - assert: + that: + - update.diff is defined + - update.data is defined + - update.data.vlan == 10 + - update is changed + + - name: Update interface configuration with idempotency + 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 + + - 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 + 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 + 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml new file mode 100644 index 00000000..2e4ddc62 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l3_firewall/tasks/main.yml @@ -0,0 +1,351 @@ +# 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: 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/aliases new file mode 100644 index 00000000..06fe32bc --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/aliases @@ -0,0 +1,2 @@ +unsupported + diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/main.yml new file mode 100644 index 00000000..bb4c6fc5 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml new file mode 100644 index 00000000..1b0e8dde --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_l7_firewall/tasks/tests.yml @@ -0,0 +1,494 @@ +# Test code for the Meraki Organization module +# Copyright: (c) 2018, Kevin Breit (@kbreit) + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- block: + - name: Test an API key is provided + fail: + msg: Please define an API key + when: auth_key is not defined + + - name: Create network + meraki_network: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + type: appliance + + - name: Query firewall rules + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: query + register: query + + - debug: + var: query + + - assert: + that: + - query.data is defined + + - name: Query firewall application categories + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: query + categories: yes + register: query_categories + + - assert: + that: + - query_categories.data is defined + + - name: Create firewall rule for IP range in check mode + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_check + check_mode: yes + + - debug: + var: create_ip_range_check + + - assert: + that: + - create_ip_range_check is changed + + - name: Create firewall rule for IP range + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range + + - debug: + var: create_ip_range + + - assert: + that: + - create_ip_range is changed + - create_ip_range.data.rules | length == 1 + + - name: Create firewall rule for IP range with idempotency with check mode + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent_check + check_mode: yes + + - assert: + that: + - create_ip_range_idempotent_check is not changed + + - name: Create firewall rule for IP range with idempotency + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent + + - assert: + that: + - create_ip_range_idempotent is not changed + + - name: Create firewall rule for IP and port + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.1:23 + register: create_ip_range_port + + - debug: + var: create_ip_range_port + + - assert: + that: + - create_ip_range_port is changed + + - name: Create firewall rule for IP range + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range + + - debug: + var: create_ip_range + + - assert: + that: + - create_ip_range is changed + - create_ip_range.data.rules | length == 1 + + - name: Create firewall rule for IP range with idempotency with check mode + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent_check + check_mode: yes + + - assert: + that: + - create_ip_range_idempotent_check is not changed + + - name: Create firewall rule for IP range with idempotency + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: ip_range + ip_range: 10.11.12.0/24 + register: create_ip_range_idempotent + + - assert: + that: + - create_ip_range_idempotent is not changed + + - name: Create firewall rule for application + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application + application: + name: facebook + register: application_rule + + - assert: + that: + - application_rule is changed + - application_rule.data.rules is defined + + - name: Create firewall rule for application via ID + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application + application: + id: meraki:layer7/application/205 + register: application_rule_id + + - assert: + that: + - application_rule_id is changed + + - name: Create firewall rule for invalid application + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application + application: + name: ansible + register: application_rule_invalid + ignore_errors: yes + + - name: Create firewall rule for application category + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + application: + name: Advertising + register: application_category_rule + + - debug: + var: application_category_rule + + - assert: + that: + - application_category_rule is changed + + - name: Create firewall rule for application category with ID and conflict + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + application: + id: meraki:layer7/category/27 + register: application_category_rule_id_conflict + + - assert: + that: + - application_category_rule_id_conflict is not changed + + - name: Create firewall rule for application category with ID + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: application_category + application: + id: meraki:layer7/category/24 + register: application_category_rule_id + + - assert: + that: + - application_category_rule_id is changed + + - name: Create firewall rule for host + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: host + host: asdf.com + register: host_rule + + - assert: + that: + - host_rule is changed + + - name: Create firewall rule for port + meraki_mx_l7_firewall: + auth_key: '{{ auth_key }}' + org_name: '{{test_org_name}}' + net_name: TestNetAppliance + state: present + rules: + - type: port + port: 1234 + register: port_rule + + - assert: + that: + - port_rule is changed + + - name: Create firewall rule for 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_site_to_site_firewall/tasks/main.yml new file mode 100644 index 00000000..df0b9164 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink/tasks/main.yml new file mode 100644 index 00000000..1e055256 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_mx_uplink/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_nat/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_nat/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_nat/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_nat/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_nat/tasks/main.yml new file mode 100644 index 00000000..721a9300 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_nat/tasks/tests.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_nat/tasks/tests.yml new file mode 100644 index 00000000..11193d13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/tasks/main.yml new file mode 100644 index 00000000..61bcb313 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_network/tasks/main.yml @@ -0,0 +1,453 @@ +# 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 + + - 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 are mutually exclusive when on + 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_exclusivity + ignore_errors: yes + + - assert: + that: + - '"must be true when setting" in status_exclusivity.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 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 assertions + assert: + that: + - 'net_query_one.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 + - IntTestNetworkWireless + - IntTestNetworkAppliance + - IntTestNetworkCombined + - IntTestNetworkTag + - IntTestNetworkTags + + - assert: + that: + - 'delete_all_no_org.msg == "org_name or org_id parameters are required"' diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/main.yml new file mode 100644 index 00000000..e7ad65b8 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/tests.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_organization/tasks/tests.yml new file mode 100644 index 00000000..28ebce01 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_site_to_site_vpn/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_site_to_site_vpn/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_site_to_site_vpn/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_site_to_site_vpn/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_site_to_site_vpn/tasks/main.yml new file mode 100644 index 00000000..d15a1b96 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_site_to_site_vpn/tasks/main.yml @@ -0,0 +1,140 @@ +# 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 + 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: 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_snmp/tasks/main.yml new file mode 100644 index 00000000..ed0868a4 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/tasks/main.yml new file mode 100644 index 00000000..638bdf12 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_ssid/tasks/main.yml @@ -0,0 +1,551 @@ +# 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) +--- +- block: + + - set_fact: + wpa_enabled: false + + - 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: TestNetSSID + type: wireless + register: test_net + + - debug: + msg: '{{test_net}}' + + - name: Add access points to network + 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 + meraki_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 + meraki_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: yes + + - assert: + that: + - enable_name_ssid_check is changed + - enable_name_ssid_check.data is defined + + - name: Enable and name SSID + meraki_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 + + - debug: + msg: '{{ enable_name_ssid }}' + + - assert: + that: + - query_all.data | length == 15 + - query_all.data.0.name == 'TestNetSSID WiFi' + - enable_name_ssid.data.name == 'AnsibleSSID' + + - name: Check for idempotency + meraki_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 + + - debug: + msg: '{{ enable_name_ssid_idempotent }}' + + - assert: + that: + - enable_name_ssid_idempotent.changed == False + - enable_name_ssid_idempotent.data is defined + + - name: Query one SSIDs + meraki_ssid: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + net_name: TestNetSSID + name: AnsibleSSID + delegate_to: localhost + register: query_one + + - debug: + msg: '{{query_one}}' + + - assert: + that: + - query_one.data.name == 'AnsibleSSID' + + - name: Query one SSID with number + meraki_ssid: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + net_name: TestNetSSID + number: 1 + delegate_to: localhost + register: query_one_number + + - debug: + msg: '{{query_one_number}}' + + - assert: + that: + - query_one_number.data.name == 'AnsibleSSID' + + - name: Disable SSID without specifying number + meraki_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 + + - debug: + msg: '{{ disable_ssid.data.enabled }}' + + - assert: + that: + - disable_ssid.data.enabled == False + + - name: Enable SSID with number + meraki_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 + + - debug: + msg: '{{ enable_ssid_number.data.enabled }}' + + - assert: + that: + - enable_ssid_number.data.enabled == True + + - name: Set VLAN arg spec + meraki_ssid: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: TestNetSSID + number: 1 + use_vlan_tagging: yes + 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 + + - debug: + var: set_vlan_arg + + - assert: + that: set_vlan_arg is changed + + - name: Set VLAN arg spec + meraki_ssid: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: TestNetSSID + number: 1 + use_vlan_tagging: yes + 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 + + - debug: + var: set_vlan_arg_idempotent + + - assert: + that: set_vlan_arg_idempotent is not changed + + + - name: Set PSK + meraki_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 + + - debug: + msg: '{{ psk }}' + + - assert: + that: + - psk.data.auth_mode == 'psk' + - psk.data.encryption_mode == 'wpa' + - psk.data.wpa_encryption_mode == 'WPA2 only' + + - name: Set PSK with idempotency + meraki_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 + + - debug: + msg: '{{ psk_idempotent }}' + + - assert: + that: + - psk_idempotent is not changed + + # + # Check WPA3 Transition Mode + # + - name: Set WPA3 Transition Mode + meraki_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 == true + + - debug: + msg: '{{ psk_wpa3_transition_mode }}' + when: wpa_enabled == True + + - 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 == True + + - name: Set WPA3 Transition Mode with Idempotency + meraki_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 == True + + - debug: + msg: '{{ psk_wpa3_transition_mode_idempotent }}' + when: wpa_enabled == True + + - assert: + that: + - psk_wpa3_transition_mode_idempotent is not changed + when: wpa_enabled == True + + - name: Enable click-through splash page + meraki_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 + + - debug: + msg: '{{ splash_click }}' + + - assert: + that: + - splash_click.data.splash_page == 'Click-through splash page' + + - name: Set walled garden + meraki_ssid: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: TestNetSSID + name: AnsibleSSID + walled_garden_enabled: yes + walled_garden_ranges: + - 192.168.0.0/24 + - '*.ansible.com' + delegate_to: localhost + register: walled_garden + + - debug: + msg: '{{ walled_garden }}' + + - assert: + that: + - 'walled_garden.data.walled_garden_enabled == True' + - walled_garden is changed + + - name: Set walled garden with idempotency + meraki_ssid: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: TestNetSSID + name: AnsibleSSID + walled_garden_enabled: yes + walled_garden_ranges: + - 192.168.0.0/24 + - '*.ansible.com' + delegate_to: localhost + register: walled_garden_idempotent + + - debug: + msg: '{{ walled_garden_idempotent }}' + + - assert: + that: + - walled_garden_idempotent.data is defined + - walled_garden_idempotent is not changed + + - name: Configure RADIUS servers + meraki_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 + + - debug: + msg: '{{ set_radius_server }}' + + - assert: + that: + - set_radius_server.data.radius_servers.0.host == '192.0.1.200' + + - name: Configure RADIUS servers with idempotency + meraki_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 + + - debug: + var: set_radius_server_idempotent + + - assert: + that: + - set_radius_server_idempotent is not changed + + ################# + # Error testing # + ################# + - name: Set PSK with wrong mode + meraki_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 + ignore_errors: yes + + - debug: + msg: '{{ psk_invalid }}' + + - assert: + that: + - psk_invalid.msg == 'PSK is only allowed when auth_mode is set to psk' + + - name: Set PSK with invalid encryption mode + meraki_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 + ignore_errors: yes + + - debug: + msg: '{{ psk_invalid_mode }}' + + - assert: + that: + - psk_invalid_mode.msg == 'PSK requires encryption_mode be set to wpa' + + - name: Error for PSK and RADIUS servers + meraki_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 + ignore_errors: yes + + - debug: + var: err_radius_server_psk + + - 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 + meraki_ssid: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: TestNetSSID + number: 1 + use_vlan_tagging: yes + ip_assignment_mode: Bridge mode + ap_tags_vlan_ids: + - tags: wifi + vlan_id: 2 + delegate_to: localhost + register: set_vlan_arg_err + ignore_errors: yes + + - debug: + var: set_vlan_arg_err + + - assert: + that: + - 'set_vlan_arg_err.msg == "default_vlan_id is required when use_vlan_tagging is True"' + + always: + - name: Delete SSID with check mode + meraki_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: yes + + - assert: + that: + - delete_ssid_check is changed + - delete_ssid_check.data is defined + + - name: Delete SSID + meraki_ssid: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + net_name: TestNetSSID + name: AnsibleSSID + delegate_to: localhost + register: delete_ssid + + - debug: + msg: '{{ delete_ssid }}' + + - assert: + that: + - delete_ssid.data.name == 'Unconfigured SSID 2' + + - name: Delete test network + meraki_network: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + net_name: TestNetSSID + register: delete_net + + - debug: + msg: '{{delete_net}}' diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_static_route/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_static_route/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_static_route/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_static_route/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_static_route/tasks/main.yml new file mode 100644 index 00000000..6313c23c --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_static_route/tasks/main.yml @@ -0,0 +1,187 @@ +# 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 + + - set_fact: + net_id: '{{net.data.id}}' + + - name: Initialize static route id list + set_fact: + route_ids: [] + + - 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 + delegate_to: localhost + register: update + + - assert: + that: + - update is changed + - update.data.subnet == "192.0.3.0/24" + + - 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/tasks/main.yml new file mode 100644 index 00000000..f671fc92 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/tasks/tests.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/tasks/tests.yml new file mode 100644 index 00000000..cb0ac0be --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_access_list/tasks/tests.yml @@ -0,0 +1,123 @@ +# 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) +--- +- 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}}' + 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: yes + + - assert: + that: + - create_check is changed + - create_check.data is defined + + - 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 + + - assert: + that: + - create is changed + - create.data is defined + + - 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 + + - debug: + var: create_idempotent + + - 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 + + - debug: + var: query + + - assert: + that: + - query.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}}' diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_stack/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_stack/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_stack/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_stack/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_stack/tasks/main.yml new file mode 100644 index 00000000..853cbbe7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_stack/tasks/main.yml @@ -0,0 +1,198 @@ +# 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) +--- +- block: + - name: Create network with type switch + 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 + + - set_fact: + net_id: '{{create_net_switch.data.id}}' + + - name: Claim devices into network + 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 + meraki_switch_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 + + - debug: + var: create + + - set_fact: + stack_id: '{{create.data.id}}' + + - assert: + that: + - create.data is defined + - create is changed + + - name: Add switch to stack + meraki_switch_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 + + - assert: + that: + - add_stack.data is defined + - add_stack.data.serials | length == 3 + - add_stack is changed + + - name: Add switch to stack idempotent + meraki_switch_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 + + - 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 + meraki_switch_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 + + - debug: + var: remove_from_stack + + - 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 + meraki_switch_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 + + - debug: + var: remove_from_stack_idempotent + + - 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 + meraki_switch_stack: + auth_key: '{{ auth_key }}' + state: query + org_name: '{{test_org_name}}' + net_name: '{{test_switch_net_name}}' + register: query_all + + - debug: + var: query_all + + - assert: + that: + - query_all.data is defined + - query_all is not changed + + - name: Query one stack + meraki_switch_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 + + - debug: + var: query_one + + - assert: + that: + - query_one.data is defined + - query_one is not changed + + - name: Query one stack using name + meraki_switch_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 + + - debug: + var: query_one_name + + - assert: + that: + - query_one_name.data is defined + - query_one_name is not changed + + always: + - name: Delete stack + meraki_switch_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 + 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_storm_control/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_storm_control/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_storm_control/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_storm_control/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_storm_control/tasks/main.yml new file mode 100644 index 00000000..e4f9fc10 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switch_storm_control/tasks/main.yml @@ -0,0 +1,107 @@ +# 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) +--- +- block: + - name: Create switch network + 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 + + - set_fact: + net_id: '{{net.data.id}}' + + - name: Add device to network + 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 + meraki_switch_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 + meraki_switch_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 + + - 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 + meraki_switch_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 + + - 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 + meraki_switch_storm_control: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + net_name: Home - Switch + delegate_to: localhost + register: query_all + + - debug: + var: query_all + + - assert: + that: + - query_all.data is defined + + # always: + # - name: Delete switch network + # meraki_network: + # auth_key: '{{ auth_key }}' + # state: absent + # org_name: '{{test_org_name}}' + # net_name: Home - Switch + # delegate_to: localhost diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switchport/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switchport/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switchport/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switchport/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switchport/tasks/main.yml new file mode 100644 index 00000000..06f0dcfe --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_switchport/tasks/main.yml @@ -0,0 +1,354 @@ +# 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 + fail: + msg: Please define an API key + when: auth_key is not defined + +- name: Query all switchports + meraki_switchport: + auth_key: '{{auth_key}}' + state: query + serial: '{{ serial_switch }}' + delegate_to: localhost + register: query_all + +- debug: + msg: '{{query_all}}' + +- name: Query one switchport + meraki_switchport: + auth_key: '{{auth_key}}' + state: query + serial: '{{ serial_switch }}' + number: 1 + delegate_to: localhost + register: query_one + +- debug: + msg: '{{query_one}}' + +- name: Enable switchport with check mode + meraki_switchport: + auth_key: '{{auth_key}}' + state: present + serial: '{{ serial_switch }}' + number: 7 + enabled: true + delegate_to: localhost + check_mode: yes + register: update_port_true_check + +- debug: + msg: '{{update_port_true_check}}' + +- assert: + that: + - update_port_true_check is changed + - update_port_true_check.data is defined + +- name: Enable switchport + meraki_switchport: + auth_key: '{{auth_key}}' + state: present + serial: '{{ serial_switch }}' + number: 7 + enabled: true + delegate_to: localhost + register: update_port_true + +- debug: + msg: '{{update_port_true}}' + +- assert: + that: + - update_port_true.data.enabled == True + +- name: Disable switchport + meraki_switchport: + auth_key: '{{auth_key}}' + state: present + serial: '{{ serial_switch }}' + number: 7 + enabled: false + delegate_to: localhost + register: update_port_false + +- debug: + msg: '{{update_port_false}}' + +- assert: + that: + - update_port_false.data.enabled == False + + +- name: Name switchport + meraki_switchport: + auth_key: '{{auth_key}}' + state: present + serial: '{{ serial_switch }}' + number: 7 + name: Test Port + delegate_to: localhost + register: update_port_name + +- debug: + msg: '{{update_port_name}}' + +- assert: + that: + - update_port_name.data.name == 'Test Port' + +- name: Configure access port with check mode + meraki_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: yes + register: update_access_port_changed + +- debug: + msg: '{{update_access_port_changed}}' + +- assert: + that: + - update_access_port_changed is changed + - update_access_port_changed.data is defined + +- name: Configure access port + meraki_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 + +- debug: + msg: '{{update_access_port}}' + +- assert: + that: + - update_access_port.data.vlan == 10 + +- name: Configure port as trunk + meraki_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: Convert trunk port to access + meraki_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 + meraki_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 + +- assert: + that: + - convert_idempotent.changed == False + +- name: Change voice VLAN for next task + meraki_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 + meraki_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 + +- debug: + msg: '{{update_port_vvlan}}' + +- assert: + that: + - update_port_vvlan.data.voice_vlan == 11 + - update_port_vvlan.changed == True + +- name: Check access port for idempotenty + meraki_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 + +- debug: + msg: '{{update_port_access_idempotent}}' + +- assert: + that: + - update_port_access_idempotent.changed == False + - update_port_access_idempotent.data is defined + +- name: Configure trunk port + meraki_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 + +- debug: + msg: '{{update_trunk}}' + +- 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 + meraki_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 + +- debug: + msg: '{{update_trunk}}' + +- 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 + meraki_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 + +- debug: + msg: '{{update_trunk}}' + +- 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 + meraki_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 + +- debug: + msg: '{{update_trunk_idempotent}}' + +- assert: + that: + - update_trunk_idempotent.changed == False + - update_trunk_idempotent.data is defined diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/tasks/main.yml new file mode 100644 index 00000000..397d1d72 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_syslog/tasks/main.yml @@ -0,0 +1,153 @@ +# 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 no timezone + meraki_network: + auth_key: '{{ auth_key }}' + state: present + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + type: appliance + 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 + delegate_to: localhost + register: create_server + + - assert: + that: + - create_server['data']['servers'][0]['host'] == "192.0.1.2" + + - 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 + delegate_to: localhost + register: create_server_idempotency + + - assert: + that: + - create_server_idempotency.changed == False + - create_server_idempotency.data is defined + + - 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 servers" 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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_vlan/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_vlan/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_vlan/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_vlan/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_vlan/tasks/main.yml new file mode 100644 index 00000000..568ee680 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_vlan/tasks/main.yml @@ -0,0 +1,471 @@ +# 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}}' + type: appliance + delegate_to: localhost + + - name: Enable VLANs on network + meraki_network: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + enable_vlans: yes + delegate_to: localhost + + - name: Create VLAN in check mode + meraki_vlan: + auth_key: '{{auth_key}}' + state: present + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + 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_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + delegate_to: localhost diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/aliases b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/aliases new file mode 100644 index 00000000..ad7ccf7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/aliases @@ -0,0 +1 @@ +unsupported diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/main.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/main.yml new file mode 100644 index 00000000..f671fc92 --- /dev/null +++ b/collections-debian-merged/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/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/tests.yml b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/tests.yml new file mode 100644 index 00000000..0791feaf --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/integration/targets/meraki_webhook/tasks/tests.yml @@ -0,0 +1,274 @@ +# 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) +--- +- 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}}' + type: appliance + + - name: Create webhook with check mode + 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: yes + register: create_one_check + + - debug: + var: create_one_check + + - assert: + that: + - create_one_check is changed + - create_one_check.data is defined + + - name: Create webhook + 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 + + - debug: + var: create_one + + - assert: + that: + - create_one is changed + - create_one.data is defined + + - set_fact: + webhook_id: '{{create_one.data.id}}' + + # - name: Query one webhook + # meraki_webhook: + # auth_key: '{{auth_key}}' + # state: query + # org_name: '{{test_org_name}}' + # net_name: '{{test_net_name}}' + # name: Test_Hook + # register: query_one + + # - debug: + # var: query_one + + # - assert: + # that: + # - query_one.data is defined + + # - name: Query one webhook with id + # 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 + + # - debug: + # var: query_one_id + + # - assert: + # that: + # - query_one_id.data is defined + + # - name: Update webhook with check mode + # 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: yes + # register: update_check + + # - assert: + # that: + # - update_check is changed + # - update_check.data is defined + + - name: Update webhook + 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 + + - debug: + var: update + + - assert: + that: + - update is changed + - update.data is defined + - update.diff.before.shared_secret == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER' + + - name: Update webhook with idempotency + 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 + + - debug: + var: update_idempotent + + - assert: + that: + - update_idempotent is not changed + - update_idempotent.data is defined + + - name: Update webhook with id + 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 + + - debug: + var: update_id + + - assert: + that: + - update_id is changed + - update_id.data is defined + + - name: Create test webhook + 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 + + - set_fact: + test_id: '{{webhook_test.data.id}}' + + - debug: + var: test_id + + - name: Get webhook status + 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 + + - debug: + var: webhook_test_status + + - assert: + that: + - webhook_test_status.data is defined + + - name: Query all webhooks + meraki_webhook: + auth_key: '{{auth_key}}' + state: query + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + register: query_all + + - debug: + var: query_all + + - name: Delete webhook invalid webhook + meraki_webhook: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + name: Test_Hook_Invalid + check_mode: yes + register: delete_invalid + ignore_errors: yes + + - debug: + var: delete_invalid + + - assert: + that: + - 'delete_invalid.msg == "There is no webhook with the name Test_Hook_Invalid"' + + - name: Delete webhook in check mode + meraki_webhook: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + name: Test_Hook + check_mode: yes + register: delete_check + + - debug: + var: delete_check + + - assert: + that: + - delete_check is changed + + - name: Delete webhook + meraki_webhook: + auth_key: '{{auth_key}}' + state: absent + org_name: '{{test_org_name}}' + net_name: '{{test_net_name}}' + name: Test_Hook + register: delete + + - debug: + var: delete + + - assert: + that: + - delete is changed + + ############################################################################# + # 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}}' diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/sanity/ignore-2.10.txt b/collections-debian-merged/ansible_collections/cisco/meraki/tests/sanity/ignore-2.10.txt new file mode 100644 index 00000000..e15daac0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/cisco/meraki/tests/sanity/ignore-2.10.txt @@ -0,0 +1,2 @@ +plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:invalid-argument-name +plugins/modules/meraki_mx_intrusion_prevention.py validate-modules:nonexistent-parameter-documented
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/sanity/ignore-2.11.txt b/collections-debian-merged/ansible_collections/cisco/meraki/tests/sanity/ignore-2.11.txt new file mode 100644 index 00000000..f6906e11 --- /dev/null +++ b/collections-debian-merged/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_mx_intrusion_prevention.py validate-modules:nonexistent-parameter-documented diff --git a/collections-debian-merged/ansible_collections/cisco/meraki/tests/sanity/ignore-2.9.txt b/collections-debian-merged/ansible_collections/cisco/meraki/tests/sanity/ignore-2.9.txt new file mode 100644 index 00000000..00914499 --- /dev/null +++ b/collections-debian-merged/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 |