mirror of
https://github.com/ansible/awx.git
synced 2024-10-27 09:25:10 +03:00
Work in progress on inventory script using API.
This commit is contained in:
parent
65defb75fb
commit
59d1ae7322
29
ansibleworks/main/authentication.py
Normal file
29
ansibleworks/main/authentication.py
Normal 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'
|
@ -2,6 +2,7 @@
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
@ -814,6 +815,13 @@ class Job(CommonModel):
|
||||
except TaskMeta.DoesNotExist:
|
||||
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):
|
||||
'''Return list of password field names needed to start the job.'''
|
||||
needed = []
|
||||
|
@ -116,3 +116,15 @@ class CustomRbac(permissions.BasePermission):
|
||||
|
||||
def has_object_permission(self, 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
|
||||
|
||||
|
@ -86,10 +86,13 @@ class RunJob(Task):
|
||||
# answer: TBD
|
||||
env['ACOM_JOB_ID'] = str(job.pk)
|
||||
env['ACOM_INVENTORY_ID'] = str(job.inventory.pk)
|
||||
env['INVENTORY_ID'] = str(job.inventory.pk)
|
||||
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir
|
||||
env['ACOM_CALLBACK_EVENT_SCRIPT'] = callback_script
|
||||
if hasattr(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.
|
||||
return env
|
||||
|
||||
@ -108,8 +111,11 @@ class RunJob(Task):
|
||||
# it doesn't make sense to rely on ansible-playbook's default of using
|
||||
# the current user.
|
||||
ssh_username = ssh_username or 'root'
|
||||
inventory_script = self.get_path_to('management', 'commands',
|
||||
'acom_inventory.py')
|
||||
if False:
|
||||
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]
|
||||
if job.job_type == 'check':
|
||||
args.append('--check')
|
||||
|
@ -6,6 +6,7 @@ from ansibleworks.main.tests.users import UsersTest
|
||||
from ansibleworks.main.tests.inventory import InventoryTest
|
||||
from ansibleworks.main.tests.projects import ProjectsTest
|
||||
from ansibleworks.main.tests.commands import *
|
||||
from ansibleworks.main.tests.scripts import *
|
||||
from ansibleworks.main.tests.tasks import RunJobTest
|
||||
from ansibleworks.main.tests.jobs import *
|
||||
|
||||
|
@ -240,3 +240,8 @@ class BaseTransactionTest(BaseTestMixin, django.test.TransactionTestCase):
|
||||
Base class for tests requiring transactions (or where the test database
|
||||
needs to be accessed by subprocesses).
|
||||
'''
|
||||
|
||||
class BaseLiveServerTest(BaseTestMixin, django.test.LiveServerTestCase):
|
||||
'''
|
||||
Base class for tests requiring a live test server.
|
||||
'''
|
||||
|
@ -13,7 +13,7 @@ from django.utils.timezone import now
|
||||
from ansibleworks.main.models import *
|
||||
from ansibleworks.main.tests.base import BaseTest
|
||||
|
||||
__all__ = ['RunCommandAsScriptTest', 'AcomInventoryTest',
|
||||
__all__ = ['RunCommandAsScriptTest',# 'AcomInventoryTest',
|
||||
'AcomCallbackEventTest']
|
||||
|
||||
class BaseCommandTest(BaseTest):
|
||||
|
271
ansibleworks/main/tests/scripts.py
Normal file
271
ansibleworks/main/tests/scripts.py
Normal 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), {})
|
||||
|
@ -7,7 +7,7 @@ import tempfile
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
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
|
||||
|
||||
TEST_PLAYBOOK = '''- hosts: test-group
|
||||
@ -90,7 +90,7 @@ TEST_SSH_KEY_DATA_UNLOCK = 'unlockme'
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||
class BaseCeleryTest(BaseTransactionTest):
|
||||
class BaseCeleryTest(BaseLiveServerTest):#BaseTransactionTest):
|
||||
'''
|
||||
Base class for celery task tests.
|
||||
'''
|
||||
@ -128,6 +128,7 @@ class RunJobTest(BaseCeleryTest):
|
||||
self.build_args_callback()
|
||||
return args
|
||||
RunJob.build_args = new_build_args
|
||||
settings.INTERNAL_API_URL = self.live_server_url
|
||||
|
||||
def tearDown(self):
|
||||
super(RunJobTest, self).tearDown()
|
||||
@ -193,6 +194,7 @@ class RunJobTest(BaseCeleryTest):
|
||||
expect_traceback=False):
|
||||
msg = 'job status is %s, expected %s' % (job.status, expected)
|
||||
msg = '%s\nargs:\n%s' % (msg, job.job_args)
|
||||
msg = '%s\nenv:\n%s' % (msg, job.job_env)
|
||||
if job.result_traceback:
|
||||
msg = '%s\ngot traceback:\n%s' % (msg, job.result_traceback)
|
||||
if job.result_stdout:
|
||||
|
@ -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]+)/root_groups/$', 'inventory_root_groups_list'),
|
||||
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',
|
||||
|
@ -26,6 +26,7 @@ from rest_framework.views import APIView
|
||||
|
||||
# AnsibleWorks
|
||||
from ansibleworks.main.access import *
|
||||
from ansibleworks.main.authentication import JobCallbackAuthentication
|
||||
from ansibleworks.main.base_views import *
|
||||
from ansibleworks.main.models import *
|
||||
from ansibleworks.main.rbac import *
|
||||
@ -983,6 +984,53 @@ class GroupVariableDetail(BaseVariableDetail):
|
||||
model = Group
|
||||
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):
|
||||
|
||||
model = JobTemplate
|
||||
|
120
ansibleworks/scripts/inventory.py
Executable file
120
ansibleworks/scripts/inventory.py
Executable 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()
|
@ -133,7 +133,6 @@ INSTALLED_APPS = (
|
||||
INTERNAL_IPS = ('127.0.0.1',)
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'FILTER_BACKEND': 'ansibleworks.main.custom_filters.CustomFilterBackend',
|
||||
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'ansibleworks.main.pagination.PaginationSerializer',
|
||||
'PAGINATE_BY': 25,
|
||||
'PAGINATE_BY_PARAM': 'page_size',
|
||||
@ -142,6 +141,9 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
),
|
||||
'DEFAULT_FILTER_BACKENDS': (
|
||||
'ansibleworks.main.custom_filters.CustomFilterBackend',
|
||||
),
|
||||
'DEFAULT_PARSER_CLASSES': (
|
||||
'rest_framework.parsers.JSONParser',
|
||||
'rest_framework.parsers.FormParser',
|
||||
@ -218,6 +220,9 @@ DEVSERVER_MODULES = (
|
||||
#'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.
|
||||
SOUTH_TESTS_MIGRATE = False
|
||||
|
||||
@ -234,6 +239,11 @@ CELERYD_TASK_SOFT_TIME_LIMIT = 3540
|
||||
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
|
||||
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 = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
|
@ -21,6 +21,8 @@ ALLOWED_HOSTS = []
|
||||
# Production should only use minified JS for UI.
|
||||
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
|
||||
# settings. Normally, local settings would only be present during development.
|
||||
try:
|
||||
|
@ -11,6 +11,7 @@ djangorestframework>=2.3.0,<2.4.0
|
||||
Markdown
|
||||
pexpect
|
||||
python-dateutil
|
||||
requests
|
||||
South>=0.8,<2.0
|
||||
|
||||
django-debug-toolbar
|
||||
|
BIN
requirements/requests-1.2.3.tar.gz
Normal file
BIN
requirements/requests-1.2.3.tar.gz
Normal file
Binary file not shown.
3
setup.py
3
setup.py
@ -53,7 +53,7 @@ def proc_data_files(data_files):
|
||||
|
||||
setup(
|
||||
name='ansibleworks',
|
||||
version=__version__.split("-")[0],
|
||||
version=__version__.split("-")[0], # FIXME: Should keep full version here?
|
||||
author='AnsibleWorks, Inc.',
|
||||
author_email='support@ansibleworks.com',
|
||||
description='AnsibleWorks API, UI and Task Engine',
|
||||
@ -75,6 +75,7 @@ setup(
|
||||
'pexpect',
|
||||
'python-dateutil',
|
||||
'PyYAML',
|
||||
'requests',
|
||||
'South>=0.8,<2.0',
|
||||
],
|
||||
setup_requires=[],
|
||||
|
Loading…
Reference in New Issue
Block a user