mirror of
https://github.com/ansible/awx.git
synced 2026-02-18 11:40:05 -03:30
Moved API code into separate Django app.
This commit is contained in:
2
awx/api/__init__.py
Normal file
2
awx/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
61
awx/api/authentication.py
Normal file
61
awx/api/authentication.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import authentication
|
||||
from rest_framework import exceptions
|
||||
|
||||
# AWX
|
||||
from awx.main.models import Job, AuthToken
|
||||
|
||||
class TokenAuthentication(authentication.TokenAuthentication):
|
||||
'''
|
||||
Custom token authentication using tokens that expire and are associated
|
||||
with parameters specific to the request.
|
||||
'''
|
||||
|
||||
model = AuthToken
|
||||
|
||||
def authenticate(self, request):
|
||||
self.request = request
|
||||
return super(TokenAuthentication, self).authenticate(request)
|
||||
|
||||
def authenticate_credentials(self, key):
|
||||
try:
|
||||
request_hash = self.model.get_request_hash(self.request)
|
||||
token = self.model.objects.get(key=key, request_hash=request_hash)
|
||||
except self.model.DoesNotExist:
|
||||
raise exceptions.AuthenticationFailed('Invalid token')
|
||||
|
||||
if token.expired:
|
||||
raise exceptions.AuthenticationFailed('Token is expired')
|
||||
|
||||
if not token.user.is_active:
|
||||
raise exceptions.AuthenticationFailed('User inactive or deleted')
|
||||
|
||||
token.refresh()
|
||||
|
||||
return (token.user, token)
|
||||
|
||||
class JobTaskAuthentication(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.task_auth_token
|
||||
if auth[1] != token:
|
||||
raise exceptions.AuthenticationFailed('Invalid job task token')
|
||||
return (None, token)
|
||||
|
||||
def authenticate_header(self, request):
|
||||
return 'Token'
|
||||
196
awx/api/filters.py
Normal file
196
awx/api/filters.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import re
|
||||
|
||||
# Django
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
|
||||
class ActiveOnlyBackend(BaseFilterBackend):
|
||||
'''
|
||||
Filter to show only objects where is_active/active is True.
|
||||
'''
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
for field in queryset.model._meta.fields:
|
||||
if field.name == 'is_active':
|
||||
queryset = queryset.filter(is_active=True)
|
||||
elif field.name == 'active':
|
||||
queryset = queryset.filter(active=True)
|
||||
return queryset
|
||||
|
||||
class FieldLookupBackend(BaseFilterBackend):
|
||||
'''
|
||||
Filter using field lookups provided via query string parameters.
|
||||
'''
|
||||
|
||||
RESERVED_NAMES = ('page', 'page_size', 'format', 'order', 'order_by',
|
||||
'search')
|
||||
|
||||
SUPPORTED_LOOKUPS = ('exact', 'iexact', 'contains', 'icontains',
|
||||
'startswith', 'istartswith', 'endswith', 'iendswith',
|
||||
'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'in',
|
||||
'isnull')
|
||||
|
||||
def get_field_from_lookup(self, model, lookup):
|
||||
field = None
|
||||
parts = lookup.split('__')
|
||||
if parts and parts[-1] not in self.SUPPORTED_LOOKUPS:
|
||||
parts.append('exact')
|
||||
# FIXME: Could build up a list of models used across relationships, use
|
||||
# those lookups combined with request.user.get_queryset(Model) to make
|
||||
# sure user cannot query using objects he could not view.
|
||||
for n, name in enumerate(parts[:-1]):
|
||||
if name == 'pk':
|
||||
field = model._meta.pk
|
||||
else:
|
||||
field = model._meta.get_field_by_name(name)[0]
|
||||
if n < (len(parts) - 2):
|
||||
if getattr(field, 'rel', None):
|
||||
model = field.rel.to
|
||||
else:
|
||||
model = field.model
|
||||
return field
|
||||
|
||||
def to_python_boolean(self, value, allow_none=False):
|
||||
value = unicode(value)
|
||||
if value.lower() in ('true', '1'):
|
||||
return True
|
||||
elif value.lower() in ('false', '0'):
|
||||
return False
|
||||
elif allow_none and value.lower() in ('none', 'null'):
|
||||
return None
|
||||
else:
|
||||
raise ValueError(u'Unable to convert "%s" to boolean' % unicode(value))
|
||||
|
||||
def to_python_related(self, value):
|
||||
value = unicode(value)
|
||||
if value.lower() in ('none', 'null'):
|
||||
return None
|
||||
else:
|
||||
return int(value)
|
||||
|
||||
def value_to_python_for_field(self, field, value):
|
||||
if isinstance(field, models.NullBooleanField):
|
||||
return self.to_python_boolean(value, allow_none=True)
|
||||
elif isinstance(field, models.BooleanField):
|
||||
return self.to_python_boolean(value)
|
||||
elif isinstance(field, RelatedObject):
|
||||
return self.to_python_related(value)
|
||||
else:
|
||||
return field.to_python(value)
|
||||
|
||||
def value_to_python(self, model, lookup, value):
|
||||
field = self.get_field_from_lookup(model, lookup)
|
||||
if lookup.endswith('__isnull'):
|
||||
value = self.to_python_boolean(value)
|
||||
elif lookup.endswith('__in'):
|
||||
items = []
|
||||
for item in value.split(','):
|
||||
items.append(self.value_to_python_for_field(field, item))
|
||||
value = items
|
||||
elif lookup.endswith('__regex') or lookup.endswith('__iregex'):
|
||||
try:
|
||||
re.compile(value)
|
||||
except re.error, e:
|
||||
raise ValueError(e.args[0])
|
||||
return value
|
||||
else:
|
||||
value = self.value_to_python_for_field(field, value)
|
||||
return value
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
try:
|
||||
# Apply filters specified via QUERY_PARAMS. Each entry in the lists
|
||||
# below is (negate, field, value).
|
||||
and_filters = []
|
||||
or_filters = []
|
||||
for key, values in request.QUERY_PARAMS.lists():
|
||||
if key in self.RESERVED_NAMES:
|
||||
continue
|
||||
|
||||
# Custom __int filter suffix (internal use only).
|
||||
q_int = False
|
||||
if key.endswith('__int'):
|
||||
key = key[:-5]
|
||||
q_int = True
|
||||
# Custom or__ filter prefix (or__ can precede not__).
|
||||
q_or = False
|
||||
if key.startswith('or__'):
|
||||
key = key[4:]
|
||||
q_or = True
|
||||
# Custom not__ filter prefix.
|
||||
q_not = False
|
||||
if key.startswith('not__'):
|
||||
key = key[5:]
|
||||
q_not = True
|
||||
|
||||
# Convert value(s) to python and add to the appropriate list.
|
||||
for value in values:
|
||||
if q_int:
|
||||
value = int(value)
|
||||
value = self.value_to_python(queryset.model, key, value)
|
||||
if q_or:
|
||||
or_filters.append((q_not, key, value))
|
||||
else:
|
||||
and_filters.append((q_not, key, value))
|
||||
|
||||
# Now build Q objects for database query filter.
|
||||
if and_filters or or_filters:
|
||||
args = []
|
||||
for n, k, v in and_filters:
|
||||
if n:
|
||||
args.append(~Q(**{k:v}))
|
||||
else:
|
||||
args.append(Q(**{k:v}))
|
||||
if or_filters:
|
||||
q = Q()
|
||||
for n,k,v in or_filters:
|
||||
if n:
|
||||
q |= ~Q(**{k:v})
|
||||
else:
|
||||
q |= Q(**{k:v})
|
||||
args.append(q)
|
||||
queryset = queryset.filter(*args)
|
||||
return queryset
|
||||
except (FieldError, FieldDoesNotExist, ValueError), e:
|
||||
raise ParseError(e.args[0])
|
||||
except ValidationError, e:
|
||||
raise ParseError(e.messages)
|
||||
|
||||
class OrderByBackend(BaseFilterBackend):
|
||||
'''
|
||||
Filter to apply ordering based on query string parameters.
|
||||
'''
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
try:
|
||||
order_by = None
|
||||
for key, value in request.QUERY_PARAMS.items():
|
||||
if key in ('order', 'order_by'):
|
||||
order_by = value
|
||||
if ',' in value:
|
||||
order_by = value.split(',')
|
||||
else:
|
||||
order_by = (value,)
|
||||
if order_by:
|
||||
queryset = queryset.order_by(*order_by)
|
||||
# Fetch the first result to run the query, otherwise we don't
|
||||
# always catch the FieldError for invalid field names.
|
||||
try:
|
||||
queryset[0]
|
||||
except IndexError:
|
||||
pass
|
||||
return queryset
|
||||
except FieldError, e:
|
||||
# Return a 400 for invalid field names.
|
||||
raise ParseError(*e.args)
|
||||
411
awx/api/generics.py
Normal file
411
awx/api/generics.py
Normal file
@@ -0,0 +1,411 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import inspect
|
||||
import json
|
||||
|
||||
# Django
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import now
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.authentication import get_authorization_header
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.request import clone_request
|
||||
from rest_framework import status
|
||||
from rest_framework import views
|
||||
|
||||
# AWX
|
||||
from awx.main.models import *
|
||||
from awx.main.utils import *
|
||||
|
||||
# FIXME: machinery for auto-adding audit trail logs to all CREATE/EDITS
|
||||
|
||||
__all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'ListCreateAPIView',
|
||||
'SubListAPIView', 'SubListCreateAPIView', 'RetrieveAPIView',
|
||||
'RetrieveUpdateAPIView', 'RetrieveUpdateDestroyAPIView']
|
||||
|
||||
def get_view_name(cls, suffix=None):
|
||||
'''
|
||||
Wrapper around REST framework get_view_name() to support get_name() method
|
||||
and view_name property on a view class.
|
||||
'''
|
||||
name = ''
|
||||
if hasattr(cls, 'get_name') and callable(cls.get_name):
|
||||
name = cls().get_name()
|
||||
elif hasattr(cls, 'view_name'):
|
||||
if callable(cls.view_name):
|
||||
name = cls.view_name()
|
||||
else:
|
||||
name = cls.view_name
|
||||
if name:
|
||||
return ('%s %s' % (name, suffix)) if suffix else name
|
||||
return views.get_view_name(cls, suffix=None)
|
||||
|
||||
def get_view_description(cls, html=False):
|
||||
'''
|
||||
Wrapper around REST framework get_view_description() to support
|
||||
get_description() method and view_description property on a view class.
|
||||
'''
|
||||
if hasattr(cls, 'get_description') and callable(cls.get_description):
|
||||
desc = cls().get_description(html=html)
|
||||
cls = type(cls.__name__, (object,), {'__doc__': desc})
|
||||
elif hasattr(cls, 'view_description'):
|
||||
if callable(cls.view_description):
|
||||
view_desc = cls.view_description()
|
||||
else:
|
||||
view_desc = cls.view_description
|
||||
cls = type(cls.__name__, (object,), {'__doc__': view_desc})
|
||||
desc = views.get_view_description(cls, html=html)
|
||||
if html:
|
||||
desc = '<div class="description">%s</div>' % desc
|
||||
return mark_safe(desc)
|
||||
|
||||
class APIView(views.APIView):
|
||||
|
||||
def get_authenticate_header(self, request):
|
||||
"""
|
||||
Determine the WWW-Authenticate header to use for 401 responses. Try to
|
||||
use the request header as an indication for which authentication method
|
||||
was attempted.
|
||||
"""
|
||||
for authenticator in self.get_authenticators():
|
||||
resp_hdr = authenticator.authenticate_header(request)
|
||||
if not resp_hdr:
|
||||
continue
|
||||
req_hdr = get_authorization_header(request)
|
||||
if not req_hdr:
|
||||
continue
|
||||
if resp_hdr.split()[0] and resp_hdr.split()[0] == req_hdr.split()[0]:
|
||||
return resp_hdr
|
||||
return super(APIView, self).get_authenticate_header(request)
|
||||
|
||||
def get_description_context(self):
|
||||
return {
|
||||
'docstring': type(self).__doc__ or '',
|
||||
'new_in_13': getattr(self, 'new_in_13', False),
|
||||
'new_in_14': getattr(self, 'new_in_14', False),
|
||||
}
|
||||
|
||||
def get_description(self, html=False):
|
||||
template_list = []
|
||||
for klass in inspect.getmro(type(self)):
|
||||
template_basename = camelcase_to_underscore(klass.__name__)
|
||||
template_list.append('api/%s.md' % template_basename)
|
||||
context = self.get_description_context()
|
||||
return render_to_string(template_list, context)
|
||||
|
||||
class GenericAPIView(generics.GenericAPIView, APIView):
|
||||
# Base class for all model-based views.
|
||||
|
||||
# Subclasses should define:
|
||||
# model = ModelClass
|
||||
# serializer_class = SerializerClass
|
||||
|
||||
def get_queryset(self):
|
||||
#if hasattr(self.request.user, 'get_queryset'):
|
||||
# return self.request.user.get_queryset(self.model)
|
||||
#else:
|
||||
return super(GenericAPIView, self).get_queryset()
|
||||
|
||||
def get_description_context(self):
|
||||
# Set instance attributes needed to get serializer metadata.
|
||||
if not hasattr(self, 'request'):
|
||||
self.request = None
|
||||
if not hasattr(self, 'format_kwarg'):
|
||||
self.format_kwarg = 'format'
|
||||
d = super(GenericAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'model_verbose_name': unicode(self.model._meta.verbose_name),
|
||||
'model_verbose_name_plural': unicode(self.model._meta.verbose_name_plural),
|
||||
'serializer_fields': self.get_serializer().metadata(),
|
||||
})
|
||||
return d
|
||||
|
||||
def metadata(self, request):
|
||||
'''
|
||||
Add field information for GET requests (so field names/labels are
|
||||
available even when we can't POST/PUT).
|
||||
'''
|
||||
ret = super(GenericAPIView, self).metadata(request)
|
||||
actions = ret.get('actions', {})
|
||||
# Remove read only fields from PUT/POST data.
|
||||
for method in ('POST', 'PUT'):
|
||||
fields = actions.get(method, {})
|
||||
for field, meta in fields.items():
|
||||
if not isinstance(meta, dict):
|
||||
continue
|
||||
if meta.get('read_only', False):
|
||||
fields.pop(field)
|
||||
if 'GET' in self.allowed_methods:
|
||||
cloned_request = clone_request(request, 'GET')
|
||||
try:
|
||||
# Test global permissions
|
||||
self.check_permissions(cloned_request)
|
||||
# Test object permissions
|
||||
if hasattr(self, 'retrieve'):
|
||||
try:
|
||||
self.get_object()
|
||||
except Http404:
|
||||
# Http404 should be acceptable and the serializer
|
||||
# metadata should be populated. Except this so the
|
||||
# outer "else" clause of the try-except-else block
|
||||
# will be executed.
|
||||
pass
|
||||
except (exceptions.APIException, PermissionDenied):
|
||||
pass
|
||||
else:
|
||||
# If user has appropriate permissions for the view, include
|
||||
# appropriate metadata about the fields that should be supplied.
|
||||
serializer = self.get_serializer()
|
||||
actions['GET'] = serializer.metadata()
|
||||
if actions:
|
||||
ret['actions'] = actions
|
||||
if getattr(self, 'search_fields', None):
|
||||
ret['search_fields'] = self.search_fields
|
||||
return ret
|
||||
|
||||
class ListAPIView(generics.ListAPIView, GenericAPIView):
|
||||
# Base class for a read-only list view.
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.user.get_queryset(self.model)
|
||||
|
||||
def get_description_context(self):
|
||||
opts = self.model._meta
|
||||
if 'username' in opts.get_all_field_names():
|
||||
order_field = 'username'
|
||||
else:
|
||||
order_field = 'name'
|
||||
d = super(ListAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'order_field': order_field,
|
||||
})
|
||||
return d
|
||||
|
||||
@property
|
||||
def search_fields(self):
|
||||
fields = []
|
||||
for field in self.model._meta.fields:
|
||||
if field.name in ('username', 'first_name', 'last_name', 'email',
|
||||
'name', 'description', 'email'):
|
||||
fields.append(field.name)
|
||||
return fields
|
||||
|
||||
class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
|
||||
# Base class for a list view that allows creating new objects.
|
||||
|
||||
def pre_save(self, obj):
|
||||
super(ListCreateAPIView, self).pre_save(obj)
|
||||
if isinstance(obj, PrimordialModel):
|
||||
obj.created_by = self.request.user
|
||||
|
||||
class SubListAPIView(ListAPIView):
|
||||
# Base class for a read-only sublist view.
|
||||
|
||||
# Subclasses should define at least:
|
||||
# model = ModelClass
|
||||
# serializer_class = SerializerClass
|
||||
# parent_model = ModelClass
|
||||
# relationship = 'rel_name_from_parent_to_model'
|
||||
# And optionally (user must have given access permission on parent object
|
||||
# to view sublist):
|
||||
# parent_access = 'read'
|
||||
|
||||
def get_description_context(self):
|
||||
d = super(SubListAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'parent_model_verbose_name': unicode(self.parent_model._meta.verbose_name),
|
||||
'parent_model_verbose_name_plural': unicode(self.parent_model._meta.verbose_name_plural),
|
||||
})
|
||||
return d
|
||||
|
||||
def get_parent_object(self):
|
||||
parent_filter = {
|
||||
self.lookup_field: self.kwargs.get(self.lookup_field, None),
|
||||
}
|
||||
return get_object_or_404(self.parent_model, **parent_filter)
|
||||
|
||||
def check_parent_access(self, parent=None):
|
||||
parent = parent or self.get_parent_object()
|
||||
parent_access = getattr(self, 'parent_access', 'read')
|
||||
if parent_access in ('read', 'delete'):
|
||||
args = (self.parent_model, parent_access, parent)
|
||||
else:
|
||||
args = (self.parent_model, parent_access, parent, None)
|
||||
if not self.request.user.can_access(*args):
|
||||
raise PermissionDenied()
|
||||
|
||||
def get_queryset(self):
|
||||
parent = self.get_parent_object()
|
||||
self.check_parent_access(parent)
|
||||
qs = self.request.user.get_queryset(self.model).distinct()
|
||||
sublist_qs = getattr(parent, self.relationship).distinct()
|
||||
return qs & sublist_qs
|
||||
|
||||
class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||
# Base class for a sublist view that allows for creating subobjects and
|
||||
# attaching/detaching them from the parent.
|
||||
|
||||
# In addition to SubListAPIView properties, subclasses may define (if the
|
||||
# sub_obj requires a foreign key to the parent):
|
||||
# parent_key = 'field_on_model_referring_to_parent'
|
||||
|
||||
def get_description_context(self):
|
||||
d = super(SubListCreateAPIView, self).get_description_context()
|
||||
d.update({
|
||||
'parent_key': getattr(self, 'parent_key', None),
|
||||
})
|
||||
return d
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# If the object ID was not specified, it probably doesn't exist in the
|
||||
# DB yet. We want to see if we can create it. The URL may choose to
|
||||
# inject it's primary key into the object because we are posting to a
|
||||
# subcollection. Use all the normal access control mechanisms.
|
||||
|
||||
# Make a copy of the data provided (since it's readonly) in order to
|
||||
# inject additional data.
|
||||
if hasattr(request.DATA, 'dict'):
|
||||
data = request.DATA.dict()
|
||||
else:
|
||||
data = request.DATA
|
||||
|
||||
# add the parent key to the post data using the pk from the URL
|
||||
parent_key = getattr(self, 'parent_key', None)
|
||||
if parent_key:
|
||||
data[parent_key] = self.kwargs['pk']
|
||||
|
||||
# attempt to deserialize the object
|
||||
serializer = self.serializer_class(data=data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors,
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Verify we have permission to add the object as given.
|
||||
if not request.user.can_access(self.model, 'add', serializer.init_data):
|
||||
raise PermissionDenied()
|
||||
|
||||
# save the object through the serializer, reload and returned the saved
|
||||
# object deserialized
|
||||
obj = serializer.save()
|
||||
serializer = self.serializer_class(obj)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def attach(self, request, *args, **kwargs):
|
||||
created = False
|
||||
parent = self.get_parent_object()
|
||||
relationship = getattr(parent, self.relationship)
|
||||
sub_id = request.DATA.get('id', None)
|
||||
data = request.DATA
|
||||
|
||||
# Create the sub object if an ID is not provided.
|
||||
if not sub_id:
|
||||
response = self.create(request, *args, **kwargs)
|
||||
if response.status_code != status.HTTP_201_CREATED:
|
||||
return response
|
||||
sub_id = response.data['id']
|
||||
data = response.data
|
||||
try:
|
||||
location = response['Location']
|
||||
except KeyError:
|
||||
location = None
|
||||
created = True
|
||||
|
||||
# Retrive the sub object (whether created or by ID).
|
||||
sub = get_object_or_400(self.model, pk=sub_id)
|
||||
|
||||
# Verify we have permission to attach.
|
||||
if not request.user.can_access(self.parent_model, 'attach', parent, sub,
|
||||
self.relationship, data,
|
||||
skip_sub_obj_read_check=created):
|
||||
raise PermissionDenied()
|
||||
|
||||
# Attach the object to the collection.
|
||||
if sub not in relationship.all():
|
||||
relationship.add(sub)
|
||||
|
||||
if created:
|
||||
headers = {}
|
||||
if location:
|
||||
headers['Location'] = location
|
||||
return Response(data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
else:
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unattach(self, request, *args, **kwargs):
|
||||
sub_id = request.DATA.get('id', None)
|
||||
if not sub_id:
|
||||
data = dict(msg='"id" is required to disassociate')
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
parent = self.get_parent_object()
|
||||
parent_key = getattr(self, 'parent_key', None)
|
||||
relationship = getattr(parent, self.relationship)
|
||||
sub = get_object_or_400(self.model, pk=sub_id)
|
||||
|
||||
if not request.user.can_access(self.parent_model, 'unattach', parent,
|
||||
sub, self.relationship):
|
||||
raise PermissionDenied()
|
||||
|
||||
if parent_key:
|
||||
# sub object has a ForeignKey to the parent, so we can't remove it
|
||||
# from the set, only mark it as inactive.
|
||||
sub.mark_inactive()
|
||||
else:
|
||||
relationship.remove(sub)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not isinstance(request.DATA, dict):
|
||||
return Response('invalid type for post data',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
if 'disassociate' in request.DATA:
|
||||
return self.unattach(request, *args, **kwargs)
|
||||
else:
|
||||
return self.attach(request, *args, **kwargs)
|
||||
|
||||
class RetrieveAPIView(generics.RetrieveAPIView, GenericAPIView):
|
||||
pass
|
||||
|
||||
class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
|
||||
|
||||
def pre_save(self, obj):
|
||||
super(RetrieveUpdateAPIView, self).pre_save(obj)
|
||||
if isinstance(obj, PrimordialModel):
|
||||
obj.created_by = self.request.user
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
self.update_filter(request, *args, **kwargs)
|
||||
return super(RetrieveUpdateAPIView, self).update(request, *args, **kwargs)
|
||||
|
||||
def update_filter(self, request, *args, **kwargs):
|
||||
''' scrub any fields the user cannot/should not put/patch, based on user context. This runs after read-only serialization filtering '''
|
||||
pass
|
||||
|
||||
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
# somewhat lame that delete has to call it's own permissions check
|
||||
obj = self.get_object()
|
||||
# FIXME: Why isn't the active check being caught earlier by RBAC?
|
||||
if getattr(obj, 'active', True) == False:
|
||||
raise Http404()
|
||||
if getattr(obj, 'is_active', True) == False:
|
||||
raise Http404()
|
||||
if not request.user.can_access(self.model, 'delete', obj):
|
||||
raise PermissionDenied()
|
||||
if hasattr(obj, 'mark_inactive'):
|
||||
obj.mark_inactive()
|
||||
else:
|
||||
raise NotImplementedError('destroy() not implemented yet for %s' % obj)
|
||||
return HttpResponse(status=204)
|
||||
4
awx/api/models.py
Normal file
4
awx/api/models.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Empty models file.
|
||||
37
awx/api/pagination.py
Normal file
37
awx/api/pagination.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import serializers, pagination
|
||||
from rest_framework.templatetags.rest_framework import replace_query_param
|
||||
|
||||
class NextPageField(pagination.NextPageField):
|
||||
'''Pagination field to output URL path.'''
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.has_next():
|
||||
return None
|
||||
page = value.next_page_number()
|
||||
request = self.context.get('request')
|
||||
url = request and request.get_full_path() or ''
|
||||
return replace_query_param(url, self.page_field, page)
|
||||
|
||||
class PreviousPageField(pagination.NextPageField):
|
||||
'''Pagination field to output URL path.'''
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.has_previous():
|
||||
return None
|
||||
page = value.previous_page_number()
|
||||
request = self.context.get('request')
|
||||
url = request and request.get_full_path() or ''
|
||||
return replace_query_param(url, self.page_field, page)
|
||||
|
||||
class PaginationSerializer(pagination.BasePaginationSerializer):
|
||||
'''
|
||||
Custom pagination serializer to output only URL path (without host/port).
|
||||
'''
|
||||
|
||||
count = serializers.Field(source='paginator.count')
|
||||
next = NextPageField(source='*')
|
||||
previous = PreviousPageField(source='*')
|
||||
188
awx/api/permissions.py
Normal file
188
awx/api/permissions.py
Normal file
@@ -0,0 +1,188 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import logging
|
||||
|
||||
# Django
|
||||
from django.http import Http404
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import permissions
|
||||
|
||||
# AWX
|
||||
from awx.main.access import *
|
||||
from awx.main.models import *
|
||||
from awx.main.utils import get_object_or_400
|
||||
|
||||
logger = logging.getLogger('awx.api.permissions')
|
||||
|
||||
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
|
||||
'JobTaskPermission']
|
||||
|
||||
class ModelAccessPermission(permissions.BasePermission):
|
||||
'''
|
||||
Default permissions class to check user access based on the model and
|
||||
request method, optionally verifying the request data.
|
||||
'''
|
||||
|
||||
def check_options_permissions(self, request, view, obj=None):
|
||||
return self.check_get_permissions(request, view, obj)
|
||||
|
||||
def check_head_permissions(self, request, view, obj=None):
|
||||
return self.check_get_permissions(request, view, obj)
|
||||
|
||||
def check_get_permissions(self, request, view, obj=None):
|
||||
if hasattr(view, 'parent_model'):
|
||||
parent_obj = get_object_or_400(view.parent_model, pk=view.kwargs['pk'])
|
||||
if not check_user_access(request.user, view.parent_model, 'read',
|
||||
parent_obj):
|
||||
return False
|
||||
if not obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'read', obj)
|
||||
|
||||
def check_post_permissions(self, request, view, obj=None):
|
||||
if hasattr(view, 'parent_model'):
|
||||
parent_obj = get_object_or_400(view.parent_model, pk=view.kwargs['pk'])
|
||||
return True
|
||||
elif getattr(view, 'is_job_start', False):
|
||||
if not obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'start', obj)
|
||||
elif getattr(view, 'is_job_cancel', False):
|
||||
if not obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'cancel', obj)
|
||||
else:
|
||||
if obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'add', request.DATA)
|
||||
|
||||
def check_put_permissions(self, request, view, obj=None):
|
||||
if not obj:
|
||||
return True # FIXME: For some reason this needs to return True
|
||||
# because it is first called with obj=None?
|
||||
if getattr(view, 'is_variable_data', False):
|
||||
return check_user_access(request.user, view.model, 'change', obj,
|
||||
dict(variables=request.DATA))
|
||||
else:
|
||||
return check_user_access(request.user, view.model, 'change', obj,
|
||||
request.DATA)
|
||||
|
||||
def check_patch_permissions(self, request, view, obj=None):
|
||||
return self.check_put_permissions(request, view, obj)
|
||||
|
||||
def check_delete_permissions(self, request, view, obj=None):
|
||||
if not obj:
|
||||
return True # FIXME: For some reason this needs to return True
|
||||
# because it is first called with obj=None?
|
||||
return check_user_access(request.user, view.model, 'delete', obj)
|
||||
|
||||
def check_permissions(self, request, view, obj=None):
|
||||
'''
|
||||
Perform basic permissions checking before delegating to the appropriate
|
||||
method based on the request method.
|
||||
'''
|
||||
|
||||
# Check that obj (if given) is active, otherwise raise a 404.
|
||||
active = getattr(obj, 'active', getattr(obj, 'is_active', True))
|
||||
if callable(active):
|
||||
active = active()
|
||||
if not active:
|
||||
raise Http404()
|
||||
|
||||
# Don't allow anonymous users. 401, not 403, hence no raised exception.
|
||||
if not request.user or request.user.is_anonymous():
|
||||
return False
|
||||
|
||||
# Don't allow inactive users (and respond with a 403).
|
||||
if not request.user.is_active:
|
||||
raise PermissionDenied('your account is inactive')
|
||||
|
||||
# Always allow superusers (as long as they are active).
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
|
||||
# Check permissions for the given view and object, based on the request
|
||||
# method used.
|
||||
check_method = getattr(self, 'check_%s_permissions' % \
|
||||
request.method.lower(), None)
|
||||
result = check_method and check_method(request, view, obj)
|
||||
if not result:
|
||||
raise PermissionDenied()
|
||||
|
||||
return result
|
||||
|
||||
def has_permission(self, request, view, obj=None):
|
||||
logger.debug('has_permission(user=%s method=%s data=%r, %s, %r)',
|
||||
request.user, request.method, request.DATA,
|
||||
view.__class__.__name__, obj)
|
||||
try:
|
||||
response = self.check_permissions(request, view, obj)
|
||||
except Exception, e:
|
||||
logger.debug('has_permission raised %r', e, exc_info=True)
|
||||
raise
|
||||
else:
|
||||
logger.debug('has_permission returned %r', response)
|
||||
return response
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return self.has_permission(request, view, obj)
|
||||
|
||||
class JobTemplateCallbackPermission(ModelAccessPermission):
|
||||
'''
|
||||
Permission check used by job template callback view for requests from
|
||||
empheral hosts.
|
||||
'''
|
||||
|
||||
def has_permission(self, request, view, obj=None):
|
||||
# If another authentication method was used and it's not a POST, return
|
||||
# True to fall through to the next permission class.
|
||||
if (request.user or request.auth) and request.method.lower() != 'post':
|
||||
return super(JobTemplateCallbackPermission, self).has_permission(request, view, obj)
|
||||
|
||||
# Require method to be POST, host_config_key to be specified and match
|
||||
# the requested job template, and require the job template to be
|
||||
# active in order to proceed.
|
||||
host_config_key = request.DATA.get('host_config_key', '')
|
||||
if request.method.lower() != 'post':
|
||||
raise PermissionDenied()
|
||||
elif not host_config_key:
|
||||
raise PermissionDenied()
|
||||
elif obj and not obj.active:
|
||||
raise PermissionDenied()
|
||||
elif obj and obj.host_config_key != host_config_key:
|
||||
raise PermissionDenied()
|
||||
else:
|
||||
return True
|
||||
|
||||
class JobTaskPermission(ModelAccessPermission):
|
||||
'''
|
||||
Permission checks used for API callbacks from running a task.
|
||||
'''
|
||||
|
||||
def has_permission(self, request, view, obj=None):
|
||||
# If another authentication method was used other than the one for job
|
||||
# callbacks, default to the superclass permissions checking.
|
||||
if request.user or not request.auth:
|
||||
return super(JobTaskPermission, self).has_permission(request, view, obj)
|
||||
|
||||
# Verify that the job ID present in the auth token is for a valid,
|
||||
# active job.
|
||||
try:
|
||||
job = Job.objects.get(active=True, status='running',
|
||||
pk=int(request.auth.split('-')[0]))
|
||||
except (Job.DoesNotExist, TypeError):
|
||||
return False
|
||||
|
||||
# Verify that the request method is one of those allowed for the given
|
||||
# view, also that the job or inventory being accessed matches the auth
|
||||
# token.
|
||||
if view.model == Inventory and request.method.lower() in ('head', 'get'):
|
||||
return bool(not obj or obj.pk == job.inventory.pk)
|
||||
elif view.model == JobEvent and request.method.lower() == 'post':
|
||||
return bool(not obj or obj.pk == job.pk)
|
||||
else:
|
||||
return False
|
||||
18
awx/api/renderers.py
Normal file
18
awx/api/renderers.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework import renderers
|
||||
|
||||
class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
|
||||
'''
|
||||
Customizations to the default browsable API renderer.
|
||||
'''
|
||||
|
||||
def get_rendered_html_form(self, view, method, request):
|
||||
'''Never show auto-generated form (only raw form).'''
|
||||
obj = getattr(view, 'object', None)
|
||||
if not self.show_form_for_method(view, method, request, obj):
|
||||
return
|
||||
if method in ('DELETE', 'OPTIONS'):
|
||||
return True # Don't actually need to return a form
|
||||
1021
awx/api/serializers.py
Normal file
1021
awx/api/serializers.py
Normal file
File diff suppressed because it is too large
Load Diff
120
awx/api/templates/api/_list_common.md
Normal file
120
awx/api/templates/api/_list_common.md
Normal file
@@ -0,0 +1,120 @@
|
||||
The resulting data structure contains:
|
||||
|
||||
{
|
||||
"count": 99,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
The `count` field indicates the total number of {{ model_verbose_name_plural }}
|
||||
found for the given query. The `next` and `previous` fields provides links to
|
||||
additional results if there are more than will fit on a single page. The
|
||||
`results` list contains zero or more {{ model_verbose_name }} records.
|
||||
|
||||
## Results
|
||||
|
||||
Each {{ model_verbose_name }} data structure includes the following fields:
|
||||
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
|
||||
## Sorting
|
||||
|
||||
To specify that {{ model_verbose_name_plural }} are returned in a particular
|
||||
order, use the `order_by` query string parameter on the GET request.
|
||||
|
||||
?order_by={{ order_field }}
|
||||
|
||||
Prefix the field name with a dash `-` to sort in reverse:
|
||||
|
||||
?order_by=-{{ order_field }}
|
||||
|
||||
Multiple sorting fields may be specified by separating the field names with a
|
||||
comma `,`:
|
||||
|
||||
?order_by={{ order_field }},some_other_field
|
||||
|
||||
## Pagination
|
||||
|
||||
Use the `page_size` query string parameter to change the number of results
|
||||
returned for each request. Use the `page` query string parameter to retrieve
|
||||
a particular page of results.
|
||||
|
||||
?page_size=100&page=2
|
||||
|
||||
The `previous` and `next` links returned with the results will set these query
|
||||
string parameters automatically.
|
||||
|
||||
## Searching
|
||||
|
||||
Use the `search` query string parameter to perform a case-insensitive search
|
||||
within all designated text fields of a model.
|
||||
|
||||
?search=findme
|
||||
|
||||
_New in AWX 1.4_
|
||||
|
||||
## Filtering
|
||||
|
||||
Any additional query string parameters may be used to filter the list of
|
||||
results returned to those matching a given value. Only fields and relations
|
||||
that exist in the database may be used for filtering. Any special characters
|
||||
in the specified value should be url-encoded. For example:
|
||||
|
||||
?field=value%20xyz
|
||||
|
||||
Fields may also span relations, only for fields and relationships defined in
|
||||
the database:
|
||||
|
||||
?other__field=value
|
||||
|
||||
To exclude results matching certain criteria, prefix the field parameter with
|
||||
`not__`:
|
||||
|
||||
?not__field=value
|
||||
|
||||
(_New in AWX 1.4_) By default, all query string filters are AND'ed together, so
|
||||
only the results matching *all* filters will be returned. To combine results
|
||||
matching *any* one of multiple criteria, prefix each query string parameter
|
||||
with `or__`:
|
||||
|
||||
?or__field=value&or__field=othervalue
|
||||
?or__not__field=value&or__field=othervalue
|
||||
|
||||
Field lookups may also be used for more advanced queries, by appending the
|
||||
lookup to the field name:
|
||||
|
||||
?field__lookup=value
|
||||
|
||||
The following field lookups are supported:
|
||||
|
||||
* `exact`: Exact match (default lookup if not specified).
|
||||
* `iexact`: Case-insensitive version of `exact`.
|
||||
* `contains`: Field contains value.
|
||||
* `icontains`: Case-insensitive version of `contains`.
|
||||
* `startswith`: Field starts with value.
|
||||
* `istartswith`: Case-insensitive version of `startswith`.
|
||||
* `endswith`: Field ends with value.
|
||||
* `iendswith`: Case-insensitive version of `endswith`.
|
||||
* `regex`: Field matches the given regular expression.
|
||||
* `iregex`: Case-insensitive version of `regex`.
|
||||
* `gt`: Greater than comparison.
|
||||
* `gte`: Greater than or equal to comparison.
|
||||
* `lt`: Less than comparison.
|
||||
* `lte`: Less than or equal to comparison.
|
||||
* `isnull`: Check whether the given field or related object is null; expects a
|
||||
boolean value.
|
||||
* `in`: Check whether the given field's value is present in the list provided;
|
||||
expects a list of items.
|
||||
|
||||
Boolean values may be specified as `True` or `1` for true, `False` or `0` for
|
||||
false (both case-insensitive).
|
||||
|
||||
Null values may be specified as `None` or `Null` (both case-insensitive),
|
||||
though it is preferred to use the `isnull` lookup to explicitly check for null
|
||||
values.
|
||||
|
||||
Lists (for the `in` lookup) may be specified as a comma-separated list of
|
||||
values.
|
||||
2
awx/api/templates/api/_new_in_awx.md
Normal file
2
awx/api/templates/api/_new_in_awx.md
Normal file
@@ -0,0 +1,2 @@
|
||||
{% if new_in_13 %}> _New in AWX 1.3_{% endif %}
|
||||
{% if new_in_14 %}> _New in AWX 1.4_{% endif %}
|
||||
6
awx/api/templates/api/_result_fields_common.md
Normal file
6
awx/api/templates/api/_result_fields_common.md
Normal file
@@ -0,0 +1,6 @@
|
||||
{% for fn, fm in serializer_fields.items %}{% spaceless %}
|
||||
{% if not write_only or not fm.read_only %}
|
||||
* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if fm.required %}, required{% endif %}{% if fm.read_only %}, read-only{% endif %})
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
{% endfor %}
|
||||
4
awx/api/templates/api/api_root_view.md
Normal file
4
awx/api/templates/api/api_root_view.md
Normal file
@@ -0,0 +1,4 @@
|
||||
The root of the AWX REST API.
|
||||
|
||||
Make a GET request to this resource to obtain information about the available
|
||||
API versions.
|
||||
12
awx/api/templates/api/api_v1_config_view.md
Normal file
12
awx/api/templates/api/api_v1_config_view.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Site configuration settings and general information.
|
||||
|
||||
Make a GET request to this resource to retrieve the configuration containing
|
||||
the following fields (some fields may not be visible to all users):
|
||||
|
||||
* `project_base_dir`: Path on the server where projects and playbooks are \
|
||||
stored.
|
||||
* `project_local_paths`: List of directories beneath `project_base_dir` to
|
||||
use when creating/editing a project.
|
||||
* `time_zone`: The configured time zone for the server.
|
||||
* `license_info`: Information about the current license.
|
||||
* `version`: Version of AWX package installed.
|
||||
4
awx/api/templates/api/api_v1_root_view.md
Normal file
4
awx/api/templates/api/api_v1_root_view.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Version 1 of the AWX REST API.
|
||||
|
||||
Make a GET request to this resource to obtain a list of all child resources
|
||||
available via the API.
|
||||
3
awx/api/templates/api/api_view.md
Normal file
3
awx/api/templates/api/api_view.md
Normal file
@@ -0,0 +1,3 @@
|
||||
{{ docstring }}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
31
awx/api/templates/api/auth_token_view.md
Normal file
31
awx/api/templates/api/auth_token_view.md
Normal file
@@ -0,0 +1,31 @@
|
||||
Make a POST request to this resource with `username` and `password` fields to
|
||||
obtain an authentication token to use for subsequent requests.
|
||||
|
||||
Example JSON to POST (content type is `application/json`):
|
||||
|
||||
{"username": "user", "password": "my pass"}
|
||||
|
||||
Example form data to post (content type is `application/x-www-form-urlencoded`):
|
||||
|
||||
username=user&password=my%20pass
|
||||
|
||||
If the username and password provided are valid, the response will contain a
|
||||
`token` field with the authentication token to use and an `expires` field with
|
||||
the timestamp when the token will expire:
|
||||
|
||||
{
|
||||
"token": "8f17825cf08a7efea124f2638f3896f6637f8745",
|
||||
"expires": "2013-09-05T21:46:35.729Z"
|
||||
}
|
||||
|
||||
Otherwise, the response will indicate the error that occurred and return a 4xx
|
||||
status code.
|
||||
|
||||
For subsequent requests, pass the token via the HTTP `Authorization` request
|
||||
header:
|
||||
|
||||
Authorization: Token 8f17825cf08a7efea124f2638f3896f6637f8745
|
||||
|
||||
Each request that uses the token for authentication will refresh its expiration
|
||||
timestamp and keep it from expiring. A token only expires when it is not used
|
||||
for the configured timeout interval (default 1800 seconds).
|
||||
9
awx/api/templates/api/base_variable_data.md
Normal file
9
awx/api/templates/api/base_variable_data.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Retrieve {{ model_verbose_name|title }} Variable Data:
|
||||
|
||||
Make a GET request to this resource to retrieve all variables defined for this
|
||||
{{ model_verbose_name }}.
|
||||
|
||||
# Update {{ model_verbose_name|title }} Variable Data:
|
||||
|
||||
Make a PUT request to this resource to update variables defined for this
|
||||
{{ model_verbose_name }}.
|
||||
7
awx/api/templates/api/group_all_hosts_list.md
Normal file
7
awx/api/templates/api/group_all_hosts_list.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# List All {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of all
|
||||
{{ model_verbose_name_plural }} directly or indirectly belonging to this
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "api/_list_common.md" %}
|
||||
9
awx/api/templates/api/group_potential_children_list.md
Normal file
9
awx/api/templates/api/group_potential_children_list.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# List Potential Child Groups for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of
|
||||
{{ model_verbose_name_plural }} available to be added as children of the
|
||||
current {{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "api/_list_common.md" %}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
7
awx/api/templates/api/host_all_groups_list.md
Normal file
7
awx/api/templates/api/host_all_groups_list.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# List All {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of all
|
||||
{{ model_verbose_name_plural }} of which the selected
|
||||
{{ parent_model_verbose_name }} is directly or indirectly a member.
|
||||
|
||||
{% include "api/_list_common.md" %}
|
||||
7
awx/api/templates/api/inventory_root_groups_list.md
Normal file
7
awx/api/templates/api/inventory_root_groups_list.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# List Root {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of root (top-level)
|
||||
{{ model_verbose_name_plural }} associated with this
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "api/_list_common.md" %}
|
||||
31
awx/api/templates/api/inventory_script_view.md
Normal file
31
awx/api/templates/api/inventory_script_view.md
Normal file
@@ -0,0 +1,31 @@
|
||||
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.
|
||||
|
||||
_(New in AWX 1.4)_ By default, the inventory script will only return hosts that
|
||||
are enabled in the inventory. This feature allows disabled hosts to be skipped
|
||||
when running jobs without removing them from the inventory. Specify a query
|
||||
string of `?all=1` to return all hosts, including disabled ones.
|
||||
|
||||
## 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
|
||||
the `--host HOSTNAME` argument to an inventory script.
|
||||
13
awx/api/templates/api/inventory_source_cancel.md
Normal file
13
awx/api/templates/api/inventory_source_cancel.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Cancel Inventory Update
|
||||
|
||||
Make a GET request to this resource to determine if the inventory update can be
|
||||
cancelled. The response will include the following field:
|
||||
|
||||
* `can_cancel`: Indicates whether this update can be canceled (boolean,
|
||||
read-only)
|
||||
|
||||
Make a POST request to this resource to cancel a pending or running inventory
|
||||
update. The response status code will be 202 if successful, or 405 if the
|
||||
update cannot be canceled.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
18
awx/api/templates/api/inventory_source_update_view.md
Normal file
18
awx/api/templates/api/inventory_source_update_view.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Update Inventory Source
|
||||
|
||||
Make a GET request to this resource to determine if the group can be updated
|
||||
from its inventory source and whether any passwords are required for the
|
||||
update. The response will include the following fields:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_update`: Password names required to update from the
|
||||
inventory source (array, read-only)
|
||||
|
||||
Make a POST request to this resource to update the inventory source. If any
|
||||
passwords are required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the inventory source
|
||||
is not defined or cannot be updated, a 405 status code will be returned.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
15
awx/api/templates/api/inventory_tree_view.md
Normal file
15
awx/api/templates/api/inventory_tree_view.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Group Tree for this {{ model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a hierarchical view of groups
|
||||
associated with the selected {{ model_verbose_name }}.
|
||||
|
||||
The resulting data structure contains a list of root groups, with each group
|
||||
also containing a list of its children.
|
||||
|
||||
## Results
|
||||
|
||||
Each group data structure includes the following fields:
|
||||
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
10
awx/api/templates/api/job_cancel.md
Normal file
10
awx/api/templates/api/job_cancel.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Cancel Job
|
||||
|
||||
Make a GET request to this resource to determine if the job can be cancelled.
|
||||
The response will include the following field:
|
||||
|
||||
* `can_cancel`: Indicates whether this job can be canceled (boolean, read-only)
|
||||
|
||||
Make a POST request to this resource to cancel a pending or running job. The
|
||||
response status code will be 202 if successful, or 405 if the job cannot be
|
||||
canceled.
|
||||
5
awx/api/templates/api/job_list.md
Normal file
5
awx/api/templates/api/job_list.md
Normal file
@@ -0,0 +1,5 @@
|
||||
{% include "api/list_create_api_view.md" %}
|
||||
|
||||
If the `job_template` field is specified, any fields not explicitly provided
|
||||
for the new job (except `name` and `description`) will use the default values
|
||||
from the job template.
|
||||
15
awx/api/templates/api/job_start.md
Normal file
15
awx/api/templates/api/job_start.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Start Job
|
||||
|
||||
Make a GET request to this resource to determine if the job can be started and
|
||||
whether any passwords are required to start the job. The response will include
|
||||
the following fields:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_start`: Password names required to start the job (array, read-only)
|
||||
|
||||
Make a POST request to this resource to start the job. If any passwords are
|
||||
required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the job cannot be
|
||||
started, a 405 status code will be returned.
|
||||
33
awx/api/templates/api/job_template_callback.md
Normal file
33
awx/api/templates/api/job_template_callback.md
Normal file
@@ -0,0 +1,33 @@
|
||||
The job template callback allows for empheral hosts to launch a new job.
|
||||
|
||||
Configure a host to POST to this resource, passing the `host_config_key`
|
||||
parameter, to start a new job limited to only the requesting host. In the
|
||||
examples below, replace the `N` parameter with the `id` of the job template
|
||||
and the `HOST_CONFIG_KEY` with the `host_config_key` associated with the
|
||||
job template.
|
||||
|
||||
For example, using curl:
|
||||
|
||||
curl --data-urlencode host_config_key=HOST_CONFIG_KEY http://server/api/v1/job_templates/N/callback/
|
||||
|
||||
Or using wget:
|
||||
|
||||
wget -O /dev/null --post-data="host_config_key=HOST_CONFIG_KEY" http://server/api/v1/job_templates/N/callback/
|
||||
|
||||
The response will return status 202 if the request is valid, 403 for an
|
||||
invalid host config key, or 400 if the host cannot be determined from the
|
||||
address making the request.
|
||||
|
||||
A GET request may be used to verify that the correct host will be selected.
|
||||
This request must authenticate as a valid user with permission to edit the
|
||||
job template. For example:
|
||||
|
||||
curl http://user:password@server/api/v1/job_templates/N/callback/
|
||||
|
||||
The response will include the host config key as well as the host name(s)
|
||||
that would match the request:
|
||||
|
||||
{
|
||||
"host_config_key": "HOST_CONFIG_KEY",
|
||||
"matching_hosts": ["hostname"]
|
||||
}
|
||||
6
awx/api/templates/api/job_template_jobs_list.md
Normal file
6
awx/api/templates/api/job_template_jobs_list.md
Normal file
@@ -0,0 +1,6 @@
|
||||
{% extends "api/sub_list_create_api_view.md" %}
|
||||
|
||||
{% block post_create %}
|
||||
Any fields not explicitly provided for the new job (except `name` and
|
||||
`description`) will use the default values from the job template.
|
||||
{% endblock %}
|
||||
8
awx/api/templates/api/list_api_view.md
Normal file
8
awx/api/templates/api/list_api_view.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# List {{ model_verbose_name_plural|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve the list of
|
||||
{{ model_verbose_name_plural }}.
|
||||
|
||||
{% include "api/_list_common.md" %}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
12
awx/api/templates/api/list_create_api_view.md
Normal file
12
awx/api/templates/api/list_create_api_view.md
Normal file
@@ -0,0 +1,12 @@
|
||||
{% include "api/list_api_view.md" %}
|
||||
|
||||
# Create {{ model_verbose_name_plural|title }}:
|
||||
|
||||
Make a POST request to this resource with the following {{ model_verbose_name }}
|
||||
fields to create a new {{ model_verbose_name }}:
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
3
awx/api/templates/api/organization_admins_list.md
Normal file
3
awx/api/templates/api/organization_admins_list.md
Normal file
@@ -0,0 +1,3 @@
|
||||
{% with model_verbose_name="admin user" model_verbose_name_plural="admin users" %}
|
||||
{% include "api/sub_list_create_api_view.md" %}
|
||||
{% endwith %}
|
||||
4
awx/api/templates/api/project_playbooks.md
Normal file
4
awx/api/templates/api/project_playbooks.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Retrieve {{ model_verbose_name|title }} Playbooks:
|
||||
|
||||
Make GET request to this resource to retrieve a list of playbooks available
|
||||
for this {{ model_verbose_name }}.
|
||||
13
awx/api/templates/api/project_update_cancel.md
Normal file
13
awx/api/templates/api/project_update_cancel.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Cancel Project Update
|
||||
|
||||
Make a GET request to this resource to determine if the project update can be
|
||||
cancelled. The response will include the following field:
|
||||
|
||||
* `can_cancel`: Indicates whether this update can be canceled (boolean,
|
||||
read-only)
|
||||
|
||||
Make a POST request to this resource to cancel a pending or running project
|
||||
update. The response status code will be 202 if successful, or 405 if the
|
||||
update cannot be canceled.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
18
awx/api/templates/api/project_update_view.md
Normal file
18
awx/api/templates/api/project_update_view.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Update Project
|
||||
|
||||
Make a GET request to this resource to determine if the project can be updated
|
||||
from its SCM source and whether any passwords are required for the update. The
|
||||
response will include the following fields:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_update`: Password names required to update the project
|
||||
(array, read-only)
|
||||
|
||||
Make a POST request to this resource to update the project. If any passwords
|
||||
are required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the project cannot be
|
||||
updated, a 405 status code will be returned.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
9
awx/api/templates/api/retrieve_api_view.md
Normal file
9
awx/api/templates/api/retrieve_api_view.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Retrieve {{ model_verbose_name|title }}:
|
||||
|
||||
Make GET request to this resource to retrieve a single {{ model_verbose_name }}
|
||||
record containing the following fields:
|
||||
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
|
||||
16
awx/api/templates/api/retrieve_update_api_view.md
Normal file
16
awx/api/templates/api/retrieve_update_api_view.md
Normal file
@@ -0,0 +1,16 @@
|
||||
{% include "api/retrieve_api_view.md" %}
|
||||
|
||||
# Update {{ model_verbose_name|title }}:
|
||||
|
||||
Make a PUT or PATCH request to this resource to update this
|
||||
{{ model_verbose_name }}. The following fields may be modified:
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
For a PUT request, include **all** fields in the request.
|
||||
|
||||
For a PATCH request, include only the fields that are being modified.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
20
awx/api/templates/api/retrieve_update_destroy_api_view.md
Normal file
20
awx/api/templates/api/retrieve_update_destroy_api_view.md
Normal file
@@ -0,0 +1,20 @@
|
||||
{% include "api/retrieve_api_view.md" %}
|
||||
|
||||
# Update {{ model_verbose_name|title }}:
|
||||
|
||||
Make a PUT or PATCH request to this resource to update this
|
||||
{{ model_verbose_name }}. The following fields may be modified:
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
For a PUT request, include **all** fields in the request.
|
||||
|
||||
For a PATCH request, include only the fields that are being modified.
|
||||
|
||||
# Delete {{ model_verbose_name|title }}:
|
||||
|
||||
Make a DELETE request to this resource to delete this {{ model_verbose_name }}.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
9
awx/api/templates/api/sub_list_api_view.md
Normal file
9
awx/api/templates/api/sub_list_api_view.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# List {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of
|
||||
{{ model_verbose_name_plural }} associated with the selected
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% include "api/_list_common.md" %}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
39
awx/api/templates/api/sub_list_create_api_view.md
Normal file
39
awx/api/templates/api/sub_list_create_api_view.md
Normal file
@@ -0,0 +1,39 @@
|
||||
{% include "api/sub_list_api_view.md" %}
|
||||
|
||||
# Create {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a POST request to this resource with the following {{ model_verbose_name }}
|
||||
fields to create a new {{ model_verbose_name }} associated with this
|
||||
{{ parent_model_verbose_name }}.
|
||||
|
||||
{% with write_only=1 %}
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
{% endwith %}
|
||||
|
||||
{% block post_create %}{% endblock %}
|
||||
|
||||
{% if parent_key %}
|
||||
# Remove {{ parent_model_verbose_name|title }} {{ model_verbose_name_plural|title }}:
|
||||
|
||||
Make a POST request to this resource with `id` and `disassociate` fields to
|
||||
delete the associated {{ model_verbose_name }}.
|
||||
|
||||
{
|
||||
"id": 123,
|
||||
"disassociate": true
|
||||
}
|
||||
|
||||
{% else %}
|
||||
# Add {{ model_verbose_name_plural|title }} for this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a POST request to this resource with only an `id` field to associate an
|
||||
existing {{ model_verbose_name }} with this {{ parent_model_verbose_name }}.
|
||||
|
||||
# Remove {{ model_verbose_name_plural|title }} from this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a POST request to this resource with `id` and `disassociate` fields to
|
||||
remove the {{ model_verbose_name }} from this {{ parent_model_verbose_name }}
|
||||
without deleting the {{ model_verbose_name }}.
|
||||
{% endif %}
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
@@ -0,0 +1,7 @@
|
||||
# List {{ model_verbose_name_plural|title }} Administered by this {{ parent_model_verbose_name|title }}:
|
||||
|
||||
Make a GET request to this resource to retrieve a list of
|
||||
{{ model_verbose_name_plural }} of which the selected
|
||||
{{ parent_model_verbose_name }} is an admin.
|
||||
|
||||
{% include "api/_list_common.md" %}
|
||||
7
awx/api/templates/api/user_me_list.md
Normal file
7
awx/api/templates/api/user_me_list.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Make a GET request to retrieve user information about the current user.
|
||||
|
||||
One result should be returned containing the following fields:
|
||||
|
||||
{% include "api/_result_fields_common.md" %}
|
||||
|
||||
Use the primary URL for the user (/api/v1/users/N/) to modify the user.
|
||||
170
awx/api/urls.py
Normal file
170
awx/api/urls.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from django.conf.urls import include, patterns, url as original_url
|
||||
|
||||
def url(regex, view, kwargs=None, name=None, prefix=''):
|
||||
# Set default name from view name (if a string).
|
||||
if isinstance(view, basestring) and name is None:
|
||||
name = view
|
||||
return original_url(regex, view, kwargs, name, prefix)
|
||||
|
||||
organization_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'organization_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'organization_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/users/$', 'organization_users_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/admins/$', 'organization_admins_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/inventories/$', 'organization_inventories_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/projects/$', 'organization_projects_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/teams/$', 'organization_teams_list'),
|
||||
)
|
||||
|
||||
user_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'user_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'user_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/teams/$', 'user_teams_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/organizations/$', 'user_organizations_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/admin_of_organizations/$', 'user_admin_of_organizations_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/projects/$', 'user_projects_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/credentials/$', 'user_credentials_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/permissions/$', 'user_permissions_list'),
|
||||
)
|
||||
|
||||
project_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'project_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'project_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/playbooks/$', 'project_playbooks'),
|
||||
url(r'^(?P<pk>[0-9]+)/organizations/$', 'project_organizations_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/teams/$', 'project_teams_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/update/$', 'project_update_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/project_updates/$', 'project_updates_list'),
|
||||
)
|
||||
|
||||
project_update_urls = patterns('awx.api.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'project_update_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/cancel/$', 'project_update_cancel'),
|
||||
)
|
||||
|
||||
team_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'team_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'team_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/projects/$', 'team_projects_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/users/$', 'team_users_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/credentials/$', 'team_credentials_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/permissions/$', 'team_permissions_list'),
|
||||
)
|
||||
|
||||
inventory_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'inventory_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'inventory_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'inventory_hosts_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]+)/variable_data/$', 'inventory_variable_data'),
|
||||
url(r'^(?P<pk>[0-9]+)/script/$', 'inventory_script_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/tree/$', 'inventory_tree_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'inventory_inventory_sources_list'),
|
||||
)
|
||||
|
||||
host_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'host_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'host_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'host_variable_data'),
|
||||
url(r'^(?P<pk>[0-9]+)/groups/$', 'host_groups_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/all_groups/$', 'host_all_groups_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/', 'host_job_events_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'host_job_host_summaries_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'host_inventory_sources_list'),
|
||||
)
|
||||
|
||||
group_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'group_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'group_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/children/$', 'group_children_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'group_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/all_hosts/$', 'group_all_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'group_variable_data'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/$', 'group_job_events_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'group_job_host_summaries_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/potential_children/$', 'group_potential_children_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/inventory_sources/$', 'group_inventory_sources_list'),
|
||||
)
|
||||
|
||||
inventory_source_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'inventory_source_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'inventory_source_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/update/$', 'inventory_source_update_view'),
|
||||
url(r'^(?P<pk>[0-9]+)/inventory_updates/$', 'inventory_source_updates_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/groups/$', 'inventory_source_groups_list'),
|
||||
#url(r'^(?P<pk>[0-9]+)/hosts/$', 'inventory_source_hosts_list'),
|
||||
)
|
||||
|
||||
inventory_update_urls = patterns('awx.api.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'inventory_update_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/cancel/$', 'inventory_update_cancel'),
|
||||
)
|
||||
|
||||
credential_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'credential_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'credential_detail'),
|
||||
# See also credentials resources on users/teams.
|
||||
)
|
||||
|
||||
permission_urls = patterns('awx.api.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'permission_detail'),
|
||||
)
|
||||
|
||||
job_template_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'job_template_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_jobs_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/callback/$', 'job_template_callback'),
|
||||
)
|
||||
|
||||
job_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'job_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/start/$', 'job_start'),
|
||||
url(r'^(?P<pk>[0-9]+)/cancel/$', 'job_cancel'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_host_summaries/$', 'job_job_host_summaries_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/$', 'job_job_events_list'),
|
||||
)
|
||||
|
||||
job_host_summary_urls = patterns('awx.api.views',
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_host_summary_detail'),
|
||||
)
|
||||
|
||||
job_event_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'job_event_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_event_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/children/$', 'job_event_children_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'job_event_hosts_list'),
|
||||
)
|
||||
|
||||
v1_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'api_v1_root_view'),
|
||||
url(r'^config/$', 'api_v1_config_view'),
|
||||
url(r'^authtoken/$', 'auth_token_view'),
|
||||
url(r'^me/$', 'user_me_list'),
|
||||
url(r'^organizations/', include(organization_urls)),
|
||||
url(r'^users/', include(user_urls)),
|
||||
url(r'^projects/', include(project_urls)),
|
||||
url(r'^project_updates/', include(project_update_urls)),
|
||||
url(r'^teams/', include(team_urls)),
|
||||
url(r'^inventories/', include(inventory_urls)),
|
||||
url(r'^hosts/', include(host_urls)),
|
||||
url(r'^groups/', include(group_urls)),
|
||||
url(r'^inventory_sources/', include(inventory_source_urls)),
|
||||
url(r'^inventory_updates/', include(inventory_update_urls)),
|
||||
url(r'^credentials/', include(credential_urls)),
|
||||
url(r'^permissions/', include(permission_urls)),
|
||||
url(r'^job_templates/', include(job_template_urls)),
|
||||
url(r'^jobs/', include(job_urls)),
|
||||
url(r'^job_host_summaries/', include(job_host_summary_urls)),
|
||||
url(r'^job_events/', include(job_event_urls)),
|
||||
)
|
||||
|
||||
urlpatterns = patterns('awx.api.views',
|
||||
url(r'^$', 'api_root_view'),
|
||||
url(r'^v1/', include(v1_urls)),
|
||||
)
|
||||
1059
awx/api/views.py
Normal file
1059
awx/api/views.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user