Add httpapi VMware REST VmwareRestModule (#60914)

* Initial commit of VMware HttpApi REST

New directory structure created for httpapi-based modules. These will
live in the 'vmware_httpapi' directory under 'modules/cloud'.  The
AnsibleModule class was extended to create the VmwareRestModule class
that lives in the 'vmware_httpapi' directory under 'module_utils'. It
implements comms with the httpapi plugin, and also provides debugging
output, url and filtering generation by object, and support for
multiple VMware REST APIs. It also provides dynamic handling of HTTP
return codes that can be tailored to each module's needs.
This commit is contained in:
n3pjk 2019-09-13 14:34:11 -04:00 committed by Gonéri Le Bouder
parent 5be0668fb0
commit 05ded87848
12 changed files with 1492 additions and 0 deletions

View file

@ -0,0 +1,708 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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
import re
import sys
from ansible.module_utils.connection import Connection
from ansible.module_utils.basic import AnsibleModule, env_fallback
# VMware ReST APIs
#
# Describes each supported VMware ReST APIs and lists its base URL. All
# vSphere ReST APIs begin with '/rest'.
API = dict(
appliance=dict(base='/rest/appliance'),
cis=dict(base='/rest/com/vmware/cis'),
content=dict(base='/rest/com/vmware/content'),
vapi=dict(base='/rest'),
vcenter=dict(base='/rest/vcenter'),
vrops=dict(base='/suiteapi')
)
# Query Filters
#
# This dictionary identifies every valid filter that can be applied to a
# vSphere ReST API query. Each filter has a name, which may be the same
# depending on the type object; an id of the value specified; a type,
# which is typically either a string or a list. If it is a string, the
# format of the expected values is provided as a regex.
FILTER = dict(
clusters=dict(
name='clusters',
id='id',
type='str',
format=r'domain\-[0-9a-fA-F]+',
),
connection_states=dict(
name='connection_states',
id='connection state',
type='list',
choices=[
'CONNECTED',
'DISCONNECTED',
'NOT_RESPONDING',
],
),
datacenters=dict(
name='datacenters',
id='id',
type='str',
format=r'datacenter\-[0-9a-fA-F]+',
),
datastore_types=dict(
name='types',
id='type',
type='list',
choices=[
'',
'CIFS',
'NFS',
'NFS41',
'VFFS',
'VMFS',
'VSAN',
'VVOL',
]
),
datastores=dict(
name='datastores',
id='id',
type='str',
format=r'datastore\-[0-9a-fA-F]+',
),
folder_types=dict(
name='type',
id='type',
type='list',
choices=[
'',
'DATACENTER',
'DATASTORE',
'HOST',
'NETWORK',
'VIRTUAL_MACHINE',
]
),
folders=dict(
name='folders',
id='id',
type='str',
format=r'group\-[hnv][0-9a-fA-F]+',
),
hosts=dict(
name='hosts',
id='id',
type='str',
format=r'host\-[0-9a-fA-F]+',
),
names=dict(
name='names',
id='name',
type='str',
format=r'.+',
),
network_types=dict(
name='types',
id='type',
type='list',
choices=[
'DISTRIBUTED_PORTGROUP',
'OPAQUE_NETWORK',
'STANDARD_PORTGROUP',
],
),
networks=dict(
name='networks',
id='id',
type='str',
format=r'[dvportgroup|network]\-[0-9a-fA-F]+',
),
parent_folders=dict(
name='parent_folders',
id='id',
type='str',
format=r'group\-[hnv][0-9a-fA-F]+',
),
parent_resource_pools=dict(
name='parent_resource_pools',
id='id',
type='str',
format=r'resgroup\-[0-9a-fA-F]+',
),
policies=dict(
name='policies',
id='GUID',
type='str',
format=(r'[0-9a-fA-F]{8}'
r'\-[0-9a-fA-F]{4}'
r'\-[0-9a-fA-F]{4}'
r'\-[0-9a-fA-F]{4}'
r'\-[0-9a-fA-F]{12}'),
),
power_states=dict(
name='power_states',
id='power state',
type='list',
choices=[
'',
'POWERED_OFF',
'POWERED_ON',
'SUSPENDED',
],
),
resource_pools=dict(
name='resource_pools',
id='id',
type='str',
format=r'resgroup\-[0-9a-fA-F]+',
),
status=dict(
name='status',
id='status',
type='list',
choices=[
'COMPLIANT',
'NON_COMPLIANT',
'NOT_APPLICABLE',
'UNKNOWN',
'UNKNOWN_COMPLIANCE',
'OUT_OF_DATE',
],
),
vms=dict(
name='vms',
id='id',
type='str',
format=r'vm\-[0-9a-fA-F]+',
),
)
# vSphere Inventory Objects
#
# This dictionary lists the queryable vSphere inventory objects. Each
# object identifies the API it is managed through, its URL off of the
# API's base, and a list of filters that are valid for this particular
# object.
#
# NOTE: This will be replaced with a class factory pattern as get_id()
# and the get_url() family are tied to this structure.
INVENTORY = dict(
category=dict(
api='cis',
url='/tagging/category',
filters=[],
),
cluster=dict(
api='vcenter',
url='/cluster',
filters=[
'clusters',
'datacenters',
'folders',
'names',
],
),
content_library=dict(
api='content',
url='/library',
filters=[],
),
content_type=dict(
api='content',
url='/type',
filters=[],
),
datacenter=dict(
api='vcenter',
url='/datacenter',
filters=[
'datacenters',
'folders',
'names',
],
),
datastore=dict(
api='vcenter',
url='/datastore',
filters=[
'datacenters',
'datastore_types',
'datastores',
'folders',
'names',
],
),
folder=dict(
api='vcenter',
url='/folder',
filters=[
'datacenters',
'folder_types',
'folders',
'names',
'parent_folders',
],
),
host=dict(
api='vcenter',
url='/host',
filters=[
'clusters',
'connection_states',
'datacenters',
'folders',
'hosts',
'names',
],
),
local_library=dict(
api='content',
url='/local-library',
filters=[],
),
network=dict(
api='vcenter',
url='/network',
filters=[
'datacenters',
'folders',
'names',
'network_types',
'networks',
],
),
resource_pool=dict(
api='vcenter',
url='/resource-pool',
filters=[
'clusters',
'datacenters',
'hosts',
'names',
'parent_resource_pools',
'resource_pools',
]
),
storage_policy=dict(
api='vcenter',
url='/storage/policies',
filters=[
'policies',
'status',
'vms',
],
),
subscribed_library=dict(
api='content',
url='/subscribed-library',
filters=[],
),
tag=dict(
api='cis',
url='/tagging/tag',
filters=[],
),
vm=dict(
api='vcenter',
url='/vm',
filters=[
'clusters',
'datacenters',
'folders',
'hosts',
'names',
'power_states',
'resource_pools',
'vms',
],
),
)
class VmwareRestModule(AnsibleModule):
def __init__(self, is_multipart=False, use_object_handler=False, *args, **kwargs):
'''Constructor - This module mediates interactions with the
VMware httpapi connector plugin, implementing VMware's ReST API.
:module: VmwareRestModule extended from AnsibleModule.
:kw is_multipart: Indicates whether module makes multiple API calls.
Default False
:kw use_object_handler: Indicates whether module supports
multiple object types. Default False
'''
# Initialize instance arguments
self.is_multipart = is_multipart
self.use_object_handler = use_object_handler
# Output of module
self.result = {}
# Current key of output
self.key = None
# Current information going to httpapi
self.request = dict(
url=None,
filter=None,
data={},
method=None,
)
# Last response from httpapi
self.response = dict(
status=None,
data={},
)
# Initialize AnsibleModule superclass before params
super(VmwareRestModule, self).__init__(*args, **kwargs)
# Params
#
# REQUIRED: Their absence will chuck a rod
self.allow_multiples = self.params['allow_multiples']
self.status_code = self.params['status_code']
# OPTIONAL: Use params.get() to gracefully fail
self.filters = self.params.get('filters')
self.state = self.params.get('state')
# Initialize connection via httpapi connector. See "REST API Calls"
self._connection = Connection(self._socket_path)
# Register default status handlers. See "Dynamic Status Handlers"
self._status_handlers = {
'success': self.handle_default_success,
'401': self.handle_default_401,
'404': self.handle_default_404,
'default': self.handle_default_generic,
}
if self.use_object_handler:
self._status_handlers['default'] = self.handle_default_object
# Turn on debug if not specified, but ANSIBLE_DEBUG is set
self.module_debug = {}
if self._debug:
self.warn('Enable debug output because ANSIBLE_DEBUG was set.')
self.params['log_level'] = 'debug'
self.log_level = self.params['log_level']
# Debugging
#
# Tools to handle debugging output from the APIs.
def _mod_debug(self, key, **kwargs):
self.module_debug[key] = kwargs
if 'module_debug' not in self.module_debug:
self.module_debug = dict(key=kwargs)
else:
self.module_debug.update(key=kwargs)
def _api_debug(self):
'''Route debugging output to the module output.
NOTE: Adding self.path to result['path'] causes an absent in
output. Adding response['data'] causes infinite loop.
'''
return dict(
url=self.request['url'],
filter=self.request['filter'],
data=self.request['data'],
method=self.request['method'],
status=self.response['status'],
state=self.state,
)
# Dynamic Status Handlers
#
# A handler is registered by adding its key, either a module-
# generated value, or the string representation of the status code;
# and the name of the handler function. The provided handlers are
# success defined, by default, as a status code of 200, but
# can be redefined, per module, using the status_code
# parameter in that module's argument_spec.
# 401 Unauthorized access to the API.
# 404 Requested object or API was not found.
# default Any status code not otherwise identified.
# The default handlers are named 'handle_default_[status_code]'.
# User defined handlers should use 'handle_[status_code]' as a
# convention. Note that if the module expects to handle more than
# one type of object, a default object handler replaces the default
# generic handler.
#
# Handlers do not take any arguments, instead using the instance's
# variables to determine the status code and any additional data,
# like object_type. To create or replace a handler, extend this
# class, define the new handler and use the provided 'set_handler'
# method. User handlers can also chain to the default handlers if
# desired.
def set_handler(self, status_key, handler):
'''Registers the handler to the status_key'''
self._status_handlers[status_key] = handler
def _use_handler(self):
'''Invokes the appropriate handler based on status_code'''
if self.response['status'] in self.status_code:
status_key = 'success'
else:
status_key = str(self.response['status'])
if status_key in self._status_handlers.keys():
self._status_handlers[status_key]()
else:
self._status_handlers['default']()
def handle_default_success(self):
'''Default handler for all successful status codes'''
self.result[self.key] = self.response['data']
if self.log_level == 'debug':
self.result[self.key].update(
debug=self._api_debug()
)
if not self.is_multipart:
self.exit()
def handle_default_401(self):
'''Default handler for Unauthorized (401) errors'''
self.fail(msg="Unable to authenticate. Provided credentials are not valid.")
def handle_default_404(self):
'''Default handler for Not-Found (404) errors'''
self.fail(msg="Requested object was not found.")
def handle_default_generic(self):
'''Catch-all handler for all other status codes'''
msg = self.response['data']['value']['messages'][0]['default_message']
self.fail(msg=msg)
def handle_default_object(self):
'''Catch-all handler capable of distinguishing multiple objects'''
try:
msg = self.response['data']['value']['messages'][0]['default_message']
except (KeyError, TypeError):
msg = 'Unable to find the %s object specified due to %s' % (self.key, self.response)
self.fail(msg=msg)
def handle_object_key_error(self):
'''Lazy exception handler'''
msg = ('Please specify correct object type to get information, '
'choices are [%s].' % ", ".join(list(INVENTORY.keys())))
self.fail(msg=msg)
# REST API Calls
#
# VMware's REST API uses GET, POST, PUT, PATCH and DELETE http
# calls to read, create, update and delete objects and their
# attributes. These calls are implemented as functions here.
def get(self, url='/rest', key='result'):
'''Sends a GET request to the httpapi plugin connection to the
specified URL. If successful, the returned data will be placed
in the output under the specified key.
'''
self.request.update(
url=url,
data={},
method='GET',
)
self.key = key
self.response['status'], self.response['data'] = self._connection.send_request(url, {}, method='GET')
self._use_handler()
def post(self, url='/rest', data=None, key='result'):
'''Sends a POST request to the httpapi plugin connection to the
specified URL, with the supplied data. If successful, any
returned data will be placed in the output under the specified
key.
'''
self.request.update(
url=url,
data=data,
method='POST',
)
self.key = key
self.response['status'], self.response['data'] = self._connection.send_request(url, data, method='POST')
self._use_handler()
def put(self, url='/rest', data=None, key='result'):
'''Sends a PUT request to the httpapi plugin connection to the
specified URL, with the supplied data. If successful, any
returned data will be placed in the output under the specified
key.
'''
self.request.update(
url=url,
data=data,
method='PUT',
)
self.key = key
self.response['status'], self.response['data'] = self._connection.send_request(url, data, method='PUT')
self._use_handler()
def delete(self, url='/rest', data='result', key='result'):
'''Sends a DELETE request to the httpapi plugin connection to
the specified URL, with the supplied data. If successful, any
returned data will be placed in the output under the specified
key.
'''
self.request.update(
url=url,
data=data,
method='DELETE',
)
self.key = key
self.response['status'], self.response['data'] = self._connection.send_request(url, data, method='DELETE')
self._use_handler()
def get_id(self, object_type, name):
'''Find id(s) of object(s) with given name. allow_multiples
determines whether multiple IDs are returned or not.
:kw object_type: The inventory object type whose id is desired.
:kw name: The name of the object(s) to be retrieved.
:returns: a list of strings representing the IDs of the objects.
'''
try:
url = (API[INVENTORY[object_type]['api']]['base']
+ INVENTORY[object_type]['url'])
if '/' in name:
name.replace('/', '%2F')
url += '&filter.names=' + name
except KeyError:
self.fail(msg='object_type must be one of [%s].'
% ", ".join(list(INVENTORY.keys())))
status, data = self._connection.send_request(url, {}, method='GET')
if status != 200:
self.request.update(url=url, data={}, method='GET')
self.response.update(status=status, data=data)
self.handle_default_generic()
num_items = len(data['value'])
if not self.allow_multiples and num_items > 1:
msg = ('Found %d objects of type %s with name %s. '
'Set allow_multiples to True if this is expected.'
% (num_items, object_type, name))
self.fail(msg=msg)
ids = []
for i in range(num_items):
ids += data[i][object_type]
return ids
def _build_filter(self, object_type):
'''Builds a filter from the optionally supplied params'''
if self.filters:
try:
first = True
for filter in self.filters:
for key in list(filter.keys()):
filter_key = key.lower()
# Check if filter is valid for current object type or not
if filter_key not in INVENTORY[object_type]['filters']:
msg = ('%s is not a valid %s filter, choices are [%s].'
% (key, object_type, ", ".join(INVENTORY[object_type]['filters'])))
self.fail(msg=msg)
# Check if value is valid for the current filter
if ((FILTER[filter_key]['type'] == 'str' and not re.match(FILTER[filter_key]['format'], filter[key])) or
(FILTER[filter_key]['type'] == 'list' and filter[key] not in FILTER[filter_key]['choices'])):
msg = ('%s is not a valid %s %s' % (filter[key], object_type, FILTER[filter_key]['name']))
self.fail(msg=msg)
if first:
self.request['filter'] = '?'
first = False
else:
self.request['filter'] += '&'
# Escape characters
if '/' in filter[key]:
filter[key].replace('/', '%2F')
self.request['filter'] += ('filter.%s=%s'
% (FILTER[filter_key]['name'], filter[key]))
except KeyError:
self.handle_object_key_error()
else:
self.request['filter'] = None
return self.request['filter']
def get_url(self, object_type, with_filter=False):
'''Retrieves the URL of a particular inventory object with or without filter'''
try:
self.url = (API[INVENTORY[object_type]['api']]['base']
+ INVENTORY[object_type]['url'])
if with_filter:
self.url += self._build_filter(object_type)
except KeyError:
self.handle_object_key_error
return self.url
def get_url_with_filter(self, object_type):
'''Same as get_url, only with_filter is explicitly set'''
return self.get_url(object_type, with_filter=True)
def reset(self):
'''Clears the decks for next request'''
self.request.update(
url=None,
data={},
method=None,
)
self.response.update(
status=None,
data={},
)
def fail(self, msg):
if self.log_level == 'debug':
if self.request['url'] is not None:
self.result['debug'] = self._api_debug()
AnsibleModule.fail_json(self, msg=msg, **self.result)
def exit(self):
'''Called to end client interaction'''
if 'invocation' not in self.result:
self.result['invocation'] = {
'module_args': self.params,
'module_kwargs': {
'is_multipart': self.is_multipart,
'use_object_handler': self.use_object_handler,
}
}
if self.log_level == 'debug':
if not self.is_multipart:
self.result['invocation'].update(debug=self._api_debug())
if self.module_debug:
self.result['invocation'].update(module_debug=self.module_debug)
AnsibleModule.exit_json(self, **self.result)
def _merge_dictionaries(self, a, b):
new = a.copy()
new.update(b)
return new
@staticmethod
def create_argument_spec(use_filters=False, use_state=False):
'''Provide a default argument spec for this module. Filters and
state are optional parameters dependinf on the module's needs.
Additional parameters can be added. The supplied parameters can
have defaults changed or choices pared down, but should not be
removed.
'''
argument_spec = dict(
allow_multiples=dict(type='bool', default=False),
log_level=dict(type='str',
choices=['debug', 'info', 'normal'],
default='normal'),
status_code=dict(type='list', default=[200]),
)
if use_filters:
argument_spec.update(filters=dict(type='list', default=[]))
if use_state:
argument_spec.update(state=dict(type='list',
choices=['absent', 'present', 'query'],
default='query'))
return argument_spec

View file

@ -0,0 +1,117 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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: vmware_appliance_access_info
short_description: Gathers info about modes of access to the vCenter appliance using REST API.
description:
- This module can be used to gather information about the four modes of accessing the VCSA.
- This module is based on REST API and uses httpapi connection plugin for persistent connection.
- The Appliance API works against the VCSA and uses the "administrator@vsphere.local" user.
version_added: '2.9'
author:
- Paul Knight (@n3pjk)
notes:
- Tested on vSphere 6.7
requirements:
- python >= 2.6
options:
access_mode:
description:
- Method of access to get to appliance
- If not specified, all modes will be returned.
required: false
choices: ['consolecli', 'dcui', 'shell', 'ssh']
type: str
extends_documentation_fragment: VmwareRestModule.documentation
'''
EXAMPLES = r'''
- hosts: all
connection: httpapi
gather_facts: false
vars:
ansible_network_os: vmware
ansible_host: vcenter.my.domain
ansible_user: administrator@vsphere.local
ansible_httpapi_password: "SomePassword"
ansbile_httpapi_use_ssl: yes
ansible_httpapi_validate_certs: false
tasks:
- name: Get all access modes information
vmware_appliance_access_info:
- name: Get ssh access mode information
vmware_appliance_access_info:
access_mode: ssh
'''
RETURN = r'''
access_mode:
description: facts about the specified access mode
returned: always
type: dict
sample: {
"value": true
}
'''
from ansible.module_utils.vmware_httpapi.VmwareRestModule import API, VmwareRestModule
SLUG = dict(
consolecli='/access/consolecli',
dcui='/access/dcui',
shell='/access/shell',
ssh='/access/ssh',
)
def get_mode(module, mode):
try:
url = API['appliance']['base'] + SLUG[mode]
except KeyError:
module.fail(msg='[%s] is not a valid access mode. '
'Please specify correct mode, valid choices are '
'[%s].' % (mode, ", ".join(list(SLUG.keys()))))
module.get(url=url, key=mode)
def main():
argument_spec = VmwareRestModule.create_argument_spec()
argument_spec.update(
access_mode=dict(type='str', choices=['consolecli', 'dcui', 'shell', 'ssh'], default=None),
)
module = VmwareRestModule(argument_spec=argument_spec,
supports_check_mode=True,
is_multipart=True,
use_object_handler=True)
access_mode = module.params['access_mode']
if access_mode is None:
access_mode = SLUG.keys()
for mode in access_mode:
get_mode(module, mode)
else:
get_mode(module, access_mode)
module.exit()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,148 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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: vmware_appliance_health_info
short_description: Gathers info about health of the VCSA.
description:
- This module can be used to gather information about VCSA health.
- This module is based on REST API and uses httpapi connection plugin for persistent connection.
- The Appliance API works against the VCSA and uses the "administrator@vsphere.local" user.
version_added: '2.9'
author:
- Paul Knight (@n3pjk)
notes:
- Tested on vSphere 6.7
requirements:
- python >= 2.6
options:
subsystem:
description:
- A subsystem of the VCSA.
required: false
choices: ['applmgmt', 'databasestorage', 'lastcheck', 'load', 'mem', 'softwarepackages', 'storage', 'swap', 'system']
type: str
asset:
description:
- A VCSA asset that has associated health metrics.
- Valid choices have yet to be determined at this time.
required: false
type: str
extends_documentation_fragment: VmwareRestModule.documentation
'''
EXAMPLES = r'''
- hosts: all
connection: httpapi
gather_facts: false
vars:
ansible_network_os: vmware
ansible_host: vcenter.my.domain
ansible_user: administrator@vsphere.local
ansible_httpapi_password: "SomePassword"
ansbile_httpapi_use_ssl: yes
ansible_httpapi_validate_certs: false
tasks:
- name: Get all health attribute information
vmware_appliance_health_info:
- name: Get system health information
vmware_appliance_health_info:
subsystem: system
'''
RETURN = r'''
attribute:
description: facts about the specified health attribute
returned: always
type: dict
sample: {
"value": true
}
'''
from ansible.module_utils.vmware_httpapi.VmwareRestModule import API, VmwareRestModule
SLUG = dict(
applmgmt='/health/applmgmt',
databasestorage='/health/database-storage',
load='/health/load',
mem='/health/mem',
softwarepackages='/health/software-packages',
storage='/health/storage',
swap='/health/swap',
system='/health/system',
lastcheck='/health/system/lastcheck',
)
def get_subsystem(module, subsystem):
try:
url = API['appliance']['base'] + SLUG[subsystem]
except KeyError:
module.fail(msg='[%s] is not a valid subsystem. '
'Please specify correct subsystem, valid choices are '
'[%s].' % (subsystem, ", ".join(list(SLUG.keys()))))
module.get(url=url, key=subsystem)
def main():
argument_spec = VmwareRestModule.create_argument_spec()
argument_spec.update(
subsystem=dict(
type='str',
required=False,
choices=[
'applmgmt',
'databasestorage',
'lastcheck',
'load',
'mem',
'softwarepackages',
'storage',
'swap',
'system',
],
),
asset=dict(type='str', required=False),
)
module = VmwareRestModule(argument_spec=argument_spec,
supports_check_mode=True,
is_multipart=True,
use_object_handler=True)
subsystem = module.params['subsystem']
asset = module.params['asset']
if asset is not None:
url = (API['appliance']['base']
+ ('/health/%s/messages' % asset))
module.get(url=url, key=asset)
elif subsystem is None:
subsystem = SLUG.keys()
for sys in subsystem:
get_subsystem(module, sys)
else:
get_subsystem(module, subsystem)
module.exit()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,149 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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: vmware_cis_category_info
short_description: Gathers info about all, or a specified category.
description:
- This module can be used to gather information about a specific category.
- This module can also gather facts about all categories.
- This module is based on REST API and uses httpapi connection plugin for persistent connection.
version_added: '2.9'
author:
- Paul Knight (@n3pjk)
notes:
- Tested on vSphere 6.7
requirements:
- python >= 2.6
options:
category_id:
description:
- The object id of the category.
- Exclusive of category_name and used_by_*.
required: false
type: str
category_name:
description:
- The name of the category.
- Exclusive of category_id and used_by_*.
required: false
type: str
used_by_id:
description:
- The id of the entity to list applied categories.
- Exclusive of other used_by_* and category_*.
type: str
used_by_name:
description:
- The name of the entity to list applied categories, whose type is specified in used_by_type.
- Exclusive of other used_by_id and category_*.
type: str
used_by_type:
description:
- The type of the entity to list applied categories, whose name is specified in used_by_name.
- Exclusive of other used_by_id and category_*.
choices: ['cluster', 'content_library', 'content_type', 'datacenter',
'datastore', 'folder', 'host', 'local_library', 'network',
'resource_pool', 'subscribed_library', 'tag', 'vm']
type: str
extends_documentation_fragment: VmwareRestModule.documentation
'''
EXAMPLES = r'''
- name: Get all categories
vmware_cis_category_info:
'''
RETURN = r'''
category:
description: facts about the specified category
returned: always
type: dict
sample: {
"value": true
}
'''
from ansible.module_utils.vmware_httpapi.VmwareRestModule import VmwareRestModule
def main():
argument_spec = VmwareRestModule.create_argument_spec()
argument_spec.update(
category_name=dict(type='str', required=False),
category_id=dict(type='str', required=False),
used_by_name=dict(type='str', required=False),
used_by_type=dict(
type='str',
required=False,
choices=[
'cluster',
'content_library',
'content_type',
'datacenter',
'datastore',
'folder',
'host',
'local_library',
'network',
'resource_pool',
'subscribed_library',
'tag',
'vm',
],
),
used_by_id=dict(type='str', required=False),
)
required_together = [
['used_by_name', 'used_by_type']
]
mutually_exclusive = [
['category_name', 'category_id', 'used_by_id', 'used_by_name'],
['category_name', 'category_id', 'used_by_id', 'used_by_type'],
]
module = VmwareRestModule(argument_spec=argument_spec,
required_together=required_together,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
category_name = module.params['category_name']
category_id = module.params['category_id']
used_by_name = module.params['used_by_name']
used_by_type = module.params['used_by_type']
used_by_id = module.params['used_by_id']
url = module.get_url('category')
data = {}
if category_name is not None:
category_id = module.get_id('category', category_name)
if category_id is not None:
url += '/id:' + category_id
module.get(url=url)
else:
if used_by_name is not None:
used_by_id = module.get_id(used_by_type, used_by_name)
url += '?~action=list-used-categories'
data = {
'used_by_entity': used_by_id
}
module.post(url=url, data=data)
module.exit()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,117 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Abhijeet Kasurde <akasurde@redhat.com>
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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: vmware_core_info
short_description: Gathers info about various VMware inventory objects using REST API
description:
- This module can be used to gather information about various VMware inventory objects.
- This module is based on REST API and uses httpapi connection plugin for persistent connection.
version_added: '2.9'
author:
- Abhijeet Kasurde (@Akasurde)
- Paul Knight (@n3pjk)
notes:
- Tested on vSphere 6.7
requirements:
- python >= 2.6
options:
object_type:
description:
- Type of VMware object.
- Valid choices are datacenter, cluster, datastore, folder, host,
network, resource_pool, virtual_machine, content_library,
local_library, subscribed_library, content_type, tag, category.
type: str
default: datacenter
filters:
description:
- A list of filters to find the given object.
- Valid filters for datacenter object type - folders, datacenters, names.
- Valid filters for cluster object type - folders, datacenters, names, clusters.
- Valid filters for datastore object type - folders, datacenters, names, datastores, types.
- Valid filters for folder object type - folders, parent_folders, names, datacenters, type.
- Valid filters for host object type - folders, hosts, names, datacenters, clusters, connection_states.
- Valid filters for network object type - folders, types, names, datacenters, networks.
- Valid filters for resource_pool object type - resource_pools, parent_resource_pools, names, datacenters, hosts, clusters.
- Valid filters for virtual_machine object type - folders, resource_pools, power_states, vms, names, datacenters, hosts, clusters.
- content_library, local_library, subscribed_library, content_type, tag, category does not take any filters.
default: []
type: list
extends_documentation_fragment: VmwareRestModule_filters.documentation
'''
EXAMPLES = r'''
- name: Get All VM without any filters
block:
- name: Get VMs
vmware_core_info:
object_type: "{{ object_type }}"
register: vm_result
- assert:
that:
- vm_result[object_type].value | length > 0
vars:
object_type: vm
- name: Get all clusters from Asia-Datacenter1
vmware_core_info:
object_type: cluster
filters:
- datacenters: "{{ datacenter_obj }}"
register: clusters_result
'''
RETURN = r'''
object_info:
description: information about the given VMware object
returned: always
type: dict
sample: {
"value": [
{
"cluster": "domain-c42",
"drs_enabled": false,
"ha_enabled": false,
"name": "Asia-Cluster1"
}
]
}
'''
from ansible.module_utils.vmware_httpapi.VmwareRestModule import VmwareRestModule
def main():
argument_spec = VmwareRestModule.create_argument_spec(use_filters=True)
argument_spec.update(
object_type=dict(type='str', default='datacenter'),
)
module = VmwareRestModule(argument_spec=argument_spec,
supports_check_mode=True,
use_object_handler=True)
object_type = module.params['object_type']
url = module.get_url_with_filter(object_type)
module.get(url=url, key=object_type)
module.exit()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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):
# Parameters for VMware ReST HTTPAPI modules omits filters and state
DOCUMENTATION = r'''
options:
allow_multiples:
description:
- Indicates whether get_id() can return multiple IDs for a given name.
- Typically, this should be false when updating or deleting; otherwise, all named objects could be affected.
required: true
version_added: "2.9"
type: bool
log_level:
description:
- If ANSIBLE_DEBUG is set, this will be forced to 'debug', but can be user-defined otherwise.
required: True
choices: ['debug', 'info', 'normal']
version_added: "2.9"
type: str
default: 'normal'
status_code:
description:
- A list of integer status codes considered to be successful for the this module.
required: true
version_added: "2.9"
type: list
default: [200]
'''

