Repository: ansible/ansible

Issue:
os_stack don't show the correct description error msg
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
 - os_stack

##### ANSIBLE VERSION
```
ansible 2.3.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Jan 19 2017, 14:48:08) [GCC 6.3.0 20170118]
```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"

Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).

-->

##### OS / ENVIRONMENT
Linux RJDVP000683 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u3 (2017-08-06) x86_64 GNU/Linux

##### SUMMARY
When I use a parameters constraints and I use a bad value, the answer should be the **description** of constrains, but I got:

fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "(400) Client Error for url: https://10.10.40.10:8004/v1/3f5265d04cef4a57a1d54b861485121e/stacks/hello_world/d8ce2792-e33e-49fc-a01b-6b0b5e7f02b7"}
...ignoring

##### STEPS TO REPRODUCE
First your HOT template:

```yaml
heat_template_version: 2016-04-08

description: Simple template to deploy a single compute instance

parameters:
  database_password:
    type: string
    label: Database Password
    description: Password to be used for database
    hidden: true
    constraints:
      - length: { min: 6, max: 8 }
        description: Password length must be between 6 and 8 characters.

resources:
  my_server:
    type: OS::Nova::Server
    properties:
      image: ubuntu
      flavor: m1.small
```
Now the playbook
```yml
---
- hosts: localhost
  tasks:
    - name: create stack
      ignore_errors: True
      register: stack_create
      os_stack:
        name: "hello_world"
        state: present
        template: "hot/helloworld.yml"
        validate_certs: no
        parameters:
          database_password: 'rsc123'
```
Now run the playbook:

ansible-playbook -i localhost os_stack.yml

##### EXPECTED RESULTS
I should get the description error:

"Password length must be between 6 and 8 characters."

##### ACTUAL RESULTS
```
    "msg": "(400) Client Error for url: https://10.10.40.10:8004/v1/3f5265d04cef4a57a1d54b861485121e/stacks/hello_world/d8ce2792-e33e-49fc-a01b-6b0b5e7f02b7"
}
...ignoring
```



Discussion:
cc @dagnello @emonty @j2sol @juliakreger @rcarrillocruz @shrews @thingee
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I'm also really struggling with it.

a relatively easy fix here would be to modify os_stack (_create_stack and _update_stack) functions with following exception handling block:
````
except shade.OpenStackCloudException as e:
        module.fail_json(msg=str(e), response=e.response.json())
````
This provides fail message with the reasoning. One should of course ensure presence of e.response first, but it is currently always populated by shade
@gtema can't you create a pull request please? It seems your fix is working. 

Resolving patch:
diff --git a/lib/ansible/modules/cloud/openstack/os_stack.py b/lib/ansible/modules/cloud/openstack/os_stack.py
--- a/lib/ansible/modules/cloud/openstack/os_stack.py
+++ b/lib/ansible/modules/cloud/openstack/os_stack.py
@@ -149,6 +149,7 @@
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.openstack import openstack_full_argument_spec, openstack_module_kwargs, openstack_cloud_from_module
+from ansible.module_utils._text import to_native
 
 
 def _create_stack(module, stack, cloud, shade):
@@ -168,7 +169,10 @@ def _create_stack(module, stack, cloud, shade):
         else:
             module.fail_json(msg="Failure in creating stack: {0}".format(stack))
     except shade.OpenStackCloudException as e:
-        module.fail_json(msg=str(e))
+        if hasattr(e, 'response'):
+            module.fail_json(msg=to_native(e), response=e.response.json())
+        else:
+            module.fail_json(msg=to_native(e))
 
 
 def _update_stack(module, stack, cloud, shade):
@@ -188,7 +192,10 @@ def _update_stack(module, stack, cloud, shade):
             module.fail_json(msg="Failure in updating stack: %s" %
                              stack['stack_status_reason'])
     except shade.OpenStackCloudException as e:
-        module.fail_json(msg=str(e))
+        if hasattr(e, 'response'):
+            module.fail_json(msg=to_native(e), response=e.response.json())
+        else:
+            module.fail_json(msg=to_native(e))
 
 
 def _system_state_change(module, stack, cloud):
@@ -260,7 +267,7 @@ def main():
                     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))
+        module.fail_json(msg=to_native(e))
 
 
 if __name__ == '__main__':


Test patch: