mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 23:07:42 -02:30
Merge remote-tracking branch 'upstream/release_2.2' into stable
* upstream/release_2.2: (31 commits) Updating changelog for 2.2 release Bump version to 2.2.2 Use smart_str instead of .encode on passwords Change default sensitive redaction behavior Update changelogs for 2.2.1 Add an error message when running a job and trying to use an OpenSSH formatted key on an older version of OpenSSH. Revert "Add sudo: true to the backup playbook" Handle Ubuntu 12.04 psutil recursive cancel fixed log viewer modal partial fixed alignment of download standard out button in modals About Tower version number fix Pin package version for older release add standard out download button to various places in the UI Bump 2.2 branch to version 2.2.1 Don't create a group that is its own parent when an EC2 tag has an empty value. Fixes https://trello.com/c/2zc0odvX Limit max depth when building mapping of group depths to avoid hitting recursion limit. Fixes https://trello.com/c/2zc0odvX Fix .ini web links for v2 Add support for detecting encrypted openssh format private keys. Fixes https://trello.com/c/ZeVOXN5U Fix psutil usage on el6 for job cancel Set recursive on child process canceling ...
This commit is contained in:
@@ -5,7 +5,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
__version__ = '2.2.0'
|
__version__ = '2.2.2'
|
||||||
|
|
||||||
__all__ = ['__version__']
|
__all__ = ['__version__']
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
|||||||
'model_verbose_name_plural': unicode(self.model._meta.verbose_name_plural),
|
'model_verbose_name_plural': unicode(self.model._meta.verbose_name_plural),
|
||||||
})
|
})
|
||||||
d.update({'serializer_fields': self.get_serializer().metadata()})
|
d.update({'serializer_fields': self.get_serializer().metadata()})
|
||||||
|
d['settings'] = settings
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def metadata(self, request):
|
def metadata(self, request):
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ class PlainTextRenderer(renderers.BaseRenderer):
|
|||||||
data = unicode(data)
|
data = unicode(data)
|
||||||
return data.encode(self.charset)
|
return data.encode(self.charset)
|
||||||
|
|
||||||
|
class DownloadTextRenderer(PlainTextRenderer):
|
||||||
|
format = "txt_download"
|
||||||
|
|
||||||
class AnsiTextRenderer(PlainTextRenderer):
|
class AnsiTextRenderer(PlainTextRenderer):
|
||||||
|
|
||||||
media_type = 'text/plain'
|
media_type = 'text/plain'
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class UnifiedJobSerializer(BaseSerializer):
|
class UnifiedJobSerializer(BaseSerializer):
|
||||||
|
|
||||||
result_stdout = serializers.CharField(source='result_stdout', label='result stdout', read_only=True)
|
result_stdout = serializers.SerializerMethodField('get_result_stdout')
|
||||||
unified_job_template = serializers.Field(source='unified_job_template_id', label='unified job template')
|
unified_job_template = serializers.Field(source='unified_job_template_id', label='unified job template')
|
||||||
job_env = serializers.CharField(source='job_env', label='job env', read_only=True)
|
job_env = serializers.CharField(source='job_env', label='job env', read_only=True)
|
||||||
|
|
||||||
@@ -519,6 +519,11 @@ class UnifiedJobSerializer(BaseSerializer):
|
|||||||
ret['elapsed'] = float(ret['elapsed'])
|
ret['elapsed'] = float(ret['elapsed'])
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def get_result_stdout(self, obj):
|
||||||
|
obj_size = obj.result_stdout_size
|
||||||
|
if 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, settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||||
|
return obj.result_stdout
|
||||||
|
|
||||||
class UnifiedJobListSerializer(UnifiedJobSerializer):
|
class UnifiedJobListSerializer(UnifiedJobSerializer):
|
||||||
|
|
||||||
@@ -557,18 +562,27 @@ class UnifiedJobListSerializer(UnifiedJobSerializer):
|
|||||||
|
|
||||||
class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
|
class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
|
||||||
|
|
||||||
|
result_stdout = serializers.SerializerMethodField('get_result_stdout')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('result_stdout',)
|
fields = ('result_stdout',)
|
||||||
|
|
||||||
|
def get_result_stdout(self, obj):
|
||||||
|
obj_size = obj.result_stdout_size
|
||||||
|
if 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, settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||||
|
return obj.result_stdout
|
||||||
|
|
||||||
def get_types(self):
|
def get_types(self):
|
||||||
if type(self) is UnifiedJobStdoutSerializer:
|
if type(self) is UnifiedJobStdoutSerializer:
|
||||||
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job']
|
return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job']
|
||||||
else:
|
else:
|
||||||
return super(UnifiedJobStdoutSerializer, self).get_types()
|
return super(UnifiedJobStdoutSerializer, self).get_types()
|
||||||
|
|
||||||
def to_native(self, obj):
|
# TODO: Needed?
|
||||||
ret = super(UnifiedJobStdoutSerializer, self).to_native(obj)
|
#def to_native(self, obj):
|
||||||
return ret.get('result_stdout', '')
|
# ret = super(UnifiedJobStdoutSerializer, self).to_native(obj)
|
||||||
|
# return ret.get('result_stdout', '')
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(BaseSerializer):
|
class UserSerializer(BaseSerializer):
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ Use the `format` query string parameter to specify the output format.
|
|||||||
* Plain Text: `?format=txt`
|
* Plain Text: `?format=txt`
|
||||||
* Plain Text with ANSI color codes: `?format=ansi`
|
* Plain Text with ANSI color codes: `?format=ansi`
|
||||||
* JSON structure: `?format=json`
|
* JSON structure: `?format=json`
|
||||||
|
* Downloaded Plain Text: `?format=txt_download`
|
||||||
|
|
||||||
(_New in Ansible Tower 2.0.0_) When using the Browsable API, HTML and JSON
|
(_New in Ansible Tower 2.0.0_) When using the Browsable API, HTML and JSON
|
||||||
formats, the `start_line` and `end_line` query string parameters can be used
|
formats, the `start_line` and `end_line` query string parameters can be used
|
||||||
@@ -20,4 +21,7 @@ to specify a range of line numbers to retrieve.
|
|||||||
Use `dark=1` or `dark=0` as a query string parameter to force or disable a
|
Use `dark=1` or `dark=0` as a query string parameter to force or disable a
|
||||||
dark background.
|
dark background.
|
||||||
|
|
||||||
|
+Files over {{ settings.STDOUT_MAX_BYTES_DISPLAY|filesizeformat }} (configurable) will not display in the browser. Use the `txt_download`
|
||||||
|
+format to download the file directly to view it.
|
||||||
|
|
||||||
{% include "api/_new_in_awx.md" %}
|
{% include "api/_new_in_awx.md" %}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ from django.utils.safestring import mark_safe
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.core.servers.basehttp import FileWrapper
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import PermissionDenied, ParseError
|
from rest_framework.exceptions import PermissionDenied, ParseError
|
||||||
@@ -2789,12 +2791,20 @@ class UnifiedJobStdout(RetrieveAPIView):
|
|||||||
serializer_class = UnifiedJobStdoutSerializer
|
serializer_class = UnifiedJobStdoutSerializer
|
||||||
renderer_classes = [BrowsableAPIRenderer, renderers.StaticHTMLRenderer,
|
renderer_classes = [BrowsableAPIRenderer, renderers.StaticHTMLRenderer,
|
||||||
PlainTextRenderer, AnsiTextRenderer,
|
PlainTextRenderer, AnsiTextRenderer,
|
||||||
renderers.JSONRenderer]
|
renderers.JSONRenderer, DownloadTextRenderer]
|
||||||
filter_backends = ()
|
filter_backends = ()
|
||||||
new_in_148 = True
|
new_in_148 = True
|
||||||
|
|
||||||
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
|
||||||
|
if request.accepted_renderer.format != 'txt_download' and 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, settings.STDOUT_MAX_BYTES_DISPLAY)
|
||||||
|
if request.accepted_renderer.format == 'json':
|
||||||
|
return Response({'range': {'start': 0, 'end': 1, 'absolute_end': 1}, 'content': response_message})
|
||||||
|
else:
|
||||||
|
return Response(response_message)
|
||||||
|
|
||||||
if request.accepted_renderer.format in ('html', 'api', 'json'):
|
if request.accepted_renderer.format in ('html', 'api', 'json'):
|
||||||
start_line = request.QUERY_PARAMS.get('start_line', 0)
|
start_line = request.QUERY_PARAMS.get('start_line', 0)
|
||||||
end_line = request.QUERY_PARAMS.get('end_line', None)
|
end_line = request.QUERY_PARAMS.get('end_line', None)
|
||||||
@@ -2820,6 +2830,14 @@ class UnifiedJobStdout(RetrieveAPIView):
|
|||||||
return Response(data)
|
return Response(data)
|
||||||
elif request.accepted_renderer.format == 'ansi':
|
elif request.accepted_renderer.format == 'ansi':
|
||||||
return Response(unified_job.result_stdout_raw)
|
return Response(unified_job.result_stdout_raw)
|
||||||
|
elif request.accepted_renderer.format == 'txt_download':
|
||||||
|
try:
|
||||||
|
content_fd = open(unified_job.result_stdout_file, 'r')
|
||||||
|
response = HttpResponse(FileWrapper(content_fd), content_type='text/plain')
|
||||||
|
response["Content-Disposition"] = 'attachment; filename="job_%s.txt"' % str(unified_job.id)
|
||||||
|
return response
|
||||||
|
except Exception, e:
|
||||||
|
return Response({"error": "Error generating stdout download file: %s" % str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
else:
|
else:
|
||||||
return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs)
|
return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Local versions of third-party packages required by Tower. Package names and
|
|||||||
versions are listed below, along with notes on which files are included.
|
versions are listed below, along with notes on which files are included.
|
||||||
|
|
||||||
amqp==1.4.5 (amqp/*)
|
amqp==1.4.5 (amqp/*)
|
||||||
ansiconv==1.0.0 (ansiconv.py)
|
ansiconv==1.0.0 (ansiconv.py, small fix, generate unicode html)
|
||||||
anyjson==0.3.3 (anyjson/*)
|
anyjson==0.3.3 (anyjson/*)
|
||||||
apache-libcloud==0.15.1 (libcloud/*)
|
apache-libcloud==0.15.1 (libcloud/*)
|
||||||
argparse==1.2.1 (argparse.py, needed for Python 2.6 support)
|
argparse==1.2.1 (argparse.py, needed for Python 2.6 support)
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ def _block_to_html(text):
|
|||||||
classes.append('ansi{0}'.format(code))
|
classes.append('ansi{0}'.format(code))
|
||||||
|
|
||||||
if classes:
|
if classes:
|
||||||
text = '<span class="{0}">{1}</span>'.format(' '.join(classes), text)
|
text = u'<span class="{0}">{1}</span>'.format(' '.join(classes), text)
|
||||||
|
|
||||||
return command, text
|
return command, text
|
||||||
|
|
||||||
|
|||||||
527
awx/main/migrations/0070_v221_changes.py
Normal file
527
awx/main/migrations/0070_v221_changes.py
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import uuid
|
||||||
|
import os
|
||||||
|
|
||||||
|
from south.utils import datetime_utils as datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import DataMigration
|
||||||
|
from django.db import models, connection
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
class Migration(DataMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
for j in orm.UnifiedJob.objects.filter(active=True):
|
||||||
|
cur = connection.cursor()
|
||||||
|
stdout_filename = os.path.join(settings.JOBOUTPUT_ROOT, "%d-%s.out" % (j.pk, str(uuid.uuid1())))
|
||||||
|
fd = open(stdout_filename, 'w')
|
||||||
|
cur.copy_expert("copy (select result_stdout_text from main_unifiedjob where id = %d) to stdout" % j.id, fd)
|
||||||
|
fd.close()
|
||||||
|
j.result_stdout_file = stdout_filename
|
||||||
|
j.result_stdout_text = ""
|
||||||
|
j.save()
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
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'}),
|
||||||
|
'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'}),
|
||||||
|
'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'}),
|
||||||
|
'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'", '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.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']
|
||||||
|
symmetrical = True
|
||||||
@@ -158,7 +158,12 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
ssh_key_data = decrypt_field(self, 'ssh_key_data')
|
ssh_key_data = decrypt_field(self, 'ssh_key_data')
|
||||||
else:
|
else:
|
||||||
ssh_key_data = self.ssh_key_data
|
ssh_key_data = self.ssh_key_data
|
||||||
return 'ENCRYPTED' in ssh_key_data
|
try:
|
||||||
|
key_data = self._validate_ssh_private_key(ssh_key_data)
|
||||||
|
except ValidationError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return bool(key_data['key_enc'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def needs_ssh_key_unlock(self):
|
def needs_ssh_key_unlock(self):
|
||||||
@@ -231,27 +236,52 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
"""Validate that the given SSH private key or certificate is,
|
"""Validate that the given SSH private key or certificate is,
|
||||||
in fact, valid.
|
in fact, valid.
|
||||||
"""
|
"""
|
||||||
cert = ''
|
# Map the X in BEGIN X PRIVATE KEY to the key type (ssh-keygen -t).
|
||||||
|
# Tower jobs using OPENSSH format private keys may still fail if the
|
||||||
|
# system SSH implementation lacks support for this format.
|
||||||
|
key_types = {
|
||||||
|
'RSA': 'rsa',
|
||||||
|
'DSA': 'dsa',
|
||||||
|
'EC': 'ecdsa',
|
||||||
|
'OPENSSH': 'ed25519',
|
||||||
|
'': 'rsa1',
|
||||||
|
}
|
||||||
|
# Key properties to return if valid.
|
||||||
|
key_data = {
|
||||||
|
'key_type': None, # Key type (from above mapping).
|
||||||
|
'key_seg': '', # Key segment (all text including begin/end).
|
||||||
|
'key_b64': '', # Key data as base64.
|
||||||
|
'key_bin': '', # Key data as binary.
|
||||||
|
'key_enc': None, # Boolean, whether key is encrypted.
|
||||||
|
'cert_seg': '', # Cert segment (all text including begin/end).
|
||||||
|
'cert_b64': '', # Cert data as base64.
|
||||||
|
'cert_bin': '', # Cert data as binary.
|
||||||
|
}
|
||||||
data = data.strip()
|
data = data.strip()
|
||||||
validation_error = ValidationError('Invalid private key')
|
validation_error = ValidationError('Invalid private key')
|
||||||
|
|
||||||
# Set up the valid private key header and footer.
|
|
||||||
begin_re = r'(-{4,})\s*BEGIN\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})'
|
|
||||||
end_re = r'(-{4,})\s*END\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})'
|
|
||||||
|
|
||||||
# Sanity check: We may potentially receive a full PEM certificate,
|
# Sanity check: We may potentially receive a full PEM certificate,
|
||||||
# and we want to accept these.
|
# and we want to accept these.
|
||||||
cert_begin_re = r'(-{4,})\s*BEGIN\s+CERTIFICATE\s*(-{4,})'
|
cert_begin_re = r'(-{4,})\s*BEGIN\s+CERTIFICATE\s*(-{4,})'
|
||||||
cert_end_re = r'(-{4,})\s*END\s+CERTIFICATE\s*(-{4,})'
|
cert_end_re = r'(-{4,})\s*END\s+CERTIFICATE\s*(-{4,})'
|
||||||
cert_begin_match = re.search(cert_begin_re, data)
|
cert_begin_match = re.search(cert_begin_re, data)
|
||||||
if cert_begin_match:
|
cert_end_match = re.search(cert_end_re, data)
|
||||||
cert_end_match = re.search(cert_end_re, data)
|
if cert_begin_match and not cert_end_match:
|
||||||
if not cert_end_match:
|
raise validation_error
|
||||||
|
elif not cert_begin_match and cert_end_match:
|
||||||
|
raise validation_error
|
||||||
|
elif cert_begin_match and cert_end_match:
|
||||||
|
cert_dashes = set([cert_begin_match.groups()[0], cert_begin_match.groups()[1],
|
||||||
|
cert_end_match.groups()[0], cert_end_match.groups()[1]])
|
||||||
|
if len(cert_dashes) != 1:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
cert = data[cert_begin_match.start():cert_end_match.end()]
|
key_data['cert_seg'] = data[cert_begin_match.start():cert_end_match.end()]
|
||||||
|
|
||||||
# Find the private key, and also ensure that it internally matches
|
# Find the private key, and also ensure that it internally matches
|
||||||
# itself.
|
# itself.
|
||||||
|
# Set up the valid private key header and footer.
|
||||||
|
begin_re = r'(-{4,})\s*BEGIN\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})'
|
||||||
|
end_re = r'(-{4,})\s*END\s+([A-Z0-9]+)?\s*PRIVATE\sKEY\s*(-{4,})'
|
||||||
begin_match = re.search(begin_re, data)
|
begin_match = re.search(begin_re, data)
|
||||||
end_match = re.search(end_re, data)
|
end_match = re.search(end_re, data)
|
||||||
if not begin_match or not end_match:
|
if not begin_match or not end_match:
|
||||||
@@ -265,18 +295,22 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
raise validation_error
|
raise validation_error
|
||||||
if begin_match.groups()[1] != end_match.groups()[1]:
|
if begin_match.groups()[1] != end_match.groups()[1]:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
line_continues = False
|
key_type = begin_match.groups()[1]
|
||||||
|
try:
|
||||||
|
key_data['key_type'] = key_types[key_type]
|
||||||
|
except KeyError:
|
||||||
|
raise ValidationError('Invalid private key: unsupported type %s' % key_type)
|
||||||
|
|
||||||
# The private key data begins and ends with the private key.
|
# The private key data begins and ends with the private key.
|
||||||
data = data[begin_match.start():end_match.end()]
|
key_data['key_seg'] = data[begin_match.start():end_match.end()]
|
||||||
|
|
||||||
# Establish that we are able to base64 decode the private key;
|
# Establish that we are able to base64 decode the private key;
|
||||||
# if we can't, then it's not a valid key.
|
# if we can't, then it's not a valid key.
|
||||||
#
|
#
|
||||||
# If we got a certificate, validate that also, in the same way.
|
# If we got a certificate, validate that also, in the same way.
|
||||||
header_re = re.compile(r'^(.+?):\s*?(.+?)(\\??)$')
|
header_re = re.compile(r'^(.+?):\s*?(.+?)(\\??)$')
|
||||||
base64_data = ''
|
for segment_name in ('cert', 'key'):
|
||||||
for segment_to_validate in (cert, data):
|
segment_to_validate = key_data['%s_seg' % segment_name]
|
||||||
# If we have nothing; skip this one.
|
# If we have nothing; skip this one.
|
||||||
# We've already validated that we have a private key above,
|
# We've already validated that we have a private key above,
|
||||||
# so we don't need to do it again.
|
# so we don't need to do it again.
|
||||||
@@ -284,6 +318,8 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Ensure that this segment is valid base64 data.
|
# Ensure that this segment is valid base64 data.
|
||||||
|
base64_data = ''
|
||||||
|
line_continues = False
|
||||||
lines = segment_to_validate.splitlines()
|
lines = segment_to_validate.splitlines()
|
||||||
for line in lines[1:-1]:
|
for line in lines[1:-1]:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@@ -301,9 +337,23 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
decoded_data = base64.b64decode(base64_data)
|
decoded_data = base64.b64decode(base64_data)
|
||||||
if not decoded_data:
|
if not decoded_data:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
|
key_data['%s_b64' % segment_name] = base64_data
|
||||||
|
key_data['%s_bin' % segment_name] = decoded_data
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise validation_error
|
raise validation_error
|
||||||
|
|
||||||
|
# Determine if key is encrypted.
|
||||||
|
if key_data['key_type'] == 'ed25519':
|
||||||
|
# See https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L3218
|
||||||
|
# Decoded key data starts with magic string (null-terminated), four byte
|
||||||
|
# length field, followed by the ciphername -- if ciphername is anything
|
||||||
|
# other than 'none' the key is encrypted.
|
||||||
|
key_data['key_enc'] = not bool(key_data['key_bin'].startswith('openssh-key-v1\x00\x00\x00\x00\x04none'))
|
||||||
|
else:
|
||||||
|
key_data['key_enc'] = bool('ENCRYPTED' in key_data['key_seg'])
|
||||||
|
|
||||||
|
return key_data
|
||||||
|
|
||||||
def clean_ssh_key_data(self):
|
def clean_ssh_key_data(self):
|
||||||
if self.pk:
|
if self.pk:
|
||||||
ssh_key_data = decrypt_field(self, 'ssh_key_data')
|
ssh_key_data = decrypt_field(self, 'ssh_key_data')
|
||||||
|
|||||||
@@ -222,6 +222,9 @@ class Inventory(CommonModel):
|
|||||||
|
|
||||||
def update_group_depths(group_pk, current_depth=0):
|
def update_group_depths(group_pk, current_depth=0):
|
||||||
max_depth = group_depths.get(group_pk, -1)
|
max_depth = group_depths.get(group_pk, -1)
|
||||||
|
# Arbitrarily limit depth to avoid hitting Python recursion limit (which defaults to 1000).
|
||||||
|
if current_depth > 100:
|
||||||
|
return
|
||||||
if current_depth > max_depth:
|
if current_depth > max_depth:
|
||||||
group_depths[group_pk] = current_depth
|
group_depths[group_pk] = current_depth
|
||||||
for child_pk in group_children_map.get(group_pk, set()):
|
for child_pk in group_children_map.get(group_pk, set()):
|
||||||
|
|||||||
@@ -353,6 +353,10 @@ class ProjectUpdate(UnifiedJob, ProjectOptions):
|
|||||||
def task_impact(self):
|
def task_impact(self):
|
||||||
return 20
|
return 20
|
||||||
|
|
||||||
|
@property
|
||||||
|
def result_stdout(self):
|
||||||
|
return self._result_stdout_raw(redact_sensitive=True, escape_ascii=True)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:project_update_detail', args=(self.pk,))
|
return reverse('api:project_update_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
|||||||
@@ -551,25 +551,6 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
if 'finished' not in update_fields:
|
if 'finished' not in update_fields:
|
||||||
update_fields.append('finished')
|
update_fields.append('finished')
|
||||||
|
|
||||||
# Take the output from the filesystem and record it in the
|
|
||||||
# database.
|
|
||||||
stdout = self.result_stdout_raw_handle()
|
|
||||||
if not isinstance(stdout, StringIO):
|
|
||||||
self.result_stdout_text = stdout.read()
|
|
||||||
if 'result_stdout_text' not in update_fields:
|
|
||||||
update_fields.append('result_stdout_text')
|
|
||||||
|
|
||||||
# Attempt to delete the job output from the filesystem if it
|
|
||||||
# was moved to the database.
|
|
||||||
if self.result_stdout_file:
|
|
||||||
try:
|
|
||||||
os.remove(self.result_stdout_file)
|
|
||||||
self.result_stdout_file = ''
|
|
||||||
if 'result_stdout_file' not in update_fields:
|
|
||||||
update_fields.append('result_stdout_file')
|
|
||||||
except:
|
|
||||||
pass # Meh. We don't care that much.
|
|
||||||
|
|
||||||
# If we have a start and finished time, and haven't already calculated
|
# If we have a start and finished time, and haven't already calculated
|
||||||
# out the time that elapsed, do so.
|
# out the time that elapsed, do so.
|
||||||
if self.started and self.finished and not self.elapsed:
|
if self.started and self.finished and not self.elapsed:
|
||||||
@@ -617,7 +598,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
if self.result_stdout_text:
|
if self.result_stdout_text:
|
||||||
return StringIO(self.result_stdout_text)
|
return StringIO(self.result_stdout_text)
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(self.result_stdout_file):
|
if not os.path.exists(self.result_stdout_file) or os.stat(self.result_stdout_file).st_size < 1:
|
||||||
return StringIO(msg['missing' if self.finished else 'pending'])
|
return StringIO(msg['missing' if self.finished else 'pending'])
|
||||||
|
|
||||||
# There is a potential timing issue here, because another
|
# There is a potential timing issue here, because another
|
||||||
@@ -641,7 +622,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
ansi_escape = re.compile(r'\x1b[^m]*m')
|
ansi_escape = re.compile(r'\x1b[^m]*m')
|
||||||
return ansi_escape.sub('', content)
|
return ansi_escape.sub('', content)
|
||||||
|
|
||||||
def _result_stdout_raw(self, redact_sensitive=True, escape_ascii=False):
|
def _result_stdout_raw(self, redact_sensitive=False, escape_ascii=False):
|
||||||
content = self.result_stdout_raw_handle().read()
|
content = self.result_stdout_raw_handle().read()
|
||||||
if redact_sensitive:
|
if redact_sensitive:
|
||||||
content = UriCleaner.remove_sensitive(content)
|
content = UriCleaner.remove_sensitive(content)
|
||||||
@@ -657,6 +638,13 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
def result_stdout(self):
|
def result_stdout(self):
|
||||||
return self._result_stdout_raw(escape_ascii=True)
|
return self._result_stdout_raw(escape_ascii=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def result_stdout_size(self):
|
||||||
|
try:
|
||||||
|
return os.stat(self.result_stdout_file).st_size
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|
||||||
def _result_stdout_raw_limited(self, start_line=0, end_line=None, redact_sensitive=True, escape_ascii=False):
|
def _result_stdout_raw_limited(self, start_line=0, end_line=None, redact_sensitive=True, escape_ascii=False):
|
||||||
return_buffer = u""
|
return_buffer = u""
|
||||||
if end_line is not None:
|
if end_line is not None:
|
||||||
@@ -848,3 +836,4 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
if settings.BROKER_URL.startswith('amqp://'):
|
if settings.BROKER_URL.startswith('amqp://'):
|
||||||
self._force_cancel()
|
self._force_cancel()
|
||||||
return self.cancel_flag
|
return self.cancel_flag
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import uuid
|
|||||||
from distutils.version import LooseVersion as Version
|
from distutils.version import LooseVersion as Version
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import yaml
|
import yaml
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
except:
|
||||||
|
psutil = None
|
||||||
|
|
||||||
# Pexpect
|
# Pexpect
|
||||||
import pexpect
|
import pexpect
|
||||||
@@ -50,6 +54,12 @@ __all__ = ['RunJob', 'RunSystemJob', 'RunProjectUpdate', 'RunInventoryUpdate',
|
|||||||
|
|
||||||
HIDDEN_PASSWORD = '**********'
|
HIDDEN_PASSWORD = '**********'
|
||||||
|
|
||||||
|
OPENSSH_KEY_ERROR = u'''\
|
||||||
|
It looks like you're trying to use a private key in OpenSSH format, which \
|
||||||
|
isn't supported by the installed version of OpenSSH on this Tower instance. \
|
||||||
|
Try upgrading OpenSSH or providing your private key in an different format. \
|
||||||
|
'''
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.tasks')
|
logger = logging.getLogger('awx.main.tasks')
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
@@ -283,6 +293,12 @@ class BaseTask(Task):
|
|||||||
if private_data is not None:
|
if private_data is not None:
|
||||||
ssh_ver = get_ssh_version()
|
ssh_ver = get_ssh_version()
|
||||||
ssh_too_old = True if ssh_ver == "unknown" else Version(ssh_ver) < Version("6.0")
|
ssh_too_old = True if ssh_ver == "unknown" else Version(ssh_ver) < Version("6.0")
|
||||||
|
openssh_keys_supported = ssh_ver != "unknown" and Version(ssh_ver) >= Version("6.5")
|
||||||
|
for name, data in private_data.iteritems():
|
||||||
|
# Bail out now if a private key was provided in OpenSSH format
|
||||||
|
# and we're running an earlier version (<6.5).
|
||||||
|
if 'OPENSSH PRIVATE KEY' in data and not openssh_keys_supported:
|
||||||
|
raise RuntimeError(OPENSSH_KEY_ERROR)
|
||||||
for name, data in private_data.iteritems():
|
for name, data in private_data.iteritems():
|
||||||
# For credentials used with ssh-add, write to a named pipe which
|
# For credentials used with ssh-add, write to a named pipe which
|
||||||
# will be read then closed, instead of leaving the SSH key on disk.
|
# will be read then closed, instead of leaving the SSH key on disk.
|
||||||
@@ -432,7 +448,23 @@ 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:
|
||||||
os.kill(child.pid, signal.SIGINT)
|
if settings.AWX_PROOT_ENABLED:
|
||||||
|
# NOTE: Refactor this once we get a newer psutil across the board
|
||||||
|
if not psutil:
|
||||||
|
os.kill(child.pid, signal.SIGKILL)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
main_proc = psutil.Process(pid=child.pid)
|
||||||
|
if hasattr(main_proc, "children"):
|
||||||
|
child_procs = main_proc.children(recursive=True)
|
||||||
|
else:
|
||||||
|
child_procs = main_proc.get_children(recursive=True)
|
||||||
|
for child_proc in child_procs:
|
||||||
|
os.kill(child_proc.pid, signal.SIGTERM)
|
||||||
|
except TypeError:
|
||||||
|
os.kill(child.pid, signal.SIGKILL)
|
||||||
|
else:
|
||||||
|
os.kill(child.pid, signal.SIGTERM)
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
canceled = True
|
canceled = True
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -493,7 +525,7 @@ class BaseTask(Task):
|
|||||||
safe_env = self.build_safe_env(instance, **kwargs)
|
safe_env = self.build_safe_env(instance, **kwargs)
|
||||||
if not os.path.exists(settings.JOBOUTPUT_ROOT):
|
if not os.path.exists(settings.JOBOUTPUT_ROOT):
|
||||||
os.makedirs(settings.JOBOUTPUT_ROOT)
|
os.makedirs(settings.JOBOUTPUT_ROOT)
|
||||||
stdout_filename = os.path.join(settings.JOBOUTPUT_ROOT, str(uuid.uuid1()) + ".out")
|
stdout_filename = os.path.join(settings.JOBOUTPUT_ROOT, "%d-%s.out" % (pk, str(uuid.uuid1())))
|
||||||
stdout_handle = codecs.open(stdout_filename, 'w', encoding='utf-8')
|
stdout_handle = codecs.open(stdout_filename, 'w', encoding='utf-8')
|
||||||
if self.should_use_proot(instance, **kwargs):
|
if self.should_use_proot(instance, **kwargs):
|
||||||
if not check_proot_installed():
|
if not check_proot_installed():
|
||||||
|
|||||||
@@ -99,6 +99,19 @@ lb[01:09:2].example.us even_odd=odd
|
|||||||
media[0:9][0:9].example.cc
|
media[0:9][0:9].example.cc
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
TEST_INVENTORY_INI_WITH_RECURSIVE_GROUPS = '''\
|
||||||
|
[family:children]
|
||||||
|
parent
|
||||||
|
|
||||||
|
[parent:children]
|
||||||
|
child
|
||||||
|
|
||||||
|
[child:children]
|
||||||
|
grandchild
|
||||||
|
|
||||||
|
[grandchild:children]
|
||||||
|
parent
|
||||||
|
'''
|
||||||
|
|
||||||
class BaseCommandMixin(object):
|
class BaseCommandMixin(object):
|
||||||
'''
|
'''
|
||||||
@@ -974,6 +987,16 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
|
|||||||
source=self.ini_path)
|
source=self.ini_path)
|
||||||
self.assertTrue(isinstance(result, ValueError), result)
|
self.assertTrue(isinstance(result, ValueError), result)
|
||||||
|
|
||||||
|
def test_ini_file_with_recursive_groups(self):
|
||||||
|
self.create_test_ini(ini_content=TEST_INVENTORY_INI_WITH_RECURSIVE_GROUPS)
|
||||||
|
new_inv = self.organizations[0].inventories.create(name='new')
|
||||||
|
self.assertEqual(new_inv.hosts.count(), 0)
|
||||||
|
self.assertEqual(new_inv.groups.count(), 0)
|
||||||
|
result, stdout, stderr = self.run_command('inventory_import',
|
||||||
|
inventory_id=new_inv.pk,
|
||||||
|
source=self.ini_path)
|
||||||
|
self.assertEqual(result, None, stdout + stderr)
|
||||||
|
|
||||||
def test_executable_file(self):
|
def test_executable_file(self):
|
||||||
# Use existing inventory as source.
|
# Use existing inventory as source.
|
||||||
old_inv = self.inventories[1]
|
old_inv = self.inventories[1]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from django.utils.timezone import now
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
from awx.main.tests.base import BaseTransactionTest
|
from awx.main.tests.base import BaseTransactionTest
|
||||||
from awx.main.tests.tasks import TEST_SSH_KEY_DATA, TEST_SSH_KEY_DATA_LOCKED, TEST_SSH_KEY_DATA_UNLOCK
|
from awx.main.tests.tasks import TEST_SSH_KEY_DATA, TEST_SSH_KEY_DATA_LOCKED, TEST_SSH_KEY_DATA_UNLOCK, TEST_OPENSSH_KEY_DATA, TEST_OPENSSH_KEY_DATA_LOCKED
|
||||||
from awx.main.utils import decrypt_field, update_scm_url
|
from awx.main.utils import decrypt_field, update_scm_url
|
||||||
|
|
||||||
TEST_PLAYBOOK = '''- hosts: mygroup
|
TEST_PLAYBOOK = '''- hosts: mygroup
|
||||||
@@ -578,6 +578,20 @@ class ProjectsTest(BaseTransactionTest):
|
|||||||
data['ssh_key_data'] = TEST_SSH_KEY_DATA
|
data['ssh_key_data'] = TEST_SSH_KEY_DATA
|
||||||
self.post(url, data, expect=201)
|
self.post(url, data, expect=201)
|
||||||
|
|
||||||
|
# Test with OpenSSH format private key.
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
data = dict(name='openssh-unlocked', user=self.super_django_user.pk, kind='ssh',
|
||||||
|
ssh_key_data=TEST_OPENSSH_KEY_DATA)
|
||||||
|
self.post(url, data, expect=201)
|
||||||
|
|
||||||
|
# Test with OpenSSH format private key that requires passphrase.
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
data = dict(name='openssh-locked', user=self.super_django_user.pk, kind='ssh',
|
||||||
|
ssh_key_data=TEST_OPENSSH_KEY_DATA_LOCKED)
|
||||||
|
self.post(url, data, expect=400)
|
||||||
|
data['ssh_key_unlock'] = TEST_SSH_KEY_DATA_UNLOCK
|
||||||
|
self.post(url, data, expect=201)
|
||||||
|
|
||||||
# Test post as organization admin where team is part of org, but user
|
# Test post as organization admin where team is part of org, but user
|
||||||
# creating credential is not a member of the team. UI may pass user
|
# creating credential is not a member of the team. UI may pass user
|
||||||
# as an empty string instead of None.
|
# as an empty string instead of None.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
|
from distutils.version import LooseVersion as Version
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -289,6 +290,65 @@ wwoi+P4JlJF6ZuhuDv6mhmBCSdXdc1bvimvdpOljhThr+cG5mM08iqWGKdA665cw
|
|||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
TEST_OPENSSH_KEY_DATA = '''-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||||
|
NhAAAAAwEAAQAAAQEA1AZAwUJUiLmOXjbO5q2ZE5DF+gMpPKe8NEr12FpvOaJr1Nz/DNpf
|
||||||
|
FE/VbssOJ4CRD/6MItlPSG2pC1Cv3AYSL7NBc0YCMlBR/P/nLI8pLAzU3p3KRYvR+R6cMW
|
||||||
|
3nMcxyB1UUgzXY9dTVFIyejOsm7stGuNfdDTTLBE2vTDz6CyzxxSALEOdYut5cfeTUuG7d
|
||||||
|
nP01K3JiaHjHaXDmwraRR/JlitylaZUnSZ+/b9WCMX5FyeJ6CnGdvcCuvMK0iNjZ8R+PxP
|
||||||
|
xJBM5AlJC5J6qa8YmeaQ6lA/2S+/wGuhJmocmiXiLFy9IzIPnQiR+h8DqStp4xp245UQxe
|
||||||
|
TIGSMmq8DQAAA9A4FMRSOBTEUgAAAAdzc2gtcnNhAAABAQDUBkDBQlSIuY5eNs7mrZkTkM
|
||||||
|
X6Ayk8p7w0SvXYWm85omvU3P8M2l8UT9Vuyw4ngJEP/owi2U9IbakLUK/cBhIvs0FzRgIy
|
||||||
|
UFH8/+csjyksDNTencpFi9H5HpwxbecxzHIHVRSDNdj11NUUjJ6M6ybuy0a4190NNMsETa
|
||||||
|
9MPPoLLPHFIAsQ51i63lx95NS4bt2c/TUrcmJoeMdpcObCtpFH8mWK3KVplSdJn79v1YIx
|
||||||
|
fkXJ4noKcZ29wK68wrSI2NnxH4/E/EkEzkCUkLknqprxiZ5pDqUD/ZL7/Aa6EmahyaJeIs
|
||||||
|
XL0jMg+dCJH6HwOpK2njGnbjlRDF5MgZIyarwNAAAAAwEAAQAAAQAp8orBMYRUAJIgJavN
|
||||||
|
i67rZgslKZbw/yaHGgWFpm628mFvHcIAIvwIorrRTq8gNZl9lpjXFDNRWxDEwlPorfLPKS
|
||||||
|
Hb0pAAsE9oRKDR+gjlRCyhVop8M+t45At25A2HlrFArh5+zxp7mH4HsMJ1ktiDCgiV7W84
|
||||||
|
e6dm1I/H/5BgwUlTNoVOGPrU183gqRsHIICjfmnjl2ObJoly+MTrAy7E9rSmsO+pHKl8z0
|
||||||
|
XODWh3mo+EkCoYrK6kP96Jy3BepSmbZMROEsctS7Mkzu6QdnfTY3QqIzENYtTGJuAGktGj
|
||||||
|
su4MHP8hbj+TznNkFeZdmIC0uTnIKu1uquwuFF1HPZiBAAAAgACX9xPKS2J04WXpQag+JS
|
||||||
|
06n2zSuBHW7Kq4q/LMydoTRd8Quf6u6eivSBrl7H779LCtGCIZqJAslvWOyPyz2CohcCBU
|
||||||
|
emubiHcUA+aN7R9E0tyitwWraJjMIwpQ7+AbgdsLsuxozNeccSrr0tva2c5y9x7YGBcIdC
|
||||||
|
UJDt4xnBi7AAAAgQDz771v8Mb18kq5W+inDcYPFUNXGtNfeYZEOhYFpxunFnYwTEAG0Xnh
|
||||||
|
YpQXOAFZ2q5mkFQHMl4cOKwoAlaP0dM4v0JKPjFDLvGisEu95fnivj4YAMP/UHgKKxBbqW
|
||||||
|
HPUhg3adAmIJ9z9u/VmTErbVklcKWlyZuTUkxeQ/BJmSIRUQAAAIEA3oKAzdDURjy8zxLX
|
||||||
|
gBLCPdi8AxCiqQJBCsGxXCgKtZewset1XJHIN9ryfb4QSZFkSOlm/LgdeGtS8Or0GNPRYd
|
||||||
|
hgnUCF0LkEsDQ7HzPZYujLrAwjumvGQH6ORp5vRh0tQb93o4e1/A2vpdSKeH7gCe/jfUSY
|
||||||
|
h7dFGNoAI4cF7/0AAAAUcm9vdEBwaWxsb3cuaXhtbS5uZXQBAgMEBQYH
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
|
'''
|
||||||
|
|
||||||
|
TEST_OPENSSH_KEY_DATA_LOCKED = '''-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABALaWMfjc
|
||||||
|
hSvC7aXxQs1ZDiAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDEDWKwZD8+
|
||||||
|
h+2gZZKna8dy2QL4jJxM1eLGDcQDnuip1ixhaf5MT5T6BMploXXHs1pfuwx8yTQ6Ts/VJp
|
||||||
|
WX6cuHQg8sPGM3P7HNGUqs9q/EQfrrRxz555uL08CRaS6FjM/6x9iolNhHU910Wlg+R+ZS
|
||||||
|
xiMrrY/s03EiEChsAWTbwBGqTopGC2xMFgIxINoQtTFXv7MtCbDfl8aWKQRDmzkLvwT07N
|
||||||
|
ycj2kqADqoukD/2bQvPrW6FIZPJPpAdeAe2SZbf/y92NgVz/glOdtjaJp3oqn1QHrOA9/k
|
||||||
|
XgXOjgVQUbzX7qyLWenxM138VsRKUJZeROaHt1MWApLrLtKQ36SrAAAD0A+PODJjfeKm3U
|
||||||
|
JknlSYD7fFh6bVZGwG6LnLMtobs0elOfj2+sdg+hOVqyrA0rPOHES5yGKslTc/wRkRQ95m
|
||||||
|
dBleAyTDIOQ90IqDxT3lsNQwpscsFKPYKGmaUvZLLk4aNY1GeANtByXwTsjetVqn8Uo59A
|
||||||
|
zu6phX8Aagn2h0qxQwBnDjlzsXf6g5H7UPZd/t1dYr1NfVP6KWJrg0jivAI8tzO2HcM9W2
|
||||||
|
cyOaodBw/6TsJNKvDV714Z+apvrNDEufBUsovKjAna2BDVZIhTCg5mYm0Dks8JStQrG2S1
|
||||||
|
Yk8EM3+fpo8uMoHVz1jbYC8UX12pwIU67MhUn24KBxqulCYaTMsrLFkNWk6vKgwib+sIa4
|
||||||
|
i1Bij1Zd0rdJWypQqTc2Oj3bBSYM47AksMXcKVpuNnFLh4+eokpQzbtIYpRqhOTh1Fky7z
|
||||||
|
xkhTgWVvf/F19M9t1bz3Rm1/t5I75Ag9qfKWs06j+VVfXnDt5v5hYAEhoJjMzSjgKaqc5g
|
||||||
|
YndeWeUwO6Vijt4XpkB8+0R7Kptsh9L0UUsNIcRoGcqrM8IUVb3D8vPWppPlj9d6LB+FCo
|
||||||
|
Cy1JlscnpBb8AQy9QMvrJTHKOyjRcenVxILPiN8PypIC008jvqpDzKimAxM4IMuA7AWE6w
|
||||||
|
j5+CzfUhDAJGdl2qH/nVc7GFUtz8bVA/v9Zkawg2MLcafgGollbLcTbKwDFcenQuyHT+Hj
|
||||||
|
uDm2f0oV/EDKFqLijlV8vcLBNUZoxY/L62Vora1jlqnapq2Z/AM9NicoELYNe21ReJ5dxM
|
||||||
|
7Pk/QdSrZjQzxoHf8uBDpb7x/KyfnSdf8GmdGCxoJ5mcepwD4tROMFC104tN0STJpdGVSm
|
||||||
|
Q5ZG1JDN7F9iJCCAwyulWH/XxTzFYnQ84199cQeV/M9rXXgbXa8ApAung6X9j8y1fcw9Lw
|
||||||
|
wV1aP06bCNgM0U50PiZ54HXwzVt+Ghs06TEF4/ZQiIgNJxdw0HFxAJj8qHqUCHuSmvBgnN
|
||||||
|
qRW/uruItwpXLaL00EHu7rAFlBi1BnnetI+D12ls04mlyTUFFM5v520B5zPV+5If2hx91w
|
||||||
|
C6Oxl1Wsp3gPkK2yiuy8qcrvoEoJ25TeEhUGEAPWx2OuQJO/Lpq9aF/JJoqGwnBaXdCsi+
|
||||||
|
5ig+ZMq5GKQtyydzyXImjlNEUH1w2prRDiGVEufANA5LSLCtqOLgDzXS62WUBjJBrQJVAM
|
||||||
|
YpWz1tiZQoyv1RT3Y0O0Vwe2Z5AK3fVM0I5jWdiLrIErtcR4ULa6T56QtA52DufhKzINTR
|
||||||
|
Vg9TtUBqfKIpRQikPSjm7vpY/Xnbc=
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
|
'''
|
||||||
|
|
||||||
TEST_SSH_CERT_KEY = """-----BEGIN CERTIFICATE-----
|
TEST_SSH_CERT_KEY = """-----BEGIN CERTIFICATE-----
|
||||||
MIIDNTCCAh2gAwIBAgIBATALBgkqhkiG9w0BAQswSTEWMBQGA1UEAwwNV2luZG93
|
MIIDNTCCAh2gAwIBAgIBATALBgkqhkiG9w0BAQswSTEWMBQGA1UEAwwNV2luZG93
|
||||||
cyBBenVyZTELMAkGA1UEBhMCVVMxIjAgBgkqhkiG9w0BCQEWE2x1a2VAc25lZXJp
|
cyBBenVyZTELMAkGA1UEBhMCVVMxIjAgBgkqhkiG9w0BCQEWE2x1a2VAc25lZXJp
|
||||||
@@ -1031,6 +1091,24 @@ class RunJobTest(BaseJobExecutionTest):
|
|||||||
self.assertFalse('"--private-key=' in job.job_args)
|
self.assertFalse('"--private-key=' in job.job_args)
|
||||||
self.assertTrue('ssh-agent' in job.job_args)
|
self.assertTrue('ssh-agent' in job.job_args)
|
||||||
|
|
||||||
|
def test_openssh_key_format(self):
|
||||||
|
ssh_ver = get_ssh_version()
|
||||||
|
openssh_keys_supported = ssh_ver != "unknown" and Version(ssh_ver) >= Version("6.5")
|
||||||
|
self.create_test_credential(ssh_key_data=TEST_OPENSSH_KEY_DATA)
|
||||||
|
self.create_test_project(TEST_PLAYBOOK)
|
||||||
|
job_template = self.create_test_job_template()
|
||||||
|
job = self.create_test_job(job_template=job_template)
|
||||||
|
self.assertEqual(job.status, 'new')
|
||||||
|
self.assertFalse(job.passwords_needed_to_start)
|
||||||
|
self.assertTrue(job.signal_start())
|
||||||
|
job = Job.objects.get(pk=job.pk)
|
||||||
|
if openssh_keys_supported:
|
||||||
|
self.check_job_result(job, 'successful')
|
||||||
|
self.assertFalse('"--private-key=' in job.job_args)
|
||||||
|
self.assertTrue('ssh-agent' in job.job_args)
|
||||||
|
else:
|
||||||
|
self.check_job_result(job, 'error', expect_traceback=True)
|
||||||
|
|
||||||
def test_locked_ssh_key_with_password(self):
|
def test_locked_ssh_key_with_password(self):
|
||||||
self.create_test_credential(ssh_key_data=TEST_SSH_KEY_DATA_LOCKED,
|
self.create_test_credential(ssh_key_data=TEST_SSH_KEY_DATA_LOCKED,
|
||||||
ssh_key_unlock=TEST_SSH_KEY_DATA_UNLOCK)
|
ssh_key_unlock=TEST_SSH_KEY_DATA_UNLOCK)
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class UnifiedJobsUnitTest(SimpleTestCase):
|
|||||||
unified_job = UnifiedJob()
|
unified_job = UnifiedJob()
|
||||||
unified_job.result_stdout_file = 'dummy'
|
unified_job.result_stdout_file = 'dummy'
|
||||||
|
|
||||||
result = unified_job.result_stdout_raw_handle()
|
with mock.patch('os.stat', st_size=1):
|
||||||
|
result = unified_job.result_stdout_raw_handle()
|
||||||
|
|
||||||
self.assertEqual(result, 'my_file_handler')
|
self.assertEqual(result, 'my_file_handler')
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ def encrypt_field(instance, field_name, ask=False):
|
|||||||
value = getattr(instance, field_name)
|
value = getattr(instance, field_name)
|
||||||
if not value or value.startswith('$encrypted$') or (ask and value == 'ASK'):
|
if not value or value.startswith('$encrypted$') or (ask and value == 'ASK'):
|
||||||
return value
|
return value
|
||||||
value = value.encode('utf-8')
|
value = smart_str(value)
|
||||||
key = get_encryption_key(instance, field_name)
|
key = get_encryption_key(instance, field_name)
|
||||||
cipher = AES.new(key, AES.MODE_ECB)
|
cipher = AES.new(key, AES.MODE_ECB)
|
||||||
while len(value) % cipher.block_size != 0:
|
while len(value) % cipher.block_size != 0:
|
||||||
|
|||||||
@@ -527,7 +527,8 @@ class Ec2Inventory(object):
|
|||||||
self.push(self.inventory, key, dest)
|
self.push(self.inventory, key, dest)
|
||||||
if self.nested_groups:
|
if self.nested_groups:
|
||||||
self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k))
|
self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k))
|
||||||
self.push_group(self.inventory, self.to_safe("tag_" + k), key)
|
if v:
|
||||||
|
self.push_group(self.inventory, self.to_safe("tag_" + k), key)
|
||||||
|
|
||||||
# Inventory: Group by Route53 domain names if enabled
|
# Inventory: Group by Route53 domain names if enabled
|
||||||
if self.route53_enabled and self.group_by_route53_names:
|
if self.route53_enabled and self.group_by_route53_names:
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ ALLOWED_HOSTS = []
|
|||||||
# reverse proxy.
|
# reverse proxy.
|
||||||
REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST']
|
REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST']
|
||||||
|
|
||||||
|
STDOUT_MAX_BYTES_DISPLAY = 1048576
|
||||||
|
|
||||||
TEMPLATE_CONTEXT_PROCESSORS += ( # NOQA
|
TEMPLATE_CONTEXT_PROCESSORS += ( # NOQA
|
||||||
'django.core.context_processors.request',
|
'django.core.context_processors.request',
|
||||||
'awx.ui.context_processors.settings',
|
'awx.ui.context_processors.settings',
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export default
|
|||||||
dataTitle: "Source Variables",
|
dataTitle: "Source Variables",
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
awPopOver: "<p>Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " +
|
awPopOver: "<p>Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " +
|
||||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/plugins/inventory/ec2.ini\" target=\"_blank\">" +
|
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/ec2.ini\" target=\"_blank\">" +
|
||||||
"view ec2.ini in the Ansible github repo.</a></p>" +
|
"view ec2.ini in the Ansible github repo.</a></p>" +
|
||||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||||
"JSON:<br />\n" +
|
"JSON:<br />\n" +
|
||||||
@@ -175,7 +175,7 @@ export default
|
|||||||
dataTitle: "Source Variables",
|
dataTitle: "Source Variables",
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
awPopOver: "<p>Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables " +
|
awPopOver: "<p>Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables " +
|
||||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/plugins/inventory/vmware.ini\" target=\"_blank\">" +
|
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/vmware.ini\" target=\"_blank\">" +
|
||||||
"view vmware.ini in the Ansible github repo.</a></p>" +
|
"view vmware.ini in the Ansible github repo.</a></p>" +
|
||||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||||
"JSON:<br />\n" +
|
"JSON:<br />\n" +
|
||||||
|
|||||||
@@ -52,17 +52,16 @@ export default
|
|||||||
scope.removeBuildAboutDialog = scope.$on('BuildAboutDialog', function(e, data) {
|
scope.removeBuildAboutDialog = scope.$on('BuildAboutDialog', function(e, data) {
|
||||||
var spaces, i, j,
|
var spaces, i, j,
|
||||||
paddedStr = "",
|
paddedStr = "",
|
||||||
|
versionParts,
|
||||||
str = data.version,
|
str = data.version,
|
||||||
subscription = data.license_info.subscription_name || "";
|
subscription = data.license_info.subscription_name || "";
|
||||||
|
|
||||||
if(str.search('-')){
|
versionParts = str.split('-');
|
||||||
str = str.substr(0,str.search('-'));
|
spaces = Math.floor((16-versionParts[0].length)/2);
|
||||||
}
|
|
||||||
spaces = Math.floor((16-str.length)/2);
|
|
||||||
for( i=0; i<=spaces; i++){
|
for( i=0; i<=spaces; i++){
|
||||||
paddedStr = paddedStr +" ";
|
paddedStr = paddedStr +" ";
|
||||||
}
|
}
|
||||||
paddedStr = paddedStr+str;
|
paddedStr = paddedStr + versionParts[0];
|
||||||
for( j = paddedStr.length; j<16; j++){
|
for( j = paddedStr.length; j<16; j++){
|
||||||
paddedStr = paddedStr + " ";
|
paddedStr = paddedStr + " ";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
*
|
*
|
||||||
* All Rights Reserved
|
* All Rights Reserved
|
||||||
*************************************************/
|
*************************************************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc function
|
* @ngdoc function
|
||||||
* @name helpers.function:LogViewer
|
* @name helpers.function:LogViewer
|
||||||
* @description logviewer
|
* @description logviewer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default
|
export default
|
||||||
angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', 'VariablesHelper'])
|
angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', 'VariablesHelper'])
|
||||||
@@ -96,9 +96,12 @@ export default
|
|||||||
|
|
||||||
if (data.result_stdout) {
|
if (data.result_stdout) {
|
||||||
$('#logview-tabs li:eq(1)').show();
|
$('#logview-tabs li:eq(1)').show();
|
||||||
|
var showStandardOut = (data.type !== "system_job") ? true : false;
|
||||||
AddPreFormattedText({
|
AddPreFormattedText({
|
||||||
id: 'stdout-form-container',
|
id: 'stdout-form-container',
|
||||||
val: data.result_stdout
|
val: data.result_stdout,
|
||||||
|
standardOut: showStandardOut,
|
||||||
|
jobUrl: data.url
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,8 +363,13 @@ export default
|
|||||||
return function(params) {
|
return function(params) {
|
||||||
var id = params.id,
|
var id = params.id,
|
||||||
val = params.val,
|
val = params.val,
|
||||||
html;
|
html = "";
|
||||||
html = "<pre ng-non-bindable>" + val + "</pre>\n";
|
if (params.standardOut) {
|
||||||
|
html += '<a href="' + params.jobUrl + 'stdout?format=txt_download" class="btn btn-primary btn-xs DownloadStandardOut DownloadStandardOut--onModal" id="download-stdout-button" type="button" aw-tool-tip="Download standard out as a .txt file" data-placement="top" ng-show="status === \'cancelled\' || status === \'failed\' || status === \'error\' || status === \'successful\'"><i class="fa fa-download DownloadStandardOut-icon DownloadStandardOut-icon--withText"></i>Download</a>';
|
||||||
|
html += "<pre class='DownloadStandardOut-pre' ng-non-bindable>" + val + "</pre>\n";
|
||||||
|
} else {
|
||||||
|
html += "<pre ng-non-bindable>" + val + "</pre>\n";
|
||||||
|
}
|
||||||
$('#' + id).empty().html(html);
|
$('#' + id).empty().html(html);
|
||||||
};
|
};
|
||||||
}])
|
}])
|
||||||
|
|||||||
28
awx/ui/static/js/shared/download-standard.out.block.less
Normal file
28
awx/ui/static/js/shared/download-standard.out.block.less
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/** @define DownloadStandardOut */
|
||||||
|
|
||||||
|
.DownloadStandardOut {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DownloadStandardOut--onStandardOutPage {
|
||||||
|
margin-top: -3px;
|
||||||
|
margin-right: -9px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DownloadStandardOut--onModal {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DownloadStandardOut-icon {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DownloadStandardOut-icon--withText {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DownloadStandardOut-pre {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div id="eventviewer-modal-dialog" title="Log View" style="display: none;">
|
<div id="eventviewer-modal-dialog" style="display: none;">
|
||||||
<ul id="eventview-tabs" class="nav nav-tabs">
|
<ul id="eventview-tabs" class="nav nav-tabs">
|
||||||
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'eventview-tabs')">Event</a></li>
|
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'eventview-tabs')">Event</a></li>
|
||||||
<li><a href="#results" id="results-link" data-toggle="tab" ng-click="toggleTab($event, 'results-link', 'eventview-tabs')">Results</a></li>
|
<li><a href="#results" id="results-link" data-toggle="tab" ng-click="toggleTab($event, 'results-link', 'eventview-tabs')">Results</a></li>
|
||||||
@@ -31,4 +31,4 @@
|
|||||||
<div id="json-form-container"></div>
|
<div id="json-form-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,8 +20,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="panel panel-default job-stdout-panel">
|
<div class="panel panel-default job-stdout-panel">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">Standard Output</h3>
|
<h3 class="panel-title">Standard Output
|
||||||
|
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download" class="btn btn-primary btn-xs DownloadStandardOut DownloadStandardOut--onStandardOutPage" id="download-stdout-button" type="button" aw-tool-tip="Download standard out as a .txt file" data-placement="top" ng-show="job.status === 'cancelled' || job.status === 'failed' || job.status === 'error' || job.status === 'successful'">
|
||||||
|
<i class="fa fa-download DownloadStandardOut-icon DownloadStandardOut-icon--withText"></i>Download</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body stdout-panel-body">
|
<div class="panel-body stdout-panel-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -158,8 +158,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">Standard Output</h3>
|
<h3 class="panel-title">Standard Output
|
||||||
|
<a href="/api/v1/ad_hoc_commands/{{ job.id }}/stdout?format=txt_download" class="btn btn-primary btn-xs DownloadStandardOut DownloadStandardOut--onStandardOutPage" id="download-stdout-button" type="button" aw-tool-tip="Download standard out as a .txt file" data-placement="top" ng-show="job.status === 'cancelled' || job.status === 'failed' || job.status === 'error' || job.status === 'successful'"><i class="fa fa-download DownloadStandardOut-icon DownloadStandardOut-icon--withText"></i>Download</a>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body stdout-panel-body">
|
<div class="panel-body stdout-panel-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
|
<div id="logviewer-modal-dialog" style="display: none;">
|
||||||
<div id="logviewer-modal-dialog" title="Log View" style="display: none;">
|
|
||||||
<ul id="logview-tabs" class="nav nav-tabs">
|
<ul id="logview-tabs" class="nav nav-tabs">
|
||||||
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'logview-tabs')">Status</a></li>
|
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'logview-tabs')">Status</a></li>
|
||||||
<li><a href="#stdout" id="stdout-link" data-toggle="tab" ng-click="toggleTab($event, 'stdout-link', 'logview-tabs')">Standard Out</a></li>
|
<li><a href="#stdout" id="stdout-link" data-toggle="tab" ng-click="toggleTab($event, 'stdout-link', 'logview-tabs')">Standard Out</a></li>
|
||||||
@@ -28,4 +27,4 @@
|
|||||||
<div id="source-container"></div>
|
<div id="source-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,15 @@ def read_requirements(towerpath):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_python(towerpath):
|
def get_python(towerpath):
|
||||||
excludes = [ 'README*', '*.dist-info', 'funtests', 'easy_install.py', 'oslo', 'pkg_resources', '_markerlib' ]
|
excludes = [
|
||||||
|
'README*',
|
||||||
|
'*.dist-info',
|
||||||
|
'funtests',
|
||||||
|
'easy_install.py',
|
||||||
|
'oslo',
|
||||||
|
'pkg_resources',
|
||||||
|
'_markerlib'
|
||||||
|
]
|
||||||
directory = '%s/awx/lib/site-packages' % (towerpath,)
|
directory = '%s/awx/lib/site-packages' % (towerpath,)
|
||||||
dirlist = os.listdir(directory)
|
dirlist = os.listdir(directory)
|
||||||
ret = []
|
ret = []
|
||||||
@@ -82,13 +90,13 @@ def get_js(towerpath):
|
|||||||
bowerfile.close()
|
bowerfile.close()
|
||||||
pkg = {}
|
pkg = {}
|
||||||
pkg['name'] = item
|
pkg['name'] = item
|
||||||
if pkginfo.has_key('license'):
|
if 'license' in pkginfo:
|
||||||
pkg['license'] = normalize_license(pkginfo['license'])
|
pkg['license'] = normalize_license(pkginfo['license'])
|
||||||
else:
|
else:
|
||||||
pkg['license'] = 'UNKNOWN'
|
pkg['license'] = 'UNKNOWN'
|
||||||
if pkginfo.has_key('homepage'):
|
if 'homepage' in pkginfo:
|
||||||
pkg['url'] = pkginfo['homepage']
|
pkg['url'] = pkginfo['homepage']
|
||||||
elif pkginfo.has_key('url'):
|
elif 'url' in pkginfo:
|
||||||
pkg['url'] = pkginfo['url']
|
pkg['url'] = pkginfo['url']
|
||||||
else:
|
else:
|
||||||
pkg['url'] = 'UNKNOWN'
|
pkg['url'] = 'UNKNOWN'
|
||||||
@@ -209,7 +217,7 @@ for req in requirements.values():
|
|||||||
cs_info = cs.release_data(req['name'],req['version'])
|
cs_info = cs.release_data(req['name'],req['version'])
|
||||||
if not cs_info:
|
if not cs_info:
|
||||||
print "Couldn't find '%s-%s'" %(req['name'],req['version'])
|
print "Couldn't find '%s-%s'" %(req['name'],req['version'])
|
||||||
if not olddata.has_key(req['name']):
|
if 'name' not in olddata:
|
||||||
print "... and it's not in the current data. This needs fixed!"
|
print "... and it's not in the current data. This needs fixed!"
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
continue
|
continue
|
||||||
@@ -233,7 +241,7 @@ for req in requirements.values():
|
|||||||
|
|
||||||
# Update JS package info
|
# Update JS package info
|
||||||
for pkg in js:
|
for pkg in js:
|
||||||
if olddata.has_key(pkg):
|
if 'pkg' in olddata:
|
||||||
data = olddata[pkg]
|
data = olddata[pkg]
|
||||||
new = js_packages[pkg]
|
new = js_packages[pkg]
|
||||||
if new['license'] != 'UNKNOWN' and new['license'] != data['license']:
|
if new['license'] != 'UNKNOWN' and new['license'] != data['license']:
|
||||||
@@ -249,4 +257,4 @@ for pkg in js:
|
|||||||
olddata[pkg] = item
|
olddata[pkg] = item
|
||||||
continue
|
continue
|
||||||
|
|
||||||
write_csv(outputfile, olddata)
|
write_csv(outputfile, olddata)
|
||||||
|
|||||||
Reference in New Issue
Block a user