View file

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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):
# Parameters for VMware ReST HTTPAPI modules includes filters
DOCUMENTATION = r'''
options:
allow_multiples:
description:
- Indicates whether get_id() can return multiple IDs for a given name.
- Typically, this should be false when updating or deleting; otherwise, all named objects could be affected.
required: true
version_added: "2.9"
type: bool
filters:
description:
- The key/value pairs describing filters to be applied to the request(s) made by this instance.
required: false
version_added: "2.9"
type: dict
log_level:
description:
- If ANSIBLE_DEBUG is set, this will be forced to 'debug', but can be user-defined otherwise.
required: True
choices: ['debug', 'info', 'normal']
version_added: "2.9"
type: str
default: 'normal'
status_code:
description:
- A list of integer status codes considered to be successful for the this module.
required: true
version_added: "2.9"
type: list
default: [200]
'''

View file

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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):
# Parameters for VMware ReST HTTPAPI modules includes filters and state
DOCUMENTATION = r'''
options:
allow_multiples:
description:
- Indicates whether get_id() can return multiple IDs for a given name.
- Typically, this should be false when updating or deleting; otherwise, all named objects could be affected.
required: true
version_added: "2.9"
type: bool
filters:
description:
- The key/value pairs describing filters to be applied to the request(s) made by this instance.
required: false
version_added: "2.9"
type: dict
log_level:
description:
- If ANSIBLE_DEBUG is set, this will be forced to 'debug', but can be user-defined otherwise.
required: True
choices: ['debug', 'info', 'normal']
version_added: "2.9"
type: str
default: 'normal'
state:
description:
- Either 'absent' or 'present', depending on whether object should be removed or created.
required: false
choices: ['absent', 'present', 'query']
version_added: "2.9"
type: str
default: 'present'
status_code:
description:
- A list of integer status codes considered to be successful for the this module.
required: true
version_added: "2.9"
type: list
default: [200]
'''

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Paul Knight <paul.knight@delaware.gov>
# 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):
# Parameters for VMware ReST HTTPAPI modules includes filters and state
DOCUMENTATION = r'''
options:
allow_multiples:
description:
- Indicates whether get_id() can return multiple IDs for a given name.
- Typically, this should be false when updating or deleting; otherwise, all named objects could be affected.
required: true
version_added: "2.9"
type: bool
log_level:
description:
- If ANSIBLE_DEBUG is set, this will be forced to 'debug', but can be user-defined otherwise.
required: True
choices: ['debug', 'info', 'normal']
version_added: "2.9"
type: str
default: 'normal'
state:
description:
- Either 'absent' or 'present', depending on whether object should be removed or created.
required: false
choices: ['absent', 'present', 'query']
version_added: "2.9"
type: str
default: 'present'
status_code:
description:
- A list of integer status codes considered to be successful for the this module.
required: true
version_added: "2.9"
type: list
default: [200]
'''

