New module - meraki/meraki_syslog (#48814)

* Initial commit of meraki_syslog module
- Query or modify syslog settings in Meraki networks
- This has not yet been tested

* Module now supports creating new syslog servers
- Added integration test
- Still needs to add support for
  - Modification or removal of roles
  - Removal of syslog servers
  - Test to make sure servers are appended to existing

* Improvements to integration test
- Code hasn't been tested so it likely is broke somewhere
- Move to a block setup and always destroy network
- Create test network just for syslog server tests
- Current tests will test present functionality
- Absent state needs to be tested further

* Working version of meraki_syslog module
- There is no absent state since module overwrites server config
- Full integration tests

* Formatting fix

* Add some examples

* Fix sanity test errors

* Add type for all parameters
This commit is contained in:
Kevin Breit 2019-02-22 16:22:37 -06:00 committed by ansibot
parent f52a088862
commit cf7ffcedbf
2 changed files with 480 additions and 0 deletions

View file

@ -0,0 +1,288 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = r'''
---
module: meraki_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
org_name:
description:
- Name of organization associated to a network.
type: str
org_id:
description:
- ID of organization associated to a network.
type: str
servers:
description:
- List of syslog server settings
suboptions:
host:
description:
- IP address or hostname of Syslog server.
port:
description:
- Port number Syslog server is listening on.
default: "514"
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']
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: string
sample: 192.0.1.1
port:
description: Port number for syslog communication.
returned: success
type: string
sample: 443
roles:
description: List of roles assigned to syslog server.
returned: success
type: list
sample: "Wireless event log, URLs"
'''
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(meraki, net_name, data):
for n in data:
if n['name'] == net_name:
return True
return False
def construct_tags(tags):
''' Assumes tags are a comma separated list '''
if tags is not None:
tags = tags.replace(' ', '')
tags = tags.split(',')
tag_list = str()
for t in tags:
tag_list = tag_list + " " + t
tag_list = tag_list + " "
return tag_list
return None
# This code was used but relying on API and/or server_arg_spec instead
# def validate_roles(meraki, data):
# ''' Validates whether provided rules are valid '''
# valid_roles = ['WIRELESS EVENT LOG',
# 'APPLIANCE EVENT LOG',
# 'SWITCH EVENT LOG',
# 'AIR MARSHAL EVENTS',
# 'FLOWS',
# 'URLS',
# 'IDS ALERTS',
# 'SECURITY EVENTS']
# for server in data['servers']:
# for role in server['roles']:
# if role.upper() not in valid_roles:
# # meraki.fail_json(msg="Heck yes")
# meraki.fail_json(msg='{0} is not a valid Syslog role.'.format(role))
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', element='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=False,
)
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'] or 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
if module.check_mode:
return meraki.result
# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
org_id = meraki.params['org_id']
if not org_id:
org_id = meraki.get_org_id(meraki.params['org_name'])
nets = meraki.get_nets(org_id=org_id)
net_id = meraki.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):
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
# 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,192 @@
# 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_switchport:
# auth_key: '{{ auth_key }}'
# host: marrrraki.com
# state: query
# serial: Q2HP-2C6E-GTLD
# org_name: IntTestOrg
# delegate_to: localhost
# register: invaliddomain
# ignore_errors: yes
# - name: Disable HTTP
# meraki_switchport:
# auth_key: '{{ auth_key }}'
# use_https: false
# state: query
# serial: Q2HP-2C6E-
# output_level: debug
# delegate_to: localhost
# register: http
# ignore_errors: yes
# - name: Connection assertions
# assert:
# that:
# - '"Failed to connect to" in invaliddomain.msg'
# - '"http" in http.url'
- 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: '{{syslog_test_net_name}}'
type: appliance
delegate_to: localhost
- name: Query syslog settings
meraki_syslog:
auth_key: '{{auth_key}}'
org_name: '{{test_org_name}}'
net_name: '{{syslog_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: '{{syslog_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: '{{syslog_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
- name: Set multiple syslog servers # Broken
meraki_syslog:
auth_key: '{{auth_key}}'
org_name: '{{test_org_name}}'
net_name: '{{syslog_test_net_name}}'
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: '{{syslog_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: '{{syslog_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: '{{syslog_test_net_name}}'
delegate_to: localhost
register: delete_all
ignore_errors: yes