mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
For AC-332. Added support to inventory script view, inventory script and task engine to include hostvars inline when using Ansible >= 1.3.
This commit is contained in:
parent
2bb5374685
commit
0129036b40
@ -3,6 +3,7 @@
|
||||
|
||||
# Python
|
||||
import cStringIO
|
||||
import distutils.version
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@ -23,6 +24,7 @@ from django.conf import settings
|
||||
|
||||
# AWX
|
||||
from awx.main.models import Job, ProjectUpdate
|
||||
from awx.main.utils import get_ansible_version
|
||||
|
||||
__all__ = ['RunJob', 'RunProjectUpdate']
|
||||
|
||||
@ -228,6 +230,16 @@ class RunJob(BaseTask):
|
||||
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir
|
||||
env['REST_API_URL'] = settings.INTERNAL_API_URL
|
||||
env['REST_API_TOKEN'] = job.task_auth_token or ''
|
||||
|
||||
# When using Ansible >= 1.3, allow the inventory script to include host
|
||||
# variables inline via ['_meta']['hostvars'].
|
||||
try:
|
||||
Version = distutils.version.StrictVersion
|
||||
if Version( get_ansible_version()) >= Version('1.3'):
|
||||
env['INVENTORY_HOSTVARS'] = str(True)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return env
|
||||
|
||||
def build_args(self, job, **kwargs):
|
||||
|
||||
@ -1,10 +1,25 @@
|
||||
Generate inventory group and host data as needed for an inventory script.
|
||||
|
||||
Refer to [External Inventory Scripts](http://www.ansibleworks.com/docs/api.html#external-inventory-scripts)
|
||||
for more information on inventory scripts.
|
||||
|
||||
## List Response
|
||||
|
||||
Make a GET request to this resource without query parameters to retrieve a JSON
|
||||
object containing groups, including the hosts, children and variables for each
|
||||
group. The response data is equivalent to that returned by passing the
|
||||
`--list` argument to an inventory script.
|
||||
|
||||
_(New in AWX 1.3)_ Specify a query string of `?hostvars=1` to retrieve the JSON
|
||||
object above including all host variables. The `['_meta']['hostvars']` object
|
||||
in the response contains an entry for each host with its variables. This
|
||||
response format can be used with Ansible 1.3 and later to avoid making a
|
||||
separate API request for each host. Refer to
|
||||
[Tuning the External Inventory Script](http://www.ansibleworks.com/docs/api.html#tuning-the-external-inventory-script)
|
||||
for more information on this feature.
|
||||
|
||||
## Host Response
|
||||
|
||||
Make a GET request to this resource with a query string similar to
|
||||
`?host=HOSTNAME` to retrieve a JSON object containing host variables for the
|
||||
specified host. The response data is equivalent to that returned by passing
|
||||
|
||||
@ -663,7 +663,60 @@ class InventoryTest(BaseTest):
|
||||
# on a group resource, I can see related resources for variables, inventories, and children
|
||||
# and these work
|
||||
|
||||
def test_get_inventory_tree(self):
|
||||
def test_get_inventory_script_view(self):
|
||||
i_a = self.inventory_a
|
||||
i_a.variables = json.dumps({'i-vars': 123})
|
||||
i_a.save()
|
||||
# Group A is parent of B, B is parent of C, C is parent of D.
|
||||
g_a = i_a.groups.create(name='A', variables=json.dumps({'A-vars': 'AAA'}))
|
||||
g_b = i_a.groups.create(name='B', variables=json.dumps({'B-vars': 'BBB'}))
|
||||
g_b.parents.add(g_a)
|
||||
g_c = i_a.groups.create(name='C', variables=json.dumps({'C-vars': 'CCC'}))
|
||||
g_c.parents.add(g_b)
|
||||
g_d = i_a.groups.create(name='D', variables=json.dumps({'D-vars': 'DDD'}))
|
||||
g_d.parents.add(g_c)
|
||||
# Each group "X" contains one host "x".
|
||||
h_a = i_a.hosts.create(name='a', variables=json.dumps({'a-vars': 'aaa'}))
|
||||
h_a.groups.add(g_a)
|
||||
h_b = i_a.hosts.create(name='b', variables=json.dumps({'b-vars': 'bbb'}))
|
||||
h_b.groups.add(g_b)
|
||||
h_c = i_a.hosts.create(name='c', variables=json.dumps({'c-vars': 'ccc'}))
|
||||
h_c.groups.add(g_c)
|
||||
h_d = i_a.hosts.create(name='d', variables=json.dumps({'d-vars': 'ddd'}))
|
||||
h_d.groups.add(g_d)
|
||||
|
||||
# Old, slow 1.2 way.
|
||||
url = reverse('main:inventory_script_view', args=(i_a.pk,))
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
self.assertTrue('all' in response)
|
||||
self.assertEqual(response['all']['vars'], i_a.variables_dict)
|
||||
for g in i_a.groups.all():
|
||||
self.assertTrue(g.name in response)
|
||||
self.assertEqual(response[g.name]['vars'], g.variables_dict)
|
||||
self.assertEqual(set(response[g.name]['children']),
|
||||
set(g.children.values_list('name', flat=True)))
|
||||
self.assertEqual(set(response[g.name]['hosts']),
|
||||
set(g.hosts.values_list('name', flat=True)))
|
||||
self.assertFalse('_meta' in response)
|
||||
for h in i_a.hosts.all():
|
||||
h_url = '%s?host=%s' % (url, h.name)
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(h_url, expect=200)
|
||||
self.assertEqual(response, h.variables_dict)
|
||||
|
||||
# New 1.3 way.
|
||||
url = reverse('main:inventory_script_view', args=(i_a.pk,))
|
||||
url = '%s?hostvars=1' % url
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
self.assertTrue('_meta' in response)
|
||||
self.assertTrue('hostvars' in response['_meta'])
|
||||
for h in i_a.hosts.all():
|
||||
self.assertEqual(response['_meta']['hostvars'][h.name],
|
||||
h.variables_dict)
|
||||
|
||||
def test_get_inventory_tree_view(self):
|
||||
# Group A is parent of B, B is parent of C, C is parent of D.
|
||||
g_a = self.inventory_a.groups.create(name='A')
|
||||
g_b = self.inventory_a.groups.create(name='B')
|
||||
|
||||
@ -208,6 +208,55 @@ class InventoryScriptTest(BaseScriptTest):
|
||||
else:
|
||||
self.assertTrue(len(v['children']) == 0)
|
||||
|
||||
def test_list_with_hostvars_inline(self):
|
||||
inventory = self.inventories[1]
|
||||
self.assertTrue(inventory.active)
|
||||
rc, stdout, stderr = self.run_inventory_script(list=True,
|
||||
inventory=inventory.pk,
|
||||
hostvars=True)
|
||||
self.assertEqual(rc, 0, stderr)
|
||||
data = json.loads(stdout)
|
||||
groups = inventory.groups.filter(active=True)
|
||||
groupnames = list(groups.values_list('name', flat=True))
|
||||
groupnames.extend(['all', '_meta'])
|
||||
self.assertEqual(set(data.keys()), set(groupnames))
|
||||
all_hostnames = set()
|
||||
# Groups for this inventory should have hosts, variable data, and one
|
||||
# parent/child relationship.
|
||||
for k,v in data.items():
|
||||
self.assertTrue(isinstance(v, dict))
|
||||
if k == 'all':
|
||||
self.assertEqual(v.get('vars', {}), inventory.variables_dict)
|
||||
continue
|
||||
if k == '_meta':
|
||||
continue
|
||||
group = inventory.groups.get(active=True, name=k)
|
||||
hosts = group.hosts.filter(active=True)
|
||||
hostnames = hosts.values_list('name', flat=True)
|
||||
all_hostnames.update(hostnames)
|
||||
self.assertEqual(set(v.get('hosts', [])), set(hostnames))
|
||||
if group.variables:
|
||||
self.assertEqual(v.get('vars', {}), group.variables_dict)
|
||||
if k == 'group-3':
|
||||
children = group.children.filter(active=True)
|
||||
childnames = children.values_list('name', flat=True)
|
||||
self.assertEqual(set(v.get('children', [])), set(childnames))
|
||||
else:
|
||||
self.assertTrue(len(v['children']) == 0)
|
||||
# Check hostvars in ['_meta']['hostvars'] dict.
|
||||
for hostname in all_hostnames:
|
||||
self.assertTrue(hostname in data['_meta']['hostvars'])
|
||||
host = inventory.hosts.get(name=hostname)
|
||||
self.assertEqual(data['_meta']['hostvars'][hostname],
|
||||
host.variables_dict)
|
||||
# Hostvars can also be requested via environment variable.
|
||||
os.environ['INVENTORY_HOSTVARS'] = str(True)
|
||||
rc, stdout, stderr = self.run_inventory_script(list=True,
|
||||
inventory=inventory.pk)
|
||||
self.assertEqual(rc, 0, stderr)
|
||||
data = json.loads(stdout)
|
||||
self.assertTrue('_meta' in data)
|
||||
|
||||
def test_valid_host(self):
|
||||
# Host without variable data.
|
||||
inventory = self.inventories[0]
|
||||
@ -280,11 +329,10 @@ class InventoryScriptTest(BaseScriptTest):
|
||||
self.assertNotEqual(rc, 0, stderr)
|
||||
self.assertEqual(json.loads(stdout), {})
|
||||
|
||||
def _test_with_both_list_and_host_arguments(self):
|
||||
def test_with_both_list_and_host_arguments(self):
|
||||
inventory = self.inventories[0]
|
||||
self.assertTrue(inventory.active)
|
||||
os.environ['INVENTORY_ID'] = str(inventory.pk)
|
||||
rc, stdout, stderr = self.run_inventory_script(list=True, host='blah')
|
||||
self.assertNotEqual(rc, 0, stderr)
|
||||
self.assertEqual(json.loads(stdout), {})
|
||||
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import ParseError, PermissionDenied
|
||||
|
||||
__all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore',
|
||||
'get_awx_version']
|
||||
'get_ansible_version', 'get_awx_version']
|
||||
|
||||
def get_object_or_400(klass, *args, **kwargs):
|
||||
'''
|
||||
@ -53,6 +57,18 @@ class RequireDebugTrueOrTest(logging.Filter):
|
||||
from django.conf import settings
|
||||
return settings.DEBUG or 'test' in sys.argv
|
||||
|
||||
def get_ansible_version():
|
||||
'''
|
||||
Return Ansible version installed.
|
||||
'''
|
||||
try:
|
||||
proc = subprocess.Popen(['ansible', '--version'],
|
||||
stdout=subprocess.PIPE)
|
||||
result = proc.communicate()[0]
|
||||
return result.lower().replace('ansible', '').strip()
|
||||
except:
|
||||
return 'unknown'
|
||||
|
||||
def get_awx_version():
|
||||
'''
|
||||
Return AWX version as reported by setuptools.
|
||||
|
||||
@ -111,6 +111,7 @@ class ApiV1ConfigView(APIView):
|
||||
time_zone=settings.TIME_ZONE,
|
||||
license_info=license_data,
|
||||
version=get_awx_version(),
|
||||
ansible_version=get_ansible_version(),
|
||||
)
|
||||
if request.user.is_superuser or request.user.admin_of_organizations.filter(active=True).count():
|
||||
data.update(dict(
|
||||
@ -584,6 +585,7 @@ class InventoryScriptView(RetrieveAPIView):
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
hostname = request.QUERY_PARAMS.get('host', '')
|
||||
hostvars = bool(request.QUERY_PARAMS.get('hostvars', ''))
|
||||
if hostname:
|
||||
host = get_object_or_404(self.object.hosts, active=True,
|
||||
name=hostname)
|
||||
@ -603,6 +605,12 @@ class InventoryScriptView(RetrieveAPIView):
|
||||
group_info['vars'] = group.variables_dict
|
||||
data[group.name] = group_info
|
||||
|
||||
if hostvars:
|
||||
data.setdefault('_meta', SortedDict())
|
||||
data['_meta'].setdefault('hostvars', SortedDict())
|
||||
for host in self.object.hosts.filter(active=True):
|
||||
data['_meta']['hostvars'][host.name] = host.variables_dict
|
||||
|
||||
# workaround for Ansible inventory bug (github #3687), localhost
|
||||
# must be explicitly listed in the all group for dynamic inventory
|
||||
# scripts to pick it up.
|
||||
|
||||
@ -78,6 +78,8 @@ class InventoryScript(object):
|
||||
url_path = '/api/v1/inventories/%d/script/' % self.inventory_id
|
||||
if self.hostname:
|
||||
url_path += '?%s' % urllib.urlencode({'host': self.hostname})
|
||||
elif self.hostvars:
|
||||
url_path += '?%s' % urllib.urlencode({'hostvars': 1})
|
||||
url = urlparse.urljoin(url, url_path)
|
||||
response = requests.get(url, auth=auth)
|
||||
response.raise_for_status()
|
||||
@ -107,6 +109,8 @@ class InventoryScript(object):
|
||||
raise ValueError('No inventory ID specified')
|
||||
self.hostname = self.options.get('hostname', '')
|
||||
self.list_ = self.options.get('list', False)
|
||||
self.hostvars = bool(self.options.get('hostvars', False) or
|
||||
os.getenv('INVENTORY_HOSTVARS', ''))
|
||||
self.indent = self.options.get('indent', None)
|
||||
if self.list_ and self.hostname:
|
||||
raise RuntimeError('Only --list or --host may be specified')
|
||||
@ -147,6 +151,10 @@ def main():
|
||||
'using INVENTORY_ID environment variable)')
|
||||
parser.add_option('--list', action='store_true', dest='list',
|
||||
default=False, help='Return JSON hash of host groups.')
|
||||
parser.add_option('--hostvars', action='store_true', dest='hostvars',
|
||||
default=False, help='Return hostvars inline with --list,'
|
||||
' under ["_meta"]["hostvars"]. Can also be specified '
|
||||
'using INVENTORY_HOSTVARS environment variable.')
|
||||
parser.add_option('--host', dest='hostname', default='',
|
||||
help='Return JSON hash of host vars.')
|
||||
parser.add_option('--indent', dest='indent', type='int', default=None,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user