diff --git a/lib/main/admin.py b/lib/main/admin.py index 0f074e8293..95d86e5f5c 100644 --- a/lib/main/admin.py +++ b/lib/main/admin.py @@ -16,6 +16,7 @@ import json +import urllib from django.conf.urls import * from django.contrib import admin @@ -51,7 +52,16 @@ admin.site.register(User, UserAdmin) # FIXME: Hide auth.Group admin -class OrganizationAdmin(admin.ModelAdmin): +class BaseModelAdmin(admin.ModelAdmin): + + def save_model(self, request, obj, form, change): + # Automatically set created_by when saved from the admin. + # FIXME: Doesn't handle inline model instances yet. + if hasattr(obj, 'created_by') and obj.created_by is None: + obj.created_by = request.user + return super(BaseModelAdmin, self).save_model(request, obj, form, change) + +class OrganizationAdmin(BaseModelAdmin): list_display = ('name', 'description', 'active') list_filter = ('active', 'tags') @@ -62,7 +72,7 @@ class OrganizationAdmin(admin.ModelAdmin): (_('Tags'), {'fields': ('tags',)}), (_('Audit Trail'), {'fields': ('creation_date', 'audit_trail',)}), ) - readonly_fields = ('creation_date', 'audit_trail') + readonly_fields = ('creation_date', 'created_by', 'audit_trail') filter_horizontal = ('users', 'admins', 'projects', 'tags') class InventoryHostInline(admin.StackedInline): @@ -79,7 +89,7 @@ class InventoryGroupInline(admin.StackedInline): fields = ('name', 'description', 'active', 'parents', 'hosts', 'tags') filter_horizontal = ('parents', 'hosts', 'tags') -class InventoryAdmin(admin.ModelAdmin): +class InventoryAdmin(BaseModelAdmin): list_display = ('name', 'organization', 'description', 'active') list_filter = ('organization', 'active') @@ -89,11 +99,11 @@ class InventoryAdmin(admin.ModelAdmin): (_('Tags'), {'fields': ('tags',)}), (_('Audit Trail'), {'fields': ('creation_date', 'audit_trail',)}), ) - readonly_fields = ('creation_date', 'audit_trail') + readonly_fields = ('creation_date', 'created_by', 'audit_trail') filter_horizontal = ('tags',) inlines = [InventoryHostInline, InventoryGroupInline] -class TagAdmin(admin.ModelAdmin): +class TagAdmin(BaseModelAdmin): list_display = ('name',) @@ -111,18 +121,18 @@ class VariableDataInline(admin.StackedInline): # FIXME: Doesn't yet work as inline due to the way the OneToOne field is # defined. -class LaunchJobHostSummaryInline(admin.TabularInline): +class JobHostSummaryInline(admin.TabularInline): - model = LaunchJobHostSummary + model = JobHostSummary extra = 0 can_delete = False def has_add_permission(self, request): return False -class LaunchJobStatusEventInline(admin.StackedInline): +class JobEventInline(admin.StackedInline): - model = LaunchJobStatusEvent + model = JobEvent extra = 0 can_delete = False @@ -130,161 +140,197 @@ class LaunchJobStatusEventInline(admin.StackedInline): return False def get_event_data_display(self, obj): - return format_html('
{0}
', json.dumps(obj.event_data, indent=4)) + return format_html('
{0}
', + json.dumps(obj.event_data, indent=4)) get_event_data_display.short_description = _('Event data') get_event_data_display.allow_tags = True -class LaunchJobHostSummaryInlineForHost(LaunchJobHostSummaryInline): +class JobHostSummaryInlineForHost(JobHostSummaryInline): - fields = ('launch_job_status', 'changed', 'dark', 'failures', 'ok', 'processed', 'skipped') - readonly_fields = ('launch_job_status', 'changed', 'dark', 'failures', 'ok', 'processed', 'skipped') + fields = ('job', 'changed', 'dark', 'failures', 'ok', 'processed', + 'skipped') + readonly_fields = ('job', 'changed', 'dark', 'failures', 'ok', 'processed', + 'skipped') -class LaunchJobStatusEventInlineForHost(LaunchJobStatusEventInline): +class JobEventInlineForHost(JobEventInline): - fields = ('created', 'event', 'get_event_data_display', 'launch_job_status') - readonly_fields = ('created', 'event', 'get_event_data_display', 'launch_job_status') + fields = ('job', 'created', 'event', 'get_event_data_display') + readonly_fields = ('job', 'created', 'event', 'get_event_data_display') -class HostAdmin(admin.ModelAdmin): +class HostAdmin(BaseModelAdmin): list_display = ('name', 'inventory', 'description', 'active') list_filter = ('inventory', 'active') fields = ('name', 'inventory', 'description', 'active', 'tags', 'created_by', 'audit_trail') + readonly_fields = ('creation_date', 'created_by', 'audit_trail') filter_horizontal = ('tags',) # FIXME: Edit reverse of many to many for groups. #inlines = [VariableDataInline] - inlines = [LaunchJobHostSummaryInlineForHost, LaunchJobStatusEventInlineForHost] + inlines = [JobHostSummaryInlineForHost, JobEventInlineForHost] -class GroupAdmin(admin.ModelAdmin): +class GroupAdmin(BaseModelAdmin): list_display = ('name', 'description', 'active') filter_horizontal = ('parents', 'hosts', 'tags') #inlines = [VariableDataInline] -class VariableDataAdmin(admin.ModelAdmin): +class VariableDataAdmin(BaseModelAdmin): list_display = ('name', 'description', 'active') filter_horizontal = ('tags',) -class CredentialAdmin(admin.ModelAdmin): +class CredentialAdmin(BaseModelAdmin): list_display = ('name', 'description', 'active') filter_horizontal = ('tags',) -class TeamAdmin(admin.ModelAdmin): +class TeamAdmin(BaseModelAdmin): list_display = ('name', 'description', 'active') filter_horizontal = ('projects', 'users', 'tags') -class ProjectAdmin(admin.ModelAdmin): +class ProjectAdmin(BaseModelAdmin): list_display = ('name', 'description', 'active') filter_horizontal = ('tags',) -class PermissionAdmin(admin.ModelAdmin): +class PermissionAdmin(BaseModelAdmin): list_display = ('name', 'description', 'active') filter_horizontal = ('tags',) -class LaunchJobAdmin(admin.ModelAdmin): +class JobTemplateAdmin(BaseModelAdmin): - list_display = ('name', 'description', 'active', 'get_start_link_display', - 'get_statuses_link_display') + list_display = ('name', 'description', 'active', 'get_create_link_display', + 'get_jobs_link_display') fieldsets = ( - (None, {'fields': ('name', 'active', 'created_by', 'description', - 'get_start_link_display', 'get_statuses_link_display')}), + (None, {'fields': ('name', 'active', 'description', + 'get_create_link_display', 'get_jobs_link_display')}), (_('Job Parameters'), {'fields': ('inventory', 'project', 'credential', 'user', 'job_type')}), - (_('Tags'), {'fields': ('tags',)}), - (_('Audit Trail'), {'fields': ('creation_date', 'audit_trail',)}), + #(_('Tags'), {'fields': ('tags',)}), + (_('Audit Trail'), {'fields': ('creation_date', 'created_by', + 'audit_trail',)}), ) - readonly_fields = ('creation_date', 'audit_trail', 'get_start_link_display', - 'get_statuses_link_display') - filter_horizontal = ('tags',) + readonly_fields = ('creation_date', 'created_by', 'audit_trail', + 'get_create_link_display', 'get_jobs_link_display') + #filter_horizontal = ('tags',) - def get_start_link_display(self, obj): - info = self.model._meta.app_label, self.model._meta.module_name - start_url = reverse('admin:%s_%s_start' % info, args=(obj.pk,), - current_app=self.admin_site.name) - return 'Run Job' % start_url - get_start_link_display.short_description = _('Run') - get_start_link_display.allow_tags = True - - def get_statuses_link_display(self, obj): - info = LaunchJobStatus._meta.app_label, LaunchJobStatus._meta.module_name - statuses_url = reverse('admin:%s_%s_changelist' % info, - current_app=self.admin_site.name) - statuses_url += '?launch_job__id__exact=%d' % obj.pk - return 'View Logs' % statuses_url - get_statuses_link_display.short_description = _('Logs') - get_statuses_link_display.allow_tags = True - - def get_urls(self): - info = self.model._meta.app_label, self.model._meta.module_name - urls = super(LaunchJobAdmin, self).get_urls() - return patterns('', - url(r'^(.+)/start/$', - self.admin_site.admin_view(self.start_job_view), - name='%s_%s_start' % info), - ) + urls - - def start_job_view(self, request, object_id): - obj = self.get_object(request, unquote(object_id)) - ljs = obj.start() - info = ljs._meta.app_label, ljs._meta.module_name - status_url = reverse('admin:%s_%s_change' % info, args=(ljs.pk,), + def get_create_link_display(self, obj): + info = Job._meta.app_label, Job._meta.module_name + create_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name) - messages.success(request, '%s has been started.' % ljs) - return HttpResponseRedirect(status_url) + create_opts = { + 'job_template': obj.pk, + 'job_type': obj.job_type, + } + if obj.inventory: + create_opts['inventory'] = obj.inventory.pk + if obj.project: + create_opts['project'] = obj.project.pk + if obj.credential: + create_opts['credential'] = obj.credential.pk + if obj.user: + create_opts['user'] = obj.user.pk + create_url += '?%s' % urllib.urlencode(create_opts) + return format_html('{1}', create_url, 'Create Job') + get_create_link_display.short_description = _('Create Job') + get_create_link_display.allow_tags = True -class LaunchJobHostSummaryInlineForLaunchJobStatus(LaunchJobHostSummaryInline): + def get_jobs_link_display(self, obj): + info = Job._meta.app_label, Job._meta.module_name + jobs_url = reverse('admin:%s_%s_changelist' % info, + current_app=self.admin_site.name) + jobs_url += '?job_template__id__exact=%d' % obj.pk + return format_html('{1}', jobs_url, 'View Jobs') + get_jobs_link_display.short_description = _('View Jobs') + get_jobs_link_display.allow_tags = True - fields = ('host', 'changed', 'dark', 'failures', 'ok', 'processed', 'skipped') - readonly_fields = ('host', 'changed', 'dark', 'failures', 'ok', 'processed', 'skipped') +class JobHostSummaryInlineForJob(JobHostSummaryInline): -class LaunchJobStatusEventInlineForLaunchJobStatus(LaunchJobStatusEventInline): + fields = ('host', 'changed', 'dark', 'failures', 'ok', 'processed', + 'skipped') + readonly_fields = ('host', 'changed', 'dark', 'failures', 'ok', + 'processed', 'skipped') + +class JobEventInlineForJob(JobEventInline): fields = ('created', 'event', 'get_event_data_display', 'host') readonly_fields = ('created', 'event', 'get_event_data_display', 'host') -class LaunchJobStatusAdmin(admin.ModelAdmin): +class JobAdmin(BaseModelAdmin): - list_display = ('name', 'launch_job', 'status') - fields = ('name', 'get_launch_job_display', 'status', - 'get_result_stdout_display', 'get_result_stderr_display', - 'get_result_traceback_display', 'celery_task_id', 'tags', - 'created_by') - readonly_fields = ('name', 'description', 'status', 'get_launch_job_display', + list_display = ('name', 'job_template', 'status') + fieldsets = ( + (None, {'fields': ('name', 'job_template', 'description')}), + (_('Job Parameters'), {'fields': ('inventory', 'project', 'credential', + 'user', 'job_type')}), + #(_('Tags'), {'fields': ('tags',)}), + (_('Audit Trail'), {'fields': ('creation_date', 'created_by', + 'audit_trail',)}), + (_('Job Status'), {'fields': ('status', 'get_result_stdout_display', + 'get_result_stderr_display', + 'get_result_traceback_display', + 'celery_task_id')}), + ) + readonly_fields = ('status', 'get_job_template_display', 'get_result_stdout_display', 'get_result_stderr_display', 'get_result_traceback_display', 'celery_task_id', - 'created_by', 'tags', 'audit_trail', 'active') + 'creation_date', 'created_by', 'audit_trail',) filter_horizontal = ('tags',) - inlines = [LaunchJobHostSummaryInlineForLaunchJobStatus, - LaunchJobStatusEventInlineForLaunchJobStatus] + inlines = [JobHostSummaryInlineForJob, JobEventInlineForJob] - def has_add_permission(self, request): - return False + def get_readonly_fields(self, request, obj=None): + ro_fields = list(super(JobAdmin, self).get_readonly_fields(request, obj)) + if obj and obj.pk: + ro_fields.extend(['name', 'description', 'job_template', + 'inventory', 'project', 'credential', 'user', + 'job_type']) + return ro_fields - def get_launch_job_display(self, obj): - info = obj.launch_job._meta.app_label, obj.launch_job._meta.module_name - lj_url = reverse('admin:%s_%s_change' % info, args=(obj.launch_job.pk,), - current_app=self.admin_site.name) - return format_html('{1}', lj_url, obj.launch_job) - get_launch_job_display.short_description = _('Launch job') - get_launch_job_display.allow_tags = True + def get_fieldsets(self, request, obj=None): + fsets = list(super(JobAdmin, self).get_fieldsets(request, obj)) + if not obj or not obj.pk: + fsets = [fs for fs in fsets if + 'creation_date' not in fs[1]['fields'] and + 'status' not in fs[1]['fields']] + return fsets + + def get_inline_instances(self, request, obj=None): + if obj and obj.pk: + return super(JobAdmin, self).get_inline_instances(request, obj) + else: + return [] + + def get_job_template_display(self, obj): + if obj.job_template: + info = JobTemplate._meta.app_label, JobTemplate._meta.module_name + job_template_url = reverse('admin:%s_%s_change' % info, + args=(obj.job_template.pk,), + current_app=self.admin_site.name) + return format_html('{1}', job_template_url, + obj.job_template) + else: + return _('(None)') + get_job_template_display.short_description = _('Job template') + get_job_template_display.allow_tags = True def get_result_stdout_display(self, obj): - return format_html('
{0}
', obj.result_stdout or ' ') + return format_html('
{0}
', + obj.result_stdout or ' ') get_result_stdout_display.short_description = _('Stdout') get_result_stdout_display.allow_tags = True def get_result_stderr_display(self, obj): - return format_html('
{0}
', obj.result_stderr or ' ') + return format_html('
{0}
', + obj.result_stderr or ' ') get_result_stderr_display.short_description = _('Stderr') get_result_stderr_display.allow_tags = True def get_result_traceback_display(self, obj): - return format_html('
{0}
', obj.result_traceback or ' ') + return format_html('
{0}
', + obj.result_traceback or ' ') get_result_traceback_display.short_description = _('Traceback') get_result_traceback_display.allow_tags = True @@ -300,5 +346,5 @@ admin.site.register(VariableData, VariableDataAdmin) admin.site.register(Team, TeamAdmin) admin.site.register(Project, ProjectAdmin) admin.site.register(Credential, CredentialAdmin) -admin.site.register(LaunchJob, LaunchJobAdmin) -admin.site.register(LaunchJobStatus, LaunchJobStatusAdmin) +admin.site.register(JobTemplate, JobTemplateAdmin) +admin.site.register(Job, JobAdmin) diff --git a/lib/main/management/commands/acom_callback_event.py b/lib/main/management/commands/acom_callback_event.py index 5485ff4417..040b9475a7 100755 --- a/lib/main/management/commands/acom_callback_event.py +++ b/lib/main/management/commands/acom_callback_event.py @@ -30,10 +30,10 @@ class Command(NoArgsCommand): help = 'Ansible Commander Callback Event Capture' option_list = NoArgsCommand.option_list + ( - make_option('-i', '--launch-job-status', dest='launch_job_status_id', + make_option('-j', '--job', dest='job_id', type='int', default=0, - help='Launch job status ID (can also be specified using ' - 'ACOM_LAUNCH_JOB_STATUS_ID environment variable)'), + help='Job ID (can also be specified using ACOM_JOB_ID ' + 'environment variable)'), make_option('-e', '--event', dest='event_type', default=None, help='Event type'), make_option('-f', '--file', dest='event_data_file', default=None, @@ -44,29 +44,28 @@ class Command(NoArgsCommand): ) def handle_noargs(self, **options): - from lib.main.models import LaunchJobStatus, LaunchJobStatusEvent + from lib.main.models import Job, JobEvent event_type = options.get('event_type', None) if not event_type: raise CommandError('No event specified') - if event_type not in [x[0] for x in LaunchJobStatusEvent.EVENT_TYPES]: + if event_type not in [x[0] for x in JobEvent.EVENT_TYPES]: raise CommandError('Unsupported event') event_data_file = options.get('event_data_file', None) event_data_json = options.get('event_data_json', None) if event_data_file is None and event_data_json is None: raise CommandError('Either --file or --data must be specified') try: - launch_job_status_id = int(os.getenv('ACOM_LAUNCH_JOB_STATUS_ID', - options.get('launch_job_status_id', 0))) + job_id = int(os.getenv('ACOM_JOB_ID', options.get('job_id', 0))) except ValueError: - raise CommandError('Launch job status ID must be an integer') - if not launch_job_status_id: - raise CommandError('No launch job status ID specified') + raise CommandError('Job ID must be an integer') + if not job_id: + raise CommandError('No Job ID specified') try: - launch_job_status = LaunchJobStatus.objects.get(id=launch_job_status_id) - except LaunchJobStatus.DoesNotExist: - raise CommandError('Launch job status with ID %d not found' % launch_job_status_id) - if launch_job_status.status != 'running': - raise CommandError('Unable to add event except when launch job is running') + job = Job.objects.get(id=job_id) + except Job.DoesNotExist: + raise CommandError('Job with ID %d not found' % job_id) + if job.status != 'running': + raise CommandError('Unable to add event except when job is running') try: if event_data_json is None: try: @@ -81,8 +80,7 @@ class Command(NoArgsCommand): event_data = json.loads(event_data_json) except ValueError: raise CommandError('Error parsing JSON data') - launch_job_status.launch_job_status_events.create(event=event_type, - event_data=event_data) + job.job_events.create(event=event_type, event_data=event_data) if __name__ == '__main__': from __init__ import run_command_as_script diff --git a/lib/main/management/commands/acom_inventory.py b/lib/main/management/commands/acom_inventory.py index 4f5f06840f..bd723722f6 100755 --- a/lib/main/management/commands/acom_inventory.py +++ b/lib/main/management/commands/acom_inventory.py @@ -27,9 +27,9 @@ class Command(NoArgsCommand): help = 'Ansible Commander Inventory script' option_list = NoArgsCommand.option_list + ( - make_option('-i', '--inventory', dest='inventory', type='int', default=0, - help='Inventory ID (can also be specified using ' - 'ACOM_INVENTORY_ID environment variable)'), + make_option('-i', '--inventory', dest='inventory_id', type='int', + default=0, help='Inventory ID (can also be specified using' + ' ACOM_INVENTORY_ID environment variable)'), make_option('--list', action='store_true', dest='list', default=False, help='Return JSON hash of host groups.'), make_option('--host', dest='host', default='', @@ -75,7 +75,7 @@ class Command(NoArgsCommand): try: # Command line argument takes precedence over environment # variable. - inventory_id = int(options.get('inventory', 0) or \ + inventory_id = int(options.get('inventory_id', 0) or \ os.getenv('ACOM_INVENTORY_ID', 0)) except ValueError: raise CommandError('Inventory ID must be an integer') diff --git a/lib/main/migrations/0013_changes.py b/lib/main/migrations/0013_changes.py new file mode 100644 index 0000000000..8b750e348a --- /dev/null +++ b/lib/main/migrations/0013_changes.py @@ -0,0 +1,501 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Removing unique constraint on 'LaunchJobHostSummary', fields ['launch_job_status', 'host'] + db.delete_unique(u'main_launchjobhostsummary', ['launch_job_status_id', 'host_id']) + + # Deleting model 'LaunchJobHostSummary' + db.delete_table(u'main_launchjobhostsummary') + + # Deleting model 'LaunchJobStatusEvent' + db.delete_table(u'main_launchjobstatusevent') + + # Deleting model 'LaunchJob' + db.delete_table(u'main_launchjob') + + # Removing M2M table for field tags on 'LaunchJob' + db.delete_table('main_launchjob_tags') + + # Removing M2M table for field audit_trail on 'LaunchJob' + db.delete_table('main_launchjob_audit_trail') + + # Deleting model 'LaunchJobStatus' + db.delete_table(u'main_launchjobstatus') + + # Removing M2M table for field tags on 'LaunchJobStatus' + db.delete_table('main_launchjobstatus_tags') + + # Removing M2M table for field audit_trail on 'LaunchJobStatus' + db.delete_table('main_launchjobstatus_audit_trail') + + # Adding model 'Job' + db.create_table(u'main_job', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'job', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])), + ('creation_date', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)), + ('active', self.gf('django.db.models.fields.BooleanField')(default=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)), + ('job_template', self.gf('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', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['main.Inventory'])), + ('credential', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['main.Credential'])), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['main.Project'])), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['auth.User'])), + ('status', self.gf('django.db.models.fields.CharField')(default='pending', max_length=20)), + ('result_stdout', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('result_stderr', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('result_traceback', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('celery_task_id', self.gf('django.db.models.fields.CharField')(default='', max_length=100, blank=True)), + )) + db.send_create_signal('main', ['Job']) + + # Adding M2M table for field tags on 'Job' + db.create_table(u'main_job_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('job', models.ForeignKey(orm['main.job'], null=False)), + ('tag', models.ForeignKey(orm['main.tag'], null=False)) + )) + db.create_unique(u'main_job_tags', ['job_id', 'tag_id']) + + # Adding M2M table for field audit_trail on 'Job' + db.create_table(u'main_job_audit_trail', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('job', models.ForeignKey(orm['main.job'], null=False)), + ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False)) + )) + db.create_unique(u'main_job_audit_trail', ['job_id', 'audittrail_id']) + + # Adding model 'JobHostSummary' + db.create_table(u'main_jobhostsummary', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('job', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_host_summaries', to=orm['main.Job'])), + ('host', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_host_summaries', to=orm['main.Host'])), + ('changed', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('dark', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('failures', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('ok', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('processed', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('skipped', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + )) + db.send_create_signal(u'main', ['JobHostSummary']) + + # Adding unique constraint on 'JobHostSummary', fields ['job', 'host'] + db.create_unique(u'main_jobhostsummary', ['job_id', 'host_id']) + + # Adding model 'JobTemplate' + db.create_table(u'main_jobtemplate', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'jobtemplate', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])), + ('creation_date', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)), + ('active', self.gf('django.db.models.fields.BooleanField')(default=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)), + ('job_type', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', on_delete=models.SET_NULL, default=None, to=orm['main.Inventory'], blank=True, null=True)), + ('credential', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', on_delete=models.SET_NULL, default=None, to=orm['main.Credential'], blank=True, null=True)), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', on_delete=models.SET_NULL, default=None, to=orm['main.Project'], blank=True, null=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', on_delete=models.SET_NULL, default=None, to=orm['auth.User'], blank=True, null=True)), + )) + db.send_create_signal('main', ['JobTemplate']) + + # Adding M2M table for field tags on 'JobTemplate' + db.create_table(u'main_jobtemplate_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)), + ('tag', models.ForeignKey(orm['main.tag'], null=False)) + )) + db.create_unique(u'main_jobtemplate_tags', ['jobtemplate_id', 'tag_id']) + + # Adding M2M table for field audit_trail on 'JobTemplate' + db.create_table(u'main_jobtemplate_audit_trail', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)), + ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False)) + )) + db.create_unique(u'main_jobtemplate_audit_trail', ['jobtemplate_id', 'audittrail_id']) + + # Adding model 'JobEvent' + db.create_table(u'main_jobevent', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('job', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_events', to=orm['main.Job'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('event', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('event_data', self.gf('jsonfield.fields.JSONField')(default='', blank=True)), + ('host', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_events', on_delete=models.SET_NULL, default=None, to=orm['main.Host'], blank=True, null=True)), + )) + db.send_create_signal('main', ['JobEvent']) + + + def backwards(self, orm): + # Removing unique constraint on 'JobHostSummary', fields ['job', 'host'] + db.delete_unique(u'main_jobhostsummary', ['job_id', 'host_id']) + + # Adding model 'LaunchJobHostSummary' + db.create_table(u'main_launchjobhostsummary', ( + ('dark', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('skipped', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('host', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_job_host_summaries', to=orm['main.Host'])), + ('ok', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('processed', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('failures', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('changed', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('launch_job_status', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_job_host_summaries', to=orm['main.LaunchJobStatus'])), + )) + db.send_create_signal(u'main', ['LaunchJobHostSummary']) + + # Adding unique constraint on 'LaunchJobHostSummary', fields ['launch_job_status', 'host'] + db.create_unique(u'main_launchjobhostsummary', ['launch_job_status_id', 'host_id']) + + # Adding model 'LaunchJobStatusEvent' + db.create_table(u'main_launchjobstatusevent', ( + ('event', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('event_data', self.gf('jsonfield.fields.JSONField')(default='', blank=True)), + ('host', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_job_status_events', on_delete=models.SET_NULL, default=None, to=orm['main.Host'], blank=True, null=True)), + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('launch_job_status', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_job_status_events', to=orm['main.LaunchJobStatus'])), + )) + db.send_create_signal('main', ['LaunchJobStatusEvent']) + + # Adding model 'LaunchJob' + db.create_table(u'main_launchjob', ( + ('credential', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_jobs', on_delete=models.SET_NULL, default=None, to=orm['main.Credential'], blank=True, null=True)), + ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('job_type', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('creation_date', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_jobs', on_delete=models.SET_NULL, default=None, to=orm['auth.User'], blank=True, null=True)), + ('active', self.gf('django.db.models.fields.BooleanField')(default=True)), + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=512, unique=True)), + ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'launchjob', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_jobs', on_delete=models.SET_NULL, default=None, to=orm['main.Project'], blank=True, null=True)), + ('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_jobs', on_delete=models.SET_NULL, default=None, to=orm['main.Inventory'], blank=True, null=True)), + )) + db.send_create_signal('main', ['LaunchJob']) + + # Adding M2M table for field tags on 'LaunchJob' + db.create_table(u'main_launchjob_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('launchjob', models.ForeignKey(orm['main.launchjob'], null=False)), + ('tag', models.ForeignKey(orm['main.tag'], null=False)) + )) + db.create_unique(u'main_launchjob_tags', ['launchjob_id', 'tag_id']) + + # Adding M2M table for field audit_trail on 'LaunchJob' + db.create_table(u'main_launchjob_audit_trail', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('launchjob', models.ForeignKey(orm['main.launchjob'], null=False)), + ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False)) + )) + db.create_unique(u'main_launchjob_audit_trail', ['launchjob_id', 'audittrail_id']) + + # Adding model 'LaunchJobStatus' + db.create_table(u'main_launchjobstatus', ( + ('status', self.gf('django.db.models.fields.CharField')(default='pending', max_length=20)), + ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('result_traceback', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('result_stdout', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'launchjobstatus', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])), + ('result_stderr', self.gf('django.db.models.fields.TextField')(default='', blank=True)), + ('celery_task_id', self.gf('django.db.models.fields.CharField')(default='', max_length=100, blank=True)), + ('launch_job', self.gf('django.db.models.fields.related.ForeignKey')(related_name='launch_job_statuses', null=True, on_delete=models.SET_NULL, to=orm['main.LaunchJob'])), + ('active', self.gf('django.db.models.fields.BooleanField')(default=True)), + ('creation_date', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)), + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=512, unique=True)), + )) + db.send_create_signal('main', ['LaunchJobStatus']) + + # Adding M2M table for field tags on 'LaunchJobStatus' + db.create_table(u'main_launchjobstatus_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('launchjobstatus', models.ForeignKey(orm['main.launchjobstatus'], null=False)), + ('tag', models.ForeignKey(orm['main.tag'], null=False)) + )) + db.create_unique(u'main_launchjobstatus_tags', ['launchjobstatus_id', 'tag_id']) + + # Adding M2M table for field audit_trail on 'LaunchJobStatus' + db.create_table(u'main_launchjobstatus_audit_trail', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('launchjobstatus', models.ForeignKey(orm['main.launchjobstatus'], null=False)), + ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False)) + )) + db.create_unique(u'main_launchjobstatus_audit_trail', ['launchjobstatus_id', 'audittrail_id']) + + # Deleting model 'Job' + db.delete_table(u'main_job') + + # Removing M2M table for field tags on 'Job' + db.delete_table('main_job_tags') + + # Removing M2M table for field audit_trail on 'Job' + db.delete_table('main_job_audit_trail') + + # Deleting model 'JobHostSummary' + db.delete_table(u'main_jobhostsummary') + + # Deleting model 'JobTemplate' + db.delete_table(u'main_jobtemplate') + + # Removing M2M table for field tags on 'JobTemplate' + db.delete_table('main_jobtemplate_tags') + + # Removing M2M table for field audit_trail on 'JobTemplate' + db.delete_table('main_jobtemplate_audit_trail') + + # Deleting model 'JobEvent' + db.delete_table(u'main_jobevent') + + + 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', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 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', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + '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.audittrail': { + 'Meta': {'object_name': 'AuditTrail'}, + 'comment': ('django.db.models.fields.TextField', [], {}), + 'delta': ('django.db.models.fields.TextField', [], {}), + 'detail': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'resource_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Tag']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}) + }, + 'main.credential': { + 'Meta': {'object_name': 'Credential'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'credential_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'default_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + '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'}), + 'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}), + 'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'credential_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + 'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'}) + }, + 'main.group': { + 'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}), + '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']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + 'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'}) + }, + 'main.host': { + 'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'host_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + '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': "'hosts'", 'to': "orm['main.Inventory']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'host_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + 'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'host'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'}) + }, + 'main.inventory': { + 'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'inventory_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'inventory_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}) + }, + 'main.job': { + 'Meta': {'object_name': 'Job'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}), + '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', [], {'max_length': '64'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}), + 'result_stderr': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '20'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}) + }, + 'main.jobevent': { + 'Meta': {'object_name': 'JobEvent'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'event_data': ('jsonfield.fields.JSONField', [], {'default': "''", 'blank': 'True'}), + 'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}), + 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']"}) + }, + u'main.jobhostsummary': { + 'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'}, + 'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}), + 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']"}), + '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.jobtemplate': { + 'Meta': {'object_name': 'JobTemplate'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobtemplate_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}), + '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': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Inventory']", 'blank': 'True', 'null': 'True'}), + 'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.Project']", 'blank': 'True', 'null': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobtemplate_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'}) + }, + 'main.organization': { + 'Meta': {'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']"}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organization_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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': u"orm['main.Project']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organization_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + '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'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'permission_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + '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']"}), + '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': u"orm['main.Project']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'permission_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + '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']"}) + }, + u'main.project': { + 'Meta': {'object_name': 'Project'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'project_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'default_playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'local_repository': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}), + 'scm_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'project_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}) + }, + 'main.tag': { + 'Meta': {'object_name': 'Tag'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'main.team': { + 'Meta': {'object_name': 'Team'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'team_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', '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': u"orm['main.Project']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'team_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"}) + }, + 'main.variabledata': { + 'Meta': {'object_name': 'VariableData'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'variabledata_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'variabledata\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}), + 'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'data': ('django.db.models.fields.TextField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'variabledata_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}) + } + } + + complete_apps = ['main'] \ No newline at end of file diff --git a/lib/main/models/__init__.py b/lib/main/models/__init__.py index e67b41bdbb..0002fb66c5 100644 --- a/lib/main/models/__init__.py +++ b/lib/main/models/__init__.py @@ -150,7 +150,7 @@ class PrimordialModel(models.Model): abstract = True description = models.TextField(blank=True, default='') - created_by = models.ForeignKey('auth.User', on_delete=SET_NULL, null=True, related_name='%s(class)s_created') # not blank=False on purpose for admin! + created_by = models.ForeignKey('auth.User', on_delete=SET_NULL, null=True, related_name='%s(class)s_created', editable=False) # not blank=False on purpose for admin! creation_date = models.DateField(auto_now_add=True) tags = models.ManyToManyField('Tag', related_name='%(class)s_by_tag', blank=True) audit_trail = models.ManyToManyField('AuditTrail', related_name='%(class)s_by_audit_trail', blank=True) @@ -707,35 +707,51 @@ class Permission(CommonModelNameNotUnique): # TODO: other job types (later) -class LaunchJob(CommonModel): +class JobTemplate(CommonModel): ''' - A launch job is a definition for applying a project (with playbook) to an - inventory source with a given credential. + A job template is a reusable job definition for applying a project (with + playbook) to an inventory source with a given credential. ''' class Meta: app_label = 'main' - inventory = models.ForeignKey('Inventory', on_delete=SET_NULL, null=True, default=None, blank=True, related_name='launch_jobs') - credential = models.ForeignKey('Credential', on_delete=SET_NULL, null=True, default=None, blank=True, related_name='launch_jobs') - project = models.ForeignKey('Project', on_delete=SET_NULL, null=True, default=None, blank=True, related_name='launch_jobs') - user = models.ForeignKey('auth.User', on_delete=SET_NULL, null=True, default=None, blank=True, related_name='launch_jobs') - - # JOB_TYPE_CHOICES are a subset of PERMISSION_TYPE_CHOICES - job_type = models.CharField(max_length=64, choices=JOB_TYPE_CHOICES) - - def start(self): - ''' - Create a new launch job status and start the task via celery. - ''' - from lib.main.tasks import run_launch_job - launch_job_status = self.launch_job_statuses.create(name='Launch Job Status %s' % now().isoformat()) - task_result = run_launch_job.delay(launch_job_status.pk) - # The TaskMeta instance in the database isn't created until the worker - # starts processing the task, so we can only store the task ID here. - launch_job_status.celery_task_id = task_result.task_id - launch_job_status.save(update_fields=['celery_task_id']) - return launch_job_status + job_type = models.CharField( + max_length=64, + choices=JOB_TYPE_CHOICES, + ) + inventory = models.ForeignKey( + 'Inventory', + related_name='job_templates', + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + ) + credential = models.ForeignKey( + 'Credential', + related_name='job_templates', + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + ) + project = models.ForeignKey( + 'Project', + related_name='job_templates', + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + ) + user = models.ForeignKey( + 'auth.User', + related_name='job_templates', + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + ) # project has one default playbook but really should have a list of playbooks and flags ... @@ -756,9 +772,11 @@ class LaunchJob(CommonModel): # --list # -- host -class LaunchJobStatus(CommonModel): +class Job(CommonModel): ''' - Status for a single run of a launch job. + A job applies a project (with playbook) to an inventory source with a given + credential. It represents a single invocation of ansible-playbook with the + given parameters. ''' STATUS_CHOICES = [ @@ -771,15 +789,77 @@ class LaunchJobStatus(CommonModel): class Meta: app_label = 'main' - verbose_name_plural = _('launch job statuses') - launch_job = models.ForeignKey('LaunchJob', null=True, on_delete=SET_NULL, related_name='launch_job_statuses') - status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') - result_stdout = models.TextField(blank=True, default='') - result_stderr = models.TextField(blank=True, default='') - result_traceback = models.TextField(blank=True, default='') - celery_task_id = models.CharField(max_length=100, blank=True, default='', editable=False) - hosts = models.ManyToManyField('Host', related_name='launch_job_statuses', blank=True, through='LaunchJobHostSummary') + job_template = models.ForeignKey( + 'JobTemplate', + related_name='jobs', + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + ) + job_type = models.CharField( + max_length=64, + choices=JOB_TYPE_CHOICES, + ) + inventory = models.ForeignKey( + 'Inventory', + related_name='jobs', + null=True, + on_delete=models.SET_NULL, + ) + credential = models.ForeignKey( + 'Credential', + related_name='jobs', + null=True, + on_delete=models.SET_NULL, + ) + project = models.ForeignKey( + 'Project', + related_name='jobs', + null=True, + on_delete=models.SET_NULL, + ) + user = models.ForeignKey( + 'auth.User', + related_name='jobs', + null=True, + on_delete=models.SET_NULL, + ) + status = models.CharField( + max_length=20, + choices=STATUS_CHOICES, + default='pending', + editable=False, + ) + result_stdout = models.TextField( + blank=True, + default='', + editable=False, + ) + result_stderr = models.TextField( + blank=True, + default='', + editable=False, + ) + result_traceback = models.TextField( + blank=True, + default='', + editable=False, + ) + celery_task_id = models.CharField( + max_length=100, + blank=True, + default='', + editable=False, + ) + hosts = models.ManyToManyField( + 'Host', + related_name='jobs', + blank=True, + editable=False, + through='JobHostSummary', + ) @property def celery_task(self): @@ -789,24 +869,46 @@ class LaunchJobStatus(CommonModel): except TaskMeta.DoesNotExist: pass - def save(self, *args, **kwargs): - super(LaunchJobStatus, self).save(*args, **kwargs) - # Create a new host summary for each host in the inventory. - for host in self.launch_job.inventory.hosts.all(): - # Due to the way the inventory script is called, hosts without a group won't be affected. - if host.groups.count(): - self.launch_job_host_summaries.get_or_create(host=host) + def _run(self): + from lib.main.tasks import run_job + task_result = run_job.delay(self.pk) + # The TaskMeta instance in the database isn't created until the worker + # starts processing the task, so we can only store the task ID here. + self.celery_task_id = task_result.task_id + self.save(update_fields=['celery_task_id']) -class LaunchJobHostSummary(models.Model): + def save(self, *args, **kwargs): + created = not bool(self.pk) + super(Job, self).save(*args, **kwargs) + # Create a new host summary for each host in the inventory. + for host in self.inventory.hosts.all(): + # Due to the way the inventory script is called, hosts without a + # group won't be included. + if host.groups.count(): + self.job_host_summaries.get_or_create(host=host) + # Start job running (but only if just created). + if created and self.status == 'pending': + self._run() + +class JobHostSummary(models.Model): + ''' + Per-host statistics for each job. + ''' class Meta: - unique_together = [('launch_job_status', 'host')] - verbose_name_plural = _('Launch Job Host Summaries') + unique_together = [('job', 'host')] + verbose_name_plural = _('Job Host Summaries') ordering = ('-pk',) - launch_job_status = models.ForeignKey('LaunchJobStatus', on_delete=models.CASCADE, related_name='launch_job_host_summaries') - host = models.ForeignKey('Host', on_delete=models.CASCADE, related_name='launch_job_host_summaries') - # FIXME: Can't use SET_NULL for host relationship because of unique constraint. + job = models.ForeignKey( + 'Job', + related_name='job_host_summaries', + on_delete=models.CASCADE, + ) + host = models.ForeignKey('Host', + related_name='job_host_summaries', + on_delete=models.CASCADE, + ) changed = models.PositiveIntegerField(default=0) dark = models.PositiveIntegerField(default=0) @@ -820,7 +922,7 @@ class LaunchJobHostSummary(models.Model): (self.host.name, self.changed, self.dark, self.failures, self.ok, self.processed, self.skipped) -class LaunchJobStatusEvent(models.Model): +class JobEvent(models.Model): ''' An event/message logged from the callback when running a job. ''' @@ -849,11 +951,30 @@ class LaunchJobStatusEvent(models.Model): class Meta: app_label = 'main' - launch_job_status = models.ForeignKey('LaunchJobStatus', related_name='launch_job_status_events', on_delete=CASCADE) - created = models.DateTimeField(auto_now_add=True) - event = models.CharField(max_length=100, choices=EVENT_TYPES) - event_data = JSONField(blank=True, default='') - host = models.ForeignKey('Host', blank=True, null=True, default=None, on_delete=SET_NULL, related_name='launch_job_status_events') + job = models.ForeignKey( + 'Job', + related_name='job_events', + on_delete=models.CASCADE, + ) + created = models.DateTimeField( + auto_now_add=True, + ) + event = models.CharField( + max_length=100, + choices=EVENT_TYPES, + ) + event_data = JSONField( + blank=True, + default='', + ) + host = models.ForeignKey( + 'Host', + related_name='job_events', + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + ) def __unicode__(self): return u'%s @ %s' % (self.get_event_display(), self.created.isoformat()) @@ -861,31 +982,36 @@ class LaunchJobStatusEvent(models.Model): def save(self, *args, **kwargs): try: if not self.host and self.event_data.get('host', ''): - # Make sure we're looking at only the hosts from this launch job's associated inventory. - self.host = self.launch_job_status.launch_job.inventory.hosts.get(name=self.event_data['host']) + self.host = self.job.inventory.hosts.get(name=self.event_data['host']) except (Host.DoesNotExist, AttributeError): pass - super(LaunchJobStatusEvent, self).save(*args, **kwargs) + super(JobEvent, self).save(*args, **kwargs) self.update_host_summary_from_stats() def update_host_summary_from_stats(self): if self.event != 'playbook_on_stats': return hostnames = set() - for v in self.event_data.values(): - hostnames.update(v.keys()) + try: + for v in self.event_data.values(): + hostnames.update(v.keys()) + except AttributeError: # In case event_data or v isn't a dict. + pass for hostname in hostnames: try: - host = self.launch_job_status.launch_job.inventory.hosts.get(name=hostname) + host = self.job.inventory.hosts.get(name=hostname) except Host.DoesNotExist: continue - host_summary = self.launch_job_status.launch_job_host_summaries.get_or_create(host=host)[0] + host_summary = self.job.job_host_summaries.get_or_create(host=host)[0] host_summary_changed = False for stat in ('changed', 'dark', 'failures', 'ok', 'processed', 'skipped'): - value = self.event_data.get(stat, {}).get(hostname, 0) - if getattr(host_summary, stat) != value: - setattr(host_summary, stat, value) - host_summary_changed = True + try: + value = self.event_data.get(stat, {}).get(hostname, 0) + if getattr(host_summary, stat) != value: + setattr(host_summary, stat, value) + host_summary_changed = True + except AttributeError: # in case event_data[stat] isn't a dict. + pass if host_summary_changed: host_summary.save() diff --git a/lib/main/tasks.py b/lib/main/tasks.py index d92315e68f..f22a5ee7b5 100644 --- a/lib/main/tasks.py +++ b/lib/main/tasks.py @@ -21,12 +21,13 @@ from celery import task from django.conf import settings from lib.main.models import * -@task(name='run_launch_job') -def run_launch_job(launch_job_status_pk): - launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status_pk) - launch_job_status.status = 'running' - launch_job_status.save() - launch_job = launch_job_status.launch_job +__all__ = ['run_job'] + +@task(name='run_job') +def run_job(job_pk): + job = Job.objects.get(pk=job_pk) + job.status = 'running' + job.save(update_fields=['status']) try: status, stdout, stderr, tb = 'error', '', '', '' @@ -40,17 +41,18 @@ def run_launch_job(launch_job_status_pk): 'acom_callback_event.py')) env = dict(os.environ.items()) # question: when running over CLI, generate a random ID or grab next, etc? - env['ACOM_LAUNCH_JOB_STATUS_ID'] = str(launch_job_status.pk) - env['ACOM_INVENTORY_ID'] = str(launch_job.inventory.pk) + # answer: TBD + env['ACOM_JOB_ID'] = str(job.pk) + env['ACOM_INVENTORY_ID'] = str(job.inventory.pk) env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir env['ACOM_CALLBACK_EVENT_SCRIPT'] = callback_script if hasattr(settings, 'ANSIBLE_TRANSPORT'): env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT') - playbook = launch_job.project.default_playbook + playbook = job.project.default_playbook cmdline = ['ansible-playbook', '-i', inventory_script] - if launch_job.job_type == 'check': + if job.job_type == 'check': cmdline.append('--check') cmdline.append(playbook) @@ -63,9 +65,10 @@ def run_launch_job(launch_job_status_pk): tb = traceback.format_exc() # Reload from database before updating/saving. - launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status_pk) - launch_job_status.status = status - launch_job_status.result_stdout = stdout - launch_job_status.result_stderr = stderr - launch_job_status.result_traceback = tb - launch_job_status.save() + job = Job.objects.get(pk=job_pk) + job.status = status + job.result_stdout = stdout + job.result_stderr = stderr + job.result_traceback = tb + job.save(update_fields=['status', 'result_stdout', 'result_stderr', + 'result_traceback']) diff --git a/lib/main/tests/__init__.py b/lib/main/tests/__init__.py index c8f2ea8b33..1825c9df8c 100644 --- a/lib/main/tests/__init__.py +++ b/lib/main/tests/__init__.py @@ -19,4 +19,4 @@ from lib.main.tests.users import UsersTest from lib.main.tests.inventory import InventoryTest from lib.main.tests.projects import ProjectsTest from lib.main.tests.commands import * -from lib.main.tests.tasks import RunLaunchJobTest +from lib.main.tests.tasks import RunJobTest diff --git a/lib/main/tests/commands.py b/lib/main/tests/commands.py index d429a6f13a..c6828f8c3a 100644 --- a/lib/main/tests/commands.py +++ b/lib/main/tests/commands.py @@ -138,7 +138,7 @@ class AcomInventoryTest(BaseCommandTest): def test_list_with_inventory_id_as_argument(self): inventory = self.inventories[0] result, stdout, stderr = self.run_command('acom_inventory', list=True, - inventory=inventory.pk) + inventory_id=inventory.pk) self.assertEqual(result, None) data = json.loads(stdout) self.assertEqual(set(data.keys()), @@ -156,7 +156,7 @@ class AcomInventoryTest(BaseCommandTest): invalid_id = [x for x in xrange(9999) if x not in inventory_pks][0] os.environ['ACOM_INVENTORY_ID'] = str(invalid_id) result, stdout, stderr = self.run_command('acom_inventory', list=True, - inventory=inventory.pk) + inventory_id=inventory.pk) self.assertEqual(result, None) data = json.loads(stdout) @@ -274,40 +274,38 @@ class AcomCallbackEventTest(BaseCommandTest): self.group = self.inventory.groups.create(name='test-group', inventory=self.inventory) self.group.hosts.add(self.host) - self.launch_job = LaunchJob.objects.create(name='test-launch-job', - inventory=self.inventory, - project=self.project) - self.launch_job_status = self.launch_job.launch_job_statuses.create( - name='launch-job-status-%s' % now().isoformat()) + self.job = Job.objects.create(name='job-%s' % now().isoformat(), + inventory=self.inventory, + project=self.project) self.valid_kwargs = { - 'launch_job_status_id': self.launch_job_status.id, + 'job_id': self.job.id, 'event_type': 'playbook_on_start', 'event_data_json': json.dumps({'test_event_data': [2,4,6]}), } - def test_with_launch_job_status_not_running(self): - # Events can only be added when the launch job is running. - self.assertEqual(self.launch_job_status.status, 'pending') + def test_with_job_status_not_running(self): + # Events can only be added when the job is running. + self.assertEqual(self.job.status, 'pending') result, stdout, stderr = self.run_command('acom_callback_event', **self.valid_kwargs) self.assertTrue(isinstance(result, CommandError)) self.assertTrue('unable to add event ' in str(result).lower()) - self.launch_job_status.status = 'successful' - self.launch_job_status.save() + self.job.status = 'successful' + self.job.save() result, stdout, stderr = self.run_command('acom_callback_event', **self.valid_kwargs) self.assertTrue(isinstance(result, CommandError)) self.assertTrue('unable to add event ' in str(result).lower()) - self.launch_job_status.status = 'failed' - self.launch_job_status.save() + self.job.status = 'failed' + self.job.save() result, stdout, stderr = self.run_command('acom_callback_event', **self.valid_kwargs) self.assertTrue(isinstance(result, CommandError)) self.assertTrue('unable to add event ' in str(result).lower()) def test_with_invalid_args(self): - self.launch_job_status.status = 'running' - self.launch_job_status.save() + self.job.status = 'running' + self.job.save() # Event type not given. kwargs = dict(self.valid_kwargs.items()) kwargs.pop('event_type') @@ -326,21 +324,21 @@ class AcomCallbackEventTest(BaseCommandTest): result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertTrue(isinstance(result, CommandError)) self.assertTrue('either --file or --data' in str(result).lower()) - # Non-integer launch job status ID. + # Non-integer job ID. kwargs = dict(self.valid_kwargs.items()) - kwargs['launch_job_status_id'] = 'foo' + kwargs['job_id'] = 'foo' result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertTrue(isinstance(result, CommandError)) self.assertTrue('id must be an integer' in str(result).lower()) - # No launch job status ID. + # No job ID. kwargs = dict(self.valid_kwargs.items()) - kwargs.pop('launch_job_status_id') + kwargs.pop('job_id') result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertTrue(isinstance(result, CommandError)) - self.assertTrue('no launch job status id' in str(result).lower()) - # Invalid launch job status ID. + self.assertTrue('no job id' in str(result).lower()) + # Invalid job ID. kwargs = dict(self.valid_kwargs.items()) - kwargs['launch_job_status_id'] = 9999 + kwargs['job_id'] = 9999 result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertTrue(isinstance(result, CommandError)) self.assertTrue('not found' in str(result).lower()) @@ -362,21 +360,21 @@ class AcomCallbackEventTest(BaseCommandTest): self.assertTrue('reading from' in str(result).lower()) def test_with_valid_args(self): - self.launch_job_status.status = 'running' - self.launch_job_status.save() + self.job.status = 'running' + self.job.save() # Default valid args. kwargs = dict(self.valid_kwargs.items()) result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertEqual(result, None) - self.assertEqual(self.launch_job_status.launch_job_status_events.count(), 1) - # Pass launch job status in environment instead. + self.assertEqual(self.job.job_events.count(), 1) + # Pass job ID in environment instead. kwargs = dict(self.valid_kwargs.items()) - kwargs.pop('launch_job_status_id') - os.environ['ACOM_LAUNCH_JOB_STATUS_ID'] = str(self.launch_job_status.id) + kwargs.pop('job_id') + os.environ['ACOM_JOB_ID'] = str(self.job.id) result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertEqual(result, None) - self.assertEqual(self.launch_job_status.launch_job_status_events.count(), 2) - os.environ.pop('ACOM_LAUNCH_JOB_STATUS_ID', None) + self.assertEqual(self.job.job_events.count(), 2) + os.environ.pop('ACOM_JOB_ID', None) # Test with JSON data in a file instead. kwargs = dict(self.valid_kwargs.items()) kwargs.pop('event_data_json') @@ -388,7 +386,7 @@ class AcomCallbackEventTest(BaseCommandTest): kwargs['event_data_file'] = tf result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertEqual(result, None) - self.assertEqual(self.launch_job_status.launch_job_status_events.count(), 3) + self.assertEqual(self.job.job_events.count(), 3) # Test with JSON data from stdin. kwargs = dict(self.valid_kwargs.items()) kwargs.pop('event_data_json') @@ -396,4 +394,4 @@ class AcomCallbackEventTest(BaseCommandTest): kwargs['stdin_fileobj'] = StringIO.StringIO(json.dumps({'blah': 'bleep'})) result, stdout, stderr = self.run_command('acom_callback_event', **kwargs) self.assertEqual(result, None) - self.assertEqual(self.launch_job_status.launch_job_status_events.count(), 4) + self.assertEqual(self.job.job_events.count(), 4) diff --git a/lib/main/tests/tasks.py b/lib/main/tests/tasks.py index 9c6147c18f..5d7864e6f2 100644 --- a/lib/main/tests/tasks.py +++ b/lib/main/tests/tasks.py @@ -46,13 +46,13 @@ class BaseCeleryTest(BaseTransactionTest): ''' @override_settings(ANSIBLE_TRANSPORT='local') -class RunLaunchJobTest(BaseCeleryTest): +class RunJobTest(BaseCeleryTest): ''' - Test cases for run_launch_job celery task. + Test cases for run_job celery task. ''' def setUp(self): - super(RunLaunchJobTest, self).setUp() + super(RunJobTest, self).setUp() self.setup_users() self.organization = self.make_organizations(self.super_django_user, 1)[0] self.project = self.make_projects(self.normal_django_user, 1)[0] @@ -65,14 +65,11 @@ class RunLaunchJobTest(BaseCeleryTest): self.group = self.inventory.groups.create(name='test-group', inventory=self.inventory) self.group.hosts.add(self.host) - self.launch_job = LaunchJob.objects.create(name='test-launch-job', - inventory=self.inventory, - project=self.project) # Pass test database name in environment for use by the inventory script. os.environ['ACOM_TEST_DATABASE_NAME'] = settings.DATABASES['default']['NAME'] def tearDown(self): - super(RunLaunchJobTest, self).tearDown() + super(RunJobTest, self).tearDown() os.environ.pop('ACOM_TEST_DATABASE_NAME', None) os.remove(self.test_playbook) @@ -84,84 +81,84 @@ class RunLaunchJobTest(BaseCeleryTest): self.project.default_playbook = self.test_playbook self.project.save() - def test_run_launch_job(self): + def test_run_job(self): self.create_test_playbook(TEST_PLAYBOOK) - launch_job_status = self.launch_job.start() - self.assertEqual(launch_job_status.status, 'pending') - self.assertEqual(set(launch_job_status.hosts.values_list('pk', flat=True)), + job = Job.objects.create(name='test-job', inventory=self.inventory, + project=self.project) + self.assertEqual(job.status, 'pending') + self.assertEqual(set(job.hosts.values_list('pk', flat=True)), set([self.host.pk])) - launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status.pk) + job = Job.objects.get(pk=job.pk) #print 'stdout:', launch_job_status.result_stdout #print 'stderr:', launch_job_status.result_stderr #print launch_job_status.status #print settings.DATABASES - self.assertEqual(launch_job_status.status, 'successful') - self.assertTrue(launch_job_status.result_stdout) - launch_job_status_events = launch_job_status.launch_job_status_events.all() + self.assertEqual(job.status, 'successful') + self.assertTrue(job.result_stdout) + job_events = job.job_events.all() #for ev in launch_job_status_events: # print ev.event, ev.event_data - self.assertEqual(launch_job_status_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_task_start').count(), 2) - self.assertEqual(launch_job_status_events.filter(event='runner_on_ok').count(), 2) - for evt in launch_job_status_events.filter(event='runner_on_ok'): + self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 2) + self.assertEqual(job_events.filter(event='runner_on_ok').count(), 2) + for evt in job_events.filter(event='runner_on_ok'): self.assertEqual(evt.host, self.host) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_stats').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) - def test_check_launch_job(self): + def test_check_job(self): self.create_test_playbook(TEST_PLAYBOOK) - self.launch_job.job_type = 'check' - self.launch_job.save() - launch_job_status = self.launch_job.start() - self.assertEqual(launch_job_status.status, 'pending') - self.assertEqual(set(launch_job_status.hosts.values_list('pk', flat=True)), + job = Job.objects.create(name='test-job', inventory=self.inventory, + project=self.project, job_type='check') + self.assertEqual(job.status, 'pending') + self.assertEqual(set(job.hosts.values_list('pk', flat=True)), set([self.host.pk])) - launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status.pk) - self.assertEqual(launch_job_status.status, 'successful') - self.assertTrue(launch_job_status.result_stdout) - launch_job_status_events = launch_job_status.launch_job_status_events.all() - self.assertEqual(launch_job_status_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_task_start').count(), 2) - self.assertEqual(launch_job_status_events.filter(event='runner_on_skipped').count(), 2) - for evt in launch_job_status_events.filter(event='runner_on_skipped'): + job = Job.objects.get(pk=job.pk) + self.assertEqual(job.status, 'successful') + self.assertTrue(job.result_stdout) + job_events = job.job_events.all() + self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 2) + self.assertEqual(job_events.filter(event='runner_on_skipped').count(), 2) + for evt in job_events.filter(event='runner_on_skipped'): self.assertEqual(evt.host, self.host) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_stats').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) - def test_run_launch_job_that_fails(self): + def test_run_job_that_fails(self): self.create_test_playbook(TEST_PLAYBOOK2) - launch_job_status = self.launch_job.start() - self.assertEqual(launch_job_status.status, 'pending') - self.assertEqual(set(launch_job_status.hosts.values_list('pk', flat=True)), + job = Job.objects.create(name='test-job', inventory=self.inventory, + project=self.project) + self.assertEqual(job.status, 'pending') + self.assertEqual(set(job.hosts.values_list('pk', flat=True)), set([self.host.pk])) - launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status.pk) - self.assertEqual(launch_job_status.status, 'failed') - self.assertTrue(launch_job_status.result_stdout) - launch_job_status_events = launch_job_status.launch_job_status_events.all() - self.assertEqual(launch_job_status_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_task_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='runner_on_failed').count(), 1) - self.assertEqual(launch_job_status_events.get(event='runner_on_failed').host, self.host) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_stats').count(), 1) + job = Job.objects.get(pk=job.pk) + self.assertEqual(job.status, 'failed') + self.assertTrue(job.result_stdout) + job_events = job.job_events.all() + self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 1) + self.assertEqual(job_events.filter(event='runner_on_failed').count(), 1) + self.assertEqual(job_events.get(event='runner_on_failed').host, self.host) + self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) - def test_check_launch_job_where_task_would_fail(self): + def test_check_job_where_task_would_fail(self): self.create_test_playbook(TEST_PLAYBOOK2) - self.launch_job.job_type = 'check' - self.launch_job.save() - launch_job_status = self.launch_job.start() - self.assertEqual(launch_job_status.status, 'pending') - self.assertEqual(set(launch_job_status.hosts.values_list('pk', flat=True)), + job = Job.objects.create(name='test-job', inventory=self.inventory, + project=self.project, job_type='check') + self.assertEqual(job.status, 'pending') + self.assertEqual(set(job.hosts.values_list('pk', flat=True)), set([self.host.pk])) - launch_job_status = LaunchJobStatus.objects.get(pk=launch_job_status.pk) + job = Job.objects.get(pk=job.pk) # Since we don't actually run the task, the --check should indicate # everything is successful. - self.assertEqual(launch_job_status.status, 'successful') - self.assertTrue(launch_job_status.result_stdout) - launch_job_status_events = launch_job_status.launch_job_status_events.all() - self.assertEqual(launch_job_status_events.filter(event='playbook_on_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_play_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_task_start').count(), 1) - self.assertEqual(launch_job_status_events.filter(event='runner_on_skipped').count(), 1) - self.assertEqual(launch_job_status_events.get(event='runner_on_skipped').host, self.host) - self.assertEqual(launch_job_status_events.filter(event='playbook_on_stats').count(), 1) + self.assertEqual(job.status, 'successful') + self.assertTrue(job.result_stdout) + job_events = job.job_events.all() + self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1) + self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 1) + self.assertEqual(job_events.filter(event='runner_on_skipped').count(), 1) + self.assertEqual(job_events.get(event='runner_on_skipped').host, self.host) + self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1) diff --git a/lib/plugins/callback/acom_callback.py b/lib/plugins/callback/acom_callback.py index a1af1c8ecc..b06fe695b9 100644 --- a/lib/plugins/callback/acom_callback.py +++ b/lib/plugins/callback/acom_callback.py @@ -1,34 +1,36 @@ # Copyright (c) 2013 AnsibleWorks, Inc. -# This file is a utility Ansible plugin that is not part of Ansible Commander or Ansible. -# (it does not import any ansible-commander code, nor does it's license apply to Ansible -# or Ansible Commander) +# This file is a utility Ansible plugin that is not part of Ansible Commander +# or Ansible. It does not import any ansible-commander code, nor does its +# license apply to Ansible or Ansible Commander. # # Copyright (c) 2013, AnsibleWorks Inc. # All rights reserved. # -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Redistributions of source code must retain the above copyright notice, this list -# of conditions and the following disclaimer. +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. # -# Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation and/or -# other materials provided with the distribution. +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. # -# Neither the name of the nor the names of its contributors may be -# used to endorse or promote products derived from this software without specific -# prior written permission. +# Neither the name of the nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -# OF SUCH DAMAGE. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. import json import os @@ -41,11 +43,11 @@ class CallbackModule(object): ''' def __init__(self): - self.acom_callback_event_script = os.getenv('ACOM_CALLBACK_EVENT_SCRIPT') + self.callback_script = os.getenv('ACOM_CALLBACK_EVENT_SCRIPT') def _log_event(self, event, **event_data): event_data_json = json.dumps(event_data) - cmdline = [self.acom_callback_event_script, '-e', event, '-d', event_data_json] + cmdline = [self.callback_script, '-e', event, '-d', event_data_json] subprocess.check_call(cmdline) def on_any(self, *args, **kwargs): @@ -117,5 +119,3 @@ class CallbackModule(object): for attr in ('changed', 'dark', 'failures', 'ok', 'processed', 'skipped'): d[attr] = getattr(stats, attr) self._log_event('playbook_on_stats', **d) - - diff --git a/lib/templates/admin/base_site.html b/lib/templates/admin/base_site.html index 7de1915db6..344e152825 100644 --- a/lib/templates/admin/base_site.html +++ b/lib/templates/admin/base_site.html @@ -116,10 +116,10 @@ pre.result-display { max-height: 300px; overflow: auto; } -#launch_job_host_summaries-group table td.original p { +#job_host_summaries-group table td.original p { display: none } -#launch_job_host_summaries-group table tr.has_original td { +#job_host_summaries-group table tr.has_original td { padding-top: 5px; }