Work in progress on inventory script using API.

This commit is contained in:
Chris Church
2013-06-20 04:20:48 -04:00
parent 65defb75fb
commit 59d1ae7322
17 changed files with 524 additions and 7 deletions

View File

@@ -0,0 +1,29 @@
# Django REST Framework
from rest_framework import authentication
from rest_framework import exceptions
# AnsibleWorks
from ansibleworks.main.models import Job
class JobCallbackAuthentication(authentication.BaseAuthentication):
'''
Custom authentication used for views accessed by the inventory and callback
scripts when running a job.
'''
def authenticate(self, request):
auth = authentication.get_authorization_header(request).split()
if len(auth) != 2 or auth[0].lower() != 'token' or '-' not in auth[1]:
return None
job_id, job_key = auth[1].split('-', 1)
try:
job = Job.objects.get(pk=job_id, status='running')
except Job.DoesNotExist:
return None
token = job.callback_auth_token
if auth[1] != token:
raise exceptions.AuthenticationFailed('Invalid job callback token')
return (None, token)
def authenticate_header(self, request):
return 'Token'

View File

@@ -2,6 +2,7 @@
# All Rights Reserved. # All Rights Reserved.
# Python # Python
import hmac
import json import json
import os import os
import shlex import shlex
@@ -814,6 +815,13 @@ class Job(CommonModel):
except TaskMeta.DoesNotExist: except TaskMeta.DoesNotExist:
pass pass
@property
def callback_auth_token(self):
'''Return temporary auth token used for task callbacks via API.'''
if self.status == 'running':
h = hmac.new(settings.SECRET_KEY, self.created.isoformat())
return '%d-%s' % (self.pk, h.hexdigest())
def get_passwords_needed_to_start(self): def get_passwords_needed_to_start(self):
'''Return list of password field names needed to start the job.''' '''Return list of password field names needed to start the job.'''
needed = [] needed = []

View File

@@ -116,3 +116,15 @@ class CustomRbac(permissions.BasePermission):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return self.has_permission(request, view, obj) return self.has_permission(request, view, obj)
class JobCallbackPermission(CustomRbac):
def has_permission(self, request, view, obj=None):
# If another authentication method was used other than the one for job
# callbacks, return True to fall through to the next permission class.
if request.user or not request.auth:
return super(JobCallbackPermission, self).has_permission(request, view, obj)
# FIXME: Verify that inventory or job event requested are for the same
# job ID present in the auth token, etc.
return True

View File

@@ -86,10 +86,13 @@ class RunJob(Task):
# answer: TBD # answer: TBD
env['ACOM_JOB_ID'] = str(job.pk) env['ACOM_JOB_ID'] = str(job.pk)
env['ACOM_INVENTORY_ID'] = str(job.inventory.pk) env['ACOM_INVENTORY_ID'] = str(job.inventory.pk)
env['INVENTORY_ID'] = str(job.inventory.pk)
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir
env['ACOM_CALLBACK_EVENT_SCRIPT'] = callback_script env['ACOM_CALLBACK_EVENT_SCRIPT'] = callback_script
if hasattr(settings, 'ANSIBLE_TRANSPORT'): if hasattr(settings, 'ANSIBLE_TRANSPORT'):
env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT') env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT')
env['REST_API_URL'] = settings.INTERNAL_API_URL
env['REST_API_TOKEN'] = job.callback_auth_token or ''
env['ANSIBLE_NOCOLOR'] = '1' # Prevent output of escape sequences. env['ANSIBLE_NOCOLOR'] = '1' # Prevent output of escape sequences.
return env return env
@@ -108,8 +111,11 @@ class RunJob(Task):
# it doesn't make sense to rely on ansible-playbook's default of using # it doesn't make sense to rely on ansible-playbook's default of using
# the current user. # the current user.
ssh_username = ssh_username or 'root' ssh_username = ssh_username or 'root'
inventory_script = self.get_path_to('management', 'commands', if False:
'acom_inventory.py') inventory_script = self.get_path_to('management', 'commands',
'acom_inventory.py')
else:
inventory_script = self.get_path_to('..', 'scripts', 'inventory.py')
args = ['ansible-playbook', '-i', inventory_script] args = ['ansible-playbook', '-i', inventory_script]
if job.job_type == 'check': if job.job_type == 'check':
args.append('--check') args.append('--check')

