MSC: Various bugfixes and features enhancements (#50200)

* MSC: Various bugfixes and features enhancements

This PR includes:
- Lookups of roles, labels and domains
- Auto-create new labels
- Improvements to comparing complex datastructures
- Force removal of sites
- Support non top-level queries
- Document internal functions
- Add parameter types to modules
- Fix documentation examples
- Improvements to idempotency wrt. returning changed
- Support site locations
- Update permission list
- Various improvements to integration tests

* Fix Ci issues
This commit is contained in:
Dag Wieers
2018-12-20 18:18:46 +01:00
committed by GitHub
parent f55481863d
commit 14b03ac15f
9 changed files with 295 additions and 91 deletions

View File

@@ -23,21 +23,25 @@ options:
label_id:
description:
- The ID of the label.
required: yes
type: str
label:
description:
- The name of the label.
- Alternative to the name, you can use C(label_id).
type: str
required: yes
aliases: [ label_name, 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: msc
@@ -49,9 +53,8 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
label_id: 101
description: North European Datacenter
label: Belgium
type: site
state: present
delegate_to: localhost
@@ -60,7 +63,7 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
label: Belgium
state: absent
delegate_to: localhost
@@ -69,7 +72,7 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
label: Belgium
state: query
delegate_to: localhost
register: query_result
@@ -151,11 +154,13 @@ def main():
elif state == 'present':
msc.previous = msc.existing
msc.sanitize(dict(
payload = dict(
id=label_id,
displayName=label,
type=label_type,
), collate=True)
)
msc.sanitize(payload, collate=True)
if msc.existing:
if not issubset(msc.sent, msc.existing):

View File

@@ -23,28 +23,39 @@ options:
role_id:
description:
- The ID of the role.
required: yes
type: str
role:
description:
- The name of the role.
- Alternative to the name, you can use C(role_id).
type: str
required: yes
aliases: [ name, role_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
@@ -55,6 +66,7 @@ options:
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: msc
@@ -66,9 +78,16 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
role_id: 101
description: North European Datacenter
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
@@ -77,7 +96,7 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
role: readOnly
state: absent
delegate_to: localhost
@@ -86,7 +105,7 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
role: readOnly
state: query
delegate_to: localhost
register: query_result
@@ -116,11 +135,18 @@ def main():
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',
@@ -183,13 +209,15 @@ def main():
elif state == 'present':
msc.previous = msc.existing
msc.sanitize(dict(
payload = dict(
id=role_id,
name=role,
displayName=role,
description=description,
permissions=permissions,
), collate=True)
)
msc.sanitize(payload, collate=True)
if msc.existing:
if not issubset(msc.sent, msc.existing):

View File

@@ -40,17 +40,30 @@ options:
description:
- The ID of the site.
type: str
required: yes
site:
description:
- The name of the site.
- Alternative to the name, you can use C(site_id).
type: str
required: yes
aliases: [ name, site_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.
suboptions:
latitude:
description:
- The latitude of the location of the site.
type: float
longitude:
description:
- The longititude of the location of the site.
type: float
urls:
description:
- A list of URLs to reference the APICs.
@@ -72,8 +85,21 @@ EXAMPLES = r'''
username: admin
password: SomeSecretPassword
site: north_europe
site_id: 101
description: North European Datacenter
apic_username: msc_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
@@ -114,12 +140,18 @@ from ansible.module_utils.network.aci.msc import MSCModule, msc_argument_spec, i
def main():
location_arg_spec = dict(
latitude=dict(type='float'),
longitude=dict(type='float'),
)
argument_spec = msc_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', required=False, aliases=['name', 'site_name']),
site_id=dict(type='str', required=False),
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
@@ -140,6 +172,10 @@ def main():
apic_site_id = module.params['apic_site_id']
site = module.params['site']
site_id = module.params['site_id']
location = module.params['location']
if location is not None:
latitude = module.params['location']['latitude']
longitude = module.params['location']['longitude']
state = module.params['state']
urls = module.params['urls']
@@ -147,6 +183,9 @@ def main():
path = 'sites'
# Convert labels
labels = msc.lookup_labels(module.params['labels'], 'site')
# Query for msc.existing object(s)
if site_id is None and site is None:
msc.existing = msc.query_objs(path)
@@ -175,31 +214,43 @@ def main():
if module.check_mode:
msc.existing = {}
else:
msc.existing = msc.request(path, method='DELETE')
msc.existing = msc.request(path, method='DELETE', qs=dict(force='true'))
elif state == 'present':
msc.previous = msc.existing
msc.sanitize(dict(
payload = dict(
apicSiteId=apic_site_id,
id=site_id,
name=site,
urls=urls,
labels=labels,
username=apic_username,
password=apic_password,
), collate=True)
)
if location is not None:
payload['location'] = dict(
lat=latitude,
long=longitude,
)
msc.sanitize(payload, collate=True)
if msc.existing:
if not issubset(msc.sent, msc.existing):
if module.check_mode:
msc.existing = msc.proposed
else:
msc.request(path, method='PUT', data=msc.sent)
msc.existing = msc.request(path, method='PUT', data=msc.sent)
else:
if module.check_mode:
msc.existing = msc.proposed
else:
msc.request(path, method='POST', data=msc.sent)
msc.existing = msc.request(path, method='POST', data=msc.sent)
if 'password' in msc.existing:
msc.existing['password'] = '******'
msc.exit_json()

View File

@@ -24,10 +24,10 @@ options:
description:
- The ID of the tenant.
type: str
required: yes
tenant:
description:
- The name of the tenant.
- Alternative to the name, you can use C(tenant_id).
type: str
required: yes
aliases: [ name, tenant_name ]
@@ -161,14 +161,20 @@ def main():
elif state == 'present':
msc.previous = msc.existing
msc.sanitize(dict(
payload = dict(
description=description,
id=tenant_id,
name=tenant,
displayName=display_name,
siteAssociations=[],
userAssociations=[dict(userId="0000ffff0000000000000020")],
), collate=True)
)
msc.sanitize(payload, collate=True)
# Ensure displayName is not undefined
if msc.sent.get('displayName') is None:
msc.sent['displayName'] = tenant
if msc.existing:
if not issubset(msc.sent, msc.existing):

View File

@@ -23,55 +23,87 @@ options:
user_id:
description:
- The ID of the user.
required: yes
type: str
user:
description:
- The name of the user.
- Alternative to the name, you can use C(user_id).
type: str
required: yes
aliases: [ name, user_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 this user has.
- 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: msc
'''
EXAMPLES = r'''
- name: Update initial admin password
msc_user:
host: msc_host
username: admin
password: we1come!
user_name: admin
user_password: SomeSecretPassword
state: present
delegate_to: localhost
- name: Add a new user
msc_user:
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
user_id: 101
description: North European Datacenter
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
@@ -80,7 +112,7 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
user_name: dag
state: absent
delegate_to: localhost
@@ -89,7 +121,7 @@ EXAMPLES = r'''
host: msc_host
username: admin
password: SomeSecretPassword
name: north_europe
user_name: dag
state: query
delegate_to: localhost
register: query_result
@@ -120,7 +152,7 @@ def main():
last_name=dict(type='str'),
email=dict(type='str'),
phone=dict(type='str'),
# FIXME: What possible options do we have ?
# TODO: What possible options do we have ?
account_status=dict(type='str', choices=['active']),
domain=dict(type='str'),
roles=dict(type='list'),
@@ -132,7 +164,7 @@ def main():
supports_check_mode=True,
required_if=[
['state', 'absent', ['user_name']],
['state', 'present', ['user_name', 'password', 'first_name', 'last_name', 'email', 'phone', 'account_status']],
['state', 'present', ['user_name']],
],
)
@@ -144,18 +176,13 @@ def main():
email = module.params['email']
phone = module.params['phone']
account_status = module.params['account_status']
# FIXME: Look up domain
domain = module.params['domain']
# FIXME: Look up roles
roles = module.params['roles']
roles_dict = list()
if roles:
for role in roles:
roles_dict.append(dict(roleId=role))
state = module.params['state']
msc = MSCModule(module)
roles = msc.lookup_roles(module.params['roles'])
domain = msc.lookup_domain(module.params['domain'])
path = 'users'
# Query for existing object(s)
@@ -191,7 +218,7 @@ def main():
elif state == 'present':
msc.previous = msc.existing
msc.sanitize(dict(
payload = dict(
id=user_id,
username=user_name,
password=user_password,
@@ -199,19 +226,23 @@ def main():
lastName=last_name,
emailAddress=email,
phoneNumber=phone,
# accountStatus={},
accountStatus=account_status,
needsPasswordUpdate=False,
domainId=domain,
roles=roles_dict,
roles=roles,
# active=True,
# remote=True,
), collate=True)
)
msc.sanitize(payload, collate=True)
if msc.sent.get('accountStatus') is None:
msc.sent['accountStatus'] = 'active'
if msc.existing:
if not issubset(msc.sent, msc.existing):
# NOTE: Since MSC always returns '******' as password, we need to assume a change
if 'password' in msc.sent:
if 'password' in msc.proposed:
msc.module.warn("A password change is assumed, as the MSC REST API does not return passwords we do not know.")
msc.result['changed'] = True
if module.check_mode: