We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
os_stack module seems to be lacking idempotency
<!--- Verify first that your issue/request is not already reported in GitHub -->
##### ISSUE TYPE

<!--- Pick one below and delete the rest: -->
- Bug Report
##### COMPONENT NAME

os_stack
##### ANSIBLE VERSION

<!--- Paste verbatim output from “ansible --version” between quotes below -->

```
ansible 2.2.0
  config file = /root/.ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

```
[root@2f4037fae5f7 ~]# cat ~/.ansible.cfg 
[defaults]
roles_path= /root/repository/rhc-ose/rhc-ose-ansible/roles:/root/repository/openshift-ansible/roles
filter_plugins= /usr/share/ansible_plugins/filter_plugins:/root/repository/openshift-ansible/filter_plugins
host_key_checking = False
```
##### OS / ENVIRONMENT

<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->

RHEL/Centos/Fedora in OpenStack
##### SUMMARY

<!--- Explain the problem briefly -->

I am attempting to design a role around the `os_stack` module to do idempotent provisioning. When I run the role (invoked via this playbook: https://github.com/etsauer/casl-ansible/blob/openstack-heat/roles/openstack-stack/test/stack-create-test.yml) for the first time, it works great. If I try to run it a second time, the run fails with some errors about missing parameters.
##### STEPS TO REPRODUCE

<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->

Clone my repo, and run the playbook here: https://github.com/etsauer/casl-ansible/blob/openstack-heat/roles/openstack-stack/test/stack-create-test.yml

```
[root@2f4037fae5f7 ~]# ansible-playbook -i ./repository/casl-ansible/inventory/openstack-stack-test ./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.yml
 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [localhost] ***************************************************************

TASK [setup] *******************************************************************
ok: [localhost]

TASK [openstack-stack : create stack] ******************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0   

[root@2f4037fae5f7 ~]# ansible-playbook -i ./repository/casl-ansible/inventory/openstack-stack-test ./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.yml
 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [localhost] ***************************************************************

TASK [setup] *******************************************************************
ok: [localhost]

TASK [openstack-stack : create stack] ******************************************
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "Error updating stack test-stack: ERROR: The Parameter (dns_image) was not provided."}
        to retry, use: --limit @./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.retry

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1   

