New module - meraki/meraki_content_filtering (#51223)

* Initial commit
- Module mostly works but doesn't work for URL categories
- Additional tests should be added
- Documentation is needed
- Module is close to being complete

* Improved documentation
- Added parameter documentation
- Added examples

* Lint changes and bug fixes
- Fixed content filtering syntax bug per Meraki documentation update

* Change test cases so they work

* Remove duplicate key

* Improve documentation and check mode
This commit is contained in:
Kevin Breit 2019-02-18 18:27:21 -06:00 committed by ansibot
parent ef2c00e106
commit a499244a53
5 changed files with 472 additions and 0 deletions

View file

@ -0,0 +1,222 @@
#!/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
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]
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
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.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']),
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']),
)
# 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'])
nets = meraki.get_nets(org_id=org_id)
net_id = None
if net_id is None:
nets = meraki.get_nets(org_id=org_id)
net_id = meraki.get_net_id(org_id, meraki.params['net_name'], data=nets)
if 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 module.check_mode:
meraki.result['data'] = payload
meraki.exit_json(**meraki.result)
if meraki.is_update_required(current, payload):
response = meraki.request(path, method='PUT', payload=json.dumps(payload))
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()

View file

@ -0,0 +1 @@
unsupported

View file

@ -0,0 +1,117 @@
# 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

View file

@ -0,0 +1,131 @@
# 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: 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: 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
- debug:
var: single_allowed.data.allowedUrlPatterns
- assert:
that:
- single_allowed.data.allowedUrlPatterns | 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
- 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.blockedUrlPatterns | 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.allowedUrlPatterns | 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_cateogry
- debug:
var: blocked_cateogry
- assert:
that:
- blocked_cateogry.changed == True
- blocked_cateogry.data.blockedUrlCategories | length == 1
- blocked_cateogry.data.urlCategoryListSize == "fullList"
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:
-
# blocked_categories:
# -