Migrated to cisco.intersight
This commit is contained in:
parent
ab5a3b4305
commit
8a2e2e8af2
7 changed files with 0 additions and 754 deletions
|
@ -1,294 +0,0 @@
|
||||||
# This code is part of Ansible, but is an independent component.
|
|
||||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
|
||||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
|
||||||
# still belong to the author of the module, and may assign their own license
|
|
||||||
# to the complete work.
|
|
||||||
#
|
|
||||||
# (c) 2016 Red Hat Inc.
|
|
||||||
# (c) 2018 Cisco Systems Inc.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
# are permitted provided that the following conditions are met:
|
|
||||||
#
|
|
||||||
# * Redistributions of source code must retain the above copyright
|
|
||||||
# notice, this list of conditions and the following disclaimer.
|
|
||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
|
||||||
# and/or other materials provided with the distribution.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
||||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
||||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
#
|
|
||||||
# Intersight REST API Module
|
|
||||||
# Author: Matthew Garrett
|
|
||||||
# Contributors: David Soper, Chris Gascoigne, John McDonough
|
|
||||||
|
|
||||||
from base64 import b64encode
|
|
||||||
from email.utils import formatdate
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import hashlib
|
|
||||||
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode, quote
|
|
||||||
from ansible.module_utils.urls import fetch_url
|
|
||||||
|
|
||||||
try:
|
|
||||||
from cryptography.hazmat.primitives import serialization, hashes
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
|
||||||
HAS_CRYPTOGRAPHY = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_CRYPTOGRAPHY = False
|
|
||||||
|
|
||||||
intersight_argument_spec = dict(
|
|
||||||
api_private_key=dict(type='path', required=True),
|
|
||||||
api_uri=dict(type='str', default='https://intersight.com/api/v1'),
|
|
||||||
api_key_id=dict(type='str', required=True),
|
|
||||||
validate_certs=dict(type='bool', default=True),
|
|
||||||
use_proxy=dict(type='bool', default=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_sha256_digest(data):
|
|
||||||
"""
|
|
||||||
Generates a SHA256 digest from a String.
|
|
||||||
|
|
||||||
:param data: data string set by user
|
|
||||||
:return: instance of digest object
|
|
||||||
"""
|
|
||||||
|
|
||||||
digest = hashlib.sha256()
|
|
||||||
digest.update(data.encode())
|
|
||||||
|
|
||||||
return digest
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_str_to_sign(req_tgt, hdrs):
|
|
||||||
"""
|
|
||||||
Concatenates Intersight headers in preparation to be RSA signed
|
|
||||||
|
|
||||||
:param req_tgt : http method plus endpoint
|
|
||||||
:param hdrs: dict with header keys
|
|
||||||
:return: concatenated header authorization string
|
|
||||||
"""
|
|
||||||
ss = ""
|
|
||||||
ss = ss + "(request-target): " + req_tgt.lower() + "\n"
|
|
||||||
|
|
||||||
length = len(hdrs.items())
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for key, value in hdrs.items():
|
|
||||||
ss = ss + key.lower() + ": " + value
|
|
||||||
if i < length - 1:
|
|
||||||
ss = ss + "\n"
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
return ss
|
|
||||||
|
|
||||||
|
|
||||||
def get_gmt_date():
|
|
||||||
"""
|
|
||||||
Generated a GMT formatted Date
|
|
||||||
|
|
||||||
:return: current date
|
|
||||||
"""
|
|
||||||
|
|
||||||
return formatdate(timeval=None, localtime=False, usegmt=True)
|
|
||||||
|
|
||||||
|
|
||||||
class IntersightModule():
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
self.module = module
|
|
||||||
self.result = dict(changed=False)
|
|
||||||
if not HAS_CRYPTOGRAPHY:
|
|
||||||
self.module.fail_json(msg='cryptography is required for this module')
|
|
||||||
self.host = self.module.params['api_uri']
|
|
||||||
self.public_key = self.module.params['api_key_id']
|
|
||||||
with open(self.module.params['api_private_key'], 'r') as f:
|
|
||||||
self.private_key = f.read()
|
|
||||||
self.digest_algorithm = 'rsa-sha256'
|
|
||||||
self.response_list = []
|
|
||||||
|
|
||||||
def get_rsasig_b64encode(self, data):
|
|
||||||
"""
|
|
||||||
Generates an RSA Signed SHA256 digest from a String
|
|
||||||
|
|
||||||
:param digest: string to be signed & hashed
|
|
||||||
:return: instance of digest object
|
|
||||||
"""
|
|
||||||
|
|
||||||
rsakey = serialization.load_pem_private_key(self.private_key.encode(), None, default_backend())
|
|
||||||
sign = rsakey.sign(data.encode(), padding.PKCS1v15(), hashes.SHA256())
|
|
||||||
|
|
||||||
return b64encode(sign)
|
|
||||||
|
|
||||||
def get_auth_header(self, hdrs, signed_msg):
|
|
||||||
"""
|
|
||||||
Assmebled an Intersight formatted authorization header
|
|
||||||
|
|
||||||
:param hdrs : object with header keys
|
|
||||||
:param signed_msg: base64 encoded sha256 hashed body
|
|
||||||
:return: concatenated authorization header
|
|
||||||
"""
|
|
||||||
|
|
||||||
auth_str = "Signature"
|
|
||||||
|
|
||||||
auth_str = auth_str + " " + "keyId=\"" + self.public_key + "\"," + "algorithm=\"" + self.digest_algorithm + "\"," + "headers=\"(request-target)"
|
|
||||||
|
|
||||||
for key, dummy in hdrs.items():
|
|
||||||
auth_str = auth_str + " " + key.lower()
|
|
||||||
auth_str = auth_str + "\""
|
|
||||||
|
|
||||||
auth_str = auth_str + "," + "signature=\"" + signed_msg.decode('ascii') + "\""
|
|
||||||
|
|
||||||
return auth_str
|
|
||||||
|
|
||||||
def get_moid_by_name(self, resource_path, target_name):
|
|
||||||
"""
|
|
||||||
Retrieve an Intersight object moid by name
|
|
||||||
|
|
||||||
:param resource_path: intersight resource path e.g. '/ntp/Policies'
|
|
||||||
:param target_name: intersight object name
|
|
||||||
:return: json http response object
|
|
||||||
"""
|
|
||||||
query_params = {
|
|
||||||
"$filter": "Name eq '{0}'".format(target_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
options = {
|
|
||||||
"http_method": "GET",
|
|
||||||
"resource_path": resource_path,
|
|
||||||
"query_params": query_params
|
|
||||||
}
|
|
||||||
|
|
||||||
get_moid = self.intersight_call(**options)
|
|
||||||
|
|
||||||
if get_moid.json()['Results'] is not None:
|
|
||||||
located_moid = get_moid.json()['Results'][0]['Moid']
|
|
||||||
else:
|
|
||||||
raise KeyError('Intersight object with name "{0}" not found!'.format(target_name))
|
|
||||||
|
|
||||||
return located_moid
|
|
||||||
|
|
||||||
def call_api(self, **options):
|
|
||||||
"""
|
|
||||||
Call the Intersight API and check for success status
|
|
||||||
:param options: options dict with method and other params for API call
|
|
||||||
:return: json http response object
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
response, info = self.intersight_call(**options)
|
|
||||||
if not re.match(r'2..', str(info['status'])):
|
|
||||||
raise RuntimeError(info['status'], info['msg'], info['body'])
|
|
||||||
except Exception as e:
|
|
||||||
self.module.fail_json(msg="API error: %s " % str(e))
|
|
||||||
|
|
||||||
response_data = response.read()
|
|
||||||
if len(response_data) > 0:
|
|
||||||
return json.loads(response_data)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def intersight_call(self, http_method="", resource_path="", query_params=None, body=None, moid=None, name=None):
|
|
||||||
"""
|
|
||||||
Invoke the Intersight API
|
|
||||||
|
|
||||||
:param resource_path: intersight resource path e.g. '/ntp/Policies'
|
|
||||||
:param query_params: dictionary object with query string parameters as key/value pairs
|
|
||||||
:param body: dictionary object with intersight data
|
|
||||||
:param moid: intersight object moid
|
|
||||||
:param name: intersight object name
|
|
||||||
:return: json http response object
|
|
||||||
"""
|
|
||||||
|
|
||||||
target_host = urlparse(self.host).netloc
|
|
||||||
target_path = urlparse(self.host).path
|
|
||||||
query_path = ""
|
|
||||||
method = http_method.upper()
|
|
||||||
bodyString = ""
|
|
||||||
|
|
||||||
# Verify an accepted HTTP verb was chosen
|
|
||||||
if(method not in ['GET', 'POST', 'PATCH', 'DELETE']):
|
|
||||||
raise ValueError('Please select a valid HTTP verb (GET/POST/PATCH/DELETE)')
|
|
||||||
|
|
||||||
# Verify the resource path isn't empy & is a valid <str> object
|
|
||||||
if(resource_path != "" and not (resource_path, str)):
|
|
||||||
raise TypeError('The *resource_path* value is required and must be of type "<str>"')
|
|
||||||
|
|
||||||
# Verify the query parameters isn't empy & is a valid <dict> object
|
|
||||||
if(query_params is not None and not isinstance(query_params, dict)):
|
|
||||||
raise TypeError('The *query_params* value must be of type "<dict>"')
|
|
||||||
|
|
||||||
# Verify the body isn't empy & is a valid <dict> object
|
|
||||||
if(body is not None and not isinstance(body, dict)):
|
|
||||||
raise TypeError('The *body* value must be of type "<dict>"')
|
|
||||||
|
|
||||||
# Verify the MOID is not null & of proper length
|
|
||||||
if(moid is not None and len(moid.encode('utf-8')) != 24):
|
|
||||||
raise ValueError('Invalid *moid* value!')
|
|
||||||
|
|
||||||
# Check for query_params, encode, and concatenate onto URL
|
|
||||||
if query_params is not None:
|
|
||||||
query_path = "?" + urlencode(query_params).replace('+', '%20')
|
|
||||||
|
|
||||||
# Handle PATCH/DELETE by Object "name" instead of "moid"
|
|
||||||
if(method == "PATCH" or method == "DELETE"):
|
|
||||||
if moid is None:
|
|
||||||
if name is not None:
|
|
||||||
if isinstance(name, str):
|
|
||||||
moid = self.get_moid_by_name(resource_path, name)
|
|
||||||
else:
|
|
||||||
raise TypeError('The *name* value must be of type "<str>"')
|
|
||||||
else:
|
|
||||||
raise ValueError('Must set either *moid* or *name* with "PATCH/DELETE!"')
|
|
||||||
|
|
||||||
# Check for moid and concatenate onto URL
|
|
||||||
if moid is not None:
|
|
||||||
resource_path += "/" + moid
|
|
||||||
|
|
||||||
# Check for GET request to properly form body
|
|
||||||
if method != "GET":
|
|
||||||
bodyString = json.dumps(body)
|
|
||||||
|
|
||||||
# Concatenate URLs for headers
|
|
||||||
target_url = self.host + resource_path + query_path
|
|
||||||
request_target = method + " " + target_path + resource_path + query_path
|
|
||||||
|
|
||||||
# Get the current GMT Date/Time
|
|
||||||
cdate = get_gmt_date()
|
|
||||||
|
|
||||||
# Generate the body digest
|
|
||||||
body_digest = get_sha256_digest(bodyString)
|
|
||||||
b64_body_digest = b64encode(body_digest.digest())
|
|
||||||
|
|
||||||
# Generate the authorization header
|
|
||||||
auth_header = {
|
|
||||||
'Date': cdate,
|
|
||||||
'Host': target_host,
|
|
||||||
'Digest': "SHA-256=" + b64_body_digest.decode('ascii')
|
|
||||||
}
|
|
||||||
|
|
||||||
string_to_sign = prepare_str_to_sign(request_target, auth_header)
|
|
||||||
b64_signed_msg = self.get_rsasig_b64encode(string_to_sign)
|
|
||||||
auth_header = self.get_auth_header(auth_header, b64_signed_msg)
|
|
||||||
|
|
||||||
# Generate the HTTP requests header
|
|
||||||
request_header = {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Host': '{0}'.format(target_host),
|
|
||||||
'Date': '{0}'.format(cdate),
|
|
||||||
'Digest': 'SHA-256={0}'.format(b64_body_digest.decode('ascii')),
|
|
||||||
'Authorization': '{0}'.format(auth_header),
|
|
||||||
}
|
|
||||||
|
|
||||||
response, info = fetch_url(self.module, target_url, data=bodyString, headers=request_header, method=method, use_proxy=self.module.params['use_proxy'])
|
|
||||||
|
|
||||||
return response, info
|
|
|
@ -1 +0,0 @@
|
||||||
intersight_info.py
|
|
|
@ -1,254 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# 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: intersight_rest_api
|
|
||||||
short_description: REST API configuration for Cisco Intersight
|
|
||||||
description:
|
|
||||||
- Direct REST API configuration for Cisco Intersight.
|
|
||||||
- All REST API resources and properties must be specified.
|
|
||||||
- For more information see L(Cisco Intersight,https://intersight.com/apidocs).
|
|
||||||
extends_documentation_fragment: intersight
|
|
||||||
options:
|
|
||||||
resource_path:
|
|
||||||
description:
|
|
||||||
- Resource URI being configured related to api_uri.
|
|
||||||
type: str
|
|
||||||
required: yes
|
|
||||||
query_params:
|
|
||||||
description:
|
|
||||||
- Query parameters for the Intersight API query language.
|
|
||||||
type: dict
|
|
||||||
update_method:
|
|
||||||
description:
|
|
||||||
- The HTTP method used for update operations.
|
|
||||||
- Some Intersight resources require POST operations for modifications.
|
|
||||||
type: str
|
|
||||||
choices: [ patch, post ]
|
|
||||||
default: patch
|
|
||||||
api_body:
|
|
||||||
description:
|
|
||||||
- The payload for API requests used to modify resources.
|
|
||||||
type: dict
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- If C(present), will verify the resource is present and will create if needed.
|
|
||||||
- If C(absent), will verify the resource is absent and will delete if needed.
|
|
||||||
choices: [present, absent]
|
|
||||||
default: present
|
|
||||||
author:
|
|
||||||
- David Soper (@dsoper2)
|
|
||||||
- CiscoUcs (@CiscoUcs)
|
|
||||||
version_added: '2.8'
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = r'''
|
|
||||||
- name: Configure Boot Policy
|
|
||||||
intersight_rest_api:
|
|
||||||
api_private_key: "{{ api_private_key }}"
|
|
||||||
api_key_id: "{{ api_key_id }}"
|
|
||||||
api_key_uri: "{{ api_key_uri }}"
|
|
||||||
validate_certs: "{{ validate_certs }}"
|
|
||||||
resource_path: /boot/PrecisionPolicies
|
|
||||||
query_params:
|
|
||||||
$filter: "Name eq 'vmedia-localdisk'"
|
|
||||||
api_body: {
|
|
||||||
"Name": "vmedia-hdd",
|
|
||||||
"ConfiguredBootMode": "Legacy",
|
|
||||||
"BootDevices": [
|
|
||||||
{
|
|
||||||
"ObjectType": "boot.VirtualMedia",
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "remote-vmedia",
|
|
||||||
"Subtype": "cimc-mapped-dvd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ObjectType": "boot.LocalDisk",
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "localdisk",
|
|
||||||
"Slot": "MRAID",
|
|
||||||
"Bootloader": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Delete Boot Policy
|
|
||||||
intersight_rest_api:
|
|
||||||
api_private_key: "{{ api_private_key }}"
|
|
||||||
api_key_id: "{{ api_key_id }}"
|
|
||||||
api_key_uri: "{{ api_key_uri }}"
|
|
||||||
validate_certs: "{{ validate_certs }}"
|
|
||||||
resource_path: /boot/PrecisionPolicies
|
|
||||||
query_params:
|
|
||||||
$filter: "Name eq 'vmedia-localdisk'"
|
|
||||||
state: absent
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
|
||||||
api_repsonse:
|
|
||||||
description: The API response output returned by the specified resource.
|
|
||||||
returned: always
|
|
||||||
type: dict
|
|
||||||
sample:
|
|
||||||
"api_response": {
|
|
||||||
"BootDevices": [
|
|
||||||
{
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "remote-vmedia",
|
|
||||||
"ObjectType": "boot.VirtualMedia",
|
|
||||||
"Subtype": "cimc-mapped-dvd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Bootloader": null,
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "boot-lun",
|
|
||||||
"ObjectType": "boot.LocalDisk",
|
|
||||||
"Slot": "MRAID"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ConfiguredBootMode": "Legacy",
|
|
||||||
"Name": "vmedia-localdisk",
|
|
||||||
"ObjectType": "boot.PrecisionPolicy",
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
import re
|
|
||||||
from ansible.module_utils.remote_management.intersight import IntersightModule, intersight_argument_spec
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.six import iteritems
|
|
||||||
|
|
||||||
|
|
||||||
def get_resource(intersight):
|
|
||||||
'''
|
|
||||||
GET a resource and return the 1st element found
|
|
||||||
'''
|
|
||||||
options = {
|
|
||||||
'http_method': 'get',
|
|
||||||
'resource_path': intersight.module.params['resource_path'],
|
|
||||||
'query_params': intersight.module.params['query_params'],
|
|
||||||
}
|
|
||||||
response_dict = intersight.call_api(**options)
|
|
||||||
if response_dict.get('Results'):
|
|
||||||
# return the 1st list element
|
|
||||||
response_dict = response_dict['Results'][0]
|
|
||||||
|
|
||||||
return response_dict
|
|
||||||
|
|
||||||
|
|
||||||
def compare_values(expected, actual):
|
|
||||||
try:
|
|
||||||
for (key, value) in iteritems(expected):
|
|
||||||
if re.search(r'P(ass)?w(or)?d', key) or key not in actual:
|
|
||||||
# do not compare any password related attributes or attributes that are not in the actual resource
|
|
||||||
continue
|
|
||||||
if not compare_values(value, actual[key]):
|
|
||||||
return False
|
|
||||||
# loop complete with all items matching
|
|
||||||
return True
|
|
||||||
except (AttributeError, TypeError):
|
|
||||||
if expected and actual != expected:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def configure_resource(intersight, moid):
|
|
||||||
if not intersight.module.check_mode:
|
|
||||||
if moid:
|
|
||||||
# update the resource - user has to specify all the props they want updated
|
|
||||||
options = {
|
|
||||||
'http_method': intersight.module.params['update_method'],
|
|
||||||
'resource_path': intersight.module.params['resource_path'],
|
|
||||||
'body': intersight.module.params['api_body'],
|
|
||||||
'moid': moid,
|
|
||||||
}
|
|
||||||
response_dict = intersight.call_api(**options)
|
|
||||||
if response_dict.get('Results'):
|
|
||||||
# return the 1st element in the results list
|
|
||||||
intersight.result['api_response'] = response_dict['Results'][0]
|
|
||||||
else:
|
|
||||||
# create the resource
|
|
||||||
options = {
|
|
||||||
'http_method': 'post',
|
|
||||||
'resource_path': intersight.module.params['resource_path'],
|
|
||||||
'body': intersight.module.params['api_body'],
|
|
||||||
}
|
|
||||||
resp = intersight.call_api(**options)
|
|
||||||
if 'Moid' not in resp:
|
|
||||||
resp = get_resource(intersight)
|
|
||||||
intersight.result['api_response'] = resp
|
|
||||||
intersight.result['changed'] = True
|
|
||||||
|
|
||||||
|
|
||||||
def delete_resource(intersight, moid):
|
|
||||||
# delete resource and create empty api_response
|
|
||||||
if not intersight.module.check_mode:
|
|
||||||
options = {
|
|
||||||
'http_method': 'delete',
|
|
||||||
'resource_path': intersight.module.params['resource_path'],
|
|
||||||
'moid': moid,
|
|
||||||
}
|
|
||||||
intersight.call_api(**options)
|
|
||||||
intersight.result['api_response'] = {}
|
|
||||||
intersight.result['changed'] = True
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argument_spec = intersight_argument_spec
|
|
||||||
argument_spec.update(
|
|
||||||
resource_path=dict(type='str', required=True),
|
|
||||||
query_params=dict(type='dict', default={}),
|
|
||||||
update_method=dict(type='str', choices=['patch', 'post'], default='patch'),
|
|
||||||
api_body=dict(type='dict', default={}),
|
|
||||||
state=dict(type='str', choices=['absent', 'present'], default='present'),
|
|
||||||
)
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec,
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
intersight = IntersightModule(module)
|
|
||||||
intersight.result['api_response'] = {}
|
|
||||||
|
|
||||||
# get the current state of the resource
|
|
||||||
intersight.result['api_response'] = get_resource(intersight)
|
|
||||||
|
|
||||||
# determine requested operation (config, delete, or neither (get resource only))
|
|
||||||
if module.params['state'] == 'present':
|
|
||||||
request_delete = False
|
|
||||||
# api_body implies resource configuration through post/patch
|
|
||||||
request_config = bool(module.params['api_body'])
|
|
||||||
else: # state == 'absent'
|
|
||||||
request_delete = True
|
|
||||||
request_config = False
|
|
||||||
|
|
||||||
moid = None
|
|
||||||
resource_values_match = False
|
|
||||||
if (request_config or request_delete) and intersight.result['api_response'].get('Moid'):
|
|
||||||
# resource exists and moid was returned
|
|
||||||
moid = intersight.result['api_response']['Moid']
|
|
||||||
if request_config:
|
|
||||||
resource_values_match = compare_values(module.params['api_body'], intersight.result['api_response'])
|
|
||||||
else: # request_delete
|
|
||||||
delete_resource(intersight, moid)
|
|
||||||
|
|
||||||
if request_config and not resource_values_match:
|
|
||||||
configure_resource(intersight, moid)
|
|
||||||
|
|
||||||
module.exit_json(**intersight.result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,45 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# This code is part of Ansible, but is an independent component.
|
|
||||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
|
||||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
|
||||||
# still belong to the author of the module, and may assign their own license
|
|
||||||
# to the complete work.
|
|
||||||
#
|
|
||||||
# (c) 2016 Red Hat Inc.
|
|
||||||
# (c) 2017 Cisco Systems Inc.
|
|
||||||
#
|
|
||||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
|
||||||
# Cisco Intersight doc fragment
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
options:
|
|
||||||
api_private_key:
|
|
||||||
description:
|
|
||||||
- 'Filename (absolute path) of a PEM formatted file that contains your private key to be used for Intersight API authentication.'
|
|
||||||
type: path
|
|
||||||
required: yes
|
|
||||||
api_uri:
|
|
||||||
description:
|
|
||||||
- URI used to access the Intersight API.
|
|
||||||
type: str
|
|
||||||
default: https://intersight.com/api/v1
|
|
||||||
api_key_id:
|
|
||||||
description:
|
|
||||||
- Public API Key ID associated with the private key.
|
|
||||||
type: str
|
|
||||||
required: yes
|
|
||||||
validate_certs:
|
|
||||||
description:
|
|
||||||
- Boolean control for verifying the api_uri TLS certificate
|
|
||||||
type: bool
|
|
||||||
default: yes
|
|
||||||
use_proxy:
|
|
||||||
description:
|
|
||||||
- If C(no), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
|
|
||||||
type: bool
|
|
||||||
default: yes
|
|
||||||
'''
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Not enabled, but can be used with Intersight by specifying API keys.
|
|
||||||
# See tasks/main.yml for examples.
|
|
||||||
unsupported
|
|
|
@ -1,152 +0,0 @@
|
||||||
---
|
|
||||||
# Test code for the Cisco Intersight modules
|
|
||||||
# Copyright 2019, David Soper (@dsoper2)
|
|
||||||
|
|
||||||
- name: Setup API access variables
|
|
||||||
debug: msg="Setup API keys"
|
|
||||||
vars:
|
|
||||||
api_info: &api_info
|
|
||||||
api_private_key: "{{ api_private_key | default('~/Downloads/SSOSecretKey.txt') }}"
|
|
||||||
api_key_id: "{{ api_key_id | default('596cc79e5d91b400010d15ad/596cc7945d91b400010d154e/5b6275df3437357030a7795f') }}"
|
|
||||||
|
|
||||||
# Setup (clean environment)
|
|
||||||
- name: Boot policy Absent
|
|
||||||
intersight_rest_api: &boot_policy_absent
|
|
||||||
<<: *api_info
|
|
||||||
resource_path: /boot/PrecisionPolicies
|
|
||||||
query_params:
|
|
||||||
$filter: "Name eq 'vmedia-localdisk'"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
# Test present (check_mode)
|
|
||||||
- name: Boot policy present (check_mode)
|
|
||||||
intersight_rest_api: &boot_policy_present
|
|
||||||
<<: *api_info
|
|
||||||
resource_path: /boot/PrecisionPolicies
|
|
||||||
query_params:
|
|
||||||
$filter: "Name eq 'vmedia-localdisk'"
|
|
||||||
api_body: {
|
|
||||||
"Name": "vmedia-localdisk",
|
|
||||||
"ConfiguredBootMode": "Legacy",
|
|
||||||
"BootDevices": [
|
|
||||||
{
|
|
||||||
"ObjectType": "boot.VirtualMedia",
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "remote-vmedia",
|
|
||||||
"Subtype": "cimc-mapped-dvd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ObjectType": "boot.LocalDisk",
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "localdisk",
|
|
||||||
"Slot": "MRAID",
|
|
||||||
"Bootloader": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
check_mode: true
|
|
||||||
register: cm_boot_policy_present
|
|
||||||
|
|
||||||
# Present (normal mode)
|
|
||||||
- name: Boot policy present (normal mode)
|
|
||||||
intersight_rest_api: *boot_policy_present
|
|
||||||
register: nm_boot_policy_present
|
|
||||||
|
|
||||||
# Test present again (idempotent)
|
|
||||||
- name: Boot policy present again (check_mode)
|
|
||||||
intersight_rest_api: *boot_policy_present
|
|
||||||
check_mode: true
|
|
||||||
register: cm_boot_policy_present_again
|
|
||||||
|
|
||||||
# Present again (normal mode)
|
|
||||||
- name: Boot policy present again (normal mode)
|
|
||||||
intersight_rest_api: *boot_policy_present
|
|
||||||
register: nm_boot_policy_present_again
|
|
||||||
|
|
||||||
# Verfiy present
|
|
||||||
- name: Verify Boot policy present results
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cm_boot_policy_present.changed == nm_boot_policy_present.changed == true
|
|
||||||
- cm_boot_policy_present_again.changed == nm_boot_policy_present_again.changed == false
|
|
||||||
|
|
||||||
# Test change (check_mode)
|
|
||||||
- name: Boot policy change (check_mode)
|
|
||||||
intersight_rest_api: &boot_policy_change
|
|
||||||
<<: *api_info
|
|
||||||
resource_path: /boot/PrecisionPolicies
|
|
||||||
query_params:
|
|
||||||
$filter: "Name eq 'vmedia-localdisk'"
|
|
||||||
api_body: {
|
|
||||||
"Name": "vmedia-localdisk",
|
|
||||||
"ConfiguredBootMode": "Legacy",
|
|
||||||
"BootDevices": [
|
|
||||||
{
|
|
||||||
"ObjectType": "boot.VirtualMedia",
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "remote-vmedia",
|
|
||||||
"Subtype": "cimc-mapped-dvd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ObjectType": "boot.LocalDisk",
|
|
||||||
"Enabled": true,
|
|
||||||
"Name": "localdisk",
|
|
||||||
"Slot": "HBA",
|
|
||||||
"Bootloader": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
check_mode: true
|
|
||||||
register: cm_boot_policy_change
|
|
||||||
|
|
||||||
# Change (normal mode)
|
|
||||||
- name: Boot policy change (normal mode)
|
|
||||||
intersight_rest_api: *boot_policy_change
|
|
||||||
register: nm_boot_policy_change
|
|
||||||
|
|
||||||
# Test change again (idempotent)
|
|
||||||
- name: Boot policy again (check_mode)
|
|
||||||
intersight_rest_api: *boot_policy_change
|
|
||||||
check_mode: true
|
|
||||||
register: cm_boot_policy_change_again
|
|
||||||
|
|
||||||
# Change again (normal mode)
|
|
||||||
- name: Boot policy change again (normal mode)
|
|
||||||
intersight_rest_api: *boot_policy_change
|
|
||||||
register: nm_boot_policy_change_again
|
|
||||||
|
|
||||||
# Verfiy change
|
|
||||||
- name: Verify Boot policy change results
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cm_boot_policy_change.changed == nm_boot_policy_change.changed == true
|
|
||||||
- cm_boot_policy_change_again.changed == nm_boot_policy_change_again.changed == false
|
|
||||||
|
|
||||||
# Teardown (clean environment)
|
|
||||||
- name: Boot policy absent (check_mode)
|
|
||||||
intersight_rest_api: *boot_policy_absent
|
|
||||||
check_mode: true
|
|
||||||
register: cm_boot_policy_absent
|
|
||||||
|
|
||||||
# Absent (normal mode)
|
|
||||||
- name: Boot policy absent (normal mode)
|
|
||||||
intersight_rest_api: *boot_policy_absent
|
|
||||||
register: nm_boot_policy_absent
|
|
||||||
|
|
||||||
# Test absent again (idempotent)
|
|
||||||
- name: Boot policy absent again (check_mode)
|
|
||||||
intersight_rest_api: *boot_policy_absent
|
|
||||||
check_mode: true
|
|
||||||
register: cm_boot_policy_absent_again
|
|
||||||
|
|
||||||
# Absent again (normal mode)
|
|
||||||
- name: Boot policy absent again (normal mode)
|
|
||||||
intersight_rest_api: *boot_policy_absent
|
|
||||||
register: nm_boot_policy_absent_again
|
|
||||||
|
|
||||||
# Verfiy absent
|
|
||||||
- name: Verify Boot policy absent results
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cm_boot_policy_absent.changed == nm_boot_policy_absent.changed == true
|
|
||||||
- cm_boot_policy_absent_again.changed == nm_boot_policy_absent_again.changed == false
|
|
|
@ -159,8 +159,6 @@ lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 pslint:PSU
|
||||||
lib/ansible/module_utils/pycompat24.py future-import-boilerplate
|
lib/ansible/module_utils/pycompat24.py future-import-boilerplate
|
||||||
lib/ansible/module_utils/pycompat24.py metaclass-boilerplate
|
lib/ansible/module_utils/pycompat24.py metaclass-boilerplate
|
||||||
lib/ansible/module_utils/pycompat24.py no-get-exception
|
lib/ansible/module_utils/pycompat24.py no-get-exception
|
||||||
lib/ansible/module_utils/remote_management/intersight.py future-import-boilerplate
|
|
||||||
lib/ansible/module_utils/remote_management/intersight.py metaclass-boilerplate
|
|
||||||
lib/ansible/module_utils/remote_management/ucs.py future-import-boilerplate
|
lib/ansible/module_utils/remote_management/ucs.py future-import-boilerplate
|
||||||
lib/ansible/module_utils/remote_management/ucs.py metaclass-boilerplate
|
lib/ansible/module_utils/remote_management/ucs.py metaclass-boilerplate
|
||||||
lib/ansible/module_utils/service.py future-import-boilerplate
|
lib/ansible/module_utils/service.py future-import-boilerplate
|
||||||
|
@ -2949,7 +2947,6 @@ lib/ansible/modules/remote_management/cpm/cpm_serial_port_info.py validate-modul
|
||||||
lib/ansible/modules/remote_management/cpm/cpm_serial_port_info.py validate-modules:parameter-type-not-in-doc
|
lib/ansible/modules/remote_management/cpm/cpm_serial_port_info.py validate-modules:parameter-type-not-in-doc
|
||||||
lib/ansible/modules/remote_management/cpm/cpm_user.py validate-modules:doc-missing-type
|
lib/ansible/modules/remote_management/cpm/cpm_user.py validate-modules:doc-missing-type
|
||||||
lib/ansible/modules/remote_management/cpm/cpm_user.py validate-modules:parameter-type-not-in-doc
|
lib/ansible/modules/remote_management/cpm/cpm_user.py validate-modules:parameter-type-not-in-doc
|
||||||
lib/ansible/modules/remote_management/intersight/intersight_rest_api.py validate-modules:parameter-type-not-in-doc
|
|
||||||
lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py validate-modules:doc-choices-do-not-match-spec
|
lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py validate-modules:doc-choices-do-not-match-spec
|
||||||
lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py validate-modules:doc-default-does-not-match-spec
|
lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py validate-modules:doc-default-does-not-match-spec
|
||||||
lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py validate-modules:doc-elements-mismatch
|
lib/ansible/modules/remote_management/ucs/ucs_disk_group_policy.py validate-modules:doc-elements-mismatch
|
||||||
|
@ -3164,8 +3161,6 @@ lib/ansible/plugins/doc_fragments/files.py future-import-boilerplate
|
||||||
lib/ansible/plugins/doc_fragments/files.py metaclass-boilerplate
|
lib/ansible/plugins/doc_fragments/files.py metaclass-boilerplate
|
||||||
lib/ansible/plugins/doc_fragments/hcloud.py future-import-boilerplate
|
lib/ansible/plugins/doc_fragments/hcloud.py future-import-boilerplate
|
||||||
lib/ansible/plugins/doc_fragments/hcloud.py metaclass-boilerplate
|
lib/ansible/plugins/doc_fragments/hcloud.py metaclass-boilerplate
|
||||||
lib/ansible/plugins/doc_fragments/intersight.py future-import-boilerplate
|
|
||||||
lib/ansible/plugins/doc_fragments/intersight.py metaclass-boilerplate
|
|
||||||
lib/ansible/plugins/doc_fragments/inventory_cache.py future-import-boilerplate
|
lib/ansible/plugins/doc_fragments/inventory_cache.py future-import-boilerplate
|
||||||
lib/ansible/plugins/doc_fragments/inventory_cache.py metaclass-boilerplate
|
lib/ansible/plugins/doc_fragments/inventory_cache.py metaclass-boilerplate
|
||||||
lib/ansible/plugins/doc_fragments/ios.py future-import-boilerplate
|
lib/ansible/plugins/doc_fragments/ios.py future-import-boilerplate
|
||||||
|
|
Loading…
Reference in a new issue