mirror of https://github.com/ansible/ansible.git
Migrated to cisco.meraki
parent
5aa37733c3
commit
8a3f3e41f8
@ -1,461 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This code is part of Ansible, but is an independent component
|
||||
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
|
||||
# Copyright: (c) 2018, Kevin Breit <kevin.breit@kevinbreit.net>
|
||||
# All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
|
||||
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, to_bytes, to_text
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def _error_report(function):
|
||||
def inner(self, *args, **kwargs):
|
||||
while True:
|
||||
try:
|
||||
response = function(self, *args, **kwargs)
|
||||
if self.status == 429:
|
||||
raise RateLimitException(
|
||||
"Rate limiter hit, retry {0}".format(self.retry))
|
||||
elif self.status == 500:
|
||||
raise InternalErrorException(
|
||||
"Internal server error 500, retry {0}".format(self.retry))
|
||||
elif self.status == 502:
|
||||
raise InternalErrorException(
|
||||
"Internal server error 502, retry {0}".format(self.retry))
|
||||
elif self.status >= 400:
|
||||
raise HTTPError("HTTP error {0} - {1}".format(self.status, response))
|
||||
self.retry = 0 # Needs to reset in case of future retries
|
||||
return response
|
||||
except RateLimitException as e:
|
||||
self.retry += 1
|
||||
if self.retry <= 10:
|
||||
self.retry_time += self.retry * RATE_LIMIT_RETRY_MULTIPLIER
|
||||
time.sleep(self.retry * RATE_LIMIT_RETRY_MULTIPLIER)
|
||||
else:
|
||||
self.retry_time += 30
|
||||
time.sleep(30)
|
||||
if self.retry_time > self.params['rate_limit_retry_time']:
|
||||
raise RateLimitException(e)
|
||||
except InternalErrorException as e:
|
||||
self.retry += 1
|
||||
if self.retry <= 10:
|
||||
self.retry_time += self.retry * INTERNAL_ERROR_RETRY_MULTIPLIER
|
||||
time.sleep(self.retry * INTERNAL_ERROR_RETRY_MULTIPLIER)
|
||||
else:
|
||||
self.retry_time += 9
|
||||
time.sleep(9)
|
||||
if self.retry_time > self.params['internal_error_retry_time']:
|
||||
raise InternalErrorException(e)
|
||||
except HTTPError as e:
|
||||
raise HTTPError(e)
|
||||
return inner
|
||||
|
||||
|
||||
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
|
||||
|
||||
# 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',
|
||||
'X-Cisco-Meraki-API-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):
|
||||
''' Compare two data-structures '''
|
||||
self.ignored_keys.append('net_id')
|
||||
if optional_ignore is not None:
|
||||
self.ignored_keys = self.ignored_keys + optional_ignore
|
||||
|
||||
if isinstance(original, list):
|
||||
if len(original) != len(proposed):
|
||||
# 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):
|
||||
# self.fail_json(msg="List doesn't match", a=a, b=b)
|
||||
return True
|
||||
elif isinstance(original, dict):
|
||||
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]):
|
||||
return True
|
||||
else:
|
||||
# self.fail_json(msg="Key not in original", k=k)
|
||||
return True
|
||||
else:
|
||||
if original != proposed:
|
||||
# self.fail_json(msg="Fallback", original=original, proposed=proposed)
|
||||
return True
|
||||
return False
|
||||
|
||||
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')
|
||||
r = self.request(path, method='GET')
|
||||
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 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
|
||||
|
||||
@_error_report
|
||||
def request(self, path, method=None, payload=None):
|
||||
"""Generic HTTP method for Meraki requests."""
|
||||
self.path = path
|
||||
self.define_protocol()
|
||||
|
||||
if method is not None:
|
||||
self.method = method
|
||||
self.url = '{protocol}://{host}/api/v0/{path}'.format(path=self.path.lstrip('/'), **self.params)
|
||||
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.response = info['msg']
|
||||
self.status = info['status']
|
||||
|
||||
try:
|
||||
return json.loads(to_native(resp.read()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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.", version=2.13)
|
||||
else:
|
||||
if 'data' in self.result:
|
||||
try:
|
||||
self.result['data'] = self.convert_camel_to_snake(self.result['data'])
|
||||
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)
|
@ -1,498 +0,0 @@
|
||||
#!/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: '2.6'
|
||||
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.
|
||||
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.
|
||||
suboptions:
|
||||
id:
|
||||
description:
|
||||
- Network ID 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: 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, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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 = json.loads(networks)
|
||||
networks_new = []
|
||||
for n in networks:
|
||||
networks_new.append({'id': meraki.get_net_id(org_name=meraki.params['org_name'],
|
||||
net_name=n['network'],
|
||||
data=nets),
|
||||
'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'] = json.loads(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:
|
||||
diff = recursive_diff(is_admin_existing, payload)
|
||||
is_admin_existing.update(payload)
|
||||
meraki.result['diff'] = {'before': diff[0],
|
||||
'after': diff[1],
|
||||
}
|
||||
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
|
||||
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='json'),
|
||||
networks=dict(type='json'),
|
||||
org_name=dict(type='str', aliases=['organization']),
|
||||
org_id=dict(type='str'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='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
|
||||
|
||||
payload = None
|
||||
|
||||
# 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()
|
@ -1,331 +0,0 @@
|
||||
#!/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: "2.7"
|
||||
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
|
||||
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']
|
||||
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: 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
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.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'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='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
|
||||
|
||||
payload = None
|
||||
|
||||
# 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 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 nets is None:
|
||||
nets = meraki.get_nets(org_id=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()
|
@ -1,248 +0,0 @@
|
||||
#!/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_content_filtering
|
||||
short_description: Edit Meraki MX content filtering policies
|
||||
version_added: "2.8"
|
||||
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
|
||||
blocked_urls:
|
||||
description:
|
||||
- List of URL patterns which should be blocked.
|
||||
type: list
|
||||
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
|
||||
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
|
||||
version_added: '2.9'
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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:
|
||||
id:
|
||||
description: Identification string of network.
|
||||
returned: success
|
||||
type: str
|
||||
sample: N_12345
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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'),
|
||||
blocked_urls=dict(type='list'),
|
||||
blocked_categories=dict(type='list'),
|
||||
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}/contentFiltering/categories'}
|
||||
policy_urls = {'content_filtering': '/networks/{net_id}/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 = 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']:
|
||||
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:
|
||||
meraki.result['diff'] = dict()
|
||||
diff = recursive_diff(current, payload)
|
||||
meraki.result['diff']['before'] = diff[0]
|
||||
meraki.result['diff']['after'] = diff[1]
|
||||
if module.check_mode:
|
||||
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
|
||||
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()
|
@ -1,430 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.7"
|
||||
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: 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
|
||||
serial_lldp_cdp:
|
||||
description:
|
||||
- Serial number of device to query LLDP/CDP information from.
|
||||
type: str
|
||||
lldp_cdp_timespan:
|
||||
description:
|
||||
- Timespan, in seconds, used to query LLDP and CDP information.
|
||||
- Must be less than 1 month.
|
||||
type: int
|
||||
serial_uplink:
|
||||
description:
|
||||
- Serial number of device to query uplink information from.
|
||||
type: str
|
||||
note:
|
||||
description:
|
||||
- Informational notes about a device.
|
||||
- Limited to 255 characters.
|
||||
version_added: '2.8'
|
||||
type: str
|
||||
|
||||
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
|
||||
|
||||
|
||||
def format_tags(tags):
|
||||
return " {tags} ".format(tags=tags)
|
||||
|
||||
|
||||
def is_device_valid(meraki, serial, data):
|
||||
for device in data:
|
||||
if device['serial'] == serial:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_org_devices(meraki, org_id):
|
||||
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 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'),
|
||||
serial_uplink=dict(type='str'),
|
||||
serial_lldp_cdp=dict(type='str'),
|
||||
lldp_cdp_timespan=dict(type='int'),
|
||||
hostname=dict(type='str', aliases=['name']),
|
||||
model=dict(type='str'),
|
||||
tags=dict(type='str'),
|
||||
lat=dict(type='float', aliases=['latitude']),
|
||||
lng=dict(type='float', aliases=['longitude']),
|
||||
address=dict(type='str'),
|
||||
move_map_marker=dict(type='bool'),
|
||||
note=dict(type='str'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='device')
|
||||
|
||||
if meraki.params['serial_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}/inventory'}
|
||||
query_device_urls = {'device': '/networks/{net_id}/devices/'}
|
||||
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/'}
|
||||
|
||||
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['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
|
||||
|
||||
# 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
|
||||
# FIXME: Work with Meraki so they can implement a check mode
|
||||
if module.check_mode:
|
||||
meraki.exit_json(**meraki.result)
|
||||
|
||||
# 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) + meraki.params['serial']
|
||||
request = meraki.request(path, method='GET')
|
||||
device.append(request)
|
||||
meraki.result['data'] = device
|
||||
elif meraki.params['serial_uplink']:
|
||||
path = meraki.construct_path('get_device', net_id=net_id) + meraki.params['serial_uplink'] + '/uplink'
|
||||
meraki.result['data'] = (meraki.request(path, method='GET'))
|
||||
elif meraki.params['serial_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', net_id=net_id) + meraki.params['serial_lldp_cdp'] + '/lldp_cdp'
|
||||
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)
|
||||
devices = meraki.request(path, method='GET')
|
||||
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 meraki.params['hostname']:
|
||||
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):
|
||||
payload = {'name': meraki.params['hostname'],
|
||||
'tags': format_tags(meraki.params['tags']),
|
||||
'lat': meraki.params['lat'],
|
||||
'lng': meraki.params['lng'],
|
||||
'address': meraki.params['address'],
|
||||
'moveMapMarker': meraki.params['move_map_marker'],
|
||||
'notes': meraki.params['note'],
|
||||
}
|
||||
query_path = meraki.construct_path('get_device', net_id=net_id) + meraki.params['serial']
|
||||
device_data = meraki.request(query_path, method='GET')
|
||||
ignore_keys = ['lanIp', 'serial', 'mac', 'model', 'networkId', 'moveMapMarker', 'wan1Ip', 'wan2Ip']
|
||||
# meraki.fail_json(msg="Compare", original=device_data, payload=payload, ignore=ignore_keys)
|
||||
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:
|
||||
if net_id is None:
|
||||
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:
|
||||
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 = {'serial': 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)
|
||||
path = path + meraki.params['serial'] + '/remove'
|
||||
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()
|
@ -1,238 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.9"
|
||||
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
|
||||
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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}/firewalledServices'}
|
||||
services_urls = {'firewalled_services': '/networks/{net_id}/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)
|
||||
diff = recursive_diff(original, diff_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)
|
||||
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:
|
||||
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()
|
@ -1,276 +0,0 @@
|
||||
#!/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_malware
|
||||
short_description: Manage Malware Protection in the Meraki cloud
|
||||
version_added: "2.9"
|
||||
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.
|
||||
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.
|
||||
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]
|
||||
|
||||
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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.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
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='malware')
|
||||
|
||||
meraki.params['follow_redirects'] = 'all'
|
||||
|
||||
query_url = {'malware': '/networks/{net_id}/security/malwareSettings'}
|
||||
update_url = {'malware': '/networks/{net_id}/security/malwareSettings'}
|
||||
|
||||
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:
|
||||
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)
|
||||
path = meraki.construct_path('update', net_id=net_id)
|
||||
data = meraki.request(path, method='PUT', payload=json.dumps(payload))
|
||||
if meraki.status == 200:
|
||||
diff = recursive_diff(original, payload)
|
||||
meraki.result['diff'] = {'before': diff[0],
|
||||
'after': diff[1],
|
||||
}
|
||||
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()
|
@ -1,283 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.7"
|
||||
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: int
|
||||
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
|
||||
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: 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'''
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.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),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='mr_l3_firewall')
|
||||
|
||||
meraki.params['follow_redirects'] = 'all'
|
||||
|
||||
query_urls = {'mr_l3_firewall': '/networks/{net_id}/ssids/{number}/l3FirewallRules'}
|
||||
update_urls = {'mr_l3_firewall': '/networks/{net_id}/ssids/{number}/l3FirewallRules'}
|
||||
|
||||
meraki.url_catalog['get_all'].update(query_urls)
|
||||
meraki.url_catalog['update'] = update_urls
|
||||
|
||||
payload = None
|
||||
|
||||
# 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
|
||||
# FIXME: Work with Meraki so they can implement a check mode
|
||||
if module.check_mode:
|
||||
meraki.exit_json(**meraki.result)
|
||||
|
||||
# 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
|
||||
if rules[len(rules) - 2] != meraki.params['allow_lan_access']:
|
||||
update = True
|
||||
if update is True:
|
||||
payload['allowLanAccess'] = meraki.params['allow_lan_access']
|
||||
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()
|
@ -1,328 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.7"
|
||||
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
|
||||
net_name:
|
||||
description:
|
||||
- Name of network which MX firewall is in.
|
||||
net_id:
|
||||
description:
|
||||
- ID of network which MX firewall is in.
|
||||
rules:
|
||||
description:
|
||||
- List of firewall rules.
|
||||
suboptions:
|
||||
policy:
|
||||
description:
|
||||
- Policy to apply if rule is hit.
|
||||
choices: [allow, deny]
|
||||
protocol:
|
||||
description:
|
||||
- Protocol to match against.
|
||||
choices: [any, icmp, tcp, udp]
|
||||
dest_port:
|
||||
description:
|
||||
- Comma separated list of destination port numbers to match against.
|
||||
dest_cidr:
|
||||
description:
|
||||
- Comma separated list of CIDR notation destination networks.
|
||||
src_port:
|
||||
description:
|
||||
- Comma separated list of source port numbers to match against.
|
||||
src_cidr:
|
||||
description:
|
||||
- Comma separated list of CIDR notation source networks.
|
||||
comment:
|
||||
description:
|
||||
- Optional comment to describe the firewall rule.
|
||||
syslog_enabled:
|
||||
description:
|
||||
- Whether to log hints against the firewall rule.
|
||||
- Only applicable if a syslog server is specified against the network.
|
||||
|
||||
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
|
||||
default: no
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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:
|
||||
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
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.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 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'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='mx_l3_firewall')
|
||||
|
||||
meraki.params['follow_redirects'] = 'all'
|
||||
|
||||
query_urls = {'mx_l3_firewall': '/networks/{net_id}/l3FirewallRules/'}
|
||||
update_urls = {'mx_l3_firewall': '/networks/{net_id}/l3FirewallRules/'}
|
||||
|
||||
meraki.url_catalog['get_all'].update(query_urls)
|
||||
meraki.url_catalog['update'] = update_urls
|
||||
|
||||
payload = None
|
||||
|
||||
# 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
|
||||
# FIXME: Work with Meraki so they can implement a check mode
|
||||
if module.check_mode:
|
||||
meraki.exit_json(**meraki.result)
|
||||
|
||||
# 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))
|
||||
|
||||
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']:
|
||||
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 len(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[len(rules) - 1]['syslogEnabled'] != meraki.params['syslog_default_rule']:
|
||||
update = True
|
||||
if update is False:
|
||||
default_rule = rules[len(rules) - 1].copy()
|
||||
del rules[len(rules) - 1] # Remove default rule for comparison
|
||||
for r in range(len(rules) - 1):
|
||||
if meraki.is_update_required(rules[r], payload['rules'][r]) is True:
|
||||
update = True
|
||||
rules.append(default_rule)
|
||||
except KeyError:
|
||||
pass
|
||||
if update is True:
|
||||
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()
|
@ -1,506 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.9"
|
||||
description:
|
||||
- Allows for creation, management, and visibility into layer 7 firewalls implemented on Meraki MX firewalls.
|
||||
notes:
|
||||
- Module assumes a complete list of firewall rules are passed as a parameter.
|
||||
- If there is interest in this module allowing manipulation of a single firewall rule, please submit an issue against this module.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Query or modify a firewall rule.
|
||||
choices: ['present', 'query']
|
||||
default: present
|
||||
type: str
|
||||
net_name:
|
||||
description:
|
||||
- Name of network which MX firewall is in.
|
||||
type: str
|
||||
net_id:
|
||||
description:
|
||||
- ID of network which MX firewall is in.
|
||||
type: str
|
||||
rules:
|
||||
description:
|
||||
- List of layer 7 firewall rules.
|
||||
type: list
|
||||
suboptions:
|
||||
policy:
|
||||
description:
|
||||
- Policy to apply if rule is hit.
|
||||
choices: [deny]
|
||||
default: deny
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- Type of policy to apply.
|
||||
choices: [application,
|
||||
application_category,
|
||||
blacklisted_countries,
|
||||
host,
|
||||
ip_range,
|
||||
port,
|
||||
whitelisted_countries]
|
||||
type: str
|
||||
application:
|
||||
description:
|
||||
- Application to filter.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Name of application to filter as defined by Meraki.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- URI of application as defined by Meraki.
|
||||
type: str
|
||||
application_category:
|
||||
description:
|
||||
- Category of applications to filter.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Name of application category to filter as defined by Meraki.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- URI of application category as defined by Meraki.
|
||||
type: str
|
||||
host:
|
||||
description:
|
||||
- FQDN of host to filter.
|
||||
type: str
|
||||
ip_range:
|
||||
description:
|
||||
- CIDR notation range of IP addresses to apply rule to.
|
||||
- Port can be appended to range with a C(":").
|
||||
type: str
|
||||
port:
|
||||
description:
|
||||
- TCP or UDP based port to filter.
|
||||
type: str
|
||||
countries:
|
||||
description:
|
||||
- List of countries to whitelist or blacklist.
|
||||
- The countries follow the two-letter ISO 3166-1 alpha-2 format.
|
||||
type: list
|
||||
categories:
|
||||
description:
|
||||
- When C(True), specifies that applications and application categories should be queried instead of firewall rules.
|
||||
type: bool
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: meraki
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Query firewall rules
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: abc123
|
||||
org_name: YourOrg
|
||||
net_name: YourNet
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query applications and application categories
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: abc123
|
||||
org_name: YourOrg
|
||||
net_name: YourNet
|
||||
categories: yes
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Set firewall rules
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: abc123
|
||||
org_name: YourOrg
|
||||
net_name: YourNet
|
||||
state: present
|
||||
rules:
|
||||
- type: whitelisted_countries
|
||||
countries:
|
||||
- US
|
||||
- FR
|
||||
- type: blacklisted_countries
|
||||
countries:
|
||||
- CN
|
||||
- policy: deny
|
||||
type: port
|
||||
port: 8080
|
||||
- type: port
|
||||
port: 1234
|
||||
- type: host
|
||||
host: asdf.com
|
||||
- type: application
|
||||
application:
|
||||
id: meraki:layer7/application/205
|
||||
- type: application_category
|
||||
application:
|
||||
id: meraki:layer7/category/24
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
data:
|
||||
description: Firewall rules associated to network.
|
||||
returned: success
|
||||
type: complex
|
||||
contains:
|
||||
rules:
|
||||
description: Ordered list of firewall rules.
|
||||
returned: success, when not querying applications
|
||||
type: list
|
||||
contains:
|
||||
policy:
|
||||
description: Action to apply when rule is hit.
|
||||
returned: success
|
||||
type: 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
|
||||
whitelistedCountries:
|
||||
description: Countries to be whitelisted.
|
||||
returned: success
|
||||
type: str
|
||||
sample: CA
|
||||
blacklistedCountries:
|
||||
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
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
|
||||
|
||||
|
||||
def get_applications(meraki, net_id):
|
||||
path = meraki.construct_path('get_categories', net_id=net_id)
|
||||
return meraki.request(path, method='GET')
|
||||
|
||||
|
||||
def lookup_application(meraki, net_id, application):
|
||||
response = get_applications(meraki, net_id)
|
||||
for category in response['applicationCategories']:
|
||||
if category['name'].lower() == application.lower():
|
||||
return category['id']
|
||||
for app in category['applications']:
|
||||
if app['name'].lower() == application.lower():
|
||||
return app['id']
|
||||
meraki.fail_json(msg="No application or category named {0} found".format(application))
|
||||
|
||||
|
||||
def assemble_payload(meraki, net_id, rule):
|
||||
if rule['type'] == 'application':
|
||||
new_rule = {'policy': rule['policy'],
|
||||
'type': 'application',
|
||||
}
|
||||
if rule['application']['id']:
|
||||
new_rule['value'] = {'id': rule['application']['id']}
|
||||
elif rule['application']['name']:
|
||||
new_rule['value'] = {'id': lookup_application(meraki, net_id, rule['application']['name'])}
|
||||
elif rule['type'] == 'application_category':
|
||||
new_rule = {'policy': rule['policy'],
|
||||
'type': 'applicationCategory',
|
||||
}
|
||||
if rule['application']['id']:
|
||||
new_rule['value'] = {'id': rule['application']['id']}
|
||||
elif rule['application']['name']:
|
||||
new_rule['value'] = {'id': lookup_application(meraki, net_id, rule['application']['name'])}
|
||||
elif rule['type'] == 'ip_range':
|
||||
new_rule = {'policy': rule['policy'],
|
||||
'type': 'ipRange',
|
||||
'value': rule['ip_range']}
|
||||
elif rule['type'] == 'host':
|
||||
new_rule = {'policy': rule['policy'],
|
||||
'type': rule['type'],
|
||||
'value': rule['host']}
|
||||
elif rule['type'] == 'port':
|
||||
new_rule = {'policy': rule['policy'],
|
||||
'type': rule['type'],
|
||||
'value': rule['port']}
|
||||
elif rule['type'] == 'blacklisted_countries':
|
||||
new_rule = {'policy': rule['policy'],
|
||||
'type': 'blacklistedCountries',
|
||||
'value': rule['countries']
|
||||
}
|
||||
elif rule['type'] == 'whitelisted_countries':
|
||||
new_rule = {'policy': rule['policy'],
|
||||
'type': 'whitelistedCountries',
|
||||
'value': rule['countries']
|
||||
}
|
||||
return new_rule
|
||||
|
||||
|
||||
def restructure_response(rules):
|
||||
for rule in rules['rules']:
|
||||
type = rule['type']
|
||||
rule[type] = copy.deepcopy(rule['value'])
|
||||
del rule['value']
|
||||
return rules
|
||||
|
||||
|
||||
def get_rules(meraki, net_id):
|
||||
path = meraki.construct_path('get_all', net_id=net_id)
|
||||
response = meraki.request(path, method='GET')
|
||||
if meraki.status == 200:
|
||||
return response
|
||||
|
||||
|
||||
def rename_id_to_appid(rules):
|
||||
for rule in rules['rules']:
|
||||
print(rule['type'])
|
||||
if rule['type'] == 'application' or rule['type'] == 'applicationCategory':
|
||||
rule['value']['appId'] = rule['value'].pop('id')
|
||||
return rules
|
||||
|
||||
|
||||
def rename_appid_to_id(rules):
|
||||
for rule in rules['rules']:
|
||||
if rule['type'] == 'application' or rule['type'] == 'applicationCategory':
|
||||
rule['value']['id'] = rule['value'].pop('appId')
|
||||
return rules
|
||||
|
||||
|
||||
def main():
|
||||
# define the available arguments/parameters that a user can pass to
|
||||
# the module
|
||||
|
||||
application_arg_spec = dict(id=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
)
|
||||
|
||||
rule_arg_spec = dict(policy=dict(type='str', choices=['deny'], default='deny'),
|
||||
type=dict(type='str', choices=['application',
|
||||
'application_category',
|
||||
'blacklisted_countries',
|
||||
'host',
|
||||
'ip_range',
|
||||
'port',
|
||||
'whitelisted_countries']),
|
||||
ip_range=dict(type='str'),
|
||||
application=dict(type='dict', default=None, options=application_arg_spec),
|
||||
host=dict(type='str'),
|
||||
port=dict(type='str'),
|
||||
countries=dict(type='list'),
|
||||
)
|
||||
|
||||
argument_spec = meraki_argument_spec()
|
||||
argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
|
||||
net_name=dict(type='str'),
|
||||
net_id=dict(type='str'),
|
||||
rules=dict(type='list', default=None, elements='dict', options=rule_arg_spec),
|
||||
categories=dict(type='bool'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='mx_l7_firewall')
|
||||
|
||||
# check for argument completeness
|
||||
if meraki.params['rules']:
|
||||
for rule in meraki.params['rules']:
|
||||
if rule['type'] == 'application' and rule['application'] is None:
|
||||
meraki.fail_json(msg="application argument is required when type is application.")
|
||||
elif rule['type'] == 'application_category' and rule['application'] is None:
|
||||
meraki.fail_json(msg="application argument is required when type is application_category.")
|
||||
elif rule['type'] == 'blacklisted_countries' and rule['countries'] is None:
|
||||
meraki.fail_json(msg="countries argument is required when type is blacklisted_countries.")
|
||||
elif rule['type'] == 'host' and rule['host'] is None:
|
||||
meraki.fail_json(msg="host argument is required when type is host.")
|
||||
elif rule['type'] == 'port' and rule['port'] is None:
|
||||
meraki.fail_json(msg="port argument is required when type is port.")
|
||||
elif rule['type'] == 'whitelisted_countries' and rule['countries'] is None:
|
||||
meraki.fail_json(msg="countries argument is required when type is whitelisted_countries.")
|
||||
|
||||
meraki.params['follow_redirects'] = 'all'
|
||||
|
||||
query_urls = {'mx_l7_firewall': '/networks/{net_id}/l7FirewallRules/'}
|
||||
query_category_urls = {'mx_l7_firewall': '/networks/{net_id}/l7FirewallRules/applicationCategories'}
|
||||
update_urls = {'mx_l7_firewall': '/networks/{net_id}/l7FirewallRules/'}
|
||||
|
||||
meraki.url_catalog['get_all'].update(query_urls)
|
||||
meraki.url_catalog['get_categories'] = (query_category_urls)
|
||||
meraki.url_catalog['update'] = update_urls
|
||||
|
||||
payload = None
|
||||
|
||||
# manipulate or modify the state as needed (this is going to be the
|
||||
# part where your module will do what it needs to do)
|
||||
org_id = meraki.params['org_id']
|
||||
orgs = None
|
||||
if org_id is None:
|
||||
orgs = meraki.get_orgs()
|
||||
for org in orgs:
|
||||
if org['name'] == meraki.params['org_name']:
|
||||
org_id = org['id']
|
||||
net_id = meraki.params['net_id']
|
||||
if net_id is None:
|
||||
if orgs is None:
|
||||
orgs = meraki.get_orgs()
|
||||
net_id = meraki.get_net_id(net_name=meraki.params['net_name'],
|
||||
data=meraki.get_nets(org_id=org_id))
|
||||
|
||||
if meraki.params['state'] == 'query':
|
||||
if meraki.params['categories'] is True: # Output only applications
|
||||
meraki.result['data'] = get_applications(meraki, net_id)
|
||||
else:
|
||||
meraki.result['data'] = restructure_response(get_rules(meraki, net_id))
|
||||
elif meraki.params['state'] == 'present':
|
||||
rules = get_rules(meraki, net_id)
|
||||
path = meraki.construct_path('get_all', net_id=net_id)
|
||||
if meraki.params['rules']:
|
||||
payload = {'rules': []}
|
||||
for rule in meraki.params['rules']:
|
||||
payload['rules'].append(assemble_payload(meraki, net_id, rule))
|
||||
else:
|
||||
payload = dict()
|
||||
|
||||
'''
|
||||
The rename_* functions are needed because the key is id and
|
||||
is_update_required() by default ignores id.
|
||||
'''
|
||||
rules = rename_id_to_appid(rules)
|
||||
payload = rename_id_to_appid(payload)
|
||||
if meraki.is_update_required(rules, payload):
|
||||
rules = rename_appid_to_id(rules)
|
||||
payload = rename_appid_to_id(payload)
|
||||
if meraki.module.check_mode is True:
|
||||
response = restructure_response(payload)
|
||||
diff = recursive_diff(restructure_response(rules), response)
|
||||
meraki.result['diff'] = {'before': diff[0],
|
||||
'after': diff[1],
|
||||
}
|
||||
meraki.result['data'] = response
|
||||
meraki.result['changed'] = True
|
||||
meraki.exit_json(**meraki.result)
|
||||
response = meraki.request(path, method='PUT', payload=json.dumps(payload))
|
||||
response = restructure_response(response)
|
||||
if meraki.status == 200:
|
||||
diff = recursive_diff(restructure_response(rules), response)
|
||||
meraki.result['diff'] = {'before': diff[0],
|
||||
'after': diff[1],
|
||||
}
|
||||
meraki.result['data'] = response
|
||||
meraki.result['changed'] = True
|
||||
else:
|
||||
rules = rename_appid_to_id(rules)
|
||||
payload = rename_appid_to_id(payload)
|
||||
if meraki.module.check_mode is True:
|
||||
meraki.result['data'] = rules
|
||||
meraki.result['changed'] = False
|
||||
meraki.exit_json(**meraki.result)
|
||||
meraki.result['data'] = payload
|
||||
|
||||
# in the event of a successful module execution, you will want to
|
||||
# simple AnsibleModule.exit_json(), passing the key/value results
|
||||
meraki.exit_json(**meraki.result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,673 +0,0 @@
|
||||
#!/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_nat
|
||||
short_description: Manage NAT rules in Meraki cloud
|
||||
version_added: "2.9"
|
||||
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
|
||||
one_to_one:
|
||||
description:
|
||||
- List of 1:1 NAT rules.
|
||||
type: list
|
||||
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
|
||||
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
|
||||
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
|
||||
one_to_many:
|
||||
description:
|
||||
- List of 1:many NAT rules.
|
||||
type: list
|
||||
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
|
||||
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
|
||||
port_forwarding:
|
||||
description:
|
||||
- List of port forwarding rules.
|
||||
type: list
|
||||
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
|
||||
protocol:
|
||||
description:
|
||||
- Protocol to forward traffic for.
|
||||
choices: [tcp, udp]
|
||||
type: str
|
||||
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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', element='str'),
|
||||
allowed_ips=dict(type='list'),
|
||||
)
|
||||
|
||||
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'),
|
||||
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', element='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', element='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'),
|
||||
)
|
||||
|
||||
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', 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}/oneToManyNatRules'}
|
||||
onetoone_urls = {'nat': '/networks/{net_id}/oneToOneNatRules'}
|
||||
port_forwarding_urls = {'nat': '/networks/{net_id}/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()
|
@ -1,390 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.6"
|
||||
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
|
||||
net_name:
|
||||
description:
|
||||
- Name of a network.
|
||||
aliases: [ name, network ]
|
||||
net_id:
|
||||
description:
|
||||
- ID number of a network.
|
||||
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
|
||||
tags:
|
||||
type: list
|
||||
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.
|
||||
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
|
||||
version_added: '2.9'
|
||||
disable_my_meraki:
|
||||
description: >
|
||||
- Disables 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)).
|
||||
- Mutually exclusive of C(enable_my_meraki).
|
||||
- Will be deprecated in Ansible 2.13 in favor of C(enable_my_meraki).
|
||||
type: bool
|
||||
version_added: '2.7'
|
||||
enable_my_meraki:
|
||||
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)).
|
||||
- Ansible 2.7 had this parameter as C(disable_my_meraki).
|
||||
type: bool
|
||||
version_added: '2.9'
|
||||
enable_remote_status_page:
|
||||
description:
|
||||
- Enables access to the device status page (U(http://device LAN IP)).
|
||||
- Can only be set if C(enable_my_meraki:) is set to C(yes).
|
||||
type: bool
|
||||
version_added: '2.9'
|
||||
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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
|
||||
'''
|
||||
|
||||
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: str
|
||||
sample: " production wireless "
|
||||
time_zone:
|
||||
description: Timezone where network resides.
|
||||
returned: success
|
||||
type: str
|
||||
sample: America/Chicago
|
||||
type:
|
||||
description: Functional type of network.
|
||||
returned: success
|
||||
type: str
|
||||
sample: switch
|
||||
disable_my_meraki_com:
|
||||
description: States whether U(my.meraki.com) and other device portals should be disabled.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
disableRemoteStatusPage:
|
||||
description: Disables access to the device status page.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.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 construct_tags(tags):
|
||||
formatted_tags = ' '.join(tags)
|
||||
return ' {0} '.format(formatted_tags) # Meraki needs space padding
|
||||
|
||||
|
||||
def list_to_string(data):
|
||||
new_string = str()
|
||||
for i, item in enumerate(data):
|
||||
if i == len(new_string) - 1:
|
||||
new_string += i
|
||||
else:
|
||||
new_string = "{0}{1} ".format(new_string, item)
|
||||
return new_string.strip()
|
||||
|
||||
|
||||
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', choices=['wireless', 'switch', 'appliance'], aliases=['net_type']),
|
||||
tags=dict(type='list'),
|
||||
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'),
|
||||
disable_my_meraki=dict(type='bool', removed_in_version=2.13),
|
||||
enable_my_meraki=dict(type='bool'),
|
||||
enable_remote_status_page=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=False,
|
||||
mutually_exclusive=[('disable_my_meraki', 'enable_my_meraki'),
|
||||
]
|
||||
)
|
||||
|
||||
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}'}
|
||||
enable_vlans_urls = {'network': '/networks/{net_id}/vlansEnabledState'}
|
||||
get_vlan_status_urls = {'network': '/networks/{net_id}/vlansEnabledState'}
|
||||
meraki.url_catalog['create'] = create_urls
|
||||
meraki.url_catalog['update'] = update_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['enable_my_meraki'] is True and meraki.params['enable_remote_status_page'] is False:
|
||||
meraki.fail_json(msg='enable_my_meraki must be true when setting enable_remote_status_page')
|
||||
|
||||
# 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
|
||||
if module.check_mode:
|
||||
return meraki.result
|
||||
|
||||
# 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['type'] = list_to_string(meraki.params['type'])
|
||||
if meraki.params['tags']:
|
||||
payload['tags'] = construct_tags(meraki.params['tags'])
|
||||
if meraki.params['timezone']:
|
||||
payload['timeZone'] = meraki.params['timezone']
|
||||
if meraki.params['enable_my_meraki'] is not None:
|
||||
if meraki.params['enable_my_meraki'] is True:
|
||||
payload['disableMyMerakiCom'] = False
|
||||
else:
|
||||
payload['disableMyMerakiCom'] = True
|
||||
elif meraki.params['disable_my_meraki'] is not None:
|
||||
payload['disableMyMerakiCom'] = meraki.params['disable_my_meraki']
|
||||
if meraki.params['enable_remote_status_page'] is not None:
|
||||
if meraki.params['enable_remote_status_page'] is True:
|
||||
payload['disableRemoteStatusPage'] = False
|
||||
# meraki.fail_json(msg="Debug", payload=payload)
|
||||
else:
|
||||
payload['disableRemoteStatusPage'] = 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']
|
||||
if not org_id:
|
||||
org_id = meraki.get_org_id(meraki.params['org_name'])
|
||||
nets = meraki.get_nets(org_id=org_id)
|
||||
|
||||
# check if network is created
|
||||
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:
|
||||
meraki.result['data'] = meraki.get_net(meraki.params['org_name'],
|
||||
meraki.params['net_name'],
|
||||
data=nets
|
||||
)
|
||||
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.")
|
||||
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
|
||||
# meraki.fail_json(msg="nets", nets=nets, net_id=net_id)
|
||||
# meraki.fail_json(msg="compare", original=net, payload=payload)
|
||||
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 = {'enabled': meraki.params['enable_vlans']}
|
||||
# meraki.fail_json(msg="here", payload=payload)
|
||||
if meraki.is_update_required(status, payload):
|
||||
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)
|
||||
net = meraki.get_net(meraki.params['org_name'], net_id=net_id, data=nets)
|
||||
if meraki.is_update_required(net, payload):
|
||||
path = meraki.construct_path('update', net_id=net_id)
|
||||
# meraki.fail_json(msg="Payload", path=path, payload=payload)
|
||||
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:
|
||||
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()
|
@ -1,244 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.6"
|
||||
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
|
||||
clone:
|
||||
description:
|
||||
- Organization to clone to a new organization.
|
||||
org_name:
|
||||
description:
|
||||
- Name of organization.
|
||||
- If C(clone) is specified, C(org_name) is the name of the new organization.
|
||||
aliases: [ name, organization ]
|
||||
org_id:
|
||||
description:
|
||||
- ID of organization.
|
||||
aliases: [ id ]
|
||||
type: str
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.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']),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='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.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()
|
@ -1,401 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.6"
|
||||
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:
|
||||
- Semi-colon delimited IP addresses which can perform SNMP queries.
|
||||
type: str
|
||||
net_name:
|
||||
description:
|
||||
- Name of network.
|
||||
type: str
|
||||
version_added: '2.9'
|
||||
net_id:
|
||||
description:
|
||||
- ID of network.
|
||||
type: str
|
||||
version_added: '2.9'
|
||||
access:
|
||||
description:
|
||||
- Type of SNMP access.
|
||||
choices: [community, none, users]
|
||||
type: str
|
||||
version_added: '2.9'
|
||||
community_string:
|
||||
description:
|
||||
- SNMP community string.
|
||||
- Only relevant if C(access) is set to C(community).
|
||||
type: str
|
||||
version_added: '2.9'
|
||||
users:
|
||||
description:
|
||||
- Information about users with access to SNMP.
|
||||
- Only relevant if C(access) is set to C(users).
|
||||
type: list
|
||||
version_added: '2.9'
|
||||
suboptions:
|
||||
username:
|
||||
description: Username of user with access.
|
||||
type: str
|
||||
version_added: '2.9'
|
||||
passphrase:
|
||||
description: Passphrase for user SNMP access.
|
||||
type: str
|
||||
version_added: '2.9'
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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
|
||||
peerIps:
|
||||
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 recursive_diff, snake_dict_to_camel_dict
|
||||
from ansible.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['peer_ips']:
|
||||
if len(meraki.params['peer_ips']) > 7:
|
||||
if ';' not in meraki.params['peer_ips']:
|
||||
meraki.fail_json(msg='Peer IP addresses are semi-colon delimited.')
|
||||
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:
|
||||
diff = recursive_diff(snmp, full_compare)
|
||||
snmp.update(payload)
|
||||
meraki.result['data'] = snmp
|
||||
meraki.result['changed'] = True
|
||||
meraki.result['diff'] = {'before': diff[0],
|
||||
'after': diff[1]}
|
||||
meraki.exit_json(**meraki.result)
|
||||
r = meraki.request(path,
|
||||
method='PUT',
|
||||
payload=json.dumps(payload))
|
||||
if meraki.status == 200:
|
||||
diff = recursive_diff(snmp, r)
|
||||
meraki.result['diff'] = {'before': diff[0],
|
||||
'after': diff[1]}
|
||||
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='str'),
|
||||
access=dict(type='str', choices=['none', 'community', 'users']),
|
||||
community_string=dict(type='str', no_log=True),
|
||||
users=dict(type='list', default=None, elements='', 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}/snmpSettings'}
|
||||
update_urls = {'snmp': '/organizations/{org_id}/snmp'}
|
||||
update_net_urls = {'snmp': '/networks/{net_id}/snmpSettings'}
|
||||
|
||||
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()
|
@ -1,606 +0,0 @@
|
||||
#!/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_ssid
|
||||
short_description: Manage wireless SSIDs in the Meraki cloud
|
||||
version_added: "2.7"
|
||||
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 WPA2 specification.
|
||||
type: str
|
||||
choices: [WPA1 and WPA2, WPA2 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']
|
||||
radius_servers:
|
||||
description:
|
||||
- List of RADIUS servers.
|
||||
type: list
|
||||
suboptions:
|
||||
host:
|
||||
description:
|
||||
- IP address or hostname of RADIUS server.
|
||||
type: str
|
||||
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
|
||||
suboptions:
|
||||
host:
|
||||
description:
|
||||
- IP address or hostname of RADIUS server.
|
||||
type: str
|
||||
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
|
||||
suboptions:
|
||||
tags:
|
||||
description:
|
||||
- List of AP tags.
|
||||
type: list
|
||||
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
|
||||
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: 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.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 main():
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
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']),
|
||||
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']),
|
||||
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'),
|
||||
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'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='ssid')
|
||||
meraki.params['follow_redirects'] = 'all'
|
||||
|
||||
query_urls = {'ssid': '/networks/{net_id}/ssids'}
|
||||
query_url = {'ssid': '/networks/{net_id}/ssids/{number}'}
|
||||
update_url = {'ssid': '/networks/{net_id}/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
|
||||
|
||||
# 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
|
||||
# FIXME: Work with Meraki so they can implement a check mode
|
||||
if module.check_mode:
|
||||
meraki.exit_json(**meraki.result)
|
||||
|
||||
# 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 = dict()
|
||||
for k, v in param_map.items():
|
||||
if meraki.params[v] is not None:
|
||||
payload[k] = meraki.params[v]
|
||||
# Short term solution for camelCase/snake_case differences
|
||||
# Will be addressed later with a method in module utils
|
||||
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
|
||||
try:
|
||||
tags = ','.join(i['tags'])
|
||||
del i['tags']
|
||||
i['tags'] = tags
|
||||
except KeyError:
|
||||
pass
|
||||
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.')
|
||||
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.')
|
||||
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()
|
@ -1,375 +0,0 @@
|
||||
#!/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_static_route
|
||||
short_description: Manage static routes in the Meraki cloud
|
||||
version_added: "2.8"
|
||||
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
|
||||
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
|
||||
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: 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
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.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': meraki.params['route_id']})
|
||||
r = meraki.request(path, method='GET')
|
||||
return r
|
||||
|
||||
|
||||
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}/staticRoutes'}
|
||||
query_one_urls = {'static_route': '/networks/{net_id}/staticRoutes/{route_id}'}
|
||||
create_urls = {'static_route': '/networks/{net_id}/staticRoutes/'}
|
||||
update_urls = {'static_route': '/networks/{net_id}/staticRoutes/{route_id}'}
|
||||
delete_urls = {'static_route': '/networks/{net_id}/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']
|
||||
# meraki.fail_json(msg="payload", payload=payload)
|
||||
if meraki.params['enabled'] is not None:
|
||||
payload['enabled'] = meraki.params['enabled']
|
||||
if meraki.params['route_id']:
|
||||
existing_route = get_static_route(meraki, net_id, meraki.params['route_id'])
|
||||
proposed = existing_route.copy()
|
||||
proposed.update(payload)
|
||||
if module.check_mode:
|
||||
meraki.result['data'] = proposed
|
||||
meraki.result['data'].update(payload)
|
||||
meraki.exit_json(**meraki.result)
|
||||
if meraki.is_update_required(existing_route, proposed, optional_ignore=['id']):
|
||||
path = meraki.construct_path('update', net_id=net_id, custom={'route_id': meraki.params['route_id']})
|
||||
meraki.result['data'] = meraki.request(path, method="PUT", payload=json.dumps(payload))
|
||||
meraki.result['changed'] = True
|
||||
else:
|
||||
meraki.result['data'] = existing_route
|
||||
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()
|
@ -1,397 +0,0 @@
|
||||
#!/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_switchport
|
||||
short_description: Manage switchports on a switch in the Meraki cloud
|
||||
version_added: "2.7"
|
||||
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_number:
|
||||
description:
|
||||
- Number of the access policy to apply.
|
||||
- Only applicable to access port types.
|
||||
allowed_vlans:
|
||||
description:
|
||||
- List of VLAN numbers to be allowed on switchport.
|
||||
default: all
|
||||
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)]
|
||||
name:
|
||||
description:
|
||||
- Switchport description.
|
||||
aliases: [description]
|
||||
number:
|
||||
description:
|
||||
- Port number.
|
||||
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.
|
||||
stp_guard:
|
||||
description:
|
||||
- Set state of STP guard.
|
||||
choices: [disabled, root guard, bpdu guard, loop guard]
|
||||
default: disabled
|
||||
tags:
|
||||
description:
|
||||
- Space delimited list of tags to assign to a port.
|
||||
type:
|
||||
description:
|
||||
- Set port type.
|
||||
choices: [access, trunk]
|
||||
default: access
|
||||
vlan:
|
||||
description:
|
||||
- VLAN number assigned to port.
|
||||
- If a port is of type trunk, the specified VLAN is the native VLAN.
|
||||
voice_vlan:
|
||||
description:
|
||||
- VLAN number assigned to a port for voice traffic.
|
||||
- Only applicable to access port type.
|
||||
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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: Space delimited list of tags assigned to port.
|
||||
returned: success
|
||||
type: str
|
||||
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"
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
|
||||
|
||||
param_map = {'access_policy_number': 'accessPolicyNumber',
|
||||
'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 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='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', 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_number=dict(type='str'),
|
||||
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}/switchPorts'}
|
||||
query_url = {'switchport': '/devices/{serial}/switchPorts/{number}'}
|
||||
update_url = {'switchport': '/devices/{serial}/switchPorts/{number}'}
|
||||
|
||||
meraki.url_catalog['get_all'].update(query_urls)
|
||||
meraki.url_catalog['get_one'].update(query_url)
|
||||
meraki.url_catalog['update'] = update_url
|
||||
|
||||
payload = None
|
||||
|
||||
# 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
|
||||
# FIXME: Work with Meraki so they can implement a check mode
|
||||
if module.check_mode:
|
||||
meraki.exit_json(**meraki.result)
|
||||
|
||||
# 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 = dict()
|
||||
|
||||
for k, v in meraki.params.items():
|
||||
try:
|
||||
payload[param_map[k]] = v
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
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
|
||||
if meraki.is_update_required(original, proposed, optional_ignore=['number']):
|
||||
path = meraki.construct_path('update', custom={'serial': meraki.params['serial'],
|
||||
'number': meraki.params['number'],
|
||||
})
|
||||
response = meraki.request(path, method='PUT', payload=json.dumps(payload))
|
||||
meraki.result['data'] = response
|
||||
meraki.result['changed'] = True
|
||||
else:
|
||||
meraki.result['data'] = original
|
||||
|
||||
# in the event of a successful module execution, you will want to
|
||||
# simple AnsibleModule.exit_json(), passing the key/value results
|
||||
meraki.exit_json(**meraki.result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,257 +0,0 @@
|
||||
#!/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.
|
||||
version_added: "2.8"
|
||||
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
|
||||
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
|
||||
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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:
|
||||
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.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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', 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 = dict()
|
||||
original['servers'] = r
|
||||
|
||||
if meraki.is_update_required(original, payload):
|
||||
if meraki.module.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)
|
||||
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.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()
|
@ -1,446 +0,0 @@
|
||||
#!/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_vlan
|
||||
short_description: Manage VLANs in the Meraki cloud
|
||||
version_added: "2.7"
|
||||
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
|
||||
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
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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: 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, json, env_fallback
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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_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 format_dns(nameservers):
|
||||
return nameservers.replace(';', '\n')
|
||||
|
||||
|
||||
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(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'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='vlan')
|
||||
|
||||
meraki.params['follow_redirects'] = 'all'
|
||||
|
||||
query_urls = {'vlan': '/networks/{net_id}/vlans'}
|
||||
query_url = {'vlan': '/networks/{net_id}/vlans/{vlan_id}'}
|
||||
create_url = {'vlan': '/networks/{net_id}/vlans'}
|
||||
update_url = {'vlan': '/networks/{net_id}/vlans/'}
|
||||
delete_url = {'vlan': '/networks/{net_id}/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 = {'id': meraki.params['vlan_id'],
|
||||
'name': meraki.params['name'],
|
||||
'subnet': meraki.params['subnet'],
|
||||
'applianceIp': meraki.params['appliance_ip'],
|
||||
}
|
||||
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')
|
||||
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']
|
||||
ignored = ['networkId']
|
||||
if meraki.is_update_required(original, payload, optional_ignore=ignored):
|
||||
meraki.result['diff'] = dict()
|
||||
diff = recursive_diff(original, payload)
|
||||
meraki.result['diff']['before'] = diff[0]
|
||||
meraki.result['diff']['after'] = diff[1]
|
||||
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
|
||||
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()
|
@ -1,342 +0,0 @@
|
||||
#!/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
|
||||
version_added: "2.9"
|
||||
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, status]
|
||||
test_id:
|
||||
description:
|
||||
- ID of webhook test query.
|
||||
type: str
|
||||
author:
|
||||
- Kevin Breit (@kbreit)
|
||||
extends_documentation_fragment: 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
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.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 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', 'status']),
|
||||
test_id=dict(type='str'),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
# we primarily care about changed and state
|
||||
# change is if this module effectively modified the target
|
||||
# state will include any data that you want your module to pass back
|
||||
# for consumption, for example, in a subsequent task
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
# the AnsibleModule object will be our abstraction working with Ansible
|
||||
# this includes instantiation, a couple of common attr would be the
|
||||
# args/params passed to the execution, as well as if the module
|
||||
# supports check mode
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
meraki = MerakiModule(module, function='webhooks')
|
||||
|
||||
meraki.params['follow_redirects'] = 'all'
|
||||
|
||||
query_url = {'webhooks': '/networks/{net_id}/httpServers'}
|
||||
query_one_url = {'webhooks': '/networks/{net_id}/httpServers/{hookid}'}
|
||||
create_url = {'webhooks': '/networks/{net_id}/httpServers'}
|
||||
update_url = {'webhooks': '/networks/{net_id}/httpServers/{hookid}'}
|
||||
delete_url = {'webhooks': '/networks/{net_id}/httpServers/{hookid}'}
|
||||
test_url = {'webhooks': '/networks/{net_id}/httpServers/webhookTests'}
|
||||
test_status_url = {'webhooks': '/networks/{net_id}/httpServers/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']
|
||||
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
|
||||
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
|
||||
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 == 200:
|
||||
meraki.result['data'] = response
|
||||
meraki.exit_json(**meraki.result)
|
||||
elif meraki.params['test'] == 'status':
|
||||
if meraki.params['test_id'] is None:
|
||||
meraki.fail_json("test_id is required when querying test status.")
|
||||
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
|
||||
meraki.exit_json(**meraki.result)
|
||||
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: # Test to see if it 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:
|
||||
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)
|
||||
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.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()
|
@ -1,78 +0,0 @@
|
||||
# -*- 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)
|
||||
|
||||
|
||||
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.
|
||||
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
|
||||
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
|
||||
'''
|
@ -1,384 +0,0 @@
|
||||
# Test code for the Meraki Admin module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Create new administrator in check mode
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jane Doe
|
||||
email: '{{email_prefix}}+janedoe@{{email_domain}}'
|
||||
org_access: read-only
|
||||
delegate_to: localhost
|
||||
check_mode: yes
|
||||
register: create_org_check
|
||||
|
||||
- name: Create new admin check mode assertion
|
||||
assert:
|
||||
that:
|
||||
- create_org_check is changed
|
||||
- 'create_org_check.data.name == "Jane Doe"'
|
||||
|
||||
- name: Create new administrator
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jane Doe
|
||||
email: '{{email_prefix}}+janedoe@{{email_domain}}'
|
||||
org_access: read-only
|
||||
delegate_to: localhost
|
||||
register: create_orgaccess
|
||||
|
||||
- name: Create new admin assertion
|
||||
assert:
|
||||
that:
|
||||
- create_orgaccess.changed == true
|
||||
- 'create_orgaccess.data.name == "Jane Doe"'
|
||||
|
||||
- name: Delete recently created administrator with check mode
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
email: '{{email_prefix}}+janedoe@{{email_domain}}'
|
||||
delegate_to: localhost
|
||||
register: delete_one_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- delete_one_check is changed
|
||||
|
||||
- name: Delete recently created administrator
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
email: '{{email_prefix}}+janedoe@{{email_domain}}'
|
||||
delegate_to: localhost
|
||||
register: delete_one
|
||||
|
||||
- name: Create new administrator with org_id
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_id: '{{test_org_id}}'
|
||||
name: Jane Doe
|
||||
email: '{{email_prefix}}+janedoe@{{email_domain}}'
|
||||
orgAccess: read-only
|
||||
delegate_to: localhost
|
||||
register: create_orgaccess_id
|
||||
|
||||
- name: Create new admin assertion
|
||||
assert:
|
||||
that:
|
||||
- create_orgaccess_id.changed == true
|
||||
- 'create_orgaccess_id.data.name == "Jane Doe"'
|
||||
|
||||
- name: Create administrator with tags with check mode
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: John Doe
|
||||
email: '{{email_prefix}}+johndoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
tags:
|
||||
- { "tag": "production", "access": "read-only" }
|
||||
- tag: beta
|
||||
access: full
|
||||
delegate_to: localhost
|
||||
register: create_tags_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: create_tags_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_tags_check is changed
|
||||
- create_tags_check.data.name == "John Doe"
|
||||
- create_tags_check.data.tags | length == 2
|
||||
|
||||
- name: Create administrator with tags
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: John Doe
|
||||
email: '{{email_prefix}}+johndoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
tags:
|
||||
- { "tag": "production", "access": "read-only" }
|
||||
- tag: beta
|
||||
access: full
|
||||
delegate_to: localhost
|
||||
register: create_tags
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_tags.changed == true
|
||||
- create_tags.data.name == "John Doe"
|
||||
- create_tags.data.tags | length == 2
|
||||
|
||||
- name: Create administrator with invalid tags
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jake Doe
|
||||
email: '{{email_prefix}}+jakedoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
tags:
|
||||
- { "tag": "production", "access": "read-only" }
|
||||
- { "tag": "alpha", "access": "invalid" }
|
||||
delegate_to: localhost
|
||||
register: create_tags_invalid
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"400" in create_tags_invalid.msg'
|
||||
# - '"must contain only valid tags" in create_tags_invalid.msg'
|
||||
|
||||
- name: Create administrator with invalid tag permission
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jake Doe
|
||||
email: '{{email_prefix}}+jakedoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
tags:
|
||||
- { "tag": "production", "access": "read-only" }
|
||||
- { "tag": "beta", "access": "invalid" }
|
||||
delegate_to: localhost
|
||||
register: create_tags_invalid_permission
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"400" in create_tags_invalid_permission.msg'
|
||||
# - '"Invalid permission type" in create_tags_invalid_permission.msg'
|
||||
|
||||
- name: Make sure TestNet and TestNet2 are created
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{item}}'
|
||||
type: switch
|
||||
loop:
|
||||
- TestNet
|
||||
- TestNet2
|
||||
|
||||
- name: Create administrator with networks with check mode
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jim Doe
|
||||
email: '{{email_prefix}}+jimdoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
networks:
|
||||
- { "network": "TestNet", "access": "read-only" }
|
||||
- { "network": "TestNet2", "access": "full" }
|
||||
delegate_to: localhost
|
||||
register: create_network_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_network_check is changed
|
||||
- create_network_check.data.name == "Jim Doe"
|
||||
- create_network_check.data.networks | length == 2
|
||||
|
||||
- name: Create administrator with networks
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jim Doe
|
||||
email: '{{email_prefix}}+jimdoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
networks:
|
||||
- { "network": "TestNet", "access": "read-only" }
|
||||
- { "network": "TestNet2", "access": "full" }
|
||||
delegate_to: localhost
|
||||
register: create_network
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_network.changed == true
|
||||
- create_network.data.name == "Jim Doe"
|
||||
- create_network.data.networks | length == 2
|
||||
|
||||
- name: Update administrator with check mode
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jim Doe
|
||||
email: '{{email_prefix}}+jimdoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
networks:
|
||||
- { "network": "TestNet", "access": "full" }
|
||||
delegate_to: localhost
|
||||
register: update_network_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: update_network_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_network_check is changed
|
||||
- update_network_check.data.networks.0.access == "full"
|
||||
- update_network_check.data.networks | length == 1
|
||||
|
||||
- name: Update administrator
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jim Doe
|
||||
email: '{{email_prefix}}+jimdoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
networks:
|
||||
- { "network": "TestNet", "access": "full" }
|
||||
delegate_to: localhost
|
||||
register: update_network
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_network.changed == true
|
||||
- update_network.data.networks.0.access == "full"
|
||||
- update_network.data.networks | length == 1
|
||||
|
||||
- name: Update administrator for idempotency check with check mode
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jim Doe
|
||||
email: '{{email_prefix}}+jimdoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
networks:
|
||||
- { "network": "TestNet", "access": "full" }
|
||||
delegate_to: localhost
|
||||
register: update_network_idempotent_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: update_network_idempotent_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_network_idempotent_check is not changed
|
||||
|
||||
- name: Update administrator for idempotency
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jim Doe
|
||||
email: '{{email_prefix}}+jimdoe@{{email_domain}}'
|
||||
orgAccess: none
|
||||
networks:
|
||||
- { "network": "TestNet", "access": "full" }
|
||||
delegate_to: localhost
|
||||
register: update_network_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_network_idempotent.changed == false
|
||||
- update_network_idempotent.data is defined
|
||||
|
||||
- name: Create administrator with invalid network
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
name: John Doe
|
||||
email: '{{email_prefix}}+John@{{email_domain}}'
|
||||
orgAccess: none
|
||||
networks:
|
||||
- { "network": "readnet", "access": "read-only" }
|
||||
delegate_to: localhost
|
||||
register: create_network_invalid
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"No network found with the name" in create_network_invalid.msg'
|
||||
# - '"400" in create_network_invalid.msg'
|
||||
|
||||
- name: Query all administrators
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
delegate_to: localhost
|
||||
register: query_all
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_all.data | length == 4
|
||||
- query_all.changed == False
|
||||
|
||||
- name: Query admin by name
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
name: Jane Doe
|
||||
delegate_to: localhost
|
||||
register: query_name
|
||||
|
||||
- name: Query admin by email
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
email: '{{email_prefix}}+janedoe@{{email_domain}}'
|
||||
delegate_to: localhost
|
||||
register: query_email
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_name.data.name == "Jane Doe"
|
||||
- 'query_email.data.email == "{{email_prefix}}+janedoe@{{email_domain}}"'
|
||||
|
||||
always:
|
||||
#############################################################################
|
||||
# Tear down starts here
|
||||
#############################################################################
|
||||
- name: Delete administrators
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
email: '{{item}}'
|
||||
delegate_to: localhost
|
||||
register: delete_all
|
||||
ignore_errors: yes
|
||||
loop:
|
||||
- '{{email_prefix}}+janedoe@{{email_domain}}'
|
||||
- '{{email_prefix}}+johndoe@{{email_domain}}'
|
||||
- '{{email_prefix}}+jimdoe@{{email_domain}}'
|
||||
|
||||
- name: Query all administrators
|
||||
meraki_admin:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
delegate_to: localhost
|
||||
register: query_all_deleted
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_all_deleted.data | length == 1
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,196 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Query all configuration templates
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
register: get_all
|
||||
|
||||
- name: Delete non-existant configuration template
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
config_template: FakeConfigTemplate
|
||||
register: deleted
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"No configuration template named" in deleted.msg'
|
||||
|
||||
- name: Create a network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
register: net_info
|
||||
|
||||
- set_fact:
|
||||
net_id: '{{net_info.data.id}}'
|
||||
|
||||
- name: Bind a template to a network with check mode
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: '{{test_template_name}}'
|
||||
check_mode: yes
|
||||
register: bind_check
|
||||
|
||||
- name: Bind a template to a network
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: '{{test_template_name}}'
|
||||
register: bind
|
||||
|
||||
- assert:
|
||||
that:
|
||||
bind.changed == True
|
||||
|
||||
- assert:
|
||||
that:
|
||||
bind_check is changed
|
||||
|
||||
- name: Bind a template to a network when it's already bound
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: '{{test_template_name}}'
|
||||
register: bind_invalid
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- bind_invalid.changed == False
|
||||
|
||||
- name: Unbind a template from a network
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: '{{test_template_name}}'
|
||||
register: unbind
|
||||
|
||||
- assert:
|
||||
that:
|
||||
unbind.changed == True
|
||||
|
||||
- name: Unbind a template from a network when it's not bound
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: '{{test_template_name}}'
|
||||
register: unbind_invalid
|
||||
|
||||
- assert:
|
||||
that:
|
||||
unbind_invalid.changed == False
|
||||
|
||||
- name: Bind a template to a network via id
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net_id}}'
|
||||
config_template: '{{test_template_name}}'
|
||||
register: bind_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
bind_id.changed == True
|
||||
|
||||
- name: Bind a template to a network via id for idempotency
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net_id}}'
|
||||
config_template: '{{test_template_name}}'
|
||||
register: bind_id_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- bind_id_idempotent.changed == False
|
||||
- bind_id_idempotent.data is defined
|
||||
|
||||
- name: Unbind a template from a network via id with check mode
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net_id}}'
|
||||
config_template: '{{test_template_name}}'
|
||||
check_mode: yes
|
||||
register: unbind_id_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
unbind_id_check is changed
|
||||
|
||||
- name: Unbind a template from a network via id
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net_id}}'
|
||||
config_template: '{{test_template_name}}'
|
||||
register: unbind_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
unbind_id.changed == True
|
||||
|
||||
# This is disabled by default since they can't be created via API
|
||||
- name: Delete sacrificial template with check mode
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
config_template: sacrificial_template
|
||||
check_mode: yes
|
||||
register: delete_template_check
|
||||
|
||||
# This is disabled by default since they can't be created via API
|
||||
- name: Delete sacrificial template
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
config_template: sacrificial_template
|
||||
output_level: debug
|
||||
register: delete_template
|
||||
|
||||
- debug:
|
||||
var: delete_template
|
||||
|
||||
always:
|
||||
- name: Delete network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
delegate_to: localhost
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,117 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Use an invalid domain
|
||||
meraki_config_template:
|
||||
auth_key: '{{ auth_key }}'
|
||||
host: marrrraki.com
|
||||
state: query
|
||||
org_name: DevTestOrg
|
||||
output_level: debug
|
||||
delegate_to: localhost
|
||||
register: invalid_domain
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Connection assertions
|
||||
assert:
|
||||
that:
|
||||
- '"Failed to connect to" in invalid_domain.msg'
|
||||
|
||||
- name: Query all configuration templates
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: DevTestOrg
|
||||
register: get_all
|
||||
|
||||
- name: Delete non-existant configuration template
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: DevTestOrg
|
||||
config_template: DevConfigTemplateInvalid
|
||||
register: deleted
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"No configuration template named" in deleted.msg'
|
||||
|
||||
- name: Create a network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Bind a template to a network
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: DevConfigTemplate
|
||||
register: bind
|
||||
|
||||
- assert:
|
||||
that:
|
||||
bind.changed == True
|
||||
|
||||
- name: Bind a template to a network when it's already bound
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: DevConfigTemplate
|
||||
register: bind_invalid
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- bind_invalid.changed == False
|
||||
|
||||
- name: Unbind a template from a network
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: DevConfigTemplate
|
||||
register: unbind
|
||||
|
||||
- assert:
|
||||
that:
|
||||
unbind.changed == True
|
||||
|
||||
- name: Unbind a template from a network when it's not bound
|
||||
meraki_config_template:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
config_template: DevConfigTemplate
|
||||
register: unbind_invalid
|
||||
|
||||
- assert:
|
||||
that:
|
||||
unbind_invalid.changed == False
|
||||
|
||||
always:
|
||||
- name: Delete network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: '{{ test_net_name }}'
|
||||
delegate_to: localhost
|
@ -1,247 +0,0 @@
|
||||
# Test code for the Meraki Content Filteringmodule
|
||||
# Copyright: (c) 2019, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
type: appliance
|
||||
timezone: America/Chicago
|
||||
delegate_to: localhost
|
||||
register: create_net_appliance
|
||||
|
||||
- name: Test net_name and id exclusivity
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
net_id: 12345
|
||||
state: present
|
||||
allowed_urls:
|
||||
- "http://www.ansible.com/*"
|
||||
register: net_exclusive
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'net_exclusive.msg == "net_name and net_id are mutually exclusive"'
|
||||
|
||||
- name: Set single allowed URL pattern with check mode
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
allowed_urls:
|
||||
- "http://www.ansible.com/*"
|
||||
register: single_allowed_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- single_allowed_check.data.allowed_url_patterns | length == 1
|
||||
- single_allowed_check is changed
|
||||
|
||||
- name: Set single allowed URL pattern
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
allowed_urls:
|
||||
- "http://www.ansible.com/*"
|
||||
register: single_allowed
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- single_allowed.data.allowed_url_patterns | length == 1
|
||||
|
||||
- name: Set single allowed URL pattern for idempotency with check mode
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
allowed_urls:
|
||||
- "http://www.ansible.com/*"
|
||||
register: single_allowed_idempotent_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: single_allowed_idempotent_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- single_allowed_idempotent_check is not changed
|
||||
- single_allowed.data.allowed_url_patterns | length == 1
|
||||
|
||||
- name: Set single allowed URL pattern for idempotency
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
allowed_urls:
|
||||
- "http://www.ansible.com/*"
|
||||
register: single_allowed_idempotent
|
||||
|
||||
- debug:
|
||||
var: single_allowed_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- single_allowed_idempotent.changed == False
|
||||
- single_allowed_idempotent.data is defined
|
||||
|
||||
- name: Set single blocked URL pattern
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
blocked_urls:
|
||||
- "http://www.ansible.com/*"
|
||||
register: single_blocked
|
||||
|
||||
- debug:
|
||||
var: single_blocked
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- single_blocked.data.blocked_url_patterns | length == 1
|
||||
|
||||
- name: Set two allowed URL pattern
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
allowed_urls:
|
||||
- "http://www.ansible.com/*"
|
||||
- "http://www.redhat.com"
|
||||
register: two_allowed
|
||||
|
||||
- debug:
|
||||
var: two_allowed
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- two_allowed.changed == True
|
||||
- two_allowed.data.allowed_url_patterns | length == 2
|
||||
|
||||
- name: Set blocked URL category
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
category_list_size: full list
|
||||
blocked_categories:
|
||||
- "Adult and Pornography"
|
||||
register: blocked_category
|
||||
|
||||
- debug:
|
||||
var: blocked_category
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- blocked_category.changed == True
|
||||
- blocked_category.data.blocked_url_categories | length == 1
|
||||
- blocked_category.data.url_category_list_size == "fullList"
|
||||
|
||||
- name: Set blocked URL category with top sites
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
category_list_size: top sites
|
||||
blocked_categories:
|
||||
- "Adult and Pornography"
|
||||
register: blocked_category
|
||||
|
||||
- debug:
|
||||
var: blocked_category
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- blocked_category.changed == True
|
||||
- blocked_category.data.blocked_url_categories | length == 1
|
||||
- blocked_category.data.url_category_list_size == "topSites"
|
||||
|
||||
- name: Query all content filtering information
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_all
|
||||
|
||||
- debug:
|
||||
var: query_all
|
||||
|
||||
- name: Query all content filtering assertion
|
||||
assert:
|
||||
that:
|
||||
- query_all.data.categories is defined
|
||||
- query_all.data.policy is defined
|
||||
|
||||
- name: Query categories
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
subset: categories
|
||||
delegate_to: localhost
|
||||
register: query_categories
|
||||
|
||||
- debug:
|
||||
var: query_categories
|
||||
|
||||
- name: Query categories assertion
|
||||
assert:
|
||||
that:
|
||||
- query_categories.data is defined
|
||||
|
||||
- name: Query content filtering policies
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
subset: policy
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_policy
|
||||
|
||||
- debug:
|
||||
var: query_policy
|
||||
|
||||
- name: Query contnet filtering policy assertion
|
||||
assert:
|
||||
that:
|
||||
- query_policy.data is defined
|
||||
|
||||
always:
|
||||
- name: Reset policies
|
||||
meraki_content_filtering:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
category_list_size: full list
|
||||
allowed_urls:
|
||||
-
|
||||
blocked_urls:
|
||||
-
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,215 +0,0 @@
|
||||
---
|
||||
- block:
|
||||
# This is commented out because a device cannot be unclaimed via API
|
||||
# - name: Claim a device into an organization
|
||||
# meraki_device:
|
||||
# auth_key: '{{auth_key}}'
|
||||
# org_name: '{{test_org_name}}'
|
||||
# serial: '{{serial}}'
|
||||
# state: present
|
||||
# delegate_to: localhost
|
||||
# register: claim_device_org
|
||||
|
||||
# - assert:
|
||||
# that:
|
||||
# - claim_device_org.changed == true
|
||||
|
||||
- name: Query status of all devices in an organization
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_device_org
|
||||
|
||||
- debug:
|
||||
msg: '{{query_device_org}}'
|
||||
|
||||
- name: Claim a device into a network
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
serial: '{{serial}}'
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
register: claim_device
|
||||
|
||||
- debug:
|
||||
msg: '{{claim_device}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- claim_device.changed == true
|
||||
|
||||
- name: Query all devices in one network by network ID
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{test_net_id}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_one_net_id
|
||||
|
||||
- debug:
|
||||
msg: '{{query_one_net_id}}'
|
||||
|
||||
- name: Query all devices in one network
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_one_net
|
||||
|
||||
- debug:
|
||||
msg: '{{query_one_net}}'
|
||||
|
||||
- name: Query device by serial
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
serial: '{{serial}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_serial_no_net
|
||||
|
||||
- debug:
|
||||
msg: '{{query_serial_no_net}}'
|
||||
|
||||
- name: Query device by serial
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
serial: '{{serial}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_serial
|
||||
|
||||
- debug:
|
||||
msg: '{{query_serial}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_serial.changed == False
|
||||
|
||||
- name: Query uplink information for a device
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
serial_uplink: '{{serial}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_serial_uplink
|
||||
|
||||
- debug:
|
||||
msg: '{{query_serial_uplink}}'
|
||||
|
||||
- name: Query LLDP/CDP information about a device
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
serial_lldp_cdp: '{{serial}}'
|
||||
lldp_cdp_timespan: 6000
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_serial_lldp_cdp
|
||||
|
||||
- debug:
|
||||
msg: '{{query_serial_lldp_cdp}}'
|
||||
|
||||
- name: Query a device by hostname
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
hostname: test-hostname
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_hostname
|
||||
|
||||
- debug:
|
||||
msg: '{{query_hostname}}'
|
||||
|
||||
- name: Query a device by model
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
model: MR26
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_model
|
||||
|
||||
- debug:
|
||||
msg: '{{query_model}}'
|
||||
|
||||
- name: Update a device
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
serial: '{{serial}}'
|
||||
name: mr26
|
||||
address: 1060 W. Addison St., Chicago, IL
|
||||
lat: 41.948038
|
||||
lng: -87.65568
|
||||
tags: recently-added
|
||||
state: present
|
||||
move_map_marker: True
|
||||
note: Test device notes
|
||||
delegate_to: localhost
|
||||
register: update_device
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_device.changed == true
|
||||
- update_device.data.0.notes == "Test device notes"
|
||||
- '"1060 W. Addison St., Chicago, IL" in update_device.data.0.address'
|
||||
|
||||
- name: Update a device with idempotency
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
serial: '{{serial}}'
|
||||
name: mr26
|
||||
address: 1060 W. Addison St., Chicago, IL
|
||||
lat: 41.948038
|
||||
lng: -87.65568
|
||||
tags: recently-added
|
||||
state: present
|
||||
move_map_marker: True
|
||||
note: Test device notes
|
||||
delegate_to: localhost
|
||||
register: update_device_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{update_device_idempotent}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_device_idempotent.changed == False
|
||||
- update_device_idempotent.data is defined
|
||||
|
||||
always:
|
||||
- name: Remove a device from a network
|
||||
meraki_device:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
serial: '{{serial}}'
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
register: delete_device
|
||||
|
||||
- debug:
|
||||
msg: '{{delete_device}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- delete_device.changed == true
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,7 +0,0 @@
|
||||
# Test code for the Meraki Firewalled Services module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: Run test cases
|
||||
include: tests.yml ansible_connection=local
|
@ -1,196 +0,0 @@
|
||||
# Test code for the Meraki modules
|
||||
# Copyright: (c) 2019, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Create network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
type: appliance
|
||||
register: create
|
||||
|
||||
- set_fact:
|
||||
net_id: create.data.id
|
||||
|
||||
- name: Set icmp service to blocked with check mode
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: ICMP
|
||||
access: blocked
|
||||
register: icmp_blocked_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: icmp_blocked_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- icmp_blocked_check.data is defined
|
||||
- icmp_blocked_check is changed
|
||||
|
||||
- name: Set icmp service to blocked
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: ICMP
|
||||
access: blocked
|
||||
register: icmp_blocked
|
||||
|
||||
- debug:
|
||||
var: icmp_blocked
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- icmp_blocked.data is defined
|
||||
- icmp_blocked is changed
|
||||
|
||||
- name: Set icmp service to blocked with idempotency
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: ICMP
|
||||
access: blocked
|
||||
register: icmp_blocked_idempotent
|
||||
|
||||
- debug:
|
||||
var: icmp_blocked_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- icmp_blocked_idempotent.data is defined
|
||||
- icmp_blocked_idempotent is not changed
|
||||
|
||||
- name: Set icmp service to restricted with check mode
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: web
|
||||
access: restricted
|
||||
allowed_ips:
|
||||
- 192.0.1.1
|
||||
- 192.0.1.2
|
||||
check_mode: yes
|
||||
register: web_restricted_check
|
||||
|
||||
- debug:
|
||||
var: web_restricted_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- web_restricted_check.data is defined
|
||||
- web_restricted_check is changed
|
||||
|
||||
- name: Set icmp service to restricted
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: web
|
||||
access: restricted
|
||||
allowed_ips:
|
||||
- 192.0.1.1
|
||||
- 192.0.1.2
|
||||
register: web_restricted
|
||||
|
||||
- debug:
|
||||
var: web_restricted
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- web_restricted.data is defined
|
||||
- web_restricted is changed
|
||||
|
||||
- name: Set icmp service to restricted with idempotency
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: web
|
||||
access: restricted
|
||||
allowed_ips:
|
||||
- 192.0.1.1
|
||||
- 192.0.1.2
|
||||
register: web_restricted_idempotent
|
||||
|
||||
- debug:
|
||||
var: web_restricted_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- web_restricted_idempotent.data is defined
|
||||
- web_restricted_idempotent is not changed
|
||||
|
||||
- name: Test error for access restricted and allowed_ips
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: web
|
||||
access: unrestricted
|
||||
allowed_ips:
|
||||
- 192.0.1.1
|
||||
- 192.0.1.2
|
||||
register: access_error
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'access_error.msg == "allowed_ips is only allowed when access is restricted."'
|
||||
|
||||
- name: Query appliance services
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
register: query_appliance
|
||||
|
||||
- debug:
|
||||
var: query_appliance
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_appliance.data is defined
|
||||
|
||||
- name: Query services
|
||||
meraki_firewalled_services:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
service: ICMP
|
||||
register: query_service
|
||||
|
||||
- debug:
|
||||
var: query_service
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_service.data is defined
|
||||
|
||||
#############################################################################
|
||||
# Tear down starts here
|
||||
#############################################################################
|
||||
always:
|
||||
- name: Delete all networks
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,247 +0,0 @@
|
||||
# Test code for the Meraki VLAN module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
register: net
|
||||
|
||||
- set_fact:
|
||||
net_id: '{{net.data.id}}'
|
||||
|
||||
- name: Enable malware protection with check mode
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
delegate_to: localhost
|
||||
check_mode: yes
|
||||
register: get_malware_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- get_malware_check is changed
|
||||
- get_malware_check.data is defined
|
||||
|
||||
- name: Enable malware protection
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
delegate_to: localhost
|
||||
register: get_malware
|
||||
|
||||
- debug:
|
||||
var: get_malware
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- get_malware is changed
|
||||
- get_malware.data.mode is defined
|
||||
|
||||
- name: Enable malware protection with idempotency
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
delegate_to: localhost
|
||||
register: get_malware_idempotent
|
||||
|
||||
- debug:
|
||||
var: get_malware_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- get_malware_idempotent is not changed
|
||||
- get_malware_idempotent.data is defined
|
||||
|
||||
- name: Test error when mode is not set
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
allowed_files:
|
||||
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
|
||||
comment: random zip
|
||||
delegate_to: localhost
|
||||
register: test_mode_err
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- test_mode_err.msg == "mode must be set when allowed_files or allowed_urls is set."
|
||||
|
||||
- name: Set whitelisted file with check mode
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
allowed_files:
|
||||
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
|
||||
comment: random zip
|
||||
delegate_to: localhost
|
||||
check_mode: yes
|
||||
register: set_file_check
|
||||
|
||||
- debug:
|
||||
var:
|
||||
set_file_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_file_check is changed
|
||||
- set_file_check.data is defined
|
||||
|
||||
- name: Set whitelisted file
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net_id}}'
|
||||
mode: enabled
|
||||
allowed_files:
|
||||
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
|
||||
comment: random zip
|
||||
delegate_to: localhost
|
||||
register: set_file
|
||||
|
||||
- debug:
|
||||
var: set_file
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_file is changed
|
||||
- set_file.data.mode is defined
|
||||
|
||||
- name: Set whitelisted file with idempotency
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
allowed_files:
|
||||
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
|
||||
comment: random zip
|
||||
delegate_to: localhost
|
||||
register: set_file_idempotent
|
||||
|
||||
- debug:
|
||||
var: set_file_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_file_idempotent is not changed
|
||||
- set_file_idempotent.data is defined
|
||||
|
||||
- name: Set whitelisted url with check mode
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
allowed_urls:
|
||||
- url: www.google.com
|
||||
comment: Google
|
||||
delegate_to: localhost
|
||||
check_mode: yes
|
||||
register: set_url_check
|
||||
|
||||
- debug:
|
||||
var:
|
||||
set_url_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_url_check is changed
|
||||
- set_url_check.data is defined
|
||||
|
||||
- name: Set whitelisted url
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
allowed_urls:
|
||||
- url: www.google.com
|
||||
comment: Google
|
||||
delegate_to: localhost
|
||||
register: set_url
|
||||
|
||||
- debug:
|
||||
var: set_url
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_url is changed
|
||||
- set_url.data.mode is defined
|
||||
|
||||
- name: Set whitelisted url with idempotency
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
mode: enabled
|
||||
allowed_urls:
|
||||
- url: www.google.com
|
||||
comment: Google
|
||||
delegate_to: localhost
|
||||
register: set_url_idempotent
|
||||
|
||||
- debug:
|
||||
var: set_url_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_url_idempotent is not changed
|
||||
- set_url_idempotent.data is defined
|
||||
|
||||
- name: Get malware settings
|
||||
meraki_malware:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
delegate_to: localhost
|
||||
register: get_malware
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- get_malware.data is defined
|
||||
|
||||
#############################################################################
|
||||
# Tear down starts here
|
||||
#############################################################################
|
||||
always:
|
||||
- name: Delete test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}} - Malware'
|
||||
delegate_to: localhost
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,100 +0,0 @@
|
||||
# Test code for the Meraki modules
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Create wireless network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetWireless
|
||||
type: wireless
|
||||
delegate_to: localhost
|
||||
register: new_net
|
||||
|
||||
- set_fact:
|
||||
net: '{{new_net.data.id}}'
|
||||
|
||||
- name: Create single firewall rule
|
||||
meraki_mr_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net}}'
|
||||
number: 1
|
||||
rules:
|
||||
- comment: Integration test rule
|
||||
policy: allow
|
||||
protocol: tcp
|
||||
dest_port: 80
|
||||
dest_cidr: 192.0.2.0/24
|
||||
allow_lan_access: no
|
||||
delegate_to: localhost
|
||||
register: create_one
|
||||
|
||||
- debug:
|
||||
msg: '{{create_one}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one.data.0.comment == 'Integration test rule'
|
||||
- create_one.data.1.policy == 'deny'
|
||||
- create_one.data is defined
|
||||
|
||||
- name: Enable local LAN access
|
||||
meraki_mr_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net}}'
|
||||
number: 1
|
||||
rules:
|
||||
allow_lan_access: yes
|
||||
delegate_to: localhost
|
||||
register: enable_lan
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- enable_lan.data.1.policy == 'allow'
|
||||
|
||||
- name: Query firewall rules
|
||||
meraki_mr_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net}}'
|
||||
number: 1
|
||||
delegate_to: localhost
|
||||
register: query
|
||||
|
||||
- debug:
|
||||
msg: '{{query}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query.data.1.comment == 'Wireless clients accessing LAN'
|
||||
- query.data.2.comment == 'Default rule'
|
||||
- query.changed == False
|
||||
|
||||
############################################################################
|
||||
# Tear down starts here
|
||||
############################################################################
|
||||
always:
|
||||
- name: Delete wireless SSID
|
||||
meraki_ssid:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net}}'
|
||||
number: 1
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Delete wireless network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net}}'
|
||||
delegate_to: localhost
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,203 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query firewall rules
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query.data|length == 1
|
||||
|
||||
- name: Set one firewall rule
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- comment: Deny to documentation address
|
||||
src_port: any
|
||||
src_cidr: any
|
||||
dest_port: 80,443
|
||||
dest_cidr: 192.0.1.1/32
|
||||
protocol: tcp
|
||||
policy: deny
|
||||
delegate_to: localhost
|
||||
register: create_one
|
||||
|
||||
- debug:
|
||||
var: create_one
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one.data|length == 2
|
||||
- create_one.data.0.dest_cidr == '192.0.1.1/32'
|
||||
- create_one.data.0.protocol == 'tcp'
|
||||
- create_one.data.0.policy == 'deny'
|
||||
- create_one.changed == True
|
||||
- create_one.data is defined
|
||||
|
||||
- name: Check for idempotency
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- comment: Deny to documentation address
|
||||
src_port: any
|
||||
src_cidr: any
|
||||
dest_port: 80,443
|
||||
dest_cidr: 192.0.1.1/32
|
||||
protocol: tcp
|
||||
policy: deny
|
||||
delegate_to: localhost
|
||||
register: create_one_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{create_one_idempotent}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_idempotent.changed == False
|
||||
- create_one_idempotent.data is defined
|
||||
|
||||
- name: Create syslog in network
|
||||
meraki_syslog:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
servers:
|
||||
- host: 192.0.2.10
|
||||
port: 514
|
||||
roles:
|
||||
- Appliance event log
|
||||
- Flows
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Enable syslog for default rule
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- comment: Deny to documentation address
|
||||
src_port: any
|
||||
src_cidr: any
|
||||
dest_port: 80,443
|
||||
dest_cidr: 192.0.1.1/32
|
||||
protocol: tcp
|
||||
policy: deny
|
||||
syslog_default_rule: yes
|
||||
delegate_to: localhost
|
||||
register: default_syslog
|
||||
|
||||
- debug:
|
||||
msg: '{{default_syslog}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- default_syslog.data is defined
|
||||
|
||||
- name: Query firewall rules
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query
|
||||
|
||||
- debug:
|
||||
msg: '{{query.data.1}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query.data.1.syslog_enabled == True
|
||||
- default_syslog.changed == True
|
||||
|
||||
- name: Disable syslog for default rule
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- comment: Deny to documentation address
|
||||
src_port: any
|
||||
src_cidr: any
|
||||
dest_port: 80,443
|
||||
dest_cidr: 192.0.1.1/32
|
||||
protocol: tcp
|
||||
policy: deny
|
||||
syslog_default_rule: no
|
||||
delegate_to: localhost
|
||||
register: disable_syslog
|
||||
|
||||
- debug:
|
||||
msg: '{{disable_syslog}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- disable_syslog.data is defined
|
||||
|
||||
- name: Query firewall rules
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query
|
||||
|
||||
- debug:
|
||||
msg: '{{query.data.1}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query.data.1.syslog_enabled == False
|
||||
- disable_syslog.changed == True
|
||||
|
||||
always:
|
||||
- name: Delete all firewall rules
|
||||
meraki_mx_l3_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules: []
|
||||
delegate_to: localhost
|
||||
register: delete_all
|
||||
|
||||
- name: Delete network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: absent
|
||||
delegate_to: localhost
|
@ -1,2 +0,0 @@
|
||||
unsupported
|
||||
|
@ -1,7 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: Run test cases
|
||||
include: tests.yml ansible_connection=local
|
@ -1,494 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
type: appliance
|
||||
|
||||
- name: Query firewall rules
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: query
|
||||
register: query
|
||||
|
||||
- debug:
|
||||
var: query
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query.data is defined
|
||||
|
||||
- name: Query firewall application categories
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: query
|
||||
categories: yes
|
||||
register: query_categories
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_categories.data is defined
|
||||
|
||||
- name: Create firewall rule for IP range in check mode
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.0/24
|
||||
register: create_ip_range_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: create_ip_range_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range_check is changed
|
||||
|
||||
- name: Create firewall rule for IP range
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.0/24
|
||||
register: create_ip_range
|
||||
|
||||
- debug:
|
||||
var: create_ip_range
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range is changed
|
||||
- create_ip_range.data.rules | length == 1
|
||||
|
||||
- name: Create firewall rule for IP range with idempotency with check mode
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.0/24
|
||||
register: create_ip_range_idempotent_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range_idempotent_check is not changed
|
||||
|
||||
- name: Create firewall rule for IP range with idempotency
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.0/24
|
||||
register: create_ip_range_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range_idempotent is not changed
|
||||
|
||||
- name: Create firewall rule for IP and port
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.1:23
|
||||
register: create_ip_range_port
|
||||
|
||||
- debug:
|
||||
var: create_ip_range_port
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range_port is changed
|
||||
|
||||
- name: Create firewall rule for IP range
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.0/24
|
||||
register: create_ip_range
|
||||
|
||||
- debug:
|
||||
var: create_ip_range
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range is changed
|
||||
- create_ip_range.data.rules | length == 1
|
||||
|
||||
- name: Create firewall rule for IP range with idempotency with check mode
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.0/24
|
||||
register: create_ip_range_idempotent_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range_idempotent_check is not changed
|
||||
|
||||
- name: Create firewall rule for IP range with idempotency
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: ip_range
|
||||
ip_range: 10.11.12.0/24
|
||||
register: create_ip_range_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_ip_range_idempotent is not changed
|
||||
|
||||
- name: Create firewall rule for application
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application
|
||||
application:
|
||||
name: facebook
|
||||
register: application_rule
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- application_rule is changed
|
||||
- application_rule.data.rules is defined
|
||||
|
||||
- name: Create firewall rule for application via ID
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application
|
||||
application:
|
||||
id: meraki:layer7/application/205
|
||||
register: application_rule_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- application_rule_id is changed
|
||||
|
||||
- name: Create firewall rule for invalid application
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application
|
||||
application:
|
||||
name: ansible
|
||||
register: application_rule_invalid
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Create firewall rule for application category
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application_category
|
||||
application:
|
||||
name: Advertising
|
||||
register: application_category_rule
|
||||
|
||||
- debug:
|
||||
var: application_category_rule
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- application_category_rule is changed
|
||||
|
||||
- name: Create firewall rule for application category with ID and conflict
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application_category
|
||||
application:
|
||||
id: meraki:layer7/category/27
|
||||
register: application_category_rule_id_conflict
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- application_category_rule_id_conflict is not changed
|
||||
|
||||
- name: Create firewall rule for application category with ID
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application_category
|
||||
application:
|
||||
id: meraki:layer7/category/24
|
||||
register: application_category_rule_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- application_category_rule_id is changed
|
||||
|
||||
- name: Create firewall rule for host
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: host
|
||||
host: asdf.com
|
||||
register: host_rule
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- host_rule is changed
|
||||
|
||||
- name: Create firewall rule for port
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: port
|
||||
port: 1234
|
||||
register: port_rule
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- port_rule is changed
|
||||
|
||||
- name: Create firewall rule for blacklisted countries
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: blacklisted_countries
|
||||
countries:
|
||||
- CA
|
||||
- AX
|
||||
register: blacklist_countries
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- blacklist_countries is changed
|
||||
|
||||
- name: Create firewall rule for whitelisted countries
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: whitelisted_countries
|
||||
countries:
|
||||
- US
|
||||
- FR
|
||||
register: whitelist_countries
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- whitelist_countries is changed
|
||||
|
||||
- name: Create firewall rule for whitelisted countries with idempotency
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: whitelisted_countries
|
||||
countries:
|
||||
- US
|
||||
- FR
|
||||
register: whitelist_countries_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- whitelist_countries_idempotent is not changed
|
||||
|
||||
- name: Create multiple firewall rules
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application_category
|
||||
application:
|
||||
id: meraki:layer7/category/27
|
||||
- type: blacklisted_countries
|
||||
countries:
|
||||
- CN
|
||||
- policy: deny
|
||||
type: port
|
||||
port: 8080
|
||||
register: multiple_rules
|
||||
|
||||
- debug:
|
||||
var: multiple_rules
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- multiple_rules.data.rules | length == 3
|
||||
- multiple_rules is changed
|
||||
|
||||
#########################################
|
||||
## Tests for argument completeness ##
|
||||
#########################################
|
||||
|
||||
- name: Test whitelisted_countries incomplete arguments
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: whitelisted_countries
|
||||
register: error_whitelist
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'error_whitelist.msg == "countries argument is required when type is whitelisted_countries."'
|
||||
|
||||
- name: Test blacklisted_countries incomplete arguments
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: blacklisted_countries
|
||||
register: error_blacklist
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'error_blacklist.msg == "countries argument is required when type is blacklisted_countries."'
|
||||
|
||||
- name: Test application_category incomplete arguments
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application_category
|
||||
register: error_app_cat
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'error_app_cat.msg == "application argument is required when type is application_category."'
|
||||
|
||||
- name: Test application incomplete arguments
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: application
|
||||
register: error_app_cat
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'error_app_cat.msg == "application argument is required when type is application."'
|
||||
|
||||
- name: Test host incomplete arguments
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: host
|
||||
register: error_app_cat
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'error_app_cat.msg == "host argument is required when type is host."'
|
||||
|
||||
- name: Test port incomplete arguments
|
||||
meraki_mx_l7_firewall:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetAppliance
|
||||
state: present
|
||||
rules:
|
||||
- type: port
|
||||
register: error_app_cat
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'error_app_cat.msg == "port argument is required when type is port."'
|
||||
|
||||
#################
|
||||
## Cleanup ##
|
||||
#################
|
||||
|
||||
# always:
|
||||
# - name: Delete network
|
||||
# meraki_network:
|
||||
# auth_key: '{{ auth_key }}'
|
||||
# org_name: '{{test_org_name}}'
|
||||
# net_name: TestNetAppliance
|
||||
# state: absent
|
||||
# delegate_to: localhost
|
@ -1,7 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: Run test cases
|
||||
include: tests.yml ansible_connection=local
|
@ -1,363 +0,0 @@
|
||||
# Test code for the Meraki NAT module
|
||||
# Copyright: (c) 2019, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Create test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
type: appliance
|
||||
|
||||
- name: Create 1:1 rule with check mode
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
one_to_one:
|
||||
- name: Service behind NAT
|
||||
public_ip: 1.2.1.2
|
||||
lan_ip: 192.168.128.1
|
||||
uplink: internet1
|
||||
allowed_inbound:
|
||||
- protocol: tcp
|
||||
destination_ports:
|
||||
- 80
|
||||
allowed_ips:
|
||||
- 10.10.10.10
|
||||
register: create_one_one_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: create_one_one_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_one_check is changed
|
||||
|
||||
- name: Create 1:1 rule
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
one_to_one:
|
||||
- name: Service behind NAT
|
||||
public_ip: 1.2.1.2
|
||||
lan_ip: 192.168.128.1
|
||||
uplink: internet1
|
||||
allowed_inbound:
|
||||
- protocol: tcp
|
||||
destination_ports:
|
||||
- 80
|
||||
allowed_ips:
|
||||
- 10.10.10.10
|
||||
register: create_one_one
|
||||
|
||||
- debug:
|
||||
var: create_one_one
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_one is changed
|
||||
|
||||
- name: Create 1:1 rule with idempotency
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
one_to_one:
|
||||
- name: Service behind NAT
|
||||
public_ip: 1.2.1.2
|
||||
lan_ip: 192.168.128.1
|
||||
uplink: internet1
|
||||
allowed_inbound:
|
||||
- protocol: tcp
|
||||
destination_ports:
|
||||
- 80
|
||||
allowed_ips:
|
||||
- 10.10.10.10
|
||||
register: create_one_one_idempotent
|
||||
|
||||
- debug:
|
||||
var: create_one_one_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_one_idempotent is not changed
|
||||
|
||||
- name: Create 1:many rule with check mode
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
one_to_many:
|
||||
- public_ip: 1.1.1.1
|
||||
uplink: internet1
|
||||
port_rules:
|
||||
- name: Test rule
|
||||
protocol: tcp
|
||||
public_port: 10
|
||||
local_ip: 192.168.128.1
|
||||
local_port: 11
|
||||
allowed_ips:
|
||||
- any
|
||||
register: create_one_many_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: create_one_many_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_many_check is changed
|
||||
|
||||
- name: Create 1:many rule
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
one_to_many:
|
||||
- public_ip: 1.1.1.1
|
||||
uplink: internet1
|
||||
port_rules:
|
||||
- name: Test rule
|
||||
protocol: tcp
|
||||
public_port: 10
|
||||
local_ip: 192.168.128.1
|
||||
local_port: 11
|
||||
allowed_ips:
|
||||
- any
|
||||
register: create_one_many
|
||||
|
||||
- debug:
|
||||
var: create_one_many
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_many is changed
|
||||
|
||||
- name: Create 1:many rule with idempotency
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
one_to_many:
|
||||
- public_ip: 1.1.1.1
|
||||
uplink: internet1
|
||||
port_rules:
|
||||
- name: Test rule
|
||||
protocol: tcp
|
||||
public_port: 10
|
||||
local_ip: 192.168.128.1
|
||||
local_port: 11
|
||||
allowed_ips:
|
||||
- any
|
||||
register: create_one_many_idempotent
|
||||
|
||||
- debug:
|
||||
var: create_one_many_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_many_idempotent is not changed
|
||||
|
||||
- name: Create port forwarding rule with check mode
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
port_forwarding:
|
||||
- name: Test map
|
||||
lan_ip: 192.168.128.1
|
||||
uplink: both
|
||||
protocol: tcp
|
||||
allowed_ips:
|
||||
- 1.1.1.1
|
||||
public_port: 10
|
||||
local_port: 11
|
||||
register: create_pf_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: create_pf_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_pf_check is changed
|
||||
|
||||
- name: Create port forwarding rule
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
port_forwarding:
|
||||
- name: Test map
|
||||
lan_ip: 192.168.128.1
|
||||
uplink: both
|
||||
protocol: tcp
|
||||
allowed_ips:
|
||||
- 1.1.1.1
|
||||
public_port: 10
|
||||
local_port: 11
|
||||
register: create_pf
|
||||
|
||||
- debug:
|
||||
var: create_pf
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_pf is changed
|
||||
|
||||
- name: Create port forwarding rule with idempotency
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
port_forwarding:
|
||||
- name: Test map
|
||||
lan_ip: 192.168.128.1
|
||||
uplink: both
|
||||
protocol: tcp
|
||||
allowed_ips:
|
||||
- 1.1.1.1
|
||||
public_port: 10
|
||||
local_port: 11
|
||||
register: create_pf_idempotent
|
||||
|
||||
- debug:
|
||||
var: create_pf_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_pf_idempotent is not changed
|
||||
- create_pf_idempotent.data.port_forwarding is defined
|
||||
|
||||
- name: Create multiple rules
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
port_forwarding:
|
||||
- name: Test map
|
||||
lan_ip: 192.168.128.1
|
||||
uplink: both
|
||||
protocol: tcp
|
||||
allowed_ips:
|
||||
- 1.1.1.2
|
||||
public_port: 10
|
||||
local_port: 11
|
||||
one_to_many:
|
||||
- public_ip: 1.1.1.3
|
||||
uplink: internet1
|
||||
port_rules:
|
||||
- name: Test rule
|
||||
protocol: tcp
|
||||
public_port: 10
|
||||
local_ip: 192.168.128.1
|
||||
local_port: 11
|
||||
allowed_ips:
|
||||
- any
|
||||
register: create_multiple
|
||||
|
||||
- debug:
|
||||
var: create_multiple
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_multiple is changed
|
||||
- create_multiple.data.one_to_many is defined
|
||||
- create_multiple.data.port_forwarding is defined
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_multiple is changed
|
||||
- create_multiple.data.one_to_many is defined
|
||||
- create_multiple.data.port_forwarding is defined
|
||||
- create_multiple.diff.before.one_to_many is defined
|
||||
- create_multiple.diff.before.port_forwarding is defined
|
||||
- create_multiple.diff.after.one_to_many is defined
|
||||
- create_multiple.diff.after.port_forwarding is defined
|
||||
|
||||
- name: Query all NAT rules
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
subset: all
|
||||
register: query_all
|
||||
|
||||
- debug:
|
||||
var: query_all
|
||||
|
||||
- name: Query 1:1 NAT rules
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
subset: '1:1'
|
||||
register: query_1to1
|
||||
|
||||
- debug:
|
||||
var: query_1to1
|
||||
|
||||
- name: Query 1:many NAT rules
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
subset: '1:many'
|
||||
register: query_1tomany
|
||||
|
||||
- debug:
|
||||
var: query_1tomany
|
||||
|
||||
- name: Query port forwarding rules
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
subset: port_forwarding
|
||||
register: query_pf
|
||||
|
||||
- debug:
|
||||
var: query_pf
|
||||
|
||||
- name: Query multiple rules
|
||||
meraki_nat:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
subset:
|
||||
- '1:1'
|
||||
- '1:many'
|
||||
register: query_multiple
|
||||
|
||||
- debug:
|
||||
var: query_multiple
|
||||
|
||||
always:
|
||||
- name: Delete test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: absent
|
||||
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,402 +0,0 @@
|
||||
# Test code for the Meraki modules
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Create network without type
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
timezone: America/Chicago
|
||||
delegate_to: localhost
|
||||
register: create_net_no_type
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_net_no_type.msg == 'type parameter is required when creating a network.'
|
||||
|
||||
- name: Create network without organization
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
net_name: IntTestNetwork
|
||||
timezone: America/Chicago
|
||||
delegate_to: localhost
|
||||
register: create_net_no_org
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Create network with type switch
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkSwitch
|
||||
type: switch
|
||||
timezone: America/Chicago
|
||||
delegate_to: localhost
|
||||
register: create_net_switch
|
||||
|
||||
- name: Create network with type switch by org ID
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_id: '{{test_org_id}}'
|
||||
net_name: IntTestNetworkSwitchOrgID
|
||||
type: switch
|
||||
timezone: America/Chicago
|
||||
delegate_to: localhost
|
||||
register: create_net_switch_org_id
|
||||
|
||||
- name: Create network with type appliance and no timezone
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
register: create_net_appliance_no_tz
|
||||
|
||||
- name: Enable VLAN support on appliance network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
enable_vlans: yes
|
||||
delegate_to: localhost
|
||||
register: enable_vlan
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- enable_vlan.data.enabled == True
|
||||
|
||||
- name: Enable VLAN support on appliance network with idempotency
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
enable_vlans: yes
|
||||
delegate_to: localhost
|
||||
register: enable_vlan_idempotent
|
||||
|
||||
- debug:
|
||||
var: enable_vlan_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- enable_vlan_idempotent is not changed
|
||||
- enable_vlan_idempotent.data is defined
|
||||
|
||||
- name: Disable VLAN support on appliance network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
enable_vlans: no
|
||||
delegate_to: localhost
|
||||
register: disable_vlan
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- disable_vlan.data.enabled == False
|
||||
|
||||
- name: Disable VLAN support on appliance network with idempotency
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: IntTestNetworkAppliance
|
||||
enable_vlans: no
|
||||
delegate_to: localhost
|
||||
register: disable_vlan_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- disable_vlan_idempotent is not changed
|
||||
- disable_vlan_idempotent.data is defined
|
||||
|
||||
- name: Create network with type wireless and disable my.meraki.com
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkWireless
|
||||
type: wireless
|
||||
timezone: America/Chicago
|
||||
disable_my_meraki: yes
|
||||
delegate_to: localhost
|
||||
register: create_net_wireless
|
||||
|
||||
- name: Create network with type wireless, disable my.meraki.com, and check for idempotency
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkWireless
|
||||
type: wireless
|
||||
timezone: America/Chicago
|
||||
disable_my_meraki: yes
|
||||
delegate_to: localhost
|
||||
register: create_net_wireless_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_net_wireless_idempotent.data is defined
|
||||
|
||||
- name: Create network with type combined and disable my.meraki.com
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{ test_org_name }}'
|
||||
net_name: IntTestNetworkCombined
|
||||
type:
|
||||
- appliance
|
||||
- switch
|
||||
timezone: America/Chicago
|
||||
enable_my_meraki: no
|
||||
delegate_to: localhost
|
||||
register: create_net_combined
|
||||
|
||||
- name: Reenable my.meraki.com
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkCombined
|
||||
enable_my_meraki: yes
|
||||
delegate_to: localhost
|
||||
register: enable_meraki_com
|
||||
|
||||
- name: Disable my.meraki.com for next test
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkCombined
|
||||
enable_my_meraki: no
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Enable remote status page
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkCombined
|
||||
enable_remote_status_page: yes
|
||||
delegate_to: localhost
|
||||
register: disable_remote_status
|
||||
|
||||
- debug:
|
||||
msg: '{{disable_remote_status}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- disable_remote_status.data.disable_remote_status_page == False
|
||||
|
||||
- name: Disable remote status page
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkCombined
|
||||
enable_remote_status_page: no
|
||||
delegate_to: localhost
|
||||
register: enable_remote_status
|
||||
|
||||
- debug:
|
||||
msg: '{{enable_remote_status}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- enable_remote_status.data.disable_remote_status_page == True
|
||||
|
||||
- name: Test status pages are mutually exclusive when on
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkCombined
|
||||
enable_my_meraki: yes
|
||||
enable_remote_status_page: no
|
||||
delegate_to: localhost
|
||||
register: status_exclusivity
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"must be true when setting" in status_exclusivity.msg'
|
||||
|
||||
- name: Create network with one tag
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkTag
|
||||
type: switch
|
||||
timezone: America/Chicago
|
||||
tags: first_tag
|
||||
delegate_to: localhost
|
||||
register: create_net_tag
|
||||
|
||||
- name: Create network with two tags
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkTags
|
||||
type: switch
|
||||
timezone: America/Chicago
|
||||
tags:
|
||||
- first_tag
|
||||
- second_tag
|
||||
delegate_to: localhost
|
||||
register: create_net_tags
|
||||
|
||||
- set_fact:
|
||||
tag_net_id: '{{create_net_tags.data.id}}'
|
||||
|
||||
- name: Modify network by net_id
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{tag_net_id}}'
|
||||
type: switch
|
||||
timezone: America/Chicago
|
||||
tags:
|
||||
- first_tag
|
||||
- second_tag
|
||||
- third_tag
|
||||
delegate_to: localhost
|
||||
register: create_net_modified
|
||||
|
||||
- name: Modify network with idempotency
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkTags
|
||||
type: switch
|
||||
timezone: America/Chicago
|
||||
tags:
|
||||
- first_tag
|
||||
- second_tag
|
||||
- third_tag
|
||||
delegate_to: localhost
|
||||
register: create_net_modified_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_net_modified_idempotent.data is defined
|
||||
|
||||
- name: Present assertions
|
||||
assert:
|
||||
that:
|
||||
- create_net_combined.data.type == 'combined'
|
||||
- create_net_combined.data.disable_my_meraki_com == True
|
||||
- enable_meraki_com.data.disable_my_meraki_com == False
|
||||
- '"org_name or org_id parameters are required" in create_net_no_org.msg'
|
||||
- '"IntTestNetworkAppliance" in create_net_appliance_no_tz.data.name'
|
||||
- create_net_appliance_no_tz.changed == True
|
||||
- '"IntTestNetworkSwitch" in create_net_switch.data.name'
|
||||
- '"IntTestNetworkSwitchOrgID" in create_net_switch_org_id.data.name'
|
||||
- '"IntTestNetworkWireless" in create_net_wireless.data.name'
|
||||
- create_net_wireless.data.disable_my_meraki_com == True
|
||||
- create_net_wireless_idempotent.changed == False
|
||||
- create_net_wireless_idempotent.data is defined
|
||||
- '"first_tag" in create_net_tag.data.tags'
|
||||
- '"second_tag" in create_net_tags.data.tags'
|
||||
- '"third_tag" in create_net_modified.data.tags'
|
||||
- create_net_modified.changed == True
|
||||
- create_net_modified_idempotent.changed == False
|
||||
- create_net_modified_idempotent.data is defined
|
||||
|
||||
- name: Query all networks
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
delegate_to: localhost
|
||||
register: net_query_all
|
||||
|
||||
- name: Query a configuration template
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_template_name}}'
|
||||
delegate_to: localhost
|
||||
register: query_config_template
|
||||
|
||||
- name: Query one network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetworkSwitch
|
||||
delegate_to: localhost
|
||||
register: net_query_one
|
||||
|
||||
- name: Query assertions
|
||||
assert:
|
||||
that:
|
||||
- 'net_query_one.data.name == "IntTestNetworkSwitch"'
|
||||
- 'query_config_template.data.name == "{{ test_template_name }}"'
|
||||
|
||||
#############################################################################
|
||||
# Tear down starts here
|
||||
#############################################################################
|
||||
always:
|
||||
- name: Delete network without org
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
net_name: IntTestNetworkSwitch
|
||||
delegate_to: localhost
|
||||
register: delete_all_no_org
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Delete network by org ID
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_id: '{{test_org_id}}'
|
||||
net_name: IntTestNetworkSwitchOrgID
|
||||
delegate_to: localhost
|
||||
register: delete_net_org_id
|
||||
|
||||
- name: Query after delete with org ID
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
delegate_to: localhost
|
||||
register: query_deleted_org_id
|
||||
|
||||
- name: Delete all networks
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{ item }}'
|
||||
delegate_to: localhost
|
||||
register: delete_all
|
||||
ignore_errors: yes
|
||||
loop:
|
||||
- IntTestNetworkSwitch
|
||||
- IntTestNetworkWireless
|
||||
- IntTestNetworkAppliance
|
||||
- IntTestNetworkCombined
|
||||
- IntTestNetworkTag
|
||||
- IntTestNetworkTags
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'delete_all_no_org.msg == "org_name or org_id parameters are required"'
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,8 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: Run test cases
|
||||
include: tests.yml ansible_connection=local
|
||||
|
@ -1,127 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create a new organization named IntTestOrg
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: IntTestOrg
|
||||
state: present
|
||||
output_level: debug
|
||||
register: new_org
|
||||
|
||||
- debug:
|
||||
msg: '{{new_org}}'
|
||||
|
||||
- name: Clone IntTestOrg
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
clone: IntTestOrg
|
||||
org_name: IntTestOrgCloned
|
||||
state: present
|
||||
register: cloned_org
|
||||
|
||||
- debug:
|
||||
msg: '{{cloned_org}}'
|
||||
|
||||
- name: Rename IntTestOrg
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: IntTestOrgRenamed
|
||||
org_id: '{{ new_org.data.id }}'
|
||||
state: present
|
||||
register: modify_org
|
||||
|
||||
- debug:
|
||||
msg: '{{ modify_org }}'
|
||||
|
||||
- set_fact:
|
||||
renamed_org_id: '{{modify_org.data.id}}'
|
||||
|
||||
- name: Rename IntTestOrg idempotent
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: IntTestOrgRenamed
|
||||
org_id: '{{ new_org.data.id }}'
|
||||
state: present
|
||||
register: modify_org_idempotent
|
||||
|
||||
- name: Present assertions
|
||||
assert:
|
||||
that:
|
||||
- '"https" in new_org.url'
|
||||
- new_org.changed == True
|
||||
- new_org.data.id is defined
|
||||
- cloned_org.changed == True
|
||||
- cloned_org.data.id is defined
|
||||
- modify_org.changed == True
|
||||
- 'modify_org.data.name == "IntTestOrgRenamed"'
|
||||
- modify_org_idempotent.changed == False
|
||||
- modify_org_idempotent.data is defined
|
||||
|
||||
- name: List all organizations
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
register: query_all
|
||||
|
||||
- name: Query information about a single organization named IntTestOrg
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: IntTestOrgRenamed
|
||||
state: query
|
||||
register: query_org
|
||||
|
||||
- name: Query information about IntTestOrg by organization ID
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_id: '{{ query_org.data.id }}'
|
||||
state: query
|
||||
register: query_org_id
|
||||
|
||||
- name: Query assertions
|
||||
assert:
|
||||
that:
|
||||
- query_org.data.id is defined
|
||||
- query_all.changed == False
|
||||
- query_all.data | length >= 1
|
||||
- 'query_org.data.name == "IntTestOrgRenamed"'
|
||||
- 'query_org_id.data.id == query_org.data.id'
|
||||
|
||||
always:
|
||||
- name: Delete cloned organizations with check mode
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: IntTestOrgCloned
|
||||
register: deleted_org_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- deleted_org_check is changed
|
||||
|
||||
- name: Delete cloned organizations
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: IntTestOrgCloned
|
||||
register: deleted_org
|
||||
|
||||
- name: Delete renamed organization by id
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_id: '{{renamed_org_id}}'
|
||||
register: deleted_org_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- deleted_org_id is changed
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,306 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create SNMP network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
register: new_net
|
||||
|
||||
- set_fact:
|
||||
net_id: new_net.data.id
|
||||
|
||||
- name: Query all SNMP settings
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: snmp_query
|
||||
|
||||
- debug:
|
||||
msg: '{{snmp_query}}'
|
||||
|
||||
- name: Enable SNMPv2c
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v2c_enabled: true
|
||||
delegate_to: localhost
|
||||
register: snmp_v2_enable
|
||||
|
||||
- debug:
|
||||
msg: '{{snmp_v2_enable}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- snmp_v2_enable.data.v2_community_string is defined
|
||||
- snmp_v2_enable.data.v2c_enabled == true
|
||||
|
||||
- name: Disable SNMPv2c
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v2c_enabled: False
|
||||
delegate_to: localhost
|
||||
register: snmp_v2_disable
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- snmp_v2_disable.data.v2_community_string is not defined
|
||||
- snmp_v2_disable.data.v2c_enabled == False
|
||||
|
||||
- name: Enable SNMPv2c with org_id
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_id: '{{test_org_id}}'
|
||||
state: present
|
||||
v2c_enabled: true
|
||||
delegate_to: localhost
|
||||
register: snmp_v2_enable_id
|
||||
|
||||
- debug:
|
||||
msg: '{{snmp_v2_enable_id}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- snmp_v2_enable_id.data.v2_community_string is defined
|
||||
- snmp_v2_enable_id.data.v2c_enabled == true
|
||||
|
||||
- name: Disable SNMPv2c with org_id
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_id: '{{test_org_id}}'
|
||||
state: present
|
||||
v2c_enabled: False
|
||||
delegate_to: localhost
|
||||
register: snmp_v2_disable_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- snmp_v2_disable_id.data.v2_community_string is not defined
|
||||
- snmp_v2_disable_id.data.v2c_enabled == False
|
||||
|
||||
- name: Enable SNMPv3 with check mode
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v3_enabled: true
|
||||
v3_auth_mode: SHA
|
||||
v3_auth_pass: ansiblepass
|
||||
v3_priv_mode: AES128
|
||||
v3_priv_pass: ansiblepass
|
||||
delegate_to: localhost
|
||||
check_mode: yes
|
||||
register: snmp_v3_enable_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- snmp_v3_enable_check.data.v3_enabled == True
|
||||
- snmp_v3_enable_check.changed == True
|
||||
|
||||
- name: Enable SNMPv3
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v3_enabled: true
|
||||
v3_auth_mode: SHA
|
||||
v3_auth_pass: ansiblepass
|
||||
v3_priv_mode: AES128
|
||||
v3_priv_pass: ansiblepass
|
||||
delegate_to: localhost
|
||||
register: snmp_v3_enable
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- snmp_v3_enable.data.v3_enabled == True
|
||||
- snmp_v3_enable.changed == True
|
||||
|
||||
- name: Check for idempotency
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v3_enabled: true
|
||||
v3_auth_mode: SHA
|
||||
v3_auth_pass: ansiblepass
|
||||
v3_priv_mode: AES128
|
||||
v3_priv_pass: ansiblepass
|
||||
delegate_to: localhost
|
||||
register: snmp_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{snmp_idempotent}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- snmp_idempotent.changed == False
|
||||
- snmp_idempotent.data is defined
|
||||
|
||||
- name: Add peer IPs
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v3_enabled: true
|
||||
v3_auth_mode: SHA
|
||||
v3_auth_pass: ansiblepass
|
||||
v3_priv_mode: AES128
|
||||
v3_priv_pass: ansiblepass
|
||||
peer_ips: 1.1.1.1;2.2.2.2
|
||||
delegate_to: localhost
|
||||
register: peers
|
||||
|
||||
- debug:
|
||||
msg: '{{peers}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- peers.data.peer_ips is defined
|
||||
|
||||
- name: Add invalid peer IPs
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
peer_ips: 1.1.1.1 2.2.2.2
|
||||
delegate_to: localhost
|
||||
register: invalid_peers
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
'"Peer IP addresses are semi-colon delimited." in invalid_peers.msg'
|
||||
|
||||
- name: Set short password
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v3_enabled: true
|
||||
v3_auth_mode: SHA
|
||||
v3_auth_pass: ansible
|
||||
v3_priv_mode: AES128
|
||||
v3_priv_pass: ansible
|
||||
peer_ips: 1.1.1.1;2.2.2.2
|
||||
delegate_to: localhost
|
||||
register: short_password
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
msg: '{{short_password}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"at least 8" in short_password.msg'
|
||||
|
||||
- name: Set network access type to community string
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
access: community
|
||||
community_string: abc123
|
||||
delegate_to: localhost
|
||||
register: set_net_community
|
||||
|
||||
- debug:
|
||||
var: set_net_community
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_net_community is changed
|
||||
- set_net_community.data is defined
|
||||
|
||||
- name: Set network access type to username
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
access: users
|
||||
users:
|
||||
- username: ansibleuser
|
||||
passphrase: ansiblepass
|
||||
delegate_to: localhost
|
||||
register: set_net_user
|
||||
|
||||
- debug:
|
||||
var: set_net_user
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_net_user is changed
|
||||
- set_net_user.data is defined
|
||||
|
||||
- name: Set network access type to none
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
access: none
|
||||
delegate_to: localhost
|
||||
register: set_net_none
|
||||
|
||||
- debug:
|
||||
var: set_net_none
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_net_none is changed
|
||||
- set_net_none.data is defined
|
||||
|
||||
- name: Query network SNMP settings
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: get_net
|
||||
|
||||
- debug:
|
||||
var: get_net
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- get_net.data is defined
|
||||
|
||||
always:
|
||||
- name: Disable SNMPv3
|
||||
meraki_snmp:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
state: present
|
||||
v3_enabled: no
|
||||
v3_auth_mode: SHA
|
||||
v3_auth_pass: ansiblepass
|
||||
v3_priv_mode: AES128
|
||||
v3_priv_pass: ansiblepass
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Delete SNMP network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: absent
|
||||
delegate_to: localhost
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,408 +0,0 @@
|
||||
# Test code for the Meraki SSID module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
type: wireless
|
||||
register: test_net
|
||||
|
||||
- debug:
|
||||
msg: '{{test_net}}'
|
||||
|
||||
- name: Query all SSIDs
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
delegate_to: localhost
|
||||
register: query_all
|
||||
|
||||
- name: Enable and name SSID
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
enabled: true
|
||||
delegate_to: localhost
|
||||
register: enable_name_ssid
|
||||
|
||||
- debug:
|
||||
msg: '{{ enable_name_ssid }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_all.data | length == 15
|
||||
- query_all.data.0.name == 'TestNetSSID WiFi'
|
||||
- enable_name_ssid.data.name == 'AnsibleSSID'
|
||||
|
||||
- name: Check for idempotency
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
enabled: true
|
||||
delegate_to: localhost
|
||||
register: enable_name_ssid_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{ enable_name_ssid_idempotent }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- enable_name_ssid_idempotent.changed == False
|
||||
- enable_name_ssid_idempotent.data is defined
|
||||
|
||||
- name: Query one SSIDs
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
delegate_to: localhost
|
||||
register: query_one
|
||||
|
||||
- debug:
|
||||
msg: '{{query_one}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_one.data.name == 'AnsibleSSID'
|
||||
|
||||
- name: Query one SSID with number
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
number: 1
|
||||
delegate_to: localhost
|
||||
register: query_one_number
|
||||
|
||||
- debug:
|
||||
msg: '{{query_one_number}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_one_number.data.name == 'AnsibleSSID'
|
||||
|
||||
- name: Disable SSID without specifying number
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
enabled: false
|
||||
delegate_to: localhost
|
||||
register: disable_ssid
|
||||
|
||||
- debug:
|
||||
msg: '{{ disable_ssid.data.enabled }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- disable_ssid.data.enabled == False
|
||||
|
||||
- name: Enable SSID with number
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
number: 1
|
||||
enabled: true
|
||||
delegate_to: localhost
|
||||
register: enable_ssid_number
|
||||
|
||||
- debug:
|
||||
msg: '{{ enable_ssid_number.data.enabled }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- enable_ssid_number.data.enabled == True
|
||||
|
||||
- name: Set VLAN arg spec
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
number: 1
|
||||
use_vlan_tagging: yes
|
||||
ip_assignment_mode: Bridge mode
|
||||
default_vlan_id: 1
|
||||
ap_tags_vlan_ids:
|
||||
- tags: wifi
|
||||
vlan_id: 2
|
||||
delegate_to: localhost
|
||||
register: set_vlan_arg
|
||||
|
||||
- debug:
|
||||
var: set_vlan_arg
|
||||
|
||||
- assert:
|
||||
that: set_vlan_arg is changed
|
||||
|
||||
- name: Set VLAN arg spec
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
number: 1
|
||||
use_vlan_tagging: yes
|
||||
ip_assignment_mode: Bridge mode
|
||||
default_vlan_id: 1
|
||||
ap_tags_vlan_ids:
|
||||
- tags: wifi
|
||||
vlan_id: 2
|
||||
delegate_to: localhost
|
||||
register: set_vlan_arg_idempotent
|
||||
|
||||
- debug:
|
||||
var: set_vlan_arg_idempotent
|
||||
|
||||
- assert:
|
||||
that: set_vlan_arg_idempotent is not changed
|
||||
|
||||
|
||||
- name: Set PSK
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
auth_mode: psk
|
||||
psk: abc1234567890
|
||||
encryption_mode: wpa
|
||||
delegate_to: localhost
|
||||
register: psk
|
||||
|
||||
- debug:
|
||||
msg: '{{ psk }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- psk.data.auth_mode == 'psk'
|
||||
- psk.data.encryption_mode == 'wpa'
|
||||
- psk.data.wpa_encryption_mode == 'WPA2 only'
|
||||
|
||||
- name: Set PSK with idempotency
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
auth_mode: psk
|
||||
psk: abc1234567890
|
||||
encryption_mode: wpa
|
||||
delegate_to: localhost
|
||||
register: psk_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{ psk_idempotent }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- psk_idempotent is not changed
|
||||
|
||||
- name: Enable click-through splash page
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
splash_page: Click-through splash page
|
||||
delegate_to: localhost
|
||||
register: splash_click
|
||||
|
||||
- debug:
|
||||
msg: '{{ splash_click }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- splash_click.data.splash_page == 'Click-through splash page'
|
||||
|
||||
- name: Configure RADIUS servers
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
auth_mode: open-with-radius
|
||||
radius_servers:
|
||||
- host: 192.0.1.200
|
||||
port: 1234
|
||||
secret: abc98765
|
||||
delegate_to: localhost
|
||||
register: set_radius_server
|
||||
|
||||
- debug:
|
||||
msg: '{{ set_radius_server }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_radius_server.data.radius_servers.0.host == '192.0.1.200'
|
||||
|
||||
- name: Configure RADIUS servers with idempotency
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
auth_mode: open-with-radius
|
||||
radius_servers:
|
||||
- host: 192.0.1.200
|
||||
port: 1234
|
||||
secret: abc98765
|
||||
delegate_to: localhost
|
||||
register: set_radius_server_idempotent
|
||||
|
||||
- debug:
|
||||
var: set_radius_server_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- set_radius_server_idempotent is not changed
|
||||
|
||||
#################
|
||||
# Error testing #
|
||||
#################
|
||||
- name: Set PSK with wrong mode
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
auth_mode: open
|
||||
psk: abc1234
|
||||
delegate_to: localhost
|
||||
register: psk_invalid
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
msg: '{{ psk_invalid }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- psk_invalid.msg == 'PSK is only allowed when auth_mode is set to psk'
|
||||
|
||||
- name: Set PSK with invalid encryption mode
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
auth_mode: psk
|
||||
psk: abc1234
|
||||
encryption_mode: eap
|
||||
delegate_to: localhost
|
||||
register: psk_invalid_mode
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
msg: '{{ psk_invalid_mode }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- psk_invalid_mode.msg == 'PSK requires encryption_mode be set to wpa'
|
||||
|
||||
- name: Error for PSK and RADIUS servers
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
auth_mode: psk
|
||||
radius_servers:
|
||||
- host: 192.0.1.200
|
||||
port: 1234
|
||||
secret: abc98765
|
||||
delegate_to: localhost
|
||||
register: err_radius_server_psk
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
var: err_radius_server_psk
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'err_radius_server_psk.msg == "radius_servers requires auth_mode to be open-with-radius or 8021x-radius"'
|
||||
|
||||
- name: Set VLAN arg without default VLAN error
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
number: 1
|
||||
use_vlan_tagging: yes
|
||||
ip_assignment_mode: Bridge mode
|
||||
ap_tags_vlan_ids:
|
||||
- tags: wifi
|
||||
vlan_id: 2
|
||||
delegate_to: localhost
|
||||
register: set_vlan_arg_err
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
var: set_vlan_arg_err
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'set_vlan_arg_err.msg == "default_vlan_id is required when use_vlan_tagging is True"'
|
||||
|
||||
always:
|
||||
- name: Delete SSID
|
||||
meraki_ssid:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
name: AnsibleSSID
|
||||
delegate_to: localhost
|
||||
register: delete_ssid
|
||||
|
||||
- debug:
|
||||
msg: '{{ delete_ssid }}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- delete_ssid.data.name == 'Unconfigured SSID 2'
|
||||
|
||||
- name: Delete test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: TestNetSSID
|
||||
register: delete_net
|
||||
|
||||
- debug:
|
||||
msg: '{{delete_net}}'
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,170 +0,0 @@
|
||||
# Test code for the Meraki modules
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Create appliance network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
timezone: America/Chicago
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
register: net
|
||||
|
||||
- set_fact:
|
||||
net_id: '{{net.data.id}}'
|
||||
|
||||
- name: Initialize static route id list
|
||||
set_fact:
|
||||
route_ids: []
|
||||
|
||||
- name: Create static_route
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
name: Test Route
|
||||
subnet: 192.0.1.0/24
|
||||
gateway_ip: 192.168.128.1
|
||||
delegate_to: localhost
|
||||
register: create_route
|
||||
|
||||
- set_fact:
|
||||
route_ids: "{{ route_ids + [create_route.data.id] }}"
|
||||
|
||||
- name: Create second static_route
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
name: Test Route 2
|
||||
subnet: 192.0.2.0/24
|
||||
gateway_ip: 192.168.128.1
|
||||
delegate_to: localhost
|
||||
register: second_create
|
||||
|
||||
- set_fact:
|
||||
route_ids: "{{ route_ids + [second_create.data.id] }}"
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_route.changed == True
|
||||
- create_route.data.id is defined
|
||||
|
||||
- name: Update static route
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
route_id: '{{create_route.data.id}}'
|
||||
subnet: 192.0.3.0/24
|
||||
enabled: yes
|
||||
delegate_to: localhost
|
||||
register: update
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update.data.subnet == "192.0.3.0/24"
|
||||
|
||||
- name: Query static routes
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
delegate_to: localhost
|
||||
register: query_all
|
||||
|
||||
- debug:
|
||||
var: query_all
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_all.data | length >= 2
|
||||
|
||||
- name: Update static route with idempotency
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
route_id: '{{create_route.data.id}}'
|
||||
name: Test Route
|
||||
gateway_ip: 192.168.128.1
|
||||
subnet: 192.0.3.0/24
|
||||
enabled: yes
|
||||
delegate_to: localhost
|
||||
register: update_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_idempotent.changed == False
|
||||
- update_idempotent.data is defined
|
||||
|
||||
- name: Update static route with fixed IP assignment and reservation
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
route_id: '{{create_route.data.id}}'
|
||||
fixed_ip_assignments:
|
||||
- mac: aa:bb:cc:dd:ee:ff
|
||||
ip: 192.0.3.11
|
||||
name: WebServer
|
||||
reserved_ip_ranges:
|
||||
- start: 192.168.3.2
|
||||
end: 192.168.3.10
|
||||
comment: Printers
|
||||
delegate_to: localhost
|
||||
register: fixed_ip
|
||||
|
||||
- debug:
|
||||
var: fixed_ip
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- fixed_ip.data.fixedIpAssignments | length == 1
|
||||
- fixed_ip.data.reservedIpRanges | length == 1
|
||||
|
||||
|
||||
- name: Query single static route
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
route_id: '{{create_route.data.id}}'
|
||||
delegate_to: localhost
|
||||
register: query_one
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_one.data.name == "Test Route"
|
||||
|
||||
- name: Delete static routes
|
||||
meraki_static_route:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
route_id: '{{item}}'
|
||||
delegate_to: localhost
|
||||
loop: '{{route_ids}}'
|
||||
register: delete_all
|
||||
|
||||
always:
|
||||
- name: Delete appliance network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: IntTestNetwork
|
||||
delegate_to: localhost
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,297 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Query all switchports
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
delegate_to: localhost
|
||||
register: query_all
|
||||
|
||||
- debug:
|
||||
msg: '{{query_all}}'
|
||||
|
||||
- name: Query one switchport
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 1
|
||||
delegate_to: localhost
|
||||
register: query_one
|
||||
|
||||
- debug:
|
||||
msg: '{{query_one}}'
|
||||
|
||||
- name: Enable switchport
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
delegate_to: localhost
|
||||
register: update_port_true
|
||||
|
||||
- debug:
|
||||
msg: '{{update_port_true}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_port_true.data.enabled == True
|
||||
|
||||
- name: Disable switchport
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: false
|
||||
delegate_to: localhost
|
||||
register: update_port_false
|
||||
|
||||
- debug:
|
||||
msg: '{{update_port_false}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_port_false.data.enabled == False
|
||||
|
||||
|
||||
- name: Name switchport
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
name: Test Port
|
||||
delegate_to: localhost
|
||||
register: update_port_name
|
||||
|
||||
- debug:
|
||||
msg: '{{update_port_name}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_port_name.data.name == 'Test Port'
|
||||
|
||||
- name: Configure access port
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
name: Test Port
|
||||
tags: desktop
|
||||
type: access
|
||||
vlan: 10
|
||||
delegate_to: localhost
|
||||
register: update_access_port
|
||||
|
||||
- debug:
|
||||
msg: '{{update_access_port}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_access_port.data.vlan == 10
|
||||
|
||||
- name: Configure port as trunk
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 8
|
||||
enabled: true
|
||||
name: Test Port
|
||||
type: trunk
|
||||
vlan: 10
|
||||
allowed_vlans: 10, 100, 200
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Convert trunk port to access
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 8
|
||||
enabled: true
|
||||
name: Test Port
|
||||
type: access
|
||||
vlan: 10
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Test converted port for idempotency
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 8
|
||||
enabled: true
|
||||
name: Test Port
|
||||
type: access
|
||||
vlan: 10
|
||||
delegate_to: localhost
|
||||
register: convert_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- convert_idempotent.changed == False
|
||||
|
||||
- name: Configure access port with voice VLAN
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
name: Test Port
|
||||
tags: desktop
|
||||
type: access
|
||||
vlan: 10
|
||||
voice_vlan: 11
|
||||
delegate_to: localhost
|
||||
register: update_port_vvlan
|
||||
|
||||
- debug:
|
||||
msg: '{{update_port_vvlan}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_port_vvlan.data.voice_vlan == 11
|
||||
- update_port_vvlan.changed == True
|
||||
|
||||
- name: Check access port for idempotenty
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
name: Test Port
|
||||
tags: desktop
|
||||
type: access
|
||||
vlan: 10
|
||||
voice_vlan: 11
|
||||
delegate_to: localhost
|
||||
register: update_port_access_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{update_port_access_idempotent}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_port_access_idempotent.changed == False
|
||||
- update_port_access_idempotent.data is defined
|
||||
|
||||
- name: Configure trunk port
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
name: Server port
|
||||
tags: server
|
||||
type: trunk
|
||||
allowed_vlans: all
|
||||
vlan: 8
|
||||
delegate_to: localhost
|
||||
register: update_trunk
|
||||
|
||||
- debug:
|
||||
msg: '{{update_trunk}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_trunk.data.tags == 'server'
|
||||
- update_trunk.data.type == 'trunk'
|
||||
- update_trunk.data.allowed_vlans == 'all'
|
||||
|
||||
- name: Configure trunk port with specific VLANs
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
name: Server port
|
||||
tags: server
|
||||
type: trunk
|
||||
vlan: 8
|
||||
allowed_vlans:
|
||||
- 10
|
||||
- 15
|
||||
- 20
|
||||
delegate_to: localhost
|
||||
register: update_trunk
|
||||
|
||||
- debug:
|
||||
msg: '{{update_trunk}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_trunk.data.tags == 'server'
|
||||
- update_trunk.data.type == 'trunk'
|
||||
- update_trunk.data.allowed_vlans == '8,10,15,20'
|
||||
|
||||
- name: Configure trunk port with specific VLANs and native VLAN
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
name: Server port
|
||||
tags: server
|
||||
type: trunk
|
||||
vlan: 2
|
||||
allowed_vlans:
|
||||
- 10
|
||||
- 15
|
||||
- 20
|
||||
delegate_to: localhost
|
||||
register: update_trunk
|
||||
|
||||
- debug:
|
||||
msg: '{{update_trunk}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_trunk.data.tags == 'server'
|
||||
- update_trunk.data.type == 'trunk'
|
||||
- update_trunk.data.allowed_vlans == '2,10,15,20'
|
||||
|
||||
- name: Check for idempotency on trunk port
|
||||
meraki_switchport:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
serial: Q2HP-2C6E-GTLD
|
||||
number: 7
|
||||
enabled: true
|
||||
name: Server port
|
||||
tags: server
|
||||
type: trunk
|
||||
vlan: 2
|
||||
allowed_vlans:
|
||||
- 10
|
||||
- 15
|
||||
- 20
|
||||
delegate_to: localhost
|
||||
register: update_trunk_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{update_trunk_idempotent}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_trunk_idempotent.changed == False
|
||||
- update_trunk_idempotent.data is defined
|
@ -1,169 +0,0 @@
|
||||
# Test code for the Meraki Organization module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- set_fact:
|
||||
syslog_test_net_name: 'syslog_{{test_net_name}}'
|
||||
|
||||
- name: Create network with type appliance and no timezone
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
register: new_net
|
||||
|
||||
- set_fact:
|
||||
net_id: '{{new_net.data.id}}'
|
||||
|
||||
- name: Query syslog settings
|
||||
meraki_syslog:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_all
|
||||
|
||||
- debug:
|
||||
msg: '{{query_all}}'
|
||||
|
||||
- name: Set syslog server
|
||||
meraki_syslog:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
servers:
|
||||
- host: 192.0.1.2
|
||||
port: 514
|
||||
roles:
|
||||
- Appliance event log
|
||||
delegate_to: localhost
|
||||
register: create_server
|
||||
|
||||
- debug:
|
||||
msg: '{{create_server.data}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_server['data'][0]['host'] == "192.0.1.2"
|
||||
|
||||
- name: Set syslog server with idempotency
|
||||
meraki_syslog:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
servers:
|
||||
- host: 192.0.1.2
|
||||
port: 514
|
||||
roles:
|
||||
- Appliance event log
|
||||
delegate_to: localhost
|
||||
register: create_server_idempotency
|
||||
|
||||
- debug:
|
||||
msg: '{{create_server_idempotency}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_server_idempotency.changed == False
|
||||
- create_server_idempotency.data is defined
|
||||
|
||||
- name: Set multiple syslog servers
|
||||
meraki_syslog:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_id: '{{net_id}}'
|
||||
state: present
|
||||
servers:
|
||||
- host: 192.0.1.3
|
||||
port: 514
|
||||
roles:
|
||||
- Appliance event log
|
||||
- host: 192.0.1.4
|
||||
port: 514
|
||||
roles:
|
||||
- Appliance Event log
|
||||
- Flows
|
||||
- host: 192.0.1.5
|
||||
port: 514
|
||||
roles:
|
||||
- Flows
|
||||
delegate_to: localhost
|
||||
register: create_multiple_servers
|
||||
|
||||
- debug:
|
||||
msg: '{{create_multiple_servers}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_multiple_servers['data'][0]['host'] == "192.0.1.3"
|
||||
- create_multiple_servers['data'][1]['host'] == "192.0.1.4"
|
||||
- create_multiple_servers['data'][2]['host'] == "192.0.1.5"
|
||||
- create_multiple_servers['data'] | length == 3
|
||||
|
||||
- name: Create syslog server with bad name
|
||||
meraki_syslog:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
servers:
|
||||
- host: 192.0.1.6
|
||||
port: 514
|
||||
roles:
|
||||
- Invalid role
|
||||
delegate_to: localhost
|
||||
register: invalid_role
|
||||
ignore_errors: yes
|
||||
|
||||
# - debug:
|
||||
# msg: '{{invalid_role.body.errors.0}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"Please select at least one valid role" in invalid_role.body.errors.0'
|
||||
|
||||
- name: Add role to existing syslog server # Adding doesn't work, just creation
|
||||
meraki_syslog:
|
||||
auth_key: '{{auth_key}}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: present
|
||||
servers:
|
||||
- host: 192.0.1.2
|
||||
port: 514
|
||||
roles:
|
||||
- flows
|
||||
delegate_to: localhost
|
||||
register: add_role
|
||||
|
||||
- debug:
|
||||
msg: '{{add_role.data.0.roles}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- add_role.data.0.roles.0 == 'Flows'
|
||||
# - add_role.data.0.roles | length == 2
|
||||
|
||||
always:
|
||||
- name: Delete syslog test network
|
||||
meraki_network:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
delegate_to: localhost
|
||||
register: delete_all
|
||||
ignore_errors: yes
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,412 +0,0 @@
|
||||
# Test code for the Meraki VLAN module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Test play without auth_key
|
||||
meraki_network:
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
ignore_errors: yes
|
||||
register: no_key
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"missing required arguments" in no_key.msg'
|
||||
|
||||
- name: Create test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
type: appliance
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Enable VLANs on network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
enable_vlans: yes
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create VLAN in check mode
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.1
|
||||
delegate_to: localhost
|
||||
register: create_vlan_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: create_vlan_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_vlan_check is changed
|
||||
|
||||
- name: Create VLAN
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.1
|
||||
delegate_to: localhost
|
||||
register: create_vlan
|
||||
environment:
|
||||
ANSIBLE_MERAKI_FORMAT: camelcase
|
||||
|
||||
- debug:
|
||||
msg: '{{create_vlan}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_vlan.data.id == 2
|
||||
- create_vlan.changed == True
|
||||
- create_vlan.data.networkId is defined
|
||||
|
||||
- name: Update VLAN with check mode
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
dns_nameservers: opendns
|
||||
delegate_to: localhost
|
||||
register: update_vlan_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: update_vlan_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_vlan_check is changed
|
||||
|
||||
- name: Update VLAN
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
dns_nameservers: opendns
|
||||
delegate_to: localhost
|
||||
register: update_vlan
|
||||
|
||||
- debug:
|
||||
msg: '{{update_vlan}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_vlan.data.appliance_ip == '192.168.250.2'
|
||||
- update_vlan.changed == True
|
||||
|
||||
- name: Update VLAN with idempotency and check mode
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
dns_nameservers: opendns
|
||||
delegate_to: localhost
|
||||
register: update_vlan_idempotent_check
|
||||
check_mode: yes
|
||||
|
||||
- debug:
|
||||
var: update_vlan_idempotent_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_vlan_idempotent_check is not changed
|
||||
|
||||
- name: Update VLAN with idempotency
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
dns_nameservers: opendns
|
||||
delegate_to: localhost
|
||||
register: update_vlan_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{update_vlan_idempotent}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_vlan_idempotent.changed == False
|
||||
- update_vlan_idempotent.data is defined
|
||||
|
||||
- name: Add IP assignments and reserved IP ranges
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
- mac: "12:34:56:78:90:12"
|
||||
ip: 192.168.250.11
|
||||
name: another_fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
- start: 192.168.250.100
|
||||
end: 192.168.250.120
|
||||
comment: reserved_range_high
|
||||
dns_nameservers: opendns
|
||||
delegate_to: localhost
|
||||
register: update_vlan_add_ip
|
||||
|
||||
- debug:
|
||||
msg: '{{update_vlan_add_ip}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_vlan_add_ip.changed == True
|
||||
- update_vlan_add_ip.data.fixed_ip_assignments | length == 2
|
||||
- update_vlan_add_ip.data.reserved_ip_ranges | length == 2
|
||||
|
||||
- name: Remove IP assignments and reserved IP ranges
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
dns_nameservers: opendns
|
||||
delegate_to: localhost
|
||||
register: update_vlan_remove_ip
|
||||
|
||||
- debug:
|
||||
msg: '{{update_vlan_remove_ip}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_vlan_remove_ip.changed == True
|
||||
- update_vlan_remove_ip.data.fixed_ip_assignments | length == 1
|
||||
- update_vlan_remove_ip.data.reserved_ip_ranges | length == 1
|
||||
|
||||
- name: Update VLAN with idempotency
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
dns_nameservers: opendns
|
||||
delegate_to: localhost
|
||||
register: update_vlan_idempotent
|
||||
|
||||
- debug:
|
||||
msg: '{{update_vlan_idempotent}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_vlan_idempotent.changed == False
|
||||
- update_vlan_idempotent.data is defined
|
||||
|
||||
- name: Update VLAN with list of DNS entries
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
name: TestVLAN
|
||||
subnet: 192.168.250.0/24
|
||||
appliance_ip: 192.168.250.2
|
||||
fixed_ip_assignments:
|
||||
- mac: "13:37:de:ad:be:ef"
|
||||
ip: 192.168.250.10
|
||||
name: fixed_ip
|
||||
reserved_ip_range:
|
||||
- start: 192.168.250.10
|
||||
end: 192.168.250.20
|
||||
comment: reserved_range
|
||||
dns_nameservers: 1.1.1.1;8.8.8.8
|
||||
delegate_to: localhost
|
||||
register: update_vlan_dns_list
|
||||
|
||||
- debug:
|
||||
msg: '{{update_vlan_dns_list}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- '"1.1.1.1" in update_vlan_dns_list.data.dns_nameservers'
|
||||
- update_vlan_dns_list.changed == True
|
||||
|
||||
- name: Query all VLANs in network
|
||||
meraki_vlan:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_vlans
|
||||
|
||||
- debug:
|
||||
msg: '{{query_vlans}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_vlans.data | length >= 2
|
||||
- query_vlans.data.1.id == 2
|
||||
- query_vlans.changed == False
|
||||
|
||||
- name: Query single VLAN
|
||||
meraki_vlan:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
state: query
|
||||
output_level: debug
|
||||
delegate_to: localhost
|
||||
register: query_vlan
|
||||
|
||||
- debug:
|
||||
msg: '{{query_vlan}}'
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_vlan.data.id == 2
|
||||
- query_vlan.changed == False
|
||||
|
||||
always:
|
||||
#############################################################################
|
||||
# Tear down starts here
|
||||
#############################################################################
|
||||
- name: Delete VLAN with check mode
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
delegate_to: localhost
|
||||
register: delete_vlan_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
delete_vlan_check is changed
|
||||
|
||||
- name: Delete VLAN
|
||||
meraki_vlan:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
vlan_id: 2
|
||||
delegate_to: localhost
|
||||
register: delete_vlan
|
||||
|
||||
- debug:
|
||||
msg: '{{delete_vlan}}'
|
||||
|
||||
- name: Delete test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
delegate_to: localhost
|
@ -1 +0,0 @@
|
||||
unsupported
|
@ -1,7 +0,0 @@
|
||||
# Test code for the Meraki Webhooks module
|
||||
# Copyright: (c) 2018, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- name: Run test cases
|
||||
include: tests.yml ansible_connection=local
|
@ -1,273 +0,0 @@
|
||||
# Test code for the Meraki Webhook module
|
||||
# Copyright: (c) 2019, Kevin Breit (@kbreit)
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
---
|
||||
- block:
|
||||
- name: Test an API key is provided
|
||||
fail:
|
||||
msg: Please define an API key
|
||||
when: auth_key is not defined
|
||||
|
||||
- name: Create test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
type: appliance
|
||||
|
||||
- name: Create webhook with check mode
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
|
||||
shared_secret: shhhdonttellanyone
|
||||
check_mode: yes
|
||||
register: create_one_check
|
||||
|
||||
- debug:
|
||||
var: create_one_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one_check is changed
|
||||
- create_one_check.data is defined
|
||||
|
||||
- name: Create webhook
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
|
||||
shared_secret: shhhdonttellanyone
|
||||
register: create_one
|
||||
|
||||
- debug:
|
||||
var: create_one
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- create_one is changed
|
||||
- create_one.data is defined
|
||||
|
||||
- set_fact:
|
||||
webhook_id: '{{create_one.data.id}}'
|
||||
|
||||
- name: Query one webhook
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
register: query_one
|
||||
|
||||
- debug:
|
||||
var: query_one
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_one.data is defined
|
||||
|
||||
- name: Query one webhook with id
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
webhook_id: '{{webhook_id}}'
|
||||
register: query_one_id
|
||||
|
||||
- debug:
|
||||
var: query_one_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- query_one_id.data is defined
|
||||
|
||||
- name: Update webhook with check mode
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
|
||||
shared_secret: shhhdonttellanyonehere
|
||||
check_mode: yes
|
||||
register: update_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_check is changed
|
||||
- update_check.data is defined
|
||||
|
||||
- name: Update webhook
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
|
||||
shared_secret: shhhdonttellanyonehere
|
||||
register: update
|
||||
|
||||
- debug:
|
||||
var: update
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update is changed
|
||||
- update.data is defined
|
||||
|
||||
- name: Update webhook with idempotency
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
|
||||
shared_secret: shhhdonttellanyonehere
|
||||
register: update_idempotent
|
||||
|
||||
- debug:
|
||||
var: update_idempotent
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_idempotent is not changed
|
||||
- update_idempotent.data is defined
|
||||
|
||||
- name: Update webhook with id
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
webhook_id: '{{webhook_id}}'
|
||||
name: Test_Hook
|
||||
url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
|
||||
shared_secret: shhhdonttellanyonehereid
|
||||
register: update_id
|
||||
|
||||
- debug:
|
||||
var: update_id
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- update_id is changed
|
||||
- update_id.data is defined
|
||||
|
||||
- name: Test webhook
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
test: test
|
||||
url: https://webhook.site/8eb5b76f-b167-4cb8-9fc4-42621b724244
|
||||
register: webhook_test
|
||||
|
||||
- debug:
|
||||
var: webhook_test
|
||||
|
||||
- set_fact:
|
||||
test_id: '{{webhook_test.data.id}}'
|
||||
|
||||
- name: Get webhook status
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: present
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
test: status
|
||||
test_id: '{{test_id}}'
|
||||
register: webhook_test_status
|
||||
|
||||
- debug:
|
||||
var: webhook_test_status
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- webhook_test_status.data is defined
|
||||
|
||||
- name: Query all webhooks
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: query
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
register: query_all
|
||||
|
||||
- debug:
|
||||
var: query_all
|
||||
|
||||
- name: Delete webhook invalid webhook
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook_Invalid
|
||||
check_mode: yes
|
||||
register: delete_invalid
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
var: delete_invalid
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'delete_invalid.msg == "There is no webhook with the name Test_Hook_Invalid"'
|
||||
|
||||
- name: Delete webhook in check mode
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
check_mode: yes
|
||||
register: delete_check
|
||||
|
||||
- debug:
|
||||
var: delete_check
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- delete_check is changed
|
||||
|
||||
- name: Delete webhook
|
||||
meraki_webhook:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
||||
name: Test_Hook
|
||||
register: delete
|
||||
|
||||
- debug:
|
||||
var: delete
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- delete is changed
|
||||
|
||||
always:
|
||||
#############################################################################
|
||||
# Tear down starts here
|
||||
#############################################################################
|
||||
- name: Delete test network
|
||||
meraki_network:
|
||||
auth_key: '{{auth_key}}'
|
||||
state: absent
|
||||
org_name: '{{test_org_name}}'
|
||||
net_name: '{{test_net_name}}'
|
@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": 2930418,
|
||||
"name": "My organization"
|
||||
}
|
||||
]
|
@ -1,160 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2019 Kevin Breit <kevin.breit@kevinbreit.net>
|
||||
|
||||
# This file is part of Ansible by Red Hat
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from units.compat import unittest, mock
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec, HTTPError, RateLimitException
|
||||
from ansible.module_utils.six import PY2, PY3
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
from units.modules.utils import set_module_args
|
||||
|
||||
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
|
||||
fixture_data = {}
|
||||
testcase_data = {
|
||||
"params": {'orgs': ['orgs.json'],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def load_fixture(name):
|
||||
path = os.path.join(fixture_path, name)
|
||||
|
||||
if path in fixture_data:
|
||||
return fixture_data[path]
|
||||
|
||||
with open(path) as f:
|
||||
data = f.read()
|
||||
|
||||
# try:
|
||||
data = json.loads(data)
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
fixture_data[path] = data
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def module():
|
||||
argument_spec = meraki_argument_spec()
|
||||
set_module_args({'auth_key': 'abc123',
|
||||
})
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=False)
|
||||
return MerakiModule(module)
|
||||
|
||||
|
||||
def mocked_fetch_url(*args, **kwargs):
|
||||
print(args)
|
||||
if args[1] == 'https://api.meraki.com/api/v0/404':
|
||||
info = {'status': 404,
|
||||
'msg': '404 - Page is missing',
|
||||
'url': 'https://api.meraki.com/api/v0/404',
|
||||
}
|
||||
info['body'] = '404'
|
||||
elif args[1] == 'https://api.meraki.com/api/v0/429':
|
||||
info = {'status': 429,
|
||||
'msg': '429 - Rate limit hit',
|
||||
'url': 'https://api.meraki.com/api/v0/429',
|
||||
}
|
||||
info['body'] = '429'
|
||||
return (None, info)
|
||||
|
||||
|
||||
def mocked_fetch_url_rate_success(module, *args, **kwargs):
|
||||
if module.retry_count == 5:
|
||||
info = {'status': 200,
|
||||
'url': 'https://api.meraki.com/api/organization',
|
||||
}
|
||||
resp = {'body': 'Succeeded'}
|
||||
else:
|
||||
info = {'status': 429,
|
||||
'msg': '429 - Rate limit hit',
|
||||
'url': 'https://api.meraki.com/api/v0/429',
|
||||
}
|
||||
info['body'] = '429'
|
||||
return (resp, info)
|
||||
|
||||
|
||||
def mocked_fail_json(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def mocked_sleep(*args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def test_fetch_url_404(module, mocker):
|
||||
url = '404'
|
||||
mocker.patch('ansible.module_utils.network.meraki.meraki.fetch_url', side_effect=mocked_fetch_url)
|
||||
mocker.patch('ansible.module_utils.network.meraki.meraki.MerakiModule.fail_json', side_effect=mocked_fail_json)
|
||||
with pytest.raises(HTTPError):
|
||||
data = module.request(url, method='GET')
|
||||
assert module.status == 404
|
||||
|
||||
|
||||
def test_fetch_url_429(module, mocker):
|
||||
url = '429'
|
||||
mocker.patch('ansible.module_utils.network.meraki.meraki.fetch_url', side_effect=mocked_fetch_url)
|
||||
mocker.patch('ansible.module_utils.network.meraki.meraki.MerakiModule.fail_json', side_effect=mocked_fail_json)
|
||||
mocker.patch('time.sleep', return_value=None)
|
||||
with pytest.raises(RateLimitException):
|
||||
data = module.request(url, method='GET')
|
||||
assert module.status == 429
|
||||
|
||||
|
||||
def test_fetch_url_429_success(module, mocker):
|
||||
url = '429'
|
||||
mocker.patch('ansible.module_utils.network.meraki.meraki.fetch_url', side_effect=mocked_fetch_url_rate_success)
|
||||
mocker.patch('ansible.module_utils.network.meraki.meraki.MerakiModule.fail_json', side_effect=mocked_fail_json)
|
||||
mocker.patch('time.sleep', return_value=None)
|
||||
# assert module.status == 200
|
||||
|
||||
|
||||
def test_define_protocol_https(module):
|
||||
module.params['use_https'] = True
|
||||
module.define_protocol()
|
||||
testdata = module.params['protocol']
|
||||
assert testdata == 'https'
|
||||
|
||||
|
||||
def test_define_protocol_http(module):
|
||||
module.params['use_https'] = False
|
||||
module.define_protocol()
|
||||
testdata = module.params['protocol']
|
||||
assert testdata == 'http'
|
||||
|
||||
|
||||
def test_is_org_valid_org_name(module):
|
||||
data = load_fixture('orgs.json')
|
||||
org_count = module.is_org_valid(data, org_name="My organization")
|
||||
assert org_count == 1
|
||||
|
||||
|
||||
def test_is_org_valid_org_id(module):
|
||||
data = load_fixture('orgs.json')
|
||||
org_count = module.is_org_valid(data, org_id=2930418)
|
||||
assert org_count == 1
|
Loading…
Reference in New Issue