mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Meraki organization module (#38773)
* Initial commit Query an organization within Meraki. No support is in place for managing or creating yet * Change output_level method and make the state parameter required. * Implemented listing all organizations - Updated documentation - Parse results and return all organizations - Parse results and return specified organization * Framework for creating an organization - Documentation example for organization creation - Framework exists for creating organizations, pending PR 36809 - Created functions for HTTP calls - Renamed from dashboard.meraki.com to api.meraki.com - Added required_if for state * Remove absent state - Meraki API does not support deleting an organization so absent is removed - Updated documentation to call it state instead of status * Small change to documentation * Support all parameters associated to organization - Added all parameters needed for all organization actions. - None of the added ones work at this time. - Added documentation for clone. * Integration test for meraki_organization module * Rename module to meraki for porting to module utility * Meraki documentation fragment - Created initial documentation fragment for Meraki modules * Add meraki module utility to branch. Formerly was on a separate branch. * CRU support for Meraki organization module * CRU is supported for Meraki organizations * There is no DELETE function for organizations in the API * This code is very messy and needs cleanup * Create and Update actions don't show status as updated, must fix * Added Meraki module utility to module utility documentation list * Added support for organization cloning * Renamed use_ssl to use_https * Removed define_method() * Removed is_org() * Added is_org_valid() which does all org sanity checks * Fixes for ansibot - Changed default of use_proxy from true to false - Removed some commented out code - Updated documentation * Changes for ansibot - Removed requirement for state parameter. I may readd this. - Updated formatting diff --git a/lib/ansible/module_utils/network/meraki/meraki.py b/lib/ansible/module_utils/network/meraki/meraki.py index 3acd3d1038..395ac7c4b4 100644 --- a/lib/ansible/module_utils/network/meraki/meraki.py +++ b/lib/ansible/module_utils/network/meraki/meraki.py @@ -42,7 +42,7 @@ def meraki_argument_spec(): return dict(auth_key=dict(type='str', no_log=True, fallback=(env_fallback, ['MERAKI_KEY'])), host=dict(type='str', default='api.meraki.com'), name=dict(type='str'), - state=dict(type='str', choices=['present', 'absent', 'query'], required=True), + state=dict(type='str', choices=['present', 'absent', 'query']), use_proxy=dict(type='bool', default=False), use_https=dict(type='bool', default=True), validate_certs=dict(type='bool', default=True), diff --git a/lib/ansible/modules/network/meraki/meraki_organization.py b/lib/ansible/modules/network/meraki/meraki_organization.py index 923d969366..3789be91d6 100644 --- a/lib/ansible/modules/network/meraki/meraki_organization.py +++ b/lib/ansible/modules/network/meraki/meraki_organization.py @@ -20,11 +20,9 @@ short_description: Manage organizations in the Meraki cloud version_added: "2.6" description: - Allows for creation, management, and visibility into organizations within Meraki - 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 - options: name: description: @@ -32,21 +30,18 @@ options: - If C(clone) is specified, C(name) is the name of the new organization. state: description: - - Create or query organizations - choices: ['query', 'present'] + - Create or modify an organization + choices: ['present', 'query'] clone: description: - Organization to clone to a new organization. - type: string org_name: description: - Name of organization. - Used when C(name) should refer to another object. - type: string org_id: description: - ID of organization - author: - Kevin Breit (@kbreit) extends_documentation_fragment: meraki @@ -86,7 +81,6 @@ RETURN = ''' response: description: Data returned from Meraki dashboard. type: dict - state: query returned: info ''' @@ -103,6 +97,7 @@ def main(): argument_spec = meraki_argument_spec() argument_spec.update(clone=dict(type='str'), + state=dict(type='str', choices=['present', 'query']), ) @@ -125,11 +120,9 @@ def main(): meraki.function = 'organizations' meraki.params['follow_redirects'] = 'all' - meraki.required_if=[ - ['state', 'present', ['name']], - ['clone', ['name']], - # ['vpn_PublicIP', ['name']], - ] + meraki.required_if = [['state', 'present', ['name']], + ['clone', ['name']], + ] create_urls = {'organizations': '/organizations', } @@ -162,23 +155,16 @@ def main(): - - # method = None - # org_id = None - - - # meraki.fail_json(msg=meraki.is_org_valid(meraki.get_orgs(), org_name='AnsibleTestOrg')) - if meraki.params['state'] == 'query': - if meraki.params['name'] is None: # Query all organizations, no matter what - orgs = meraki.get_orgs() - meraki.result['organization'] = orgs - elif meraki.params['name'] is not None: # Query by organization name - module.warn('All matching organizations will be returned, even if there are duplicate named organizations') - orgs = meraki.get_orgs() - for o in orgs: - if o['name'] == meraki.params['name']: - meraki.result['organization'] = o + if meraki.params['name'] is None: # Query all organizations, no matter what + orgs = meraki.get_orgs() + meraki.result['organization'] = orgs + elif meraki.params['name'] is not None: # Query by organization name + module.warn('All matching organizations will be returned, even if there are duplicate named organizations') + orgs = meraki.get_orgs() + for o in orgs: + if o['name'] == meraki.params['name']: + meraki.result['organization'] = o elif meraki.params['state'] == 'present': if meraki.params['clone'] is not None: # Cloning payload = {'name': meraki.params['name']} @@ -193,7 +179,10 @@ def main(): payload = {'name': meraki.params['name'], 'id': meraki.params['org_id'], } - meraki.result['response'] = json.loads(meraki.request(meraki.construct_path('update', org_id=meraki.params['org_id']), payload=json.dumps(payload), method='PUT')) + meraki.result['response'] = json.loads(meraki.request(meraki.construct_path('update', + org_id=meraki.params['org_id']), + payload=json.dumps(payload), + method='PUT')) diff --git a/lib/ansible/utils/module_docs_fragments/meraki.py b/lib/ansible/utils/module_docs_fragments/meraki.py index e268d02e68..3569d83b99 100644 --- a/lib/ansible/utils/module_docs_fragments/meraki.py +++ b/lib/ansible/utils/module_docs_fragments/meraki.py @@ -35,6 +35,7 @@ options: description: - Set amount of debug output during module execution choices: ['normal', 'debug'] + default: 'normal' timeout: description: - Time to timeout for HTTP requests. diff --git a/test/integration/targets/meraki_organization/aliases b/test/integration/targets/meraki_organization/aliases new file mode 100644 index 0000000000..ad7ccf7ada --- /dev/null +++ b/test/integration/targets/meraki_organization/aliases @@ -0,0 +1 @@ +unsupported * Formatting fix * Minor updates due to testing - Made state required again - Improved formatting for happier PEP8 - request() now sets instance method * Fix reporting of the result * Enhance idempotency checks - Remove merging functionality as the proposed should be used - Do check and reverse check to look for differences * Rewrote and added additional integration tests. This isn't done. * Updated is_update_required method: - Original and proposed data is passed to method - Added ignored_keys list so it can be skipped if needed * Changes per comments from dag - Optionally assign function on class instantiation - URLs now have {} for substitution method - Move auth_key check to module utility - Remove is_new and get_existing - Minor changes to documentation * Enhancements for future modules and organization - Rewrote construct_path method for simplicity - Increased support for network functionality to be committed * Changes based on Dag feedback and to debug problems * Minor fixes for validitation testing * Small changes for dag and Ansibot - Changed how auth_key is processed - Removed some commented lines - Updated documentation fragment, but that may get reverted * Remove blank line and comment * Improvements for testing and code simplification - Added network integration tests - Modified error handling in request() - More testing to come on this - Rewrote construct_path again. Very simple now. * Remove trailing whitespace * Small changes based on dag's response * Removed certain sections from exit_json and fail_json as they're old
This commit is contained in:
parent
c2d7347819
commit
c8d287fece
9 changed files with 588 additions and 0 deletions
|
@ -50,6 +50,7 @@ The following is a list of module_utils files and a general description. The mod
|
|||
- network/iosxr/iosxr.py - Definitions and helper functions for modules that manage Cisco IOS-XR networking devices.
|
||||
- network/ironware/ironware.py - Module support utilities for managing Brocade IronWare devices.
|
||||
- network/junos/junos.py - Definitions and helper functions for modules that manage Junos networking devices.
|
||||
- network/meraki/meraki.py - Utilities specifically for the Meraki network modules.
|
||||
- network/netscaler/netscaler.py - Utilities specifically for the netscaler network modules.
|
||||
- network/nso/nso.py - Utilities for modules that work with Cisco NSO.
|
||||
- network/nxos/nxos.py - Contains definitions and helper functions specific to Cisco NXOS networking devices.
|
||||
|
|
0
lib/ansible/module_utils/network/meraki/__init__.py
Normal file
0
lib/ansible/module_utils/network/meraki/__init__.py
Normal file
263
lib/ansible/module_utils/network/meraki/meraki.py
Normal file
263
lib/ansible/module_utils/network/meraki/meraki.py
Normal file
|
@ -0,0 +1,263 @@
|
|||
# -*- 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 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, to_bytes, to_text
|
||||
|
||||
|
||||
def meraki_argument_spec():
|
||||
return dict(auth_key=dict(type='str', no_log=True, fallback=(env_fallback, ['MERAKI_KEY'])),
|
||||
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_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'),
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
|
||||
# normal output
|
||||
self.existing = None
|
||||
|
||||
# info output
|
||||
self.config = dict()
|
||||
self.original = None
|
||||
self.proposed = dict()
|
||||
self.merged = None
|
||||
|
||||
# debug output
|
||||
self.filter_string = ''
|
||||
self.method = None
|
||||
self.path = None
|
||||
self.response = None
|
||||
self.status = None
|
||||
self.url = None
|
||||
|
||||
# If URLs need to be modified or added for specific purposes, use .update() on the url_catalog dictionary
|
||||
self.get_urls = {'organizations': '/organizations',
|
||||
'networks': '/organizations/{org_id}/networks',
|
||||
'admins': '/organizations/{org_id}/admins',
|
||||
'configTemplates': '/organizations/{org_id}/configTemplates',
|
||||
'samlRoles': '/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',
|
||||
}
|
||||
|
||||
self.get_one_urls = {'organizations': '/organizations/{org_id}',
|
||||
'networks': '/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 needs to be tested
|
||||
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 is_update_required(self, original, proposed):
|
||||
''' Compare original and proposed data to see if an update is needed '''
|
||||
is_changed = False
|
||||
ignored_keys = ('id', 'organizationId')
|
||||
|
||||
# self.fail_json(msg="Update required check", original=original, proposed=proposed)
|
||||
|
||||
for k, v in original.items():
|
||||
try:
|
||||
if k not in ignored_keys:
|
||||
if v != proposed[k]:
|
||||
is_changed = True
|
||||
except KeyError:
|
||||
if v != '':
|
||||
is_changed = True
|
||||
for k, v in proposed.items():
|
||||
try:
|
||||
if k not in ignored_keys:
|
||||
if v != original[k]:
|
||||
is_changed = True
|
||||
except KeyError:
|
||||
if v != '':
|
||||
is_changed = True
|
||||
return is_changed
|
||||
|
||||
def get_orgs(self):
|
||||
''' Downloads all organizations '''
|
||||
return json.loads(self.request('/organizations', method='GET'))
|
||||
|
||||
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()
|
||||
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_net(self, org_name, net_name, data=None):
|
||||
''' Return network information '''
|
||||
if not data:
|
||||
org_id = self.get_org_id(org_name)
|
||||
path = '/organizations/{org_id}/networks/{net_id}'.format(org_id=org_id, net_id=self.get_net_id(org_name=org_name, net_name=net_name, data=data))
|
||||
return json.loads(self.request('GET', path))
|
||||
else:
|
||||
for n in data:
|
||||
if n['name'] == net_name:
|
||||
return n
|
||||
|
||||
def get_net_id(self, org_name=None, net_name=None, data=None):
|
||||
''' Return network id from lookup or existing data '''
|
||||
if not data:
|
||||
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 construct_path(self, action, function=None, org_id=None, net_id=None, org_name=None):
|
||||
built_path = None
|
||||
if function is None:
|
||||
built_path = self.url_catalog[action][self.function]
|
||||
else:
|
||||
self.function = function
|
||||
built_path = self.url_catalog[action][function]
|
||||
if org_name:
|
||||
org_id = self.get_org_id(org_name)
|
||||
|
||||
built_path = built_path.format(org_id=org_id, net_id=net_id)
|
||||
return built_path
|
||||
|
||||
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']
|
||||
|
||||
if self.status >= 300:
|
||||
self.fail_json(msg='Request failed for {url}: {status} - {msg}'.format(**info))
|
||||
return to_native(resp.read())
|
||||
|
||||
def exit_json(self, **kwargs):
|
||||
self.result['response'] = self.response
|
||||
self.result['status'] = self.status
|
||||
# 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)
|
||||
self.module.exit_json(**self.result)
|
||||
|
||||
def fail_json(self, msg, **kwargs):
|
||||
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)
|
0
lib/ansible/modules/network/meraki/__init__.py
Normal file
0
lib/ansible/modules/network/meraki/__init__.py
Normal file
206
lib/ansible/modules/network/meraki/meraki_organization.py
Normal file
206
lib/ansible/modules/network/meraki/meraki_organization.py
Normal file
|
@ -0,0 +1,206 @@
|
|||
#!/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.
|
||||
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.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Create or modify an organization.
|
||||
choices: ['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 ]
|
||||
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: 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'''
|
||||
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.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 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=['present', 'query'], default='present'),
|
||||
org_name=dict(type='str', aliases=['name', 'organization']),
|
||||
org_id=dict(type='int', 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}',
|
||||
}
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
orgs = meraki.get_orgs()
|
||||
meraki.result['data'] = orgs
|
||||
elif meraki.params['state'] == 'present':
|
||||
if meraki.params['clone']: # Cloning
|
||||
payload = {'name': meraki.params['org_name']}
|
||||
meraki.result['data'] = json.loads(
|
||||
meraki.request(
|
||||
meraki.construct_path(
|
||||
'clone',
|
||||
org_name=meraki.params['clone']
|
||||
),
|
||||
payload=json.dumps(payload),
|
||||
method='POST'))
|
||||
elif not meraki.params['org_id'] and meraki.params['org_name']: # Create new organization
|
||||
payload = {'name': meraki.params['org_name']}
|
||||
meraki.result['data'] = json.loads(
|
||||
meraki.request(
|
||||
meraki.construct_path('create'),
|
||||
method='POST',
|
||||
payload=json.dumps(payload)))
|
||||
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'],
|
||||
}
|
||||
meraki.result['data'] = json.loads(
|
||||
meraki.request(
|
||||
meraki.construct_path(
|
||||
'update',
|
||||
org_id=meraki.params['org_id']
|
||||
),
|
||||
method='PUT',
|
||||
payload=json.dumps(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()
|
56
lib/ansible/utils/module_docs_fragments/meraki.py
Normal file
56
lib/ansible/utils/module_docs_fragments/meraki.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
# -*- 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 = '''
|
||||
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
|
||||
options:
|
||||
auth_key:
|
||||
description:
|
||||
- Authentication key provided by the dashboard. Required if environmental variable MERAKI_KEY is not set.
|
||||
host:
|
||||
description:
|
||||
- Hostname for Meraki dashboard
|
||||
- Only useful for internal Meraki developers
|
||||
type: string
|
||||
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_level:
|
||||
description:
|
||||
- Set amount of debug output during module execution
|
||||
choices: ['normal', 'debug']
|
||||
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.
|
||||
aliases: [ organization ]
|
||||
org_id:
|
||||
description:
|
||||
- ID of organization.
|
||||
'''
|
|
@ -16,6 +16,7 @@ ios
|
|||
iosxr
|
||||
ironware
|
||||
junos
|
||||
meraki
|
||||
net
|
||||
netconf
|
||||
nxos
|
||||
|
|
1
test/integration/targets/meraki_organization/aliases
Normal file
1
test/integration/targets/meraki_organization/aliases
Normal file
|
@ -0,0 +1 @@
|
|||
unsupported
|
60
test/integration/targets/meraki_organization/tasks/main.yml
Normal file
60
test/integration/targets/meraki_organization/tasks/main.yml
Normal file
|
@ -0,0 +1,60 @@
|
|||
# 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: Create a new organization named IntTestOrg
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: IntTestOrg
|
||||
state: present
|
||||
output_level: debug
|
||||
delegate_to: localhost
|
||||
register: new_org
|
||||
|
||||
- name: List all organizations
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_all
|
||||
|
||||
- name: Query information about a single organization named IntTestOrg
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
org_name: IntTestOrg
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
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
|
||||
delegate_to: localhost
|
||||
register: query_org_id
|
||||
|
||||
- name: Clone IntTestOrg
|
||||
meraki_organization:
|
||||
auth_key: '{{ auth_key }}'
|
||||
clone: IntTestOrg
|
||||
org_name: IntTestOrgCloned
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
register: cloned_org
|
||||
|
||||
- name: Present assertions
|
||||
assert:
|
||||
that:
|
||||
- new_org.data.id is defined
|
||||
- '{{ query_all | length}} > 0'
|
||||
- query_org.data.id is defined
|
||||
- 'query_org.data.name == "IntTestOrg"'
|
||||
- cloned_org.data.id is defined
|
||||
- 'query_org_id.data.id == query_org.data.id'
|
Loading…
Reference in a new issue