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:
parent
5be0668fb0
commit
05ded87848
12 changed files with 1492 additions and 0 deletions
708
lib/ansible/module_utils/vmware_httpapi/VmwareRestModule.py
Normal file
708
lib/ansible/module_utils/vmware_httpapi/VmwareRestModule.py
Normal 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
|
0
lib/ansible/module_utils/vmware_httpapi/__init__.py
Normal file
0
lib/ansible/module_utils/vmware_httpapi/__init__.py
Normal file
0
lib/ansible/modules/cloud/vmware_httpapi/__init__.py
Normal file
0
lib/ansible/modules/cloud/vmware_httpapi/__init__.py
Normal 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()
|
|
@ -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()
|
|
@ -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()
|
117
lib/ansible/modules/cloud/vmware_httpapi/vmware_core_info.py
Normal file
117
lib/ansible/modules/cloud/vmware_httpapi/vmware_core_info.py
Normal 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()
|
35
lib/ansible/plugins/doc_fragments/VmwareRestModule.py
Normal file
35
lib/ansible/plugins/doc_fragments/VmwareRestModule.py
Normal 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]
|
||||
'''
|
|
@ -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]
|
||||
'''
|
49
lib/ansible/plugins/doc_fragments/VmwareRestModule_full.py
Normal file
49
lib/ansible/plugins/doc_fragments/VmwareRestModule_full.py
Normal 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]
|
||||
'''
|
43
lib/ansible/plugins/doc_fragments/VmwareRestModule_state.py
Normal file
43
lib/ansible/plugins/doc_fragments/VmwareRestModule_state.py
Normal 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]
|
||||
'''
|
85
lib/ansible/plugins/httpapi/vmware.py
Normal file
85
lib/ansible/plugins/httpapi/vmware.py
Normal 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)
|
Loading…
Reference in a new issue