View File

@@ -6,6 +6,7 @@ from ansibleworks.main.tests.users import UsersTest
from ansibleworks.main.tests.inventory import InventoryTest from ansibleworks.main.tests.inventory import InventoryTest
from ansibleworks.main.tests.projects import ProjectsTest from ansibleworks.main.tests.projects import ProjectsTest
from ansibleworks.main.tests.commands import * from ansibleworks.main.tests.commands import *
from ansibleworks.main.tests.scripts import *
from ansibleworks.main.tests.tasks import RunJobTest from ansibleworks.main.tests.tasks import RunJobTest
from ansibleworks.main.tests.jobs import * from ansibleworks.main.tests.jobs import *

View File

@@ -240,3 +240,8 @@ class BaseTransactionTest(BaseTestMixin, django.test.TransactionTestCase):
Base class for tests requiring transactions (or where the test database Base class for tests requiring transactions (or where the test database
needs to be accessed by subprocesses). needs to be accessed by subprocesses).
''' '''
class BaseLiveServerTest(BaseTestMixin, django.test.LiveServerTestCase):
'''
Base class for tests requiring a live test server.
'''

View File

@@ -13,7 +13,7 @@ from django.utils.timezone import now
from ansibleworks.main.models import * from ansibleworks.main.models import *
from ansibleworks.main.tests.base import BaseTest from ansibleworks.main.tests.base import BaseTest
__all__ = ['RunCommandAsScriptTest', 'AcomInventoryTest', __all__ = ['RunCommandAsScriptTest',# 'AcomInventoryTest',
'AcomCallbackEventTest'] 'AcomCallbackEventTest']
class BaseCommandTest(BaseTest): class BaseCommandTest(BaseTest):

View File

