summaryrefslogblamecommitdiff
path: root/theforeman.py
blob: 398c3eacb9d3db38d6c4d657c49602a3e90a4306 (plain) (tree)












































                                                                                   
            















                                                                           
               


























                                                                                                                                       



                        

























































                                                                                                    
                                                                               








































                                                                                                           



                                                                                                             















                                                                           
                                 
































































                                                                                     
#!/usr/bin/env python
#
# Internet Systems Consortium license
#
# Copyright (c) 2014, Franck Cuny (<franckcuny@gmail.com>)
#
# Permission to use, copy, modify, and/or distribute this software for any purpose
# with or without fee is hereby granted, provided that the above copyright notice
# and this permission notice appear in all copies.

# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTUOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
# THIS SOFTWARE.


'''
Foreman external inventory script
=================================

Generates inventory that Ansible can understand by making API requests
to Foreman.

Information about the Foreman's instance can be stored in the ``foreman.ini`` file.
A ``base_url``, ``username`` and ``password`` need to be provided. The path to an
alternate configuration file can be provided by exporting the ``FOREMAN_INI_PATH``
variable.

When run against a specific host, this script returns the following variables
based on the data obtained from Foreman:
 - id
 - ip
 - name
 - environment
 - os
 - model
 - compute_resource
 - domain
 - architecture
 - created
 - updated
 - status
 - hostgroup
 - ansible_ssh_host

When run in --list mode, instances are grouped by the following categories:
 - group

Examples:
  Execute uname on all instances in the dev group
  $ ansible -i theforeman.py dev -m shell -a \"/bin/uname -a\"

Author: Franck Cuny <franckcuny@gmail.com>
Version: 0.0.1
'''

import sys
import os
import re
import optparse
import ConfigParser
import collections

try:
    import json
except ImportError:
    import simplejson as json

try:
    from foreman.client import Foreman
    from requests.exceptions import ConnectionError
except ImportError, e:
    print ('python-foreman required for this module')
    print e
    sys.exit(1)