```

<!--- You can also paste gist.github.com links for larger files -->
##### EXPECTED RESULTS

<!--- What did you expect to happen when running the steps above? -->

A second successful run, changing nothing because the module can detect that what I'm asking for has already been provisioned.
##### ACTUAL RESULTS

<!--- What actually happened? If possible run with high verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->

```
TASK [openstack-stack : create stack] ******************************************
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "Error updating stack test-stack: ERROR: The Parameter (dns_image) was not provided."}
        to retry, use: --limit @./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.retry
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cloud/openstack/os_stack.py`
Content:
```
1 #!/usr/bin/python
2 #coding: utf-8 -*-
3 
4 # (c) 2016, Mathieu Bultel <mbultel@redhat.com>
5 # (c) 2016, Steve Baker <sbaker@redhat.com>
6 #
7 # This module is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This software is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this software.  If not, see <http://www.gnu.org/licenses/>.
19 
20 from time import sleep
21 from distutils.version import StrictVersion
22 try:
23     import shade
24     HAS_SHADE = True
25 except ImportError:
26     HAS_SHADE = False
27 
28 DOCUMENTATION = '''
29 ---
30 module: os_stack
31 short_description: Add/Remove Heat Stack
32 extends_documentation_fragment: openstack
33 version_added: "2.2"
34 author: "Mathieu Bultel (matbu), Steve Baker (steveb)"
35 description:
36    - Add or Remove a Stack to an OpenStack Heat
37 options:
38     state:
39       description:
40         - Indicate desired state of the resource
41       choices: ['present', 'absent']
42       required: false
43       default: present
44     name:
45       description:
46         - Name of the stack that should be created, name could be char and digit, no space
47       required: true
48     template:
49       description:
50         - Path of the template file to use for the stack creation
51       required: false
52       default: None
53     environment:
54       description:
55         - List of environment files that should be used for the stack creation
56       required: false
57       default: None
58     parameters:
59       description:
60         - Dictionary of parameters for the stack creation
61       required: false
62       default: None
63     rollback:
64       description:
65         - Rollback stack creation
66       required: false
67       default: false
68     timeout:
69       description:
70         - Maximum number of seconds to wait for the stack creation
71       required: false
72       default: 3600
73 requirements:
74     - "python >= 2.6"
75     - "shade"
76 '''
77 EXAMPLES = '''
78 ---
79 - name: create stack
80   ignore_errors: True
81   register: stack_create
82   os_stack:
83     name: "{{ stack_name }}"
84     state: present
85     template: "/path/to/my_stack.yaml"
86     environment:
87     - /path/to/resource-registry.yaml
88     - /path/to/environment.yaml
89     parameters:
90         bmc_flavor: m1.medium
91         bmc_image: CentOS
92         key_name: default
93         private_net: {{ private_net_param }}
94         node_count: 2
95         name: undercloud
96         image: CentOS
97         my_flavor: m1.large
98         external_net: {{ external_net_param }}
99 '''
100 
101 RETURN = '''
102 id:
103     description: Stack ID.
104     type: string
105     sample: "97a3f543-8136-4570-920e-fd7605c989d6"
106 
107 stack:
108     action:
109         description: Action, could be Create or Update.
110         type: string
111         sample: "CREATE"
112     creation_time:
113         description: Time when the action has been made.
114         type: string
115         sample: "2016-07-05T17:38:12Z"
116     description:
117         description: Description of the Stack provided in the heat template.
118         type: string
119         sample: "HOT template to create a new instance and networks"
120     id:
121         description: Stack ID.
122         type: string
123         sample: "97a3f543-8136-4570-920e-fd7605c989d6"
124     name:
125         description: Name of the Stack
126         type: string
127         sample: "test-stack"
128     identifier:
129         description: Identifier of the current Stack action.
130         type: string
131         sample: "test-stack/97a3f543-8136-4570-920e-fd7605c989d6"
132     links:
133         description: Links to the current Stack.
134         type: list of dict
135         sample: "[{'href': 'http://foo:8004/v1/7f6a/stacks/test-stack/97a3f543-8136-4570-920e-fd7605c989d6']"
136     outputs:
137         description: Output returned by the Stack.
138         type: list of dict
139         sample: "{'description': 'IP address of server1 in private network',
140                     'output_key': 'server1_private_ip',
141                     'output_value': '10.1.10.103'}"
142     parameters:
143         description: Parameters of the current Stack
144         type: dict
145         sample: "{'OS::project_id': '7f6a3a3e01164a4eb4eecb2ab7742101',
146                     'OS::stack_id': '97a3f543-8136-4570-920e-fd7605c989d6',
147                     'OS::stack_name': 'test-stack',
148                     'stack_status': 'CREATE_COMPLETE',
149                     'stack_status_reason': 'Stack CREATE completed successfully',
150                     'status': 'COMPLETE',
151                     'template_description': 'HOT template to create a new instance and networks',
152                     'timeout_mins': 60,
153                     'updated_time': null}"
154 '''
155 
156 def _create_stack(module, stack, cloud):
157     try:
158         stack = cloud.create_stack(module.params['name'],
159                                        template_file=module.params['template'],
160                                        environment_files=module.params['environment'],
161                                        timeout=module.params['timeout'],
162                                        wait=True,
163                                        rollback=module.params['rollback'],
164                                        **module.params['parameters'])
165 
166         stack = cloud.get_stack(stack.id, None)
167         if stack.stack_status == 'CREATE_COMPLETE':
168             return stack
169         else:
170             return False
171             module.fail_json(msg = "Failure in creating stack: ".format(stack))
172     except shade.OpenStackCloudException as e:
173         module.fail_json(msg=str(e))
174 
175 def _update_stack(module, stack, cloud):
176     try:
177         stack = cloud.update_stack(
178             module.params['name'],
179             template_file=module.params['template'],
180             environment_files=module.params['environment'],
181             timeout=module.params['timeout'],
182             rollback=module.params['rollback'],
183             wait=module.params['wait'])
184 
185         if stack['stack_status'] == 'UPDATE_COMPLETE':
186             return stack
187         else:
188             module.fail_json(msg = "Failure in updating stack: %s" %
189                              stack['stack_status_reason'])
190     except shade.OpenStackCloudException as e:
191         module.fail_json(msg=str(e))
192 
193 def _system_state_change(module, stack, cloud):
194     state = module.params['state']
195     if state == 'present':
196         if not stack:
197             return True
198     if state == 'absent' and stack:
199         return True
200     return False
201 
202 def main():
203 
204     argument_spec = openstack_full_argument_spec(
205         name=dict(required=True),
206         template=dict(default=None),
207         environment=dict(default=None, type='list'),
208         parameters=dict(default={}, type='dict'),
209         rollback=dict(default=False, type='bool'),
210         timeout=dict(default=3600, type='int'),
211         state=dict(default='present', choices=['absent', 'present']),
212     )
213 
214     module_kwargs = openstack_module_kwargs()
215     module = AnsibleModule(argument_spec,
216                            supports_check_mode=True,
217                            **module_kwargs)
218 
219     # stack API introduced in 1.8.0
220     if not HAS_SHADE or (StrictVersion(shade.__version__) < StrictVersion('1.8.0')):
221         module.fail_json(msg='shade 1.8.0 or higher is required for this module')
222 
223     state = module.params['state']
224     name = module.params['name']
225     # Check for required parameters when state == 'present'
226     if state == 'present':
227         for p in ['template']:
228             if not module.params[p]:
229                 module.fail_json(msg='%s required with present state' % p)
230 
231     try:
232         cloud = shade.openstack_cloud(**module.params)
233         stack = cloud.get_stack(name)
234 
235         if module.check_mode:
236             module.exit_json(changed=_system_state_change(module, stack,
237                                                           cloud))
238 
239         if state == 'present':
240             if not stack:
241                 stack = _create_stack(module, stack, cloud)
242             else:
243                 stack = _update_stack(module, stack, cloud)
244             changed = True
245             module.exit_json(changed=changed,
246                              stack=stack,
247                              id=stack.id)
248         elif state == 'absent':
249             if not stack:
250                 changed = False
251             else:
252                 changed = True
253                 if not cloud.delete_stack(name, wait=module.params['wait']):
254                     module.fail_json(msg='delete stack failed for stack: %s' % name)
255             module.exit_json(changed=changed)
256     except shade.OpenStackCloudException as e:
257         module.fail_json(msg=str(e))
258 
259 from ansible.module_utils.basic import *
260 from ansible.module_utils.openstack import *
261 if __name__ == '__main__':
262     main()
263
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cloud/openstack/os_stack.py b/cloud/openstack/os_stack.py
--- a/cloud/openstack/os_stack.py
+++ b/cloud/openstack/os_stack.py
@@ -180,7 +180,8 @@
             environment_files=module.params['environment'],
             timeout=module.params['timeout'],
             rollback=module.params['rollback'],
