diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/LICENSE b/content/ansible/roles/manageiq-core.manageiq-automate/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/README.md b/content/ansible/roles/manageiq-core.manageiq-automate/README.md new file mode 100644 index 000000000..8f839c863 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/README.md @@ -0,0 +1,249 @@ +manageiq-core.manageiq-automate +========= + +The `manageiq-core.manageiq-automate` role allows for users of ManageIQ Automate to modify and add to the Automate Workspace via an Ansible Playbook. +The role includes a module `manageiq_automate` which does all the heavy lifting needed to modify or change the Automate Workspace. + +Requirements +------------ + +ManageIQ API v3.0.0 or higher. + +The example playbook makes use of the `manageiq_automate` module which is also included as part of this role. + +Role Variables +-------------- + +Auto Commit: + `auto_commit` defaults to `True` in `defaults/main.yml`. + If set to `False` it will not auto commit back to ManageIQ each + call to a `set_` method in the `manageiq_automate` module. + +Validate Certs: + `manageiq_validate_certs` defaults to `True`. + If set to `False` in the `manageiq_connection` dictionary or + passed in via `extra_vars` or assigned in the playbook vars + then the lookup will allow self signed certificates + to be used when using SSL REST API connection urls. + +ManageIQ: + `manageiq_connection` is a dictionary with connection default keys. + Use of this connection information is ONLY needed if the role is used outside of a ManageIQ + appliance. A ManageIQ appliance passes in `manageiq_connection` via `extra_vars` so connection + information is included automatically. + Remember to use Ansible Vault for passwords. + `automate_workspace` is the href slug and guid required to talk to the Automate Workspace. + +``` + manageiq_connection: + url: 'https://localhost.ssl:3000' + username: 'admin' + password: 'password' + automate_workspace: 'automate_workspaces/1234' + manageiq_validate_certs: false +``` + +Workspace: + `workspace` instantiated via `tasks/main.yml`. + The current version of the workspace as it is modified via methods + in the `manageiq_automate` module. + +Dependencies +------------ + +None + +Example Playbook +---------------- + +A verbose example with manual strings passed to each method of the +`manageiq_automate` module + +``` +- name: Modify the Automate Workspace + hosts: localhost + connection: local + + gather_facts: False + vars: + - auto_commit: True + # Only needed if this playbook is NOT run on a ManageIQ Appliance + - manageiq_connection: + url: 'https://localhost.ssl:3000' + username: 'admin' + password: 'password' + automate_workspace: 'automate_workspaces/1234' + manageiq_validate_certs: false + + roles: + - manageiq-core.manageiq-automate + + tasks: + - name: "Check an attribute" + manageiq_automate: + workspace: "{{ workspace }}" + attribute_exists: + object: "/ManageIQ/System/Request/call_instance" + attribute: "::miq::parent" + + - name: "Get an attribute" + manageiq_automate: + workspace: "{{ workspace }}" + get_attribute: object: "/ManageIQ/System/Request/call_instance" + attribute: "::miq::parent" + + - name: "Check a state_var" + manageiq_automate: + workspace: "{{ workspace }}" + state_var_exists: + attribute: "task_id" + + - name: "Get a state_var" + manageiq_automate: + workspace: "{{ workspace }}" + get_state_var: + attribute: "task_id" + + - name: "Set a State Var" + manageiq_automate: + workspace: "{{ workspace }}" + set_state_var: + attribute: "job_id" + value: "xyz" + register: workspace + + - name: "Check a Method Parameter" + manageiq_automate: + workspace: "{{ workspace }}" + method_parameter_exists: + parameter: "task_id" + + - name: "Get a Method Parameter" + manageiq_automate: + workspace: "{{ workspace }}" + get_method_parameter: + parameter: "invoice" + + - name: "Get the full list of Objects" + manageiq_automate: + workspace: "{{ workspace }}" + get_object_names: yes + + - name: "Get the list of Method Parameters" + manageiq_automate: + workspace: "{{ workspace }}" + get_method_parameters: yes + register: method_params + + - name: "Get the list of State Vars" + manageiq_automate: + workspace: "{{ workspace }}" + get_state_var_names: yes + + - name: "Get the full list of Object Attribute Names" + manageiq_automate: + workspace: "{{ workspace }}" + get_object_attribute_names: + object: "root" + + - name: "Set an attribute" + manageiq_automate: + workspace: "{{ workspace }}" + set_attribute: + object: "root" + attribute: "my_name" + value: "jim" + register: workspace + + - name: "Set attributes" + manageiq_automate: + workspace: "{{ workspace }}" + set_attributes: + object: "root" + attributes: + family_name: "timmer" + eldest_son: "reed" + youngest_son: "olaf" + register: workspace + + - name: Decrypt an attribute from an object + manageiq_automate: + workspace: "{{ workspace }}" + get_decrypted_attribute: + object: root + attribute: fred + register: decrypted_attribute + + - debug: msg=decrypted_attribute + + - name: Decrypt a method_parameter from an object + manageiq_automate: + workspace: "{{ workspace }}" + get_decrypted_method_parameter: + attribute: fred + + - name: Encrypt an object attribute + manageiq_automate: + workspace: "{{ workspace }}" + set_encrypted_attribute: + object: root + attribute: freddy + value: 'smartvm' + + - name: Grab a vmdb object + manageiq_automate: + workspace: "{{ workspace }}" + get_vmdb_object: + object: root + attribute: miq_group + +``` + +An example making use of variable substitution to modify some object +attributes with passed in `method_parameters` and change the retry. + +``` +- name: Siphon Method Parameters into an object + hosts: localhost + connection: local + vars: + - auto_commit: True + - object: root + - interval: 600 + # Only needed if this playbook is NOT run on a ManageIQ Appliance + - manageiq_connection: + url: 'https://localhost.ssl:3000' + username: 'admin' + password: 'password' + automate_workspace: 'automate_workspaces/1234' + manageiq_validate_certs: false + + gather_facts: False + roles: + - manageiq-core.manageiq-automate + + tasks: + - name: "Get the list of Method Parameters" + manageiq_automate: + workspace: "{{ workspace }}" + get_method_parameters: yes + register: method_params + + - name: "Set attributes" + manageiq_automate: + workspace: "{{ workspace }}" + set_attributes: + object: "{{ object }}" + attributes: "{{ method_params.value }}" + + - name: Set Retry + manageiq_automate: + workspace: "{{ workspace }}" + set_retry: + interval: "{{ interval }}" +``` + +License +------- + +Apache diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/action_plugins/manageiq_automate.py b/content/ansible/roles/manageiq-core.manageiq-automate/action_plugins/manageiq_automate.py new file mode 100644 index 000000000..0006a6a57 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/action_plugins/manageiq_automate.py @@ -0,0 +1,55 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +from ansible.plugins.action import ActionBase +from ansible.utils.vars import merge_hash + +MANAGEIQ_MODULE_VARS = ('username', + 'password', + 'url', + 'token', + 'group', + 'automate_workspace', + 'X_MIQ_Group', + 'manageiq_validate_certs', + 'force_basic_auth', + 'client_cert', + 'client_key') + + + +class ActionModule(ActionBase): + + def manageiq_extra_vars(self, module_vars, task_vars): + if 'manageiq_connection' in task_vars.keys(): + module_vars['manageiq_connection'] = task_vars['manageiq_connection'] + if 'manageiq_validate_certs' in task_vars.keys(): + module_vars['manageiq_connection']['manageiq_validate_certs'] = task_vars.get('manageiq_validate_certs') + if 'manageiq' not in task_vars.keys(): + return module_vars + + + if 'manageiq_connection' not in module_vars.keys() or module_vars['manageiq_connection'] is None: + module_vars['manageiq_connection'] = dict() + + for k in MANAGEIQ_MODULE_VARS: + if k not in module_vars['manageiq_connection'].keys(): + try: + module_vars['manageiq_connection'][k] = task_vars['manageiq'][k] + except KeyError: + pass + + + return module_vars + + + def run(self, tmp=None, task_vars=None): + results = super(ActionModule, self).run(tmp, task_vars or dict()) + + module_vars = self.manageiq_extra_vars(self._task.args.copy(), task_vars) + + results = merge_hash( + results, + self._execute_module(module_args=module_vars, task_vars=task_vars), + ) + + return results diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/defaults/main.yml b/content/ansible/roles/manageiq-core.manageiq-automate/defaults/main.yml new file mode 100644 index 000000000..cd925accc --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/defaults/main.yml @@ -0,0 +1,3 @@ +--- +# Auto Commit defaults to True +auto_commit: True diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/examples/.gitkeep b/content/ansible/roles/manageiq-core.manageiq-automate/examples/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/handlers/main.yml b/content/ansible/roles/manageiq-core.manageiq-automate/handlers/main.yml new file mode 100644 index 000000000..7cb97d029 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/handlers/main.yml @@ -0,0 +1,6 @@ +--- +# handlers file for ansible_manageiq +- name: "Commit the Workspace" + manageiq_automate: + workspace: "{{ workspace }}" + commit_workspace: yes diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/library/manageiq_automate.py b/content/ansible/roles/manageiq-core.manageiq-automate/library/manageiq_automate.py new file mode 100644 index 000000000..8ff2965f7 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/library/manageiq_automate.py @@ -0,0 +1,492 @@ +#! /usr/bin/python + +from __future__ import (absolute_import, division, print_function) +import os + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +module: manageiq_automate +''' +import json +import operator +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url + + +class ManageIQAutomate(object): + """ + Object to execute automate workspace management operations in manageiq. + """ + + def __init__(self, module, workspace): + self._target = workspace + self._module = module + self._api_url = self._module.params['manageiq_connection']['url'] + '/api' + self._auth = self._build_auth() + + + def _build_auth(self): + self._headers = {'Content-Type': 'application/json; charset=utf-8'} + # Force CERT validation to work with fetch_url + self._module.params['validate_certs'] = self._module.params['manageiq_connection']['manageiq_validate_certs'] + for cert in ('force_basic_auth', 'client_cert', 'client_key'): + self._module.params[cert] = self._module.params['manageiq_connection'][cert] + if self._module.params['manageiq_connection'].get('token'): + self._headers["X-Auth-Token"] = self._module.params['manageiq_connection']['token'] + else: + self._module.params['url_username'] = self._module.params['manageiq_connection']['username'] + self._module.params['url_password'] = self._module.params['manageiq_connection']['password'] + + + + def url(self): + """ + The url to connect to the workspace + """ + url_str = self._module.params['manageiq_connection']['automate_workspace'] + return self._api_url + '/' + url_str + + + def href_slug_url(self, value): + """ + The url to connect to the vmdb + """ + base_url = value.split('::')[1] + return self._api_url + '/' + base_url + + + def get(self, alt_url=None): + """ + Get any attribute, object from the REST API + """ + if alt_url: + url = alt_url + else: + url = self.url() + + result, _info = fetch_url(self._module, url, None, self._headers, 'get') + return json.loads(result.read()) + + + def set(self, data): + """ + Set any attribute, object from the REST API + """ + post_data = json.dumps(dict(action='edit', resource=data)) + result, _info = fetch_url(self._module, self.url(), post_data, self._headers, 'post') + return json.loads(result.read()) + + + def encrypt(self, data): + """ + Set any attribute, object from the REST API + """ + post_data = json.dumps(dict(action='encrypt', resource=data)) + result, _info = fetch_url(self._module, self.url(), post_data, self._headers, 'post') + return json.loads(result.read()) + + + def decrypt(self, data): + """ + Decrypt any attribute, object from the REST API + """ + post_data = json.dumps(dict(action='decrypt', resource=data)) + result, _info = fetch_url(self._module, self.url(), post_data, self._headers, 'post') + return json.loads(result.read()) + + + def exists(self, path): + """ + Validate all passed objects before attempting to set or get values from them + """ + list_path = path.split("|") + try: + return bool(reduce(operator.getitem, list_path, self._target)) + except KeyError as error: + return False + + + def auto_commit(self): + """ ManageIQAutomate + + Returns: + Boolean auto_commit on or off + """ + return bool(self._target['workspace']['options'].get('auto_commit')) + + +class Workspace(ManageIQAutomate): + """ + Object to modify and get the Workspace + """ + + def current(self): + current_path = '/' + self._target['workspace']['current']['namespace'] + '/' + self._target['workspace']['current']['class'] + '/' + self._target['workspace']['current']['instance'] + return current_path + + + def set_or_commit(self): + """ + Commit the workspace or return the current version + """ + if self.auto_commit(): + return self.commit_workspace() + return dict(changed=True, workspace=self._target['workspace']) + + + def get_real_object_name(self, dict_options): + if dict_options['object'] == 'current': + return self.current() + return dict_options['object'] + + + def object_exists(self, dict_options): + """ + Check if the specific object exists + """ + + search_path = "workspace|input|objects|" + self.get_real_object_name(dict_options) + + if self.exists(search_path): + return dict(changed=False, value=True) + return dict(changed=False, value=False) + + + def attribute_exists(self, dict_options): + """ + Check if the specific attribute exists + """ + + obj = self.get_real_object_name(dict_options) + attribute = dict_options['attribute'] + path = "workspace|input|objects" + search_path = "|".join([path, obj, attribute]) + if self.exists(search_path): + return dict(changed=False, value=True) + return dict(changed=False, value=False) + + + def state_var_exists(self, dict_options): + """ + Check if the specific state_var exists + """ + + attribute = dict_options['attribute'] + path = "workspace|input|state_vars" + search_path = "|".join([path, attribute]) + if self.exists(search_path): + return dict(changed=False, value=True) + return dict(changed=False, value=False) + + + def method_parameter_exists(self, dict_options): + """ + Check if the specific method_parameter exists + """ + + parameter = dict_options['parameter'] + path = "workspace|input|method_parameters" + search_path = "|".join([path, parameter]) + if self.exists(search_path): + return dict(changed=False, value=True) + return dict(changed=False, value=False) + + + def get_decrypted_attribute(self, dict_options): + decrypted_attribute = self.decrypt(dict_options) + return dict(changed=False, value=decrypted_attribute) + + + def get_decrypted_method_parameter(self, dict_options): + decrypted_dict = dict(object='method_parameters', attribute=dict_options['attribute']) + decrypted_attribute = self.decrypt(decrypted_dict) + return dict(changed=False, value=decrypted_attribute) + + + def get_attribute(self, dict_options): + """ + Get the passed in attribute from the Workspace + """ + + if self.attribute_exists(dict_options)['value']: + return_value = self._target['workspace']['input']['objects'][dict_options['object']][dict_options['attribute']] + + return dict(changed=False, value=return_value) + else: + self._module.fail_json(msg='Object %s Attribute %s does not exist' % (dict_options['object'], dict_options['attribute'])) + + + def get_state_var(self, dict_options): + """ + Get the passed in state_var from the Workspace + """ + return_value = None + + if self.state_var_exists(dict_options)['value']: + return_value = self._target['workspace']['input']['state_vars'][dict_options['attribute']] + + return dict(changed=False, value=return_value) + else: + self._module.fail_json(msg='State Var %s does not exist' % dict_options['attribute']) + + + def get_method_parameter(self, dict_options): + """ + Get the passed in method_parameter from the Workspace + """ + return_value = None + + if self.method_parameter_exists(dict_options)['value']: + return_value = self._target['workspace']['input']['method_parameters'][dict_options['parameter']] + + return dict(changed=False, value=return_value) + else: + self._module.fail_json(msg='Method Parameter %s does not exist' % dict_options['parameter']) + + + def get_object_names(self): + """ + Get a list of all current object names + """ + + return_value = self._target['workspace']['input']['objects'].keys() + return dict(changed=False, value=return_value) + + + def get_method_parameters(self): + """ + Get a list of all current method_paramters + """ + + return_value = self._target['workspace']['input']['method_parameters'] + return dict(changed=False, value=return_value) + + + def get_state_var_names(self): + """ + Get a list of all current state_var names + """ + + return_value = self._target['workspace']['input']['state_vars'].keys() + return dict(changed=False, value=return_value) + + + def get_object_attribute_names(self, dict_options): + """ + Get a list of all object_attribute names + """ + + if self.object_exists(dict_options): + return_value = self._target['workspace']['input']['objects'][dict_options['object']].keys() + return dict(changed=False, value=return_value) + else: + self._module.fail_json(msg='Object %s does not exist' % dict_options['object']) + + + def get_vmdb_object(self, dict_options): + """ + Get a vmdb object via an href_slug passed in on an attribute + """ + result = self.get_attribute(dict_options) + attribute = dict_options['attribute'] + obj = dict_options['object'] + if self.object_exists(dict_options): + vmdb_object = self.get(self.href_slug_url(result['value'])) + return dict(changed=False, value=vmdb_object) + else: + self._module.fail_json(msg='Attribute %s does not exist for Object %s' % (attribute, obj)) + + + def set_state_var(self, dict_options): + """ + Set the state_var called with the passed in value + """ + + new_attribute = dict_options['attribute'] + new_value = dict_options['value'] + self._target['workspace']['input']['state_vars'][new_attribute] = new_value + self._target['workspace']['output']['state_vars'][new_attribute] = new_value + return self.set_or_commit() + + + def set_retry(self, dict_options): + """ + Set Retry + """ + attributes = dict() + attributes['object'] = 'root' + attributes['attributes'] = dict(ae_result='retry', ae_retry_limit=dict_options['interval']) + + self.set_attributes(attributes) + return self.set_or_commit() + + + def set_encrypted_attribute(self, dict_options): + encrypted_attribute = self.encrypt(dict_options) + return dict(changed=True, value=encrypted_attribute) + + + def set_attribute(self, dict_options): + """ + Set the attribute called on the object with the passed in value + """ + + new_attribute = dict_options['attribute'] + new_value = dict_options['value'] + obj = self.get_real_object_name(dict_options) + if self.object_exists(dict_options): + self._target['workspace']['input']['objects'][obj][new_attribute] = new_value + new_dict = {obj:{new_attribute: new_value}} + self._target['workspace']['output']['objects'] = new_dict + return self.set_or_commit() + else: + msg = 'Failed to set the attribute %s with value %s for %s' % (new_attribute, new_value, obj) + self._module.fail_json(msg=msg, changed=False) + + + def set_attributes(self, dict_options): + """ + Set the attributes called on the object with the passed in values + """ + new_attributes = dict_options['attributes'] + + obj = dict_options['object'] + if self.object_exists(dict_options): + for new_attribute, new_value in new_attributes.iteritems(): + self._target['workspace']['input']['objects'][obj][new_attribute] = new_value + if self._target['workspace']['output']['objects'].get(obj) is None: + self._target['workspace']['output']['objects'][obj] = dict() + self._target['workspace']['output']['objects'][obj][new_attribute] = new_value + return self.set_or_commit() + else: + msg = 'Failed to set the attributes %s for %s' % (new_attributes, obj) + self._module.fail_json(msg=msg, changed=False) + + + def commit_workspace(self): + """ + Commit the workspace and re apply the auto_commit options + """ + auto_commit_dict = self._target['workspace'].get('options') + workspace = self.set(self._target['workspace']['output']) + if 'options' not in workspace.keys(): + workspace['options'] = auto_commit_dict + return dict(changed=True, workspace=workspace) + + + def initialize_workspace(self, dict_options): + """ + Initialize the Workspace with auto_commit set to true or false + """ + + workspace = self.get() + workspace['options'] = dict(auto_commit=(dict_options.get('auto_commit') or False)) + workspace['output'] = dict(objects=dict(), state_vars=dict()) + + return dict(changed=False, workspace=workspace) + + +def manageiq_argument_spec(): + return dict( + url=dict(default=os.environ.get('MIQ_URL', None)), + username=dict(default=os.environ.get('MIQ_USERNAME', None)), + password=dict(default=os.environ.get('MIQ_PASSWORD', None), no_log=True), + token=dict(default=os.environ.get('MIQ_TOKEN', None), no_log=True), + automate_workspace=dict(default=None, type='str', no_log=True), + group=dict(default=None, type='str'), + X_MIQ_Group=dict(default=None, type='str'), + manageiq_validate_certs=dict(required=False, type='bool', default=True), + force_basic_auth=dict(required=False, type='bool', default='no'), + client_cert=dict(required=False, type='path', default=None), + client_key=dict(required=False, type='path', default=None), + ) + + +def main(): + """ + The entry point to the ManageIQ Automate module + """ + module = AnsibleModule( + argument_spec=dict( + manageiq_connection=dict(required=True, type='dict', + options=manageiq_argument_spec()), + initialize_workspace=dict(required=False, type='dict'), + commit_workspace=dict(type='bool', default=False), + set_attribute=dict(required=False, type='dict'), + set_attributes=dict(required=False, type='dict'), + object_exists=dict(required=False, type='str'), + attribute_exists=dict(required=False, type='dict'), + state_var_exists=dict(required=False, type='dict'), + method_parameter_exists=dict(required=False, type='dict'), + commit_attribute=dict(required=False, type='dict'), + commit_attributes=dict(required=False, type='dict'), + commit_state_var=dict(required=False, type='dict'), + get_attribute=dict(required=False, type='dict'), + get_state_var=dict(required=False, type='dict'), + get_method_parameter=dict(required=False, type='dict'), + set_retry=dict(required=False, type='dict'), + set_state_var=dict(required=False, type='dict'), + set_encrypted_attribute=dict(required=False, type='dict'), + get_vmdb_object=dict(required=False, type='dict'), + get_decrypted_attribute=dict(required=False, type='dict'), + get_decrypted_method_parameter=dict(required=False, type='dict'), + get_object_names=dict(required=False, type='bool'), + get_state_var_names=dict(required=False, type='bool'), + get_method_parameters=dict(required=False, type='bool'), + get_object_attribute_names=dict(required=False, type='dict'), + workspace=dict(required=False, type='dict') + ), + ) + + argument_opts = { + 'initialize_workspace':module.params['initialize_workspace'], + 'commit_workspace':module.params['commit_workspace'], + 'get_attribute':module.params['get_attribute'], + 'get_method_parameter':module.params['get_method_parameter'], + 'get_state_var':module.params['get_state_var'], + 'get_object_attribute_names':module.params['get_object_attribute_names'], + 'get_vmdb_object':module.params['get_vmdb_object'], + 'get_decrypted_attribute':module.params['get_decrypted_attribute'], + 'get_decrypted_method_parameter':module.params['get_decrypted_method_parameter'], + 'object_exists':module.params['object_exists'], + 'method_parameter_exists':module.params['method_parameter_exists'], + 'attribute_exists':module.params['attribute_exists'], + 'state_var_exists':module.params['state_var_exists'], + 'set_attribute':module.params['set_attribute'], + 'set_attributes':module.params['set_attributes'], + 'set_encrypted_attribute':module.params['set_encrypted_attribute'], + 'set_retry':module.params['set_retry'], + 'set_state_var':module.params['set_state_var'] + } + + boolean_opts = { + 'get_object_names':module.params['get_object_names'], + 'get_method_parameters':module.params['get_method_parameters'], + 'get_state_var_names':module.params['get_state_var_names'] + } + + workspace = Workspace(module, module.params['workspace']) + + for key, value in boolean_opts.iteritems(): + if value: + result = getattr(workspace, key)() + module.exit_json(**result) + for key, value in argument_opts.iteritems(): + if value: + result = getattr(workspace, key)(value) + module.exit_json(**result) + + + module.fail_json(msg="No workspace found") + + +if __name__ == "__main__": + main() diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/meta/main.yml b/content/ansible/roles/manageiq-core.manageiq-automate/meta/main.yml new file mode 100644 index 000000000..dc98a6983 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/meta/main.yml @@ -0,0 +1,7 @@ +--- +galaxy_info: + author: "ManageIQ Developers" + license: license (Apache) + min_ansible_version: 2.2 + galaxy_tags: [] +dependencies: [] diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/tasks/main.yml b/content/ansible/roles/manageiq-core.manageiq-automate/tasks/main.yml new file mode 100644 index 000000000..9461029d8 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- name: "Initialize the Workspace" + manageiq_automate: + initialize_workspace: + auto_commit: "{{ auto_commit }}" + register: workspace diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/tests/inventory b/content/ansible/roles/manageiq-core.manageiq-automate/tests/inventory new file mode 100644 index 000000000..878877b07 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/tests/test.yml b/content/ansible/roles/manageiq-core.manageiq-automate/tests/test.yml new file mode 100644 index 000000000..ca5f04d23 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - manageiq-core.manageiq-automate diff --git a/content/ansible/roles/manageiq-core.manageiq-automate/vars/main.yml b/content/ansible/roles/manageiq-core.manageiq-automate/vars/main.yml new file mode 100644 index 000000000..8943c4fef --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-automate/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for manageiq-core.manageiq-automate diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/LICENSE b/content/ansible/roles/manageiq-core.manageiq-vmdb/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/README.md b/content/ansible/roles/manageiq-core.manageiq-vmdb/README.md new file mode 100644 index 000000000..bc3e42d7d --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/README.md @@ -0,0 +1,119 @@ +manageiq-core.manageiq-vmdb +========= + +The `manageiq-core.manageiq-vmdb` role allows for users of ManageIQ to modify and/or change VMDB objects via an Ansible Playbook. +The role includes a module `manageiq_vmdb` which does all the heavy lifting needed to modify or change objects in the database. + +Requirements +------------ + +The example playbook makes use of the `manageiq_vmdb` module which is also included as part of this role. + +Role Variables +-------------- + +Validate Certs: + `manageiq_validate_certs` defaults to `True`. + If set to `False` in the `manageiq_connection` dictionary + then the lookup will allow self signed certificates + to be used when using SSL REST API connection urls. + +ManageIQ: + `manageiq_connection` is a dictionary with connection default keys. + Use of this connection information is ONLY needed if the role is used outside of a ManageIQ + appliance. A ManageIQ appliance passes in `manageiq_connection` via `extra_vars` so connection + information is included automatically. + Remember to use Ansible Vault for passwords. + +``` + manageiq_connection: + url: 'https://localhost.ssl:3000' + username: 'admin' + password: 'password' + manageiq_validate_certs: false +``` + +Dependencies +------------ + +None + +Example Playbook +---------------- + +An example which provisions a VM to EC2. The playbook +links that vm to a service in the ManageIQ VMDB using the +`manageiq_vmdb` module. +The example shows two ways to pass +the VMDB object to the module, either via an href slug or +via a variable. + +``` +- name: Service Linking VM's to an existing service + hosts: localhost + connection: local + gather_facts: False + + vars: + - key: db + - name: db-test-provision-1 + - instance_type: t2.nano + - security_group: sg-sdf234 + - image: ami-234234lkj + - region: us-east-1 + - subnet: subnet-adsf098 + # Only needed if this playbook is NOT run on a ManageIQ Appliance + - manageiq_connection: + url: 'https://localhost.ssl:3000' + username: 'admin' + password: 'smartvm' + manageiq_validate_certs: false + + roles: + - manageiq-core.manageiq-vmdb + + tasks: + - name: Get a vmdb object + manageiq_vmdb: + href: "services/80" + register: vmdb_object + + - name: Create Ec2 Instance + ec2: + key_name: "{{ key }}" + instance_tags: {Name: "{{ name }}"} + group_id: "{{ security_group }}" + instance_type: "{{ instance_type }}" + region: "{{ region }}" + image: "{{ image }}" + wait: yes + count: 1 + vpc_subnet_id: "{{ subnet }}" + assign_public_ip: yes + register: ec2 + + - name: Service Linking via an href slug + manageiq_vmdb: + href: "href_slug::services/80" + action: add_provider_vms + data: + uid_ems: + - "{{ ec2.instances[0].id }}" + provider: + id: 24 + + - name: Service Linking via an object + manageiq_vmdb: + vmdb: "{{ vmdb_object }}" + action: add_provider_vms + data: + uid_ems: + - "asdf234" + provider: + id: 24 +``` + +License +------- + +Apache diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/action_plugins/manageiq_vmdb.py b/content/ansible/roles/manageiq-core.manageiq-vmdb/action_plugins/manageiq_vmdb.py new file mode 100644 index 000000000..ac69a2efe --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/action_plugins/manageiq_vmdb.py @@ -0,0 +1,53 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +from ansible.plugins.action import ActionBase +from ansible.utils.vars import merge_hash + +MANAGEIQ_MODULE_VARS = ('username', + 'password', + 'url', + 'token', + 'group', + 'X_MIQ_Group', + 'manageiq_validate_certs', + 'force_basic_auth', + 'client_cert', + 'client_key') + + +class ActionModule(ActionBase): + + def manageiq_extra_vars(self, module_vars, task_vars): + if 'manageiq_connection' in task_vars.keys(): + module_vars['manageiq_connection'] = task_vars['manageiq_connection'] + if 'manageiq_validate_certs' in task_vars.keys(): + module_vars['manageiq_connection']['manageiq_validate_certs'] = task_vars.get('manageiq_validate_certs') + if 'manageiq' not in task_vars.keys(): + return module_vars + + + if 'manageiq_connection' not in module_vars.keys() or module_vars['manageiq_connection'] is None: + module_vars['manageiq_connection'] = dict() + + for k in MANAGEIQ_MODULE_VARS: + if k not in module_vars['manageiq_connection'].keys(): + try: + module_vars['manageiq_connection'][k] = task_vars['manageiq'][k] + except KeyError: + pass + + + return module_vars + + + def run(self, tmp=None, task_vars=None): + results = super(ActionModule, self).run(tmp, task_vars or dict()) + + module_vars = self.manageiq_extra_vars(self._task.args.copy(), task_vars) + + results = merge_hash( + results, + self._execute_module(module_args=module_vars, task_vars=task_vars), + ) + + return results diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/defaults/main.yml b/content/ansible/roles/manageiq-core.manageiq-vmdb/defaults/main.yml new file mode 100644 index 000000000..b22580c2e --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# Set defaults here if needed diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/handlers/main.yml b/content/ansible/roles/manageiq-core.manageiq-vmdb/handlers/main.yml new file mode 100644 index 000000000..b8dae4883 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for manageiq-core.manageiq-vmdb diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/library/manageiq_vmdb.py b/content/ansible/roles/manageiq-core.manageiq-vmdb/library/manageiq_vmdb.py new file mode 100644 index 000000000..077b45bc6 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/library/manageiq_vmdb.py @@ -0,0 +1,199 @@ +#! /usr/bin/python + +from __future__ import (absolute_import, division, print_function) +import os + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +module: manageiq_vmdb +''' +import json +import re +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.urls import fetch_url +from ansible.module_utils.six.moves.urllib.parse import urlparse + +class ManageIQVmdb(object): + """ + Object to execute VMDB management operations in manageiq. + """ + + def __init__(self, module): + self._module = module + self._debug = bool(self._module._verbosity >= 3) + self._api_url = self._module.params['manageiq_connection']['url'] + self._vmdb = self._module.params.get('vmdb') or self._module.params.get('href') + self._href = None + self._error = None + self._auth = self._build_auth() + + + def _build_auth(self): + self._headers = {'Content-Type': 'application/json; charset=utf-8'} + # Force CERT validation to work with fetch_url + self._module.params['validate_certs'] = self._module.params['manageiq_connection']['manageiq_validate_certs'] + for cert in ('force_basic_auth', 'client_cert', 'client_key'): + self._module.params[cert] = self._module.params['manageiq_connection'][cert] + if self._module.params['manageiq_connection'].get('token'): + self._headers["X-Auth-Token"] = self._module.params['manageiq_connection']['token'] + else: + self._module.params['url_username'] = self._module.params['manageiq_connection']['username'] + self._module.params['url_password'] = self._module.params['manageiq_connection']['password'] + + + + + @property + def url(self): + """ + The url to connect to the VMDB Object + """ + return self.build_url() + + + def build_url(self): + """ + Using any type of href input, build out the correct url + """ + + url_actual = urlparse(self._href) + if re.search('api', url_actual.path): + return self._api_url + url_actual.path + return self._api_url + '/api/' + url_actual.path + + + def build_result(self, method, data=None): + """ + Make the REST call and return the result to the caller + """ + result, info = fetch_url(self._module, self.url, data, self._headers, method) + try: + vmdb = json.loads(result.read()) + if self._debug: + vmdb['debug'] = info + return vmdb + except AttributeError: + self._module.fail_json(msg=info) + return json.loads(result.read()) + + + def get(self): + """ + Get any attribute, object from the REST API + """ + return self.build_result('get') + + + def set(self, post_dict): + """ + Set any attribute, object from the REST API + """ + post_data = json.dumps(dict(action=post_dict['action'], resource=post_dict['resource'])) + return self.build_result('post', post_data) + + + def parse(self, item): + """ + Read what is passed in and set the _href instance variable + """ + if isinstance(item, dict): + self._href = self._vmdb['href'] + elif isinstance(item, str): + slug = item.split("::") + if len(slug) == 2: + self._href = slug[1] + return + self._href = item + + + def exists(self, path): + """ + Validate all passed objects before attempting to set or get values from them + """ + result = self.get() + actions = [d['name'] for d in result['actions']] + return bool(path in actions) + + +class Vmdb(ManageIQVmdb): + """ + Object to modify and get the Vmdb Object + """ + + def get_object(self): + """ + Return the VMDB Object + """ + self.parse(self._vmdb) + return dict(self.get()) + + + def action(self): + """ + Call an action if it exists + """ + self.parse(self._vmdb) + data = self._module.params['data'] + action_string = self._module.params.get('action') + + if self.exists(action_string): + result = self.set(dict(action=action_string, resource=data)) + if result or result['success']: + return dict(changed=True, value=result) + return self._module.fail_json(msg=result['message']) + return self._module.fail_json(msg="Action not found") + + +def manageiq_argument_spec(): + return dict( + url=dict(default=os.environ.get('MIQ_URL', None)), + username=dict(default=os.environ.get('MIQ_USERNAME', None)), + password=dict(default=os.environ.get('MIQ_PASSWORD', None), no_log=True), + token=dict(default=os.environ.get('MIQ_TOKEN', None), no_log=True), + automate_workspace=dict(default=None, type='str', no_log=True), + group=dict(default=None, type='str'), + X_MIQ_Group=dict(default=None, type='str'), + manageiq_validate_certs=dict(required=False, type='bool', default=True), + force_basic_auth=dict(required=False, type='bool', default='no'), + client_cert=dict(required=False, type='path', default=None), + client_key=dict(required=False, type='path', default=None) + ) + + +def main(): + """ + The entry point to the ManageIQ Vmdb module + """ + module = AnsibleModule( + argument_spec=dict( + manageiq_connection=dict(required=True, type='dict', + options=manageiq_argument_spec()), + vmdb=dict(required=False, type='dict'), + action=dict(required=False, type='str'), + href=dict(required=False, type='str'), + data=dict(required=False, type='dict') + ), + required_one_of=[['vmdb', 'href']] + ) + + + vmdb = Vmdb(module) + + if module.params.get('action'): + result = vmdb.action() + module.exit_json(**result) + else: + result = vmdb.get_object() + module.exit_json(**result) + + module.fail_json(msg="No VMDB object found") + + +if __name__ == "__main__": + main() diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/meta/main.yml b/content/ansible/roles/manageiq-core.manageiq-vmdb/meta/main.yml new file mode 100644 index 000000000..dc98a6983 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/meta/main.yml @@ -0,0 +1,7 @@ +--- +galaxy_info: + author: "ManageIQ Developers" + license: license (Apache) + min_ansible_version: 2.2 + galaxy_tags: [] +dependencies: [] diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/tasks/main.yml b/content/ansible/roles/manageiq-core.manageiq-vmdb/tasks/main.yml new file mode 100644 index 000000000..ed97d539c --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/tasks/main.yml @@ -0,0 +1 @@ +--- diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/tests/inventory b/content/ansible/roles/manageiq-core.manageiq-vmdb/tests/inventory new file mode 100644 index 000000000..878877b07 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/tests/test.yml b/content/ansible/roles/manageiq-core.manageiq-vmdb/tests/test.yml new file mode 100644 index 000000000..a846874ca --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - manageiq-core.manageiq-vmdb diff --git a/content/ansible/roles/manageiq-core.manageiq-vmdb/vars/main.yml b/content/ansible/roles/manageiq-core.manageiq-vmdb/vars/main.yml new file mode 100644 index 000000000..a215da9d1 --- /dev/null +++ b/content/ansible/roles/manageiq-core.manageiq-vmdb/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for manageiq-core.manageiq-vmdb