@@ -0,0 +1,271 @@
# Copyright (c) 2013 AnsibleWorks, Inc.
# All Rights Reserved.
# Python
import json
import os
import StringIO
import subprocess
import sys
import tempfile
# Django
from django.conf import settings
from django.utils.timezone import now
# AnsibleWorks
from ansibleworks.main.models import *
from ansibleworks.main.tests.base import BaseLiveServerTest
__all__ = ['InventoryScriptTest']
class BaseScriptTest(BaseLiveServerTest):
'''
Base class for tests that run external scripts to access the API.
'''
def setUp(self):
super(BaseScriptTest, self).setUp()
self._sys_path = [x for x in sys.path]
self._environ = dict(os.environ.items())
self._temp_files = []
def tearDown(self):
super(BaseScriptTest, self).tearDown()
sys.path = self._sys_path
for k,v in self._environ.items():
if os.environ.get(k, None) != v:
os.environ[k] = v
for k,v in os.environ.items():
if k not in self._environ.keys():
del os.environ[k]
for tf in self._temp_files:
if os.path.exists(tf):
os.remove(tf)
def run_script(self, name, *args, **options):
'''
Run an external script and capture its stdout/stderr and return code.
'''
#stdin_fileobj = options.pop('stdin_fileobj', None)
pargs = [name]
for k,v in options.items():
pargs.append('%s%s' % ('-' if len(k) == 1 else '--', k))
if not v is True:
pargs.append(str(v))
for arg in args:
pargs.append(str(arg))
proc = subprocess.Popen(pargs, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
return proc.returncode, stdout, stderr
class InventoryScriptTest(BaseScriptTest):
'''
Test helper to run management command as standalone script.
'''
def setUp(self):
super(InventoryScriptTest, self).setUp()
self.setup_users()
self.organizations = self.make_organizations(self.super_django_user, 2)
self.projects = self.make_projects(self.normal_django_user, 2)
self.organizations[0].projects.add(self.projects[1])
self.organizations[1].projects.add(self.projects[0])
self.inventories = []
self.hosts = []
self.groups = []
for n, organization in enumerate(self.organizations):
inventory = Inventory.objects.create(name='inventory-%d' % n,
description='description for inventory %d' % n,
organization=organization,
variables=json.dumps({'n': n}) if n else '')
self.inventories.append(inventory)
hosts = []
for x in xrange(10):
if n > 0:
variables = json.dumps({'ho': 'hum-%d' % x})
else:
variables = ''
host = inventory.hosts.create(name='host-%02d-%02d.example.com' % (n, x),
inventory=inventory,
variables=variables)
if x in (3, 7):
host.mark_inactive()
hosts.append(host)
self.hosts.extend(hosts)
groups = []
for x in xrange(5):
if n > 0:
variables = json.dumps({'gee': 'whiz-%d' % x})
else:
variables = ''
group = inventory.groups.create(name='group-%d' % x,
inventory=inventory,
variables=variables)
if x == 2:
group.mark_inactive()
groups.append(group)
group.hosts.add(hosts[x])
group.hosts.add(hosts[x + 5])
if n > 0 and x == 4:
group.parents.add(groups[3])
self.groups.extend(groups)
def run_inventory_script(self, *args, **options):
os.environ.setdefault('REST_API_URL', self.live_server_url)
os.environ.setdefault('REST_API_TOKEN',
self.super_django_user.auth_token.key)
name = os.path.join(os.path.dirname(__file__), '..', '..', 'scripts',
'inventory.py')
return self.run_script(name, *args, **options)
def test_without_inventory_id(self):
rc, stdout, stderr = self.run_inventory_script(list=True)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
rc, stdout, stderr = self.run_inventory_script(host=self.hosts[0].name)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
def test_list_with_inventory_id_as_argument(self):
inventory = self.inventories[0]
self.assertTrue(inventory.active)
rc, stdout, stderr = self.run_inventory_script(list=True,
inventory=inventory.pk)
self.assertEqual(rc, 0, stderr)
data = json.loads(stdout)
groups = inventory.groups.filter(active=True)
groupnames = groups.values_list('name', flat=True)
self.assertEqual(set(data.keys()), set(groupnames))
# Groups for this inventory should only have hosts, and no group
# variable data or parent/child relationships.
for k,v in data.items():
self.assertTrue(isinstance(v, (list, tuple)))
group = inventory.groups.get(active=True, name=k)
hosts = group.hosts.filter(active=True)
hostnames = hosts.values_list('name', flat=True)
self.assertEqual(set(v), set(hostnames))
for group in inventory.groups.filter(active=False):
self.assertFalse(group.name in data.keys(),
'deleted group %s should not be in data' % group)
# Command line argument for inventory ID should take precedence over
# environment variable.
inventory_pks = set(map(lambda x: x.pk, self.inventories))
invalid_id = [x for x in xrange(9999) if x not in inventory_pks][0]
os.environ['INVENTORY_ID'] = str(invalid_id)
rc, stdout, stderr = self.run_inventory_script(list=True,
inventory=inventory.pk)
self.assertEqual(rc, 0, stderr)
data = json.loads(stdout)
def test_list_with_inventory_id_in_environment(self):
inventory = self.inventories[1]
self.assertTrue(inventory.active)
os.environ['INVENTORY_ID'] = str(inventory.pk)
rc, stdout, stderr = self.run_inventory_script(list=True)
self.assertEqual(rc, 0, stderr)
data = json.loads(stdout)
groups = inventory.groups.filter(active=True)
groupnames = list(groups.values_list('name', flat=True)) + ['all']
self.assertEqual(set(data.keys()), set(groupnames))
# 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
group = inventory.groups.get(active=True, name=k)
hosts = group.hosts.filter(active=True)
hostnames = hosts.values_list('name', flat=True)
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.assertFalse('children' in v)
def test_valid_host(self):
# Host without variable data.
inventory = self.inventories[0]
self.assertTrue(inventory.active)
host = inventory.hosts.filter(active=True)[2]
os.environ['INVENTORY_ID'] = str(inventory.pk)
rc, stdout, stderr = self.run_inventory_script(host=host.name)
self.assertEqual(rc, 0, stderr)
data = json.loads(stdout)
self.assertEqual(data, {})
# Host with variable data.
inventory = self.inventories[1]
self.assertTrue(inventory.active)
host = inventory.hosts.filter(active=True)[4]
os.environ['INVENTORY_ID'] = str(inventory.pk)
rc, stdout, stderr = self.run_inventory_script(host=host.name)
self.assertEqual(rc, 0, stderr)
data = json.loads(stdout)
self.assertEqual(data, host.variables_dict)
def test_invalid_host(self):
# Valid host, but not part of the specified inventory.
inventory = self.inventories[0]
self.assertTrue(inventory.active)
host = Host.objects.exclude(inventory=inventory)[0]
self.assertTrue(host.active)
os.environ['INVENTORY_ID'] = str(inventory.pk)
rc, stdout, stderr = self.run_inventory_script(host=host.name)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
# Invalid hostname not in database.
rc, stdout, stderr = self.run_inventory_script(host='blah.example.com')
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
def test_with_invalid_inventory_id(self):
inventory_pks = set(map(lambda x: x.pk, self.inventories))
invalid_id = [x for x in xrange(1, 9999) if x not in inventory_pks][0]
os.environ['INVENTORY_ID'] = str(invalid_id)
rc, stdout, stderr = self.run_inventory_script(list=True)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
os.environ['INVENTORY_ID'] = 'not_an_int'
rc, stdout, stderr = self.run_inventory_script(list=True)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
os.environ['INVENTORY_ID'] = str(invalid_id)
rc, stdout, stderr = self.run_inventory_script(host=self.hosts[1].name)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
os.environ['INVENTORY_ID'] = 'not_an_int'
rc, stdout, stderr = self.run_inventory_script(host=self.hosts[2].name)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
def test_with_deleted_inventory(self):
inventory = self.inventories[0]
inventory.mark_inactive()
self.assertFalse(inventory.active)
os.environ['INVENTORY_ID'] = str(inventory.pk)
rc, stdout, stderr = self.run_inventory_script(list=True)
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
def test_without_list_or_host_argument(self):
inventory = self.inventories[0]
self.assertTrue(inventory.active)
os.environ['INVENTORY_ID'] = str(inventory.pk)
rc, stdout, stderr = self.run_inventory_script()
self.assertNotEqual(rc, 0, stderr)
self.assertEqual(json.loads(stdout), {})
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), {})

