mirror of https://github.com/ansible/ansible.git
Migrated to cisco.mso
parent
8a3f3e41f8
commit
ae8fb5e371
@ -1,569 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from copy import deepcopy
|
||||
from ansible.module_utils.basic import AnsibleModule, json
|
||||
from ansible.module_utils.six import PY3
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
|
||||
|
||||
if PY3:
|
||||
def cmp(a, b):
|
||||
return (a > b) - (a < b)
|
||||
|
||||
|
||||
def issubset(subset, superset):
|
||||
''' Recurse through nested dictionary and compare entries '''
|
||||
|
||||
# Both objects are the same object
|
||||
if subset is superset:
|
||||
return True
|
||||
|
||||
# Both objects are identical
|
||||
if subset == superset:
|
||||
return True
|
||||
|
||||
# Both objects have a different type
|
||||
if type(subset) != type(superset):
|
||||
return False
|
||||
|
||||
for key, value in subset.items():
|
||||
# Ignore empty values
|
||||
if value is None:
|
||||
return True
|
||||
|
||||
# Item from subset is missing from superset
|
||||
if key not in superset:
|
||||
return False
|
||||
|
||||
# Item has different types in subset and superset
|
||||
if type(superset[key]) != type(value):
|
||||
return False
|
||||
|
||||
# Compare if item values are subset
|
||||
if isinstance(value, dict):
|
||||
if not issubset(superset[key], value):
|
||||
return False
|
||||
elif isinstance(value, list):
|
||||
try:
|
||||
# NOTE: Fails for lists of dicts
|
||||
if not set(value) <= set(superset[key]):
|
||||
return False
|
||||
except TypeError:
|
||||
# Fall back to exact comparison for lists of dicts
|
||||
if not cmp(value, superset[key]):
|
||||
return False
|
||||
elif isinstance(value, set):
|
||||
if not value <= superset[key]:
|
||||
return False
|
||||
else:
|
||||
if not value == superset[key]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def update_qs(params):
|
||||
''' Append key-value pairs to self.filter_string '''
|
||||
accepted_params = dict((k, v) for (k, v) in params.items() if v is not None)
|
||||
return '?' + urlencode(accepted_params)
|
||||
|
||||
|
||||
def mso_argument_spec():
|
||||
return dict(
|
||||
host=dict(type='str', required=True, aliases=['hostname']),
|
||||
port=dict(type='int', required=False),
|
||||
username=dict(type='str', default='admin'),
|
||||
password=dict(type='str', required=True, no_log=True),
|
||||
output_level=dict(type='str', default='normal', choices=['debug', 'info', 'normal']),
|
||||
timeout=dict(type='int', default=30),
|
||||
use_proxy=dict(type='bool', default=True),
|
||||
use_ssl=dict(type='bool', default=True),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
)
|
||||
|
||||
|
||||
def mso_reference_spec():
|
||||
return dict(
|
||||
name=dict(type='str', required=True),
|
||||
schema=dict(type='str'),
|
||||
template=dict(type='str'),
|
||||
)
|
||||
|
||||
|
||||
def mso_subnet_spec():
|
||||
return dict(
|
||||
subnet=dict(type='str', required=True, aliases=['ip']),
|
||||
description=dict(type='str'),
|
||||
scope=dict(type='str', choices=['private', 'public']),
|
||||
shared=dict(type='bool'),
|
||||
no_default_gateway=dict(type='bool'),
|
||||
)
|
||||
|
||||
|
||||
def mso_contractref_spec():
|
||||
return dict(
|
||||
name=dict(type='str', required=True),
|
||||
schema=dict(type='str'),
|
||||
template=dict(type='str'),
|
||||
type=dict(type='str', required=True, choices=['consumer', 'provider']),
|
||||
)
|
||||
|
||||
|
||||
class MSOModule(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.params = module.params
|
||||
self.result = dict(changed=False)
|
||||
self.headers = {'Content-Type': 'text/json'}
|
||||
|
||||
# normal output
|
||||
self.existing = dict()
|
||||
|
||||
# info output
|
||||
self.previous = dict()
|
||||
self.proposed = dict()
|
||||
self.sent = dict()
|
||||
|
||||
# debug output
|
||||
self.has_modified = False
|
||||
self.filter_string = ''
|
||||
self.method = None
|
||||
self.path = None
|
||||
self.response = None
|
||||
self.status = None
|
||||
self.url = None
|
||||
|
||||
# Ensure protocol is set
|
||||
self.params['protocol'] = 'https' if self.params.get('use_ssl', True) else 'http'
|
||||
|
||||
# Set base_uri
|
||||
if self.params.get('port') is not None:
|
||||
self.baseuri = '{protocol}://{host}:{port}/api/v1/'.format(**self.params)
|
||||
else:
|
||||
self.baseuri = '{protocol}://{host}/api/v1/'.format(**self.params)
|
||||
|
||||
if self.module._debug:
|
||||
self.module.warn('Enable debug output because ANSIBLE_DEBUG was set.')
|
||||
self.params['output_level'] = 'debug'
|
||||
|
||||
if self.params.get('password'):
|
||||
# Perform password-based authentication, log on using password
|
||||
self.login()
|
||||
else:
|
||||
self.module.fail_json(msg="Parameter 'password' is required for authentication")
|
||||
|
||||
def login(self):
|
||||
''' Log in to MSO '''
|
||||
|
||||
# Perform login request
|
||||
self.url = urljoin(self.baseuri, 'auth/login')
|
||||
payload = {'username': self.params.get('username'), 'password': self.params.get('password')}
|
||||
resp, auth = fetch_url(self.module,
|
||||
self.url,
|
||||
data=json.dumps(payload),
|
||||
method='POST',
|
||||
headers=self.headers,
|
||||
timeout=self.params.get('timeout'),
|
||||
use_proxy=self.params.get('use_proxy'))
|
||||
|
||||
# Handle MSO response
|
||||
if auth.get('status') != 201:
|
||||
self.response = auth.get('msg')
|
||||
self.status = auth.get('status')
|
||||
self.fail_json(msg='Authentication failed: {msg}'.format(**auth))
|
||||
|
||||
payload = json.loads(resp.read())
|
||||
|
||||
self.headers['Authorization'] = 'Bearer {token}'.format(**payload)
|
||||
|
||||
def request(self, path, method=None, data=None, qs=None):
|
||||
''' Generic HTTP method for MSO requests. '''
|
||||
self.path = path
|
||||
|
||||
if method is not None:
|
||||
self.method = method
|
||||
|
||||
# If we PATCH with empty operations, return
|
||||
if method == 'PATCH' and not data:
|
||||
return {}
|
||||
|
||||
self.url = urljoin(self.baseuri, path)
|
||||
|
||||
if qs is not None:
|
||||
self.url = self.url + update_qs(qs)
|
||||
|
||||
resp, info = fetch_url(self.module,
|
||||
self.url,
|
||||
headers=self.headers,
|
||||
data=json.dumps(data),
|
||||
method=self.method,
|
||||
timeout=self.params.get('timeout'),
|
||||
use_proxy=self.params.get('use_proxy'),
|
||||
)
|
||||
self.response = info.get('msg')
|
||||
self.status = info.get('status')
|
||||
|
||||
# self.result['info'] = info
|
||||
|
||||
# Get change status from HTTP headers
|
||||
if 'modified' in info:
|
||||
self.has_modified = True
|
||||
if info.get('modified') == 'false':
|
||||
self.result['changed'] = False
|
||||
elif info.get('modified') == 'true':
|
||||
self.result['changed'] = True
|
||||
|
||||
# 200: OK, 201: Created, 202: Accepted, 204: No Content
|
||||
if self.status in (200, 201, 202, 204):
|
||||
output = resp.read()
|
||||
if output:
|
||||
return json.loads(output)
|
||||
|
||||
# 404: Not Found
|
||||
elif self.method == 'DELETE' and self.status == 404:
|
||||
return {}
|
||||
|
||||
# 400: Bad Request, 401: Unauthorized, 403: Forbidden,
|
||||
# 405: Method Not Allowed, 406: Not Acceptable
|
||||
# 500: Internal Server Error, 501: Not Implemented
|
||||
elif self.status >= 400:
|
||||
try:
|
||||
output = resp.read()
|
||||
payload = json.loads(output)
|
||||
except (ValueError, AttributeError):
|
||||
try:
|
||||
payload = json.loads(info['body'])
|
||||
except Exception:
|
||||
self.fail_json(msg='MSO Error:', data=data, info=info)
|
||||
if 'code' in payload:
|
||||
self.fail_json(msg='MSO Error {code}: {message}'.format(**payload), data=data, info=info, payload=payload)
|
||||
else:
|
||||
self.fail_json(msg='MSO Error:'.format(**payload), data=data, info=info, payload=payload)
|
||||
|
||||
return {}
|
||||
|
||||
def query_objs(self, path, key=None, **kwargs):
|
||||
''' Query the MSO REST API for objects in a path '''
|
||||
found = []
|
||||
objs = self.request(path, method='GET')
|
||||
|
||||
if objs == {}:
|
||||
return found
|
||||
|
||||
if key is None:
|
||||
key = path
|
||||
|
||||
if key not in objs:
|
||||
self.fail_json(msg="Key '%s' missing from data", data=objs)
|
||||
|
||||
for obj in objs[key]:
|
||||
for kw_key, kw_value in kwargs.items():
|
||||
if kw_value is None:
|
||||
continue
|
||||
if obj[kw_key] != kw_value:
|
||||
break
|
||||
else:
|
||||
found.append(obj)
|
||||
return found
|
||||
|
||||
def get_obj(self, path, **kwargs):
|
||||
''' Get a specific object from a set of MSO REST objects '''
|
||||
objs = self.query_objs(path, **kwargs)
|
||||
if len(objs) == 0:
|
||||
return {}
|
||||
if len(objs) > 1:
|
||||
self.fail_json(msg='More than one object matches unique filter: {0}'.format(kwargs))
|
||||
return objs[0]
|
||||
|
||||
def lookup_schema(self, schema):
|
||||
''' Look up schema and return its id '''
|
||||
if schema is None:
|
||||
return schema
|
||||
|
||||
s = self.get_obj('schemas', displayName=schema)
|
||||
if not s:
|
||||
self.module.fail_json(msg="Schema '%s' is not a valid schema name." % schema)
|
||||
if 'id' not in s:
|
||||
self.module.fail_json(msg="Schema lookup failed for schema '%s': %s" % (schema, s))
|
||||
return s.get('id')
|
||||
|
||||
def lookup_domain(self, domain):
|
||||
''' Look up a domain and return its id '''
|
||||
if domain is None:
|
||||
return domain
|
||||
|
||||
d = self.get_obj('auth/domains', key='domains', name=domain)
|
||||
if not d:
|
||||
self.module.fail_json(msg="Domain '%s' is not a valid domain name." % domain)
|
||||
if 'id' not in d:
|
||||
self.module.fail_json(msg="Domain lookup failed for domain '%s': %s" % (domain, d))
|
||||
return d.get('id')
|
||||
|
||||
def lookup_roles(self, roles):
|
||||
''' Look up roles and return their ids '''
|
||||
if roles is None:
|
||||
return roles
|
||||
|
||||
ids = []
|
||||
for role in roles:
|
||||
r = self.get_obj('roles', name=role)
|
||||
if not r:
|
||||
self.module.fail_json(msg="Role '%s' is not a valid role name." % role)
|
||||
if 'id' not in r:
|
||||
self.module.fail_json(msg="Role lookup failed for role '%s': %s" % (role, r))
|
||||
ids.append(dict(roleId=r.get('id')))
|
||||
return ids
|
||||
|
||||
def lookup_site(self, site):
|
||||
''' Look up a site and return its id '''
|
||||
if site is None:
|
||||
return site
|
||||
|
||||
s = self.get_obj('sites', name=site)
|
||||
if not s:
|
||||
self.module.fail_json(msg="Site '%s' is not a valid site name." % site)
|
||||
if 'id' not in s:
|
||||
self.module.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s))
|
||||
return s.get('id')
|
||||
|
||||
def lookup_sites(self, sites):
|
||||
''' Look up sites and return their ids '''
|
||||
if sites is None:
|
||||
return sites
|
||||
|
||||
ids = []
|
||||
for site in sites:
|
||||
s = self.get_obj('sites', name=site)
|
||||
if not s:
|
||||
self.module.fail_json(msg="Site '%s' is not a valid site name." % site)
|
||||
if 'id' not in s:
|
||||
self.module.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s))
|
||||
ids.append(dict(siteId=s.get('id'), securityDomains=[]))
|
||||
return ids
|
||||
|
||||
def lookup_tenant(self, tenant):
|
||||
''' Look up a tenant and return its id '''
|
||||
if tenant is None:
|
||||
return tenant
|
||||
|
||||
t = self.get_obj('tenants', key='tenants', name=tenant)
|
||||
if not t:
|
||||
self.module.fail_json(msg="Tenant '%s' is not valid tenant name." % tenant)
|
||||
if 'id' not in t:
|
||||
self.module.fail_json(msg="Tenant lookup failed for tenant '%s': %s" % (tenant, t))
|
||||
return t.get('id')
|
||||
|
||||
def lookup_users(self, users):
|
||||
''' Look up users and return their ids '''
|
||||
if users is None:
|
||||
return users
|
||||
|
||||
ids = []
|
||||
for user in users:
|
||||
u = self.get_obj('users', username=user)
|
||||
if not u:
|
||||
self.module.fail_json(msg="User '%s' is not a valid user name." % user)
|
||||
if 'id' not in u:
|
||||
self.module.fail_json(msg="User lookup failed for user '%s': %s" % (user, u))
|
||||
ids.append(dict(userId=u.get('id')))
|
||||
return ids
|
||||
|
||||
def create_label(self, label, label_type):
|
||||
''' Create a new label '''
|
||||
return self.request('labels', method='POST', data=dict(displayName=label, type=label_type))
|
||||
|
||||
def lookup_labels(self, labels, label_type):
|
||||
''' Look up labels and return their ids (create if necessary) '''
|
||||
if labels is None:
|
||||
return None
|
||||
|
||||
ids = []
|
||||
for label in labels:
|
||||
l = self.get_obj('labels', displayName=label)
|
||||
if not l:
|
||||
l = self.create_label(label, label_type)
|
||||
if 'id' not in l:
|
||||
self.module.fail_json(msg="Label lookup failed for label '%s': %s" % (label, l))
|
||||
ids.append(l.get('id'))
|
||||
return ids
|
||||
|
||||
def anp_ref(self, **data):
|
||||
''' Create anpRef string '''
|
||||
return '/schemas/{schema_id}/templates/{template}/anps/{anp}'.format(**data)
|
||||
|
||||
def epg_ref(self, **data):
|
||||
''' Create epgRef string '''
|
||||
return '/schemas/{schema_id}/templates/{template}/anps/{anp}/epgs/{epg}'.format(**data)
|
||||
|
||||
def bd_ref(self, **data):
|
||||
''' Create bdRef string '''
|
||||
return '/schemas/{schema_id}/templates/{template}/bds/{bd}'.format(**data)
|
||||
|
||||
def contract_ref(self, **data):
|
||||
''' Create contractRef string '''
|
||||
# Support the contract argspec
|
||||
if 'name' in data:
|
||||
data['contract'] = data.get('name')
|
||||
return '/schemas/{schema_id}/templates/{template}/contracts/{contract}'.format(**data)
|
||||
|
||||
def filter_ref(self, **data):
|
||||
''' Create a filterRef string '''
|
||||
return '/schemas/{schema_id}/templates/{template}/filters/{filter}'.format(**data)
|
||||
|
||||
def vrf_ref(self, **data):
|
||||
''' Create vrfRef string '''
|
||||
return '/schemas/{schema_id}/templates/{template}/vrfs/{vrf}'.format(**data)
|
||||
|
||||
def make_reference(self, data, reftype, schema_id, template):
|
||||
''' Create a reference from a dictionary '''
|
||||
# Removes entry from payload
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if data.get('schema') is not None:
|
||||
schema_obj = self.get_obj('schemas', displayName=data.get('schema'))
|
||||
if not schema_obj:
|
||||
self.fail_json(msg="Referenced schema '{schema}' in {reftype}ref does not exist".format(reftype=reftype, **data))
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
if data.get('template') is not None:
|
||||
template = data.get('template')
|
||||
|
||||
refname = '%sName' % reftype
|
||||
|
||||
return {
|
||||
refname: data.get('name'),
|
||||
'schemaId': schema_id,
|
||||
'templateName': template,
|
||||
}
|
||||
|
||||
def make_subnets(self, data):
|
||||
''' Create a subnets list from input '''
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
subnets = []
|
||||
for subnet in data:
|
||||
subnets.append(dict(
|
||||
ip=subnet.get('ip'),
|
||||
description=subnet.get('description', subnet.get('ip')),
|
||||
scope=subnet.get('scope', 'private'),
|
||||
shared=subnet.get('shared', False),
|
||||
noDefaultGateway=subnet.get('no_default_gateway', False),
|
||||
))
|
||||
|
||||
return subnets
|
||||
|
||||
def sanitize(self, updates, collate=False, required=None, unwanted=None):
|
||||
''' Clean up unset keys from a request payload '''
|
||||
if required is None:
|
||||
required = []
|
||||
if unwanted is None:
|
||||
unwanted = []
|
||||
self.proposed = deepcopy(self.existing)
|
||||
self.sent = deepcopy(self.existing)
|
||||
|
||||
for key in self.existing:
|
||||
# Remove References
|
||||
if key.endswith('Ref'):
|
||||
del(self.proposed[key])
|
||||
del(self.sent[key])
|
||||
continue
|
||||
|
||||
# Removed unwanted keys
|
||||
elif key in unwanted:
|
||||
del(self.proposed[key])
|
||||
del(self.sent[key])
|
||||
continue
|
||||
|
||||
# Clean up self.sent
|
||||
for key in updates:
|
||||
# Always retain 'id'
|
||||
if key in required:
|
||||
pass
|
||||
|
||||
# Remove unspecified values
|
||||
elif not collate and updates[key] is None:
|
||||
if key in self.existing:
|
||||
del(self.sent[key])
|
||||
continue
|
||||
|
||||
# Remove identical values
|
||||
elif not collate and key in self.existing and updates[key] == self.existing[key]:
|
||||
del(self.sent[key])
|
||||
continue
|
||||
|
||||
# Add everything else
|
||||
if updates[key] is not None:
|
||||
self.sent[key] = updates[key]
|
||||
|
||||
# Update self.proposed
|
||||
self.proposed.update(self.sent)
|
||||
|
||||
def exit_json(self, **kwargs):
|
||||
''' Custom written method to exit from module. '''
|
||||
|
||||
if self.params.get('state') in ('absent', 'present'):
|
||||
if self.params.get('output_level') in ('debug', 'info'):
|
||||
self.result['previous'] = self.previous
|
||||
# FIXME: Modified header only works for PATCH
|
||||
if not self.has_modified and self.previous != self.existing:
|
||||
self.result['changed'] = True
|
||||
|
||||
# Return the gory details when we need it
|
||||
if self.params.get('output_level') == 'debug':
|
||||
self.result['method'] = self.method
|
||||
self.result['response'] = self.response
|
||||
self.result['status'] = self.status
|
||||
self.result['url'] = self.url
|
||||
|
||||
if self.params.get('state') in ('absent', 'present'):
|
||||
self.result['sent'] = self.sent
|
||||
self.result['proposed'] = self.proposed
|
||||
|
||||
self.result['current'] = self.existing
|
||||
|
||||
if self.module._diff and self.result.get('changed') is True:
|
||||
self.result['diff'] = dict(
|
||||
before=self.previous,
|
||||
after=self.existing,
|
||||
)
|
||||
|
||||
self.result.update(**kwargs)
|
||||
self.module.exit_json(**self.result)
|
||||
|
||||
def fail_json(self, msg, **kwargs):
|
||||
''' Custom written method to return info on failure. '''
|
||||
|
||||
if self.params.get('state') in ('absent', 'present'):
|
||||
if self.params.get('output_level') in ('debug', 'info'):
|
||||
self.result['previous'] = self.previous
|
||||
# FIXME: Modified header only works for PATCH
|
||||
if not self.has_modified and self.previous != self.existing:
|
||||
self.result['changed'] = True
|
||||
|
||||
# Return the gory details when we need it
|
||||
if self.params.get('output_level') == 'debug':
|
||||
if self.url is not None:
|
||||
self.result['method'] = self.method
|
||||
self.result['response'] = self.response
|
||||
self.result['status'] = self.status
|
||||
self.result['url'] = self.url
|
||||
|
||||
if self.params.get('state') in ('absent', 'present'):
|
||||
self.result['sent'] = self.sent
|
||||
self.result['proposed'] = self.proposed
|
||||
|
||||
self.result['current'] = self.existing
|
||||
|
||||
self.result.update(**kwargs)
|
||||
self.module.fail_json(msg=msg, **self.result)
|
@ -1,167 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_label
|
||||
short_description: Manage labels
|
||||
description:
|
||||
- Manage labels on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
label:
|
||||
description:
|
||||
- The name of the label.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
type:
|
||||
description:
|
||||
- The type of the label.
|
||||
type: str
|
||||
choices: [ site ]
|
||||
default: site
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new label
|
||||
mso_label:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
label: Belgium
|
||||
type: site
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a label
|
||||
mso_label:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
label: Belgium
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a label
|
||||
mso_label:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
label: Belgium
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all labels
|
||||
mso_label:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
label=dict(type='str', aliases=['name']),
|
||||
type=dict(type='str', default='site', choices=['site']),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['label']],
|
||||
['state', 'present', ['label']],
|
||||
],
|
||||
)
|
||||
|
||||
label = module.params.get('label')
|
||||
label_type = module.params.get('type')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
label_id = None
|
||||
path = 'labels'
|
||||
|
||||
# Query for existing object(s)
|
||||
if label:
|
||||
mso.existing = mso.get_obj(path, displayName=label)
|
||||
if mso.existing:
|
||||
label_id = mso.existing.get('id')
|
||||
# If we found an existing object, continue with it
|
||||
path = 'labels/{id}'.format(id=label_id)
|
||||
else:
|
||||
mso.existing = mso.query_objs(path)
|
||||
|
||||
if state == 'query':
|
||||
pass
|
||||
|
||||
elif state == 'absent':
|
||||
mso.previous = mso.existing
|
||||
if mso.existing:
|
||||
if module.check_mode:
|
||||
mso.existing = {}
|
||||
else:
|
||||
mso.existing = mso.request(path, method='DELETE')
|
||||
|
||||
elif state == 'present':
|
||||
mso.previous = mso.existing
|
||||
|
||||
payload = dict(
|
||||
id=label_id,
|
||||
displayName=label,
|
||||
type=label_type,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
if not issubset(mso.sent, mso.existing):
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='PUT', data=mso.sent)
|
||||
else:
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='POST', data=mso.sent)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,224 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_role
|
||||
short_description: Manage roles
|
||||
description:
|
||||
- Manage roles on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
role:
|
||||
description:
|
||||
- The name of the role.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name of the role to be displayed in the web UI.
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- The description of the role.
|
||||
type: str
|
||||
permissions:
|
||||
description:
|
||||
- A list of permissions tied to this role.
|
||||
type: list
|
||||
choices:
|
||||
- backup-db
|
||||
- manage-audit-records
|
||||
- manage-labels
|
||||
- manage-roles
|
||||
- manage-schemas
|
||||
- manage-sites
|
||||
- manage-tenants
|
||||
- manage-tenant-schemas
|
||||
- manage-users
|
||||
- platform-logs
|
||||
- view-all-audit-records
|
||||
- view-labels
|
||||
- view-roles
|
||||
- view-schemas
|
||||
- view-sites
|
||||
- view-tenants
|
||||
- view-tenant-schemas
|
||||
- view-users
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new role
|
||||
mso_role:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
role: readOnly
|
||||
display_name: Read Only
|
||||
description: Read-only access for troubleshooting
|
||||
permissions:
|
||||
- view-roles
|
||||
- view-schemas
|
||||
- view-sites
|
||||
- view-tenants
|
||||
- view-tenant-schemas
|
||||
- view-users
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a role
|
||||
mso_role:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
role: readOnly
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a role
|
||||
mso_role:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
role: readOnly
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all roles
|
||||
mso_role:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
role=dict(type='str', aliases=['name']),
|
||||
display_name=dict(type='str'),
|
||||
description=dict(type='str'),
|
||||
permissions=dict(type='list', choices=[
|
||||
'backup-db',
|
||||
'manage-audit-records',
|
||||
'manage-labels',
|
||||
'manage-roles',
|
||||
'manage-schemas',
|
||||
'manage-sites',
|
||||
'manage-tenants',
|
||||
'manage-tenant-schemas',
|
||||
'manage-users',
|
||||
'platform-logs',
|
||||
'view-all-audit-records',
|
||||
'view-labels',
|
||||
'view-roles',
|
||||
'view-schemas',
|
||||
'view-sites',
|
||||
'view-tenants',
|
||||
'view-tenant-schemas',
|
||||
'view-users',
|
||||
]),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['role']],
|
||||
['state', 'present', ['role']],
|
||||
],
|
||||
)
|
||||
|
||||
role = module.params.get('role')
|
||||
description = module.params.get('description')
|
||||
permissions = module.params.get('permissions')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
role_id = None
|
||||
path = 'roles'
|
||||
|
||||
# Query for existing object(s)
|
||||
if role:
|
||||
mso.existing = mso.get_obj(path, name=role)
|
||||
if mso.existing:
|
||||
role_id = mso.existing.get('id')
|
||||
# If we found an existing object, continue with it
|
||||
path = 'roles/{id}'.format(id=role_id)
|
||||
else:
|
||||
mso.existing = mso.query_objs(path)
|
||||
|
||||
if state == 'query':
|
||||
pass
|
||||
|
||||
elif state == 'absent':
|
||||
mso.previous = mso.existing
|
||||
if mso.existing:
|
||||
if module.check_mode:
|
||||
mso.existing = {}
|
||||
else:
|
||||
mso.existing = mso.request(path, method='DELETE')
|
||||
|
||||
elif state == 'present':
|
||||
mso.previous = mso.existing
|
||||
|
||||
payload = dict(
|
||||
id=role_id,
|
||||
name=role,
|
||||
displayName=role,
|
||||
description=description,
|
||||
permissions=permissions,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
if not issubset(mso.sent, mso.existing):
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='PUT', data=mso.sent)
|
||||
else:
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='POST', data=mso.sent)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,192 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema
|
||||
short_description: Manage schemas
|
||||
description:
|
||||
- Manage schemas on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
templates:
|
||||
description:
|
||||
- A list of templates for this schema.
|
||||
type: list
|
||||
sites:
|
||||
description:
|
||||
- A list of sites mapped to templates in this schema.
|
||||
type: list
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- Due to restrictions of the MSO REST API this module cannot create empty schemas (i.e. schemas without templates).
|
||||
Use the M(mso_schema_template) to automatically create schemas with templates.
|
||||
seealso:
|
||||
- module: mso_schema_site
|
||||
- module: mso_schema_template
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new schema
|
||||
mso_schema:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
state: present
|
||||
templates:
|
||||
- name: Template1
|
||||
displayName: Template 1
|
||||
tenantId: north_europe
|
||||
anps:
|
||||
<...>
|
||||
- name: Template2
|
||||
displayName: Template 2
|
||||
tenantId: nort_europe
|
||||
anps:
|
||||
<...>
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove schemas
|
||||
mso_schema:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a schema
|
||||
mso_schema:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all schemas
|
||||
mso_schema:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', aliases=['name']),
|
||||
templates=dict(type='list'),
|
||||
sites=dict(type='list'),
|
||||
# messages=dict(type='dict'),
|
||||
# associations=dict(type='list'),
|
||||
# health_faults=dict(type='list'),
|
||||
# references=dict(type='dict'),
|
||||
# policy_states=dict(type='list'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['schema']],
|
||||
['state', 'present', ['schema', 'templates']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
templates = module.params.get('templates')
|
||||
sites = module.params.get('sites')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
schema_id = None
|
||||
path = 'schemas'
|
||||
|
||||
# Query for existing object(s)
|
||||
if schema:
|
||||
mso.existing = mso.get_obj(path, displayName=schema)
|
||||
if mso.existing:
|
||||
schema_id = mso.existing.get('id')
|
||||
path = 'schemas/{id}'.format(id=schema_id)
|
||||
else:
|
||||
mso.existing = mso.query_objs(path)
|
||||
|
||||
if state == 'query':
|
||||
pass
|
||||
|
||||
elif state == 'absent':
|
||||
mso.previous = mso.existing
|
||||
if mso.existing:
|
||||
if module.check_mode:
|
||||
mso.existing = {}
|
||||
else:
|
||||
mso.existing = mso.request(path, method='DELETE')
|
||||
|
||||
elif state == 'present':
|
||||
mso.previous = mso.existing
|
||||
|
||||
payload = dict(
|
||||
id=schema_id,
|
||||
displayName=schema,
|
||||
templates=templates,
|
||||
sites=sites,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
if not issubset(mso.sent, mso.existing):
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='PUT', data=mso.sent)
|
||||
else:
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='POST', data=mso.sent)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,200 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site
|
||||
short_description: Manage sites in schemas
|
||||
description:
|
||||
- Manage sites on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site to manage.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_template
|
||||
- module: mso_site
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site to a schema
|
||||
mso_schema_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
site: bdsol-pod51
|
||||
template: Template 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site from a schema
|
||||
mso_schema_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
site: bdsol-pod51
|
||||
template: Template 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a schema site
|
||||
mso_schema_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
site: bdsol-pod51
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all schema sites
|
||||
mso_schema_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
site: bdsol-pod51
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', aliases=['name']),
|
||||
template=dict(type='str'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['site', 'template']],
|
||||
['state', 'present', ['site', 'template']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
# Schema exists
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
mso.existing = {}
|
||||
if 'sites' in schema_obj:
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if template:
|
||||
if (site_id, template) in sites:
|
||||
site_idx = sites.index((site_id, template))
|
||||
mso.existing = schema_obj.get('sites')[site_idx]
|
||||
else:
|
||||
mso.existing = schema_obj.get('sites')
|
||||
|
||||
if state == 'query':
|
||||
if not mso.existing:
|
||||
if template:
|
||||
mso.fail_json(msg="Template '{0}' not found".format(template))
|
||||
else:
|
||||
mso.existing = []
|
||||
mso.exit_json()
|
||||
|
||||
sites_path = '/sites'
|
||||
site_path = '/sites/{0}'.format(site)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
# Remove existing site
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=site_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
# Add new site
|
||||
payload = dict(
|
||||
siteId=site_id,
|
||||
templateName=template,
|
||||
anps=[],
|
||||
bds=[],
|
||||
contracts=[],
|
||||
externalEpgs=[],
|
||||
intersiteL3outs=[],
|
||||
serviceGraphs=[],
|
||||
vrfs=[],
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
ops.append(dict(op='add', path=sites_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,213 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_anp
|
||||
short_description: Manage site-local Application Network Profiles (ANPs) in schema template
|
||||
description:
|
||||
- Manage site-local ANPs in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_site
|
||||
- module: mso_schema_site_anp_epg
|
||||
- module: mso_schema_template_anp
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site ANP
|
||||
mso_schema_site_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site ANP
|
||||
mso_schema_site_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site ANPs
|
||||
mso_schema_site_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site ANPs
|
||||
mso_schema_site_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['anp']],
|
||||
['state', 'present', ['anp']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get ANP
|
||||
anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
|
||||
anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
|
||||
|
||||
if anp is not None and anp_ref in anps:
|
||||
anp_idx = anps.index(anp_ref)
|
||||
anp_path = '/sites/{0}/anps/{1}'.format(site_template, anp)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]
|
||||
|
||||
if state == 'query':
|
||||
if anp is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp))
|
||||
mso.exit_json()
|
||||
|
||||
anps_path = '/sites/{0}/anps'.format(site_template)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=anp_path))
|
||||
|
||||
elif state == 'present':
|
||||
|
||||
payload = dict(
|
||||
anpRef=dict(
|
||||
schemaId=schema_id,
|
||||
templateName=template,
|
||||
anpName=anp,
|
||||
),
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if not mso.existing:
|
||||
ops.append(dict(op='add', path=anps_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,228 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_anp_epg
|
||||
short_description: Manage site-local Endpoint Groups (EPGs) in schema template
|
||||
description:
|
||||
- Manage site-local EPGs in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_site_anp
|
||||
- module: mso_schema_site_anp_epg_subnet
|
||||
- module: mso_schema_template_anp_epg
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site EPG
|
||||
mso_schema_site_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site EPG
|
||||
mso_schema_site_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site EPGs
|
||||
mso_schema_site_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site EPGs
|
||||
mso_schema_site_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['epg']],
|
||||
['state', 'present', ['epg']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get ANP
|
||||
anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
|
||||
anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
|
||||
if anp_ref not in anps:
|
||||
mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
|
||||
anp_idx = anps.index(anp_ref)
|
||||
|
||||
# Get EPG
|
||||
epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
|
||||
epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg is not None and epg_ref in epgs:
|
||||
epg_idx = epgs.index(epg_ref)
|
||||
epg_path = '/sites/{0}/anps/{1}/epgs/{2}'.format(site_template, anp, epg)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]
|
||||
|
||||
if state == 'query':
|
||||
if epg is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg))
|
||||
mso.exit_json()
|
||||
|
||||
epgs_path = '/sites/{0}/anps/{1}/epgs'.format(site_template, anp)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=epg_path))
|
||||
|
||||
elif state == 'present':
|
||||
|
||||
payload = dict(
|
||||
epgRef=dict(
|
||||
schemaId=schema_id,
|
||||
templateName=template,
|
||||
anpName=anp,
|
||||
epgName=epg,
|
||||
),
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if not mso.existing:
|
||||
ops.append(dict(op='add', path=epgs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,401 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Nirav Katarmal (@nkatarmal-crest) <nirav.katarmal@crestdatasys.com>
|
||||
# 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: mso_schema_site_anp_epg_domain
|
||||
short_description: Manage site-local EPG domains in schema template
|
||||
description:
|
||||
- Manage site-local EPG domains in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Nirav Katarmal (@nkatarmal-crest)
|
||||
version_added: '2.9'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG.
|
||||
type: str
|
||||
domain_association_type:
|
||||
description:
|
||||
- The type of domain to associate.
|
||||
type: str
|
||||
choices: [ vmmDomain, l3ExtDomain, l2ExtDomain, physicalDomain, fibreChannel ]
|
||||
domain_profile:
|
||||
description:
|
||||
- The domain profile name.
|
||||
type: str
|
||||
deployment_immediacy:
|
||||
description:
|
||||
- The deployment immediacy of the domain.
|
||||
- C(immediate) means B(Deploy immediate).
|
||||
- C(lazy) means B(deploy on demand).
|
||||
type: str
|
||||
choices: [ immediate, lazy ]
|
||||
resolution_immediacy:
|
||||
description:
|
||||
- Determines when the policies should be resolved and available.
|
||||
- Defaults to C(lazy) when unset during creation.
|
||||
type: str
|
||||
choices: [ immediate, lazy, pre-provision ]
|
||||
micro_seg_vlan_type:
|
||||
description:
|
||||
- Virtual LAN type for microsegmentation. This attribute can only be used with vmmDomain domain association.
|
||||
type: str
|
||||
micro_seg_vlan:
|
||||
description:
|
||||
- Virtual LAN for microsegmentation. This attribute can only be used with vmmDomain domain association.
|
||||
type: int
|
||||
port_encap_vlan_type:
|
||||
description:
|
||||
- Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
|
||||
type: str
|
||||
port_encap_vlan:
|
||||
description:
|
||||
- Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
|
||||
type: int
|
||||
vlan_encap_mode:
|
||||
description:
|
||||
- Which VLAN enacap mode to use. This attribute can only be used with vmmDomain domain association.
|
||||
type: str
|
||||
choices: [ static, dynamic ]
|
||||
allow_micro_segmentation:
|
||||
description:
|
||||
- Specifies microsegmentation is enabled or not. This attribute can only be used with vmmDomain domain association.
|
||||
type: bool
|
||||
switch_type:
|
||||
description:
|
||||
- Which switch type to use with this domain association. This attribute can only be used with vmmDomain domain association.
|
||||
type: str
|
||||
switching_mode:
|
||||
description:
|
||||
- Which switching mode to use with this domain association. This attribute can only be used with vmmDomain domain association.
|
||||
type: str
|
||||
enhanced_lagpolicy_name:
|
||||
description:
|
||||
- EPG enhanced lagpolicy name. This attribute can only be used with vmmDomain domain association.
|
||||
type: str
|
||||
enhanced_lagpolicy_dn:
|
||||
description:
|
||||
- Distinguished name of EPG lagpolicy. This attribute can only be used with vmmDomain domain association.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing on object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_anp_epg
|
||||
- module: mso_schema_template_anp_epg
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new static leaf to a site EPG
|
||||
mso_schema_site_anp_epg_domain:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
domain_association_type: vmmDomain
|
||||
domain_profile: 'VMware-VMM'
|
||||
deployment_immediacy: lazy
|
||||
resolution_immediacy: pre-provision
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a static leaf from a site EPG
|
||||
mso_schema_site_anp_epg_domain:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
domain_association_type: vmmDomain
|
||||
domain_profile: 'VMware-VMM'
|
||||
deployment_immediacy: lazy
|
||||
resolution_immediacy: pre-provision
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site EPG static leaf
|
||||
mso_schema_site_anp_epg_domain:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
domain_association_type: vmmDomain
|
||||
domain_profile: 'VMware-VMM'
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site EPG static leafs
|
||||
mso_schema_site_anp_epg_domain:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', required=True),
|
||||
domain_association_type=dict(type='str', choices=['vmmDomain', 'l3ExtDomain', 'l2ExtDomain', 'physicalDomain', 'fibreChannel']),
|
||||
domain_profile=dict(type='str'),
|
||||
deployment_immediacy=dict(type='str', choices=['immediate', 'lazy']),
|
||||
resolution_immediacy=dict(type='str', choices=['immediate', 'lazy', 'pre-provision']),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
micro_seg_vlan_type=dict(type='str'),
|
||||
micro_seg_vlan=dict(type='int'),
|
||||
port_encap_vlan_type=dict(type='str'),
|
||||
port_encap_vlan=dict(type='int'),
|
||||
vlan_encap_mode=dict(type='str', choices=['static', 'dynamic']),
|
||||
allow_micro_segmentation=dict(type='bool'),
|
||||
switch_type=dict(type='str'),
|
||||
switching_mode=dict(type='str'),
|
||||
enhanced_lagpolicy_name=dict(type='str'),
|
||||
enhanced_lagpolicy_dn=dict(type='str'),
|
||||
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['domain_association_type', 'domain_profile', 'deployment_immediacy', 'resolution_immediacy']],
|
||||
['state', 'present', ['domain_association_type', 'domain_profile', 'deployment_immediacy', 'resolution_immediacy']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
domain_association_type = module.params.get('domain_association_type')
|
||||
domain_profile = module.params.get('domain_profile')
|
||||
deployment_immediacy = module.params.get('deployment_immediacy')
|
||||
resolution_immediacy = module.params.get('resolution_immediacy')
|
||||
state = module.params.get('state')
|
||||
micro_seg_vlan_type = module.params.get('micro_seg_vlan_type')
|
||||
micro_seg_vlan = module.params.get('micro_seg_vlan')
|
||||
port_encap_vlan_type = module.params.get('port_encap_vlan_type')
|
||||
port_encap_vlan = module.params.get('port_encap_vlan')
|
||||
vlan_encap_mode = module.params.get('vlan_encap_mode')
|
||||
allow_micro_segmentation = module.params.get('allow_micro_segmentation')
|
||||
switch_type = module.params.get('switch_type')
|
||||
switching_mode = module.params.get('switching_mode')
|
||||
enhanced_lagpolicy_name = module.params.get('enhanced_lagpolicy_name')
|
||||
enhanced_lagpolicy_dn = module.params.get('enhanced_lagpolicy_dn')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get ANP
|
||||
anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
|
||||
anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
|
||||
if anp_ref not in anps:
|
||||
mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
|
||||
anp_idx = anps.index(anp_ref)
|
||||
|
||||
# Get EPG
|
||||
epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
|
||||
print(epg_ref)
|
||||
epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg_ref not in epgs:
|
||||
mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1} epgref {2}".format(epg, str(schema_obj.get('sites')[site_idx]), epg_ref))
|
||||
epg_idx = epgs.index(epg_ref)
|
||||
|
||||
if domain_association_type == 'vmmDomain':
|
||||
domain_dn = 'uni/vmmp-VMware/dom-{0}'.format(domain_profile)
|
||||
elif domain_association_type == 'l3ExtDomain':
|
||||
domain_dn = 'uni/l3dom-{0}'.format(domain_profile)
|
||||
elif domain_association_type == 'l2ExtDomain':
|
||||
domain_dn = 'uni/l2dom-{0}'.format(domain_profile)
|
||||
elif domain_association_type == 'physicalDomain':
|
||||
domain_dn = 'uni/phys-{0}'.format(domain_profile)
|
||||
elif domain_association_type == 'fibreChannel':
|
||||
domain_dn = 'uni/fc-{0}'.format(domain_profile)
|
||||
else:
|
||||
domain_dn = ''
|
||||
|
||||
# Get Domains
|
||||
domains = [dom.get('dn') for dom in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['domainAssociations']]
|
||||
if domain_dn in domains:
|
||||
domain_idx = domains.index(domain_dn)
|
||||
domain_path = '/sites/{0}/anps/{1}/epgs/{2}/domainAssociations/{3}'.format(site_template, anp, epg, domain_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['domainAssociations'][domain_idx]
|
||||
|
||||
if state == 'query':
|
||||
if domain_association_type is None or domain_profile is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['domainAssociations']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Domain association '{domain_association_type}/{domain_profile}' not found".format(
|
||||
domain_association_type=domain_association_type,
|
||||
domain_profile=domain_profile))
|
||||
mso.exit_json()
|
||||
|
||||
domains_path = '/sites/{0}/anps/{1}/epgs/{2}/domainAssociations'.format(site_template, anp, epg)
|
||||
ops = []
|
||||
if domain_association_type == 'vmmDomain':
|
||||
vmmDomainProperties = {}
|
||||
if micro_seg_vlan_type and micro_seg_vlan:
|
||||
microSegVlan = dict(vlanType=micro_seg_vlan_type, vlan=micro_seg_vlan)
|
||||
vmmDomainProperties['microSegVlan'] = microSegVlan
|
||||
elif not micro_seg_vlan_type and micro_seg_vlan:
|
||||
mso.fail_json(msg="micro_seg_vlan_type is required when micro_seg_vlan is provided.")
|
||||
elif micro_seg_vlan_type and not micro_seg_vlan:
|
||||
mso.fail_json(msg="micro_seg_vlan is required when micro_seg_vlan_type is provided.")
|
||||
|
||||
if micro_seg_vlan_type and micro_seg_vlan:
|
||||
portEncapVlan = dict(vlanType=port_encap_vlan_type, vlan=port_encap_vlan)
|
||||
vmmDomainProperties['portEncapVlan'] = portEncapVlan
|
||||
elif not port_encap_vlan_type and port_encap_vlan:
|
||||
mso.fail_json(msg="port_encap_vlan_type is required when port_encap_vlan is provided.")
|
||||
elif port_encap_vlan_type and not port_encap_vlan:
|
||||
mso.fail_json(msg="port_encap_vlan is required when port_encap_vlan_type is provided.")
|
||||
|
||||
if vlan_encap_mode:
|
||||
vmmDomainProperties['vlanEncapMode'] = vlan_encap_mode
|
||||
|
||||
if allow_micro_segmentation:
|
||||
vmmDomainProperties['allowMicroSegmentation'] = allow_micro_segmentation
|
||||
if switch_type:
|
||||
vmmDomainProperties['switchType'] = switch_type
|
||||
if switching_mode:
|
||||
vmmDomainProperties['switchingMode'] = switching_mode
|
||||
|
||||
if enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
|
||||
enhancedLagPol = dict(name=enhanced_lagpolicy_name, dn=enhanced_lagpolicy_dn)
|
||||
epgLagPol = dict(enhancedLagPol=enhancedLagPol)
|
||||
vmmDomainProperties['epgLagPol'] = epgLagPol
|
||||
elif not enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
|
||||
mso.fail_json(msg="enhanced_lagpolicy_name is required when enhanced_lagpolicy_dn is provided.")
|
||||
elif enhanced_lagpolicy_name and not enhanced_lagpolicy_dn:
|
||||
mso.fail_json(msg="enhanced_lagpolicy_dn is required when enhanced_lagpolicy_name is provided.")
|
||||
|
||||
payload = dict(
|
||||
dn=domain_dn,
|
||||
domainType=domain_association_type,
|
||||
deploymentImmediacy=deployment_immediacy,
|
||||
resolutionImmediacy=resolution_immediacy,
|
||||
)
|
||||
|
||||
if vmmDomainProperties:
|
||||
payload['vmmDomainProperties'] = vmmDomainProperties
|
||||
else:
|
||||
payload = dict(
|
||||
dn=domain_dn,
|
||||
domainType=domain_association_type,
|
||||
deploymentImmediacy=deployment_immediacy,
|
||||
resolutionImmediacy=resolution_immediacy,
|
||||
)
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=domains_path))
|
||||
elif state == 'present':
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=domain_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=domains_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,261 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_anp_epg_staticleaf
|
||||
short_description: Manage site-local EPG static leafs in schema template
|
||||
description:
|
||||
- Manage site-local EPG static leafs in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG.
|
||||
type: str
|
||||
pod:
|
||||
description:
|
||||
- The pod of the static leaf.
|
||||
type: str
|
||||
leaf:
|
||||
description:
|
||||
- The path of the static leaf.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
vlan:
|
||||
description:
|
||||
- The VLAN id of the static leaf.
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing on object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_anp_epg
|
||||
- module: mso_schema_template_anp_epg
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new static leaf to a site EPG
|
||||
mso_schema_site_anp_epg_staticleaf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
leaf: Leaf1
|
||||
vlan: 123
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a static leaf from a site EPG
|
||||
mso_schema_site_anp_epg_staticleaf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
leaf: Leaf1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site EPG static leaf
|
||||
mso_schema_site_anp_epg_staticleaf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
leaf: Leaf1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site EPG static leafs
|
||||
mso_schema_site_anp_epg_staticleaf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', required=True),
|
||||
pod=dict(type='str'), # This parameter is not required for querying all objects
|
||||
leaf=dict(type='str', aliases=['name']),
|
||||
vlan=dict(type='int'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['pod', 'leaf', 'vlan']],
|
||||
['state', 'present', ['pod', 'leaf', 'vlan']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
pod = module.params.get('pod')
|
||||
leaf = module.params.get('leaf')
|
||||
vlan = module.params.get('vlan')
|
||||
state = module.params.get('state')
|
||||
|
||||
leafpath = 'topology/{0}/node-{1}'.format(pod, leaf)
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get ANP
|
||||
anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
|
||||
anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
|
||||
if anp_ref not in anps:
|
||||
mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
|
||||
anp_idx = anps.index(anp_ref)
|
||||
|
||||
# Get EPG
|
||||
epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
|
||||
epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg_ref not in epgs:
|
||||
mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ', '.join(epgs)))
|
||||
epg_idx = epgs.index(epg_ref)
|
||||
|
||||
# Get Leaf
|
||||
leafs = [(l.get('path'), l.get('portEncapVlan')) for l in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs']]
|
||||
if (leafpath, vlan) in leafs:
|
||||
leaf_idx = leafs.index((leafpath, vlan))
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
leaf_path = '/sites/{0}/anps/{1}/epgs/{2}/staticLeafs/{3}'.format(site_template, anp, epg, leaf_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs'][leaf_idx]
|
||||
|
||||
if state == 'query':
|
||||
if leaf is None or vlan is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Static leaf '{leaf}/{vlan}' not found".format(leaf=leaf, vlan=vlan))
|
||||
mso.exit_json()
|
||||
|
||||
leafs_path = '/sites/{0}/anps/{1}/epgs/{2}/staticLeafs'.format(site_template, anp, epg)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=leaf_path))
|
||||
|
||||
elif state == 'present':
|
||||
payload = dict(
|
||||
path=leafpath,
|
||||
portEncapVlan=vlan,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=leaf_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=leafs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,315 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_anp_epg_staticport
|
||||
short_description: Manage site-local EPG static ports in schema template
|
||||
description:
|
||||
- Manage site-local EPG static ports in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG.
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- The path type of the static port
|
||||
type: str
|
||||
choices: [ port, vpc ]
|
||||
default: port
|
||||
pod:
|
||||
description:
|
||||
- The pod of the static port.
|
||||
type: str
|
||||
leaf:
|
||||
description:
|
||||
- The leaf of the static port.
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- The path of the static port.
|
||||
type: str
|
||||
vlan:
|
||||
description:
|
||||
- The port encap VLAN id of the static port.
|
||||
type: int
|
||||
deployment_immediacy:
|
||||
description:
|
||||
- The deployment immediacy of the static port.
|
||||
- C(immediate) means B(Deploy immediate).
|
||||
- C(lazy) means B(deploy on demand).
|
||||
type: str
|
||||
choices: [ immediate, lazy ]
|
||||
mode:
|
||||
description:
|
||||
- The mode of the static port.
|
||||
- C(native) means B(Access (802.1p)).
|
||||
- C(regular) means B(Trunk).
|
||||
- C(untagged) means B(Access (untagged)).
|
||||
type: str
|
||||
choices: [ native, regular, untagged ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing an object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_anp_epg
|
||||
- module: mso_schema_template_anp_epg
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new static port to a site EPG
|
||||
mso_schema_site_anp_epg_staticport:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
type: port
|
||||
pod: pod-1
|
||||
leaf: 101
|
||||
path: eth1/1
|
||||
vlan: 126
|
||||
deployment_immediacy: immediate
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a static port from a site EPG
|
||||
mso_schema_site_anp_epg_staticport:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
type: port
|
||||
pod: pod-1
|
||||
leaf: 101
|
||||
path: eth1/1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site EPG static port
|
||||
mso_schema_site_anp_epg_staticport:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
type: port
|
||||
pod: pod-1
|
||||
leaf: 101
|
||||
path: eth1/1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site EPG static ports
|
||||
mso_schema_site_anp_epg_staticport:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', required=True),
|
||||
type=dict(type='str', default='port', choices=['port', 'vpc']),
|
||||
pod=dict(type='str'), # This parameter is not required for querying all objects
|
||||
leaf=dict(type='str'), # This parameter is not required for querying all objects
|
||||
path=dict(type='str'), # This parameter is not required for querying all objects
|
||||
vlan=dict(type='int'), # This parameter is not required for querying all objects
|
||||
deployment_immediacy=dict(type='str', choices=['immediate', 'lazy']),
|
||||
mode=dict(type='str', choices=['native', 'regular', 'untagged']),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['type', 'pod', 'leaf', 'path', 'vlan']],
|
||||
['state', 'present', ['type', 'pod', 'leaf', 'path', 'vlan']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
path_type = module.params.get('type')
|
||||
pod = module.params.get('pod')
|
||||
leaf = module.params.get('leaf')
|
||||
path = module.params.get('path')
|
||||
vlan = module.params.get('vlan')
|
||||
deployment_immediacy = module.params.get('deployment_immediacy')
|
||||
mode = module.params.get('mode')
|
||||
state = module.params.get('state')
|
||||
|
||||
if path_type == 'port':
|
||||
portpath = 'topology/{0}/paths-{1}/pathep-[{2}]'.format(pod, leaf, path)
|
||||
elif path_type == 'vpc':
|
||||
portpath = 'topology/{0}/protpaths-{1}/pathep-[{2}]'.format(pod, leaf, path)
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get ANP
|
||||
anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
|
||||
anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
|
||||
if anp_ref not in anps:
|
||||
mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
|
||||
anp_idx = anps.index(anp_ref)
|
||||
|
||||
# Get EPG
|
||||
epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
|
||||
epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg_ref not in epgs:
|
||||
mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ', '.join(epgs)))
|
||||
epg_idx = epgs.index(epg_ref)
|
||||
|
||||
# Get Leaf
|
||||
portpaths = [p.get('path') for p in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticPorts']]
|
||||
if portpath in portpaths:
|
||||
portpath_idx = portpaths.index(portpath)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
port_path = '/sites/{0}/anps/{1}/epgs/{2}/staticPorts/{3}'.format(site_template, anp, epg, portpath_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticPorts'][portpath_idx]
|
||||
|
||||
if state == 'query':
|
||||
if leaf is None or vlan is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticPorts']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Static port '{portpath}' not found".format(portpath=portpath))
|
||||
mso.exit_json()
|
||||
|
||||
ports_path = '/sites/{0}/anps/{1}/epgs/{2}/staticPorts'.format(site_template, anp, epg)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=port_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
if deployment_immediacy is None:
|
||||
deployment_immediacy = 'lazy'
|
||||
if mode is None:
|
||||
mode = 'untagged'
|
||||
|
||||
payload = dict(
|
||||
deploymentImmediacy=deployment_immediacy,
|
||||
mode=mode,
|
||||
path=portpath,
|
||||
portEncapVlan=vlan,
|
||||
type=path_type,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=port_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=ports_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,281 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_anp_epg_subnet
|
||||
short_description: Manage site-local EPG subnets in schema template
|
||||
description:
|
||||
- Manage site-local EPG subnets in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG.
|
||||
type: str
|
||||
subnet:
|
||||
description:
|
||||
- The IP range in CIDR notation.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ ip ]
|
||||
description:
|
||||
description:
|
||||
- The description of this subnet.
|
||||
type: str
|
||||
scope:
|
||||
description:
|
||||
- The scope of the subnet.
|
||||
type: str
|
||||
choices: [ private, public ]
|
||||
shared:
|
||||
description:
|
||||
- Whether this subnet is shared between VRFs.
|
||||
type: bool
|
||||
no_default_gateway:
|
||||
description:
|
||||
- Whether this subnet has a default gateway.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing on object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_anp_epg
|
||||
- module: mso_schema_template_anp_epg_subnet
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new subnet to a site EPG
|
||||
mso_schema_site_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
subnet: 10.0.0.0/24
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a subnet from a site EPG
|
||||
mso_schema_site_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
subnet: 10.0.0.0/24
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site EPG subnet
|
||||
mso_schema_site_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
epg: EPG1
|
||||
subnet: 10.0.0.0/24
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site EPG subnets
|
||||
mso_schema_site_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
anp: ANP1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_subnet_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', required=True),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
argument_spec.update(mso_subnet_spec())
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['subnet']],
|
||||
['state', 'present', ['subnet']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
subnet = module.params.get('subnet')
|
||||
description = module.params.get('description')
|
||||
scope = module.params.get('scope')
|
||||
shared = module.params.get('shared')
|
||||
no_default_gateway = module.params.get('no_default_gateway')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get ANP
|
||||
anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
|
||||
anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
|
||||
if anp_ref not in anps:
|
||||
mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
|
||||
anp_idx = anps.index(anp_ref)
|
||||
|
||||
# Get EPG
|
||||
epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
|
||||
epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg_ref not in epgs:
|
||||
mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ', '.join(epgs)))
|
||||
epg_idx = epgs.index(epg_ref)
|
||||
|
||||
# Get Subnet
|
||||
subnets = [s.get('ip') for s in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']]
|
||||
if subnet in subnets:
|
||||
subnet_idx = subnets.index(subnet)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
subnet_path = '/sites/{0}/anps/{1}/epgs/{2}/subnets/{3}'.format(site_template, anp, epg, subnet_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets'][subnet_idx]
|
||||
|
||||
if state == 'query':
|
||||
if subnet is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
|
||||
mso.exit_json()
|
||||
|
||||
subnets_path = '/sites/{0}/anps/{1}/epgs/{2}/subnets'.format(site_template, anp, epg)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=subnet_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
if description is None:
|
||||
description = subnet
|
||||
if scope is None:
|
||||
scope = 'private'
|
||||
if shared is None:
|
||||
shared = False
|
||||
if no_default_gateway is None:
|
||||
no_default_gateway = False
|
||||
|
||||
payload = dict(
|
||||
ip=subnet,
|
||||
description=description,
|
||||
scope=scope,
|
||||
shared=shared,
|
||||
noDefaultGateway=no_default_gateway,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,225 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_bd
|
||||
short_description: Manage site-local Bridge Domains (BDs) in schema template
|
||||
description:
|
||||
- Manage site-local BDs in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
bd:
|
||||
description:
|
||||
- The name of the BD to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
host_route:
|
||||
description:
|
||||
- Whether host-based routing is enabled.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_site
|
||||
- module: mso_schema_site_bd_l3out
|
||||
- module: mso_schema_site_bd_subnet
|
||||
- module: mso_schema_template_bd
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site BD
|
||||
mso_schema_site_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site BD
|
||||
mso_schema_site_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site BD
|
||||
mso_schema_site_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site BDs
|
||||
mso_schema_site_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
bd=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
host_route=dict(type='bool'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['bd']],
|
||||
['state', 'present', ['bd']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
bd = module.params.get('bd')
|
||||
host_route = module.params.get('host_route')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get BD
|
||||
bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd)
|
||||
bds = [v.get('bdRef') for v in schema_obj.get('sites')[site_idx]['bds']]
|
||||
if bd is not None and bd_ref in bds:
|
||||
bd_idx = bds.index(bd_ref)
|
||||
bd_path = '/sites/{0}/bds/{1}'.format(site_template, bd)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]
|
||||
|
||||
if state == 'query':
|
||||
if bd is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['bds']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="BD '{bd}' not found".format(bd=bd))
|
||||
mso.exit_json()
|
||||
|
||||
bds_path = '/sites/{0}/bds'.format(site_template)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=bd_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
if host_route is None:
|
||||
host_route = False
|
||||
|
||||
payload = dict(
|
||||
bdRef=dict(
|
||||
schemaId=schema_id,
|
||||
templateName=template,
|
||||
bdName=bd,
|
||||
),
|
||||
hostBasedRouting=host_route,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=bd_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=bds_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,222 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_bd_l3out
|
||||
short_description: Manage site-local BD l3out's in schema template
|
||||
description:
|
||||
- Manage site-local BDs l3out's in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
bd:
|
||||
description:
|
||||
- The name of the BD.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
l3out:
|
||||
description:
|
||||
- The name of the l3out.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing on object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_bd
|
||||
- module: mso_schema_template_bd
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site BD l3out
|
||||
mso_schema_site_bd_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
l3out: L3out1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site BD l3out
|
||||
mso_schema_site_bd_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
l3out: L3out1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site BD l3out
|
||||
mso_schema_site_bd_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
l3out: L3out1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site BD l3outs
|
||||
mso_schema_site_bd_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
bd=dict(type='str', required=True),
|
||||
l3out=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['l3out']],
|
||||
['state', 'present', ['l3out']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
bd = module.params.get('bd')
|
||||
l3out = module.params.get('l3out')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get BD
|
||||
bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd)
|
||||
bds = [v.get('bdRef') for v in schema_obj.get('sites')[site_idx]['bds']]
|
||||
if bd_ref not in bds:
|
||||
mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ', '.join(bds)))
|
||||
bd_idx = bds.index(bd_ref)
|
||||
|
||||
# Get L3out
|
||||
l3outs = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs']
|
||||
if l3out is not None and l3out in l3outs:
|
||||
l3out_idx = l3outs.index(l3out)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
l3out_path = '/sites/{0}/bds/{1}/l3Outs/{2}'.format(site_template, bd, l3out_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs'][l3out_idx]
|
||||
|
||||
if state == 'query':
|
||||
if l3out is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="L3out '{l3out}' not found".format(l3out=l3out))
|
||||
mso.exit_json()
|
||||
|
||||
l3outs_path = '/sites/{0}/bds/{1}/l3Outs'.format(site_template, bd)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=l3out_path))
|
||||
|
||||
elif state == 'present':
|
||||
mso.sent = l3out
|
||||
if not mso.existing:
|
||||
ops.append(dict(op='add', path=l3outs_path + '/-', value=l3out))
|
||||
|
||||
mso.existing = mso.sent
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,266 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_bd_subnet
|
||||
short_description: Manage site-local BD subnets in schema template
|
||||
description:
|
||||
- Manage site-local BD subnets in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
bd:
|
||||
description:
|
||||
- The name of the BD.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
subnet:
|
||||
description:
|
||||
- The IP range in CIDR notation.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ ip ]
|
||||
description:
|
||||
description:
|
||||
- The description of this subnet.
|
||||
type: str
|
||||
scope:
|
||||
description:
|
||||
- The scope of the subnet.
|
||||
type: str
|
||||
choices: [ private, public ]
|
||||
shared:
|
||||
description:
|
||||
- Whether this subnet is shared between VRFs.
|
||||
type: bool
|
||||
no_default_gateway:
|
||||
description:
|
||||
- Whether this subnet has a default gateway.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing on object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_bd
|
||||
- module: mso_schema_template_bd
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site BD subnet
|
||||
mso_schema_site_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
subnet: 11.11.11.0/24
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site BD subnet
|
||||
mso_schema_site_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
subnet: 11.11.11.0/24
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site BD subnet
|
||||
mso_schema_site_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
subnet: 11.11.11.0/24
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site BD subnets
|
||||
mso_schema_site_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
bd: BD1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_subnet_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
bd=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
argument_spec.update(mso_subnet_spec())
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['bd']],
|
||||
['state', 'present', ['bd']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
bd = module.params.get('bd')
|
||||
subnet = module.params.get('subnet')
|
||||
description = module.params.get('description')
|
||||
scope = module.params.get('scope')
|
||||
shared = module.params.get('shared')
|
||||
no_default_gateway = module.params.get('no_default_gateway')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get BD
|
||||
bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd)
|
||||
bds = [v.get('bdRef') for v in schema_obj.get('sites')[site_idx]['bds']]
|
||||
if bd_ref not in bds:
|
||||
mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ', '.join(bds)))
|
||||
bd_idx = bds.index(bd_ref)
|
||||
|
||||
# Get Subnet
|
||||
subnets = [s.get('ip') for s in schema_obj.get('sites')[site_idx]['bds'][bd_idx]['subnets']]
|
||||
if subnet in subnets:
|
||||
subnet_idx = subnets.index(subnet)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
subnet_path = '/sites/{0}/bds/{1}/subnets/{2}'.format(site_template, bd, subnet_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['subnets'][subnet_idx]
|
||||
|
||||
if state == 'query':
|
||||
if subnet is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['subnets']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
|
||||
mso.exit_json()
|
||||
|
||||
subnets_path = '/sites/{0}/bds/{1}/subnets'.format(site_template, bd)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=subnet_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
if description is None:
|
||||
description = subnet
|
||||
if scope is None:
|
||||
scope = 'private'
|
||||
if shared is None:
|
||||
shared = False
|
||||
if no_default_gateway is None:
|
||||
no_default_gateway = False
|
||||
|
||||
payload = dict(
|
||||
ip=subnet,
|
||||
description=description,
|
||||
scope=scope,
|
||||
shared=shared,
|
||||
noDefaultGateway=no_default_gateway,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,212 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_vrf
|
||||
short_description: Manage site-local VRFs in schema template
|
||||
description:
|
||||
- Manage site-local VRFs in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
vrf:
|
||||
description:
|
||||
- The name of the VRF to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_site
|
||||
- module: mso_schema_template_vrf
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site VRF
|
||||
mso_schema_site_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site VRF
|
||||
mso_schema_site_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site VRF
|
||||
mso_schema_site_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site VRFs
|
||||
mso_schema_site_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
vrf=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['vrf']],
|
||||
['state', 'present', ['vrf']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
vrf = module.params.get('vrf')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get VRF
|
||||
vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
|
||||
vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
|
||||
if vrf is not None and vrf_ref in vrfs:
|
||||
vrf_idx = vrfs.index(vrf_ref)
|
||||
vrf_path = '/sites/{0}/vrfs/{1}'.format(site_template, vrf)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]
|
||||
|
||||
if state == 'query':
|
||||
if vrf is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf))
|
||||
mso.exit_json()
|
||||
|
||||
vrfs_path = '/sites/{0}/vrfs'.format(site_template)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=vrf_path))
|
||||
|
||||
elif state == 'present':
|
||||
payload = dict(
|
||||
vrfRef=dict(
|
||||
schemaId=schema_id,
|
||||
templateName=template,
|
||||
vrfName=vrf,
|
||||
),
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=vrf_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=vrfs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,225 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_vrf_region
|
||||
short_description: Manage site-local VRF regions in schema template
|
||||
description:
|
||||
- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
vrf:
|
||||
description:
|
||||
- The name of the VRF.
|
||||
type: str
|
||||
region:
|
||||
description:
|
||||
- The name of the region to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_site_vrf
|
||||
- module: mso_schema_template_vrf
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site VRF region
|
||||
mso_schema_template_vrf_region:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site VRF region
|
||||
mso_schema_template_vrf_region:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site VRF region
|
||||
mso_schema_template_vrf_region:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site VRF regions
|
||||
mso_schema_template_vrf_region:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
vrf=dict(type='str', required=True),
|
||||
region=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['region']],
|
||||
['state', 'present', ['region']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
vrf = module.params.get('vrf')
|
||||
region = module.params.get('region')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get VRF
|
||||
vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
|
||||
vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
|
||||
if vrf_ref not in vrfs:
|
||||
mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ', '.join(vrfs)))
|
||||
vrf_idx = vrfs.index(vrf_ref)
|
||||
|
||||
# Get Region
|
||||
regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']]
|
||||
if region is not None and region in regions:
|
||||
region_idx = regions.index(region)
|
||||
region_path = '/sites/{0}/vrfs/{1}/regions/{2}'.format(site_template, vrf, region)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]
|
||||
|
||||
if state == 'query':
|
||||
if region is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Region '{region}' not found".format(region=region))
|
||||
mso.exit_json()
|
||||
|
||||
regions_path = '/sites/{0}/vrfs/{1}/regions'.format(site_template, vrf)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=region_path))
|
||||
|
||||
elif state == 'present':
|
||||
|
||||
payload = dict(
|
||||
name=region,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=region_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=regions_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,257 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_vrf_region_cidr
|
||||
short_description: Manage site-local VRF region CIDRs in schema template
|
||||
description:
|
||||
- Manage site-local VRF region CIDRs in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
vrf:
|
||||
description:
|
||||
- The name of the VRF.
|
||||
type: str
|
||||
region:
|
||||
description:
|
||||
- The name of the region.
|
||||
type: str
|
||||
cidr:
|
||||
description:
|
||||
- The name of the region CIDR to manage.
|
||||
type: str
|
||||
aliases: [ ip ]
|
||||
primary:
|
||||
description:
|
||||
- Whether this is the primary CIDR.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing on object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_vrf_region
|
||||
- module: mso_schema_site_vrf_region_cidr_subnet
|
||||
- module: mso_schema_template_vrf
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site VRF region CIDR
|
||||
mso_schema_template_vrf_region_cidr:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
cidr: 14.14.14.1/24
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site VRF region CIDR
|
||||
mso_schema_template_vrf_region_cidr:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
cidr: 14.14.14.1/24
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site VRF region CIDR
|
||||
mso_schema_template_vrf_region_cidr:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
cidr: 14.14.14.1/24
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site VRF region CIDR
|
||||
mso_schema_template_vrf_region_cidr:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
vrf=dict(type='str', required=True),
|
||||
region=dict(type='str', required=True),
|
||||
cidr=dict(type='str', aliases=['ip']), # This parameter is not required for querying all objects
|
||||
primary=dict(type='bool'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['cidr']],
|
||||
['state', 'present', ['cidr']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
vrf = module.params.get('vrf')
|
||||
region = module.params.get('region')
|
||||
cidr = module.params.get('cidr')
|
||||
primary = module.params.get('primary')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get VRF
|
||||
vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
|
||||
vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
|
||||
if vrf_ref not in vrfs:
|
||||
mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ', '.join(vrfs)))
|
||||
vrf_idx = vrfs.index(vrf_ref)
|
||||
|
||||
# Get Region
|
||||
regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']]
|
||||
if region not in regions:
|
||||
mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ', '.join(regions)))
|
||||
region_idx = regions.index(region)
|
||||
|
||||
# Get CIDR
|
||||
cidrs = [c.get('ip') for c in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs']]
|
||||
if cidr is not None and cidr in cidrs:
|
||||
cidr_idx = cidrs.index(cidr)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
cidr_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}'.format(site_template, vrf, region, cidr_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]
|
||||
|
||||
if state == 'query':
|
||||
if cidr is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="CIDR IP '{cidr}' not found".format(cidr=cidr))
|
||||
mso.exit_json()
|
||||
|
||||
cidrs_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs'.format(site_template, vrf, region)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=cidr_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
if primary is None:
|
||||
primary = False
|
||||
|
||||
payload = dict(
|
||||
ip=cidr,
|
||||
primary=primary,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=cidr_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=cidrs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,271 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_site_vrf_region_cidr_subnet
|
||||
short_description: Manage site-local VRF regions in schema template
|
||||
description:
|
||||
- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
vrf:
|
||||
description:
|
||||
- The name of the VRF.
|
||||
type: str
|
||||
region:
|
||||
description:
|
||||
- The name of the region.
|
||||
type: str
|
||||
cidr:
|
||||
description:
|
||||
- The IP range of for the region CIDR.
|
||||
type: str
|
||||
subnet:
|
||||
description:
|
||||
- The IP subnet of this region CIDR.
|
||||
type: str
|
||||
aliases: [ ip ]
|
||||
zone:
|
||||
description:
|
||||
- The name of the zone for the region CIDR subnet.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
|
||||
This can cause silent corruption on concurrent access when changing/removing on object as
|
||||
the wrong object may be referenced. This module is affected by this deficiency.
|
||||
seealso:
|
||||
- module: mso_schema_site_vrf_region_cidr
|
||||
- module: mso_schema_template_vrf
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site VRF region CIDR subnet
|
||||
mso_schema_template_vrf_region_cidr_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
cidr: 14.14.14.1/24
|
||||
subnet: 14.14.14.2/24
|
||||
zone: us-west-1a
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site VRF region CIDR subnet
|
||||
mso_schema_site_vrf_region_cidr_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
cidr: 14.14.14.1/24
|
||||
subnet: 14.14.14.2/24
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific site VRF region CIDR subnet
|
||||
mso_schema_site_vrf_region_cidr_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
cidr: 14.14.14.1/24
|
||||
subnet: 14.14.14.2/24
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all site VRF region CIDR subnet
|
||||
mso_schema_site_vrf_region_cidr_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema1
|
||||
site: Site1
|
||||
template: Template1
|
||||
vrf: VRF1
|
||||
region: us-west-1
|
||||
cidr: 14.14.14.1/24
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
site=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
vrf=dict(type='str', required=True),
|
||||
region=dict(type='str', required=True),
|
||||
cidr=dict(type='str', required=True),
|
||||
subnet=dict(type='str', aliases=['ip']), # This parameter is not required for querying all objects
|
||||
zone=dict(type='str', aliases=['name']),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['subnet']],
|
||||
['state', 'present', ['subnet', 'zone']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
site = module.params.get('site')
|
||||
template = module.params.get('template')
|
||||
vrf = module.params.get('vrf')
|
||||
region = module.params.get('region')
|
||||
cidr = module.params.get('cidr')
|
||||
subnet = module.params.get('subnet')
|
||||
zone = module.params.get('zone')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
schema_id = schema_obj.get('id')
|
||||
|
||||
# Get site
|
||||
site_id = mso.lookup_site(site)
|
||||
|
||||
# Get site_idx
|
||||
sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
|
||||
if (site_id, template) not in sites:
|
||||
mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
|
||||
|
||||
# Schema-access uses indexes
|
||||
site_idx = sites.index((site_id, template))
|
||||
# Path-based access uses site_id-template
|
||||
site_template = '{0}-{1}'.format(site_id, template)
|
||||
|
||||
# Get VRF
|
||||
vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
|
||||
vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
|
||||
if vrf_ref not in vrfs:
|
||||
mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ', '.join(vrfs)))
|
||||
vrf_idx = vrfs.index(vrf_ref)
|
||||
|
||||
# Get Region
|
||||
regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']]
|
||||
if region not in regions:
|
||||
mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ', '.join(regions)))
|
||||
region_idx = regions.index(region)
|
||||
|
||||
# Get CIDR
|
||||
cidrs = [c.get('ip') for c in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs']]
|
||||
if cidr not in cidrs:
|
||||
mso.fail_json(msg="Provided CIDR IP '{0}' does not exist. Existing CIDR IPs: {1}".format(cidr, ', '.join(cidrs)))
|
||||
cidr_idx = cidrs.index(cidr)
|
||||
|
||||
# Get Subnet
|
||||
subnets = [s.get('ip') for s in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]['subnets']]
|
||||
if subnet is not None and subnet in subnets:
|
||||
subnet_idx = subnets.index(subnet)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
subnet_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets/{4}'.format(site_template, vrf, region, cidr_idx, subnet_idx)
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]['subnets'][subnet_idx]
|
||||
|
||||
if state == 'query':
|
||||
if subnet is None:
|
||||
mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]['subnets']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
|
||||
mso.exit_json()
|
||||
|
||||
subnets_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets'.format(site_template, vrf, region, cidr_idx)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=subnet_path))
|
||||
|
||||
elif state == 'present':
|
||||
|
||||
payload = dict(
|
||||
ip=subnet,
|
||||
zone=zone,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,247 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template
|
||||
short_description: Manage templates in schemas
|
||||
description:
|
||||
- Manage templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
tenant:
|
||||
description:
|
||||
- The tenant used for this template.
|
||||
type: str
|
||||
required: yes
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- Due to restrictions of the MSO REST API this module creates schemas when needed, and removes them when the last template has been removed.
|
||||
seealso:
|
||||
- module: mso_schema
|
||||
- module: mso_schema_site
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new template to a schema
|
||||
mso_schema_template:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
tenant: Tenant 1
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a template from a schema
|
||||
mso_schema_template:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
tenant: Tenant 1
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a template
|
||||
mso_schema_template:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
tenant: Tenant 1
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all templates
|
||||
mso_schema_template:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
tenant: Tenant 1
|
||||
schema: Schema 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
tenant=dict(type='str', required=True),
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', aliases=['name']),
|
||||
display_name=dict(type='str'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['template']],
|
||||
['state', 'present', ['template']],
|
||||
],
|
||||
)
|
||||
|
||||
tenant = module.params.get('tenant')
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
display_name = module.params.get('display_name')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
|
||||
mso.existing = {}
|
||||
if schema_obj:
|
||||
# Schema exists
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template:
|
||||
if template in templates:
|
||||
template_idx = templates.index(template)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]
|
||||
else:
|
||||
mso.existing = schema_obj.get('templates')
|
||||
else:
|
||||
schema_path = 'schemas'
|
||||
|
||||
if state == 'query':
|
||||
if not mso.existing:
|
||||
if template:
|
||||
mso.fail_json(msg="Template '{0}' not found".format(template))
|
||||
else:
|
||||
mso.existing = []
|
||||
mso.exit_json()
|
||||
|
||||
template_path = '/templates/{0}'.format(template)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
mso.proposed = mso.sent = {}
|
||||
|
||||
if not schema_obj:
|
||||
# There was no schema to begin with
|
||||
pass
|
||||
elif len(templates) == 1:
|
||||
# There is only one tenant, remove schema
|
||||
mso.existing = {}
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='DELETE')
|
||||
elif mso.existing:
|
||||
# Remove existing template
|
||||
mso.existing = {}
|
||||
ops.append(dict(op='remove', path=template_path))
|
||||
else:
|
||||
# There was no template to begin with
|
||||
pass
|
||||
|
||||
elif state == 'present':
|
||||
tenant_id = mso.lookup_tenant(tenant)
|
||||
|
||||
if display_name is None:
|
||||
display_name = mso.existing.get('displayName', template)
|
||||
|
||||
if not schema_obj:
|
||||
# Schema does not exist, so we have to create it
|
||||
payload = dict(
|
||||
displayName=schema,
|
||||
templates=[dict(
|
||||
name=template,
|
||||
displayName=display_name,
|
||||
tenantId=tenant_id,
|
||||
)],
|
||||
sites=[],
|
||||
)
|
||||
|
||||
mso.existing = payload.get('templates')[0]
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='POST', data=payload)
|
||||
|
||||
elif mso.existing:
|
||||
# Template exists, so we have to update it
|
||||
payload = dict(
|
||||
name=template,
|
||||
displayName=display_name,
|
||||
tenantId=tenant_id,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
ops.append(dict(op='replace', path=template_path + '/displayName', value=display_name))
|
||||
ops.append(dict(op='replace', path=template_path + '/tenantId', value=tenant_id))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
# Template does not exist, so we have to add it
|
||||
payload = dict(
|
||||
name=template,
|
||||
displayName=display_name,
|
||||
tenantId=tenant_id,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
ops.append(dict(op='add', path='/templates/-', value=payload))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,205 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_anp
|
||||
short_description: Manage Application Network Profiles (ANPs) in schema templates
|
||||
description:
|
||||
- Manage ANPs in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_template
|
||||
- module: mso_schema_template_anp_epg
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new ANP
|
||||
mso_schema_template_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove an ANP
|
||||
mso_schema_template_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific ANPs
|
||||
mso_schema_template_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all ANPs
|
||||
mso_schema_template_anp:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
display_name=dict(type='str'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['anp']],
|
||||
['state', 'present', ['anp']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
display_name = module.params.get('display_name')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get ANP
|
||||
anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
|
||||
|
||||
if anp is not None and anp in anps:
|
||||
anp_idx = anps.index(anp)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]
|
||||
|
||||
if state == 'query':
|
||||
if anp is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp))
|
||||
mso.exit_json()
|
||||
|
||||
anps_path = '/templates/{0}/anps'.format(template)
|
||||
anp_path = '/templates/{0}/anps/{1}'.format(template, anp)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=anp_path))
|
||||
|
||||
elif state == 'present':
|
||||
|
||||
if display_name is None and not mso.existing:
|
||||
display_name = anp
|
||||
|
||||
epgs = []
|
||||
if mso.existing:
|
||||
epgs = None
|
||||
|
||||
payload = dict(
|
||||
name=anp,
|
||||
displayName=display_name,
|
||||
epgs=epgs,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
if display_name is not None:
|
||||
ops.append(dict(op='replace', path=anp_path + '/displayName', value=display_name))
|
||||
else:
|
||||
ops.append(dict(op='add', path=anps_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,373 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_anp_epg
|
||||
short_description: Manage Endpoint Groups (EPGs) in schema templates
|
||||
description:
|
||||
- Manage EPGs in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
required: yes
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
# contracts:
|
||||
# description:
|
||||
# - A list of contracts associated to this ANP.
|
||||
# type: list
|
||||
bd:
|
||||
description:
|
||||
- The BD associated to this ANP.
|
||||
type: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the BD to associate with.
|
||||
required: true
|
||||
type: str
|
||||
schema:
|
||||
description:
|
||||
- The schema that defines the referenced BD.
|
||||
- If this parameter is unspecified, it defaults to the current schema.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- The template that defines the referenced BD.
|
||||
type: str
|
||||
vrf:
|
||||
version_added: '2.9'
|
||||
description:
|
||||
- The VRF associated to this ANP.
|
||||
type: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the VRF to associate with.
|
||||
required: true
|
||||
type: str
|
||||
schema:
|
||||
description:
|
||||
- The schema that defines the referenced VRF.
|
||||
- If this parameter is unspecified, it defaults to the current schema.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- The template that defines the referenced VRF.
|
||||
type: str
|
||||
subnets:
|
||||
description:
|
||||
- The subnets associated to this ANP.
|
||||
type: list
|
||||
suboptions:
|
||||
subnet:
|
||||
description:
|
||||
- The IP range in CIDR notation.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ ip ]
|
||||
description:
|
||||
description:
|
||||
- The description of this subnet.
|
||||
type: str
|
||||
scope:
|
||||
description:
|
||||
- The scope of the subnet.
|
||||
type: str
|
||||
choices: [ private, public ]
|
||||
shared:
|
||||
description:
|
||||
- Whether this subnet is shared between VRFs.
|
||||
type: bool
|
||||
no_default_gateway:
|
||||
description:
|
||||
- Whether this subnet has a default gateway.
|
||||
type: bool
|
||||
useg_epg:
|
||||
description:
|
||||
- Whether this is a USEG EPG.
|
||||
type: bool
|
||||
# useg_epg_attributes:
|
||||
# description:
|
||||
# - A dictionary consisting of USEG attributes.
|
||||
# type: dict
|
||||
intra_epg_isolation:
|
||||
description:
|
||||
- Whether intra EPG isolation is enforced.
|
||||
- When not specified, this parameter defaults to C(unenforced).
|
||||
type: str
|
||||
choices: [ enforced, unenforced ]
|
||||
intersite_multicaste_source:
|
||||
description:
|
||||
- Whether intersite multicast source is enabled.
|
||||
- When not specified, this parameter defaults to C(no).
|
||||
type: bool
|
||||
preferred_group:
|
||||
description:
|
||||
- Whether this EPG is added to preferred group or not.
|
||||
- When not specified, this parameter defaults to C(no).
|
||||
type: bool
|
||||
version_added: 2.9
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_template_anp
|
||||
- module: mso_schema_template_anp_epg_subnet
|
||||
- module: mso_schema_template_bd
|
||||
- module: mso_schema_template_contract_filter
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new EPG
|
||||
mso_schema_template_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
bd:
|
||||
name: bd1
|
||||
vrf:
|
||||
name: vrf1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Add a new EPG with preferred group.
|
||||
mso_schema_template_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
state: present
|
||||
preferred_group: yes
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove an EPG
|
||||
mso_schema_template_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
bd:
|
||||
name: bd1
|
||||
vrf:
|
||||
name: vrf1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific EPG
|
||||
mso_schema_template_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
bd:
|
||||
name: bd1
|
||||
vrf:
|
||||
name: vrf1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all EPGs
|
||||
mso_schema_template_anp_epg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
bd:
|
||||
name: bd1
|
||||
vrf:
|
||||
name: vrf1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_subnet_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
bd=dict(type='dict', options=mso_reference_spec()),
|
||||
vrf=dict(type='dict', options=mso_reference_spec()),
|
||||
display_name=dict(type='str'),
|
||||
useg_epg=dict(type='bool'),
|
||||
intra_epg_isolation=dict(type='str', choices=['enforced', 'unenforced']),
|
||||
intersite_multicaste_source=dict(type='bool'),
|
||||
subnets=dict(type='list', options=mso_subnet_spec()),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
preferred_group=dict(type='bool'),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['epg']],
|
||||
['state', 'present', ['epg']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
display_name = module.params.get('display_name')
|
||||
bd = module.params.get('bd')
|
||||
vrf = module.params.get('vrf')
|
||||
useg_epg = module.params.get('useg_epg')
|
||||
intra_epg_isolation = module.params.get('intra_epg_isolation')
|
||||
intersite_multicaste_source = module.params.get('intersite_multicaste_source')
|
||||
subnets = module.params.get('subnets')
|
||||
state = module.params.get('state')
|
||||
preferred_group = module.params.get('preferred_group')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if schema_obj:
|
||||
schema_id = schema_obj.get('id')
|
||||
else:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get ANP
|
||||
anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
|
||||
if anp not in anps:
|
||||
mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
|
||||
anp_idx = anps.index(anp)
|
||||
|
||||
# Get EPG
|
||||
epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg is not None and epg in epgs:
|
||||
epg_idx = epgs.index(epg)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]
|
||||
|
||||
if state == 'query':
|
||||
if epg is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg))
|
||||
mso.exit_json()
|
||||
|
||||
epgs_path = '/templates/{0}/anps/{1}/epgs'.format(template, anp)
|
||||
epg_path = '/templates/{0}/anps/{1}/epgs/{2}'.format(template, anp, epg)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=epg_path))
|
||||
|
||||
elif state == 'present':
|
||||
bd_ref = mso.make_reference(bd, 'bd', schema_id, template)
|
||||
vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
|
||||
subnets = mso.make_subnets(subnets)
|
||||
|
||||
if display_name is None and not mso.existing:
|
||||
display_name = epg
|
||||
|
||||
payload = dict(
|
||||
name=epg,
|
||||
displayName=display_name,
|
||||
uSegEpg=useg_epg,
|
||||
intraEpg=intra_epg_isolation,
|
||||
proxyArp=intersite_multicaste_source,
|
||||
# FIXME: Missing functionality
|
||||
# uSegAttrs=[],
|
||||
contractRelationships=[],
|
||||
subnets=subnets,
|
||||
bdRef=bd_ref,
|
||||
preferredGroup=preferred_group,
|
||||
vrfRef=vrf_ref,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=epg_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=epgs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,262 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_anp_epg_contract
|
||||
short_description: Manage EPG contracts in schema templates
|
||||
description:
|
||||
- Manage EPG contracts in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template to change.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
required: yes
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG to manage.
|
||||
type: str
|
||||
required: yes
|
||||
contract:
|
||||
description:
|
||||
- A contract associated to this EPG.
|
||||
type: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the Contract to associate with.
|
||||
required: true
|
||||
type: str
|
||||
schema:
|
||||
description:
|
||||
- The schema that defines the referenced BD.
|
||||
- If this parameter is unspecified, it defaults to the current schema.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- The template that defines the referenced BD.
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- The type of contract.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ consumer, provider ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_template_anp_epg
|
||||
- module: mso_schema_template_contract_filter
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a contract to an EPG
|
||||
mso_schema_template_anp_epg_contract:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
contract:
|
||||
name: Contract 1
|
||||
type: consumer
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a Contract
|
||||
mso_schema_template_anp_epg_contract:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
contract:
|
||||
name: Contract 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific Contract
|
||||
mso_schema_template_anp_epg_contract:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
contract:
|
||||
name: Contract 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all Contracts
|
||||
mso_schema_template_anp_epg_contract:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_contractref_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', required=True),
|
||||
contract=dict(type='dict', options=mso_contractref_spec()),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['contract']],
|
||||
['state', 'present', ['contract']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
contract = module.params.get('contract')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
if contract:
|
||||
if contract.get('schema') is None:
|
||||
contract['schema'] = schema
|
||||
contract['schema_id'] = mso.lookup_schema(contract.get('schema'))
|
||||
if contract.get('template') is None:
|
||||
contract['template'] = template
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if schema_obj:
|
||||
schema_id = schema_obj.get('id')
|
||||
else:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get ANP
|
||||
anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
|
||||
if anp not in anps:
|
||||
mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
|
||||
anp_idx = anps.index(anp)
|
||||
|
||||
# Get EPG
|
||||
epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg not in epgs:
|
||||
mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=', '.join(epgs)))
|
||||
epg_idx = epgs.index(epg)
|
||||
|
||||
# Get Contract
|
||||
if contract:
|
||||
contracts = [(c.get('contractRef'),
|
||||
c.get('relationshipType')) for c in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['contractRelationships']]
|
||||
contract_ref = mso.contract_ref(**contract)
|
||||
if (contract_ref, contract.get('type')) in contracts:
|
||||
contract_idx = contracts.index((contract_ref, contract.get('type')))
|
||||
contract_path = '/templates/{0}/anps/{1}/epgs/{2}/contractRelationships/{3}'.format(template, anp, epg, contract)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['contractRelationships'][contract_idx]
|
||||
|
||||
if state == 'query':
|
||||
if not contract:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['contractRelationships']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Contract '{0}' not found".format(contract_ref))
|
||||
mso.exit_json()
|
||||
|
||||
contracts_path = '/templates/{0}/anps/{1}/epgs/{2}/contractRelationships'.format(template, anp, epg)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=contract_path))
|
||||
|
||||
elif state == 'present':
|
||||
payload = dict(
|
||||
relationshipType=contract.get('type'),
|
||||
contractRef=dict(
|
||||
contractName=contract.get('name'),
|
||||
templateName=contract.get('template'),
|
||||
schemaId=contract.get('schema_id'),
|
||||
),
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=contract_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=contracts_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,258 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_anp_epg_subnet
|
||||
short_description: Manage EPG subnets in schema templates
|
||||
description:
|
||||
- Manage EPG subnets in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template to change.
|
||||
type: str
|
||||
required: yes
|
||||
anp:
|
||||
description:
|
||||
- The name of the ANP.
|
||||
type: str
|
||||
required: yes
|
||||
epg:
|
||||
description:
|
||||
- The name of the EPG to manage.
|
||||
type: str
|
||||
required: yes
|
||||
subnet:
|
||||
description:
|
||||
- The IP range in CIDR notation.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ ip ]
|
||||
description:
|
||||
description:
|
||||
- The description of this subnet.
|
||||
type: str
|
||||
scope:
|
||||
description:
|
||||
- The scope of the subnet.
|
||||
type: str
|
||||
choices: [ private, public ]
|
||||
shared:
|
||||
description:
|
||||
- Whether this subnet is shared between VRFs.
|
||||
type: bool
|
||||
no_default_gateway:
|
||||
description:
|
||||
- Whether this subnet has a default gateway.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- Due to restrictions of the MSO REST API concurrent modifications to EPG subnets can be dangerous and corrupt data.
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new subnet to an EPG
|
||||
mso_schema_template_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
subnet: 10.0.0.0/24
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a subnet from an EPG
|
||||
mso_schema_template_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
subnet: 10.0.0.0/24
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific EPG subnet
|
||||
mso_schema_template_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
epg: EPG 1
|
||||
subnet: 10.0.0.0/24
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all EPGs subnets
|
||||
mso_schema_template_anp_epg_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
anp: ANP 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_subnet_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
anp=dict(type='str', required=True),
|
||||
epg=dict(type='str', required=True),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
argument_spec.update(mso_subnet_spec())
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['subnet']],
|
||||
['state', 'present', ['subnet']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
anp = module.params.get('anp')
|
||||
epg = module.params.get('epg')
|
||||
subnet = module.params.get('subnet')
|
||||
description = module.params.get('description')
|
||||
scope = module.params.get('scope')
|
||||
shared = module.params.get('shared')
|
||||
no_default_gateway = module.params.get('no_default_gateway')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
|
||||
templates=', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get ANP
|
||||
anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
|
||||
if anp not in anps:
|
||||
mso.fail_json(msg="Provided anp '{anp}' does not exist. Existing anps: {anps}".format(anp=anp, anps=', '.join(anps)))
|
||||
anp_idx = anps.index(anp)
|
||||
|
||||
# Get EPG
|
||||
epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']]
|
||||
if epg not in epgs:
|
||||
mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=', '.join(epgs)))
|
||||
epg_idx = epgs.index(epg)
|
||||
|
||||
# Get Subnet
|
||||
subnets = [s.get('ip') for s in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']]
|
||||
if subnet in subnets:
|
||||
subnet_idx = subnets.index(subnet)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
subnet_path = '/templates/{0}/anps/{1}/epgs/{2}/subnets/{3}'.format(template, anp, epg, subnet_idx)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets'][subnet_idx]
|
||||
|
||||
if state == 'query':
|
||||
if subnet is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
|
||||
mso.exit_json()
|
||||
|
||||
subnets_path = '/templates/{0}/anps/{1}/epgs/{2}/subnets'.format(template, anp, epg)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.existing = {}
|
||||
ops.append(dict(op='remove', path=subnet_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
if description is None:
|
||||
description = subnet
|
||||
if scope is None:
|
||||
scope = 'private'
|
||||
if shared is None:
|
||||
shared = False
|
||||
if no_default_gateway is None:
|
||||
no_default_gateway = False
|
||||
|
||||
payload = dict(
|
||||
ip=subnet,
|
||||
description=description,
|
||||
scope=scope,
|
||||
shared=shared,
|
||||
noDefaultGateway=no_default_gateway,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,295 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_bd
|
||||
short_description: Manage Bridge Domains (BDs) in schema templates
|
||||
description:
|
||||
- Manage BDs in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
bd:
|
||||
description:
|
||||
- The name of the BD to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
vrf:
|
||||
description:
|
||||
- The VRF associated to this BD. This is required only when creating a new BD.
|
||||
type: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the VRF to associate with.
|
||||
required: true
|
||||
type: str
|
||||
schema:
|
||||
description:
|
||||
- The schema that defines the referenced VRF.
|
||||
- If this parameter is unspecified, it defaults to the current schema.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- The template that defines the referenced VRF.
|
||||
- If this parameter is unspecified, it defaults to the current template.
|
||||
type: str
|
||||
subnets:
|
||||
description:
|
||||
- The subnets associated to this BD.
|
||||
type: list
|
||||
suboptions:
|
||||
subnet:
|
||||
description:
|
||||
- The IP range in CIDR notation.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ ip ]
|
||||
description:
|
||||
description:
|
||||
- The description of this subnet.
|
||||
type: str
|
||||
scope:
|
||||
description:
|
||||
- The scope of the subnet.
|
||||
type: str
|
||||
choices: [ private, public ]
|
||||
shared:
|
||||
description:
|
||||
- Whether this subnet is shared between VRFs.
|
||||
type: bool
|
||||
no_default_gateway:
|
||||
description:
|
||||
- Whether this subnet has a default gateway.
|
||||
type: bool
|
||||
intersite_bum_traffic:
|
||||
description:
|
||||
- Whether to allow intersite BUM traffic.
|
||||
type: bool
|
||||
optimize_wan_bandwidth:
|
||||
description:
|
||||
- Whether to optimize WAN bandwidth.
|
||||
type: bool
|
||||
layer2_stretch:
|
||||
description:
|
||||
- Whether to enable L2 stretch.
|
||||
type: bool
|
||||
layer2_unknown_unicast:
|
||||
description:
|
||||
- Layer2 unknown unicast.
|
||||
type: str
|
||||
choices: [ flood, proxy ]
|
||||
layer3_multicast:
|
||||
description:
|
||||
- Whether to enable L3 multicast.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new BD
|
||||
mso_schema_template_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
bd: BD 1
|
||||
vrf:
|
||||
name: VRF1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove an BD
|
||||
mso_schema_template_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
bd: BD1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific BDs
|
||||
mso_schema_template_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
bd: BD1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all BDs
|
||||
mso_schema_template_bd:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_subnet_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
bd=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
display_name=dict(type='str'),
|
||||
intersite_bum_traffic=dict(type='bool'),
|
||||
optimize_wan_bandwidth=dict(type='bool'),
|
||||
layer2_stretch=dict(type='bool'),
|
||||
layer2_unknown_unicast=dict(type='str', choices=['flood', 'proxy']),
|
||||
layer3_multicast=dict(type='bool'),
|
||||
vrf=dict(type='dict', options=mso_reference_spec()),
|
||||
subnets=dict(type='list', options=mso_subnet_spec()),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['bd']],
|
||||
['state', 'present', ['bd', 'vrf']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
bd = module.params.get('bd')
|
||||
display_name = module.params.get('display_name')
|
||||
intersite_bum_traffic = module.params.get('intersite_bum_traffic')
|
||||
optimize_wan_bandwidth = module.params.get('optimize_wan_bandwidth')
|
||||
layer2_stretch = module.params.get('layer2_stretch')
|
||||
layer2_unknown_unicast = module.params.get('layer2_unknown_unicast')
|
||||
layer3_multicast = module.params.get('layer3_multicast')
|
||||
vrf = module.params.get('vrf')
|
||||
subnets = module.params.get('subnets')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if schema_obj:
|
||||
schema_id = schema_obj.get('id')
|
||||
else:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get ANP
|
||||
bds = [b.get('name') for b in schema_obj.get('templates')[template_idx]['bds']]
|
||||
|
||||
if bd is not None and bd in bds:
|
||||
bd_idx = bds.index(bd)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['bds'][bd_idx]
|
||||
|
||||
if state == 'query':
|
||||
if bd is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['bds']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="BD '{bd}' not found".format(bd=bd))
|
||||
mso.exit_json()
|
||||
|
||||
bds_path = '/templates/{0}/bds'.format(template)
|
||||
bd_path = '/templates/{0}/bds/{1}'.format(template, bd)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=bd_path))
|
||||
|
||||
elif state == 'present':
|
||||
vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
|
||||
subnets = mso.make_subnets(subnets)
|
||||
|
||||
if display_name is None and not mso.existing:
|
||||
display_name = bd
|
||||
if subnets is None and not mso.existing:
|
||||
subnets = []
|
||||
|
||||
payload = dict(
|
||||
name=bd,
|
||||
displayName=display_name,
|
||||
intersiteBumTrafficAllow=intersite_bum_traffic,
|
||||
optimizeWanBandwidth=optimize_wan_bandwidth,
|
||||
l2UnknownUnicast=layer2_unknown_unicast,
|
||||
l2Stretch=layer2_stretch,
|
||||
l3MCast=layer3_multicast,
|
||||
subnets=subnets,
|
||||
vrfRef=vrf_ref,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=bd_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=bds_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,241 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_bd_subnet
|
||||
short_description: Manage BD subnets in schema templates
|
||||
description:
|
||||
- Manage BD subnets in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template to change.
|
||||
type: str
|
||||
required: yes
|
||||
bd:
|
||||
description:
|
||||
- The name of the BD to manage.
|
||||
type: str
|
||||
required: yes
|
||||
subnet:
|
||||
description:
|
||||
- The IP range in CIDR notation.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ ip ]
|
||||
description:
|
||||
description:
|
||||
- The description of this subnet.
|
||||
type: str
|
||||
scope:
|
||||
description:
|
||||
- The scope of the subnet.
|
||||
type: str
|
||||
choices: [ private, public ]
|
||||
shared:
|
||||
description:
|
||||
- Whether this subnet is shared between VRFs.
|
||||
type: bool
|
||||
no_default_gateway:
|
||||
description:
|
||||
- Whether this subnet has a default gateway.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- Due to restrictions of the MSO REST API concurrent modifications to BD subnets can be dangerous and corrupt data.
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new subnet to a BD
|
||||
mso_schema_template_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
bd: BD 1
|
||||
subnet: 10.0.0.0/24
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a subset from a BD
|
||||
mso_schema_template_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
bd: BD 1
|
||||
subnet: 10.0.0.0/24
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific BD subnet
|
||||
mso_schema_template_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
bd: BD 1
|
||||
subnet: 10.0.0.0/24
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all BD subnets
|
||||
mso_schema_template_bd_subnet:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
bd: BD 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_subnet_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
bd=dict(type='str', required=True),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
argument_spec.update(mso_subnet_spec())
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['subnet']],
|
||||
['state', 'present', ['subnet']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
bd = module.params.get('bd')
|
||||
subnet = module.params.get('subnet')
|
||||
description = module.params.get('description')
|
||||
scope = module.params.get('scope')
|
||||
shared = module.params.get('shared')
|
||||
no_default_gateway = module.params.get('no_default_gateway')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get BD
|
||||
bds = [b.get('name') for b in schema_obj.get('templates')[template_idx]['bds']]
|
||||
if bd not in bds:
|
||||
mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ', '.join(bds)))
|
||||
bd_idx = bds.index(bd)
|
||||
|
||||
# Get Subnet
|
||||
subnets = [s.get('ip') for s in schema_obj.get('templates')[template_idx]['bds'][bd_idx]['subnets']]
|
||||
if subnet in subnets:
|
||||
subnet_idx = subnets.index(subnet)
|
||||
# FIXME: Changes based on index are DANGEROUS
|
||||
subnet_path = '/templates/{0}/bds/{1}/subnets/{2}'.format(template, bd, subnet_idx)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['bds'][bd_idx]['subnets'][subnet_idx]
|
||||
|
||||
if state == 'query':
|
||||
if subnet is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['bds'][bd_idx]['subnets']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
|
||||
mso.exit_json()
|
||||
|
||||
subnets_path = '/templates/{0}/bds/{1}/subnets'.format(template, bd)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=subnet_path))
|
||||
|
||||
elif state == 'present':
|
||||
if not mso.existing:
|
||||
if description is None:
|
||||
description = subnet
|
||||
if scope is None:
|
||||
scope = 'private'
|
||||
if shared is None:
|
||||
shared = False
|
||||
if no_default_gateway is None:
|
||||
no_default_gateway = False
|
||||
|
||||
payload = dict(
|
||||
ip=subnet,
|
||||
description=description,
|
||||
scope=scope,
|
||||
shared=shared,
|
||||
noDefaultGateway=no_default_gateway,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,340 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_contract_filter
|
||||
short_description: Manage contract filters in schema templates
|
||||
description:
|
||||
- Manage contract filters in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
contract:
|
||||
description:
|
||||
- The name of the contract to manage.
|
||||
type: str
|
||||
required: yes
|
||||
contract_display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
- This defaults to the contract name when unset on creation.
|
||||
type: str
|
||||
contract_filter_type:
|
||||
description:
|
||||
- The type of filters defined in this contract.
|
||||
- This defaults to C(both-way) when unset on creation.
|
||||
type: str
|
||||
choices: [ both-way, one-way ]
|
||||
contract_scope:
|
||||
description:
|
||||
- The scope of the contract.
|
||||
- This defaults to C(vrf) when unset on creation.
|
||||
type: str
|
||||
choices: [ application-profile, global, tenant, vrf ]
|
||||
filter:
|
||||
description:
|
||||
- The filter to associate with this contract.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
filter_template:
|
||||
description:
|
||||
- The template name in which the filter is located.
|
||||
type: str
|
||||
filter_schema:
|
||||
description:
|
||||
- The schema name in which the filter is located.
|
||||
type: str
|
||||
filter_type:
|
||||
description:
|
||||
- The type of filter to manage.
|
||||
type: str
|
||||
choices: [ both-way, consumer-to-provider, provider-to-consumer ]
|
||||
default: both-way
|
||||
aliases: [ type ]
|
||||
filter_directives:
|
||||
description:
|
||||
- A list of filter directives.
|
||||
type: list
|
||||
choices: [ log, none ]
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_template_filter_entry
|
||||
notes:
|
||||
- Due to restrictions of the MSO REST API this module creates contracts when needed, and removes them when the last filter has been removed.
|
||||
- Due to restrictions of the MSO REST API concurrent modifications to contract filters can be dangerous and corrupt data.
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new contract filter
|
||||
mso_schema_template_contract_filter:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
contract: Contract 1
|
||||
contract_scope: global
|
||||
filter: Filter 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a contract filter
|
||||
mso_schema_template_contract_filter:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
contract: Contract 1
|
||||
filter: Filter 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific contract filter
|
||||
mso_schema_template_contract_filter:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
contract: Contract 1
|
||||
filter: Filter 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all contract filters
|
||||
mso_schema_template_contract_filter:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
contract: Contract 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, issubset
|
||||
|
||||
FILTER_KEYS = {
|
||||
'both-way': 'filterRelationships',
|
||||
'consumer-to-provider': 'filterRelationshipsConsumerToProvider',
|
||||
'provider-to-consumer': 'filterRelationshipsProviderToConsumer',
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
contract=dict(type='str', required=True),
|
||||
contract_display_name=dict(type='str'),
|
||||
contract_scope=dict(type='str', choices=['application-profile', 'global', 'tenant', 'vrf']),
|
||||
contract_filter_type=dict(type='str', choices=['both-way', 'one-way']),
|
||||
filter=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
filter_directives=dict(type='list', choices=['log', 'none']),
|
||||
filter_template=dict(type='str'),
|
||||
filter_schema=dict(type='str'),
|
||||
filter_type=dict(type='str', default='both-way', choices=FILTER_KEYS.keys(), aliases=['type']),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['filter']],
|
||||
['state', 'present', ['filter']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
contract = module.params.get('contract')
|
||||
contract_display_name = module.params.get('contract_display_name')
|
||||
contract_filter_type = module.params.get('contract_filter_type')
|
||||
contract_scope = module.params.get('contract_scope')
|
||||
filter_name = module.params.get('filter')
|
||||
filter_directives = module.params.get('filter_directives')
|
||||
filter_template = module.params.get('filter_template')
|
||||
filter_schema = module.params.get('filter_schema')
|
||||
filter_type = module.params.get('filter_type')
|
||||
state = module.params.get('state')
|
||||
|
||||
contract_ftype = 'bothWay' if contract_filter_type == 'both-way' else 'oneWay'
|
||||
|
||||
if contract_filter_type == 'both-way' and filter_type != 'both-way':
|
||||
module.warn("You are adding 'one-way' filters to a 'both-way' contract")
|
||||
elif contract_filter_type != 'both-way' and filter_type == 'both-way':
|
||||
module.warn("You are adding 'both-way' filters to a 'one-way' contract")
|
||||
|
||||
if filter_template is None:
|
||||
filter_template = template
|
||||
|
||||
if filter_schema is None:
|
||||
filter_schema = schema
|
||||
|
||||
filter_key = FILTER_KEYS[filter_type]
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
filter_schema_id = mso.lookup_schema(filter_schema)
|
||||
|
||||
# Get schema object
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if schema_obj:
|
||||
schema_id = schema_obj.get('id')
|
||||
else:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get contracts
|
||||
mso.existing = {}
|
||||
contract_idx = None
|
||||
filter_idx = None
|
||||
contracts = [c.get('name') for c in schema_obj.get('templates')[template_idx]['contracts']]
|
||||
|
||||
if contract in contracts:
|
||||
contract_idx = contracts.index(contract)
|
||||
|
||||
filters = [f.get('filterRef') for f in schema_obj.get('templates')[template_idx]['contracts'][contract_idx][filter_key]]
|
||||
filter_ref = mso.filter_ref(schema_id=filter_schema_id, template=filter_template, filter=filter_name)
|
||||
if filter_ref in filters:
|
||||
filter_idx = filters.index(filter_ref)
|
||||
filter_path = '/templates/{0}/contracts/{1}/{2}/{3}'.format(template, contract, filter_key, filter_name)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['contracts'][contract_idx][filter_key][filter_idx]
|
||||
|
||||
if state == 'query':
|
||||
if contract_idx is None:
|
||||
mso.fail_json(msg="Provided contract '{0}' does not exist. Existing contracts: {1}".format(contract, ', '.join(contracts)))
|
||||
|
||||
if filter_name is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['contracts'][contract_idx][filter_key]
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="FilterRef '{filter_ref}' not found".format(filter_ref=filter_ref))
|
||||
mso.exit_json()
|
||||
|
||||
ops = []
|
||||
contract_path = '/templates/{0}/contracts/{1}'.format(template, contract)
|
||||
filters_path = '/templates/{0}/contracts/{1}/{2}'.format(template, contract, filter_key)
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
mso.proposed = mso.sent = {}
|
||||
|
||||
if contract_idx is None:
|
||||
# There was no contract to begin with
|
||||
pass
|
||||
elif filter_idx is None:
|
||||
# There was no filter to begin with
|
||||
pass
|
||||
elif len(filters) == 1:
|
||||
# There is only one filter, remove contract
|
||||
mso.existing = {}
|
||||
ops.append(dict(op='remove', path=contract_path))
|
||||
else:
|
||||
# Remove filter
|
||||
mso.existing = {}
|
||||
ops.append(dict(op='remove', path=filter_path))
|
||||
|
||||
elif state == 'present':
|
||||
if filter_directives is None:
|
||||
filter_directives = ['none']
|
||||
|
||||
payload = dict(
|
||||
filterRef=dict(
|
||||
filterName=filter_name,
|
||||
templateName=filter_template,
|
||||
schemaId=filter_schema_id,
|
||||
),
|
||||
directives=filter_directives,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
mso.existing = mso.sent
|
||||
|
||||
if contract_idx is None:
|
||||
# Contract does not exist, so we have to create it
|
||||
if contract_display_name is None:
|
||||
contract_display_name = contract
|
||||
if contract_filter_type is None:
|
||||
contract_ftype = 'bothWay'
|
||||
if contract_scope is None:
|
||||
contract_scope = 'context'
|
||||
|
||||
payload = {
|
||||
'name': contract,
|
||||
'displayName': contract_display_name,
|
||||
'filterType': contract_ftype,
|
||||
'scope': contract_scope,
|
||||
}
|
||||
|
||||
ops.append(dict(op='add', path='/templates/{0}/contracts/-'.format(template), value=payload))
|
||||
else:
|
||||
# Contract exists, but may require an update
|
||||
if contract_display_name is not None:
|
||||
ops.append(dict(op='replace', path=contract_path + '/displayName', value=contract_display_name))
|
||||
if contract_filter_type is not None:
|
||||
ops.append(dict(op='replace', path=contract_path + '/filterType', value=contract_ftype))
|
||||
if contract_scope is not None:
|
||||
ops.append(dict(op='replace', path=contract_path + '/scope', value=contract_scope))
|
||||
|
||||
if filter_idx is None:
|
||||
# Filter does not exist, so we have to add it
|
||||
ops.append(dict(op='add', path=filters_path + '/-', value=mso.sent))
|
||||
|
||||
else:
|
||||
# Filter exists, we have to update it
|
||||
ops.append(dict(op='replace', path=filter_path, value=mso.sent))
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,142 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_deploy
|
||||
short_description: Deploy schema templates to sites
|
||||
description:
|
||||
- Deploy schema templates to sites.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
site:
|
||||
description:
|
||||
- The name of the site B(to undeploy).
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Use C(deploy) to deploy schema template.
|
||||
- Use C(status) to get deployment status.
|
||||
- Use C(undeploy) to deploy schema template from a site.
|
||||
type: str
|
||||
choices: [ deploy, status, undeploy ]
|
||||
default: deploy
|
||||
seealso:
|
||||
- module: mso_schema_site
|
||||
- module: mso_schema_template
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Deploy a schema template
|
||||
mso_schema_template_deploy:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: deploy
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Undeploy a schema template
|
||||
mso_schema_template_deploy:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
site: Site 1
|
||||
state: undeploy
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Get deployment status
|
||||
mso_schema:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: status
|
||||
delegate_to: localhost
|
||||
register: status_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True, aliases=['name']),
|
||||
site=dict(type='str'),
|
||||
state=dict(type='str', default='deploy', choices=['deploy', 'status', 'undeploy']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'undeploy', ['site']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
site = module.params.get('site')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema
|
||||
schema_id = mso.lookup_schema(schema)
|
||||
|
||||
payload = dict(
|
||||
schemaId=schema_id,
|
||||
templateName=template,
|
||||
)
|
||||
|
||||
qs = None
|
||||
if state == 'deploy':
|
||||
path = 'execute/schema/{0}/template/{1}'.format(schema_id, template)
|
||||
elif state == 'status':
|
||||
path = 'status/schema/{0}/template/{1}'.format(schema_id, template)
|
||||
elif state == 'undeploy':
|
||||
path = 'execute/schema/{0}/template/{1}'.format(schema_id, template)
|
||||
site_id = mso.lookup_site(site)
|
||||
qs = dict(undeploy=site_id)
|
||||
|
||||
if not module.check_mode:
|
||||
status = mso.request(path, method='GET', data=payload, qs=qs)
|
||||
|
||||
mso.exit_json(**status)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,226 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_externalepg
|
||||
short_description: Manage external EPGs in schema templates
|
||||
description:
|
||||
- Manage external EPGs in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
externalepg:
|
||||
description:
|
||||
- The name of the external EPG to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
vrf:
|
||||
description:
|
||||
- The VRF associated to this ANP.
|
||||
type: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the VRF to associate with.
|
||||
required: true
|
||||
type: str
|
||||
schema:
|
||||
description:
|
||||
- The schema that defines the referenced VRF.
|
||||
- If this parameter is unspecified, it defaults to the current schema.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- The template that defines the referenced VRF.
|
||||
- If this parameter is unspecified, it defaults to the current template.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new external EPG
|
||||
mso_schema_template_externalepg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
externalepg: External EPG 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove an external EPG
|
||||
mso_schema_template_externalepg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
externalepg: external EPG1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific external EPGs
|
||||
mso_schema_template_externalepg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
externalepg: external EPG1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all external EPGs
|
||||
mso_schema_template_externalepg:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
externalepg=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
display_name=dict(type='str'),
|
||||
vrf=dict(type='dict', options=mso_reference_spec()),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['externalepg']],
|
||||
['state', 'present', ['externalepg', 'vrf']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
externalepg = module.params.get('externalepg')
|
||||
display_name = module.params.get('display_name')
|
||||
vrf = module.params.get('vrf')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if schema_obj:
|
||||
schema_id = schema_obj.get('id')
|
||||
else:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get external EPGs
|
||||
externalepgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['externalEpgs']]
|
||||
|
||||
if externalepg is not None and externalepg in externalepgs:
|
||||
externalepg_idx = externalepgs.index(externalepg)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][externalepg_idx]
|
||||
|
||||
if state == 'query':
|
||||
if externalepg is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="External EPG '{externalepg}' not found".format(externalepg=externalepg))
|
||||
mso.exit_json()
|
||||
|
||||
eepgs_path = '/templates/{0}/externalEpgs'.format(template)
|
||||
eepg_path = '/templates/{0}/externalEpgs/{1}'.format(template, externalepg)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=eepg_path))
|
||||
|
||||
elif state == 'present':
|
||||
vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
|
||||
|
||||
if display_name is None and not mso.existing:
|
||||
display_name = externalepg
|
||||
|
||||
payload = dict(
|
||||
name=externalepg,
|
||||
displayName=display_name,
|
||||
vrfRef=vrf_ref,
|
||||
# FIXME
|
||||
subnets=[],
|
||||
contractRelationships=[],
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=eepg_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=eepgs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,363 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_filter_entry
|
||||
short_description: Manage filter entries in schema templates
|
||||
description:
|
||||
- Manage filter entries in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
filter:
|
||||
description:
|
||||
- The name of the filter to manage.
|
||||
type: str
|
||||
required: yes
|
||||
filter_display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
entry:
|
||||
description:
|
||||
- The filter entry name to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
aliases: [ entry_display_name ]
|
||||
description:
|
||||
description:
|
||||
- The description of this filer entry.
|
||||
type: str
|
||||
aliases: [ entry_description ]
|
||||
ethertype:
|
||||
description:
|
||||
- The ethernet type to use for this filter entry.
|
||||
type: str
|
||||
choices: [ arp, fcoe, ip, ipv4, ipv6, mac-security, mpls-unicast, trill, unspecified ]
|
||||
ip_protocol:
|
||||
description:
|
||||
- The IP protocol to use for this filter entry.
|
||||
type: str
|
||||
choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, unspecified ]
|
||||
tcp_session_rules:
|
||||
description:
|
||||
- A list of TCP session rules.
|
||||
type: list
|
||||
choices: [ acknowledgement, established, finish, synchronize, reset, unspecified ]
|
||||
source_from:
|
||||
description:
|
||||
- The source port range from.
|
||||
type: str
|
||||
source_to:
|
||||
description:
|
||||
- The source port range to.
|
||||
type: str
|
||||
destination_from:
|
||||
description:
|
||||
- The destination port range from.
|
||||
type: str
|
||||
destination_to:
|
||||
description:
|
||||
- The destination port range to.
|
||||
type: str
|
||||
arp_flag:
|
||||
description:
|
||||
- The ARP flag to use for this filter entry.
|
||||
type: str
|
||||
choices: [ reply, request, unspecified ]
|
||||
stateful:
|
||||
description:
|
||||
- Whether this filter entry is stateful.
|
||||
type: bool
|
||||
default: no
|
||||
fragments_only:
|
||||
description:
|
||||
- Whether this filter entry only matches fragments.
|
||||
type: bool
|
||||
default: no
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
seealso:
|
||||
- module: mso_schema_template_contract_filter
|
||||
notes:
|
||||
- Due to restrictions of the MSO REST API this module creates filters when needed, and removes them when the last entry has been removed.
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new filter entry
|
||||
mso_schema_template_filter_entry:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
filter: Filter 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a filter entry
|
||||
mso_schema_template_filter_entry:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
filter: Filter 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific filter entry
|
||||
mso_schema_template_filter_entry:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
filter: Filter 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all filter entries
|
||||
mso_schema_template_filter_entry:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
filter=dict(type='str', required=True),
|
||||
filter_display_name=dict(type='str'),
|
||||
entry=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
description=dict(type='str', aliases=['entry_description']),
|
||||
display_name=dict(type='str', aliases=['entry_display_name']),
|
||||
ethertype=dict(type='str', choices=['arp', 'fcoe', 'ip', 'ipv4', 'ipv6', 'mac-security', 'mpls-unicast', 'trill', 'unspecified']),
|
||||
ip_protocol=dict(type='str', choices=['eigrp', 'egp', 'icmp', 'icmpv6', 'igmp', 'igp', 'l2tp', 'ospfigp', 'pim', 'tcp', 'udp', 'unspecified']),
|
||||
tcp_session_rules=dict(type='list', choices=['acknowledgement', 'established', 'finish', 'synchronize', 'reset', 'unspecified']),
|
||||
source_from=dict(type='str'),
|
||||
source_to=dict(type='str'),
|
||||
destination_from=dict(type='str'),
|
||||
destination_to=dict(type='str'),
|
||||
arp_flag=dict(type='str', choices=['reply', 'request', 'unspecified']),
|
||||
stateful=dict(type='bool'),
|
||||
fragments_only=dict(type='bool'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['entry']],
|
||||
['state', 'present', ['entry']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
filter_name = module.params.get('filter')
|
||||
filter_display_name = module.params.get('filter_display_name')
|
||||
entry = module.params.get('entry')
|
||||
display_name = module.params.get('display_name')
|
||||
description = module.params.get('description')
|
||||
ethertype = module.params.get('ethertype')
|
||||
ip_protocol = module.params.get('ip_protocol')
|
||||
tcp_session_rules = module.params.get('tcp_session_rules')
|
||||
source_from = module.params.get('source_from')
|
||||
source_to = module.params.get('source_to')
|
||||
destination_from = module.params.get('destination_from')
|
||||
destination_to = module.params.get('destination_to')
|
||||
arp_flag = module.params.get('arp_flag')
|
||||
stateful = module.params.get('stateful')
|
||||
fragments_only = module.params.get('fragments_only')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
|
||||
templates=', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get filters
|
||||
mso.existing = {}
|
||||
filter_idx = None
|
||||
entry_idx = None
|
||||
filters = [f.get('name') for f in schema_obj.get('templates')[template_idx]['filters']]
|
||||
if filter_name in filters:
|
||||
filter_idx = filters.index(filter_name)
|
||||
|
||||
entries = [f.get('name') for f in schema_obj.get('templates')[template_idx]['filters'][filter_idx]['entries']]
|
||||
if entry in entries:
|
||||
entry_idx = entries.index(entry)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['filters'][filter_idx]['entries'][entry_idx]
|
||||
|
||||
if state == 'query':
|
||||
if entry is None:
|
||||
if filter_idx is None:
|
||||
mso.fail_json(msg="Filter '{filter}' not found".format(filter=filter_name))
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['filters'][filter_idx]['entries']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="Entry '{entry}' not found".format(entry=entry))
|
||||
mso.exit_json()
|
||||
|
||||
filters_path = '/templates/{0}/filters'.format(template)
|
||||
filter_path = '/templates/{0}/filters/{1}'.format(template, filter_name)
|
||||
entries_path = '/templates/{0}/filters/{1}/entries'.format(template, filter_name)
|
||||
entry_path = '/templates/{0}/filters/{1}/entries/{2}'.format(template, filter_name, entry)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
mso.proposed = mso.sent = {}
|
||||
|
||||
if filter_idx is None:
|
||||
# There was no filter to begin with
|
||||
pass
|
||||
elif entry_idx is None:
|
||||
# There was no entry to begin with
|
||||
pass
|
||||
elif len(entries) == 1:
|
||||
# There is only one entry, remove filter
|
||||
mso.existing = {}
|
||||
ops.append(dict(op='remove', path=filter_path))
|
||||
|
||||
else:
|
||||
mso.existing = {}
|
||||
ops.append(dict(op='remove', path=entry_path))
|
||||
|
||||
elif state == 'present':
|
||||
|
||||
if not mso.existing:
|
||||
if display_name is None:
|
||||
display_name = entry
|
||||
if description is None:
|
||||
description = ''
|
||||
if ethertype is None:
|
||||
ethertype = 'unspecified'
|
||||
if ip_protocol is None:
|
||||
ip_protocol = 'unspecified'
|
||||
if tcp_session_rules is None:
|
||||
tcp_session_rules = ['unspecified']
|
||||
if source_from is None:
|
||||
source_from = 'unspecified'
|
||||
if source_to is None:
|
||||
source_to = 'unspecified'
|
||||
if destination_from is None:
|
||||
destination_from = 'unspecified'
|
||||
if destination_to is None:
|
||||
destination_to = 'unspecified'
|
||||
if arp_flag is None:
|
||||
arp_flag = 'unspecified'
|
||||
if stateful is None:
|
||||
stateful = False
|
||||
if fragments_only is None:
|
||||
fragments_only = False
|
||||
|
||||
payload = dict(
|
||||
name=entry,
|
||||
displayName=display_name,
|
||||
description=description,
|
||||
etherType=ethertype,
|
||||
ipProtocol=ip_protocol,
|
||||
tcpSessionRules=tcp_session_rules,
|
||||
sourceFrom=source_from,
|
||||
sourceTo=source_to,
|
||||
destinationFrom=destination_from,
|
||||
destinationTo=destination_to,
|
||||
arpFlag=arp_flag,
|
||||
stateful=stateful,
|
||||
matchOnlyFragments=fragments_only,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if filter_idx is None:
|
||||
# Filter does not exist, so we have to create it
|
||||
if filter_display_name is None:
|
||||
filter_display_name = filter_name
|
||||
|
||||
payload = dict(
|
||||
name=filter_name,
|
||||
displayName=filter_display_name,
|
||||
entries=[mso.sent],
|
||||
)
|
||||
|
||||
ops.append(dict(op='add', path=filters_path + '/-', value=payload))
|
||||
|
||||
elif entry_idx is None:
|
||||
# Entry does not exist, so we have to add it
|
||||
ops.append(dict(op='add', path=entries_path + '/-', value=mso.sent))
|
||||
|
||||
else:
|
||||
# Entry exists, we have to update it
|
||||
for (key, value) in mso.sent.items():
|
||||
ops.append(dict(op='replace', path=entry_path + '/' + key, value=value))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,223 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_l3out
|
||||
short_description: Manage l3outs in schema templates
|
||||
description:
|
||||
- Manage l3outs in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
l3out:
|
||||
description:
|
||||
- The name of the l3out to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
vrf:
|
||||
description:
|
||||
- The VRF associated to this L3out.
|
||||
type: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the VRF to associate with.
|
||||
required: true
|
||||
type: str
|
||||
schema:
|
||||
description:
|
||||
- The schema that defines the referenced VRF.
|
||||
- If this parameter is unspecified, it defaults to the current schema.
|
||||
type: str
|
||||
template:
|
||||
description:
|
||||
- The template that defines the referenced VRF.
|
||||
- If this parameter is unspecified, it defaults to the current schema.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new L3out
|
||||
mso_schema_template_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
l3out: L3out 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove an L3out
|
||||
mso_schema_template_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
l3out: L3out 1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific L3outs
|
||||
mso_schema_template_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
l3out: L3out 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all L3outs
|
||||
mso_schema_template_l3out:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
l3out=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
display_name=dict(type='str'),
|
||||
vrf=dict(type='dict', options=mso_reference_spec()),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['l3out']],
|
||||
['state', 'present', ['l3out', 'vrf']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
l3out = module.params.get('l3out')
|
||||
display_name = module.params.get('display_name')
|
||||
vrf = module.params.get('vrf')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if schema_obj:
|
||||
schema_id = schema_obj.get('id')
|
||||
else:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get L3out
|
||||
l3outs = [l.get('name') for l in schema_obj.get('templates')[template_idx]['intersiteL3outs']]
|
||||
|
||||
if l3out is not None and l3out in l3outs:
|
||||
l3out_idx = l3outs.index(l3out)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['intersiteL3outs'][l3out_idx]
|
||||
|
||||
if state == 'query':
|
||||
if l3out is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['intersiteL3outs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="L3out '{l3out}' not found".format(l3out=l3out))
|
||||
mso.exit_json()
|
||||
|
||||
l3outs_path = '/templates/{0}/intersiteL3outs'.format(template)
|
||||
l3out_path = '/templates/{0}/intersiteL3outs/{1}'.format(template, l3out)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=l3out_path))
|
||||
|
||||
elif state == 'present':
|
||||
vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
|
||||
|
||||
if display_name is None and not mso.existing:
|
||||
display_name = l3out
|
||||
|
||||
payload = dict(
|
||||
name=l3out,
|
||||
displayName=display_name,
|
||||
vrfRef=vrf_ref,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=l3out_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=l3outs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,205 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_schema_template_vrf
|
||||
short_description: Manage VRFs in schema templates
|
||||
description:
|
||||
- Manage VRFs in schema templates on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
schema:
|
||||
description:
|
||||
- The name of the schema.
|
||||
type: str
|
||||
required: yes
|
||||
template:
|
||||
description:
|
||||
- The name of the template.
|
||||
type: str
|
||||
required: yes
|
||||
vrf:
|
||||
description:
|
||||
- The name of the VRF to manage.
|
||||
type: str
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name as displayed on the MSO web interface.
|
||||
type: str
|
||||
layer3_multicast:
|
||||
description:
|
||||
- Whether to enable L3 multicast.
|
||||
type: bool
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new VRF
|
||||
mso_schema_template_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
vrf: VRF 1
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove an VRF
|
||||
mso_schema_template_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
vrf: VRF1
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a specific VRFs
|
||||
mso_schema_template_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
vrf: VRF1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all VRFs
|
||||
mso_schema_template_vrf:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
schema: Schema 1
|
||||
template: Template 1
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
schema=dict(type='str', required=True),
|
||||
template=dict(type='str', required=True),
|
||||
vrf=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
|
||||
display_name=dict(type='str'),
|
||||
layer3_multicast=dict(type='bool'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['vrf']],
|
||||
['state', 'present', ['vrf']],
|
||||
],
|
||||
)
|
||||
|
||||
schema = module.params.get('schema')
|
||||
template = module.params.get('template')
|
||||
vrf = module.params.get('vrf')
|
||||
display_name = module.params.get('display_name')
|
||||
layer3_multicast = module.params.get('layer3_multicast')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Get schema_id
|
||||
schema_obj = mso.get_obj('schemas', displayName=schema)
|
||||
if not schema_obj:
|
||||
mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
|
||||
|
||||
schema_path = 'schemas/{id}'.format(**schema_obj)
|
||||
|
||||
# Get template
|
||||
templates = [t.get('name') for t in schema_obj.get('templates')]
|
||||
if template not in templates:
|
||||
mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
|
||||
template_idx = templates.index(template)
|
||||
|
||||
# Get ANP
|
||||
vrfs = [v.get('name') for v in schema_obj.get('templates')[template_idx]['vrfs']]
|
||||
|
||||
if vrf is not None and vrf in vrfs:
|
||||
vrf_idx = vrfs.index(vrf)
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]
|
||||
|
||||
if state == 'query':
|
||||
if vrf is None:
|
||||
mso.existing = schema_obj.get('templates')[template_idx]['vrfs']
|
||||
elif not mso.existing:
|
||||
mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf))
|
||||
mso.exit_json()
|
||||
|
||||
vrfs_path = '/templates/{0}/vrfs'.format(template)
|
||||
vrf_path = '/templates/{0}/vrfs/{1}'.format(template, vrf)
|
||||
ops = []
|
||||
|
||||
mso.previous = mso.existing
|
||||
if state == 'absent':
|
||||
if mso.existing:
|
||||
mso.sent = mso.existing = {}
|
||||
ops.append(dict(op='remove', path=vrf_path))
|
||||
|
||||
elif state == 'present':
|
||||
if display_name is None and not mso.existing:
|
||||
display_name = vrf
|
||||
|
||||
payload = dict(
|
||||
name=vrf,
|
||||
displayName=display_name,
|
||||
l3MCast=layer3_multicast,
|
||||
# FIXME
|
||||
regions=[],
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
ops.append(dict(op='replace', path=vrf_path, value=mso.sent))
|
||||
else:
|
||||
ops.append(dict(op='add', path=vrfs_path + '/-', value=mso.sent))
|
||||
|
||||
mso.existing = mso.proposed
|
||||
|
||||
if not module.check_mode:
|
||||
mso.request(schema_path, method='PATCH', data=ops)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,246 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_site
|
||||
short_description: Manage sites
|
||||
description:
|
||||
- Manage sites on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
apic_password:
|
||||
description:
|
||||
- The password for the APICs.
|
||||
type: str
|
||||
required: yes
|
||||
apic_site_id:
|
||||
description:
|
||||
- The site ID of the APICs.
|
||||
type: str
|
||||
required: yes
|
||||
apic_username:
|
||||
description:
|
||||
- The username for the APICs.
|
||||
type: str
|
||||
required: yes
|
||||
default: admin
|
||||
site:
|
||||
description:
|
||||
- The name of the site.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
labels:
|
||||
description:
|
||||
- The labels for this site.
|
||||
- Labels that do not already exist will be automatically created.
|
||||
type: list
|
||||
location:
|
||||
description:
|
||||
- Location of the site.
|
||||
type: dict
|
||||
suboptions:
|
||||
latitude:
|
||||
description:
|
||||
- The latitude of the location of the site.
|
||||
type: float
|
||||
longitude:
|
||||
description:
|
||||
- The longitude of the location of the site.
|
||||
type: float
|
||||
urls:
|
||||
description:
|
||||
- A list of URLs to reference the APICs.
|
||||
type: list
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new site
|
||||
mso_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
site: north_europe
|
||||
description: North European Datacenter
|
||||
apic_username: mso_admin
|
||||
apic_password: AnotherSecretPassword
|
||||
apic_site_id: 12
|
||||
urls:
|
||||
- 10.2.3.4
|
||||
- 10.2.4.5
|
||||
- 10.3.5.6
|
||||
labels:
|
||||
- NEDC
|
||||
- Europe
|
||||
- Diegem
|
||||
location:
|
||||
latitude: 50.887318
|
||||
longitude: 4.447084
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a site
|
||||
mso_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
site: north_europe
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a site
|
||||
mso_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
site: north_europe
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all sites
|
||||
mso_site:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
location_arg_spec = dict(
|
||||
latitude=dict(type='float'),
|
||||
longitude=dict(type='float'),
|
||||
)
|
||||
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
apic_password=dict(type='str', no_log=True),
|
||||
apic_site_id=dict(type='str'),
|
||||
apic_username=dict(type='str', default='admin'),
|
||||
labels=dict(type='list'),
|
||||
location=dict(type='dict', options=location_arg_spec),
|
||||
site=dict(type='str', aliases=['name']),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
urls=dict(type='list'),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['site']],
|
||||
['state', 'present', ['apic_site_id', 'site']],
|
||||
],
|
||||
)
|
||||
|
||||
apic_username = module.params.get('apic_username')
|
||||
apic_password = module.params.get('apic_password')
|
||||
apic_site_id = module.params.get('apic_site_id')
|
||||
site = module.params.get('site')
|
||||
location = module.params.get('location')
|
||||
if location is not None:
|
||||
latitude = module.params.get('location')['latitude']
|
||||
longitude = module.params.get('location')['longitude']
|
||||
state = module.params.get('state')
|
||||
urls = module.params.get('urls')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
site_id = None
|
||||
path = 'sites'
|
||||
|
||||
# Convert labels
|
||||
labels = mso.lookup_labels(module.params.get('labels'), 'site')
|
||||
|
||||
# Query for mso.existing object(s)
|
||||
if site:
|
||||
mso.existing = mso.get_obj(path, name=site)
|
||||
if mso.existing:
|
||||
site_id = mso.existing.get('id')
|
||||
# If we found an existing object, continue with it
|
||||
path = 'sites/{id}'.format(id=site_id)
|
||||
else:
|
||||
mso.existing = mso.query_objs(path)
|
||||
|
||||
if state == 'query':
|
||||
pass
|
||||
|
||||
elif state == 'absent':
|
||||
mso.previous = mso.existing
|
||||
if mso.existing:
|
||||
if module.check_mode:
|
||||
mso.existing = {}
|
||||
else:
|
||||
mso.existing = mso.request(path, method='DELETE', qs=dict(force='true'))
|
||||
|
||||
elif state == 'present':
|
||||
mso.previous = mso.existing
|
||||
|
||||
payload = dict(
|
||||
apicSiteId=apic_site_id,
|
||||
id=site_id,
|
||||
name=site,
|
||||
urls=urls,
|
||||
labels=labels,
|
||||
username=apic_username,
|
||||
password=apic_password,
|
||||
)
|
||||
|
||||
if location is not None:
|
||||
payload['location'] = dict(
|
||||
lat=latitude,
|
||||
long=longitude,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.existing:
|
||||
if not issubset(mso.sent, mso.existing):
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='PUT', data=mso.sent)
|
||||
else:
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='POST', data=mso.sent)
|
||||
|
||||
if 'password' in mso.existing:
|
||||
mso.existing['password'] = '******'
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,200 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_tenant
|
||||
short_description: Manage tenants
|
||||
description:
|
||||
- Manage tenants on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
tenant:
|
||||
description:
|
||||
- The name of the tenant.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
display_name:
|
||||
description:
|
||||
- The name of the tenant to be displayed in the web UI.
|
||||
type: str
|
||||
required: yes
|
||||
description:
|
||||
description:
|
||||
- The description for this tenant.
|
||||
type: str
|
||||
users:
|
||||
description:
|
||||
- A list of associated users for this tenant.
|
||||
- Using this property will replace any existing associated users.
|
||||
type: list
|
||||
sites:
|
||||
description:
|
||||
- A list of associated sites for this tenant.
|
||||
- Using this property will replace any existing associated sites.
|
||||
type: list
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Add a new tenant
|
||||
mso_tenant:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
tenant: north_europe
|
||||
display_name: North European Datacenter
|
||||
description: This tenant manages the NEDC environment.
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a tenant
|
||||
mso_tenant:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
tenant: north_europe
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a tenant
|
||||
mso_tenant:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
tenant: north_europe
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all tenants
|
||||
mso_tenant:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
description=dict(type='str'),
|
||||
display_name=dict(type='str'),
|
||||
tenant=dict(type='str', aliases=['name']),
|
||||
users=dict(type='list'),
|
||||
sites=dict(type='list'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['tenant']],
|
||||
['state', 'present', ['tenant']],
|
||||
],
|
||||
)
|
||||
|
||||
description = module.params.get('description')
|
||||
display_name = module.params.get('display_name')
|
||||
tenant = module.params.get('tenant')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
# Convert sites and users
|
||||
sites = mso.lookup_sites(module.params.get('sites'))
|
||||
users = mso.lookup_users(module.params.get('users'))
|
||||
|
||||
tenant_id = None
|
||||
path = 'tenants'
|
||||
|
||||
# Query for existing object(s)
|
||||
if tenant:
|
||||
mso.existing = mso.get_obj(path, name=tenant)
|
||||
if mso.existing:
|
||||
tenant_id = mso.existing.get('id')
|
||||
# If we found an existing object, continue with it
|
||||
path = 'tenants/{id}'.format(id=tenant_id)
|
||||
else:
|
||||
mso.existing = mso.query_objs(path)
|
||||
|
||||
if state == 'query':
|
||||
pass
|
||||
|
||||
elif state == 'absent':
|
||||
mso.previous = mso.existing
|
||||
if mso.existing:
|
||||
if module.check_mode:
|
||||
mso.existing = {}
|
||||
else:
|
||||
mso.existing = mso.request(path, method='DELETE')
|
||||
|
||||
elif state == 'present':
|
||||
mso.previous = mso.existing
|
||||
|
||||
payload = dict(
|
||||
description=description,
|
||||
id=tenant_id,
|
||||
name=tenant,
|
||||
displayName=display_name,
|
||||
siteAssociations=sites,
|
||||
userAssociations=users,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
# Ensure displayName is not undefined
|
||||
if mso.sent.get('displayName') is None:
|
||||
mso.sent['displayName'] = tenant
|
||||
|
||||
# Ensure tenant has at least admin user
|
||||
if mso.sent.get('userAssociations') is None:
|
||||
mso.sent['userAssociations'] = [dict(userId="0000ffff0000000000000020")]
|
||||
|
||||
if mso.existing:
|
||||
if not issubset(mso.sent, mso.existing):
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='PUT', data=mso.sent)
|
||||
else:
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='POST', data=mso.sent)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,248 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# 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: mso_user
|
||||
short_description: Manage users
|
||||
description:
|
||||
- Manage users on Cisco ACI Multi-Site.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: '2.8'
|
||||
options:
|
||||
user:
|
||||
description:
|
||||
- The name of the user.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
user_password:
|
||||
description:
|
||||
- The password of the user.
|
||||
type: str
|
||||
first_name:
|
||||
description:
|
||||
- The first name of the user.
|
||||
- This parameter is required when creating new users.
|
||||
type: str
|
||||
last_name:
|
||||
description:
|
||||
- The last name of the user.
|
||||
- This parameter is required when creating new users.
|
||||
type: str
|
||||
email:
|
||||
description:
|
||||
- The email address of the user.
|
||||
- This parameter is required when creating new users.
|
||||
type: str
|
||||
phone:
|
||||
description:
|
||||
- The phone number of the user.
|
||||
- This parameter is required when creating new users.
|
||||
type: str
|
||||
account_status:
|
||||
description:
|
||||
- The status of the user account.
|
||||
type: str
|
||||
choices: [ active ]
|
||||
domain:
|
||||
description:
|
||||
- The domain this user belongs to.
|
||||
- When creating new users, this defaults to C(Local).
|
||||
type: str
|
||||
roles:
|
||||
description:
|
||||
- The roles for this user.
|
||||
type: list
|
||||
state:
|
||||
description:
|
||||
- Use C(present) or C(absent) for adding or removing.
|
||||
- Use C(query) for listing an object or multiple objects.
|
||||
type: str
|
||||
choices: [ absent, present, query ]
|
||||
default: present
|
||||
notes:
|
||||
- A default installation of ACI Multi-Site ships with admin password 'we1come!' which requires a password change on first login.
|
||||
See the examples of how to change the 'admin' password using Ansible.
|
||||
extends_documentation_fragment: mso
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Update initial admin password
|
||||
mso_user:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: we1come!
|
||||
user_name: admin
|
||||
user_password: SomeSecretPassword
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Add a new user
|
||||
mso_user:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
user_name: dag
|
||||
description: Test user
|
||||
first_name: Dag
|
||||
last_name: Wieers
|
||||
email: dag@wieers.com
|
||||
phone: +32 478 436 299
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Remove a user
|
||||
mso_user:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
user_name: dag
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Query a user
|
||||
mso_user:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
user_name: dag
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
|
||||
- name: Query all users
|
||||
mso_user:
|
||||
host: mso_host
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: query
|
||||
delegate_to: localhost
|
||||
register: query_result
|
||||
'''
|
||||
|
||||
RETURN = r''' # '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, issubset
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = mso_argument_spec()
|
||||
argument_spec.update(
|
||||
user=dict(type='str', aliases=['name']),
|
||||
user_password=dict(type='str', no_log=True),
|
||||
first_name=dict(type='str'),
|
||||
last_name=dict(type='str'),
|
||||
email=dict(type='str'),
|
||||
phone=dict(type='str'),
|
||||
# TODO: What possible options do we have ?
|
||||
account_status=dict(type='str', choices=['active']),
|
||||
domain=dict(type='str'),
|
||||
roles=dict(type='list'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
['state', 'absent', ['user']],
|
||||
['state', 'present', ['user']],
|
||||
],
|
||||
)
|
||||
|
||||
user_name = module.params.get('user')
|
||||
user_password = module.params.get('user_password')
|
||||
first_name = module.params.get('first_name')
|
||||
last_name = module.params.get('last_name')
|
||||
email = module.params.get('email')
|
||||
phone = module.params.get('phone')
|
||||
account_status = module.params.get('account_status')
|
||||
state = module.params.get('state')
|
||||
|
||||
mso = MSOModule(module)
|
||||
|
||||
roles = mso.lookup_roles(module.params.get('roles'))
|
||||
domain = mso.lookup_domain(module.params.get('domain'))
|
||||
|
||||
user_id = None
|
||||
path = 'users'
|
||||
|
||||
# Query for existing object(s)
|
||||
if user_name:
|
||||
mso.existing = mso.get_obj(path, username=user_name)
|
||||
if mso.existing:
|
||||
user_id = mso.existing.get('id')
|
||||
# If we found an existing object, continue with it
|
||||
path = 'users/{id}'.format(id=user_id)
|
||||
else:
|
||||
mso.existing = mso.query_objs(path)
|
||||
|
||||
if state == 'query':
|
||||
pass
|
||||
|
||||
elif state == 'absent':
|
||||
mso.previous = mso.existing
|
||||
if mso.existing:
|
||||
if module.check_mode:
|
||||
mso.existing = {}
|
||||
else:
|
||||
mso.existing = mso.request(path, method='DELETE')
|
||||
|
||||
elif state == 'present':
|
||||
mso.previous = mso.existing
|
||||
|
||||
payload = dict(
|
||||
id=user_id,
|
||||
username=user_name,
|
||||
password=user_password,
|
||||
firstName=first_name,
|
||||
lastName=last_name,
|
||||
emailAddress=email,
|
||||
phoneNumber=phone,
|
||||
accountStatus=account_status,
|
||||
domainId=domain,
|
||||
roles=roles,
|
||||
# active=True,
|
||||
# remote=True,
|
||||
)
|
||||
|
||||
mso.sanitize(payload, collate=True)
|
||||
|
||||
if mso.sent.get('accountStatus') is None:
|
||||
mso.sent['accountStatus'] = 'active'
|
||||
|
||||
if mso.existing:
|
||||
if not issubset(mso.sent, mso.existing):
|
||||
# NOTE: Since MSO always returns '******' as password, we need to assume a change
|
||||
if 'password' in mso.proposed:
|
||||
mso.module.warn("A password change is assumed, as the MSO REST API does not return passwords we do not know.")
|
||||
mso.result['changed'] = True
|
||||
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='PUT', data=mso.sent)
|
||||
else:
|
||||
if module.check_mode:
|
||||
mso.existing = mso.proposed
|
||||
else:
|
||||
mso.existing = mso.request(path, method='POST', data=mso.sent)
|
||||
|
||||
mso.exit_json()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,71 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
# Standard files documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- IP Address or hostname of the ACI Multi Site Orchestrator host.
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ hostname ]
|
||||
port:
|
||||
description:
|
||||
- Port number to be used for the REST connection.
|
||||
- The default value depends on parameter `use_ssl`.
|
||||
type: int
|
||||
username:
|
||||
description:
|
||||
- The username to use for authentication.
|
||||
type: str
|
||||
default: admin
|
||||
password:
|
||||
description:
|
||||
- The password to use for authentication.
|
||||
- This option is mutual exclusive with C(private_key). If C(private_key) is provided too, it will be used instead.
|
||||
type: str
|
||||
required: yes
|
||||
output_level:
|
||||
description:
|
||||
- Influence the output of this ACI module.
|
||||
- C(normal) means the standard output, incl. C(current) dict
|
||||
- C(info) adds informational output, incl. C(previous), C(proposed) and C(sent) dicts
|
||||
- C(debug) adds debugging output, incl. C(filter_string), C(method), C(response), C(status) and C(url) information
|
||||
type: str
|
||||
choices: [ debug, info, normal ]
|
||||
default: normal
|
||||
timeout:
|
||||
description:
|
||||
- The socket level timeout in seconds.
|
||||
type: int
|
||||
default: 30
|
||||
use_proxy:
|
||||
description:
|
||||
- If C(no), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
|
||||
type: bool
|
||||
default: yes
|
||||
use_ssl:
|
||||
description:
|
||||
- If C(no), an HTTP connection will be used instead of the default HTTPS connection.
|
||||
type: bool
|
||||
default: yes
|
||||
validate_certs:
|
||||
description:
|
||||
- If C(no), SSL certificates will not be validated.
|
||||
- This should only set to C(no) when used on personally controlled sites using self-signed certificates.
|
||||
type: bool
|
||||
default: yes
|
||||
requirements:
|
||||
- Multi Site Orchestrator v2.1 or newer
|
||||
notes:
|
||||
- Please read the :ref:`aci_guide` for more detailed information on how to manage your ACI infrastructure using Ansible.
|
||||
- This module was written to support ACI Multi Site Orchestrator v2.1 or newer. Some or all functionality may not work on earlier versions.
|
||||
'''
|
@ -1,2 +0,0 @@
|
||||
# No ACI MultiSite infrastructure, so not enabled
|
||||
unsupported
|
@ -1,283 +0,0 @@
|
||||
# Test code for the MSO modules
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
- name: Test that we have an ACI MultiSite host, username and password
|
||||
fail:
|
||||
msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
|
||||
when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
|
||||
|
||||
|
||||
# CLEAN ENVIRONMENT
|
||||
- name: Remove label ansible_test2
|
||||
mso_label: &label_absent
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
label: ansible_test2
|
||||
state: absent
|
||||
|
||||
- name: Remove label ansible_test
|
||||
mso_label:
|
||||
<<: *label_absent
|
||||
label: ansible_test
|
||||
register: cm_remove_label
|
||||
|
||||
|
||||
# ADD LABEL
|
||||
- name: Add label (check_mode)
|
||||
mso_label: &label_present
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
label: ansible_test
|
||||
state: present
|
||||
check_mode: yes
|
||||
register: cm_add_label
|
||||
|
||||
- name: Verify cm_add_label
|
||||
assert:
|
||||
that:
|
||||
- cm_add_label is changed
|
||||
- cm_add_label.previous == {}
|
||||
- cm_add_label.current.displayName == 'ansible_test'
|
||||
- cm_add_label.current.id is not defined
|
||||
- cm_add_label.current.type == 'site'
|
||||
|
||||
- name: Add label (normal mode)
|
||||
mso_label: *label_present
|
||||
register: nm_add_label
|
||||
|
||||
- name: nm_Verify add_label
|
||||
assert:
|
||||
that:
|
||||
- nm_add_label is changed
|
||||
- nm_add_label.previous == {}
|
||||
- nm_add_label.current.displayName == 'ansible_test'
|
||||
- nm_add_label.current.id is defined
|
||||
- nm_add_label.current.type == 'site'
|
||||
|
||||
- name: Add label again (check_mode)
|
||||
mso_label: *label_present
|
||||
check_mode: yes
|
||||
register: cm_add_label_again
|
||||
|
||||
- name: Verify cm_add_label_again
|
||||
assert:
|
||||
that:
|
||||
- cm_add_label_again is not changed
|
||||
- cm_add_label_again.previous.displayName == 'ansible_test'
|
||||
- cm_add_label_again.previous.type == 'site'
|
||||
- cm_add_label_again.current.displayName == 'ansible_test'
|
||||
- cm_add_label_again.current.id == nm_add_label.current.id
|
||||
- cm_add_label_again.current.type == 'site'
|
||||
|
||||
- name: Add label again (normal mode)
|
||||
mso_label: *label_present
|
||||
register: nm_add_label_again
|
||||
|
||||
- name: Verify nm_add_label_again
|
||||
assert:
|
||||
that:
|
||||
- nm_add_label_again is not changed
|
||||
- nm_add_label_again.previous.displayName == 'ansible_test'
|
||||
- nm_add_label_again.previous.type == 'site'
|
||||
- nm_add_label_again.current.displayName == 'ansible_test'
|
||||
- nm_add_label_again.current.id == nm_add_label.current.id
|
||||
- nm_add_label_again.current.type == 'site'
|
||||
|
||||
|
||||
# CHANGE LABEL
|
||||
- name: Change label (check_mode)
|
||||
mso_label:
|
||||
<<: *label_present
|
||||
label_id: '{{ nm_add_label.current.id }}'
|
||||
label: ansible_test2
|
||||
check_mode: yes
|
||||
register: cm_change_label
|
||||
|
||||
- name: Verify cm_change_label
|
||||
assert:
|
||||
that:
|
||||
- cm_change_label is changed
|
||||
- cm_change_label.current.displayName == 'ansible_test2'
|
||||
- cm_change_label.current.id == nm_add_label.current.id
|
||||
- cm_change_label.current.type == 'site'
|
||||
|
||||
- name: Change label (normal mode)
|
||||
mso_label:
|
||||
<<: *label_present
|
||||
label_id: '{{ nm_add_label.current.id }}'
|
||||
label: ansible_test2
|
||||
output_level: debug
|
||||
register: nm_change_label
|
||||
|
||||
- name: Verify nm_change_label
|
||||
assert:
|
||||
that:
|
||||
- nm_change_label is changed
|
||||
- cm_change_label.current.displayName == 'ansible_test2'
|
||||
- nm_change_label.current.id == nm_add_label.current.id
|
||||
- nm_change_label.current.type == 'site'
|
||||
|
||||
- name: Change label again (check_mode)
|
||||
mso_label:
|
||||
<<: *label_present
|
||||
label_id: '{{ nm_add_label.current.id }}'
|
||||
label: ansible_test2
|
||||
check_mode: yes
|
||||
register: cm_change_label_again
|
||||
|
||||
- name: Verify cm_change_label_again
|
||||
assert:
|
||||
that:
|
||||
- cm_change_label_again is not changed
|
||||
- cm_change_label_again.current.displayName == 'ansible_test2'
|
||||
- cm_change_label_again.current.id == nm_add_label.current.id
|
||||
- cm_change_label_again.current.type == 'site'
|
||||
|
||||
- name: Change label again (normal mode)
|
||||
mso_label:
|
||||
<<: *label_present
|
||||
label_id: '{{ nm_add_label.current.id }}'
|
||||
label: ansible_test2
|
||||
register: nm_change_label_again
|
||||
|
||||
- name: Verify nm_change_label_again
|
||||
assert:
|
||||
that:
|
||||
- nm_change_label_again is not changed
|
||||
- nm_change_label_again.current.displayName == 'ansible_test2'
|
||||
- nm_change_label_again.current.id == nm_add_label.current.id
|
||||
- nm_change_label_again.current.type == 'site'
|
||||
|
||||
|
||||
# QUERY ALL LABELS
|
||||
- name: Query all labels (check_mode)
|
||||
mso_label: &label_query
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
state: query
|
||||
check_mode: yes
|
||||
register: cm_query_all_labels
|
||||
|
||||
- name: Query all labels (normal mode)
|
||||
mso_label: *label_query
|
||||
register: nm_query_all_labels
|
||||
|
||||
- name: Verify query_all_labels
|
||||
assert:
|
||||
that:
|
||||
- cm_query_all_labels is not changed
|
||||
- nm_query_all_labels is not changed
|
||||
# NOTE: Order of labels is not stable between calls
|
||||
#- cm_query_all_labels == nm_query_all_labels
|
||||
|
||||
|
||||
# QUERY A LABEL
|
||||
- name: Query our label
|
||||
mso_label:
|
||||
<<: *label_query
|
||||
label: ansible_test2
|
||||
check_mode: yes
|
||||
register: cm_query_label
|
||||
|
||||
- name: Query our label
|
||||
mso_label:
|
||||
<<: *label_query
|
||||
label: ansible_test2
|
||||
register: nm_query_label
|
||||
|
||||
- name: Verify query_label
|
||||
assert:
|
||||
that:
|
||||
- cm_query_label is not changed
|
||||
- cm_query_label.current.displayName == 'ansible_test2'
|
||||
- cm_query_label.current.id == nm_add_label.current.id
|
||||
- cm_query_label.current.type == 'site'
|
||||
- nm_query_label is not changed
|
||||
- nm_query_label.current.displayName == 'ansible_test2'
|
||||
- nm_query_label.current.id == nm_add_label.current.id
|
||||
- nm_query_label.current.type == 'site'
|
||||
- cm_query_label == nm_query_label
|
||||
|
||||
|
||||
# REMOVE LABEL
|
||||
- name: Remove label (check_mode)
|
||||
mso_label: *label_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_label
|
||||
|
||||
- name: Verify cm_remove_label
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_label is changed
|
||||
- cm_remove_label.current == {}
|
||||
|
||||
- name: Remove label (normal mode)
|
||||
mso_label: *label_absent
|
||||
register: nm_remove_label
|
||||
|
||||
- name: Verify nm_remove_label
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_label is changed
|
||||
- nm_remove_label.current == {}
|
||||
|
||||
- name: Remove label again (check_mode)
|
||||
mso_label: *label_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_label_again
|
||||
|
||||
- name: Verify cm_remove_label_again
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_label_again is not changed
|
||||
- cm_remove_label_again.current == {}
|
||||
|
||||
- name: Remove label again (normal mode)
|
||||
mso_label: *label_absent
|
||||
register: nm_remove_label_again
|
||||
|
||||
- name: Verify nm_remove_label_again
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_label_again is not changed
|
||||
- nm_remove_label_again.current == {}
|
||||
|
||||
|
||||
# QUERY NON-EXISTING LABEL
|
||||
- name: Query non-existing label (check_mode)
|
||||
mso_label:
|
||||
<<: *label_query
|
||||
label: ansible_test
|
||||
check_mode: yes
|
||||
register: cm_query_non_label
|
||||
|
||||
- name: Query non-existing label (normal mode)
|
||||
mso_label:
|
||||
<<: *label_query
|
||||
label: ansible_test
|
||||
register: nm_query_non_label
|
||||
|
||||
# TODO: Implement more tests
|
||||
- name: Verify query_non_label
|
||||
assert:
|
||||
that:
|
||||
- cm_query_non_label is not changed
|
||||
- nm_query_non_label is not changed
|
||||
- cm_query_non_label == nm_query_non_label
|
@ -1,2 +0,0 @@
|
||||
# No ACI MultiSite infrastructure, so not enabled
|
||||
unsupported
|
@ -1,289 +0,0 @@
|
||||
# Test code for the MSO modules
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
- name: Test that we have an ACI MultiSite host, username and password
|
||||
fail:
|
||||
msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
|
||||
when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
|
||||
|
||||
|
||||
# CLEAN ENVIRONMENT
|
||||
- name: Remove role ansible_test2
|
||||
mso_role: &role_absent
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
role: ansible_test2
|
||||
state: absent
|
||||
|
||||
- name: Remove role ansible_test
|
||||
mso_role:
|
||||
<<: *role_absent
|
||||
role: ansible_test
|
||||
register: cm_remove_role
|
||||
|
||||
|
||||
# ADD ROLE
|
||||
- name: Add role (check_mode)
|
||||
mso_role: &role_present
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
role: ansible_test
|
||||
description: Ansible test role
|
||||
permissions: view-sites
|
||||
state: present
|
||||
check_mode: yes
|
||||
register: cm_add_role
|
||||
|
||||
- name: Verify cm_add_role
|
||||
assert:
|
||||
that:
|
||||
- cm_add_role is changed
|
||||
- cm_add_role.previous == {}
|
||||
- cm_add_role.current.description == 'Ansible test role'
|
||||
- cm_add_role.current.displayName == 'ansible_test'
|
||||
- cm_add_role.current.id is not defined
|
||||
|
||||
- name: Add role (normal mode)
|
||||
mso_role: *role_present
|
||||
register: nm_add_role
|
||||
|
||||
- name: nm_Verify add_role
|
||||
assert:
|
||||
that:
|
||||
- nm_add_role is changed
|
||||
- nm_add_role.previous == {}
|
||||
- nm_add_role.current.description == 'Ansible test role'
|
||||
- nm_add_role.current.displayName == 'ansible_test'
|
||||
- nm_add_role.current.id is defined
|
||||
|
||||
- name: Add role again (check_mode)
|
||||
mso_role: *role_present
|
||||
check_mode: yes
|
||||
register: cm_add_role_again
|
||||
|
||||
- name: Verify cm_add_role_again
|
||||
assert:
|
||||
that:
|
||||
- cm_add_role_again is not changed
|
||||
- cm_add_role_again.previous.description == 'Ansible test role'
|
||||
- cm_add_role_again.previous.displayName == 'ansible_test'
|
||||
- cm_add_role_again.current.description == 'Ansible test role'
|
||||
- cm_add_role_again.current.displayName == 'ansible_test'
|
||||
- cm_add_role_again.current.id == nm_add_role.current.id
|
||||
|
||||
- name: Add role again (normal mode)
|
||||
mso_role: *role_present
|
||||
register: nm_add_role_again
|
||||
|
||||
- name: Verify nm_add_role_again
|
||||
assert:
|
||||
that:
|
||||
- nm_add_role_again is not changed
|
||||
- nm_add_role_again.previous.description == 'Ansible test role'
|
||||
- nm_add_role_again.previous.displayName == 'ansible_test'
|
||||
- nm_add_role_again.current.description == 'Ansible test role'
|
||||
- nm_add_role_again.current.displayName == 'ansible_test'
|
||||
- nm_add_role_again.current.id == nm_add_role.current.id
|
||||
|
||||
|
||||
# CHANGE ROLE
|
||||
- name: Change role (check_mode)
|
||||
mso_role:
|
||||
<<: *role_present
|
||||
role_id: '{{ nm_add_role.current.id }}'
|
||||
role: ansible_test2
|
||||
description: Ansible test role 2
|
||||
check_mode: yes
|
||||
register: cm_change_role
|
||||
|
||||
- name: Verify cm_change_role
|
||||
assert:
|
||||
that:
|
||||
- cm_change_role is changed
|
||||
- cm_change_role.current.description == 'Ansible test role 2'
|
||||
- cm_change_role.current.displayName == 'ansible_test2'
|
||||
- cm_change_role.current.id == nm_add_role.current.id
|
||||
|
||||
- name: Change role (normal mode)
|
||||
mso_role:
|
||||
<<: *role_present
|
||||
role_id: '{{ nm_add_role.current.id }}'
|
||||
role: ansible_test2
|
||||
description: Ansible test role 2
|
||||
output_level: debug
|
||||
register: nm_change_role
|
||||
|
||||
- name: Verify nm_change_role
|
||||
assert:
|
||||
that:
|
||||
- nm_change_role is changed
|
||||
- nm_change_role.current.description == 'Ansible test role 2'
|
||||
- nm_change_role.current.displayName == 'ansible_test2'
|
||||
- nm_change_role.current.id == nm_add_role.current.id
|
||||
|
||||
- name: Change role again (check_mode)
|
||||
mso_role:
|
||||
<<: *role_present
|
||||
role_id: '{{ nm_add_role.current.id }}'
|
||||
role: ansible_test2
|
||||
description: Ansible test role 2
|
||||
check_mode: yes
|
||||
register: cm_change_role_again
|
||||
|
||||
- name: Verify cm_change_role_again
|
||||
assert:
|
||||
that:
|
||||
- cm_change_role_again is not changed
|
||||
- cm_change_role_again.current.description == 'Ansible test role 2'
|
||||
- cm_change_role_again.current.displayName == 'ansible_test2'
|
||||
- cm_change_role_again.current.id == nm_add_role.current.id
|
||||
|
||||
- name: Change role again (normal mode)
|
||||
mso_role:
|
||||
<<: *role_present
|
||||
role_id: '{{ nm_add_role.current.id }}'
|
||||
role: ansible_test2
|
||||
description: Ansible test role 2
|
||||
register: nm_change_role_again
|
||||
|
||||
- name: Verify nm_change_role_again
|
||||
assert:
|
||||
that:
|
||||
- nm_change_role_again is not changed
|
||||
- nm_change_role_again.current.description == 'Ansible test role 2'
|
||||
- nm_change_role_again.current.displayName == 'ansible_test2'
|
||||
- nm_change_role_again.current.id == nm_add_role.current.id
|
||||
|
||||
|
||||
# QUERY ALL ROLES
|
||||
- name: Query all roles (check_mode)
|
||||
mso_role: &role_query
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
state: query
|
||||
check_mode: yes
|
||||
register: cm_query_all_roles
|
||||
|
||||
- name: Query all roles (normal mode)
|
||||
mso_role: *role_query
|
||||
register: nm_query_all_roles
|
||||
|
||||
- name: Verify query_all_roles
|
||||
assert:
|
||||
that:
|
||||
- cm_query_all_roles is not changed
|
||||
- nm_query_all_roles is not changed
|
||||
# NOTE: Order of roles is not stable between calls
|
||||
#- cm_query_all_roles == nm_query_all_roles
|
||||
|
||||
|
||||
# QUERY A ROLE
|
||||
- name: Query our role
|
||||
mso_role:
|
||||
<<: *role_query
|
||||
role: ansible_test2
|
||||
check_mode: yes
|
||||
register: cm_query_role
|
||||
|
||||
- name: Query our role
|
||||
mso_role:
|
||||
<<: *role_query
|
||||
role: ansible_test2
|
||||
register: nm_query_role
|
||||
|
||||
- name: Verify query_role
|
||||
assert:
|
||||
that:
|
||||
- cm_query_role is not changed
|
||||
- cm_query_role.current.description == 'Ansible test role 2'
|
||||
- cm_query_role.current.displayName == 'ansible_test2'
|
||||
- cm_query_role.current.id == nm_add_role.current.id
|
||||
- nm_query_role is not changed
|
||||
- nm_query_role.current.description == 'Ansible test role 2'
|
||||
- nm_query_role.current.displayName == 'ansible_test2'
|
||||
- nm_query_role.current.id == nm_add_role.current.id
|
||||
- cm_query_role == nm_query_role
|
||||
|
||||
|
||||
# REMOVE ROLE
|
||||
- name: Remove role (check_mode)
|
||||
mso_role: *role_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_role
|
||||
|
||||
- name: Verify cm_remove_role
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_role is changed
|
||||
- cm_remove_role.current == {}
|
||||
|
||||
- name: Remove role (normal mode)
|
||||
mso_role: *role_absent
|
||||
register: nm_remove_role
|
||||
|
||||
- name: Verify nm_remove_role
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_role is changed
|
||||
- nm_remove_role.current == {}
|
||||
|
||||
- name: Remove role again (check_mode)
|
||||
mso_role: *role_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_role_again
|
||||
|
||||
- name: Verify cm_remove_role_again
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_role_again is not changed
|
||||
- cm_remove_role_again.current == {}
|
||||
|
||||
- name: Remove role again (normal mode)
|
||||
mso_role: *role_absent
|
||||
register: nm_remove_role_again
|
||||
|
||||
- name: Verify nm_remove_role_again
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_role_again is not changed
|
||||
- nm_remove_role_again.current == {}
|
||||
|
||||
|
||||
# QUERY NON-EXISTING ROLE
|
||||
- name: Query non-existing role (check_mode)
|
||||
mso_role:
|
||||
<<: *role_query
|
||||
role: ansible_test
|
||||
check_mode: yes
|
||||
register: cm_query_non_role
|
||||
|
||||
- name: Query non-existing role (normal mode)
|
||||
mso_role:
|
||||
<<: *role_query
|
||||
role: ansible_test
|
||||
register: nm_query_non_role
|
||||
|
||||
# TODO: Implement more tests
|
||||
- name: Verify query_non_role
|
||||
assert:
|
||||
that:
|
||||
- cm_query_non_role is not changed
|
||||
- nm_query_non_role is not changed
|
||||
- cm_query_non_role == nm_query_non_role
|
@ -1,2 +0,0 @@
|
||||
# No ACI MultiSite infrastructure, so not enabled
|
||||
unsupported
|
@ -1,283 +0,0 @@
|
||||
# Test code for the MSO modules
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
- name: Test that we have an ACI MultiSite host, username and password
|
||||
fail:
|
||||
msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
|
||||
when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
|
||||
|
||||
|
||||
# CLEAN ENVIRONMENT
|
||||
- name: Remove site 2
|
||||
mso_site: &site_absent
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
site: '{{ mso_site | default("ansible_test") }}_2'
|
||||
state: absent
|
||||
|
||||
- name: Remove site
|
||||
mso_site:
|
||||
<<: *site_absent
|
||||
site: '{{ mso_site | default("ansible_test") }}'
|
||||
register: cm_remove_site
|
||||
|
||||
|
||||
# ADD SITE
|
||||
- name: Add site (check_mode)
|
||||
mso_site: &site_present
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
site: '{{ mso_site | default("ansible_test") }}'
|
||||
apic_username: admin
|
||||
apic_password: '{{ apic_password }}'
|
||||
apic_site_id: 101
|
||||
urls:
|
||||
- https://{{ apic_hostname }}
|
||||
location:
|
||||
latitude: 50.887318
|
||||
longitude: 4.447084
|
||||
labels:
|
||||
- Diegem
|
||||
- EMEA
|
||||
- POD51
|
||||
state: present
|
||||
check_mode: yes
|
||||
register: cm_add_site
|
||||
|
||||
- name: Verify cm_add_site
|
||||
assert:
|
||||
that:
|
||||
- cm_add_site is changed
|
||||
- cm_add_site.previous == {}
|
||||
- cm_add_site.current.id is not defined
|
||||
- cm_add_site.current.name == mso_site|default("ansible_test")
|
||||
|
||||
- name: Add site (normal mode)
|
||||
mso_site: *site_present
|
||||
register: nm_add_site
|
||||
|
||||
- name: nm_Verify add_site
|
||||
assert:
|
||||
that:
|
||||
- nm_add_site is changed
|
||||
- nm_add_site.previous == {}
|
||||
- nm_add_site.current.id is defined
|
||||
- nm_add_site.current.name == mso_site|default("ansible_test")
|
||||
|
||||
- name: Add site again (check_mode)
|
||||
mso_site: *site_present
|
||||
check_mode: yes
|
||||
register: cm_add_site_again
|
||||
|
||||
- name: Verify cm_add_site_again
|
||||
assert:
|
||||
that:
|
||||
- cm_add_site_again is not changed
|
||||
- cm_add_site_again.previous.name == mso_site|default("ansible_test")
|
||||
- cm_add_site_again.current.id == nm_add_site.current.id
|
||||
- cm_add_site_again.current.name == mso_site|default("ansible_test")
|
||||
|
||||
- name: Add site again (normal mode)
|
||||
mso_site: *site_present
|
||||
register: nm_add_site_again
|
||||
|
||||
- name: Verify nm_add_site_again
|
||||
assert:
|
||||
that:
|
||||
- nm_add_site_again is not changed
|
||||
- nm_add_site_again.previous.name == mso_site|default("ansible_test")
|
||||
- nm_add_site_again.current.id == nm_add_site.current.id
|
||||
- nm_add_site_again.current.name == mso_site|default("ansible_test")
|
||||
|
||||
|
||||
# CHANGE SITE
|
||||
- name: Change site (check_mode)
|
||||
mso_site:
|
||||
<<: *site_present
|
||||
site_id: '{{ nm_add_site.current.id }}'
|
||||
site: '{{ mso_site | default("ansible_test") }}_2'
|
||||
check_mode: yes
|
||||
register: cm_change_site
|
||||
|
||||
- name: Verify cm_change_site
|
||||
assert:
|
||||
that:
|
||||
- cm_change_site is changed
|
||||
- cm_change_site.current.id == nm_add_site.current.id
|
||||
- cm_change_site.current.name == '{{ mso_site | default("ansible_test") }}_2'
|
||||
|
||||
- name: Change site (normal mode)
|
||||
mso_site:
|
||||
<<: *site_present
|
||||
site_id: '{{ nm_add_site.current.id }}'
|
||||
site: '{{ mso_site | default("ansible_test") }}_2'
|
||||
output_level: debug
|
||||
register: nm_change_site
|
||||
|
||||
- name: Verify nm_change_site
|
||||
assert:
|
||||
that:
|
||||
- nm_change_site is changed
|
||||
- nm_change_site.current.id == nm_add_site.current.id
|
||||
- nm_change_site.current.name == '{{ mso_site | default("ansible_test") }}_2'
|
||||
|
||||
- name: Change site again (check_mode)
|
||||
mso_site:
|
||||
<<: *site_present
|
||||
site_id: '{{ nm_add_site.current.id }}'
|
||||
site: '{{ mso_site | default("ansible_test") }}_2'
|
||||
check_mode: yes
|
||||
register: cm_change_site_again
|
||||
|
||||
- name: Verify cm_change_site_again
|
||||
assert:
|
||||
that:
|
||||
- cm_change_site_again is not changed
|
||||
- cm_change_site_again.current.id == nm_add_site.current.id
|
||||
- cm_change_site_again.current.name == '{{ mso_site | default("ansible_test") }}_2'
|
||||
|
||||
- name: Change site again (normal mode)
|
||||
mso_site:
|
||||
<<: *site_present
|
||||
site_id: '{{ nm_add_site.current.id }}'
|
||||
site: '{{ mso_site | default("ansible_test") }}_2'
|
||||
register: nm_change_site_again
|
||||
|
||||
- name: Verify nm_change_site_again
|
||||
assert:
|
||||
that:
|
||||
- nm_change_site_again is not changed
|
||||
- nm_change_site_again.current.id == nm_add_site.current.id
|
||||
- nm_change_site_again.current.name == '{{ mso_site | default("ansible_test") }}_2'
|
||||
|
||||
|
||||
# QUERY ALL SITES
|
||||
- name: Query all sites (check_mode)
|
||||
mso_site: &site_query
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
state: query
|
||||
check_mode: yes
|
||||
register: cm_query_all_sites
|
||||
|
||||
- name: Query all sites (normal mode)
|
||||
mso_site: *site_query
|
||||
register: nm_query_all_sites
|
||||
|
||||
- name: Verify query_all_sites
|
||||
assert:
|
||||
that:
|
||||
- cm_query_all_sites is not changed
|
||||
- nm_query_all_sites is not changed
|
||||
# NOTE: Order of sites is not stable between calls
|
||||
#- cm_query_all_sites == nm_query_all_sites
|
||||
|
||||
|
||||
# QUERY A SITE
|
||||
- name: Query our site
|
||||
mso_site:
|
||||
<<: *site_query
|
||||
site: '{{ mso_site | default("ansible_test") }}_2'
|
||||
check_mode: yes
|
||||
register: cm_query_site
|
||||
|
||||
- name: Query our site
|
||||
mso_site:
|
||||
<<: *site_query
|
||||
site: '{{ mso_site | default("ansible_test") }}_2'
|
||||
register: nm_query_site
|
||||
|
||||
- name: Verify query_site
|
||||
assert:
|
||||
that:
|
||||
- cm_query_site is not changed
|
||||
- cm_query_site.current.id == nm_add_site.current.id
|
||||
- cm_query_site.current.name == '{{ mso_site | default("ansible_test") }}_2'
|
||||
- nm_query_site is not changed
|
||||
- nm_query_site.current.id == nm_add_site.current.id
|
||||
- nm_query_site.current.name == '{{ mso_site | default("ansible_test") }}_2'
|
||||
- cm_query_site == nm_query_site
|
||||
|
||||
|
||||
# REMOVE SITE
|
||||
- name: Remove site (check_mode)
|
||||
mso_site: *site_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_site
|
||||
|
||||
- name: Verify cm_remove_site
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_site is changed
|
||||
- cm_remove_site.current == {}
|
||||
|
||||
- name: Remove site (normal mode)
|
||||
mso_site: *site_absent
|
||||
register: nm_remove_site
|
||||
|
||||
- name: Verify nm_remove_site
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_site is changed
|
||||
- nm_remove_site.current == {}
|
||||
|
||||
- name: Remove site again (check_mode)
|
||||
mso_site: *site_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_site_again
|
||||
|
||||
- name: Verify cm_remove_site_again
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_site_again is not changed
|
||||
- cm_remove_site_again.current == {}
|
||||
|
||||
- name: Remove site again (normal mode)
|
||||
mso_site: *site_absent
|
||||
register: nm_remove_site_again
|
||||
|
||||
- name: Verify nm_remove_site_again
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_site_again is not changed
|
||||
- nm_remove_site_again.current == {}
|
||||
|
||||
|
||||
# QUERY NON-EXISTING SITE
|
||||
- name: Query non-existing site (check_mode)
|
||||
mso_site:
|
||||
<<: *site_query
|
||||
site: '{{ mso_site | default("ansible_test") }}'
|
||||
check_mode: yes
|
||||
register: cm_query_non_site
|
||||
|
||||
- name: Query non-existing site (normal mode)
|
||||
mso_site:
|
||||
<<: *site_query
|
||||
site: '{{ mso_site | default("ansible_test") }}'
|
||||
register: nm_query_non_site
|
||||
|
||||
# TODO: Implement more tests
|
||||
- name: Verify query_non_site
|
||||
assert:
|
||||
that:
|
||||
- cm_query_non_site is not changed
|
||||
- nm_query_non_site is not changed
|
||||
- cm_query_non_site == nm_query_non_site
|
@ -1,2 +0,0 @@
|
||||
# No ACI MultiSite infrastructure, so not enabled
|
||||
unsupported
|
@ -1,289 +0,0 @@
|
||||
# Test code for the MSO modules
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
- name: Test that we have an ACI MultiSite host, username and password
|
||||
fail:
|
||||
msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
|
||||
when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
|
||||
|
||||
|
||||
# CLEAN ENVIRONMENT
|
||||
- name: Remove tenant ansible_test2
|
||||
mso_tenant: &tenant_absent
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
tenant: ansible_test2
|
||||
state: absent
|
||||
|
||||
- name: Remove tenant ansible_test
|
||||
mso_tenant:
|
||||
<<: *tenant_absent
|
||||
tenant: ansible_test
|
||||
register: cm_remove_tenant
|
||||
|
||||
|
||||
# ADD TENANT
|
||||
- name: Add tenant (check_mode)
|
||||
mso_tenant: &tenant_present
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
tenant: ansible_test
|
||||
display_name: Ansible test title
|
||||
description: Ansible test tenant
|
||||
state: present
|
||||
check_mode: yes
|
||||
register: cm_add_tenant
|
||||
|
||||
- name: Verify cm_add_tenant
|
||||
assert:
|
||||
that:
|
||||
- cm_add_tenant is changed
|
||||
- cm_add_tenant.previous == {}
|
||||
- cm_add_tenant.current.id is not defined
|
||||
- cm_add_tenant.current.name == 'ansible_test'
|
||||
- cm_add_tenant.current.description == 'Ansible test tenant'
|
||||
|
||||
- name: Add tenant (normal mode)
|
||||
mso_tenant: *tenant_present
|
||||
register: nm_add_tenant
|
||||
|
||||
- name: nm_Verify add_tenant
|
||||
assert:
|
||||
that:
|
||||
- nm_add_tenant is changed
|
||||
- nm_add_tenant.previous == {}
|
||||
- nm_add_tenant.current.id is defined
|
||||
- nm_add_tenant.current.name == 'ansible_test'
|
||||
- nm_add_tenant.current.description == 'Ansible test tenant'
|
||||
|
||||
- name: Add tenant again (check_mode)
|
||||
mso_tenant: *tenant_present
|
||||
check_mode: yes
|
||||
register: cm_add_tenant_again
|
||||
|
||||
- name: Verify cm_add_tenant_again
|
||||
assert:
|
||||
that:
|
||||
- cm_add_tenant_again is not changed
|
||||
- cm_add_tenant_again.previous.name == 'ansible_test'
|
||||
- cm_add_tenant_again.previous.description == 'Ansible test tenant'
|
||||
- cm_add_tenant_again.current.id == nm_add_tenant.current.id
|
||||
- cm_add_tenant_again.current.name == 'ansible_test'
|
||||
- cm_add_tenant_again.current.description == 'Ansible test tenant'
|
||||
|
||||
- name: Add tenant again (normal mode)
|
||||
mso_tenant: *tenant_present
|
||||
register: nm_add_tenant_again
|
||||
|
||||
- name: Verify nm_add_tenant_again
|
||||
assert:
|
||||
that:
|
||||
- nm_add_tenant_again is not changed
|
||||
- nm_add_tenant_again.previous.name == 'ansible_test'
|
||||
- nm_add_tenant_again.previous.description == 'Ansible test tenant'
|
||||
- nm_add_tenant_again.current.id == nm_add_tenant.current.id
|
||||
- nm_add_tenant_again.current.name == 'ansible_test'
|
||||
- nm_add_tenant_again.current.description == 'Ansible test tenant'
|
||||
|
||||
|
||||
# CHANGE TENANT
|
||||
- name: Change tenant (check_mode)
|
||||
mso_tenant:
|
||||
<<: *tenant_present
|
||||
tenant_id: '{{ nm_add_tenant.current.id }}'
|
||||
tenant: ansible_test2
|
||||
description: Ansible test tenant 2
|
||||
check_mode: yes
|
||||
register: cm_change_tenant
|
||||
|
||||
- name: Verify cm_change_tenant
|
||||
assert:
|
||||
that:
|
||||
- cm_change_tenant is changed
|
||||
- cm_change_tenant.current.id == nm_add_tenant.current.id
|
||||
- cm_change_tenant.current.name == 'ansible_test2'
|
||||
- cm_change_tenant.current.description == 'Ansible test tenant 2'
|
||||
|
||||
- name: Change tenant (normal mode)
|
||||
mso_tenant:
|
||||
<<: *tenant_present
|
||||
tenant_id: '{{ nm_add_tenant.current.id }}'
|
||||
tenant: ansible_test2
|
||||
description: Ansible test tenant 2
|
||||
output_level: debug
|
||||
register: nm_change_tenant
|
||||
|
||||
- name: Verify nm_change_tenant
|
||||
assert:
|
||||
that:
|
||||
- nm_change_tenant is changed
|
||||
- nm_change_tenant.current.id == nm_add_tenant.current.id
|
||||
- nm_change_tenant.current.name == 'ansible_test2'
|
||||
- nm_change_tenant.current.description == 'Ansible test tenant 2'
|
||||
|
||||
- name: Change tenant again (check_mode)
|
||||
mso_tenant:
|
||||
<<: *tenant_present
|
||||
tenant_id: '{{ nm_add_tenant.current.id }}'
|
||||
tenant: ansible_test2
|
||||
description: Ansible test tenant 2
|
||||
check_mode: yes
|
||||
register: cm_change_tenant_again
|
||||
|
||||
- name: Verify cm_change_tenant_again
|
||||
assert:
|
||||
that:
|
||||
- cm_change_tenant_again is not changed
|
||||
- cm_change_tenant_again.current.id == nm_add_tenant.current.id
|
||||
- cm_change_tenant_again.current.name == 'ansible_test2'
|
||||
- cm_change_tenant_again.current.description == 'Ansible test tenant 2'
|
||||
|
||||
- name: Change tenant again (normal mode)
|
||||
mso_tenant:
|
||||
<<: *tenant_present
|
||||
tenant_id: '{{ nm_add_tenant.current.id }}'
|
||||
tenant: ansible_test2
|
||||
description: Ansible test tenant 2
|
||||
register: nm_change_tenant_again
|
||||
|
||||
- name: Verify nm_change_tenant_again
|
||||
assert:
|
||||
that:
|
||||
- nm_change_tenant_again is not changed
|
||||
- nm_change_tenant_again.current.id == nm_add_tenant.current.id
|
||||
- nm_change_tenant_again.current.name == 'ansible_test2'
|
||||
- nm_change_tenant_again.current.description == 'Ansible test tenant 2'
|
||||
|
||||
|
||||
# QUERY ALL TENANTS
|
||||
- name: Query all tenants (check_mode)
|
||||
mso_tenant: &tenant_query
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
state: query
|
||||
check_mode: yes
|
||||
register: cm_query_all_tenants
|
||||
|
||||
- name: Query all tenants (normal mode)
|
||||
mso_tenant: *tenant_query
|
||||
register: nm_query_all_tenants
|
||||
|
||||
- name: Verify query_all_tenants
|
||||
assert:
|
||||
that:
|
||||
- cm_query_all_tenants is not changed
|
||||
- nm_query_all_tenants is not changed
|
||||
# NOTE: Order of tenants is not stable between calls
|
||||
#- cm_query_all_tenants == nm_query_all_tenants
|
||||
|
||||
|
||||
# QUERY A TENANT
|
||||
- name: Query our tenant
|
||||
mso_tenant:
|
||||
<<: *tenant_query
|
||||
tenant: ansible_test2
|
||||
check_mode: yes
|
||||
register: cm_query_tenant
|
||||
|
||||
- name: Query our tenant
|
||||
mso_tenant:
|
||||
<<: *tenant_query
|
||||
tenant: ansible_test2
|
||||
register: nm_query_tenant
|
||||
|
||||
- name: Verify query_tenant
|
||||
assert:
|
||||
that:
|
||||
- cm_query_tenant is not changed
|
||||
- cm_query_tenant.current.id == nm_add_tenant.current.id
|
||||
- cm_query_tenant.current.name == 'ansible_test2'
|
||||
- cm_query_tenant.current.description == 'Ansible test tenant 2'
|
||||
- nm_query_tenant is not changed
|
||||
- nm_query_tenant.current.id == nm_add_tenant.current.id
|
||||
- nm_query_tenant.current.name == 'ansible_test2'
|
||||
- nm_query_tenant.current.description == 'Ansible test tenant 2'
|
||||
- cm_query_tenant == nm_query_tenant
|
||||
|
||||
|
||||
# REMOVE TENANT
|
||||
- name: Remove tenant (check_mode)
|
||||
mso_tenant: *tenant_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_tenant
|
||||
|
||||
- name: Verify cm_remove_tenant
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_tenant is changed
|
||||
- cm_remove_tenant.current == {}
|
||||
|
||||
- name: Remove tenant (normal mode)
|
||||
mso_tenant: *tenant_absent
|
||||
register: nm_remove_tenant
|
||||
|
||||
- name: Verify nm_remove_tenant
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_tenant is changed
|
||||
- nm_remove_tenant.current == {}
|
||||
|
||||
- name: Remove tenant again (check_mode)
|
||||
mso_tenant: *tenant_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_tenant_again
|
||||
|
||||
- name: Verify cm_remove_tenant_again
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_tenant_again is not changed
|
||||
- cm_remove_tenant_again.current == {}
|
||||
|
||||
- name: Remove tenant again (normal mode)
|
||||
mso_tenant: *tenant_absent
|
||||
register: nm_remove_tenant_again
|
||||
|
||||
- name: Verify nm_remove_tenant_again
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_tenant_again is not changed
|
||||
- nm_remove_tenant_again.current == {}
|
||||
|
||||
|
||||
# QUERY NON-EXISTING TENANT
|
||||
- name: Query non-existing tenant (check_mode)
|
||||
mso_tenant:
|
||||
<<: *tenant_query
|
||||
tenant: ansible_test
|
||||
check_mode: yes
|
||||
register: cm_query_non_tenant
|
||||
|
||||
- name: Query non-existing tenant (normal mode)
|
||||
mso_tenant:
|
||||
<<: *tenant_query
|
||||
tenant: ansible_test
|
||||
register: nm_query_non_tenant
|
||||
|
||||
# TODO: Implement more tests
|
||||
- name: Verify query_non_tenant
|
||||
assert:
|
||||
that:
|
||||
- cm_query_non_tenant is not changed
|
||||
- nm_query_non_tenant is not changed
|
||||
- cm_query_non_tenant == nm_query_non_tenant
|
@ -1,2 +0,0 @@
|
||||
# No ACI MultiSite infrastructure, so not enabled
|
||||
unsupported
|
@ -1,292 +0,0 @@
|
||||
# Test code for the MSO modules
|
||||
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
- name: Test that we have an ACI MultiSite host, username and password
|
||||
fail:
|
||||
msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
|
||||
when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
|
||||
|
||||
|
||||
# CLEAN ENVIRONMENT
|
||||
- name: Remove user ansible_test2
|
||||
mso_user: &user_absent
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
user_name: ansible_test2
|
||||
state: absent
|
||||
|
||||
- name: Remove user ansible_test
|
||||
mso_user:
|
||||
<<: *user_absent
|
||||
user_name: ansible_test
|
||||
register: cm_remove_user
|
||||
|
||||
|
||||
# ADD USER
|
||||
- name: Add user (check_mode)
|
||||
mso_user: &user_present
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
user_name: ansible_test
|
||||
user_password: 'S0m3!1n1t14l!p455w0rd'
|
||||
# NOTE: First name, last name, phone and email are mandatory on creation
|
||||
first_name: Ansible
|
||||
last_name: Test
|
||||
email: msc@cisco.com
|
||||
phone: +32 478 436 299
|
||||
account_status: active
|
||||
roles:
|
||||
- powerUser
|
||||
domain: Local
|
||||
|
||||
state: present
|
||||
check_mode: yes
|
||||
register: cm_add_user
|
||||
|
||||
- name: Verify cm_add_user
|
||||
assert:
|
||||
that:
|
||||
- cm_add_user is changed
|
||||
- cm_add_user.previous == {}
|
||||
- cm_add_user.current.id is not defined
|
||||
- cm_add_user.current.username == 'ansible_test'
|
||||
|
||||
- name: Add user (normal mode)
|
||||
mso_user: *user_present
|
||||
register: nm_add_user
|
||||
|
||||
- name: nm_Verify add_user
|
||||
assert:
|
||||
that:
|
||||
- nm_add_user is changed
|
||||
- nm_add_user.previous == {}
|
||||
- nm_add_user.current.id is defined
|
||||
- nm_add_user.current.username == 'ansible_test'
|
||||
|
||||
- name: Add user again (check_mode)
|
||||
mso_user:
|
||||
<<: *user_present
|
||||
# NOTE: We need to modify the password for a new user
|
||||
user_password: 'S0m3!n3w!p455w0rd'
|
||||
check_mode: yes
|
||||
register: cm_add_user_again
|
||||
|
||||
- name: Verify cm_add_user_again
|
||||
assert:
|
||||
that:
|
||||
- cm_add_user_again is changed
|
||||
- cm_add_user_again.previous.username == 'ansible_test'
|
||||
- cm_add_user_again.current.id == nm_add_user.current.id
|
||||
- cm_add_user_again.current.username == 'ansible_test'
|
||||
|
||||
- name: Add user again (normal mode)
|
||||
mso_user:
|
||||
<<: *user_present
|
||||
# NOTE: We need to modify the password for a new user
|
||||
user_password: 'S0m3!n3w!p455w0rd'
|
||||
register: nm_add_user_again
|
||||
|
||||
- name: Verify nm_add_user_again
|
||||
assert:
|
||||
that:
|
||||
- nm_add_user_again is changed
|
||||
- nm_add_user_again.previous.username == 'ansible_test'
|
||||
- nm_add_user_again.current.id == nm_add_user.current.id
|
||||
- nm_add_user_again.current.username == 'ansible_test'
|
||||
|
||||
|
||||
# CHANGE USER
|
||||
- name: Change user (check_mode)
|
||||
mso_user:
|
||||
<<: *user_present
|
||||
user_id: '{{ nm_add_user.current.id }}'
|
||||
user_name: ansible_test2
|
||||
user_password: null
|
||||
check_mode: yes
|
||||
register: cm_change_user
|
||||
|
||||
- name: Verify cm_change_user
|
||||
assert:
|
||||
that:
|
||||
- cm_change_user is changed
|
||||
- cm_change_user.current.id == nm_add_user.current.id
|
||||
- cm_change_user.current.username == 'ansible_test2'
|
||||
|
||||
- name: Change user (normal mode)
|
||||
mso_user:
|
||||
<<: *user_present
|
||||
user_id: '{{ nm_add_user.current.id }}'
|
||||
user_name: ansible_test2
|
||||
user_password: null
|
||||
output_level: debug
|
||||
register: nm_change_user
|
||||
|
||||
- name: Verify nm_change_user
|
||||
assert:
|
||||
that:
|
||||
- nm_change_user is changed
|
||||
- nm_change_user.current.id == nm_add_user.current.id
|
||||
- nm_change_user.current.username == 'ansible_test2'
|
||||
|
||||
- name: Change user again (check_mode)
|
||||
mso_user:
|
||||
<<: *user_present
|
||||
user_id: '{{ nm_add_user.current.id }}'
|
||||
user_name: ansible_test2
|
||||
user_password: null
|
||||
check_mode: yes
|
||||
register: cm_change_user_again
|
||||
|
||||
- name: Verify cm_change_user_again
|
||||
assert:
|
||||
that:
|
||||
- cm_change_user_again is not changed
|
||||
- cm_change_user_again.current.id == nm_add_user.current.id
|
||||
- cm_change_user_again.current.username == 'ansible_test2'
|
||||
|
||||
- name: Change user again (normal mode)
|
||||
mso_user:
|
||||
<<: *user_present
|
||||
user_id: '{{ nm_add_user.current.id }}'
|
||||
user_name: ansible_test2
|
||||
user_password: null
|
||||
register: nm_change_user_again
|
||||
|
||||
- name: Verify nm_change_user_again
|
||||
assert:
|
||||
that:
|
||||
- nm_change_user_again is not changed
|
||||
- nm_change_user_again.current.id == nm_add_user.current.id
|
||||
- nm_change_user_again.current.username == 'ansible_test2'
|
||||
|
||||
|
||||
# QUERY ALL USERS
|
||||
- name: Query all users (check_mode)
|
||||
mso_user: &user_query
|
||||
host: '{{ mso_hostname }}'
|
||||
username: '{{ mso_username }}'
|
||||
password: '{{ mso_password }}'
|
||||
validate_certs: '{{ mso_validate_certs | default(false) }}'
|
||||
use_ssl: '{{ mso_use_ssl | default(true) }}'
|
||||
use_proxy: '{{ mso_use_proxy | default(true) }}'
|
||||
output_level: '{{ mso_output_level | default("info") }}'
|
||||
state: query
|
||||
check_mode: yes
|
||||
register: cm_query_all_users
|
||||
|
||||
- name: Query all users (normal mode)
|
||||
mso_user: *user_query
|
||||
register: nm_query_all_users
|
||||
|
||||
- name: Verify query_all_users
|
||||
assert:
|
||||
that:
|
||||
- cm_query_all_users is not changed
|
||||
- nm_query_all_users is not changed
|
||||
# NOTE: Order of users is not stable between calls
|
||||
#- cm_query_all_users == nm_query_all_users
|
||||
|
||||
|
||||
# QUERY A USER
|
||||
- name: Query our user
|
||||
mso_user:
|
||||
<<: *user_query
|
||||
user_name: ansible_test2
|
||||
check_mode: yes
|
||||
register: cm_query_user
|
||||
|
||||
- name: Query our user
|
||||
mso_user:
|
||||
<<: *user_query
|
||||
user_name: ansible_test2
|
||||
register: nm_query_user
|
||||
|
||||
- name: Verify query_user
|
||||
assert:
|
||||
that:
|
||||
- cm_query_user is not changed
|
||||
- cm_query_user.current.id == nm_add_user.current.id
|
||||
- cm_query_user.current.username == 'ansible_test2'
|
||||
- nm_query_user is not changed
|
||||
- nm_query_user.current.id == nm_add_user.current.id
|
||||
- nm_query_user.current.username == 'ansible_test2'
|
||||
- cm_query_user == nm_query_user
|
||||
|
||||
|
||||
# REMOVE USER
|
||||
- name: Remove user (check_mode)
|
||||
mso_user: *user_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_user
|
||||
|
||||
- name: Verify cm_remove_user
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_user is changed
|
||||
- cm_remove_user.current == {}
|
||||
|
||||
- name: Remove user (normal mode)
|
||||
mso_user: *user_absent
|
||||
register: nm_remove_user
|
||||
|
||||
- name: Verify nm_remove_user
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_user is changed
|
||||
- nm_remove_user.current == {}
|
||||
|
||||
- name: Remove user again (check_mode)
|
||||
mso_user: *user_absent
|
||||
check_mode: yes
|
||||
register: cm_remove_user_again
|
||||
|
||||
- name: Verify cm_remove_user_again
|
||||
assert:
|
||||
that:
|
||||
- cm_remove_user_again is not changed
|
||||
- cm_remove_user_again.current == {}
|
||||
|
||||
- name: Remove user again (normal mode)
|
||||
mso_user: *user_absent
|
||||
register: nm_remove_user_again
|
||||
|
||||
- name: Verify nm_remove_user_again
|
||||
assert:
|
||||
that:
|
||||
- nm_remove_user_again is not changed
|
||||
- nm_remove_user_again.current == {}
|
||||
|
||||
|
||||
# QUERY NON-EXISTING USER
|
||||
- name: Query non-existing user (check_mode)
|
||||
mso_user:
|
||||
<<: *user_query
|
||||
user_name: ansible_test
|
||||
check_mode: yes
|
||||
register: cm_query_non_user
|
||||
|
||||
- name: Query non-existing user (normal mode)
|
||||
mso_user:
|
||||
<<: *user_query
|
||||
user_name: ansible_test
|
||||
register: nm_query_non_user
|
||||
|
||||
# TODO: Implement more tests
|
||||
- name: Verify query_non_user
|
||||
assert:
|
||||
that:
|
||||
- cm_query_non_user is not changed
|
||||
- nm_query_non_user is not changed
|
||||
- cm_query_non_user == nm_query_non_user
|
Loading…
Reference in New Issue