diff --git a/contrib/inventory/scaleway.ini b/contrib/inventory/scaleway.ini new file mode 100644 index 00000000000..ab0b109e99a --- /dev/null +++ b/contrib/inventory/scaleway.ini @@ -0,0 +1,34 @@ +# Ansible dynamic inventory script for Scaleway cloud provider +# + +[compute] +# Fetch inventory for regions. If not defined will read the SCALEWAY_REGION environment variable +# +# regions = all +# regions = ams1 +# regions = par1, ams1 +regions = par1 + + +# Define a Scaleway token to perform required queries on the API +# in order to generate inventory output. +# +[auth] +api_token = mysecrettoken + + +# To avoid performing excessive calls to Scaleway API you can define a +# cache for the plugin output. Within the time defined in seconds, latest +# output will be reused. After that time, the cache will be refreshed. +# +[cache] +cache_max_age = 60 +cache_dir = '~/.ansible/tmp' + + +[defaults] +# You may want to use only public IP addresses or private IP addresses. +# You can set public_ip_only configuration to get public IPs only. +# If not defined defaults to retrieving private IP addresses. +# +public_ip_only = false diff --git a/contrib/inventory/scaleway.py b/contrib/inventory/scaleway.py new file mode 100755 index 00000000000..c6ed7b3690a --- /dev/null +++ b/contrib/inventory/scaleway.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +External inventory script for Scaleway +==================================== + +Shamelessly copied from an existing inventory script. + +This script generates an inventory that Ansible can understand by making API requests to Scaleway API + +Requires some python libraries, ensure to have them installed when using this script. (pip install requests https://pypi.python.org/pypi/requests) + +Before using this script you may want to modify scaleway.ini config file. + +This script generates an Ansible hosts file with these host groups: + +: Defines host itself with Scaleway's hostname as group name. +: Contains all hosts which has "" as tag. +: Contains all hosts which are in the "" region. +all: Contains all hosts defined in Scaleway. +''' + +# (c) 2017, Paul B. +# +# 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 . + +import copy +import os +import requests +import six +from six.moves import configparser +import sys +import time +import traceback + +try: + import json +except ImportError: + import simplejson as json + +EMPTY_GROUP = { + 'children': [], + 'hosts': [] +} + + +class ScalewayAPI: + REGIONS = ['par1', 'ams1'] + + def __init__(self, auth_token, region): + self.session = requests.session() + self.session.headers.update({ + 'User-Agent': 'Ansible Python/%s' % (sys.version.split(' ')[0]) + }) + self.session.headers.update({ + 'X-Auth-Token': auth_token.encode('latin1') + }) + self.base_url = 'https://cp-%s.scaleway.com' % (region) + + def servers(self): + raw = self.session.get('/'.join([self.base_url, 'servers'])) + + try: + response = raw.json() + return self.get_resource('servers', response, raw) + except ValueError: + return [] + + def get_resource(self, resource, response, raw): + raw.raise_for_status() + + if resource in response: + return response[resource] + else: + raise ValueError( + "Resource %s not found in Scaleway API response" % (resource)) + + +def env_or_param(env_key, param=None, fallback=None): + env_value = os.environ.get(env_key) + + if (param, env_value) == (None, None): + return fallback + elif env_value is not None: + return env_value + else: + return param + + +def save_cache(data, config): + ''' saves item to cache ''' + dpath = config.get('cache', 'cache_dir') + try: + cache = open('/'.join([dpath, 'scaleway_ansible_inventory.json']), 'w') + cache.write(json.dumps(data)) + cache.close() + except IOError as e: + pass # not really sure what to do here + + +def get_cache(cache_item, config): + ''' returns cached item ''' + dpath = config.get('cache', 'cache_dir') + inv = {} + try: + cache = open('/'.join([dpath, 'scaleway_ansible_inventory.json']), 'r') + inv = cache.read() + cache.close() + except IOError as e: + pass # not really sure what to do here + + return inv + + +def cache_available(config): + ''' checks if we have a 'fresh' cache available for item requested ''' + + if config.has_option('cache', 'cache_dir'): + dpath = config.get('cache', 'cache_dir') + + try: + existing = os.stat( + '/'.join([dpath, 'scaleway_ansible_inventory.json'])) + except OSError: + return False + + if config.has_option('cache', 'cache_max_age'): + maxage = config.get('cache', 'cache_max_age') + else: + maxage = 60 + if (int(time.time()) - int(existing.st_mtime)) <= int(maxage): + return True + + return False + + +def generate_inv_from_api(config): + try: + inventory['all'] = copy.deepcopy(EMPTY_GROUP) + + auth_token = env_or_param( + 'SCALEWAY_TOKEN', config.get('auth', 'api_token')) + if config.has_option('compute', 'regions'): + regions = config.get('compute', 'regions') + if regions == 'all': + regions = ScalewayAPI.REGIONS + else: + regions = map(str.strip, regions.split(',')) + else: + regions = [ + env_or_param('SCALEWAY_REGION', fallback='par1') + ] + + for region in regions: + api = ScalewayAPI(auth_token, region) + + for server in api.servers(): + hostname = server['hostname'] + if config.has_option('defaults', 'public_ip_only') and config.getboolean('defaults', 'public_ip_only'): + ip = server['public_ip']['address'] + else: + ip = server['private_ip'] + for server_tag in server['tags']: + if server_tag not in inventory: + inventory[server_tag] = copy.deepcopy(EMPTY_GROUP) + inventory[server_tag]['children'].append(hostname) + if region not in inventory: + inventory[region] = copy.deepcopy(EMPTY_GROUP) + inventory[region]['children'].append(hostname) + inventory['all']['children'].append(hostname) + inventory[hostname] = [] + inventory[hostname].append(ip) + + return inventory + except Exception: + # Return empty hosts output + traceback.print_exc() + return {'all': {'hosts': []}, '_meta': {'hostvars': {}}} + + +def get_inventory(config): + ''' Reads the inventory from cache or Scaleway api ''' + + if cache_available(config): + inv = get_cache('scaleway_ansible_inventory.json', config) + else: + inv = generate_inv_from_api(config) + + save_cache(inv, config) + return json.dumps(inv) + + +if __name__ == '__main__': + inventory = {} + + # Read config + if six.PY3: + config = configparser.ConfigParser() + else: + config = configparser.SafeConfigParser() + for configfilename in [os.path.abspath(sys.argv[0]).rsplit('.py')[0] + '.ini', 'scaleway.ini']: + if os.path.exists(configfilename): + config.read(configfilename) + break + + if cache_available(config): + inventory = get_cache('scaleway_ansible_inventory.json', config) + else: + inventory = get_inventory(config) + + # return to ansible + sys.stdout.write(str(inventory)) + sys.stdout.flush()