gce.py 8.55 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
#!/usr/bin/python
# Copyright 2013 Google Inc.
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible.  If not, see <http://www.gnu.org/licenses/>.

'''
GCE external inventory script
=================================

Generates inventory that Ansible can understand by making API requests
Google Compute Engine via the libcloud library.  Full install/configuration
instructions for the gce* modules can be found in the comments of
ansible/test/gce_tests.py.

When run against a specific host, this script returns the following variables
based on the data obtained from the libcloud Node object:
 - gce_uuid
 - gce_id
 - gce_image
 - gce_machine_type
 - gce_private_ip
 - gce_public_ip
 - gce_name
 - gce_description
 - gce_status
 - gce_zone
 - gce_tags
 - gce_metadata
 - gce_network

When run in --list mode, instances are grouped by the following categories:
 - zone:
   zone group name examples are us-central1-b, europe-west1-a, etc.
 - instance tags:
   An entry is created for each tag.  For example, if you have two instances
   with a common tag called 'foo', they will both be grouped together under
   the 'tag_foo' name.
 - network name:
   the name of the network is appended to 'network_' (e.g. the 'default'
   network will result in a group named 'network_default')
 - machine type
   types follow a pattern like n1-standard-4, g1-small, etc.
 - running status:
   group name prefixed with 'status_' (e.g. status_running, status_stopped,..)
 - image:
   when using an ephemeral/scratch disk, this will be set to the image name
   used when creating the instance (e.g. debian-7-wheezy-v20130816).  when
   your instance was created with a root persistent disk it will be set to
   'persistent_disk' since there is no current way to determine the image.

Examples:
  Execute uname on all instances in the us-central1-a zone
  $ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a"

  Use the GCE inventory script to print out instance specific information
  $ plugins/inventory/gce.py --host my_instance

Author: Eric Johnson <erjohnso@google.com>
Version: 0.0.1
'''

Eric Johnson committed
75
USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
76
USER_AGENT_VERSION="v1"
Eric Johnson committed
77

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
import sys
import os
import argparse
import ConfigParser

try:
    import json
except ImportError:
    import simplejson as json

try:
    from libcloud.compute.types import Provider
    from libcloud.compute.providers import get_driver
    _ = Provider.GCE
except:
    print("GCE inventory script requires libcloud >= 0.13")
    sys.exit(1)


class GceInventory(object):
    def __init__(self):
        # Read settings and parse CLI arguments
        self.parse_cli_args()
        self.driver = self.get_gce_driver()

        # Just display data for specific host
        if self.args.host:
            print self.json_format_dict(self.node_to_dict(
                    self.get_instance(self.args.host)))
            sys.exit(0)

        # Otherwise, assume user wants all instances grouped
        print self.json_format_dict(self.group_instances())
        sys.exit(0)


    def get_gce_driver(self):
        '''Determine GCE authorization settings and return libcloud driver.'''

117 118 119 120
        gce_ini_default_path = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), "gce.ini")
        gce_ini_path = os.environ.get('GCE_INI_PATH', gce_ini_default_path)

121
        config = ConfigParser.SafeConfigParser()
122
        config.read(gce_ini_path)
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

        # the GCE params in 'secrets.py' will override these
        secrets_path = config.get('gce', 'libcloud_secrets')

        secrets_found = False
        try:
            import secrets
            args = getattr(secrets, 'GCE_PARAMS', ())
            kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
            secrets_found = True
        except:
            pass

        if not secrets_found and secrets_path:
            if not secrets_path.endswith('secrets.py'):
                err = "Must specify libcloud secrets file as "
                err += "/absolute/path/to/secrets.py"
                print(err)
                sys.exit(1)
            sys.path.append(os.path.dirname(secrets_path))
            try:
                import secrets
                args = getattr(secrets, 'GCE_PARAMS', ())
                kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
                secrets_found = True
            except:
                pass
        if not secrets_found:
            args = (
                config.get('gce','gce_service_account_email_address'),
                config.get('gce','gce_service_account_pem_file_path')
            )
            kwargs = {'project': config.get('gce','gce_project_id')}
Eric Johnson committed
156 157 158 159 160

        gce = get_driver(Provider.GCE)(*args, **kwargs)
        gce.connection.user_agent_append("%s/%s" % (
                USER_AGENT_PRODUCT, USER_AGENT_VERSION))
        return gce
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176


    def parse_cli_args(self):
        ''' Command line argument processing '''

        parser = argparse.ArgumentParser(
                description='Produce an Ansible Inventory file based on GCE')
        parser.add_argument('--list', action='store_true', default=True,
                           help='List instances (default: True)')
        parser.add_argument('--host', action='store',
                           help='Get all information about an instance')
        self.args = parser.parse_args()


    def node_to_dict(self, inst):
        md = {}
177 178 179 180

        if inst is None:
            return {}

181 182 183 184 185 186 187 188 189 190
        if inst.extra['metadata'].has_key('items'):
            for entry in inst.extra['metadata']['items']:
                md[entry['key']] = entry['value']

        net = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
        return {
            'gce_uuid': inst.uuid,
            'gce_id': inst.id,
            'gce_image': inst.image,
            'gce_machine_type': inst.size,
191 192
            'gce_private_ip': inst.private_ips[0],
            'gce_public_ip': inst.public_ips[0],
193 194 195 196 197 198
            'gce_name': inst.name,
            'gce_description': inst.extra['description'],
            'gce_status': inst.extra['status'],
            'gce_zone': inst.extra['zone'].name,
            'gce_tags': inst.extra['tags'],
            'gce_metadata': md,
199 200 201
            'gce_network': net,
            # Hosts don't have a public name, so we add an IP
            'ansible_ssh_host': inst.public_ips[0]
202 203 204 205
        }

    def get_instance(self, instance_name):
        '''Gets details about a specific instance '''
206 207 208 209
        try:
            return self.driver.ex_get_node(instance_name)
        except Exception, e:
            return None
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

    def group_instances(self):
        '''Group all instances'''
        groups = {}
        for node in self.driver.list_nodes():
            name = node.name

            zone = node.extra['zone'].name
            if groups.has_key(zone): groups[zone].append(name)
            else: groups[zone] = [name]

            tags = node.extra['tags']
            for t in tags:
                tag = 'tag_%s' % t
                if groups.has_key(tag): groups[tag].append(name)
                else: groups[tag] = [name]

            net = node.extra['networkInterfaces'][0]['network'].split('/')[-1]
            net = 'network_%s' % net
            if groups.has_key(net): groups[net].append(name)
            else: groups[net] = [name]

            machine_type = node.size
            if groups.has_key(machine_type): groups[machine_type].append(name)
            else: groups[machine_type] = [name]

            image = node.image and node.image or 'persistent_disk'
            if groups.has_key(image): groups[image].append(name)
            else: groups[image] = [name]

            status = node.extra['status']
            stat = 'status_%s' % status.lower()
            if groups.has_key(stat): groups[stat].append(name)
            else: groups[stat] = [name]
        return groups

    def json_format_dict(self, data, pretty=False):
        ''' Converts a dict to a JSON object and dumps it as a formatted
        string '''

        if pretty:
            return json.dumps(data, sort_keys=True, indent=2)
        else:
            return json.dumps(data)


# Run the script
GceInventory()