summaryrefslogtreecommitdiffstats
path: root/collections-debian-merged/ansible_collections/cisco/meraki/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'collections-debian-merged/ansible_collections/cisco/meraki/plugins')
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/doc_fragments/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/doc_fragments/meraki.py84
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/module_utils/network/meraki/meraki.py517
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_admin.py504
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_alert.py337
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_config_template.py331
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_content_filtering.py282
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_device.py432
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_firewalled_services.py233
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_intrusion_prevention.py366
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_malware.py264
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_management_interface.py384
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_l3_firewall.py288
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_rf_profile.py663
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_settings.py222
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mr_ssid.py614
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_access_list.py319
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_l3_interface.py373
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_link_aggregation.py258
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_ospf.py322
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_stack.py277
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_storm_control.py201
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ms_switchport.py424
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_content_filtering.py282
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_intrusion_prevention.py366
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l2_interface.py273
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l3_firewall.py365
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_l7_firewall.py479
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_malware.py264
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_nat.py679
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_firewall.py330
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_site_to_site_vpn.py250
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_static_route.py392
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink.py325
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_uplink_bandwidth.py325
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_mx_vlan.py583
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_nat.py679
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_network.py412
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_organization.py242
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_site_to_site_vpn.py250
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_snmp.py387
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_ssid.py614
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_static_route.py392
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_access_list.py319
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_stack.py277
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switch_storm_control.py201
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_switchport.py424
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_syslog.py260
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_vlan.py583
-rw-r--r--collections-debian-merged/ansible_collections/cisco/meraki/plugins/modules/meraki_webhook.py347
52 files changed, 17995 insertions, 0 deletions
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()