mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
1020 lines
41 KiB
Python
1020 lines
41 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2017, Nokia
|
|
# 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 = '''
|
|
---
|
|
module: nuage_vspk
|
|
short_description: Manage Nuage VSP environments
|
|
description:
|
|
- Manage or find Nuage VSP entities, this includes create, update, delete, assign, unassign and find, with all supported properties.
|
|
author: Philippe Dellaert (@pdellaert)
|
|
options:
|
|
auth:
|
|
description:
|
|
- Dict with the authentication information required to connect to a Nuage VSP environment.
|
|
- Requires a I(api_username) parameter (example csproot).
|
|
- Requires either a I(api_password) parameter (example csproot) or a I(api_certificate) and I(api_key) parameters,
|
|
which point to the certificate and key files for certificate based authentication.
|
|
- Requires a I(api_enterprise) parameter (example csp).
|
|
- Requires a I(api_url) parameter (example https://10.0.0.10:8443).
|
|
- Requires a I(api_version) parameter (example v4_0).
|
|
required: true
|
|
type:
|
|
description:
|
|
- The type of entity you want to work on (example Enterprise).
|
|
- This should match the objects CamelCase class name in VSPK-Python.
|
|
- This Class name can be found on U(https://nuagenetworks.github.io/vspkdoc/index.html).
|
|
required: true
|
|
id:
|
|
description:
|
|
- The ID of the entity you want to work on.
|
|
- In combination with I(command=find), it will only return the single entity.
|
|
- In combination with I(state), it will either update or delete this entity.
|
|
- Will take precedence over I(match_filter) and I(properties) whenever an entity needs to be found.
|
|
parent_id:
|
|
description:
|
|
- The ID of the parent of the entity you want to work on.
|
|
- When I(state) is specified, the entity will be gathered from this parent, if it exists, unless an I(id) is specified.
|
|
- When I(command=find) is specified, the entity will be searched for in this parent, unless an I(id) is specified.
|
|
- If specified, I(parent_type) also needs to be specified.
|
|
parent_type:
|
|
description:
|
|
- The type of parent the ID is specified for (example Enterprise).
|
|
- This should match the objects CamelCase class name in VSPK-Python.
|
|
- This Class name can be found on U(https://nuagenetworks.github.io/vspkdoc/index.html).
|
|
- If specified, I(parent_id) also needs to be specified.
|
|
state:
|
|
description:
|
|
- Specifies the desired state of the entity.
|
|
- If I(state=present), in case the entity already exists, will update the entity if it is needed.
|
|
- If I(state=present), in case the relationship with the parent is a member relationship, will assign the entity as a member of the parent.
|
|
- If I(state=absent), in case the relationship with the parent is a member relationship, will unassign the entity as a member of the parent.
|
|
- Either I(state) or I(command) needs to be defined, both can not be defined at the same time.
|
|
choices:
|
|
- present
|
|
- absent
|
|
command:
|
|
description:
|
|
- Specifies a command to be executed.
|
|
- With I(command=find), if I(parent_id) and I(parent_type) are defined, it will only search within the parent. Otherwise, if allowed,
|
|
will search in the root object.
|
|
- With I(command=find), if I(id) is specified, it will only return the single entity matching the id.
|
|
- With I(command=find), otherwise, if I(match_filter) is define, it will use that filter to search.
|
|
- With I(command=find), otherwise, if I(properties) are defined, it will do an AND search using all properties.
|
|
- With I(command=change_password), a password of a user can be changed. Warning - In case the password is the same as the existing,
|
|
it will throw an error.
|
|
- With I(command=wait_for_job), the module will wait for a job to either have a status of SUCCESS or ERROR. In case an ERROR status is found,
|
|
the module will exit with an error.
|
|
- With I(command=wait_for_job), the job will always be returned, even if the state is ERROR situation.
|
|
- Either I(state) or I(command) needs to be defined, both can not be defined at the same time.
|
|
choices:
|
|
- find
|
|
- change_password
|
|
- wait_for_job
|
|
- get_csp_enterprise
|
|
match_filter:
|
|
description:
|
|
- A filter used when looking (both in I(command) and I(state) for entities, in the format the Nuage VSP API expects.
|
|
- If I(match_filter) is defined, it will take precedence over the I(properties), but not on the I(id)
|
|
properties:
|
|
description:
|
|
- Properties are the key, value pairs of the different properties an entity has.
|
|
- If no I(id) and no I(match_filter) is specified, these are used to find or determine if the entity exists.
|
|
children:
|
|
description:
|
|
- Can be used to specify a set of child entities.
|
|
- A mandatory property of each child is the I(type).
|
|
- Supported optional properties of each child are I(id), I(properties) and I(match_filter).
|
|
- The function of each of these properties is the same as in the general task definition.
|
|
- This can be used recursively
|
|
- Only useable in case I(state=present).
|
|
notes:
|
|
- Check mode is supported, but with some caveats. It will not do any changes, and if possible try to determine if it is able do what is requested.
|
|
- In case a parent id is provided from a previous task, it might be empty and if a search is possible on root, it will do so, which can impact performance.
|
|
requirements:
|
|
- Python 2.7
|
|
- Supports Nuage VSP 4.0Rx & 5.x.y
|
|
- Proper VSPK-Python installed for your Nuage version
|
|
- Tested with NuageX U(https://nuagex.io)
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# This can be executed as a single role, with the following vars
|
|
# vars:
|
|
# auth:
|
|
# api_username: csproot
|
|
# api_password: csproot
|
|
# api_enterprise: csp
|
|
# api_url: https://10.0.0.10:8443
|
|
# api_version: v5_0
|
|
# enterprise_name: Ansible-Enterprise
|
|
# enterprise_new_name: Ansible-Updated-Enterprise
|
|
#
|
|
# or, for certificate based authentication
|
|
# vars:
|
|
# auth:
|
|
# api_username: csproot
|
|
# api_certificate: /path/to/user-certificate.pem
|
|
# api_key: /path/to/user-Key.pem
|
|
# api_enterprise: csp
|
|
# api_url: https://10.0.0.10:8443
|
|
# api_version: v5_0
|
|
# enterprise_name: Ansible-Enterprise
|
|
# enterprise_new_name: Ansible-Updated-Enterprise
|
|
|
|
# Creating a new enterprise
|
|
- name: Create Enterprise
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: Enterprise
|
|
state: present
|
|
properties:
|
|
name: "{{ enterprise_name }}-basic"
|
|
register: nuage_enterprise
|
|
|
|
# Checking if an Enterprise with the new name already exists
|
|
- name: Check if an Enterprise exists with the new name
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: Enterprise
|
|
command: find
|
|
properties:
|
|
name: "{{ enterprise_new_name }}-basic"
|
|
ignore_errors: yes
|
|
register: nuage_check_enterprise
|
|
|
|
# Updating an enterprise's name
|
|
- name: Update Enterprise name
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: Enterprise
|
|
id: "{{ nuage_enterprise.id }}"
|
|
state: present
|
|
properties:
|
|
name: "{{ enterprise_new_name }}-basic"
|
|
when: nuage_check_enterprise is failed
|
|
|
|
# Creating a User in an Enterprise
|
|
- name: Create admin user
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: User
|
|
parent_id: "{{ nuage_enterprise.id }}"
|
|
parent_type: Enterprise
|
|
state: present
|
|
match_filter: "userName == 'ansible-admin'"
|
|
properties:
|
|
email: "ansible@localhost.local"
|
|
first_name: "Ansible"
|
|
last_name: "Admin"
|
|
password: "ansible-password"
|
|
user_name: "ansible-admin"
|
|
register: nuage_user
|
|
|
|
# Updating password for User
|
|
- name: Update admin password
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: User
|
|
id: "{{ nuage_user.id }}"
|
|
command: change_password
|
|
properties:
|
|
password: "ansible-new-password"
|
|
ignore_errors: yes
|
|
|
|
# Finding a group in an enterprise
|
|
- name: Find Administrators group in Enterprise
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: Group
|
|
parent_id: "{{ nuage_enterprise.id }}"
|
|
parent_type: Enterprise
|
|
command: find
|
|
properties:
|
|
name: "Administrators"
|
|
register: nuage_group
|
|
|
|
# Assign the user to the group
|
|
- name: Assign admin user to administrators
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: User
|
|
id: "{{ nuage_user.id }}"
|
|
parent_id: "{{ nuage_group.id }}"
|
|
parent_type: Group
|
|
state: present
|
|
|
|
# Creating multiple DomainTemplates
|
|
- name: Create multiple DomainTemplates
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: DomainTemplate
|
|
parent_id: "{{ nuage_enterprise.id }}"
|
|
parent_type: Enterprise
|
|
state: present
|
|
properties:
|
|
name: "{{ item }}"
|
|
description: "Created by Ansible"
|
|
with_items:
|
|
- "Template-1"
|
|
- "Template-2"
|
|
|
|
# Finding all DomainTemplates
|
|
- name: Fetching all DomainTemplates
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: DomainTemplate
|
|
parent_id: "{{ nuage_enterprise.id }}"
|
|
parent_type: Enterprise
|
|
command: find
|
|
register: nuage_domain_templates
|
|
|
|
# Deleting all DomainTemplates
|
|
- name: Deleting all found DomainTemplates
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: DomainTemplate
|
|
state: absent
|
|
id: "{{ item.ID }}"
|
|
with_items: "{{ nuage_domain_templates.entities }}"
|
|
when: nuage_domain_templates.entities is defined
|
|
|
|
# Unassign user from group
|
|
- name: Unassign admin user to administrators
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: User
|
|
id: "{{ nuage_user.id }}"
|
|
parent_id: "{{ nuage_group.id }}"
|
|
parent_type: Group
|
|
state: absent
|
|
|
|
# Deleting an enterprise
|
|
- name: Delete Enterprise
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: Enterprise
|
|
id: "{{ nuage_enterprise.id }}"
|
|
state: absent
|
|
|
|
# Setup an enterprise with Children
|
|
- name: Setup Enterprise and domain structure
|
|
connection: local
|
|
nuage_vspk:
|
|
auth: "{{ nuage_auth }}"
|
|
type: Enterprise
|
|
state: present
|
|
properties:
|
|
name: "Child-based-Enterprise"
|
|
children:
|
|
- type: L2DomainTemplate
|
|
properties:
|
|
name: "Unmanaged-Template"
|
|
children:
|
|
- type: EgressACLTemplate
|
|
match_filter: "name == 'Allow All'"
|
|
properties:
|
|
name: "Allow All"
|
|
active: true
|
|
default_allow_ip: true
|
|
default_allow_non_ip: true
|
|
default_install_acl_implicit_rules: true
|
|
description: "Created by Ansible"
|
|
priority_type: "TOP"
|
|
- type: IngressACLTemplate
|
|
match_filter: "name == 'Allow All'"
|
|
properties:
|
|
name: "Allow All"
|
|
active: true
|
|
default_allow_ip: true
|
|
default_allow_non_ip: true
|
|
description: "Created by Ansible"
|
|
priority_type: "TOP"
|
|
'''
|
|
|
|
RETURN = '''
|
|
id:
|
|
description: The id of the entity that was found, created, updated or assigned.
|
|
returned: On state=present and command=find in case one entity was found.
|
|
type: str
|
|
sample: bae07d8d-d29c-4e2b-b6ba-621b4807a333
|
|
entities:
|
|
description: A list of entities handled. Each element is the to_dict() of the entity.
|
|
returned: On state=present and find, with only one element in case of state=present or find=one.
|
|
type: list
|
|
sample: [{
|
|
"ID": acabc435-3946-4117-a719-b8895a335830",
|
|
"assocEntityType": "DOMAIN",
|
|
"command": "BEGIN_POLICY_CHANGES",
|
|
"creationDate": 1487515656000,
|
|
"entityScope": "ENTERPRISE",
|
|
"externalID": null,
|
|
"lastUpdatedBy": "8a6f0e20-a4db-4878-ad84-9cc61756cd5e",
|
|
"lastUpdatedDate": 1487515656000,
|
|
"owner": "8a6f0e20-a4db-4878-ad84-9cc61756cd5e",
|
|
"parameters": null,
|
|
"parentID": "a22fddb9-3da4-4945-bd2e-9d27fe3d62e0",
|
|
"parentType": "domain",
|
|
"progress": 0.0,
|
|
"result": null,
|
|
"status": "RUNNING"
|
|
}]
|
|
'''
|
|
|
|
import time
|
|
|
|
try:
|
|
import importlib
|
|
HAS_IMPORTLIB = True
|
|
except ImportError:
|
|
HAS_IMPORTLIB = False
|
|
|
|
try:
|
|
from bambou.exceptions import BambouHTTPError
|
|
HAS_BAMBOU = True
|
|
except ImportError:
|
|
HAS_BAMBOU = False
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
SUPPORTED_COMMANDS = ['find', 'change_password', 'wait_for_job', 'get_csp_enterprise']
|
|
VSPK = None
|
|
|
|
|
|
class NuageEntityManager(object):
|
|
"""
|
|
This module is meant to manage an entity in a Nuage VSP Platform
|
|
"""
|
|
|
|
def __init__(self, module):
|
|
self.module = module
|
|
self.auth = module.params['auth']
|
|
self.api_username = None
|
|
self.api_password = None
|
|
self.api_enterprise = None
|
|
self.api_url = None
|
|
self.api_version = None
|
|
self.api_certificate = None
|
|
self.api_key = None
|
|
self.type = module.params['type']
|
|
|
|
self.state = module.params['state']
|
|
self.command = module.params['command']
|
|
self.match_filter = module.params['match_filter']
|
|
self.entity_id = module.params['id']
|
|
self.parent_id = module.params['parent_id']
|
|
self.parent_type = module.params['parent_type']
|
|
self.properties = module.params['properties']
|
|
self.children = module.params['children']
|
|
|
|
self.entity = None
|
|
self.entity_class = None
|
|
self.parent = None
|
|
self.parent_class = None
|
|
self.entity_fetcher = None
|
|
|
|
self.result = {
|
|
'state': self.state,
|
|
'id': self.entity_id,
|
|
'entities': []
|
|
}
|
|
self.nuage_connection = None
|
|
|
|
self._verify_api()
|
|
self._verify_input()
|
|
self._connect_vspk()
|
|
self._find_parent()
|
|
|
|
def _connect_vspk(self):
|
|
"""
|
|
Connects to a Nuage API endpoint
|
|
"""
|
|
try:
|
|
# Connecting to Nuage
|
|
if self.api_certificate and self.api_key:
|
|
self.nuage_connection = VSPK.NUVSDSession(username=self.api_username, enterprise=self.api_enterprise, api_url=self.api_url,
|
|
certificate=(self.api_certificate, self.api_key))
|
|
else:
|
|
self.nuage_connection = VSPK.NUVSDSession(username=self.api_username, password=self.api_password, enterprise=self.api_enterprise,
|
|
api_url=self.api_url)
|
|
self.nuage_connection.start()
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Unable to connect to the API URL with given username, password and enterprise: {0}'.format(error))
|
|
|
|
def _verify_api(self):
|
|
"""
|
|
Verifies the API and loads the proper VSPK version
|
|
"""
|
|
# Checking auth parameters
|
|
if ('api_password' not in list(self.auth.keys()) or not self.auth['api_password']) and ('api_certificate' not in list(self.auth.keys()) or
|
|
'api_key' not in list(self.auth.keys()) or
|
|
not self.auth['api_certificate'] or not self.auth['api_key']):
|
|
self.module.fail_json(msg='Missing api_password or api_certificate and api_key parameter in auth')
|
|
|
|
self.api_username = self.auth['api_username']
|
|
if 'api_password' in list(self.auth.keys()) and self.auth['api_password']:
|
|
self.api_password = self.auth['api_password']
|
|
if 'api_certificate' in list(self.auth.keys()) and 'api_key' in list(self.auth.keys()) and self.auth['api_certificate'] and self.auth['api_key']:
|
|
self.api_certificate = self.auth['api_certificate']
|
|
self.api_key = self.auth['api_key']
|
|
self.api_enterprise = self.auth['api_enterprise']
|
|
self.api_url = self.auth['api_url']
|
|
self.api_version = self.auth['api_version']
|
|
|
|
try:
|
|
global VSPK
|
|
VSPK = importlib.import_module('vspk.{0:s}'.format(self.api_version))
|
|
except ImportError:
|
|
self.module.fail_json(msg='vspk is required for this module, or the API version specified does not exist.')
|
|
|
|
def _verify_input(self):
|
|
"""
|
|
Verifies the parameter input for types and parent correctness and necessary parameters
|
|
"""
|
|
|
|
# Checking if type exists
|
|
try:
|
|
self.entity_class = getattr(VSPK, 'NU{0:s}'.format(self.type))
|
|
except AttributeError:
|
|
self.module.fail_json(msg='Unrecognised type specified')
|
|
|
|
if self.module.check_mode:
|
|
return
|
|
|
|
if self.parent_type:
|
|
# Checking if parent type exists
|
|
try:
|
|
self.parent_class = getattr(VSPK, 'NU{0:s}'.format(self.parent_type))
|
|
except AttributeError:
|
|
# The parent type does not exist, fail
|
|
self.module.fail_json(msg='Unrecognised parent type specified')
|
|
|
|
fetcher = self.parent_class().fetcher_for_rest_name(self.entity_class.rest_name)
|
|
if fetcher is None:
|
|
# The parent has no fetcher, fail
|
|
self.module.fail_json(msg='Specified parent is not a valid parent for the specified type')
|
|
elif not self.entity_id:
|
|
# If there is an id, we do not need a parent because we'll interact directly with the entity
|
|
# If an assign needs to happen, a parent will have to be provided
|
|
# Root object is the parent
|
|
self.parent_class = VSPK.NUMe
|
|
fetcher = self.parent_class().fetcher_for_rest_name(self.entity_class.rest_name)
|
|
if fetcher is None:
|
|
self.module.fail_json(msg='No parent specified and root object is not a parent for the type')
|
|
|
|
# Verifying if a password is provided in case of the change_password command:
|
|
if self.command and self.command == 'change_password' and 'password' not in self.properties.keys():
|
|
self.module.fail_json(msg='command is change_password but the following are missing: password property')
|
|
|
|
def _find_parent(self):
|
|
"""
|
|
Fetches the parent if needed, otherwise configures the root object as parent. Also configures the entity fetcher
|
|
Important notes:
|
|
- If the parent is not set, the parent is automatically set to the root object
|
|
- It the root object does not hold a fetcher for the entity, you have to provide an ID
|
|
- If you want to assign/unassign, you have to provide a valid parent
|
|
"""
|
|
self.parent = self.nuage_connection.user
|
|
|
|
if self.parent_id:
|
|
self.parent = self.parent_class(id=self.parent_id)
|
|
try:
|
|
self.parent.fetch()
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Failed to fetch the specified parent: {0}'.format(error))
|
|
|
|
self.entity_fetcher = self.parent.fetcher_for_rest_name(self.entity_class.rest_name)
|
|
|
|
def _find_entities(self, entity_id=None, entity_class=None, match_filter=None, properties=None, entity_fetcher=None):
|
|
"""
|
|
Will return a set of entities matching a filter or set of properties if the match_filter is unset. If the
|
|
entity_id is set, it will return only the entity matching that ID as the single element of the list.
|
|
:param entity_id: Optional ID of the entity which should be returned
|
|
:param entity_class: Optional class of the entity which needs to be found
|
|
:param match_filter: Optional search filter
|
|
:param properties: Optional set of properties the entities should contain
|
|
:param entity_fetcher: The fetcher for the entity type
|
|
:return: List of matching entities
|
|
"""
|
|
search_filter = ''
|
|
|
|
if entity_id:
|
|
found_entity = entity_class(id=entity_id)
|
|
try:
|
|
found_entity.fetch()
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Failed to fetch the specified entity by ID: {0}'.format(error))
|
|
|
|
return [found_entity]
|
|
|
|
elif match_filter:
|
|
search_filter = match_filter
|
|
elif properties:
|
|
# Building filter
|
|
for num, property_name in enumerate(properties):
|
|
if num > 0:
|
|
search_filter += ' and '
|
|
search_filter += '{0:s} == "{1}"'.format(property_name, properties[property_name])
|
|
|
|
if entity_fetcher is not None:
|
|
try:
|
|
return entity_fetcher.get(filter=search_filter)
|
|
except BambouHTTPError:
|
|
pass
|
|
return []
|
|
|
|
def _find_entity(self, entity_id=None, entity_class=None, match_filter=None, properties=None, entity_fetcher=None):
|
|
"""
|
|
Finds a single matching entity that matches all the provided properties, unless an ID is specified, in which
|
|
case it just fetches the one item
|
|
:param entity_id: Optional ID of the entity which should be returned
|
|
:param entity_class: Optional class of the entity which needs to be found
|
|
:param match_filter: Optional search filter
|
|
:param properties: Optional set of properties the entities should contain
|
|
:param entity_fetcher: The fetcher for the entity type
|
|
:return: The first entity matching the criteria, or None if none was found
|
|
"""
|
|
search_filter = ''
|
|
if entity_id:
|
|
found_entity = entity_class(id=entity_id)
|
|
try:
|
|
found_entity.fetch()
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Failed to fetch the specified entity by ID: {0}'.format(error))
|
|
|
|
return found_entity
|
|
|
|
elif match_filter:
|
|
search_filter = match_filter
|
|
elif properties:
|
|
# Building filter
|
|
for num, property_name in enumerate(properties):
|
|
if num > 0:
|
|
search_filter += ' and '
|
|
search_filter += '{0:s} == "{1}"'.format(property_name, properties[property_name])
|
|
|
|
if entity_fetcher is not None:
|
|
try:
|
|
return entity_fetcher.get_first(filter=search_filter)
|
|
except BambouHTTPError:
|
|
pass
|
|
return None
|
|
|
|
def handle_main_entity(self):
|
|
"""
|
|
Handles the Ansible task
|
|
"""
|
|
if self.command and self.command == 'find':
|
|
self._handle_find()
|
|
elif self.command and self.command == 'change_password':
|
|
self._handle_change_password()
|
|
elif self.command and self.command == 'wait_for_job':
|
|
self._handle_wait_for_job()
|
|
elif self.command and self.command == 'get_csp_enterprise':
|
|
self._handle_get_csp_enterprise()
|
|
elif self.state == 'present':
|
|
self._handle_present()
|
|
elif self.state == 'absent':
|
|
self._handle_absent()
|
|
self.module.exit_json(**self.result)
|
|
|
|
def _handle_absent(self):
|
|
"""
|
|
Handles the Ansible task when the state is set to absent
|
|
"""
|
|
# Absent state
|
|
self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties,
|
|
entity_fetcher=self.entity_fetcher)
|
|
if self.entity and (self.entity_fetcher is None or self.entity_fetcher.relationship in ['child', 'root']):
|
|
# Entity is present, deleting
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
self._delete_entity(self.entity)
|
|
self.result['id'] = None
|
|
elif self.entity and self.entity_fetcher.relationship == 'member':
|
|
# Entity is a member, need to check if already present
|
|
if self._is_member(entity_fetcher=self.entity_fetcher, entity=self.entity):
|
|
# Entity is not a member yet
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
self._unassign_member(entity_fetcher=self.entity_fetcher, entity=self.entity, entity_class=self.entity_class, parent=self.parent,
|
|
set_output=True)
|
|
|
|
def _handle_present(self):
|
|
"""
|
|
Handles the Ansible task when the state is set to present
|
|
"""
|
|
# Present state
|
|
self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties,
|
|
entity_fetcher=self.entity_fetcher)
|
|
# Determining action to take
|
|
if self.entity_fetcher is not None and self.entity_fetcher.relationship == 'member' and not self.entity:
|
|
self.module.fail_json(msg='Trying to assign an entity that does not exist')
|
|
elif self.entity_fetcher is not None and self.entity_fetcher.relationship == 'member' and self.entity:
|
|
# Entity is a member, need to check if already present
|
|
if not self._is_member(entity_fetcher=self.entity_fetcher, entity=self.entity):
|
|
# Entity is not a member yet
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
self._assign_member(entity_fetcher=self.entity_fetcher, entity=self.entity, entity_class=self.entity_class, parent=self.parent,
|
|
set_output=True)
|
|
elif self.entity_fetcher is not None and self.entity_fetcher.relationship in ['child', 'root'] and not self.entity:
|
|
# Entity is not present as a child, creating
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
self.entity = self._create_entity(entity_class=self.entity_class, parent=self.parent, properties=self.properties)
|
|
self.result['id'] = self.entity.id
|
|
self.result['entities'].append(self.entity.to_dict())
|
|
|
|
# Checking children
|
|
if self.children:
|
|
for child in self.children:
|
|
self._handle_child(child=child, parent=self.entity)
|
|
elif self.entity:
|
|
# Need to compare properties in entity and found entity
|
|
changed = self._has_changed(entity=self.entity, properties=self.properties)
|
|
|
|
if self.module.check_mode:
|
|
self.result['changed'] = changed
|
|
elif changed:
|
|
self.entity = self._save_entity(entity=self.entity)
|
|
self.result['id'] = self.entity.id
|
|
self.result['entities'].append(self.entity.to_dict())
|
|
else:
|
|
self.result['id'] = self.entity.id
|
|
self.result['entities'].append(self.entity.to_dict())
|
|
|
|
# Checking children
|
|
if self.children:
|
|
for child in self.children:
|
|
self._handle_child(child=child, parent=self.entity)
|
|
elif not self.module.check_mode:
|
|
self.module.fail_json(msg='Invalid situation, verify parameters')
|
|
|
|
def _handle_get_csp_enterprise(self):
|
|
"""
|
|
Handles the Ansible task when the command is to get the csp enterprise
|
|
"""
|
|
self.entity_id = self.parent.enterprise_id
|
|
self.entity = VSPK.NUEnterprise(id=self.entity_id)
|
|
try:
|
|
self.entity.fetch()
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Unable to fetch CSP enterprise: {0}'.format(error))
|
|
self.result['id'] = self.entity_id
|
|
self.result['entities'].append(self.entity.to_dict())
|
|
|
|
def _handle_wait_for_job(self):
|
|
"""
|
|
Handles the Ansible task when the command is to wait for a job
|
|
"""
|
|
# Command wait_for_job
|
|
self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties,
|
|
entity_fetcher=self.entity_fetcher)
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
self._wait_for_job(self.entity)
|
|
|
|
def _handle_change_password(self):
|
|
"""
|
|
Handles the Ansible task when the command is to change a password
|
|
"""
|
|
# Command change_password
|
|
self.entity = self._find_entity(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties,
|
|
entity_fetcher=self.entity_fetcher)
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
try:
|
|
getattr(self.entity, 'password')
|
|
except AttributeError:
|
|
self.module.fail_json(msg='Entity does not have a password property')
|
|
|
|
try:
|
|
setattr(self.entity, 'password', self.properties['password'])
|
|
except AttributeError:
|
|
self.module.fail_json(msg='Password can not be changed for entity')
|
|
|
|
self.entity = self._save_entity(entity=self.entity)
|
|
self.result['id'] = self.entity.id
|
|
self.result['entities'].append(self.entity.to_dict())
|
|
|
|
def _handle_find(self):
|
|
"""
|
|
Handles the Ansible task when the command is to find an entity
|
|
"""
|
|
# Command find
|
|
entities = self._find_entities(entity_id=self.entity_id, entity_class=self.entity_class, match_filter=self.match_filter, properties=self.properties,
|
|
entity_fetcher=self.entity_fetcher)
|
|
self.result['changed'] = False
|
|
if entities:
|
|
if len(entities) == 1:
|
|
self.result['id'] = entities[0].id
|
|
for entity in entities:
|
|
self.result['entities'].append(entity.to_dict())
|
|
elif not self.module.check_mode:
|
|
self.module.fail_json(msg='Unable to find matching entries')
|
|
|
|
def _handle_child(self, child, parent):
|
|
"""
|
|
Handles children of a main entity. Fields are similar to the normal fields
|
|
Currently only supported state: present
|
|
"""
|
|
if 'type' not in list(child.keys()):
|
|
self.module.fail_json(msg='Child type unspecified')
|
|
elif 'id' not in list(child.keys()) and 'properties' not in list(child.keys()):
|
|
self.module.fail_json(msg='Child ID or properties unspecified')
|
|
|
|
# Setting intern variables
|
|
child_id = None
|
|
if 'id' in list(child.keys()):
|
|
child_id = child['id']
|
|
child_properties = None
|
|
if 'properties' in list(child.keys()):
|
|
child_properties = child['properties']
|
|
child_filter = None
|
|
if 'match_filter' in list(child.keys()):
|
|
child_filter = child['match_filter']
|
|
|
|
# Checking if type exists
|
|
entity_class = None
|
|
try:
|
|
entity_class = getattr(VSPK, 'NU{0:s}'.format(child['type']))
|
|
except AttributeError:
|
|
self.module.fail_json(msg='Unrecognised child type specified')
|
|
|
|
entity_fetcher = parent.fetcher_for_rest_name(entity_class.rest_name)
|
|
if entity_fetcher is None and not child_id and not self.module.check_mode:
|
|
self.module.fail_json(msg='Unable to find a fetcher for child, and no ID specified.')
|
|
|
|
# Try and find the child
|
|
entity = self._find_entity(entity_id=child_id, entity_class=entity_class, match_filter=child_filter, properties=child_properties,
|
|
entity_fetcher=entity_fetcher)
|
|
|
|
# Determining action to take
|
|
if entity_fetcher.relationship == 'member' and not entity:
|
|
self.module.fail_json(msg='Trying to assign a child that does not exist')
|
|
elif entity_fetcher.relationship == 'member' and entity:
|
|
# Entity is a member, need to check if already present
|
|
if not self._is_member(entity_fetcher=entity_fetcher, entity=entity):
|
|
# Entity is not a member yet
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
self._assign_member(entity_fetcher=entity_fetcher, entity=entity, entity_class=entity_class, parent=parent, set_output=False)
|
|
elif entity_fetcher.relationship in ['child', 'root'] and not entity:
|
|
# Entity is not present as a child, creating
|
|
if self.module.check_mode:
|
|
self.result['changed'] = True
|
|
else:
|
|
entity = self._create_entity(entity_class=entity_class, parent=parent, properties=child_properties)
|
|
elif entity_fetcher.relationship in ['child', 'root'] and entity:
|
|
changed = self._has_changed(entity=entity, properties=child_properties)
|
|
|
|
if self.module.check_mode:
|
|
self.result['changed'] = changed
|
|
elif changed:
|
|
entity = self._save_entity(entity=entity)
|
|
|
|
if entity:
|
|
self.result['entities'].append(entity.to_dict())
|
|
|
|
# Checking children
|
|
if 'children' in list(child.keys()) and not self.module.check_mode:
|
|
for subchild in child['children']:
|
|
self._handle_child(child=subchild, parent=entity)
|
|
|
|
def _has_changed(self, entity, properties):
|
|
"""
|
|
Compares a set of properties with a given entity, returns True in case the properties are different from the
|
|
values in the entity
|
|
:param entity: The entity to check
|
|
:param properties: The properties to check
|
|
:return: boolean
|
|
"""
|
|
# Need to compare properties in entity and found entity
|
|
changed = False
|
|
if properties:
|
|
for property_name in list(properties.keys()):
|
|
if property_name == 'password':
|
|
continue
|
|
entity_value = ''
|
|
try:
|
|
entity_value = getattr(entity, property_name)
|
|
except AttributeError:
|
|
self.module.fail_json(msg='Property {0:s} is not valid for this type of entity'.format(property_name))
|
|
|
|
if entity_value != properties[property_name]:
|
|
# Difference in values changing property
|
|
changed = True
|
|
try:
|
|
setattr(entity, property_name, properties[property_name])
|
|
except AttributeError:
|
|
self.module.fail_json(msg='Property {0:s} can not be changed for this type of entity'.format(property_name))
|
|
return changed
|
|
|
|
def _is_member(self, entity_fetcher, entity):
|
|
"""
|
|
Verifies if the entity is a member of the parent in the fetcher
|
|
:param entity_fetcher: The fetcher for the entity type
|
|
:param entity: The entity to look for as a member in the entity fetcher
|
|
:return: boolean
|
|
"""
|
|
members = entity_fetcher.get()
|
|
for member in members:
|
|
if member.id == entity.id:
|
|
return True
|
|
return False
|
|
|
|
def _assign_member(self, entity_fetcher, entity, entity_class, parent, set_output):
|
|
"""
|
|
Adds the entity as a member to a parent
|
|
:param entity_fetcher: The fetcher of the entity type
|
|
:param entity: The entity to add as a member
|
|
:param entity_class: The class of the entity
|
|
:param parent: The parent on which to add the entity as a member
|
|
:param set_output: If set to True, sets the Ansible result variables
|
|
"""
|
|
members = entity_fetcher.get()
|
|
members.append(entity)
|
|
try:
|
|
parent.assign(members, entity_class)
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Unable to assign entity as a member: {0}'.format(error))
|
|
self.result['changed'] = True
|
|
if set_output:
|
|
self.result['id'] = entity.id
|
|
self.result['entities'].append(entity.to_dict())
|
|
|
|
def _unassign_member(self, entity_fetcher, entity, entity_class, parent, set_output):
|
|
"""
|
|
Removes the entity as a member of a parent
|
|
:param entity_fetcher: The fetcher of the entity type
|
|
:param entity: The entity to remove as a member
|
|
:param entity_class: The class of the entity
|
|
:param parent: The parent on which to add the entity as a member
|
|
:param set_output: If set to True, sets the Ansible result variables
|
|
"""
|
|
members = []
|
|
for member in entity_fetcher.get():
|
|
if member.id != entity.id:
|
|
members.append(member)
|
|
try:
|
|
parent.assign(members, entity_class)
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Unable to remove entity as a member: {0}'.format(error))
|
|
self.result['changed'] = True
|
|
if set_output:
|
|
self.result['id'] = entity.id
|
|
self.result['entities'].append(entity.to_dict())
|
|
|
|
def _create_entity(self, entity_class, parent, properties):
|
|
"""
|
|
Creates a new entity in the parent, with all properties configured as in the file
|
|
:param entity_class: The class of the entity
|
|
:param parent: The parent of the entity
|
|
:param properties: The set of properties of the entity
|
|
:return: The entity
|
|
"""
|
|
entity = entity_class(**properties)
|
|
try:
|
|
parent.create_child(entity)
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Unable to create entity: {0}'.format(error))
|
|
self.result['changed'] = True
|
|
return entity
|
|
|
|
def _save_entity(self, entity):
|
|
"""
|
|
Updates an existing entity
|
|
:param entity: The entity to save
|
|
:return: The updated entity
|
|
"""
|
|
try:
|
|
entity.save()
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Unable to update entity: {0}'.format(error))
|
|
self.result['changed'] = True
|
|
return entity
|
|
|
|
def _delete_entity(self, entity):
|
|
"""
|
|
Deletes an entity
|
|
:param entity: The entity to delete
|
|
"""
|
|
try:
|
|
entity.delete()
|
|
except BambouHTTPError as error:
|
|
self.module.fail_json(msg='Unable to delete entity: {0}'.format(error))
|
|
self.result['changed'] = True
|
|
|
|
def _wait_for_job(self, entity):
|
|
"""
|
|
Waits for a job to finish
|
|
:param entity: The job to wait for
|
|
"""
|
|
running = False
|
|
if entity.status == 'RUNNING':
|
|
self.result['changed'] = True
|
|
running = True
|
|
|
|
while running:
|
|
time.sleep(1)
|
|
entity.fetch()
|
|
|
|
if entity.status != 'RUNNING':
|
|
running = False
|
|
|
|
self.result['entities'].append(entity.to_dict())
|
|
if entity.status == 'ERROR':
|
|
self.module.fail_json(msg='Job ended in an error')
|
|
|
|
|
|
def main():
|
|
"""
|
|
Main method
|
|
"""
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
auth=dict(
|
|
required=True,
|
|
type='dict',
|
|
options=dict(
|
|
api_username=dict(required=True, type='str'),
|
|
api_enterprise=dict(required=True, type='str'),
|
|
api_url=dict(required=True, type='str'),
|
|
api_version=dict(required=True, type='str'),
|
|
api_password=dict(default=None, required=False, type='str', no_log=True),
|
|
api_certificate=dict(default=None, required=False, type='str', no_log=True),
|
|
api_key=dict(default=None, required=False, type='str', no_log=True)
|
|
)
|
|
),
|
|
type=dict(required=True, type='str'),
|
|
id=dict(default=None, required=False, type='str'),
|
|
parent_id=dict(default=None, required=False, type='str'),
|
|
parent_type=dict(default=None, required=False, type='str'),
|
|
state=dict(default=None, choices=['present', 'absent'], type='str'),
|
|
command=dict(default=None, choices=SUPPORTED_COMMANDS, type='str'),
|
|
match_filter=dict(default=None, required=False, type='str'),
|
|
properties=dict(default=None, required=False, type='dict'),
|
|
children=dict(default=None, required=False, type='list')
|
|
),
|
|
mutually_exclusive=[
|
|
['command', 'state']
|
|
],
|
|
required_together=[
|
|
['parent_id', 'parent_type']
|
|
],
|
|
required_one_of=[
|
|
['command', 'state']
|
|
],
|
|
required_if=[
|
|
['state', 'present', ['id', 'properties', 'match_filter'], True],
|
|
['state', 'absent', ['id', 'properties', 'match_filter'], True],
|
|
['command', 'change_password', ['id', 'properties']],
|
|
['command', 'wait_for_job', ['id']]
|
|
],
|
|
supports_check_mode=True
|
|
)
|
|
|
|
if not HAS_BAMBOU:
|
|
module.fail_json(msg='bambou is required for this module')
|
|
|
|
if not HAS_IMPORTLIB:
|
|
module.fail_json(msg='importlib (python 2.7) is required for this module')
|
|
|
|
entity_manager = NuageEntityManager(module)
|
|
entity_manager.handle_main_entity()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|