class ForemanInventory(object):
    """Foreman Inventory"""

    def _empty_inventory(self):
        """Empty inventory"""
        return {'_meta': {'hostvars': {}}}

    def _empty_cache(self):
        """Empty cache"""
        keys = ['operatingsystem', 'hostgroup', 'environment', 'model', 'compute_resource', 'domain', 'subnet', 'architecture', 'host']
        keys_d = {}
        for i in keys:
          keys_d[i] = {}
        return keys_d

    def __init__(self):
        """Main execution path"""

        self.inventory = self._empty_inventory()
        self._cache = self._empty_cache()

        self.base_url = None
        self.username = None
        self.password = None

        # Read settings and parse CLI arguments
        self.read_settings()
        self.parse_cli_args()

        if self.base_url is None or self.username is None or self.password is None:
            print '''Could not find values for Foreman base_url, username or password.
They must be specified via ini file.'''
            sys.exit(1)

        try:
            self.client = Foreman(self.base_url, (self.username, self.password))
        except ConnectionError, e:
            print '''It looks like Foreman's API is unreachable.'''
            print e
            sys.exit(1)

        if self.args.host:
            data_to_print = self.get_host_info(self.args.host)
        elif self.args.list:
            data_to_print = self.get_inventory()
        else:
            data_to_print = {}

        print(json.dumps(data_to_print, sort_keys=True, indent=4))

    def get_host_info(self, host_id):
        """Get information about an host"""
        host_desc = {}

        meta = self._get_object_from_id('host', host_id)
        if meta is None:
            return host_desc

        host_desc = {
            'id': meta.get('id'),
            'ip': meta.get('ip'),
            'name': meta.get('name'),
            'environment': meta.get('environment').get('environment').get('name').lower(),
            'os': self._get_os_from_id(meta.get('operatingsystem_id')),
            'model': self._get_model_from_id(meta.get('model_id')),
            'compute_resource': self._get_compute_resource_from_id(meta.get('compute_resource_id')),
            'domain': self._get_domain_from_id(meta.get('domain_id')),
            'subnet': self._get_subnet_from_id(meta.get('subnet_id')),
            'architecture': self._get_architecture_from_id(meta.get('architecture_id')),
            'created': meta.get('created_at'),
            'updated': meta.get('updated_at'),
            'status': meta.get('status'),
            'hostgroup': self._get_hostgroup_from_id(meta.get('hostgroup_id')),
            # to ssh from ansible
            'ansible_ssh_host': meta.get('ip'),
        }

        return host_desc

    def get_inventory(self):
        """Get all the host from the inventory"""
        groups = collections.defaultdict(list)
        hosts  = []

        page = 1
        while True:
            resp = self.client.index_hosts(page=page)
            if len(resp) < 1:
                break
            page  += 1
            hosts += resp

        if len(hosts) < 1:
            return groups

        for host in hosts:
            host_group = self._get_hostgroup_from_id(host.get('host').get('hostgroup_id'))
            server_name = host.get('host').get('name')
            groups[host_group].append(server_name)

        return groups

    def read_settings(self):
        """Read the settings from the foreman.ini file"""
        config = ConfigParser.SafeConfigParser()
        foreman_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'foreman.ini')
        foreman_ini_path = os.environ.get('FOREMAN_INI_PATH', foreman_default_ini_path)
        config.read(foreman_ini_path)
        self.base_url = config.get('foreman', 'base_url')
        self.username = config.get('foreman', 'username')
        self.password = config.get('foreman', 'password')

    def parse_cli_args(self):
        """Command line argument processing"""
        parser = optparse.OptionParser(description='Produce an Ansible Inventory file based on Foreman')
        parser.add_option('--list', action='store_true', default=True, help='List instances (default: True)')
        parser.add_option('--host', action='store', help='Get all the variables about a specific instance')
        (self.args, self.options) = parser.parse_args()

    def _get_os_from_id(self, os_id):
        """Get operating system name"""
        os_obj = self._get_object_from_id('operatingsystem', os_id)
        if os_obj is None:
            return os_obj

        os_name = "{0}-{1}".format(os_obj.get('name'), os_obj.get('major'))
        return os_name

    def _get_hostgroup_from_id(self, host_id):
        """Get hostgroup name"""
        group = self._get_object_from_id('hostgroup', host_id)
        if group is None:
            return group

        return group.get('label')

    def _get_environment_from_id(self, env_id):
        """Get environment name"""
        environment = self._get_object_from_id('environment', env_id)
        if environment is None:
            return environment

        return environment.get('name').lower()

    def _get_model_from_id(self, model_id):
        """Get model from an ID"""
        model = self._get_object_from_id('model', model_id)
        if model is None:
            return model

        return model.get('name')

    def _get_compute_resource_from_id(self, resource_id):
        """Get compute resource from id"""
        compute_resource =  self._get_object_from_id('compute_resource', resource_id)
        if compute_resource is None:
            return compute_resource

        return compute_resource.get('name')

    def _get_domain_from_id(self, domain_id):
        """Get domain from id"""
        domain = self._get_object_from_id('domain', domain_id)
        if domain is None:
            return domain
        return domain.get('name')

    def _get_subnet_from_id(self, subnet_id):
        """Get subnet from id"""
        subnet = self._get_object_from_id('subnet', subnet_id)
        if subnet is None:
            return subnet

        return subnet.get('name')

    def _get_architecture_from_id(self, arch_id):
        """Get architecture from id"""
        arch = self._get_object_from_id('architecture', arch_id)
        if arch is None:
            return None

        return arch.get('name')

    def _get_object_from_id(self, obj_type, obj_id):
        """Get an object from it's ID"""
        if obj_id is None:
            return None

        obj = self._cache.get(obj_type).get(obj_id, None)

        if obj is None:
            method_name = "show_{0}s".format(obj_type)
            func = getattr(self.client, method_name)
            obj = func(obj_id)
            self._cache[obj_type][obj_id] = obj

        return obj.get(obj_type)


ForemanInventory()