Merge pull request #541 from matburt/database_config_source

Database config source
This commit is contained in:
Matthew Jones
2016-01-12 11:20:43 -05:00
25 changed files with 1109 additions and 54 deletions

View File

@@ -6,7 +6,6 @@ import urllib
# Django # Django
from django.utils.timezone import now as tz_now from django.utils.timezone import now as tz_now
from django.conf import settings
# Django REST Framework # Django REST Framework
from rest_framework import authentication from rest_framework import authentication
@@ -15,7 +14,7 @@ from rest_framework import HTTP_HEADER_ENCODING
# AWX # AWX
from awx.main.models import UnifiedJob, AuthToken from awx.main.models import UnifiedJob, AuthToken
from awx.main.conf import tower_settings
class TokenAuthentication(authentication.TokenAuthentication): class TokenAuthentication(authentication.TokenAuthentication):
''' '''
@@ -90,7 +89,7 @@ class TokenAuthentication(authentication.TokenAuthentication):
# Token invalidated due to session limit config being reduced # Token invalidated due to session limit config being reduced
# Session limit reached invalidation will also take place on authentication # Session limit reached invalidation will also take place on authentication
if settings.AUTH_TOKEN_PER_USER != -1: if tower_settings.AUTH_TOKEN_PER_USER != -1:
if not token.in_valid_tokens(now=now): if not token.in_valid_tokens(now=now):
token.invalidate(reason='limit_reached') token.invalidate(reason='limit_reached')
raise exceptions.AuthenticationFailed(AuthToken.reason_long('limit_reached')) raise exceptions.AuthenticationFailed(AuthToken.reason_long('limit_reached'))

View File

@@ -144,6 +144,7 @@ class APIView(views.APIView):
'new_in_220': getattr(self, 'new_in_220', False), 'new_in_220': getattr(self, 'new_in_220', False),
'new_in_230': getattr(self, 'new_in_230', False), 'new_in_230': getattr(self, 'new_in_230', False),
'new_in_240': getattr(self, 'new_in_240', False), 'new_in_240': getattr(self, 'new_in_240', False),
'new_in_300': getattr(self, 'new_in_300', False),
} }
def get_description(self, html=False): def get_description(self, html=False):
@@ -160,7 +161,7 @@ class APIView(views.APIView):
''' '''
ret = super(APIView, self).metadata(request) ret = super(APIView, self).metadata(request)
added_in_version = '1.2' added_in_version = '1.2'
for version in ('2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'): for version in ('3.0.0', '2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
if getattr(self, 'new_in_%s' % version.replace('.', ''), False): if getattr(self, 'new_in_%s' % version.replace('.', ''), False):
added_in_version = version added_in_version = version
break break

View File

@@ -38,6 +38,7 @@ from awx.main.constants import SCHEDULEABLE_PROVIDERS
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.utils import get_type_for_model, get_model_for_type, build_url, timestamp_apiformat from awx.main.utils import get_type_for_model, get_model_for_type, build_url, timestamp_apiformat
from awx.main.redact import REPLACE_STR from awx.main.redact import REPLACE_STR
from awx.main.conf import tower_settings
from awx.api.license import feature_enabled from awx.api.license import feature_enabled
@@ -267,7 +268,7 @@ class BaseSerializer(serializers.ModelSerializer):
return choices return choices
def get_url(self, obj): def get_url(self, obj):
if obj is None: if obj is None or not hasattr(obj, 'get_absolute_url'):
return '' return ''
elif isinstance(obj, User): elif isinstance(obj, User):
return reverse('api:user_detail', args=(obj.pk,)) return reverse('api:user_detail', args=(obj.pk,))
@@ -521,8 +522,9 @@ class UnifiedJobSerializer(BaseSerializer):
def get_result_stdout(self, obj): def get_result_stdout(self, obj):
obj_size = obj.result_stdout_size obj_size = obj.result_stdout_size
if obj_size > settings.STDOUT_MAX_BYTES_DISPLAY: if obj_size > tower_settings.STDOUT_MAX_BYTES_DISPLAY:
return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size, settings.STDOUT_MAX_BYTES_DISPLAY) return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size,
tower_settings.STDOUT_MAX_BYTES_DISPLAY)
return obj.result_stdout return obj.result_stdout
class UnifiedJobListSerializer(UnifiedJobSerializer): class UnifiedJobListSerializer(UnifiedJobSerializer):
@@ -569,8 +571,9 @@ class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
def get_result_stdout(self, obj): def get_result_stdout(self, obj):
obj_size = obj.result_stdout_size obj_size = obj.result_stdout_size
if obj_size > settings.STDOUT_MAX_BYTES_DISPLAY: if obj_size > tower_settings.STDOUT_MAX_BYTES_DISPLAY:
return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size, settings.STDOUT_MAX_BYTES_DISPLAY) return "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size,
tower_settings.STDOUT_MAX_BYTES_DISPLAY)
return obj.result_stdout return obj.result_stdout
def get_types(self): def get_types(self):
@@ -2106,6 +2109,39 @@ class ActivityStreamSerializer(BaseSerializer):
last_name = obj.actor.last_name) last_name = obj.actor.last_name)
return summary_fields return summary_fields
class TowerSettingsSerializer(BaseSerializer):
class Meta:
model = TowerSettings
fields = ('key', 'description', 'category', 'value', 'value_type', 'user')
read_only_fields = ('description', 'category', 'value_type', 'user')
def from_native(self, data, files):
if data['key'] not in settings.TOWER_SETTINGS_MANIFEST:
self._errors = {'key': 'Key {0} is not a valid settings key'.format(data['key'])}
return
current_val = TowerSettings.objects.filter(key=data['key'])
if current_val.exists():
current_val.delete()
manifest_val = settings.TOWER_SETTINGS_MANIFEST[data['key']]
data['description'] = manifest_val['description']
data['category'] = manifest_val['category']
data['value_type'] = manifest_val['type']
return super(TowerSettingsSerializer, self).from_native(data, files)
def validate(self, attrs):
manifest = settings.TOWER_SETTINGS_MANIFEST
if attrs['key'] not in manifest:
raise serializers.ValidationError(dict(key=["Key {0} is not a valid settings key".format(attrs['key'])]))
# TODO: Type checking/coercion, contextual validation
return attrs
def save_object(self, obj, **kwargs):
manifest_val = settings.TOWER_SETTINGS_MANIFEST[obj.key]
obj.description = manifest_val['description']
obj.category = manifest_val['category']
obj.value_type = manifest_val['type']
return super(TowerSettingsSerializer, self).save_object(obj, **kwargs)
class AuthTokenSerializer(serializers.Serializer): class AuthTokenSerializer(serializers.Serializer):

View File

@@ -6,3 +6,4 @@
{% if new_in_220 %}> _New in Ansible Tower 2.2.0_{% endif %} {% if new_in_220 %}> _New in Ansible Tower 2.2.0_{% endif %}
{% if new_in_230 %}> _New in Ansible Tower 2.3.0_{% endif %} {% if new_in_230 %}> _New in Ansible Tower 2.3.0_{% endif %}
{% if new_in_240 %}> _New in Ansible Tower 2.4.0_{% endif %} {% if new_in_240 %}> _New in Ansible Tower 2.4.0_{% endif %}
{% if new_in_300 %}> _New in Ansible Tower 3.0.0_{% endif %}

View File

@@ -220,6 +220,10 @@ activity_stream_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/$', 'activity_stream_detail'), url(r'^(?P<pk>[0-9]+)/$', 'activity_stream_detail'),
) )
settings_urls = patterns('awx.api.views',
url(r'^$', 'settings_list'),
url(r'^reset/$', 'settings_reset'))
v1_urls = patterns('awx.api.views', v1_urls = patterns('awx.api.views',
url(r'^$', 'api_v1_root_view'), url(r'^$', 'api_v1_root_view'),
url(r'^ping/$', 'api_v1_ping_view'), url(r'^ping/$', 'api_v1_ping_view'),
@@ -230,7 +234,8 @@ v1_urls = patterns('awx.api.views',
url(r'^dashboard/$', 'dashboard_view'), url(r'^dashboard/$', 'dashboard_view'),
url(r'^dashboard/graphs/jobs/$', 'dashboard_jobs_graph_view'), url(r'^dashboard/graphs/jobs/$', 'dashboard_jobs_graph_view'),
url(r'^dashboard/graphs/inventory/$', 'dashboard_inventory_graph_view'), url(r'^dashboard/graphs/inventory/$', 'dashboard_inventory_graph_view'),
url(r'^schedules/', include(schedule_urls)), url(r'^settings/', include(settings_urls)),
url(r'^schedules/', include(schedule_urls)),
url(r'^organizations/', include(organization_urls)), url(r'^organizations/', include(organization_urls)),
url(r'^users/', include(user_urls)), url(r'^users/', include(user_urls)),
url(r'^projects/', include(project_urls)), url(r'^projects/', include(project_urls)),

View File

@@ -12,6 +12,7 @@ import socket
import sys import sys
import errno import errno
from base64 import b64encode from base64 import b64encode
from collections import namedtuple
# Django # Django
from django.conf import settings from django.conf import settings
@@ -69,6 +70,7 @@ from awx.api.renderers import * # noqa
from awx.api.serializers import * # noqa from awx.api.serializers import * # noqa
from awx.fact.models import * # noqa from awx.fact.models import * # noqa
from awx.main.utils import emit_websocket_notification from awx.main.utils import emit_websocket_notification
from awx.main.conf import tower_settings
def api_exception_handler(exc): def api_exception_handler(exc):
''' '''
@@ -113,6 +115,7 @@ class ApiV1RootView(APIView):
data['authtoken'] = reverse('api:auth_token_view') data['authtoken'] = reverse('api:auth_token_view')
data['ping'] = reverse('api:api_v1_ping_view') data['ping'] = reverse('api:api_v1_ping_view')
data['config'] = reverse('api:api_v1_config_view') data['config'] = reverse('api:api_v1_config_view')
data['settings'] = reverse('api:settings_list')
data['me'] = reverse('api:user_me_list') data['me'] = reverse('api:user_me_list')
data['dashboard'] = reverse('api:dashboard_view') data['dashboard'] = reverse('api:dashboard_view')
data['organizations'] = reverse('api:organization_list') data['organizations'] = reverse('api:organization_list')
@@ -189,9 +192,9 @@ class ApiV1ConfigView(APIView):
'''Return various sitewide configuration settings.''' '''Return various sitewide configuration settings.'''
license_reader = TaskSerializer() license_reader = TaskSerializer()
license_data = license_reader.from_file(show_key=request.user.is_superuser) license_data = license_reader.from_database(show_key=request.user.is_superuser)
pendo_state = settings.PENDO_TRACKING_STATE if settings.PENDO_TRACKING_STATE in ('off', 'anonymous', 'detailed') else 'off' pendo_state = tower_settings.PENDO_TRACKING_STATE if tower_settings.PENDO_TRACKING_STATE in ('off', 'anonymous', 'detailed') else 'off'
data = dict( data = dict(
time_zone=settings.TIME_ZONE, time_zone=settings.TIME_ZONE,
@@ -261,9 +264,7 @@ class ApiV1ConfigView(APIView):
# If the license is valid, write it to disk. # If the license is valid, write it to disk.
if license_data['valid_key']: if license_data['valid_key']:
fh = open(TASK_FILE, "w") tower_settings.LICENSE = data_actual
fh.write(data_actual)
fh.close()
# Spawn a task to ensure that MongoDB is started (or stopped) # Spawn a task to ensure that MongoDB is started (or stopped)
# as appropriate, based on whether the license uses it. # as appropriate, based on whether the license uses it.
@@ -592,7 +593,7 @@ class AuthTokenView(APIView):
# Note: This header is normally added in the middleware whenever an # Note: This header is normally added in the middleware whenever an
# auth token is included in the request header. # auth token is included in the request header.
headers = { headers = {
'Auth-Token-Timeout': int(settings.AUTH_TOKEN_EXPIRATION) 'Auth-Token-Timeout': int(tower_settings.AUTH_TOKEN_EXPIRATION)
} }
return Response({'token': token.key, 'expires': token.expires}, headers=headers) return Response({'token': token.key, 'expires': token.expires}, headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -2859,8 +2860,9 @@ class UnifiedJobStdout(RetrieveAPIView):
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
unified_job = self.get_object() unified_job = self.get_object()
obj_size = unified_job.result_stdout_size obj_size = unified_job.result_stdout_size
if request.accepted_renderer.format != 'txt_download' and obj_size > settings.STDOUT_MAX_BYTES_DISPLAY: if request.accepted_renderer.format != 'txt_download' and obj_size > tower_settings.STDOUT_MAX_BYTES_DISPLAY:
response_message = "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size, settings.STDOUT_MAX_BYTES_DISPLAY) response_message = "Standard Output too large to display (%d bytes), only download supported for sizes over %d bytes" % (obj_size,
tower_settings.STDOUT_MAX_BYTES_DISPLAY)
if request.accepted_renderer.format == 'json': if request.accepted_renderer.format == 'json':
return Response({'range': {'start': 0, 'end': 1, 'absolute_end': 1}, 'content': response_message}) return Response({'range': {'start': 0, 'end': 1, 'absolute_end': 1}, 'content': response_message})
else: else:
@@ -2959,6 +2961,61 @@ class ActivityStreamDetail(RetrieveAPIView):
# Okay, let it through. # Okay, let it through.
return super(type(self), self).get(request, *args, **kwargs) return super(type(self), self).get(request, *args, **kwargs)
class SettingsList(ListCreateAPIView):
model = TowerSettings
serializer_class = TowerSettingsSerializer
authentication_classes = [TokenGetAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
new_in_300 = True
filter_backends = ()
def get_queryset(self):
# TODO: docs
if not self.request.user.is_superuser:
# NOTE: Shortcutting the rbac class due to the merging of the settings manifest and the database
# we'll need to extend this more in the future when we have user settings
return []
SettingsTuple = namedtuple('Settings', ['key', 'description', 'category', 'value', 'value_type', 'user'])
all_defined_settings = {s.key: SettingsTuple(s.key,
s.description,
s.category,
s.value_converted,
s.value_type,
s.user) for s in TowerSettings.objects.all()}
manifest_settings = settings.TOWER_SETTINGS_MANIFEST
settings_actual = []
for settings_key in manifest_settings:
if settings_key in all_defined_settings:
settings_actual.append(all_defined_settings[settings_key])
else:
m_entry = manifest_settings[settings_key]
settings_actual.append(SettingsTuple(settings_key,
m_entry['description'],
m_entry['category'],
m_entry['default'],
m_entry['type'],
None))
return settings_actual
def delete(self, request, *args, **kwargs):
if not request.user.can_access(self.model, 'delete', None):
raise PermissionDenied()
TowerSettings.objects.all().delete()
return Response()
class SettingsReset(APIView):
view_name = "Reset a settings value"
new_in_300 = True
def post(self, request):
# NOTE: Extend more with user settings
if not request.user.can_access(TowerSettings, 'delete', None):
raise PermissionDenied()
settings_key = request.DATA.get('key', None)
if settings_key is not None:
TowerSettings.objects.filter(key=settings_key).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# Create view functions for all of the class-based views to simplify inclusion # Create view functions for all of the class-based views to simplify inclusion
# in URL patterns and reverse URL lookups, converting CamelCase names to # in URL patterns and reverse URL lookups, converting CamelCase names to

View File

@@ -7,7 +7,6 @@ import sys
import logging import logging
# Django # Django
from django.conf import settings
from django.db.models import F, Q from django.db.models import F, Q
from django.contrib.auth.models import User from django.contrib.auth.models import User
@@ -19,6 +18,7 @@ from awx.main.utils import * # noqa
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.api.license import LicenseForbids from awx.api.license import LicenseForbids
from awx.main.task_engine import TaskSerializer from awx.main.task_engine import TaskSerializer
from awx.main.conf import tower_settings
__all__ = ['get_user_queryset', 'check_user_access'] __all__ = ['get_user_queryset', 'check_user_access']
@@ -196,7 +196,7 @@ class UserAccess(BaseAccess):
qs = self.model.objects.filter(is_active=True).distinct() qs = self.model.objects.filter(is_active=True).distinct()
if self.user.is_superuser: if self.user.is_superuser:
return qs return qs
if settings.ORG_ADMINS_CAN_SEE_ALL_USERS and self.user.admin_of_organizations.filter(active=True).exists(): if tower_settings.ORG_ADMINS_CAN_SEE_ALL_USERS and self.user.admin_of_organizations.filter(active=True).exists():
return qs return qs
return qs.filter( return qs.filter(
Q(pk=self.user.pk) | Q(pk=self.user.pk) |
@@ -1566,6 +1566,10 @@ class ActivityStreamAccess(BaseAccess):
ad_hoc_command_qs = self.user.get_queryset(AdHocCommand) ad_hoc_command_qs = self.user.get_queryset(AdHocCommand)
qs.filter(ad_hoc_command__in=ad_hoc_command_qs) qs.filter(ad_hoc_command__in=ad_hoc_command_qs)
# TowerSettings Filter
settings_qs = self.user.get_queryset(TowerSettings)
qs.filter(tower_settings__in=settings_qs)
# organization_qs = self.user.get_queryset(Organization) # organization_qs = self.user.get_queryset(Organization)
# user_qs = self.user.get_queryset(User) # user_qs = self.user.get_queryset(User)
# inventory_qs = self.user.get_queryset(Inventory) # inventory_qs = self.user.get_queryset(Inventory)
@@ -1636,6 +1640,30 @@ class CustomInventoryScriptAccess(BaseAccess):
return True return True
return False return False
class TowerSettingsAccess(BaseAccess):
'''
- I can see settings when
- I am a super user
- I can edit settings when
- I am a super user
- I can clear settings when
- I am a super user
'''
model = TowerSettings
def get_queryset(self):
if self.user.is_superuser:
return self.model.objects.all()
return self.model.objects.none()
def can_change(self, obj, data):
return self.user.is_superuser
def can_delete(self, obj):
return self.user.is_superuser
register_access(User, UserAccess) register_access(User, UserAccess)
register_access(Organization, OrganizationAccess) register_access(Organization, OrganizationAccess)
register_access(Inventory, InventoryAccess) register_access(Inventory, InventoryAccess)
@@ -1661,3 +1689,4 @@ register_access(UnifiedJobTemplate, UnifiedJobTemplateAccess)
register_access(UnifiedJob, UnifiedJobAccess) register_access(UnifiedJob, UnifiedJobAccess)
register_access(ActivityStream, ActivityStreamAccess) register_access(ActivityStream, ActivityStreamAccess)
register_access(CustomInventoryScript, CustomInventoryScriptAccess) register_access(CustomInventoryScript, CustomInventoryScriptAccess)
register_access(TowerSettings, TowerSettingsAccess)

51
awx/main/conf.py Normal file
View File

@@ -0,0 +1,51 @@
# Copyright (c) 2015 Ansible, Inc..
# All Rights Reserved.
import logging
from django.conf import settings as django_settings
from django.db.utils import ProgrammingError
from django.db import OperationalError
from awx.main.models.configuration import TowerSettings
logger = logging.getLogger('awx.main.conf')
class TowerConfiguration(object):
# TODO: Caching so we don't have to hit the database every time for settings
def __getattr__(self, key):
settings_manifest = django_settings.TOWER_SETTINGS_MANIFEST
if key not in settings_manifest:
raise AttributeError("Tower Setting with key '{0}' is not defined in the manifest".format(key))
default_value = settings_manifest[key]['default']
ts = TowerSettings.objects.filter(key=key)
try:
if not ts.exists():
try:
val_actual = getattr(django_settings, key)
except AttributeError:
val_actual = default_value
return val_actual
return ts[0].value_converted
except (ProgrammingError, OperationalError), e:
# Database is not available yet, usually during migrations so lets use the default
logger.debug("Database settings not available yet, using defaults ({0})".format(e))
return default_value
def __setattr__(self, key, value):
settings_manifest = django_settings.TOWER_SETTINGS_MANIFEST
if key not in settings_manifest:
raise AttributeError("Tower Setting with key '{0}' does not exist".format(key))
settings_entry = settings_manifest[key]
settings_actual = TowerSettings.objects.filter(key=key)
if not settings_actual.exists():
settings_actual = TowerSettings(key=key,
description=settings_entry['description'],
category=settings_entry['category'],
value=value,
value_type=settings_entry['type'])
else:
settings_actual['value'] = value
settings_actual.save()
tower_settings = TowerConfiguration()

View File

@@ -28,6 +28,7 @@ from awx.main.models import * # noqa
from awx.main.utils import ignore_inventory_computed_fields, check_proot_installed, wrap_args_with_proot from awx.main.utils import ignore_inventory_computed_fields, check_proot_installed, wrap_args_with_proot
from awx.main.signals import disable_activity_stream from awx.main.signals import disable_activity_stream
from awx.main.task_engine import TaskSerializer as LicenseReader from awx.main.task_engine import TaskSerializer as LicenseReader
from awx.main.conf import tower_settings
logger = logging.getLogger('awx.main.commands.inventory_import') logger = logging.getLogger('awx.main.commands.inventory_import')
@@ -356,7 +357,7 @@ class ExecutableJsonLoader(BaseLoader):
data = {} data = {}
stdout, stderr = '', '' stdout, stderr = '', ''
try: try:
if self.is_custom and getattr(settings, 'AWX_PROOT_ENABLED', False): if self.is_custom and getattr(tower_settings, 'AWX_PROOT_ENABLED', False):
if not check_proot_installed(): if not check_proot_installed():
raise RuntimeError("proot is not installed but is configured for use") raise RuntimeError("proot is not installed but is configured for use")
kwargs = {'proot_temp_dir': self.source_dir} # TODO: Remove proot dir kwargs = {'proot_temp_dir': self.source_dir} # TODO: Remove proot dir

View File

@@ -11,10 +11,10 @@ from django.db import IntegrityError
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.functional import curry from django.utils.functional import curry
from django.conf import settings
from awx import __version__ as version from awx import __version__ as version
from awx.main.models import ActivityStream, Instance from awx.main.models import ActivityStream, Instance
from awx.main.conf import tower_settings
from awx.api.authentication import TokenAuthentication from awx.api.authentication import TokenAuthentication
@@ -117,6 +117,6 @@ class AuthTokenTimeoutMiddleware(object):
if not TokenAuthentication._get_x_auth_token_header(request): if not TokenAuthentication._get_x_auth_token_header(request):
return response return response
response['Auth-Token-Timeout'] = int(settings.AUTH_TOKEN_EXPIRATION) response['Auth-Token-Timeout'] = int(tower_settings.AUTH_TOKEN_EXPIRATION)
return response return response

View File

@@ -0,0 +1,557 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'TowerSettings'
db.create_table(u'main_towersettings', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('created', self.gf('django.db.models.fields.DateTimeField')(default=None)),
('modified', self.gf('django.db.models.fields.DateTimeField')(default=None)),
('key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
('description', self.gf('django.db.models.fields.TextField')()),
('category', self.gf('django.db.models.fields.CharField')(max_length=128)),
('value', self.gf('django.db.models.fields.TextField')()),
('value_type', self.gf('django.db.models.fields.CharField')(max_length=12)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='settings', null=True, to=orm['auth.User'])),
))
db.send_create_signal('main', ['TowerSettings'])
# Adding M2M table for field tower_settings on 'ActivityStream'
m2m_table_name = db.shorten_name(u'main_activitystream_tower_settings')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('activitystream', models.ForeignKey(orm['main.activitystream'], null=False)),
('towersettings', models.ForeignKey(orm['main.towersettings'], null=False))
))
db.create_unique(m2m_table_name, ['activitystream_id', 'towersettings_id'])
def backwards(self, orm):
# Deleting model 'TowerSettings'
db.delete_table(u'main_towersettings')
# Removing M2M table for field tower_settings on 'ActivityStream'
db.delete_table(db.shorten_name(u'main_activitystream_tower_settings'))
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.activitystream': {
'Meta': {'object_name': 'ActivityStream'},
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
'object1': ('django.db.models.fields.TextField', [], {}),
'object2': ('django.db.models.fields.TextField', [], {}),
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'tower_settings': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.TowerSettings']", 'symmetrical': 'False', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'main.adhoccommand': {
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.adhoccommandevent': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
},
'main.authtoken': {
'Meta': {'object_name': 'AuthToken'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
},
'main.credential': {
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'security_token': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
'main.custominventoryscript': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.group': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.instance': {
'Meta': {'object_name': 'Instance'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
},
'main.inventory': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventorysource': {
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'main.inventoryupdate': {
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.job': {
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
},
'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.joborigin': {
'Meta': {'object_name': 'JobOrigin'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
},
'main.jobtemplate': {
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'ordering': "('name',)", 'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': "orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Project']"}),
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
'main.profile': {
'Meta': {'object_name': 'Profile'},
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
'main.project': {
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.projectupdate': {
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.schedule': {
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.systemjob': {
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.systemjobtemplate': {
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.team': {
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': "orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.towersettings': {
'Meta': {'object_name': 'TowerSettings'},
'category': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'description': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'settings'", 'null': 'True', 'to': u"orm['auth.User']"}),
'value': ('django.db.models.fields.TextField', [], {}),
'value_type': ('django.db.models.fields.CharField', [], {'max_length': '12'})
},
'main.unifiedjob': {
'Meta': {'object_name': 'UnifiedJob'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.unifiedjobtemplate': {
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
}
}
complete_apps = ['main']

View File

@@ -16,6 +16,7 @@ from awx.main.models.ad_hoc_commands import * # noqa
from awx.main.models.schedules import * # noqa from awx.main.models.schedules import * # noqa
from awx.main.models.activity_stream import * # noqa from awx.main.models.activity_stream import * # noqa
from awx.main.models.ha import * # noqa from awx.main.models.ha import * # noqa
from awx.main.models.configuration import * # noqa
# Monkeypatch Django serializer to ignore django-taggit fields (which break # Monkeypatch Django serializer to ignore django-taggit fields (which break
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155). # the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
@@ -55,6 +56,7 @@ activity_stream_registrar.connect(Job)
activity_stream_registrar.connect(AdHocCommand) activity_stream_registrar.connect(AdHocCommand)
# activity_stream_registrar.connect(JobHostSummary) # activity_stream_registrar.connect(JobHostSummary)
# activity_stream_registrar.connect(JobEvent) # activity_stream_registrar.connect(JobEvent)
#activity_stream_registrar.connect(Profile) # activity_stream_registrar.connect(Profile)
activity_stream_registrar.connect(Schedule) activity_stream_registrar.connect(Schedule)
activity_stream_registrar.connect(CustomInventoryScript) activity_stream_registrar.connect(CustomInventoryScript)
activity_stream_registrar.connect(TowerSettings)

View File

@@ -53,6 +53,7 @@ class ActivityStream(models.Model):
ad_hoc_command = models.ManyToManyField("AdHocCommand", blank=True) ad_hoc_command = models.ManyToManyField("AdHocCommand", blank=True)
schedule = models.ManyToManyField("Schedule", blank=True) schedule = models.ManyToManyField("Schedule", blank=True)
custom_inventory_script = models.ManyToManyField("CustomInventoryScript", blank=True) custom_inventory_script = models.ManyToManyField("CustomInventoryScript", blank=True)
tower_settings = models.ManyToManyField("TowerSettings", blank=True)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('api:activity_stream_detail', args=(self.pk,)) return reverse('api:activity_stream_detail', args=(self.pk,))

View File

@@ -21,6 +21,7 @@ from jsonfield import JSONField
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.models.unified_jobs import * # noqa from awx.main.models.unified_jobs import * # noqa
from awx.main.utils import decrypt_field from awx.main.utils import decrypt_field
from awx.main.conf import tower_settings
logger = logging.getLogger('awx.main.models.ad_hoc_commands') logger = logging.getLogger('awx.main.models.ad_hoc_commands')
@@ -29,8 +30,8 @@ __all__ = ['AdHocCommand', 'AdHocCommandEvent']
class AdHocCommand(UnifiedJob): class AdHocCommand(UnifiedJob):
MODULE_NAME_CHOICES = [(x,x) for x in settings.AD_HOC_COMMANDS] MODULE_NAME_CHOICES = [(x,x) for x in tower_settings.AD_HOC_COMMANDS]
MODULE_NAME_DEFAULT = 'command' if 'command' in settings.AD_HOC_COMMANDS else None MODULE_NAME_DEFAULT = 'command' if 'command' in tower_settings.AD_HOC_COMMANDS else None
class Meta(object): class Meta(object):
app_label = 'main' app_label = 'main'
@@ -104,7 +105,7 @@ class AdHocCommand(UnifiedJob):
if type(self.module_name) not in (str, unicode): if type(self.module_name) not in (str, unicode):
raise ValidationError("Invalid type for ad hoc command") raise ValidationError("Invalid type for ad hoc command")
module_name = self.module_name.strip() or 'command' module_name = self.module_name.strip() or 'command'
if module_name not in settings.AD_HOC_COMMANDS: if module_name not in tower_settings.AD_HOC_COMMANDS:
raise ValidationError('Unsupported module for ad hoc commands.') raise ValidationError('Unsupported module for ad hoc commands.')
return module_name return module_name

View File

@@ -0,0 +1,62 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import json
# Django
from django.db import models
from django.utils.translation import ugettext_lazy as _
# Tower
from awx.main.models.base import CreatedModifiedModel
class TowerSettings(CreatedModifiedModel):
class Meta:
app_label = 'main'
SETTINGS_TYPE_CHOICES = [
('string', _("String")),
('int', _('Integer')),
('float', _('Decimal')),
('json', _('JSON')),
('bool', _('Boolean')),
('password', _('Password')),
('list', _('List'))
]
key = models.CharField(
max_length=255,
unique=True
)
description = models.TextField()
category = models.CharField(max_length=128)
value = models.TextField()
value_type = models.CharField(
max_length=12,
choices=SETTINGS_TYPE_CHOICES
)
user = models.ForeignKey(
'auth.User',
related_name='settings',
default=None,
null=True,
editable=False,
)
@property
def value_converted(self):
if self.value_type == 'json':
converted_type = json.loads(self.value)
elif self.value_type == 'password':
converted_type = self.value
elif self.value_type == 'list':
converted_type = [x.strip() for x in self.value.split(',')]
elif self.value_type == 'bool':
converted_type = self.value in [True, "true", "True", 1, "1", "yes"]
elif self.value_type == 'string':
converted_type = self.value
else:
t = __builtins__[self.value_type]
converted_type = t(self.value)
return converted_type

View File

@@ -24,6 +24,7 @@ from awx.main.models.unified_jobs import * # noqa
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
from awx.main.utils import emit_websocket_notification from awx.main.utils import emit_websocket_notification
from awx.main.redact import PlainTextCleaner from awx.main.redact import PlainTextCleaner
from awx.main.conf import tower_settings
logger = logging.getLogger('awx.main.models.jobs') logger = logging.getLogger('awx.main.models.jobs')
@@ -318,9 +319,9 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
@property @property
def cache_timeout_blocked(self): def cache_timeout_blocked(self):
if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(settings, 'SCHEDULE_MAX_JOBS', 10): if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(tower_settings, 'SCHEDULE_MAX_JOBS', 10):
logger.error("Job template %s could not be started because there are more than %s other jobs from that template waiting to run" % logger.error("Job template %s could not be started because there are more than %s other jobs from that template waiting to run" %
(self.name, getattr(settings, 'SCHEDULE_MAX_JOBS', 10))) (self.name, getattr(tower_settings, 'SCHEDULE_MAX_JOBS', 10)))
return True return True
return False return False

View File

@@ -18,6 +18,7 @@ from django.utils.translation import ugettext_lazy as _
# AWX # AWX
from awx.main.fields import AutoOneToOneField from awx.main.fields import AutoOneToOneField
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.conf import tower_settings
__all__ = ['Organization', 'Team', 'Permission', 'Profile', 'AuthToken'] __all__ = ['Organization', 'Team', 'Permission', 'Profile', 'AuthToken']
@@ -242,7 +243,7 @@ class AuthToken(BaseModel):
if not now: if not now:
now = tz_now() now = tz_now()
if not self.pk or not self.is_expired(now=now): if not self.pk or not self.is_expired(now=now):
self.expires = now + datetime.timedelta(seconds=settings.AUTH_TOKEN_EXPIRATION) self.expires = now + datetime.timedelta(seconds=tower_settings.AUTH_TOKEN_EXPIRATION)
if save: if save:
self.save() self.save()
@@ -259,12 +260,12 @@ class AuthToken(BaseModel):
if now is None: if now is None:
now = tz_now() now = tz_now()
invalid_tokens = AuthToken.objects.none() invalid_tokens = AuthToken.objects.none()
if settings.AUTH_TOKEN_PER_USER != -1: if tower_settings.AUTH_TOKEN_PER_USER != -1:
invalid_tokens = AuthToken.objects.filter( invalid_tokens = AuthToken.objects.filter(
user=user, user=user,
expires__gt=now, expires__gt=now,
reason='', reason='',
).order_by('-created')[settings.AUTH_TOKEN_PER_USER:] ).order_by('-created')[tower_settings.AUTH_TOKEN_PER_USER:]
return invalid_tokens return invalid_tokens
def generate_key(self): def generate_key(self):
@@ -293,7 +294,7 @@ class AuthToken(BaseModel):
valid_n_tokens_qs = self.user.auth_tokens.filter( valid_n_tokens_qs = self.user.auth_tokens.filter(
expires__gt=now, expires__gt=now,
reason='', reason='',
).order_by('-created')[0:settings.AUTH_TOKEN_PER_USER] ).order_by('-created')[0:tower_settings.AUTH_TOKEN_PER_USER]
valid_n_tokens = valid_n_tokens_qs.values_list('key', flat=True) valid_n_tokens = valid_n_tokens_qs.values_list('key', flat=True)
return bool(self.key in valid_n_tokens) return bool(self.key in valid_n_tokens)

View File

@@ -3,7 +3,6 @@
import logging import logging
from django.conf import settings
from django.db.models.signals import pre_save, post_save, post_delete, m2m_changed from django.db.models.signals import pre_save, post_save, post_delete, m2m_changed
logger = logging.getLogger('awx.main.registrar') logger = logging.getLogger('awx.main.registrar')
@@ -14,7 +13,8 @@ class ActivityStreamRegistrar(object):
self.models = [] self.models = []
def connect(self, model): def connect(self, model):
if not getattr(settings, 'ACTIVITY_STREAM_ENABLED', True): from awx.main.conf import tower_settings
if not getattr(tower_settings, 'ACTIVITY_STREAM_ENABLED', True):
return return
from awx.main.signals import activity_stream_create, activity_stream_update, activity_stream_delete, activity_stream_associate from awx.main.signals import activity_stream_create, activity_stream_update, activity_stream_delete, activity_stream_associate

View File

@@ -8,7 +8,6 @@ import threading
import json import json
# Django # Django
from django.conf import settings
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
from django.dispatch import receiver from django.dispatch import receiver
@@ -22,6 +21,7 @@ from awx.api.serializers import * # noqa
from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore, emit_websocket_notification from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore, emit_websocket_notification
from awx.main.utils import ignore_inventory_computed_fields, ignore_inventory_group_removal, _inventory_updates from awx.main.utils import ignore_inventory_computed_fields, ignore_inventory_group_removal, _inventory_updates
from awx.main.tasks import update_inventory_computed_fields from awx.main.tasks import update_inventory_computed_fields
from awx.main.conf import tower_settings
__all__ = [] __all__ = []
@@ -273,7 +273,7 @@ def update_host_last_job_after_job_deleted(sender, **kwargs):
class ActivityStreamEnabled(threading.local): class ActivityStreamEnabled(threading.local):
def __init__(self): def __init__(self):
self.enabled = getattr(settings, 'ACTIVITY_STREAM_ENABLED', True) self.enabled = getattr(tower_settings, 'ACTIVITY_STREAM_ENABLED', True)
def __nonzero__(self): def __nonzero__(self):
return bool(self.enabled) return bool(self.enabled)
@@ -306,6 +306,7 @@ model_serializer_mapping = {
JobTemplate: JobTemplateSerializer, JobTemplate: JobTemplateSerializer,
Job: JobSerializer, Job: JobSerializer,
AdHocCommand: AdHocCommandSerializer, AdHocCommand: AdHocCommandSerializer,
TowerSettings: TowerSettingsSerializer,
} }
def activity_stream_create(sender, instance, created, **kwargs): def activity_stream_create(sender, instance, created, **kwargs):
@@ -320,7 +321,11 @@ def activity_stream_create(sender, instance, created, **kwargs):
object1=object1, object1=object1,
changes=json.dumps(model_to_dict(instance, model_serializer_mapping))) changes=json.dumps(model_to_dict(instance, model_serializer_mapping)))
activity_entry.save() activity_entry.save()
getattr(activity_entry, object1).add(instance) #TODO: Weird situation where cascade SETNULL doesn't work
# it might actually be a good idea to remove all of these FK references since
# we don't really use them anyway.
if type(instance) is not TowerSettings:
getattr(activity_entry, object1).add(instance)
def activity_stream_update(sender, instance, **kwargs): def activity_stream_update(sender, instance, **kwargs):
if instance.id is None: if instance.id is None:
@@ -347,7 +352,8 @@ def activity_stream_update(sender, instance, **kwargs):
object1=object1, object1=object1,
changes=json.dumps(changes)) changes=json.dumps(changes))
activity_entry.save() activity_entry.save()
getattr(activity_entry, object1).add(instance) if type(instance) is not TowerSettings:
getattr(activity_entry, object1).add(instance)
def activity_stream_delete(sender, instance, **kwargs): def activity_stream_delete(sender, instance, **kwargs):
if not activity_stream_enabled: if not activity_stream_enabled:

View File

@@ -44,6 +44,7 @@ from django.utils.timezone import now
from awx.main.constants import CLOUD_PROVIDERS from awx.main.constants import CLOUD_PROVIDERS
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.queue import FifoQueue from awx.main.queue import FifoQueue
from awx.main.conf import tower_settings
from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url, from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url,
ignore_inventory_computed_fields, emit_websocket_notification, ignore_inventory_computed_fields, emit_websocket_notification,
check_proot_installed, build_proot_temp_dir, wrap_args_with_proot) check_proot_installed, build_proot_temp_dir, wrap_args_with_proot)
@@ -348,7 +349,7 @@ class BaseTask(Task):
python_paths.insert(0, local_site_packages) python_paths.insert(0, local_site_packages)
env['PYTHONPATH'] = os.pathsep.join(python_paths) env['PYTHONPATH'] = os.pathsep.join(python_paths)
if self.should_use_proot: if self.should_use_proot:
env['PROOT_TMP_DIR'] = settings.AWX_PROOT_BASE_PATH env['PROOT_TMP_DIR'] = tower_settings.AWX_PROOT_BASE_PATH
return env return env
def build_safe_env(self, instance, **kwargs): def build_safe_env(self, instance, **kwargs):
@@ -461,7 +462,7 @@ class BaseTask(Task):
instance = self.update_model(instance.pk) instance = self.update_model(instance.pk)
if instance.cancel_flag: if instance.cancel_flag:
try: try:
if settings.AWX_PROOT_ENABLED: if tower_settings.AWX_PROOT_ENABLED:
# NOTE: Refactor this once we get a newer psutil across the board # NOTE: Refactor this once we get a newer psutil across the board
if not psutil: if not psutil:
os.kill(child.pid, signal.SIGKILL) os.kill(child.pid, signal.SIGKILL)
@@ -654,9 +655,9 @@ class RunJob(BaseTask):
''' '''
plugin_dir = self.get_path_to('..', 'plugins', 'callback') plugin_dir = self.get_path_to('..', 'plugins', 'callback')
plugin_dirs = [plugin_dir] plugin_dirs = [plugin_dir]
if hasattr(settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and \ if hasattr(tower_settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and \
settings.AWX_ANSIBLE_CALLBACK_PLUGINS: tower_settings.AWX_ANSIBLE_CALLBACK_PLUGINS:
plugin_dirs.append(settings.AWX_ANSIBLE_CALLBACK_PLUGINS) plugin_dirs.append(tower_settings.AWX_ANSIBLE_CALLBACK_PLUGINS)
plugin_path = ':'.join(plugin_dirs) plugin_path = ':'.join(plugin_dirs)
env = super(RunJob, self).build_env(job, **kwargs) env = super(RunJob, self).build_env(job, **kwargs)
# Set environment variables needed for inventory and job event # Set environment variables needed for inventory and job event
@@ -850,7 +851,7 @@ class RunJob(BaseTask):
''' '''
Return whether this task should use proot. Return whether this task should use proot.
''' '''
return getattr(settings, 'AWX_PROOT_ENABLED', False) return getattr(tower_settings, 'AWX_PROOT_ENABLED', False)
def pre_run_hook(self, job, **kwargs): def pre_run_hook(self, job, **kwargs):
if job.job_type == PERM_INVENTORY_SCAN: if job.job_type == PERM_INVENTORY_SCAN:
@@ -1475,7 +1476,7 @@ class RunAdHocCommand(BaseTask):
''' '''
Return whether this task should use proot. Return whether this task should use proot.
''' '''
return getattr(settings, 'AWX_PROOT_ENABLED', False) return getattr(tower_settings, 'AWX_PROOT_ENABLED', False)
def post_run_hook(self, ad_hoc_command, **kwargs): def post_run_hook(self, ad_hoc_command, **kwargs):
''' '''

View File

@@ -19,3 +19,4 @@ from awx.main.tests.commands import * # noqa
from awx.main.tests.fact import * # noqa from awx.main.tests.fact import * # noqa
from awx.main.tests.unified_jobs import * # noqa from awx.main.tests.unified_jobs import * # noqa
from awx.main.tests.ha import * # noqa from awx.main.tests.ha import * # noqa
from awx.main.tests.settings import * # noqa

105
awx/main/tests/settings.py Normal file
View File

@@ -0,0 +1,105 @@
# Copyright (c) 2016 Ansible, Inc.
# All Rights Reserved.
from awx.main.tests.base import BaseTest
from awx.main.models import * # noqa
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
TEST_TOWER_SETTINGS_MANIFEST = {
"TEST_SETTING_INT": {
"name": "An Integer Field",
"description": "An Integer Field",
"default": 1,
"type": "int",
"category": "test"
},
"TEST_SETTING_STRING": {
"name": "A String Field",
"description": "A String Field",
"default": "test",
"type": "string",
"category": "test"
},
"TEST_SETTING_BOOL": {
"name": "A Bool Field",
"description": "A Bool Field",
"default": True,
"type": "bool",
"category": "test"
},
"TEST_SETTING_LIST": {
"name": "A List Field",
"description": "A List Field",
"default": ["A", "Simple", "List"],
"type": "list",
"category": "test"
}
}
@override_settings(TOWER_SETTINGS_MANIFEST=TEST_TOWER_SETTINGS_MANIFEST)
class SettingsTest(BaseTest):
def setUp(self):
super(SettingsTest, self).setUp()
self.setup_instances()
self.setup_users()
def get_settings(self, expected_count=4):
result = self.get(reverse('api:settings_list'), expect=200)
self.assertEqual(result['count'], expected_count)
return result['results']
def get_individual_setting(self, setting):
all_settings = self.get_settings()
setting_actual = None
for setting_item in all_settings:
if setting_item['key'] == setting:
setting_actual = setting_item
break
self.assertIsNotNone(setting_actual)
return setting_actual
def set_setting(self, key, value):
self.post(reverse('api:settings_list'), data={"key": key, "value": value}, expect=201)
def test_get_settings(self):
# Regular user should see nothing (no user settings yet)
with self.current_user(self.normal_django_user):
self.get_settings(expected_count=0)
# anonymous user should get a 401
self.get(reverse('api:settings_list'), expect=401)
# super user can see everything
with self.current_user(self.super_django_user):
self.get_settings(expected_count=len(TEST_TOWER_SETTINGS_MANIFEST))
def test_set_and_reset_settings(self):
settings_reset = reverse('api:settings_reset')
with self.current_user(self.super_django_user):
# Set and reset a single setting
setting_int = self.get_individual_setting('TEST_SETTING_INT')
self.assertEqual(setting_int['value'], TEST_TOWER_SETTINGS_MANIFEST['TEST_SETTING_INT']['default'])
self.set_setting('TEST_SETTING_INT', 2)
setting_int = self.get_individual_setting('TEST_SETTING_INT')
self.assertEqual(setting_int['value'], 2)
self.post(settings_reset, data={"key": 'TEST_SETTING_INT'}, expect=204)
setting_int = self.get_individual_setting('TEST_SETTING_INT')
self.assertEqual(setting_int['value'], TEST_TOWER_SETTINGS_MANIFEST['TEST_SETTING_INT']['default'])
def test_clear_all_settings(self):
settings_list = reverse('api:settings_list')
with self.current_user(self.super_django_user):
self.set_setting('TEST_SETTING_INT', 2)
self.set_setting('TEST_SETTING_STRING', "foo")
self.set_setting('TEST_SETTING_BOOL', False)
self.set_setting('TEST_SETTING_LIST', [1,2,3])
all_settings = self.get_settings()
for setting_entry in all_settings:
self.assertNotEqual(setting_entry['value'],
TEST_TOWER_SETTINGS_MANIFEST[setting_entry['key']]['default'])
self.delete(settings_list, expect=200)
all_settings = self.get_settings()
for setting_entry in all_settings:
self.assertEqual(setting_entry['value'],
TEST_TOWER_SETTINGS_MANIFEST[setting_entry['key']]['default'])

View File

@@ -16,6 +16,7 @@ from django.test.utils import override_settings
# AWX # AWX
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.tests.base import BaseTest from awx.main.tests.base import BaseTest
from awx.main.conf import tower_settings
__all__ = ['AuthTokenTimeoutTest', 'AuthTokenLimitTest', 'AuthTokenProxyTest', 'UsersTest', 'LdapTest'] __all__ = ['AuthTokenTimeoutTest', 'AuthTokenLimitTest', 'AuthTokenProxyTest', 'UsersTest', 'LdapTest']
@@ -38,7 +39,7 @@ class AuthTokenTimeoutTest(BaseTest):
response = self._generic_rest(dashboard_url, expect=200, method='get', return_response_object=True, client_kwargs=kwargs) response = self._generic_rest(dashboard_url, expect=200, method='get', return_response_object=True, client_kwargs=kwargs)
self.assertIn('Auth-Token-Timeout', response) self.assertIn('Auth-Token-Timeout', response)
self.assertEqual(response['Auth-Token-Timeout'], str(settings.AUTH_TOKEN_EXPIRATION)) self.assertEqual(response['Auth-Token-Timeout'], str(tower_settings.AUTH_TOKEN_EXPIRATION))
class AuthTokenLimitTest(BaseTest): class AuthTokenLimitTest(BaseTest):
def setUp(self): def setUp(self):

View File

@@ -448,8 +448,8 @@ def build_proot_temp_dir():
''' '''
Create a temporary directory for proot to use. Create a temporary directory for proot to use.
''' '''
from django.conf import settings from awx.main.conf import tower_settings
path = tempfile.mkdtemp(prefix='ansible_tower_proot_', dir=settings.AWX_PROOT_BASE_PATH) path = tempfile.mkdtemp(prefix='ansible_tower_proot_', dir=tower_settings.AWX_PROOT_BASE_PATH)
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return path return path
@@ -462,13 +462,14 @@ def wrap_args_with_proot(args, cwd, **kwargs):
- /var/log/supervisor - /var/log/supervisor
- /tmp (except for own tmp files) - /tmp (except for own tmp files)
''' '''
from awx.main.conf import tower_settings
from django.conf import settings from django.conf import settings
new_args = [getattr(settings, 'AWX_PROOT_CMD', 'proot'), '-v', new_args = [getattr(settings, 'AWX_PROOT_CMD', 'proot'), '-v',
str(getattr(settings, 'AWX_PROOT_VERBOSITY', '0')), '-r', '/'] str(getattr(settings, 'AWX_PROOT_VERBOSITY', '0')), '-r', '/']
hide_paths = ['/etc/tower', '/var/lib/awx', '/var/log', hide_paths = ['/etc/tower', '/var/lib/awx', '/var/log',
tempfile.gettempdir(), settings.PROJECTS_ROOT, tempfile.gettempdir(), settings.PROJECTS_ROOT,
settings.JOBOUTPUT_ROOT] settings.JOBOUTPUT_ROOT]
hide_paths.extend(getattr(settings, 'AWX_PROOT_HIDE_PATHS', None) or []) hide_paths.extend(getattr(tower_settings, 'AWX_PROOT_HIDE_PATHS', None) or [])
for path in sorted(set(hide_paths)): for path in sorted(set(hide_paths)):
if not os.path.exists(path): if not os.path.exists(path):
continue continue
@@ -484,7 +485,7 @@ def wrap_args_with_proot(args, cwd, **kwargs):
show_paths = [cwd, kwargs['private_data_dir']] show_paths = [cwd, kwargs['private_data_dir']]
else: else:
show_paths = [cwd] show_paths = [cwd]
show_paths.extend(getattr(settings, 'AWX_PROOT_SHOW_PATHS', None) or []) show_paths.extend(getattr(tower_settings, 'AWX_PROOT_SHOW_PATHS', None) or [])
for path in sorted(set(show_paths)): for path in sorted(set(show_paths)):
if not os.path.exists(path): if not os.path.exists(path):
continue continue

View File

@@ -262,11 +262,11 @@ SERVER_EMAIL = 'root@localhost'
# Default email address to use for various automated correspondence from # Default email address to use for various automated correspondence from
# the site managers. # the site managers.
DEFAULT_FROM_EMAIL = 'webmaster@localhost' DEFAULT_FROM_EMAIL = 'tower@localhost'
# Subject-line prefix for email messages send with django.core.mail.mail_admins # Subject-line prefix for email messages send with django.core.mail.mail_admins
# or ...mail_managers. Make sure to include the trailing space. # or ...mail_managers. Make sure to include the trailing space.
EMAIL_SUBJECT_PREFIX = '[AWX] ' EMAIL_SUBJECT_PREFIX = '[Tower] '
# The email backend to use. For possible shortcuts see django.core.mail. # The email backend to use. For possible shortcuts see django.core.mail.
# The default is to use the SMTP backend. # The default is to use the SMTP backend.
@@ -688,6 +688,141 @@ FACT_CACHE_PORT = 6564
ORG_ADMINS_CAN_SEE_ALL_USERS = True ORG_ADMINS_CAN_SEE_ALL_USERS = True
TOWER_SETTINGS_MANIFEST = {
"SCHEDULE_MAX_JOBS": {
"name": "Maximum Scheduled Jobs",
"description": "Maximum number of the same job template that can be waiting to run when launching from a schedule before no more are created",
"default": SCHEDULE_MAX_JOBS,
"type": "int",
"category": "jobs",
},
"STDOUT_MAX_BYTES_DISPLAY": {
"name": "Standard Output Maximum Display Size",
"description": "Maximum Size of Standard Output in bytes to display before requiring the output be downloaded",
"default": STDOUT_MAX_BYTES_DISPLAY,
"type": "int",
"category": "jobs",
},
"AUTH_TOKEN_EXPIRATION": {
"name": "Idle Time Force Log Out",
"description": "Number of seconds that a user is inactive before they will need to login again",
"type": "int",
"default": AUTH_TOKEN_EXPIRATION,
"category": "authentication",
},
"AUTH_TOKEN_PER_USER": {
"name": "Maximum number of simultaneous logins",
"description": "Maximum number of simultaneous logins a user may have. To disable enter -1",
"type": "int",
"default": AUTH_TOKEN_PER_USER,
"category": "authentication",
},
# "AUTH_BASIC_ENABLED": {
# "name": "Enable HTTP Basic Auth",
# "description": "Enable HTTP Basic Auth for the API Browser",
# "default": AUTH_BASIC_ENABLED,
# "type": "bool",
# "category": "authentication",
# },
# "AUTH_LDAP_SERVER_URI": {
# "name": "LDAP Server URI",
# "description": "URI Location of the LDAP Server",
# "default": AUTH_LDAP_SERVER_URI,
# "type": "string",
# "category": "authentication",
# },
# "RADIUS_SERVER": {
# "name": "Radius Server Host",
# "description": "Host to communicate with for Radius Authentication",
# "default": RADIUS_SERVER,
# "type": "string",
# "category": "authentication",
# },
# "RADIUS_PORT": {
# "name": "Radius Server Port",
# "description": "Port on the Radius host for Radius Authentication",
# "default": RADIUS_PORT,
# "type": "string",
# "category": "authentication",
# },
# "RADIUS_SECRET": {
# "name": "Radius Server Secret",
# "description": "Secret used when negotiating with the Radius server",
# "default": RADIUS_SECRET,
# "type": "string",
# "category": "authentication",
# },
"AWX_PROOT_ENABLED": {
"name": "Enable PRoot for Job Execution",
"description": "Isolates an Ansible job from protected parts of the Tower system to prevent exposing sensitive information",
"default": AWX_PROOT_ENABLED,
"type": "bool",
"category": "jobs",
},
"AWX_PROOT_HIDE_PATHS": {
"name": "Paths to hide from PRoot jobs",
"description": "Extra paths to hide from PRoot isolated processes",
"default": AWX_PROOT_HIDE_PATHS,
"type": "list",
"category": "jobs",
},
"AWX_PROOT_SHOW_PATHS": {
"name": "Paths to expose to PRoot jobs",
"description": "Explicit whitelist of paths to expose to PRoot jobs",
"default": AWX_PROOT_SHOW_PATHS,
"type": "list",
"category": "jobs",
},
"AWX_PROOT_BASE_PATH": {
"name": "Base PRoot execution path",
"description": "The location that PRoot will create its temporary working directory",
"default": AWX_PROOT_BASE_PATH,
"type": "string",
"category": "jobs",
},
"AWX_ANSIBLE_CALLBACK_PLUGINS": {
"name": "Ansible Callback Plugins",
"description": "Colon Seperated Paths for extra callback plugins to be used when running jobs",
"default": AWX_ANSIBLE_CALLBACK_PLUGINS,
"type": "string",
"category": "jobs",
},
"PENDO_TRACKING_STATE": {
"name": "Analytics Tracking State",
"description": "Enable or Disable Analytics Tracking",
"default": PENDO_TRACKING_STATE,
"type": "string",
"category": "ui",
},
"AD_HOC_COMMANDS": {
"name": "Ansible Modules Allowed for Ad Hoc Jobs",
"description": "A colon-seperated whitelist of modules allowed to be used by ad-hoc jobs",
"default": AD_HOC_COMMANDS,
"type": "list",
"category": "jobs",
},
"ACTIVITY_STREAM_ENABLED": {
"name": "Enable Activity Stream",
"description": "Enable capturing activity for the Tower activity stream",
"default": ACTIVITY_STREAM_ENABLED,
"type": "bool",
"category": "system",
},
"ORG_ADMINS_CAN_SEE_ALL_USERS": {
"name": "All Users Visible to Organization Admins",
"description": "Controls whether any Organization Admin can view all users, even those not associated with their Organization",
"default": ORG_ADMINS_CAN_SEE_ALL_USERS,
"type": "bool",
"category": "system",
},
"LICENSE": {
"name": "Tower License",
"description": "Controls what features and functionality is enabled in Tower.",
"default": "{}",
"type": "string",
"category": "system",
},
}
# Logging configuration. # Logging configuration.
LOGGING = { LOGGING = {
'version': 1, 'version': 1,