View file

@ -0,0 +1,85 @@
# Copyright: (c) 2018 Red Hat Inc.
# Copyright: (c) 2019, Ansible Project
# Copyright: (c) 2019, Abhijeet Kasurde <akasurde@redhat.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
DOCUMENTATION = """
---
author: Abhijeet Kasurde (Akasurde)
httpapi : vmware
short_description: HttpApi Plugin for VMware REST API
description:
- This HttpApi plugin provides methods to connect to VMware vCenter over a HTTP(S)-based APIs.
version_added: "2.9"
"""
import json
from ansible.module_utils.basic import to_text
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.plugins.httpapi import HttpApiBase
from ansible.module_utils.connection import ConnectionError
BASE_HEADERS = {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
class HttpApi(HttpApiBase):
def login(self, username, password):
if username and password:
payload = {}
url = '/rest/com/vmware/cis/session'
response, response_data = self.send_request(url, payload)
else:
raise AnsibleConnectionFailure('Username and password are required for login')
if response == 404:
raise ConnectionError(response_data)
if not response_data.get('value'):
raise ConnectionError('Server returned response without token info during connection authentication: %s' % response)
self.connection._session_uid = "vmware-api-session-id:%s" % response_data['value']
self.connection._token = response_data['value']
def logout(self):
response, dummy = self.send_request('/rest/com/vmware/cis/session', None, method='DELETE')
def get_session_uid(self):
return self.connection._session_uid
def get_session_token(self):
return self.connection._token
def send_request(self, path, body_params, method='POST'):
data = json.dumps(body_params) if body_params else '{}'
try:
self._display_request(method=method)
response, response_data = self.connection.send(path, data, method=method, headers=BASE_HEADERS, force_basic_auth=True)
response_value = self._get_response_value(response_data)
return response.getcode(), self._response_to_json(response_value)
except AnsibleConnectionFailure as e:
return 404, 'Object not found'
except HTTPError as e:
return e.code, json.loads(e.read())
def _display_request(self, method='POST'):
self.connection.queue_message('vvvv', 'Web Services: %s %s' % (method, self.connection._url))
def _get_response_value(self, response_data):
return to_text(response_data.getvalue())
def _response_to_json(self, response_text):
try:
return json.loads(response_text) if response_text else {}
# JSONDecodeError only available on Python 3.5+
except ValueError:
raise ConnectionError('Invalid JSON response: %s' % response_text)