From c6a45234e094922786de78a83db3028804cc9141 Mon Sep 17 00:00:00 2001 From: mathieu bultel Date: Mon, 11 Jul 2016 19:36:00 +0200 Subject: [PATCH] Add os_stack module for create, update and delete stack (#2002) * Add os_stack module for create and delete stack * Add ansible module for creating and deleting heat stack * Parameters: - stack name - template - environment_files (list) - parameters (dict) - timeout - rollback - state: In a near futur I would like to improve this module by providing a way updating the stack if already exist. Shade doesn't offer this functionality AFAIK * Add update stack feature * Update added_version and return doc * Add copyright for os_stack module * Add wait user choice and minor fixes * Remove Todo for Shade 1.8.0 and bad line example * Add documentation for the return values * Fix type on return value * Fix yaml syntax * Cast message to string instead * add missing check mode --- cloud/openstack/os_stack.py | 262 ++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 cloud/openstack/os_stack.py diff --git a/cloud/openstack/os_stack.py b/cloud/openstack/os_stack.py new file mode 100644 index 00000000000..503ae635dbb --- /dev/null +++ b/cloud/openstack/os_stack.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (c) 2016, Mathieu Bultel +# (c) 2016, Steve Baker +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +from time import sleep +from distutils.version import StrictVersion +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +--- +module: os_stack +short_description: Add/Remove Heat Stack +extends_documentation_fragment: openstack +version_added: "2.2" +author: "Mathieu Bultel (matbu), Steve Baker (steveb)" +description: + - Add or Remove a Stack to an OpenStack Heat +options: + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + required: false + default: present + name: + description: + - Name of the stack that should be created, name could be char and digit, no space + required: true + template: + description: + - Path of the template file to use for the stack creation + required: false + default: None + environment: + description: + - List of environment files that should be used for the stack creation + required: false + default: None + parameters: + description: + - Dictionary of parameters for the stack creation + required: false + default: None + rollback: + description: + - Rollback stack creation + required: false + default: false + timeout: + description: + - Maximum number of seconds to wait for the stack creation + required: false + default: 3600 +requirements: + - "python >= 2.6" + - "shade" +''' +EXAMPLES = ''' +--- +- name: create stack + ignore_errors: True + register: stack_create + os_stack: + name: "{{ stack_name }}" + state: present + template: "/path/to/my_stack.yaml" + environment: + - /path/to/resource-registry.yaml + - /path/to/environment.yaml + parameters: + bmc_flavor: m1.medium + bmc_image: CentOS + key_name: default + private_net: {{ private_net_param }} + node_count: 2 + name: undercloud + image: CentOS + my_flavor: m1.large + external_net: {{ external_net_param }} +''' + +RETURN = ''' +id: + description: Stack ID. + type: string + sample: "97a3f543-8136-4570-920e-fd7605c989d6" + +stack: + action: + description: Action, could be Create or Update. + type: string + sample: "CREATE" + creation_time: + description: Time when the action has been made. + type: string + sample: "2016-07-05T17:38:12Z" + description: + description: Description of the Stack provided in the heat template. + type: string + sample: "HOT template to create a new instance and networks" + id: + description: Stack ID. + type: string + sample: "97a3f543-8136-4570-920e-fd7605c989d6" + name: + description: Name of the Stack + type: string + sample: "test-stack" + identifier: + description: Identifier of the current Stack action. + type: string + sample: "test-stack/97a3f543-8136-4570-920e-fd7605c989d6" + links: + description: Links to the current Stack. + type: list of dict + sample: "[{'href': 'http://foo:8004/v1/7f6a/stacks/test-stack/97a3f543-8136-4570-920e-fd7605c989d6']" + outputs: + description: Output returned by the Stack. + type: list of dict + sample: "{'description': 'IP address of server1 in private network', + 'output_key': 'server1_private_ip', + 'output_value': '10.1.10.103'}" + parameters: + description: Parameters of the current Stack + type: dict + sample: "{'OS::project_id': '7f6a3a3e01164a4eb4eecb2ab7742101', + 'OS::stack_id': '97a3f543-8136-4570-920e-fd7605c989d6', + 'OS::stack_name': 'test-stack', + 'stack_status': 'CREATE_COMPLETE', + 'stack_status_reason': 'Stack CREATE completed successfully', + 'status': 'COMPLETE', + 'template_description': 'HOT template to create a new instance and networks', + 'timeout_mins': 60, + 'updated_time': null}" +''' + +def _create_stack(module, stack, cloud): + try: + stack = cloud.create_stack(module.params['name'], + template_file=module.params['template'], + environment_files=module.params['environment'], + timeout=module.params['timeout'], + wait=True, + rollback=module.params['rollback'], + **module.params['parameters']) + + stack = cloud.get_stack(stack.id, None) + if stack.stack_status == 'CREATE_COMPLETE': + return stack + else: + return False + module.fail_json(msg = "Failure in creating stack: ".format(stack)) + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +def _update_stack(module, stack, cloud): + try: + stack = cloud.update_stack( + module.params['name'], + template_file=module.params['template'], + environment_files=module.params['environment'], + timeout=module.params['timeout'], + rollback=module.params['rollback'], + wait=module.params['wait']) + + if stack['stack_status'] == 'UPDATE_COMPLETE': + return stack + else: + module.fail_json(msg = "Failure in updating stack: %s" % + stack['stack_status_reason']) + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +def _system_state_change(module, stack, cloud): + state = module.params['state'] + if state == 'present': + if not stack: + return True + if state == 'absent' and stack: + return True + return False + +def main(): + + argument_spec = openstack_full_argument_spec( + name=dict(required=True), + template=dict(default=None), + environment=dict(default=None, type='list'), + parameters=dict(default={}, type='dict'), + rollback=dict(default=False, type='bool'), + timeout=dict(default=3600, type='int'), + state=dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, + supports_check_mode=True, + **module_kwargs) + + # stack API introduced in 1.8.0 + if not HAS_SHADE or (StrictVersion(shade.__version__) < StrictVersion('1.8.0')): + module.fail_json(msg='shade 1.8.0 or higher is required for this module') + + state = module.params['state'] + name = module.params['name'] + # Check for required parameters when state == 'present' + if state == 'present': + for p in ['template']: + if not module.params[p]: + module.fail_json(msg='%s required with present state' % p) + + try: + cloud = shade.openstack_cloud(**module.params) + stack = cloud.get_stack(name) + + if module.check_mode: + module.exit_json(changed=_system_state_change(module, stack, + cloud)) + + if state == 'present': + if not stack: + stack = _create_stack(module, stack, cloud) + else: + stack = _update_stack(module, stack, cloud) + changed = True + module.exit_json(changed=changed, + stack=stack, + id=stack.id) + elif state == 'absent': + if not stack: + changed = False + else: + changed = True + if not cloud.delete_stack(name, wait=module.params['wait']): + module.fail_json(msg='delete stack failed for stack: %s' % name) + module.exit_json(changed=changed) + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main()