-            wait=module.params['wait'])
+            wait=module.params['wait'],
+            **module.params['parameters'])
 
         if stack['stack_status'] == 'UPDATE_COMPLETE':
             return stack

{"golden_diff": "diff --git a/cloud/openstack/os_stack.py b/cloud/openstack/os_stack.py\n--- a/cloud/openstack/os_stack.py\n+++ b/cloud/openstack/os_stack.py\n@@ -180,7 +180,8 @@\n             environment_files=module.params['environment'],\n             timeout=module.params['timeout'],\n             rollback=module.params['rollback'],\n-            wait=module.params['wait'])\n+            wait=module.params['wait'],\n+            **module.params['parameters'])\n \n         if stack['stack_status'] == 'UPDATE_COMPLETE':\n             return stack\n", "issue": "os_stack module seems to be lacking idempotency\n<!--- Verify first that your issue/request is not already reported in GitHub -->\n##### ISSUE TYPE\n\n<!--- Pick one below and delete the rest: -->\n- Bug Report\n##### COMPONENT NAME\n\nos_stack\n##### ANSIBLE VERSION\n\n<!--- Paste verbatim output from \u201cansible --version\u201d between quotes below -->\n\n```\nansible 2.2.0\n  config file = /root/.ansible.cfg\n  configured module search path = Default w/o overrides\n```\n##### CONFIGURATION\n\n<!---\nMention any settings you have changed/added/removed in ansible.cfg\n(or using the ANSIBLE_* environment variables).\n-->\n\n```\n[root@2f4037fae5f7 ~]# cat ~/.ansible.cfg \n[defaults]\nroles_path= /root/repository/rhc-ose/rhc-ose-ansible/roles:/root/repository/openshift-ansible/roles\nfilter_plugins= /usr/share/ansible_plugins/filter_plugins:/root/repository/openshift-ansible/filter_plugins\nhost_key_checking = False\n```\n##### OS / ENVIRONMENT\n\n<!---\nMention the OS you are running Ansible from, and the OS you are\nmanaging, or say \u201cN/A\u201d for anything that is not platform-specific.\n-->\n\nRHEL/Centos/Fedora in OpenStack\n##### SUMMARY\n\n<!--- Explain the problem briefly -->\n\nI am attempting to design a role around the `os_stack` module to do idempotent provisioning. When I run the role (invoked via this playbook: https://github.com/etsauer/casl-ansible/blob/openstack-heat/roles/openstack-stack/test/stack-create-test.yml) for the first time, it works great. If I try to run it a second time, the run fails with some errors about missing parameters.\n##### STEPS TO REPRODUCE\n\n<!---\nFor bugs, show exactly how to reproduce the problem.\nFor new features, show how the feature would be used.\n-->\n\n<!--- Paste example playbooks or commands between quotes below -->\n\nClone my repo, and run the playbook here: https://github.com/etsauer/casl-ansible/blob/openstack-heat/roles/openstack-stack/test/stack-create-test.yml\n\n```\n[root@2f4037fae5f7 ~]# ansible-playbook -i ./repository/casl-ansible/inventory/openstack-stack-test ./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.yml\n [WARNING]: provided hosts list is empty, only localhost is available\n\n\nPLAY [localhost] ***************************************************************\n\nTASK [setup] *******************************************************************\nok: [localhost]\n\nTASK [openstack-stack : create stack] ******************************************\nchanged: [localhost]\n\nPLAY RECAP *********************************************************************\nlocalhost                  : ok=2    changed=1    unreachable=0    failed=0   \n\n[root@2f4037fae5f7 ~]# ansible-playbook -i ./repository/casl-ansible/inventory/openstack-stack-test ./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.yml\n [WARNING]: provided hosts list is empty, only localhost is available\n\n\nPLAY [localhost] ***************************************************************\n\nTASK [setup] *******************************************************************\nok: [localhost]\n\nTASK [openstack-stack : create stack] ******************************************\nfatal: [localhost]: FAILED! => {\"changed\": false, \"failed\": true, \"msg\": \"Error updating stack test-stack: ERROR: The Parameter (dns_image) was not provided.\"}\n        to retry, use: --limit @./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.retry\n\nPLAY RECAP *********************************************************************\nlocalhost                  : ok=1    changed=0    unreachable=0    failed=1   \n\n```\n\n<!--- You can also paste gist.github.com links for larger files -->\n##### EXPECTED RESULTS\n\n<!--- What did you expect to happen when running the steps above? -->\n\nA second successful run, changing nothing because the module can detect that what I'm asking for has already been provisioned.\n##### ACTUAL RESULTS\n\n<!--- What actually happened? If possible run with high verbosity (-vvvv) -->\n\n<!--- Paste verbatim command output between quotes below -->\n\n```\nTASK [openstack-stack : create stack] ******************************************\nfatal: [localhost]: FAILED! => {\"changed\": false, \"failed\": true, \"msg\": \"Error updating stack test-stack: ERROR: The Parameter (dns_image) was not provided.\"}\n        to retry, use: --limit @./repository/casl-ansible/roles/openstack-stack/test/stack-create-test.retry\n```\n\n", "before_files": [{"content": "#!/usr/bin/python\n#coding: utf-8 -*-\n\n# (c) 2016, Mathieu Bultel <mbultel@redhat.com>\n# (c) 2016, Steve Baker <sbaker@redhat.com>\n#\n# This module is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This software is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this software.  If not, see <http://www.gnu.org/licenses/>.\n\nfrom time import sleep\nfrom distutils.version import StrictVersion\ntry:\n    import shade\n    HAS_SHADE = True\nexcept ImportError:\n    HAS_SHADE = False\n\nDOCUMENTATION = '''\n---\nmodule: os_stack\nshort_description: Add/Remove Heat Stack\nextends_documentation_fragment: openstack\nversion_added: \"2.2\"\nauthor: \"Mathieu Bultel (matbu), Steve Baker (steveb)\"\ndescription:\n   - Add or Remove a Stack to an OpenStack Heat\noptions:\n    state:\n      description:\n        - Indicate desired state of the resource\n      choices: ['present', 'absent']\n      required: false\n      default: present\n    name:\n      description:\n        - Name of the stack that should be created, name could be char and digit, no space\n      required: true\n    template:\n      description:\n        - Path of the template file to use for the stack creation\n      required: false\n      default: None\n    environment:\n      description:\n        - List of environment files that should be used for the stack creation\n      required: false\n      default: None\n    parameters:\n      description:\n        - Dictionary of parameters for the stack creation\n      required: false\n      default: None\n    rollback:\n      description:\n        - Rollback stack creation\n      required: false\n      default: false\n    timeout:\n      description:\n        - Maximum number of seconds to wait for the stack creation\n      required: false\n      default: 3600\nrequirements:\n    - \"python >= 2.6\"\n    - \"shade\"\n'''\nEXAMPLES = '''\n---\n- name: create stack\n  ignore_errors: True\n  register: stack_create\n  os_stack:\n    name: \"{{ stack_name }}\"\n    state: present\n    template: \"/path/to/my_stack.yaml\"\n    environment:\n    - /path/to/resource-registry.yaml\n    - /path/to/environment.yaml\n    parameters:\n        bmc_flavor: m1.medium\n        bmc_image: CentOS\n        key_name: default\n        private_net: {{ private_net_param }}\n        node_count: 2\n        name: undercloud\n        image: CentOS\n        my_flavor: m1.large\n        external_net: {{ external_net_param }}\n'''\n\nRETURN = '''\nid:\n    description: Stack ID.\n    type: string\n    sample: \"97a3f543-8136-4570-920e-fd7605c989d6\"\n\nstack:\n    action:\n        description: Action, could be Create or Update.\n        type: string\n        sample: \"CREATE\"\n    creation_time:\n        description: Time when the action has been made.\n        type: string\n        sample: \"2016-07-05T17:38:12Z\"\n    description:\n        description: Description of the Stack provided in the heat template.\n        type: string\n        sample: \"HOT template to create a new instance and networks\"\n    id:\n        description: Stack ID.\n        type: string\n        sample: \"97a3f543-8136-4570-920e-fd7605c989d6\"\n    name:\n        description: Name of the Stack\n        type: string\n        sample: \"test-stack\"\n    identifier:\n        description: Identifier of the current Stack action.\n        type: string\n        sample: \"test-stack/97a3f543-8136-4570-920e-fd7605c989d6\"\n    links:\n        description: Links to the current Stack.\n        type: list of dict\n        sample: \"[{'href': 'http://foo:8004/v1/7f6a/stacks/test-stack/97a3f543-8136-4570-920e-fd7605c989d6']\"\n    outputs:\n        description: Output returned by the Stack.\n        type: list of dict\n        sample: \"{'description': 'IP address of server1 in private network',\n                    'output_key': 'server1_private_ip',\n                    'output_value': '10.1.10.103'}\"\n    parameters:\n        description: Parameters of the current Stack\n        type: dict\n        sample: \"{'OS::project_id': '7f6a3a3e01164a4eb4eecb2ab7742101',\n                    'OS::stack_id': '97a3f543-8136-4570-920e-fd7605c989d6',\n                    'OS::stack_name': 'test-stack',\n                    'stack_status': 'CREATE_COMPLETE',\n                    'stack_status_reason': 'Stack CREATE completed successfully',\n                    'status': 'COMPLETE',\n                    'template_description': 'HOT template to create a new instance and networks',\n                    'timeout_mins': 60,\n                    'updated_time': null}\"\n'''\n\ndef _create_stack(module, stack, cloud):\n    try:\n        stack = cloud.create_stack(module.params['name'],\n                                       template_file=module.params['template'],\n                                       environment_files=module.params['environment'],\n                                       timeout=module.params['timeout'],\n                                       wait=True,\n                                       rollback=module.params['rollback'],\n                                       **module.params['parameters'])\n\n        stack = cloud.get_stack(stack.id, None)\n        if stack.stack_status == 'CREATE_COMPLETE':\n            return stack\n        else:\n            return False\n            module.fail_json(msg = \"Failure in creating stack: \".format(stack))\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\ndef _update_stack(module, stack, cloud):\n    try:\n        stack = cloud.update_stack(\n            module.params['name'],\n            template_file=module.params['template'],\n            environment_files=module.params['environment'],\n            timeout=module.params['timeout'],\n            rollback=module.params['rollback'],\n            wait=module.params['wait'])\n\n        if stack['stack_status'] == 'UPDATE_COMPLETE':\n            return stack\n        else:\n            module.fail_json(msg = \"Failure in updating stack: %s\" %\n                             stack['stack_status_reason'])\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\ndef _system_state_change(module, stack, cloud):\n    state = module.params['state']\n    if state == 'present':\n        if not stack:\n            return True\n    if state == 'absent' and stack:\n        return True\n    return False\n\ndef main():\n\n    argument_spec = openstack_full_argument_spec(\n        name=dict(required=True),\n        template=dict(default=None),\n        environment=dict(default=None, type='list'),\n        parameters=dict(default={}, type='dict'),\n        rollback=dict(default=False, type='bool'),\n        timeout=dict(default=3600, type='int'),\n        state=dict(default='present', choices=['absent', 'present']),\n    )\n\n    module_kwargs = openstack_module_kwargs()\n    module = AnsibleModule(argument_spec,\n                           supports_check_mode=True,\n                           **module_kwargs)\n\n    # stack API introduced in 1.8.0\n    if not HAS_SHADE or (StrictVersion(shade.__version__) < StrictVersion('1.8.0')):\n        module.fail_json(msg='shade 1.8.0 or higher is required for this module')\n\n    state = module.params['state']\n    name = module.params['name']\n    # Check for required parameters when state == 'present'\n    if state == 'present':\n        for p in ['template']:\n            if not module.params[p]:\n                module.fail_json(msg='%s required with present state' % p)\n\n    try:\n        cloud = shade.openstack_cloud(**module.params)\n        stack = cloud.get_stack(name)\n\n        if module.check_mode:\n            module.exit_json(changed=_system_state_change(module, stack,\n                                                          cloud))\n\n        if state == 'present':\n            if not stack:\n                stack = _create_stack(module, stack, cloud)\n            else:\n                stack = _update_stack(module, stack, cloud)\n            changed = True\n            module.exit_json(changed=changed,\n                             stack=stack,\n                             id=stack.id)\n        elif state == 'absent':\n            if not stack:\n                changed = False\n            else:\n                changed = True\n                if not cloud.delete_stack(name, wait=module.params['wait']):\n                    module.fail_json(msg='delete stack failed for stack: %s' % name)\n            module.exit_json(changed=changed)\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.openstack import *\nif __name__ == '__main__':\n    main()\n", "path": "cloud/openstack/os_stack.py"}], "after_files": [{"content": "#!/usr/bin/python\n#coding: utf-8 -*-\n\n# (c) 2016, Mathieu Bultel <mbultel@redhat.com>\n# (c) 2016, Steve Baker <sbaker@redhat.com>\n#\n# This module is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This software is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this software.  If not, see <http://www.gnu.org/licenses/>.\n\nfrom time import sleep\nfrom distutils.version import StrictVersion\ntry:\n    import shade\n    HAS_SHADE = True\nexcept ImportError:\n    HAS_SHADE = False\n\nDOCUMENTATION = '''\n---\nmodule: os_stack\nshort_description: Add/Remove Heat Stack\nextends_documentation_fragment: openstack\nversion_added: \"2.2\"\nauthor: \"Mathieu Bultel (matbu), Steve Baker (steveb)\"\ndescription:\n   - Add or Remove a Stack to an OpenStack Heat\noptions:\n    state:\n      description:\n        - Indicate desired state of the resource\n      choices: ['present', 'absent']\n      required: false\n      default: present\n    name:\n      description:\n        - Name of the stack that should be created, name could be char and digit, no space\n      required: true\n    template:\n      description:\n        - Path of the template file to use for the stack creation\n      required: false\n      default: None\n    environment:\n      description:\n        - List of environment files that should be used for the stack creation\n      required: false\n      default: None\n    parameters:\n      description:\n        - Dictionary of parameters for the stack creation\n      required: false\n      default: None\n    rollback:\n      description:\n        - Rollback stack creation\n      required: false\n      default: false\n    timeout:\n      description:\n        - Maximum number of seconds to wait for the stack creation\n      required: false\n      default: 3600\nrequirements:\n    - \"python >= 2.6\"\n    - \"shade\"\n'''\nEXAMPLES = '''\n---\n- name: create stack\n  ignore_errors: True\n  register: stack_create\n  os_stack:\n    name: \"{{ stack_name }}\"\n    state: present\n    template: \"/path/to/my_stack.yaml\"\n    environment:\n    - /path/to/resource-registry.yaml\n    - /path/to/environment.yaml\n    parameters:\n        bmc_flavor: m1.medium\n        bmc_image: CentOS\n        key_name: default\n        private_net: {{ private_net_param }}\n        node_count: 2\n        name: undercloud\n        image: CentOS\n        my_flavor: m1.large\n        external_net: {{ external_net_param }}\n'''\n\nRETURN = '''\nid:\n    description: Stack ID.\n    type: string\n    sample: \"97a3f543-8136-4570-920e-fd7605c989d6\"\n\nstack:\n    action:\n        description: Action, could be Create or Update.\n        type: string\n        sample: \"CREATE\"\n    creation_time:\n        description: Time when the action has been made.\n        type: string\n        sample: \"2016-07-05T17:38:12Z\"\n    description:\n        description: Description of the Stack provided in the heat template.\n        type: string\n        sample: \"HOT template to create a new instance and networks\"\n    id:\n        description: Stack ID.\n        type: string\n        sample: \"97a3f543-8136-4570-920e-fd7605c989d6\"\n    name:\n        description: Name of the Stack\n        type: string\n        sample: \"test-stack\"\n    identifier:\n        description: Identifier of the current Stack action.\n        type: string\n        sample: \"test-stack/97a3f543-8136-4570-920e-fd7605c989d6\"\n    links:\n        description: Links to the current Stack.\n        type: list of dict\n        sample: \"[{'href': 'http://foo:8004/v1/7f6a/stacks/test-stack/97a3f543-8136-4570-920e-fd7605c989d6']\"\n    outputs:\n        description: Output returned by the Stack.\n        type: list of dict\n        sample: \"{'description': 'IP address of server1 in private network',\n                    'output_key': 'server1_private_ip',\n                    'output_value': '10.1.10.103'}\"\n    parameters:\n        description: Parameters of the current Stack\n        type: dict\n        sample: \"{'OS::project_id': '7f6a3a3e01164a4eb4eecb2ab7742101',\n                    'OS::stack_id': '97a3f543-8136-4570-920e-fd7605c989d6',\n                    'OS::stack_name': 'test-stack',\n                    'stack_status': 'CREATE_COMPLETE',\n                    'stack_status_reason': 'Stack CREATE completed successfully',\n                    'status': 'COMPLETE',\n                    'template_description': 'HOT template to create a new instance and networks',\n                    'timeout_mins': 60,\n                    'updated_time': null}\"\n'''\n\ndef _create_stack(module, stack, cloud):\n    try:\n        stack = cloud.create_stack(module.params['name'],\n                                       template_file=module.params['template'],\n                                       environment_files=module.params['environment'],\n                                       timeout=module.params['timeout'],\n                                       wait=True,\n                                       rollback=module.params['rollback'],\n                                       **module.params['parameters'])\n\n        stack = cloud.get_stack(stack.id, None)\n        if stack.stack_status == 'CREATE_COMPLETE':\n            return stack\n        else:\n            return False\n            module.fail_json(msg = \"Failure in creating stack: \".format(stack))\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\ndef _update_stack(module, stack, cloud):\n    try:\n        stack = cloud.update_stack(\n            module.params['name'],\n            template_file=module.params['template'],\n            environment_files=module.params['environment'],\n            timeout=module.params['timeout'],\n            rollback=module.params['rollback'],\n            wait=module.params['wait'],\n            **module.params['parameters'])\n\n        if stack['stack_status'] == 'UPDATE_COMPLETE':\n            return stack\n        else:\n            module.fail_json(msg = \"Failure in updating stack: %s\" %\n                             stack['stack_status_reason'])\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\ndef _system_state_change(module, stack, cloud):\n    state = module.params['state']\n    if state == 'present':\n        if not stack:\n            return True\n    if state == 'absent' and stack:\n        return True\n    return False\n\ndef main():\n\n    argument_spec = openstack_full_argument_spec(\n        name=dict(required=True),\n        template=dict(default=None),\n        environment=dict(default=None, type='list'),\n        parameters=dict(default={}, type='dict'),\n        rollback=dict(default=False, type='bool'),\n        timeout=dict(default=3600, type='int'),\n        state=dict(default='present', choices=['absent', 'present']),\n    )\n\n    module_kwargs = openstack_module_kwargs()\n    module = AnsibleModule(argument_spec,\n                           supports_check_mode=True,\n                           **module_kwargs)\n\n    # stack API introduced in 1.8.0\n    if not HAS_SHADE or (StrictVersion(shade.__version__) < StrictVersion('1.8.0')):\n        module.fail_json(msg='shade 1.8.0 or higher is required for this module')\n\n    state = module.params['state']\n    name = module.params['name']\n    # Check for required parameters when state == 'present'\n    if state == 'present':\n        for p in ['template']:\n            if not module.params[p]:\n                module.fail_json(msg='%s required with present state' % p)\n\n    try:\n        cloud = shade.openstack_cloud(**module.params)\n        stack = cloud.get_stack(name)\n\n        if module.check_mode:\n            module.exit_json(changed=_system_state_change(module, stack,\n                                                          cloud))\n\n        if state == 'present':\n            if not stack:\n                stack = _create_stack(module, stack, cloud)\n            else:\n                stack = _update_stack(module, stack, cloud)\n            changed = True\n            module.exit_json(changed=changed,\n                             stack=stack,\n                             id=stack.id)\n        elif state == 'absent':\n            if not stack:\n                changed = False\n            else:\n                changed = True\n                if not cloud.delete_stack(name, wait=module.params['wait']):\n                    module.fail_json(msg='delete stack failed for stack: %s' % name)\n            module.exit_json(changed=changed)\n    except shade.OpenStackCloudException as e:\n        module.fail_json(msg=str(e))\n\nfrom ansible.module_utils.basic import *\nfrom ansible.module_utils.openstack import *\nif __name__ == '__main__':\n    main()\n", "path": "cloud/openstack/os_stack.py"}]}