View File

@@ -7,7 +7,7 @@ import tempfile
from django.conf import settings from django.conf import settings
from django.test.utils import override_settings from django.test.utils import override_settings
from ansibleworks.main.models import * from ansibleworks.main.models import *
from ansibleworks.main.tests.base import BaseTransactionTest from ansibleworks.main.tests.base import BaseTransactionTest, BaseLiveServerTest
from ansibleworks.main.tasks import RunJob from ansibleworks.main.tasks import RunJob
TEST_PLAYBOOK = '''- hosts: test-group TEST_PLAYBOOK = '''- hosts: test-group
@@ -90,7 +90,7 @@ TEST_SSH_KEY_DATA_UNLOCK = 'unlockme'
@override_settings(CELERY_ALWAYS_EAGER=True, @override_settings(CELERY_ALWAYS_EAGER=True,
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True) CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
class BaseCeleryTest(BaseTransactionTest): class BaseCeleryTest(BaseLiveServerTest):#BaseTransactionTest):
''' '''
Base class for celery task tests. Base class for celery task tests.
''' '''
@@ -128,6 +128,7 @@ class RunJobTest(BaseCeleryTest):
self.build_args_callback() self.build_args_callback()
return args return args
RunJob.build_args = new_build_args RunJob.build_args = new_build_args
settings.INTERNAL_API_URL = self.live_server_url
def tearDown(self): def tearDown(self):
super(RunJobTest, self).tearDown() super(RunJobTest, self).tearDown()
@@ -193,6 +194,7 @@ class RunJobTest(BaseCeleryTest):
expect_traceback=False): expect_traceback=False):
msg = 'job status is %s, expected %s' % (job.status, expected) msg = 'job status is %s, expected %s' % (job.status, expected)
msg = '%s\nargs:\n%s' % (msg, job.job_args) msg = '%s\nargs:\n%s' % (msg, job.job_args)
msg = '%s\nenv:\n%s' % (msg, job.job_env)
if job.result_traceback: if job.result_traceback:
msg = '%s\ngot traceback:\n%s' % (msg, job.result_traceback) msg = '%s\ngot traceback:\n%s' % (msg, job.result_traceback)
if job.result_stdout: if job.result_stdout:

