gce.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. #!/usr/bin/env python2
  2. # Copyright 2013 Google Inc.
  3. #
  4. # This file is part of Ansible
  5. #
  6. # Ansible is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # Ansible is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
  18. '''
  19. GCE external inventory script
  20. =================================
  21. Generates inventory that Ansible can understand by making API requests
  22. Google Compute Engine via the libcloud library. Full install/configuration
  23. instructions for the gce* modules can be found in the comments of
  24. ansible/test/gce_tests.py.
  25. When run against a specific host, this script returns the following variables
  26. based on the data obtained from the libcloud Node object:
  27. - gce_uuid
  28. - gce_id
  29. - gce_image
  30. - gce_machine_type
  31. - gce_private_ip
  32. - gce_public_ip
  33. - gce_name
  34. - gce_description
  35. - gce_status
  36. - gce_zone
  37. - gce_tags
  38. - gce_metadata
  39. - gce_network
  40. When run in --list mode, instances are grouped by the following categories:
  41. - zone:
  42. zone group name examples are us-central1-b, europe-west1-a, etc.
  43. - instance tags:
  44. An entry is created for each tag. For example, if you have two instances
  45. with a common tag called 'foo', they will both be grouped together under
  46. the 'tag_foo' name.
  47. - network name:
  48. the name of the network is appended to 'network_' (e.g. the 'default'
  49. network will result in a group named 'network_default')
  50. - machine type
  51. types follow a pattern like n1-standard-4, g1-small, etc.
  52. - running status:
  53. group name prefixed with 'status_' (e.g. status_running, status_stopped,..)
  54. - image:
  55. when using an ephemeral/scratch disk, this will be set to the image name
  56. used when creating the instance (e.g. debian-7-wheezy-v20130816). when
  57. your instance was created with a root persistent disk it will be set to
  58. 'persistent_disk' since there is no current way to determine the image.
  59. Examples:
  60. Execute uname on all instances in the us-central1-a zone
  61. $ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a"
  62. Use the GCE inventory script to print out instance specific information
  63. $ contrib/inventory/gce.py --host my_instance
  64. Author: Eric Johnson <erjohnso@google.com>
  65. Version: 0.0.1
  66. '''
  67. __requires__ = ['pycrypto>=2.6']
  68. try:
  69. import pkg_resources
  70. except ImportError:
  71. # Use pkg_resources to find the correct versions of libraries and set
  72. # sys.path appropriately when there are multiversion installs. We don't
  73. # fail here as there is code that better expresses the errors where the
  74. # library is used.
  75. pass
  76. USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
  77. USER_AGENT_VERSION="v1"
  78. import sys
  79. import os
  80. import argparse
  81. import ConfigParser
  82. try:
  83. import json
  84. except ImportError:
  85. import simplejson as json
  86. try:
  87. from libcloud.compute.types import Provider
  88. from libcloud.compute.providers import get_driver
  89. _ = Provider.GCE
  90. except:
  91. print("GCE inventory script requires libcloud >= 0.13")
  92. sys.exit(1)
  93. class GceInventory(object):
  94. def __init__(self):
  95. # Read settings and parse CLI arguments
  96. self.parse_cli_args()
  97. self.driver = self.get_gce_driver()
  98. # Just display data for specific host
  99. if self.args.host:
  100. print(self.json_format_dict(self.node_to_dict(
  101. self.get_instance(self.args.host)),
  102. pretty=self.args.pretty))
  103. sys.exit(0)
  104. # Otherwise, assume user wants all instances grouped
  105. print(self.json_format_dict(self.group_instances(),
  106. pretty=self.args.pretty))
  107. sys.exit(0)
  108. def get_gce_driver(self):
  109. """Determine the GCE authorization settings and return a
  110. libcloud driver.
  111. """
  112. gce_ini_default_path = os.path.join(
  113. os.path.dirname(os.path.realpath(__file__)), "gce.ini")
  114. gce_ini_path = os.environ.get('GCE_INI_PATH', gce_ini_default_path)
  115. # Create a ConfigParser.
  116. # This provides empty defaults to each key, so that environment
  117. # variable configuration (as opposed to INI configuration) is able
  118. # to work.
  119. config = ConfigParser.SafeConfigParser(defaults={
  120. 'gce_service_account_email_address': '',
  121. 'gce_service_account_pem_file_path': '',
  122. 'gce_project_id': '',
  123. 'libcloud_secrets': '',
  124. })
  125. if 'gce' not in config.sections():
  126. config.add_section('gce')
  127. config.read(gce_ini_path)
  128. # Attempt to get GCE params from a configuration file, if one
  129. # exists.
  130. secrets_path = config.get('gce', 'libcloud_secrets')
  131. secrets_found = False
  132. try:
  133. import secrets
  134. args = list(getattr(secrets, 'GCE_PARAMS', []))
  135. kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
  136. secrets_found = True
  137. except:
  138. pass
  139. if not secrets_found and secrets_path:
  140. if not secrets_path.endswith('secrets.py'):
  141. err = "Must specify libcloud secrets file as "
  142. err += "/absolute/path/to/secrets.py"
  143. print(err)
  144. sys.exit(1)
  145. sys.path.append(os.path.dirname(secrets_path))
  146. try:
  147. import secrets
  148. args = list(getattr(secrets, 'GCE_PARAMS', []))
  149. kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
  150. secrets_found = True
  151. except:
  152. pass
  153. if not secrets_found:
  154. args = [
  155. config.get('gce','gce_service_account_email_address'),
  156. config.get('gce','gce_service_account_pem_file_path')
  157. ]
  158. kwargs = {'project': config.get('gce', 'gce_project_id')}
  159. # If the appropriate environment variables are set, they override
  160. # other configuration; process those into our args and kwargs.
  161. args[0] = os.environ.get('GCE_EMAIL', args[0])
  162. args[1] = os.environ.get('GCE_PEM_FILE_PATH', args[1])
  163. kwargs['project'] = os.environ.get('GCE_PROJECT', kwargs['project'])
  164. # Retrieve and return the GCE driver.
  165. gce = get_driver(Provider.GCE)(*args, **kwargs)
  166. gce.connection.user_agent_append(
  167. '%s/%s' % (USER_AGENT_PRODUCT, USER_AGENT_VERSION),
  168. )
  169. return gce
  170. def parse_cli_args(self):
  171. ''' Command line argument processing '''
  172. parser = argparse.ArgumentParser(
  173. description='Produce an Ansible Inventory file based on GCE')
  174. parser.add_argument('--list', action='store_true', default=True,
  175. help='List instances (default: True)')
  176. parser.add_argument('--host', action='store',
  177. help='Get all information about an instance')
  178. parser.add_argument('--pretty', action='store_true', default=False,
  179. help='Pretty format (default: False)')
  180. self.args = parser.parse_args()
  181. def node_to_dict(self, inst):
  182. md = {}
  183. if inst is None:
  184. return {}
  185. if inst.extra['metadata'].has_key('items'):
  186. for entry in inst.extra['metadata']['items']:
  187. md[entry['key']] = entry['value']
  188. net = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
  189. return {
  190. 'gce_uuid': inst.uuid,
  191. 'gce_id': inst.id,
  192. 'gce_image': inst.image,
  193. 'gce_machine_type': inst.size,
  194. 'gce_private_ip': inst.private_ips[0],
  195. 'gce_public_ip': inst.public_ips[0] if len(inst.public_ips) >= 1 else None,
  196. 'gce_name': inst.name,
  197. 'gce_description': inst.extra['description'],
  198. 'gce_status': inst.extra['status'],
  199. 'gce_zone': inst.extra['zone'].name,
  200. 'gce_tags': inst.extra['tags'],
  201. 'gce_metadata': md,
  202. 'gce_network': net,
  203. # Hosts don't have a public name, so we add an IP
  204. 'ansible_ssh_host': inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0]
  205. }
  206. def get_instance(self, instance_name):
  207. '''Gets details about a specific instance '''
  208. try:
  209. return self.driver.ex_get_node(instance_name)
  210. except Exception as e:
  211. return None
  212. def group_instances(self):
  213. '''Group all instances'''
  214. groups = {}
  215. meta = {}
  216. meta["hostvars"] = {}
  217. for node in self.driver.list_nodes():
  218. name = node.name
  219. meta["hostvars"][name] = self.node_to_dict(node)
  220. zone = node.extra['zone'].name
  221. if groups.has_key(zone): groups[zone].append(name)
  222. else: groups[zone] = [name]
  223. tags = node.extra['tags']
  224. for t in tags:
  225. if t.startswith('group-'):
  226. tag = t[6:]
  227. else:
  228. tag = 'tag_%s' % t
  229. if groups.has_key(tag): groups[tag].append(name)
  230. else: groups[tag] = [name]
  231. net = node.extra['networkInterfaces'][0]['network'].split('/')[-1]
  232. net = 'network_%s' % net
  233. if groups.has_key(net): groups[net].append(name)
  234. else: groups[net] = [name]
  235. machine_type = node.size
  236. if groups.has_key(machine_type): groups[machine_type].append(name)
  237. else: groups[machine_type] = [name]
  238. image = node.image and node.image or 'persistent_disk'
  239. if groups.has_key(image): groups[image].append(name)
  240. else: groups[image] = [name]
  241. status = node.extra['status']
  242. stat = 'status_%s' % status.lower()
  243. if groups.has_key(stat): groups[stat].append(name)
  244. else: groups[stat] = [name]
  245. groups["_meta"] = meta
  246. return groups
  247. def json_format_dict(self, data, pretty=False):
  248. ''' Converts a dict to a JSON object and dumps it as a formatted
  249. string '''
  250. if pretty:
  251. return json.dumps(data, sort_keys=True, indent=2)
  252. else:
  253. return json.dumps(data)
  254. # Run the script
  255. GceInventory()