aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rwxr-xr-xforeman_ansible_inventory.py80
-rw-r--r--requirements.txt3
-rw-r--r--setup.cfg6
-rw-r--r--setup.py25
-rw-r--r--tests/test_get_json.py76
-rw-r--r--tox.ini18
7 files changed, 164 insertions, 47 deletions
diff --git a/.gitignore b/.gitignore
index 8934ee2..e8629a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
*.index
*.cache
*.params
+*.facts
+*.pyc
+.tox/
build/
dist/
*.egg-info/
diff --git a/foreman_ansible_inventory.py b/foreman_ansible_inventory.py
index 2395811..ea0fd2f 100755
--- a/foreman_ansible_inventory.py
+++ b/foreman_ansible_inventory.py
@@ -23,9 +23,9 @@ import ConfigParser
import copy
import os
import re
-from time import time
import requests
from requests.auth import HTTPBasicAuth
+from time import time
try:
import json
@@ -35,18 +35,23 @@ except ImportError:
class ForemanInventory(object):
def __init__(self):
- """ Main execution path """
self.inventory = dict() # A list of groups and the hosts in that group
- self.cache = dict() # Details about hosts in the inventory
- self.params = dict() # Params of each host
- self.facts = dict() # Facts of each host
+ self.cache = dict() # Details about hosts in the inventory
+ self.params = dict() # Params of each host
+ self.facts = dict() # Facts of each host
self.hostgroups = dict() # host groups
+ def run(self):
+ self._read_settings()
+ self._get_inventory()
+ self._print_data()
+
+ def _read_settings(self):
# Read settings and parse CLI arguments
self.read_settings()
self.parse_cli_args()
- # Cache
+ def _get_inventory(self):
if self.args.refresh_cache:
self.update_cache()
elif not self.is_cache_valid():
@@ -57,9 +62,8 @@ class ForemanInventory(object):
self.load_facts_from_cache()
self.load_cache_from_cache()
+ def _print_data(self):
data_to_print = ""
-
- # Data to print
if self.args.host:
data_to_print += self.get_host_info()
else:
@@ -77,20 +81,19 @@ class ForemanInventory(object):
print(data_to_print)
def is_cache_valid(self):
- """ Determines if the cache files have expired, or if it is still valid """
-
+ """Determines if the cache is still valid"""
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
if (os.path.isfile(self.cache_path_inventory) and
os.path.isfile(self.cache_path_params) and
- os.path.isfile(self.cache_path_facts)):
+ os.path.isfile(self.cache_path_facts)):
return True
return False
def read_settings(self):
- """ Reads the settings from the foreman.ini file """
+ """Reads the settings from the foreman.ini file"""
config = ConfigParser.SafeConfigParser()
config_paths = [
@@ -141,7 +144,7 @@ class ForemanInventory(object):
self.cache_max_age = config.getint('cache', 'max_age')
def parse_cli_args(self):
- """ Command line argument processing """
+ """Command line argument processing"""
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on foreman')
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
@@ -162,10 +165,13 @@ class ForemanInventory(object):
break
ret.raise_for_status()
json = ret.json()
- if not json.has_key('results'):
+ # /hosts/:id has not results key
+ if 'results' not in json:
return json
- if type(json['results']) == type({}):
+ # Facts are returned as dict in results not list
+ if isinstance(json['results'], dict):
return json['results']
+ # List of all hosts is returned paginaged
results = results + json['results']
if len(results) >= json['total']:
break
@@ -184,7 +190,8 @@ class ForemanInventory(object):
def _get_all_params_by_id(self, hid):
url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid)
ret = self._get_json(url, [404])
- if ret == []: ret = {}
+ if ret == []:
+ ret = {}
return ret.get('all_parameters', {})
def _get_facts_by_id(self, hid):
@@ -192,9 +199,7 @@ class ForemanInventory(object):
return self._get_json(url)
def _resolve_params(self, host):
- """
- Fetch host params and convert to dict
- """
+ """Fetch host params and convert to dict"""
params = {}
for param in self._get_all_params_by_id(host['id']):
@@ -204,9 +209,7 @@ class ForemanInventory(object):
return params
def _get_facts(self, host):
- """
- Fetch all host facts of the host
- """
+ """Fetch all host facts of the host"""
if not self.want_facts:
return {}
@@ -234,7 +237,7 @@ class ForemanInventory(object):
if val:
safe_key = self.to_safe('%s%s_%s' % (self.group_prefix, group, val.lower()))
self.push(self.inventory, safe_key, dns_name)
-
+
for group in ['lifecycle_environment', 'content_view']:
val = host.get('content_facet_attributes', {}).get('%s_name' % group)
if val:
@@ -264,14 +267,16 @@ class ForemanInventory(object):
self.params[dns_name] = params
self.facts[dns_name] = self._get_facts(host)
self.push(self.inventory, 'all', dns_name)
+ self._write_cache()
+ def _write_cache(self):
self.write_to_cache(self.cache, self.cache_path_cache)
self.write_to_cache(self.inventory, self.cache_path_inventory)
self.write_to_cache(self.params, self.cache_path_params)
self.write_to_cache(self.facts, self.cache_path_facts)
def get_host_info(self):
- """ Get variables about a specific host """
+ """Get variables about a specific host"""
if not self.cache or len(self.cache) == 0:
# Need to load index from cache
@@ -294,21 +299,21 @@ class ForemanInventory(object):
d[k] = [v]
def load_inventory_from_cache(self):
- """ Reads the index from the cache file sets self.index """
+ """Read the index from the cache file sets self.index"""
cache = open(self.cache_path_inventory, 'r')
json_inventory = cache.read()
self.inventory = json.loads(json_inventory)
def load_params_from_cache(self):
- """ Reads the index from the cache file sets self.index """
+ """Read the index from the cache file sets self.index"""
cache = open(self.cache_path_params, 'r')
json_params = cache.read()
self.params = json.loads(json_params)
def load_facts_from_cache(self):
- """ Reads the index from the cache file sets self.index """
+ """Read the index from the cache file sets self.facts"""
if not self.want_facts:
return
cache = open(self.cache_path_facts, 'r')
@@ -316,26 +321,33 @@ class ForemanInventory(object):
self.facts = json.loads(json_facts)
def load_cache_from_cache(self):
- """ Reads the cache from the cache file sets self.cache """
+ """Read the cache from the cache file sets self.cache"""
cache = open(self.cache_path_cache, 'r')
json_cache = cache.read()
self.cache = json.loads(json_cache)
def write_to_cache(self, data, filename):
- """ Writes data in JSON format to a file """
+ """Write data in JSON format to a file"""
json_data = self.json_format_dict(data, True)
cache = open(filename, 'w')
cache.write(json_data)
cache.close()
- def to_safe(self, word):
- ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups '''
+ @staticmethod
+ def to_safe(word):
+ '''Converts 'bad' characters in a string to underscores
+
+ so they can be used as Ansible groups
+
+ >>> ForemanInventory.to_safe("foo-bar baz")
+ 'foo_barbaz'
+ '''
regex = "[^A-Za-z0-9\_]"
return re.sub(regex, "_", word.replace(" ", ""))
def json_format_dict(self, data, pretty=False):
- """ Converts a dict to a JSON object and dumps it as a formatted string """
+ """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)
@@ -343,5 +355,5 @@ class ForemanInventory(object):
return json.dumps(data)
if __name__ == '__main__':
- ForemanInventory()
-
+ inv = ForemanInventory()
+ inv.run()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..8ebf0ae
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+nose
+requests
+responses
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..0a5283b
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,6 @@
+[nosetests]
+with-doctest=1
+exe=1
+
+[flake8]
+ignore = E501,F821
diff --git a/setup.py b/setup.py
index 1dbfc7e..4877532 100644
--- a/setup.py
+++ b/setup.py
@@ -18,21 +18,20 @@
#
# END OF COPYRIGHT #
-import subprocess
-from setuptools import setup, find_packages
-import os
+from setuptools import setup
-setup(name = "foreman_ansible_inventory",
- version = "0.0.3",
- author = u'Guido Günther',
- author_email = 'agx@sigxcpu.org',
- description = 'Ansible dynamic inventory that queries the Foreman',
- license = 'GPLv3+',
- classifiers = [
+setup(name="foreman_ansible_inventory",
+ version="0.0.3",
+ author=u'Guido Günther',
+ author_email='agx@sigxcpu.org',
+ description='Ansible dynamic inventory that queries the Foreman',
+ license='GPLv3+',
+ classifiers=[
'Environment :: Console',
'Programming Language :: Python :: 2',
'Operating System :: POSIX :: Linux',
],
- scripts = ['foreman_ansible_inventory.py'],
- requires = ["requests"],
-)
+ scripts=['foreman_ansible_inventory.py'],
+ requires=["requests"],
+ tests_require=['responses'],
+ )
diff --git a/tests/test_get_json.py b/tests/test_get_json.py
new file mode 100644
index 0000000..d424936
--- /dev/null
+++ b/tests/test_get_json.py
@@ -0,0 +1,76 @@
+# vim: set fileencoding=utf-8 :
+
+import responses
+import unittest
+
+from foreman_ansible_inventory import ForemanInventory
+
+
+class TestGetJson(unittest.TestCase):
+ def setUp(self):
+ self.inv = ForemanInventory()
+ self.inv.foreman_url = 'http://localhost:3000'
+ self.inv.foreman_user = 'doesnot'
+ self.inv.foreman_pw = 'mastter'
+ self.inv.foreman_ssl_verify = True
+
+ @responses.activate
+ def test_get_hosts(self):
+ url = 'http://localhost:3000/api/v2/hosts'
+ responses.add(responses.GET,
+ url,
+ json={'results': [{'name': 'foo'},
+ {'name': 'bar'}],
+ 'total': 4},
+ status=200)
+
+ ret = self.inv._get_hosts()
+ self.assertEqual(sorted(ret),
+ sorted([{u'name': u'foo'},
+ {u'name': u'bar'},
+ {u'name': u'foo'},
+ {u'name': u'bar'}]))
+ self.assertEqual(len(responses.calls), 2)
+ self.assertEqual(responses.calls[0].request.url,
+ '%s?per_page=250&page=1' % url)
+ self.assertEqual(responses.calls[1].request.url,
+ '%s?per_page=250&page=2' % url)
+
+ @responses.activate
+ def test_get_facts(self):
+ self.inv.want_facts = True
+ url = 'http://localhost:3000/api/v2/hosts/10/facts'
+ responses.add(responses.GET,
+ url,
+ json={'results': {'facts': {'fact1': 'val1',
+ 'fact2': 'val2',
+ }
+ },
+ },
+ status=200)
+
+ ret = self.inv._get_facts({'id': 10})
+ self.assertEqual(ret, {u'fact2': u'val2', u'fact1': u'val1'})
+ self.assertEqual(len(responses.calls), 1)
+ self.assertEqual(responses.calls[0].request.url,
+ '%s?per_page=250&page=1' % url)
+
+ @responses.activate
+ def test_resolve_params(self):
+ url = 'http://localhost:3000/api/v2/hosts/10'
+ responses.add(responses.GET,
+ url,
+ json={'all_parameters':
+ [{'name': 'param1',
+ 'value': 'value1'},
+ {'name': 'param2',
+ 'value': 'value2'}]},
+ status=200)
+
+ ret = self.inv._resolve_params({'id': 10})
+ self.assertEqual(sorted(ret.items()),
+ sorted({'param1': 'value1',
+ 'param2': 'value2'}.items()))
+ self.assertEqual(len(responses.calls), 1)
+ self.assertEqual(responses.calls[0].request.url,
+ '%s?per_page=250&page=1' % url)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..432d303
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,18 @@
+[tox]
+envlist = pep8, py27
+
+[testenv]
+install_command = pip install {opts} {packages}
+deps = -r{toxinidir}/requirements.txt
+sitepackages = True
+
+[testenv:pep8]
+deps = hacking
+commands =
+ flake8 {posargs}
+
+[testenv:py27]
+setenv =
+ PYTHONHASHSEED = 0
+commands =
+ python setup.py nosetests --verbosity=3