#!/usr/bin/env python # Copyright (c) 2016, Hugh Ma # # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type # Stacki inventory script # Configure stacki.yml with proper auth information and place in the following: # - ../inventory/stacki.yml # - /etc/stacki/stacki.yml # - /etc/ansible/stacki.yml # The stacki.yml file can contain entries for authentication information # regarding the Stacki front-end node. # # use_hostnames uses hostname rather than interface ip as connection # # """ Example Usage: List Stacki Nodes $ ./stack.py --list Example Configuration: --- stacki: auth: stacki_user: admin stacki_password: abc12345678910 stacki_endpoint: http://192.168.200.50/stack use_hostnames: false """ import argparse import os import sys import yaml from distutils.version import StrictVersion import json try: import requests except Exception: sys.exit('requests package is required for this inventory script') CONFIG_FILES = ['/etc/stacki/stacki.yml', '/etc/ansible/stacki.yml'] def stack_auth(params): endpoint = params['stacki_endpoint'] auth_creds = {'USERNAME': params['stacki_user'], 'PASSWORD': params['stacki_password']} client = requests.session() client.get(endpoint) init_csrf = client.cookies['csrftoken'] header = {'csrftoken': init_csrf, 'X-CSRFToken': init_csrf, 'Content-type': 'application/x-www-form-urlencoded'} login_endpoint = endpoint + "/login" login_req = client.post(login_endpoint, data=auth_creds, headers=header) csrftoken = login_req.cookies['csrftoken'] sessionid = login_req.cookies['sessionid'] auth_creds.update(CSRFTOKEN=csrftoken, SESSIONID=sessionid) return client, auth_creds def stack_build_header(auth_creds): header = {'csrftoken': auth_creds['CSRFTOKEN'], 'X-CSRFToken': auth_creds['CSRFTOKEN'], 'sessionid': auth_creds['SESSIONID'], 'Content-type': 'application/json'} return header def stack_host_list(endpoint, header, client): stack_r = client.post(endpoint, data=json.dumps({"cmd": "list host"}), headers=header) return json.loads(stack_r.json()) def stack_net_list(endpoint, header, client): stack_r = client.post(endpoint, data=json.dumps({"cmd": "list host interface"}), headers=header) return json.loads(stack_r.json()) def format_meta(hostdata, intfdata, config): use_hostnames = config['use_hostnames'] meta = dict(all=dict(hosts=list()), frontends=dict(hosts=list()), backends=dict(hosts=list()), _meta=dict(hostvars=dict())) # Iterate through list of dicts of hosts and remove # environment key as it causes conflicts for host in hostdata: del host['environment'] meta['_meta']['hostvars'][host['host']] = host meta['_meta']['hostvars'][host['host']]['interfaces'] = list() # @bbyhuy to improve readability in next iteration for intf in intfdata: if intf['host'] in meta['_meta']['hostvars']: meta['_meta']['hostvars'][intf['host']]['interfaces'].append(intf) if intf['default'] is True: meta['_meta']['hostvars'][intf['host']]['ansible_host'] = intf['ip'] if not use_hostnames: meta['all']['hosts'].append(intf['ip']) if meta['_meta']['hostvars'][intf['host']]['appliance'] != 'frontend': meta['backends']['hosts'].append(intf['ip']) else: meta['frontends']['hosts'].append(intf['ip']) else: meta['all']['hosts'].append(intf['host']) if meta['_meta']['hostvars'][intf['host']]['appliance'] != 'frontend': meta['backends']['hosts'].append(intf['host']) else: meta['frontends']['hosts'].append(intf['host']) return meta def parse_args(): parser = argparse.ArgumentParser(description='Stacki Inventory Module') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--list', action='store_true', help='List active hosts') group.add_argument('--host', help='List details about the specific host') return parser.parse_args() def main(): args = parse_args() if StrictVersion(requests.__version__) < StrictVersion("2.4.3"): sys.exit('requests>=2.4.3 is required for this inventory script') try: config_files = CONFIG_FILES config_files.append(os.path.dirname(os.path.realpath(__file__)) + '/stacki.yml') config = None for cfg_file in config_files: if os.path.isfile(cfg_file): stream = open(cfg_file, 'r') config = yaml.safe_load(stream) break if not config: sys.stderr.write("No config file found at {0}\n".format(config_files)) sys.exit(1) client, auth_creds = stack_auth(config['stacki']['auth']) header = stack_build_header(auth_creds) host_list = stack_host_list(config['stacki']['auth']['stacki_endpoint'], header, client) intf_list = stack_net_list(config['stacki']['auth']['stacki_endpoint'], header, client) final_meta = format_meta(host_list, intf_list, config) print(json.dumps(final_meta, indent=4)) except Exception as e: sys.stderr.write('%s\n' % e.message) sys.exit(1) sys.exit(0) if __name__ == '__main__': main()