mirror of https://github.com/ansible/ansible.git
intersight_rest_api module and integration tests. (#52430)
* intersight_rest_api module and integration tests. Fix intersight module_utils issues when using POST/PATCH/DELETE. * Update json returns based on code reviewpull/67953/head
parent
008313b8cc
commit
63ea76d174
@ -0,0 +1,252 @@
|
|||||||
|
#!/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 languange.
|
||||||
|
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 paylod 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 not actual.get(key):
|
||||||
|
# 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'],
|
||||||
|
}
|
||||||
|
intersight.call_api(**options)
|
||||||
|
intersight.result['api_response'] = get_resource(intersight)
|
||||||
|
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()
|
@ -0,0 +1,3 @@
|
|||||||
|
# Not enabled, but can be used with Intersight by specifying API keys.
|
||||||
|
# See tasks/main.yml for examples.
|
||||||
|
unsupported
|
@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
# 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
|
Loading…
Reference in New Issue