1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 18:21:12 +03:00

AC-382, AC-352. Added status and last_updated fields for projects API. Various other updates to support projects using SCM.

This commit is contained in:
Chris Church 2013-09-05 01:19:52 -04:00
parent 15fbf95c2a
commit 72d87fb908
10 changed files with 750 additions and 72 deletions

View File

@ -569,7 +569,7 @@ class ProjectAccess(BaseAccess):
def get_queryset(self):
qs = Project.objects.filter(active=True).distinct()
qs = qs.select_related('created_by')
qs = qs.select_related('created_by', 'current_update', 'last_update')
if self.user.is_superuser:
return qs
allowed = [PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]

View File

@ -14,21 +14,27 @@ from django.utils.dateparse import parse_datetime
from django.utils.timezone import now, is_aware, make_aware
# AWX
from awx.main.models import Job
from awx.main.models import ProjectUpdate, Job
class Command(NoArgsCommand):
'''
Management command to cleanup old jobs.
Management command to cleanup old jobs and project updates.
'''
help = 'Remove old jobs and events from the database.'
help = 'Remove old jobs and project updates from the database.'
option_list = NoArgsCommand.option_list + (
make_option('--days', dest='days', type='int', default=90, metavar='N',
help='Remove jobs executed more than N days ago'),
help='Remove jobs/updates executed more than N days ago'),
make_option('--dry-run', dest='dry_run', action='store_true',
default=False, help='Dry run mode (show items that would '
'be removed)'),
make_option('--jobs', dest='only_jobs', action='store_true',
default=False,
help='Only remove jobs (leave project updates alone)'),
make_option('--project-updates', dest='only_project_updates',
action='store_true', default=False,
help='Only remove project updates (leave jobs alone)'),
)
def cleanup_jobs(self):
@ -38,7 +44,7 @@ class Command(NoArgsCommand):
job_display = '"%s" (started %s, %d host summaries, %d events)' % \
(unicode(job), unicode(job.created),
job.job_host_summaries.count(), job.job_events.count())
if job.status in ('pending', 'running'):
if job.status in ('pending', 'waiting', 'running'):
action_text = 'would skip' if self.dry_run else 'skipping'
self.logger.debug('%s %s job %s', action_text, job.status, job_display)
elif job.created >= self.cutoff:
@ -50,6 +56,24 @@ class Command(NoArgsCommand):
if not self.dry_run:
job.delete()
def cleanup_project_updates(self):
for pu in ProjectUpdate.objects.all():
pu_display = '"%s" (started %s)' % (unicode(pu), unicode(pu.created))
if pu.status in ('pending', 'waiting', 'running'):
action_text = 'would skip' if self.dry_run else 'skipping'
self.logger.debug('%s %s project update %s', action_text, pu.status, pu_display)
if pu in (pu.project.current_update, pu.project.last_update) and pu.project.scm_type:
action_text = 'would skip' if self.dry_run else 'skipping'
self.logger.debug('%s %s', action_text, pu_display)
elif pu.created >= self.cutoff:
action_text = 'would skip' if self.dry_run else 'skipping'
self.logger.debug('%s %s', action_text, pu_display)
else:
action_text = 'would delete' if self.dry_run else 'deleting'
self.logger.info('%s %s', action_text, pu_display)
if not self.dry_run:
pu.delete()
def init_logging(self):
log_levels = dict(enumerate([logging.ERROR, logging.INFO,
logging.DEBUG, 0]))
@ -67,4 +91,9 @@ class Command(NoArgsCommand):
self.days = int(options.get('days', 90))
self.dry_run = bool(options.get('dry_run', False))
self.cutoff = now() - datetime.timedelta(days=self.days)
self.cleanup_jobs()
self.only_jobs = bool(options.get('only_jobs', False))
self.only_project_updates = bool(options.get('only_project_updates', False))
if self.only_jobs or (not self.only_jobs and not self.only_project_updates):
self.cleanup_jobs()
if self.only_project_updates or (not self.only_jobs and not self.only_project_updates):
self.cleanup_project_updates()