View File

@@ -53,6 +53,7 @@ inventory_urls = patterns('ansibleworks.main.views',
url(r'^(?P<pk>[0-9]+)/groups/$', 'inventory_groups_list'), url(r'^(?P<pk>[0-9]+)/groups/$', 'inventory_groups_list'),
url(r'^(?P<pk>[0-9]+)/root_groups/$', 'inventory_root_groups_list'), url(r'^(?P<pk>[0-9]+)/root_groups/$', 'inventory_root_groups_list'),
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'inventory_variable_detail'), url(r'^(?P<pk>[0-9]+)/variable_data/$', 'inventory_variable_detail'),
url(r'^(?P<pk>[0-9]+)/script/$', 'inventory_script_view'),
) )
host_urls = patterns('ansibleworks.main.views', host_urls = patterns('ansibleworks.main.views',

View File

@@ -26,6 +26,7 @@ from rest_framework.views import APIView
# AnsibleWorks # AnsibleWorks
from ansibleworks.main.access import * from ansibleworks.main.access import *
from ansibleworks.main.authentication import JobCallbackAuthentication
from ansibleworks.main.base_views import * from ansibleworks.main.base_views import *
from ansibleworks.main.models import * from ansibleworks.main.models import *
from ansibleworks.main.rbac import * from ansibleworks.main.rbac import *
@@ -983,6 +984,53 @@ class GroupVariableDetail(BaseVariableDetail):
model = Group model = Group
serializer_class = GroupVariableDataSerializer serializer_class = GroupVariableDataSerializer
class InventoryScriptView(generics.RetrieveAPIView):
'''
Return inventory group and host data as needed for an inventory script.
Without query parameters, return groups with hosts, children and vars
(equivalent to the --list parameter to an inventory script).
With ?host=HOSTNAME, return host vars for the given host (equivalent to the
--host HOSTNAME parameter to an inventory script).
'''
model = Inventory
authentication_classes = [JobCallbackAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
permission_classes = (JobCallbackPermission,)
filter_backends = ()
def retrieve(self, request, *args, **kwargs):
self.object = self.get_object()
hostname = request.QUERY_PARAMS.get('host', '')
if hostname:
try:
host = self.object.hosts.get(active=True, name=hostname)
data = host.variables_dict
except Host.DoesNotExist:
raise Http404
else:
data = {}
for group in self.object.groups.filter(active=True):
hosts = group.hosts.filter(active=True)
children = group.children.filter(active=True)
group_info = {
'hosts': list(hosts.values_list('name', flat=True)),
'children': list(children.values_list('name', flat=True)),
'vars': group.variables_dict,
}
group_info = dict(filter(lambda x: bool(x[1]),
group_info.items()))
if group_info.keys() in ([], ['hosts']):
data[group.name] = group_info.get('hosts', [])
else:
data[group.name] = group_info
if self.object.variables_dict:
data['all'] = {
'vars': self.object.variables_dict,
}
return Response(data)
class JobTemplateList(BaseList): class JobTemplateList(BaseList):
model = JobTemplate model = JobTemplate

120
ansibleworks/scripts/inventory.py Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python
# Copyright (c) 2013 AnsibleWorks, Inc.
# All Rights Reserved.
# Python
import json
import optparse
import os
import sys
import urllib
import urlparse
# Requests
import requests
class TokenAuth(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, request):
request.headers['Authorization'] = 'Token %s' % self.token
return request
class InventoryScript(object):
def __init__(self, **options):
self.options = options
def get_data(self):
parts = urlparse.urlsplit(self.base_url)
if parts.username and parts.password:
auth = (parts.username, parts.password)
elif self.auth_token:
auth = TokenAuth(self.auth_token)
else:
auth = None
url = urlparse.urlunsplit([parts.scheme,
'%s:%d' % (parts.hostname, parts.port),
parts.path, parts.query, parts.fragment])
url_path = '/api/v1/inventories/%d/script/' % self.inventory_id
if self.hostname:
url_path += '?%s' % urllib.urlencode({'host': self.hostname})
url = urlparse.urljoin(url, url_path)
response = requests.get(url, auth=auth)
response.raise_for_status()
sys.stdout.write(json.dumps(response.json(), indent=self.indent) + '\n')
def run(self):
try:
self.base_url = self.options.get('base_url', '') or \
os.getenv('REST_API_URL', '')
if not self.base_url:
raise ValueError('No REST API URL specified')
self.auth_token = self.options.get('authtoken', '') or \
os.getenv('REST_API_TOKEN', '')
parts = urlparse.urlsplit(self.base_url)
if not (parts.username and parts.password) and not self.auth_token:
raise ValueError('No REST API token or username/password '
'specified')
try:
# Command line argument takes precedence over environment
# variable.
self.inventory_id = int(self.options.get('inventory_id', 0) or \
os.getenv('INVENTORY_ID', 0))
except ValueError:
raise ValueError('Inventory ID must be an integer')
if not self.inventory_id:
raise ValueError('No inventory ID specified')
self.hostname = self.options.get('hostname', '')
self.list_ = self.options.get('list', False)
self.indent = self.options.get('indent', None)
if self.list_ and self.hostname:
raise RuntimeError('Only --list or --host may be specified')
elif self.list_ or self.hostname:
self.get_data()
else:
raise RuntimeError('Either --list or --host must be specified')
except Exception, e:
# Always return an empty hash on stdout, even when an error occurs.
sys.stdout.write(json.dumps({}))
#print >> file(os.path.join(os.path.dirname(__file__), 'foo.log'), 'a'), repr(e)
#if hasattr(e, 'response'):
# print >> file(os.path.join(os.path.dirname(__file__), 'foo.log'), 'a'), e.response.content
if self.options.get('traceback', False):
raise
sys.stderr.write(str(e) + '\n')
if hasattr(e, 'response'):
sys.stderr.write(e.response.content + '\n')
sys.exit(1)
def main():
parser = optparse.OptionParser()
parser.add_option('-v', '--verbosity', action='store', dest='verbosity',
default='1', type='choice', choices=['0', '1', '2', '3'],
help='Verbosity level; 0=minimal output, 1=normal output'
', 2=verbose output, 3=very verbose output')
parser.add_option('--traceback', action='store_true',
help='Raise on exception on error')
parser.add_option('-u', '--url', dest='base_url', default='',
help='Base URL to access REST API (can also be specified'
' using REST_API_URL environment variable)')
parser.add_option('--authtoken', dest='authtoken', default='',
help='Authentication token used to access REST API (can '
'also be specified using REST_API_TOKEN environment '
'variable)')
parser.add_option('-i', '--inventory', dest='inventory_id', type='int',
default=0, help='Inventory ID (can also be specified '
'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('--host', dest='hostname', default='',
help='Return JSON hash of host vars.')
parser.add_option('--indent', dest='indent', type='int', default=None,
help='Indentation level for pretty printing output')
options, args = parser.parse_args()
InventoryScript(**vars(options)).run()
if __name__ == '__main__':
main()

View File

@@ -133,7 +133,6 @@ INSTALLED_APPS = (
INTERNAL_IPS = ('127.0.0.1',) INTERNAL_IPS = ('127.0.0.1',)
REST_FRAMEWORK = { REST_FRAMEWORK = {
'FILTER_BACKEND': 'ansibleworks.main.custom_filters.CustomFilterBackend',
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'ansibleworks.main.pagination.PaginationSerializer', 'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'ansibleworks.main.pagination.PaginationSerializer',
'PAGINATE_BY': 25, 'PAGINATE_BY': 25,
'PAGINATE_BY_PARAM': 'page_size', 'PAGINATE_BY_PARAM': 'page_size',
@@ -142,6 +141,9 @@ REST_FRAMEWORK = {
'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
), ),
'DEFAULT_FILTER_BACKENDS': (
'ansibleworks.main.custom_filters.CustomFilterBackend',
),
'DEFAULT_PARSER_CLASSES': ( 'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser', 'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser', 'rest_framework.parsers.FormParser',
@@ -218,6 +220,9 @@ DEVSERVER_MODULES = (
#'devserver.modules.profile.LineProfilerModule', #'devserver.modules.profile.LineProfilerModule',
) )
# Set default ports for live server tests.
os.environ.setdefault('DJANGO_LIVE_TEST_SERVER_ADDRESS', 'localhost:9013-9199')
# Skip migrations when running tests. # Skip migrations when running tests.
SOUTH_TESTS_MIGRATE = False SOUTH_TESTS_MIGRATE = False
@@ -234,6 +239,11 @@ CELERYD_TASK_SOFT_TIME_LIMIT = 3540
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
CELERYBEAT_MAX_LOOP_INTERVAL = 60 CELERYBEAT_MAX_LOOP_INTERVAL = 60
if 'devserver' in INSTALLED_APPS:
INTERNAL_API_URL = 'http://127.0.0.1:%s' % DEVSERVER_DEFAULT_PORT
else:
INTERNAL_API_URL = 'http://127.0.0.1:8000'
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,

View File

@@ -21,6 +21,8 @@ ALLOWED_HOSTS = []
# Production should only use minified JS for UI. # Production should only use minified JS for UI.
USE_MINIFIED_JS = True USE_MINIFIED_JS = True
INTERNAL_API_URL = 'http://127.0.0.1:80'
# If a local_settings.py file is present here, use it and ignore the global # If a local_settings.py file is present here, use it and ignore the global
# settings. Normally, local settings would only be present during development. # settings. Normally, local settings would only be present during development.
try: try:

View File

@@ -11,6 +11,7 @@ djangorestframework>=2.3.0,<2.4.0
Markdown Markdown
pexpect pexpect
python-dateutil python-dateutil
requests
South>=0.8,<2.0 South>=0.8,<2.0
django-debug-toolbar django-debug-toolbar

Binary file not shown.

View File

@@ -53,7 +53,7 @@ def proc_data_files(data_files):
setup( setup(
name='ansibleworks', name='ansibleworks',
version=__version__.split("-")[0], version=__version__.split("-")[0], # FIXME: Should keep full version here?
author='AnsibleWorks, Inc.', author='AnsibleWorks, Inc.',
author_email='support@ansibleworks.com', author_email='support@ansibleworks.com',
description='AnsibleWorks API, UI and Task Engine', description='AnsibleWorks API, UI and Task Engine',
@@ -75,6 +75,7 @@ setup(
'pexpect', 'pexpect',
'python-dateutil', 'python-dateutil',
'PyYAML', 'PyYAML',
'requests',
'South>=0.8,<2.0', 'South>=0.8,<2.0',
], ],
setup_requires=[], setup_requires=[],