Repository: ansible/ansible

Issue:
gce modules need a better way to handle invalid key files
If the user specifies an unsupported Service Account private key, the cloud/gce\* modules do not catch the error and produce a traceback like:

failed: [127.0.0.1] => {"failed": true, "parsed": false}
invalid output was: Traceback (most recent call last):
  File "/root/.ansible/tmp/ansible-1389289770.6-124236068572659/gce", line 1446, in <module>
    main()
  File "/root/.ansible/tmp/ansible-1389289770.6-124236068572659/gce", line 376, in main
    module.fail_json(msg=unexpected_error_msg(e), changed=False)
  File "/root/.ansible/tmp/ansible-1389289770.6-124236068572659/gce", line 190, in unexpected_error_msg
    error.http_code, error.code, str(error.value))
AttributeError: 'exceptions.ValueError' object has no attribute 'http_code'

@erjohnso will work on a fix and submit a pull-request.



Discussion:
Perhaps related, https://groups.google.com/forum/#!msg/ansible-project/WTef9t1TyA0/8YOuZNs9EwAJ


Resolving patch:
diff --git a/lib/ansible/module_utils/gce.py b/lib/ansible/module_utils/gce.py
new file mode 100644
--- /dev/null
+++ b/lib/ansible/module_utils/gce.py
@@ -0,0 +1,41 @@
+USER_AGENT_PRODUCT="Ansible-gce"
+USER_AGENT_VERSION="v1"
+
+def gce_connect(module):
+    """Return a Google Cloud Engine connection."""
+    service_account_email = module.params.get('service_account_email', None)
+    pem_file = module.params.get('pem_file', None)
+    project_id = module.params.get('project_id', None)
+
+    if service_account_email is None or pem_file is None:
+        # Load in the libcloud secrets file
+        try:
+            import secrets
+        except ImportError:
+            secrets = None
+
+        service_account_email, pem_file = getattr(secrets, 'GCE_PARAMS', (None, None))
+        keyword_params = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
+        project_id = keyword_params.get('project', None)
+
+    if service_account_email is None or pem_file is None or project_id is None:
+        module.fail_json(msg='Missing GCE connection parameters in libcloud secrets file.')
+        return None
+
+    try:
+        gce = get_driver(Provider.GCE)(service_account_email, pem_file, datacenter=module.params.get('zone'), project=project_id)
+        gce.connection.user_agent_append("%s/%s" % (
+            USER_AGENT_PRODUCT, USER_AGENT_VERSION))
+    except (RuntimeError, ValueError), e:
+        module.fail_json(msg=str(e), changed=False)
+    except Exception, e:
+        module.fail_json(msg=unexpected_error_msg(e), changed=False)
+
+    return gce
+
+def unexpected_error_msg(error):
+    """Create an error string based on passed in error."""
+    msg='Unexpected response: HTTP return_code['
+    msg+='%s], API error code[%s] and message: %s' % (
+        error.http_code, error.code, str(error.value))
+    return msg
diff --git a/plugins/inventory/gce.py b/plugins/inventory/gce.py
--- a/plugins/inventory/gce.py
+++ b/plugins/inventory/gce.py
@@ -73,7 +73,7 @@
 '''
 
 USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
-USER_AGENT_VERSION="v1beta15"
+USER_AGENT_VERSION="v1"
 
 import sys
 import os
@@ -174,6 +174,10 @@ def parse_cli_args(self):
 
     def node_to_dict(self, inst):
         md = {}
+
+        if inst is None:
+            return {}
+
         if inst.extra['metadata'].has_key('items'):
             for entry in inst.extra['metadata']['items']:
                 md[entry['key']] = entry['value']
@@ -192,12 +196,17 @@ def node_to_dict(self, inst):
             'gce_zone': inst.extra['zone'].name,
             'gce_tags': inst.extra['tags'],
             'gce_metadata': md,
-            'gce_network': net
+            'gce_network': net,
+            # Hosts don't have a public name, so we add an IP
+            'ansible_ssh_host': inst.public_ips[0]
         }
 
     def get_instance(self, instance_name):
         '''Gets details about a specific instance '''
-        return self.driver.ex_get_node(instance_name)
+        try:
+            return self.driver.ex_get_node(instance_name)
+        except Exception, e:
+            return None
 
     def group_instances(self):
         '''Group all instances'''


Test patch: