Migrated to cisco.mso

pull/68298/head
Ansible Core Team 5 years ago committed by Matt Martz
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

@ -1456,34 +1456,6 @@ lib/ansible/modules/net_tools/basics/uri.py pylint:blacklisted-name
lib/ansible/modules/net_tools/basics/uri.py validate-modules:doc-required-mismatch
lib/ansible/modules/net_tools/basics/uri.py validate-modules:parameter-list-no-elements
lib/ansible/modules/net_tools/basics/uri.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/network/aci/mso_label.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_role.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_role.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_schema.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_schema_site.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_anp_epg.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_anp_epg_domain.py pylint:ansible-bad-function
lib/ansible/modules/network/aci/mso_schema_site_anp_epg_domain.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_anp_epg_staticleaf.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_anp_epg_staticport.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_anp_epg_subnet.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_bd_l3out.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_vrf_region.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_vrf_region_cidr.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_site_vrf_region_cidr_subnet.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_template_anp_epg.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_schema_template_bd.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_schema_template_contract_filter.py validate-modules:invalid-ansiblemodule-schema
lib/ansible/modules/network/aci/mso_schema_template_contract_filter.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_schema_template_deploy.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_schema_template_filter_entry.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_site.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_site.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_tenant.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_tenant.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/aci/mso_user.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/aci/mso_user.py validate-modules:parameter-list-no-elements
lib/ansible/modules/network/dellos10/dellos10_command.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/network/dellos10/dellos10_command.py validate-modules:doc-missing-type
lib/ansible/modules/network/dellos10/dellos10_command.py validate-modules:doc-required-mismatch

Loading…
Cancel
Save