View File

@ -0,0 +1,521 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
from django.utils.timezone import now
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'ProjectUpdate.modified'
db.add_column(u'main_projectupdate', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'ProjectUpdate.modified_by'
db.add_column(u'main_projectupdate', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'projectupdate', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'Job.modified'
db.add_column(u'main_job', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Job.modified_by'
db.add_column(u'main_job', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'job', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'Inventory.modified'
db.add_column(u'main_inventory', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Inventory.modified_by'
db.add_column(u'main_inventory', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'inventory', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'Host.modified'
db.add_column(u'main_host', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Host.modified_by'
db.add_column(u'main_host', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'host', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'JobHostSummary.created'
db.add_column(u'main_jobhostsummary', 'created',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now_add=True, blank=True),
keep_default=False)
# Adding field 'JobHostSummary.modified'
db.add_column(u'main_jobhostsummary', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Group.modified'
db.add_column(u'main_group', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Group.modified_by'
db.add_column(u'main_group', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'group', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'Credential.modified'
db.add_column(u'main_credential', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Credential.modified_by'
db.add_column(u'main_credential', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'credential', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'JobTemplate.modified'
db.add_column(u'main_jobtemplate', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'JobTemplate.modified_by'
db.add_column(u'main_jobtemplate', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'jobtemplate', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'Team.modified'
db.add_column(u'main_team', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Team.modified_by'
db.add_column(u'main_team', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'team', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'JobEvent.modified'
db.add_column(u'main_jobevent', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Project.modified'
db.add_column(u'main_project', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Project.modified_by'
db.add_column(u'main_project', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'project', 'app_label': u'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'Project.current_update'
db.add_column(u'main_project', 'current_update',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='project_as_current_update+', null=True, to=orm['main.ProjectUpdate']),
keep_default=False)
# Changing field 'Project.scm_url'
db.alter_column(u'main_project', 'scm_url', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
# Adding field 'Organization.modified'
db.add_column(u'main_organization', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Organization.modified_by'
db.add_column(u'main_organization', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'organization', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Adding field 'Permission.modified'
db.add_column(u'main_permission', 'modified',
self.gf('django.db.models.fields.DateTimeField')(default=now, auto_now=True, blank=True),
keep_default=False)
# Adding field 'Permission.modified_by'
db.add_column(u'main_permission', 'modified_by',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name="{'class': 'permission', 'app_label': 'main'}(class)s_modified+", null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
def backwards(self, orm):
# Deleting field 'ProjectUpdate.modified'
db.delete_column(u'main_projectupdate', 'modified')
# Deleting field 'ProjectUpdate.modified_by'
db.delete_column(u'main_projectupdate', 'modified_by_id')
# Deleting field 'Job.modified'
db.delete_column(u'main_job', 'modified')
# Deleting field 'Job.modified_by'
db.delete_column(u'main_job', 'modified_by_id')
# Deleting field 'Inventory.modified'
db.delete_column(u'main_inventory', 'modified')
# Deleting field 'Inventory.modified_by'
db.delete_column(u'main_inventory', 'modified_by_id')
# Deleting field 'Host.modified'
db.delete_column(u'main_host', 'modified')
# Deleting field 'Host.modified_by'
db.delete_column(u'main_host', 'modified_by_id')
# Deleting field 'JobHostSummary.created'
db.delete_column(u'main_jobhostsummary', 'created')
# Deleting field 'JobHostSummary.modified'
db.delete_column(u'main_jobhostsummary', 'modified')
# Deleting field 'Group.modified'
db.delete_column(u'main_group', 'modified')
# Deleting field 'Group.modified_by'
db.delete_column(u'main_group', 'modified_by_id')
# Deleting field 'Credential.modified'
db.delete_column(u'main_credential', 'modified')
# Deleting field 'Credential.modified_by'
db.delete_column(u'main_credential', 'modified_by_id')
# Deleting field 'JobTemplate.modified'
db.delete_column(u'main_jobtemplate', 'modified')
# Deleting field 'JobTemplate.modified_by'
db.delete_column(u'main_jobtemplate', 'modified_by_id')
# Deleting field 'Team.modified'
db.delete_column(u'main_team', 'modified')
# Deleting field 'Team.modified_by'
db.delete_column(u'main_team', 'modified_by_id')
# Deleting field 'JobEvent.modified'
db.delete_column(u'main_jobevent', 'modified')
# Deleting field 'Project.modified'
db.delete_column(u'main_project', 'modified')
# Deleting field 'Project.modified_by'
db.delete_column(u'main_project', 'modified_by_id')
# Deleting field 'Project.current_update'
db.delete_column(u'main_project', 'current_update_id')
# Changing field 'Project.scm_url'
db.alter_column(u'main_project', 'scm_url', self.gf('django.db.models.fields.URLField')(max_length=1024, null=True))
# Deleting field 'Organization.modified'
db.delete_column(u'main_organization', 'modified')
# Deleting field 'Organization.modified_by'
db.delete_column(u'main_organization', 'modified_by_id')
# Deleting field 'Permission.modified'
db.delete_column(u'main_permission', 'modified')
# Deleting field 'Permission.modified_by'
db.delete_column(u'main_permission', 'modified_by_id')
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.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'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'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'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'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', '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_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'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': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'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'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': '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']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'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'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'current_update': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'project_as_current_update+'", 'null': 'True', 'to': "orm['main.ProjectUpdate']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_update': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'project_as_last_update+'", 'null': 'True', 'to': "orm['main.ProjectUpdate']"}),
'last_update_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'null': 'True', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
'scm_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'scm_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'null': 'True', 'blank': 'True'}),
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'scm_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'null': 'True', 'blank': 'True'})
},
'main.projectupdate': {
'Meta': {'object_name': 'ProjectUpdate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'projectupdate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'projectupdate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': u"orm['main.Project']"}),
'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': "'new'", 'max_length': '20'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'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']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']

View File

@ -2,6 +2,7 @@
# All Rights Reserved.
# Python
import datetime
import hmac
import json
import logging
@ -19,7 +20,7 @@ from django.db.models import CASCADE, SET_NULL, PROTECT
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.utils.timezone import now
from django.utils.timezone import now, make_aware, get_default_timezone
# Django-JSONField
from jsonfield import JSONField
@ -67,6 +68,7 @@ PERMISSION_TYPE_CHOICES = [
JOB_STATUS_CHOICES = [
('new', _('New')), # Job has been created, but not started.
('pending', _('Pending')), # Job has been queued, but is not yet running.
('waiting', _('Waiting')), # Job is waiting on an update/dependency.
('running', _('Running')), # Job is currently running.
('successful', _('Successful')), # Job completed successfully.
('failed', _('Failed')), # Job completed, but with failures.
@ -85,11 +87,16 @@ 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',
editable=False) # not blank=False on purpose for admin!
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True, default=now)
created_by = models.ForeignKey('auth.User',
related_name='%s(class)s_created+',
default=None, null=True, editable=False,
on_delete=models.SET_NULL)
modified_by = models.ForeignKey('auth.User',
related_name='%s(class)s_modified+',
default=None, null=True, editable=False,
on_delete=models.SET_NULL)
active = models.BooleanField(default=True)
tags = TaggableManager(blank=True)
@ -522,9 +529,6 @@ class Project(CommonModel):
local_path = models.CharField(
max_length=1024,
# Not unique for now, otherwise "deletes" won't allow reusing the
# same path for another active project.
#unique=True,
blank=True,
help_text=_('Local path (relative to PROJECTS_ROOT) containing '
'playbooks and related files for this project.')
@ -538,7 +542,7 @@ class Project(CommonModel):
default='',
verbose_name=_('SCM Type'),
)
scm_url = models.URLField(
scm_url = models.CharField(
max_length=1024,
blank=True,
null=True,
@ -598,6 +602,13 @@ class Project(CommonModel):
help_text=_('Passphrase to unlock SSH private key if encrypted (or '
'"ASK" to prompt the user).'),
)
current_update = models.ForeignKey(
'ProjectUpdate',
null=True,
default=None,
editable=False,
related_name='project_as_current_update+',
)
last_update = models.ForeignKey(
'ProjectUpdate',
null=True,
@ -611,12 +622,8 @@ class Project(CommonModel):
)
# FIXME: Still need to implement:
# - some scm_url validation
# - scm_update_on_launch
# - prompt for passwords for project update
# - prompt for passwords when running job when scm_update_on_launch set
# - prevent simultaneous updates of project and running jobs using project
# - prevent manually setting local path when scm_type is set
# - masking passwords in project update args/stdout
def save(self, *args, **kwargs):
@ -650,8 +657,45 @@ class Project(CommonModel):
needed.append(field)
return needed
def update(self, **kwargs):
@property
def status(self):
# FIXME: Update status values!
if self.scm_type:
if self.current_update:
return 'updating'
elif not self.last_update:
return 'never updated'
elif self.last_update_failed:
return 'failed'
elif not self.get_project_path():
return 'missing'
else:
return 'successsful'
elif not self.get_project_path():
return 'missing'
else:
return 'ok'
@property
def last_updated(self):
if self.scm_type and self.last_update:
return self.last_update.modified
else:
project_path = self.get_project_path()
if project_path:
try:
mtime = os.path.getmtime(project_path)
dt = datetime.datetime.fromtimestamp(mtime)
return make_aware(dt, get_default_timezone())
except os.error:
pass
@property
def can_update(self):
return bool(self.scm_type and not self.current_update)
def update(self, **kwargs):
if self.can_update:
needed = self.scm_passwords_needed
opts = dict([(field, kwargs.get(field, '')) for field in needed])
if not all(opts.values()):
@ -660,10 +704,6 @@ class Project(CommonModel):
project_update.start(**opts)
return project_update
@property
def active_updates(self):
return self.project_updates.filter(active=True, status__in=('new', 'pending', 'running'))
def get_absolute_url(self):
return reverse('main:project_detail', args=(self.pk,))
@ -779,15 +819,24 @@ class ProjectUpdate(PrimordialModel):
status_before = project_update_before.status
self.failed = bool(self.status in ('failed', 'error', 'canceled'))
super(ProjectUpdate, self).save(*args, **kwargs)
# If status changed, and update has completed, update project.
# If status changed, update project.
if self.status != status_before:
if self.status in ('successful', 'failed', 'error', 'canceled'):
if self.status in ('pending', 'waiting', 'running'):
project = self.project
if project.current_update != self:
project.current_update = self
project.save(update_fields=['current_update'])
elif self.status in ('successful', 'failed', 'error', 'canceled'):
project = self.project
if project.current_update == self:
project.current_update = None
project.last_update = self
project.last_update_failed = self.failed
if not self.failed and project.scm_delete_on_next_update:
project.scm_delete_on_next_update = False
project.save()
project.save(update_fields=['current_update', 'last_update',
'last_update_failed',
'scm_delete_on_next_update'])
def get_absolute_url(self):
return reverse('main:project_update_detail', args=(self.pk,))
@ -800,10 +849,6 @@ class ProjectUpdate(PrimordialModel):
except TaskMeta.DoesNotExist:
pass
def get_passwords_needed_to_start(self):
'''Return list of password field names needed to start the job.'''
return (self.credential and self.credential.passwords_needed) or []
@property
def can_start(self):
return bool(self.status == 'new')
@ -830,7 +875,7 @@ class ProjectUpdate(PrimordialModel):
@property
def can_cancel(self):
return bool(self.status in ('pending', 'running'))
return bool(self.status in ('pending', 'waiting', 'running'))
def cancel(self):
if self.can_cancel:
@ -1151,9 +1196,15 @@ class Job(CommonModelNameNotUnique):
h = hmac.new(settings.SECRET_KEY, self.created.isoformat())
return '%d-%s' % (self.pk, h.hexdigest())
def get_passwords_needed_to_start(self):
@property
def passwords_needed_to_start(self):
'''Return list of password field names needed to start the job.'''
return (self.credential and self.credential.passwords_needed) or []
needed = []
if self.credential:
needed.extend(self.credential.passwords_needed)
if self.project.scm_update_on_launch:
needed.extend(self.project.scm_passwords_needed)
return needed
@property
def can_start(self):
@ -1163,7 +1214,7 @@ class Job(CommonModelNameNotUnique):
from awx.main.tasks import RunJob
if not self.can_start:
return False
needed = self.get_passwords_needed_to_start()
needed = self.passwords_needed_to_start
opts = dict([(field, kwargs.get(field, '')) for field in needed])
if not all(opts.values()):
return False
@ -1181,7 +1232,7 @@ class Job(CommonModelNameNotUnique):
@property
def can_cancel(self):
return bool(self.status in ('pending', 'running'))
return bool(self.status in ('pending', 'waiting', 'running'))
def cancel(self):
if self.can_cancel:
@ -1245,6 +1296,14 @@ class JobHostSummary(models.Model):
on_delete=models.CASCADE,
editable=False,
)
created = models.DateTimeField(
auto_now_add=True,
default=now,
)
modified = models.DateTimeField(
auto_now=True,
default=now,
)
changed = models.PositiveIntegerField(default=0, editable=False)
dark = models.PositiveIntegerField(default=0, editable=False)
@ -1353,6 +1412,11 @@ class JobEvent(models.Model):
)
created = models.DateTimeField(
auto_now_add=True,
default=now,
)
modified = models.DateTimeField(
auto_now=True,
default=now,
)
event = models.CharField(
max_length=100,

View File

@ -3,6 +3,7 @@
# Python
import json
import urlparse
# PyYAML
import yaml
@ -21,8 +22,8 @@ from rest_framework import serializers
# AWX
from awx.main.models import *
BASE_FIELDS = ('id', 'url', 'related', 'summary_fields', 'created', 'name',
'description')
BASE_FIELDS = ('id', 'url', 'related', 'summary_fields', 'created', 'modified',
'name', 'description')
# objects that if found we should add summary info for them
SUMMARIZABLE_FKS = (
@ -43,6 +44,7 @@ class BaseSerializer(serializers.ModelSerializer):
# make certain fields read only
created = serializers.SerializerMethodField('get_created')
modified = serializers.SerializerMethodField('get_modified')
active = serializers.SerializerMethodField('get_active')
def get_fields(self):
@ -63,6 +65,9 @@ class BaseSerializer(serializers.ModelSerializer):
elif key == 'created':
field.help_text = 'Timestamp when this %s was created.' % unicode(opts.verbose_name)
field.type_label = 'datetime'
elif key == 'modified':
field.help_text = 'Timestamp when this %s was last modified.' % unicode(opts.verbose_name)
field.type_label = 'datetime'
return ret
def get_url(self, obj):
@ -101,6 +106,12 @@ class BaseSerializer(serializers.ModelSerializer):
else:
return obj.created
def get_modified(self, obj):
if isinstance(obj, User):
return obj.last_login # Not actually exposed for User.
else:
return obj.modified
def get_active(self, obj):
if isinstance(obj, User):
return obj.is_active
@ -182,6 +193,8 @@ class ProjectSerializer(BaseSerializer):
playbooks = serializers.Field(source='playbooks', help_text='Array of playbooks available within this project.')
scm_delete_on_next_update = serializers.Field(source='scm_delete_on_next_update')
status = serializers.Field(source='status')
last_updated = serializers.Field(source='last_updated')
class Meta:
model = Project
@ -190,7 +203,7 @@ class ProjectSerializer(BaseSerializer):
'scm_delete_on_update', 'scm_delete_on_next_update',
'scm_update_on_launch',
'scm_username', 'scm_password', 'scm_key_data',
'scm_key_unlock', 'last_update_failed')
'scm_key_unlock', 'last_update_failed', 'status', 'last_updated')
def get_related(self, obj):
res = super(ProjectSerializer, self).get_related(obj)
@ -201,6 +214,9 @@ class ProjectSerializer(BaseSerializer):
update = reverse('main:project_update_view', args=(obj.pk,)),
project_updates = reverse('main:project_updates_list', args=(obj.pk,)),
))
if obj.current_update:
res['current_update'] = reverse('main:project_update_detail',
args=(obj.current_update.pk,))
if obj.last_update:
res['last_update'] = reverse('main:project_update_detail',
args=(obj.last_update.pk,))
@ -208,13 +224,32 @@ class ProjectSerializer(BaseSerializer):
def validate_local_path(self, attrs, source):
# Don't allow assigning a local_path used by another project.
# Don't allow assigning a local_path when scm_type is set.
print attrs, source, self.object
valid_local_paths = Project.get_local_path_choices()
if self.object:
valid_local_paths.append(self.object.local_path)
scm_type = attrs.get('scm_type', self.object.scm_type)
if not scm_type:
valid_local_paths.append(self.object.local_path)
else:
scm_type = attrs.get('scm_type', '')
if scm_type:
attrs.pop(source, None)
if source in attrs and attrs[source] not in valid_local_paths:
raise serializers.ValidationError('Invalid path choice')
return attrs
def validate_scm_url(self, attrs, source):
if self.object:
scm_type = attrs.get('scm_type', self.object.scm_type)
else:
scm_type = attrs.get('scm_type', '')
scm_url = unicode(attrs.get(source, None) or '')
scm_url_parts = urlparse.urlsplit(scm_url)
if scm_type and not any(scm_url_parts):
raise serializers.ValidationError('SCM URL must be provided')
return attrs
class ProjectPlaybooksSerializer(ProjectSerializer):
class Meta:
@ -230,7 +265,7 @@ class ProjectUpdateSerializer(BaseSerializer):
class Meta:
model = ProjectUpdate
fields = ('id', 'url', 'related', 'summary_fields', 'created',
'project', 'status', 'failed', 'result_stdout',
'modified', 'project', 'status', 'failed', 'result_stdout',
'result_traceback', 'job_args', 'job_cwd', 'job_env')
def get_related(self, obj):
@ -530,9 +565,9 @@ class JobHostSummarySerializer(BaseSerializer):
class Meta:
model = JobHostSummary
fields = ('id', 'url', 'job', 'host', 'summary_fields', 'related',
'changed', 'dark', 'failures', 'ok', 'processed', 'skipped',
'failed')
fields = ('id', 'url', 'job', 'host', 'created', 'modified',
'summary_fields', 'related', 'changed', 'dark', 'failures',
'ok', 'processed', 'skipped', 'failed')
def get_related(self, obj):
res = super(JobHostSummarySerializer, self).get_related(obj)
@ -549,9 +584,10 @@ class JobEventSerializer(BaseSerializer):
class Meta:
model = JobEvent
fields = ('id', 'url', 'created', 'job', 'event', 'event_display',
'event_data', 'event_level', 'failed', 'changed', 'host',
'related', 'summary_fields', 'parent', 'play', 'task')
fields = ('id', 'url', 'created', 'modified', 'job', 'event',
'event_display', 'event_data', 'event_level', 'failed',
'changed', 'host', 'related', 'summary_fields', 'parent',
'play', 'task')
def get_related(self, obj):
res = super(JobEventSerializer, self).get_related(obj)

View File

@ -30,10 +30,13 @@ __all__ = ['RunJob', 'RunProjectUpdate']
logger = logging.getLogger('awx.main.tasks')
# FIXME: Cleanly cancel task when celery worker is stopped.
class BaseTask(Task):
name = None
model = None
idle_timeout = None
def update_model(self, pk, **updates):
'''
@ -132,10 +135,10 @@ class BaseTask(Task):
expect_passwords[n] = passwords.get(item[1], '') or ''
expect_list.extend([pexpect.TIMEOUT, pexpect.EOF])
while child.isalive():
result_id = child.expect(expect_list, timeout=2)
result_id = child.expect(expect_list, timeout=5)
if result_id in expect_passwords:
child.sendline(expect_passwords[result_id])
updates = {}
updates = {'status': 'running'}
if logfile_pos != logfile.tell():
logfile_pos = logfile.tell()
updates['result_stdout'] = logfile.getvalue()
@ -144,11 +147,11 @@ class BaseTask(Task):
if instance.cancel_flag:
child.close(True)
canceled = True
elif (time.time() - last_stdout_update) > 30: # FIXME: Configurable idle timeout?
print 'no updates...'
# print 'canceling...'
# child.close(True)
# canceled = True
# FIXME: Configurable idle timeout? Find a way to determine if task
# is hung waiting at a prompt.
if self.idle_timeout and (time.time() - last_stdout_update) > self.idle_timeout:
child.close(True)
canceled = True
if canceled:
status = 'canceled'
elif child.exitstatus == 0:
@ -166,10 +169,17 @@ class BaseTask(Task):
return False
return True
def post_run_hook(self, instance):
'''
Hook for actions after job/task has completed.
'''
def run(self, pk, **kwargs):
'''
Run the job/task using ansible-playbook and capture its output.
'''
self.pk = pk
self.kwargs = dict(kwargs.items())
instance = self.update_model(pk)
if not self.pre_run_check(instance, **kwargs):
return
@ -193,8 +203,9 @@ class BaseTask(Task):
os.remove(kwargs['ssh_key_path'])
except IOError:
pass
self.update_model(pk, status=status, result_stdout=stdout,
result_traceback=tb)
instance = self.update_model(pk, status=status, result_stdout=stdout,
result_traceback=tb)
self.post_run_hook(instance)
class RunJob(BaseTask):
'''
@ -313,12 +324,21 @@ class RunJob(BaseTask):
if not super(RunJob, self).pre_run_check(job, **kwargs):
return False
# FIXME: Check if job is waiting on any projects that are being updated.
if job.project.has_active_updates:
pass
return True
def post_run_hook(self, job):
'''
Hook for actions after job has completed.
'''
# Start any project updates that were blocked waiting for the job.
class RunProjectUpdate(BaseTask):
name = 'run_project_update'
model = ProjectUpdate
idle_timeout = 30
def build_passwords(self, project_update, **kwargs):
'''
@ -359,6 +379,8 @@ class RunProjectUpdate(BaseTask):
optionally using ssh-agent for public/private key authentication.
'''
args = ['ansible-playbook', '-i', 'localhost,']
# Since we specify -vvv and tasks use async polling, we should get some
# output regularly...
args.append('-%s' % ('v' * 3))
project = project_update.project
scm_url = project.scm_url
@ -375,8 +397,6 @@ class RunProjectUpdate(BaseTask):
'scm_url': scm_url,
'scm_branch': scm_branch,
'scm_clean': project.scm_clean,
#'scm_username': project.scm_username,
#'scm_password': project.scm_password,
'scm_delete_on_update': scm_delete_on_update,
}
args.extend(['-e', json.dumps(extra_vars)])
@ -408,4 +428,13 @@ class RunProjectUpdate(BaseTask):
if not super(RunProjectUpdate, self).pre_run_check(project_update, **kwargs):
return False
# FIXME: Check if project update is blocked by any jobs that are being run.
project = project_update.project
if project.jobs.filter(status__in=('pending', 'waiting', 'running')):
pass
return True
def post_run_hook(self, project_update):
'''
Hook for actions after project_update has completed.
'''
# Start any jobs waiting on this update to finish.

View File

@ -778,6 +778,8 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
# Sue can start a job (when passwords are already saved) as long as the
# status is new. Reverse list so "new" will be last.
for status in reversed([x[0] for x in JOB_STATUS_CHOICES]):
if status == 'waiting':
continue
job.status = status
job.save()
with self.current_user(self.user_sue):
@ -864,6 +866,8 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
# sue can cancel the job, but only when it is pending or running.
for status in [x[0] for x in JOB_STATUS_CHOICES]:
if status == 'waiting':
continue
job.status = status
job.save()
with self.current_user(self.user_sue):

View File

@ -268,7 +268,7 @@ class ProjectUpdateView(GenericAPIView):
def get(self, request, *args, **kwargs):
obj = self.get_object()
data = dict(
can_update=bool(obj.scm_type),
can_update=obj.can_update,
)
if obj.scm_type:
data['passwords_needed_to_update'] = obj.scm_passwords_needed
@ -276,13 +276,14 @@ class ProjectUpdateView(GenericAPIView):
def post(self, request, *args, **kwargs):
obj = self.get_object()
if bool(obj.scm_type):
if obj.can_update:
project_update = obj.update(**request.DATA)
if not project_update:
data = dict(passwords_needed_to_update=obj.scm_passwords_needed)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(status=status.HTTP_202_ACCEPTED)
headers = {'Location': project_update.get_absolute_url()}
return Response(status=status.HTTP_202_ACCEPTED, headers=headers)
else:
return self.http_method_not_allowed(request, *args, **kwargs)
@ -796,7 +797,7 @@ class JobStart(GenericAPIView):
can_start=obj.can_start,
)
if obj.can_start:
data['passwords_needed_to_start'] = obj.get_passwords_needed_to_start()
data['passwords_needed_to_start'] = obj.asswords_needed_to_start
return Response(data)
def post(self, request, *args, **kwargs):
@ -804,7 +805,7 @@ class JobStart(GenericAPIView):
if obj.can_start:
result = obj.start(**request.DATA)
if not result:
data = dict(passwords_needed_to_start=obj.get_passwords_needed_to_start())
data = dict(passwords_needed_to_start=obj.passwords_needed_to_start)
return Response(data, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(status=status.HTTP_202_ACCEPTED)

View File

@ -20,17 +20,11 @@
- name: update project using git
git: dest={{project_path}} repo={{scm_url}} version={{scm_branch}} force={{scm_clean}}
when: scm_type == 'git'
async: 0
poll: 5
- name: update project using hg
hg: dest={{project_path}} repo={{scm_url}} revision={{scm_branch}} force={{scm_clean}}
when: scm_type == 'hg'
async: 0
poll: 5
- name: update project using svn
subversion: dest={{project_path}} repo={{scm_url}} revision={{scm_branch}} force={{scm_clean}}
when: scm_type == 'svn'
async: 0
poll: 5

View File

@ -245,8 +245,8 @@ BROKER_URL = 'django://'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TRACK_STARTED = True
CELERYD_TASK_TIME_LIMIT = 3600
CELERYD_TASK_SOFT_TIME_LIMIT = 3540
CELERYD_TASK_TIME_LIMIT = None
CELERYD_TASK_SOFT_TIME_LIMIT = None
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
CELERYBEAT_MAX_LOOP_INTERVAL = 60