diff --git a/docsite/rst/intro_dynamic_inventory.rst b/docsite/rst/intro_dynamic_inventory.rst index a2bcf1a221e..e42da4bad8f 100644 --- a/docsite/rst/intro_dynamic_inventory.rst +++ b/docsite/rst/intro_dynamic_inventory.rst @@ -197,6 +197,7 @@ In addition to Cobbler and EC2, inventory scripts are also available for:: BSD Jails Digital Ocean + Google Compute Engine Linode OpenShift OpenStack Nova diff --git a/lib/ansible/module_utils/gce.py b/lib/ansible/module_utils/gce.py new file mode 100644 index 00000000000..f6401c68d01 --- /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/library/cloud/gce b/library/cloud/gce old mode 100644 new mode 100755 index f45377b6cd6..b14ce8996da --- a/library/cloud/gce +++ b/library/cloud/gce @@ -51,6 +51,27 @@ options: required: false default: null aliases: [] + service_account_email: + version_added: 1.5.1 + description: + - service account email + required: false + default: null + aliases: [] + pem_file: + version_added: 1.5.1 + description: + - path to the pem file associated with the service account email + required: false + default: null + aliases: [] + project_id: + version_added: 1.5.1 + description: + - your GCE project ID + required: false + default: null + aliases: [] name: description: - identifier when working with a single instance @@ -90,6 +111,8 @@ options: aliases: [] requirements: [ "libcloud" ] +notes: + - Either I(name) or I(instance_names) is required. author: Eric Johnson ''' @@ -119,10 +142,14 @@ EXAMPLES = ''' machine_type: n1-standard-1 image: debian-6 zone: us-central1-a + service_account_email: unique-email@developer.gserviceaccount.com + pem_file: /path/to/pem_file + project_id: project-id tasks: - name: Launch instances local_action: gce instance_names={{names}} machine_type={{machine_type}} - image={{image}} zone={{zone}} + image={{image}} zone={{zone}} service_account_email={{ service_account_email }} + pem_file={{ pem_file }} project_id={{ project_id }} register: gce - name: Wait for SSH to come up local_action: wait_for host={{item.public_ip}} port=22 delay=10 @@ -150,9 +177,6 @@ EXAMPLES = ''' import sys -USER_AGENT_PRODUCT="Ansible-gce" -USER_AGENT_VERSION="v1beta15" - try: from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver @@ -171,25 +195,6 @@ except ImportError: "msg='GCE module requires python's 'ast' module, python v2.6+'") sys.exit(1) -# Load in the libcloud secrets file -try: - import secrets -except ImportError: - secrets = None -ARGS = getattr(secrets, 'GCE_PARAMS', ()) -KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {}) - -if not ARGS or not 'project' in KWARGS: - print("failed=True " + \ - "msg='Missing GCE connection parametres in libcloud secrets file.'") - sys.exit(1) - -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 def get_instance_info(inst): """Retrieves instance information from an instance object and returns it @@ -353,9 +358,14 @@ def main(): zone = dict(choices=['us-central1-a', 'us-central1-b', 'us-central2-a', 'europe-west1-a', 'europe-west1-b'], default='us-central1-a'), + service_account_email = dict(), + pem_file = dict(), + project_id = dict(), ) ) + gce = gce_connect(module) + image = module.params.get('image') instance_names = module.params.get('instance_names') machine_type = module.params.get('machine_type') @@ -368,13 +378,6 @@ def main(): zone = module.params.get('zone') changed = False - try: - gce = get_driver(Provider.GCE)(*ARGS, datacenter=zone, **KWARGS) - gce.connection.user_agent_append("%s/%s" % ( - USER_AGENT_PRODUCT, USER_AGENT_VERSION)) - except Exception, e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - inames = [] if isinstance(instance_names, list): inames = instance_names @@ -418,5 +421,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.gce import * main() diff --git a/library/cloud/gce_lb b/library/cloud/gce_lb index 5f3cac7d075..3e22c216998 100644 --- a/library/cloud/gce_lb +++ b/library/cloud/gce_lb @@ -110,6 +110,27 @@ options: default: "present" choices: ["active", "present", "absent", "deleted"] aliases: [] + service_account_email: + version_added: 1.5.1 + description: + - service account email + required: false + default: null + aliases: [] + pem_file: + version_added: 1.5.1 + description: + - path to the pem file associated with the service account email + required: false + default: null + aliases: [] + project_id: + version_added: 1.5.1 + description: + - your GCE project ID + required: false + default: null + aliases: [] requirements: [ "libcloud" ] author: Eric Johnson @@ -129,41 +150,20 @@ EXAMPLES = ''' import sys -USER_AGENT_PRODUCT="Ansible-gce_lb" -USER_AGENT_VERSION="v1beta15" -try: +try: from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver from libcloud.loadbalancer.types import Provider as Provider_lb from libcloud.loadbalancer.providers import get_driver as get_driver_lb from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ ResourceExistsError, ResourceNotFoundError - _ = Provider.GCE -except ImportError: + _ = Provider.GCE +except ImportError: print("failed=True " + \ "msg='libcloud with GCE support required for this module.'") - sys.exit(1) - -# Load in the libcloud secrets file -try: - import secrets -except ImportError: - secrets = None -ARGS = getattr(secrets, 'GCE_PARAMS', ()) -KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {}) - -if not ARGS or not 'project' in KWARGS: - print("failed=True msg='Missing GCE connection " + \ - "parameters in libcloud secrets file.'") sys.exit(1) -def unexpected_error_msg(error): - """Format 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 def main(): module = AnsibleModule( @@ -183,9 +183,14 @@ def main(): port_range = dict(), members = dict(type='list'), state = dict(default='present'), + service_account_email = dict(), + pem_file = dict(), + project_id = dict(), ) ) + gce = gce_connect(module) + httphealthcheck_name = module.params.get('httphealthcheck_name') httphealthcheck_port = module.params.get('httphealthcheck_port') httphealthcheck_path = module.params.get('httphealthcheck_path') @@ -205,9 +210,6 @@ def main(): state = module.params.get('state') try: - gce = get_driver(Provider.GCE)(*ARGS, **KWARGS) - gce.connection.user_agent_append("%s/%s" % ( - USER_AGENT_PRODUCT, USER_AGENT_VERSION)) gcelb = get_driver_lb(Provider_lb.GCE)(gce_driver=gce) gcelb.connection.user_agent_append("%s/%s" % ( USER_AGENT_PRODUCT, USER_AGENT_VERSION)) @@ -329,5 +331,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.gce import * main() diff --git a/library/cloud/gce_net b/library/cloud/gce_net index de272ea3825..4e731f196d3 100644 --- a/library/cloud/gce_net +++ b/library/cloud/gce_net @@ -73,6 +73,27 @@ options: default: "present" choices: ["active", "present", "absent", "deleted"] aliases: [] + service_account_email: + version_added: 1.5.1 + description: + - service account email + required: false + default: null + aliases: [] + pem_file: + version_added: 1.5.1 + description: + - path to the pem file associated with the service account email + required: false + default: null + aliases: [] + project_id: + version_added: 1.5.1 + description: + - your GCE project ID + required: false + default: null + aliases: [] requirements: [ "libcloud" ] author: Eric Johnson @@ -96,39 +117,17 @@ EXAMPLES = ''' import sys -USER_AGENT_PRODUCT="Ansible-gce_net" -USER_AGENT_VERSION="v1beta15" - -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver +try: + from libcloud.compute.types import Provider + from libcloud.compute.providers import get_driver from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ ResourceExistsError, ResourceNotFoundError - _ = Provider.GCE -except ImportError: + _ = Provider.GCE +except ImportError: print("failed=True " + \ "msg='libcloud with GCE support required for this module.'") - sys.exit(1) - -# Load in the libcloud secrets file -try: - import secrets -except ImportError: - secrets = None -ARGS = getattr(secrets, 'GCE_PARAMS', ()) -KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {}) - -if not ARGS or not 'project' in KWARGS: - print("failed=True msg='Missing GCE connection " + \ - "parameters in libcloud secrets file.'") sys.exit(1) -def unexpected_error_msg(error): - """Format 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 def format_allowed(allowed): """Format the 'allowed' value so that it is GCE compatible.""" @@ -159,9 +158,14 @@ def main(): src_range = dict(), src_tags = dict(type='list'), state = dict(default='present'), + service_account_email = dict(), + pem_file = dict(), + project_id = dict(), ) ) + gce = gce_connect(module) + allowed = module.params.get('allowed') ipv4_range = module.params.get('ipv4_range') fwname = module.params.get('fwname') @@ -170,13 +174,6 @@ def main(): src_tags = module.params.get('src_tags') state = module.params.get('state') - try: - gce = get_driver(Provider.GCE)(*ARGS, **KWARGS) - gce.connection.user_agent_append("%s/%s" % ( - USER_AGENT_PRODUCT, USER_AGENT_VERSION)) - except Exception, e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - changed = False json_output = {'state': state} @@ -269,5 +266,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.gce import * main() diff --git a/library/cloud/gce_pd b/library/cloud/gce_pd index 6bd71eb3f37..a8e631a5522 100644 --- a/library/cloud/gce_pd +++ b/library/cloud/gce_pd @@ -75,6 +75,27 @@ options: required: false default: "us-central1-b" aliases: [] + service_account_email: + version_added: 1.5.1 + description: + - service account email + required: false + default: null + aliases: [] + pem_file: + version_added: 1.5.1 + description: + - path to the pem file associated with the service account email + required: false + default: null + aliases: [] + project_id: + version_added: 1.5.1 + description: + - your GCE project ID + required: false + default: null + aliases: [] requirements: [ "libcloud" ] author: Eric Johnson @@ -82,47 +103,26 @@ author: Eric Johnson EXAMPLES = ''' # Simple attachment action to an existing instance -- local_action: - module: gce_pd +- local_action: + module: gce_pd instance_name: notlocalhost - size_gb: 5 + size_gb: 5 name: pd ''' import sys -USER_AGENT_PRODUCT="Ansible-gce_pd" -USER_AGENT_VERSION="v1beta15" - -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver +try: + from libcloud.compute.types import Provider + from libcloud.compute.providers import get_driver from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ ResourceExistsError, ResourceNotFoundError, ResourceInUseError - _ = Provider.GCE -except ImportError: + _ = Provider.GCE +except ImportError: print("failed=True " + \ "msg='libcloud with GCE support is required for this module.'") - sys.exit(1) - -# Load in the libcloud secrets file -try: - import secrets -except ImportError: - secrets = None -ARGS = getattr(secrets, 'GCE_PARAMS', ()) -KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {}) - -if not ARGS or not 'project' in KWARGS: - print("failed=True " + \ - "msg='Missing GCE connection parameters in libcloud secrets file.'") sys.exit(1) -def unexpected_error_msg(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 def main(): module = AnsibleModule( @@ -135,9 +135,14 @@ def main(): size_gb = dict(default=10), state = dict(default='present'), zone = dict(default='us-central1-b'), + service_account_email = dict(), + pem_file = dict(), + project_id = dict(), ) ) + gce = gce_connect(module) + detach_only = module.params.get('detach_only') instance_name = module.params.get('instance_name') mode = module.params.get('mode') @@ -151,13 +156,6 @@ def main(): msg='Must specify an instance name when detaching a disk', changed=False) - try: - gce = get_driver(Provider.GCE)(*ARGS, datacenter=zone, **KWARGS) - gce.connection.user_agent_append("%s/%s" % ( - USER_AGENT_PRODUCT, USER_AGENT_VERSION)) - except Exception, e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - disk = inst = None changed = is_attached = False @@ -251,5 +249,6 @@ def main(): # import module snippets from ansible.module_utils.basic import * +from ansible.module_utils.gce import * main() diff --git a/plugins/inventory/gce.py b/plugins/inventory/gce.py index 22082a1e65f..6757e87b55c 100755 --- a/plugins/inventory/gce.py +++ b/plugins/inventory/gce.py @@ -73,7 +73,7 @@ Version: 0.0.1 ''' USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin" -USER_AGENT_VERSION="v1beta15" +USER_AGENT_VERSION="v1" import sys import os @@ -174,6 +174,10 @@ class GceInventory(object): 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 @@ class GceInventory(object): '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'''