Work in progress on credential/job updates.

This commit is contained in:
Chris Church
2013-04-23 16:21:29 -04:00
parent 3a9533ffa0
commit cc25d55121
7 changed files with 675 additions and 25 deletions

View File

@@ -28,7 +28,7 @@ runserver:
celeryd: celeryd:
# run to start the background celery worker # run to start the background celery worker
python manage.py celeryd -l DEBUG -B python manage.py celeryd -l DEBUG -B --autoreload
# already done and should not have to happen again: # already done and should not have to happen again:
# #

View File

@@ -198,9 +198,9 @@ class CredentialAdmin(BaseModelAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': (('name', 'active'), ('user', 'team'), 'description')}), (None, {'fields': (('name', 'active'), ('user', 'team'), 'description')}),
(_('Auth Info'), {'fields': ('default_username', 'ssh_key_data', (_('Auth Info'), {'fields': (('ssh_username', 'ssh_password'),
'ssh_key_unlock', 'ssh_password', 'ssh_key_data', 'ssh_key_unlock',
'sudo_password')}), ('sudo_username', 'sudo_password'))}),
#(_('Tags'), {'fields': ('tags',)}), #(_('Tags'), {'fields': ('tags',)}),
(_('Audit Trail'), {'fields': ('creation_date', 'created_by', 'audit_trail',)}), (_('Audit Trail'), {'fields': ('creation_date', 'created_by', 'audit_trail',)}),
) )
@@ -245,6 +245,9 @@ class JobTemplateAdmin(BaseModelAdmin):
'get_create_link_display', 'get_jobs_link_display')}), 'get_create_link_display', 'get_jobs_link_display')}),
(_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook', (_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook',
'credential', 'job_type')}), 'credential', 'job_type')}),
(_('More Options'), {'fields': ('use_sudo', 'forks', 'limit',
'verbosity', 'extra_vars'),
'classes': ('collapse',)}),
#(_('Tags'), {'fields': ('tags',)}), #(_('Tags'), {'fields': ('tags',)}),
(_('Audit Trail'), {'fields': ('creation_date', 'created_by', (_('Audit Trail'), {'fields': ('creation_date', 'created_by',
'audit_trail',)}), 'audit_trail',)}),
@@ -274,6 +277,17 @@ class JobTemplateAdmin(BaseModelAdmin):
create_opts['playbook'] = obj.playbook create_opts['playbook'] = obj.playbook
if obj.credential: if obj.credential:
create_opts['credential'] = obj.credential.pk create_opts['credential'] = obj.credential.pk
if obj.use_sudo is not None:
# Assume these are the defaults for a null boolean field select.
create_opts['use_sudo'] = 2 if obj.use_sudo else 3
if obj.forks:
create_opts['forks'] = obj.forks
if obj.limit:
create_opts['limit'] = obj.limit
if obj.verbosity:
create_opts['verbosity'] = obj.verbosity
if obj.extra_vars:
create_opts['extra_vars'] = json.dumps(obj.extra_vars)
create_url += '?%s' % urllib.urlencode(create_opts) create_url += '?%s' % urllib.urlencode(create_opts)
return format_html('<a href="{0}">{1}</a>', create_url, 'Create Job') return format_html('<a href="{0}">{1}</a>', create_url, 'Create Job')
get_create_link_display.short_description = _('Create Job') get_create_link_display.short_description = _('Create Job')
@@ -308,8 +322,11 @@ class JobAdmin(BaseModelAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('name', 'job_template', 'description')}), (None, {'fields': ('name', 'job_template', 'description')}),
(_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook', (_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook',
'credential', 'job_type', 'credential', 'job_type')}),
'start_job')}), (_('More Options'), {'fields': ('use_sudo', 'forks', 'limit',
'verbosity', 'extra_vars'),
'classes': ('collapse',)}),
(_('Start/Cancel Job'), {'fields': ('start_job',)}),
#(_('Tags'), {'fields': ('tags',)}), #(_('Tags'), {'fields': ('tags',)}),
(_('Audit Trail'), {'fields': ('creation_date', 'created_by', (_('Audit Trail'), {'fields': ('creation_date', 'created_by',
'audit_trail',)}), 'audit_trail',)}),
@@ -331,7 +348,8 @@ class JobAdmin(BaseModelAdmin):
if obj and obj.pk and obj.status != 'new': if obj and obj.pk and obj.status != 'new':
ro_fields.extend(['name', 'description', 'job_template', ro_fields.extend(['name', 'description', 'job_template',
'inventory', 'project', 'playbook', 'credential', 'inventory', 'project', 'playbook', 'credential',
'job_type']) 'job_type', 'use_sudo', 'forks', 'limit',
'verbosity', 'extra_vars'])
return ro_fields return ro_fields
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
@@ -342,11 +360,12 @@ class JobAdmin(BaseModelAdmin):
'status' not in fs[1]['fields']] 'status' not in fs[1]['fields']]
elif obj and obj.pk and obj.status != 'new': elif obj and obj.pk and obj.status != 'new':
#print obj, obj.pk, obj.status #print obj, obj.pk, obj.status
for fs in fsets: fsets = [fs for fs in fsets if 'start_job' not in fs[1]['fields']]
# FIXME: Show start job on add view #for fs in fsets:
if 'start_job' in fs[1]['fields']: # # FIXME: Show start job on add view
fs[1]['fields'] = [x for x in fs[1]['fields'] # if 'start_job' in fs[1]['fields']:
if x != 'start_job'] # fs[1]['fields'] = [x for x in fs[1]['fields']
# if x != 'start_job']
return fsets return fsets
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):

View File

@@ -0,0 +1,378 @@
# -*- 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):
# Adding field 'Job.use_sudo'
db.add_column(u'main_job', 'use_sudo',
self.gf('django.db.models.fields.NullBooleanField')(default=None, null=True, blank=True),
keep_default=False)
# Adding field 'Job.forks'
db.add_column(u'main_job', 'forks',
self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True),
keep_default=False)
# Adding field 'Job.limit'
db.add_column(u'main_job', 'limit',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
# Adding field 'Job.verbosity'
db.add_column(u'main_job', 'verbosity',
self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True),
keep_default=False)
# Adding field 'Job.extra_vars'
db.add_column(u'main_job', 'extra_vars',
self.gf('jsonfield.fields.JSONField')(default='', blank=True),
keep_default=False)
# Adding field 'Job.cancel_flag'
db.add_column(u'main_job', 'cancel_flag',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Deleting field 'Credential.default_username'
db.delete_column(u'main_credential', 'default_username')
# Adding field 'Credential.ssh_username'
db.add_column(u'main_credential', 'ssh_username',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
# Adding field 'Credential.sudo_username'
db.add_column(u'main_credential', 'sudo_username',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
# Adding field 'JobTemplate.use_sudo'
db.add_column(u'main_jobtemplate', 'use_sudo',
self.gf('django.db.models.fields.NullBooleanField')(default=None, null=True, blank=True),
keep_default=False)
# Adding field 'JobTemplate.forks'
db.add_column(u'main_jobtemplate', 'forks',
self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True),
keep_default=False)
# Adding field 'JobTemplate.limit'
db.add_column(u'main_jobtemplate', 'limit',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
# Adding field 'JobTemplate.verbosity'
db.add_column(u'main_jobtemplate', 'verbosity',
self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True),
keep_default=False)
# Adding field 'JobTemplate.extra_vars'
db.add_column(u'main_jobtemplate', 'extra_vars',
self.gf('jsonfield.fields.JSONField')(default='', blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Job.use_sudo'
db.delete_column(u'main_job', 'use_sudo')
# Deleting field 'Job.forks'
db.delete_column(u'main_job', 'forks')
# Deleting field 'Job.limit'
db.delete_column(u'main_job', 'limit')
# Deleting field 'Job.verbosity'
db.delete_column(u'main_job', 'verbosity')
# Deleting field 'Job.extra_vars'
db.delete_column(u'main_job', 'extra_vars')
# Deleting field 'Job.cancel_flag'
db.delete_column(u'main_job', 'cancel_flag')
# Adding field 'Credential.default_username'
db.add_column(u'main_credential', 'default_username',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
# Deleting field 'Credential.ssh_username'
db.delete_column(u'main_credential', 'ssh_username')
# Deleting field 'Credential.sudo_username'
db.delete_column(u'main_credential', 'sudo_username')
# Deleting field 'JobTemplate.use_sudo'
db.delete_column(u'main_jobtemplate', 'use_sudo')
# Deleting field 'JobTemplate.forks'
db.delete_column(u'main_jobtemplate', 'forks')
# Deleting field 'JobTemplate.limit'
db.delete_column(u'main_jobtemplate', 'limit')
# Deleting field 'JobTemplate.verbosity'
db.delete_column(u'main_jobtemplate', 'verbosity')
# Deleting field 'JobTemplate.extra_vars'
db.delete_column(u'main_jobtemplate', 'extra_vars')
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'}),
'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'}),
'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'}),
'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']"}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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'}),
'extra_vars': ('jsonfield.fields.JSONField', [], {'default': "''", 'blank': 'True'}),
'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_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'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', '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_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': "'new'", 'max_length': '20'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'use_sudo': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'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'}),
'extra_vars': ('jsonfield.fields.JSONField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', '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_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'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']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobtemplate_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'use_sudo': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'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']"}),
'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'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.FilePathField', [], {'path': "'/Users/chris/Sandbox/ansible-commander/lib/projects'", 'unique': 'True', 'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'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']

View File

@@ -527,7 +527,7 @@ class Credential(CommonModelNameNotUnique):
# if ssh_key_unlock is provided provide key password # if ssh_key_unlock is provided provide key password
# if not provided, FAIL # if not provided, FAIL
# #
# default_username if set corresponds to -u on ansible-playbook, if unset -u root # ssh_username if set corresponds to -u on ansible-playbook, if unset -u root
# #
# STAGE 2: # STAGE 2:
# OR if ssh_password is set instead, do not use SSH agent # OR if ssh_password is set instead, do not use SSH agent
@@ -542,11 +542,59 @@ class Credential(CommonModelNameNotUnique):
# #
# ansible-playbook foo.yml ... # ansible-playbook foo.yml ...
ssh_key_data = models.TextField(blank=True, default='') ssh_username = models.CharField(
ssh_key_unlock = models.CharField(blank=True, default='', max_length=1024) blank=True,
default_username = models.CharField(blank=True, default='', max_length=1024) default='',
ssh_password = models.CharField(blank=True, default='', max_length=1024) max_length=1024,
sudo_password = models.CharField(blank=True, default='', max_length=1024) verbose_name=_('SSH username'),
help_text=_('SSH username for a job using this credential.'),
)
ssh_password = models.CharField(
blank=True,
default='',
max_length=1024,
verbose_name=_('SSH password'),
help_text=_('SSH password (or "ASK" to prompt the user).'),
)
ssh_key_data = models.TextField(
blank=True,
default='',
verbose_name=_('SSH private key'),
help_text=_('RSA or DSA private key to be used instead of password.'),
)
ssh_key_unlock = models.CharField(
max_length=1024,
blank=True,
default='',
verbose_name=_('SSH key unlock'),
help_text=_('Passphrase to unlock SSH private key if encrypted (or '
'"ASK" to prompt the user).'),
)
sudo_username = models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Sudo username for a job using this credential.'),
)
sudo_password = models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Sudo password (or "ASK" to prompt the user).'),
)
@property
def needs_ssh_password(self):
return not self.ssh_key_data and self.ssh_password == 'ASK'
@property
def needs_ssh_key_unlock(self):
return 'ENCRYPTED' in self.ssh_key_data and \
(not self.ssh_key_unlock or self.ssh_key_unlock == 'ASK')
@property
def needs_sudo_password(self):
return self.sudo_password == 'ASK'
@classmethod @classmethod
def can_user_administrate(cls, user, obj, data): def can_user_administrate(cls, user, obj, data):
@@ -796,6 +844,27 @@ class JobTemplate(CommonModel):
default=None, default=None,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )
use_sudo = models.NullBooleanField(
blank=True,
default=None,
)
forks = models.PositiveIntegerField(
blank=True,
default=0,
)
limit = models.CharField(
max_length=1024,
blank=True,
default='',
)
verbosity = models.PositiveIntegerField(
blank=True,
default=0,
)
extra_vars = JSONField(
blank=True,
default='',
)
def create_job(self, **kwargs): def create_job(self, **kwargs):
''' '''
@@ -811,6 +880,11 @@ class JobTemplate(CommonModel):
kwargs.setdefault('project', self.project) kwargs.setdefault('project', self.project)
kwargs.setdefault('playbook', self.playbook) kwargs.setdefault('playbook', self.playbook)
kwargs.setdefault('credential', self.credential) kwargs.setdefault('credential', self.credential)
kwargs.setdefault('use_sudo', self.use_sudo)
kwargs.setdefault('forks', self.forks)
kwargs.setdefault('limit', self.limit)
kwargs.setdefault('verbosity', self.verbosity)
kwargs.setdefault('extra_vars', self.extra_vars)
job = Job(**kwargs) job = Job(**kwargs)
if save_job: if save_job:
job.save() job.save()
@@ -925,6 +999,7 @@ class Job(CommonModel):
('successful', _('Successful')), # Job completed successfully. ('successful', _('Successful')), # Job completed successfully.
('failed', _('Failed')), # Job completed, but with failures. ('failed', _('Failed')), # Job completed, but with failures.
('error', _('Error')), # The job was unable to run. ('error', _('Error')), # The job was unable to run.
('canceled', _('Canceled')), # The job was canceled before completion.
] ]
class Meta: class Meta:
@@ -963,6 +1038,31 @@ class Job(CommonModel):
playbook = models.CharField( playbook = models.CharField(
max_length=1024, max_length=1024,
) )
use_sudo = models.NullBooleanField(
blank=True,
default=None,
)
forks = models.PositiveIntegerField(
blank=True,
default=0,
)
limit = models.CharField(
max_length=1024,
blank=True,
default='',
)
verbosity = models.PositiveIntegerField(
blank=True,
default=0,
)
extra_vars = JSONField(
blank=True,
default='',
)
cancel_flag = models.BooleanField(
blank=True,
default=False,
)
status = models.CharField( status = models.CharField(
max_length=20, max_length=20,
choices=STATUS_CHOICES, choices=STATUS_CHOICES,
@@ -1006,17 +1106,29 @@ class Job(CommonModel):
except TaskMeta.DoesNotExist: except TaskMeta.DoesNotExist:
pass pass
def start(self): def start(self, **kwargs):
from lib.main.tasks import run_job from lib.main.tasks import run_job
if self.status != 'new': if self.status != 'new':
return return False
#username = kwargs.get('username', self.username)
opts = {}
self.status = 'pending' self.status = 'pending'
self.save(update_fields=['status']) self.save(update_fields=['status'])
task_result = run_job.delay(self.pk) task_result = run_job.delay(self.pk, **opts)
# The TaskMeta instance in the database isn't created until the worker # 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. # starts processing the task, so we can only store the task ID here.
self.celery_task_id = task_result.task_id self.celery_task_id = task_result.task_id
self.save(update_fields=['celery_task_id']) self.save(update_fields=['celery_task_id'])
return True
def cancel(self):
if self.status in ('pending', 'running'):
if not self.cancel_flag:
self.cancel_flag = True
self.save(update_fields=['cancel_flag'])
return self.cancel_flag
@property @property
def successful_hosts(self): def successful_hosts(self):

View File

@@ -14,8 +14,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>. # along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>.
import logging
import os import os
import select
import subprocess import subprocess
import time
import traceback import traceback
from celery import task from celery import task
from django.conf import settings from django.conf import settings
@@ -23,8 +26,59 @@ from lib.main.models import *
__all__ = ['run_job'] __all__ = ['run_job']
logger = logging.getLogger('lib.tasks')
class Timeout(object):
def __init__(self, duration=None):
# If initializing from another instance, create a new timeout from the
# remaining time on the other instance.
if isinstance(duration, Timeout):
duration = duration.remaining
self.reset(duration)
def __repr__(self):
if self._duration is None:
return 'Timeout(None)'
else:
return 'Timeout(%f)' % self._duration
def __hash__(self):
return self._duration
def __nonzero__(self):
return self.block
def reset(self, duration=False):
if duration is not False:
self._duration = float(max(0, duration)) if duration is not None else None
self._begin = time.time()
def expire(self):
self._begin = time.time() - max(0, self._duration or 0.0)
@property
def duration(self):
return self._duration
@property
def elapsed(self):
return float(max(0, time.time() - self._begin))
@property
def remaining(self):
if self._duration is None:
return None
else:
return float(max(0, self._duration + self._begin - time.time()))
@property
def block(self):
return bool(self.remaining or self.remaining is None)
@task(name='run_job') @task(name='run_job')
def run_job(job_pk): def run_job(job_pk, **kwargs):
job = Job.objects.get(pk=job_pk) job = Job.objects.get(pk=job_pk)
job.status = 'running' job.status = 'running'
job.save(update_fields=['status']) job.save(update_fields=['status'])
@@ -50,17 +104,89 @@ def run_job(job_pk):
if hasattr(settings, 'ANSIBLE_TRANSPORT'): if hasattr(settings, 'ANSIBLE_TRANSPORT'):
env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT') env['ANSIBLE_TRANSPORT'] = getattr(settings, 'ANSIBLE_TRANSPORT')
creds = job.credential
username = creds.ssh_username
#sudo_username = job.credential.sudo_username
cwd = job.project.local_path cwd = job.project.local_path
cmdline = ['ansible-playbook', '-i', inventory_script] cmdline = ['ansible-playbook', '-i', inventory_script]
if job.job_type == 'check': if job.job_type == 'check':
cmdline.append('--check') cmdline.append('--check')
if job.use_sudo:
cmdline.append('--sudo')
if job.forks: # FIXME: Max limit?
cmdline.append('--forks=%d' % job.forks)
if job.limit:
cmdline.append('--limit=%s' % job.limit)
if job.verbosity:
cmdline.append('-%s' % ('v' * min(3, job.verbosity)))
if job.extra_vars:
# FIXME: escaping!
extra_vars = ' '.join(['%s=%s' % (str(k), str(v)) for k,v in
job.extra_vars.items()])
cmdline.append('-e', extra_vars)
cmdline.append(job.playbook) # relative path to project.local_path cmdline.append(job.playbook) # relative path to project.local_path
# FIXME: How to cancel/interrupt job? (not that important for now)
proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=cwd, env=env) stderr=subprocess.PIPE, cwd=cwd, env=env)
stdout, stderr = proc.communicate() # stdout, stderr = proc.communicate()
status = 'successful' if proc.returncode == 0 else 'failed' proc_canceled = False
while proc.returncode is None:
new_stdout, new_stderr = '', ''
timeout = Timeout(1.0)
while timeout:
# FIXME: Probably want to use poll (when on Linux), needs to be tested.
if hasattr(select, 'poll') and False:
poll = select.poll()
poll.register(proc.stdout.fileno(), select.POLLIN or select.POLLPRI)
poll.register(proc.stderr.fileno(), select.POLLIN or select.POLLPRI)
fd_events = poll.poll(1.0)
if not fd_events:
break
for fd, evt in fd_events:
if fd == proc.stdout.fileno() and evt > 0:
new_stdout += proc.stdout.read(1)
elif fd == proc.stderr.fileno() and evt > 0:
new_stderr += proc.stderr.read(1)
else:
stdout_byte, stderr_byte = '', ''
fdlist = [proc.stdout.fileno(), proc.stderr.fileno()]
rwx = select.select(fdlist, [], [], timeout.remaining)
if proc.stdout.fileno() in rwx[0]:
stdout_byte = proc.stdout.read(1)
new_stdout += stdout_byte
if proc.stderr.fileno() in rwx[0]:
stderr_byte = proc.stderr.read(1)
new_stderr += stderr_byte
if not stdout_byte and not stderr_byte:
break
job = Job.objects.get(pk=job_pk)
update_fields = []
if new_stdout:
stdout += new_stdout
job.result_stdout = stdout
update_fields.append('result_stdout')
if new_stderr:
stderr += new_stderr
job.result_stderr = stderr
update_fields.append('result_stderr')
if update_fields:
job.save(update_fields=update_fields)
proc.poll()
if job.cancel_flag and not proc_canceled:
proc.terminate()
proc_canceled = True
stdout += proc.stdout.read()
stderr += proc.stderr.read()
if proc_canceled:
status = 'canceled'
elif proc.returncode == 0:
status = 'successful'
else:
status = 'failed'
except Exception: except Exception:
tb = traceback.format_exc() tb = traceback.format_exc()

View File

@@ -74,6 +74,20 @@ class RunJobTest(BaseCeleryTest):
if self.test_project_path: if self.test_project_path:
shutil.rmtree(self.test_project_path, True) shutil.rmtree(self.test_project_path, True)
def create_test_credential(self, **kwargs):
opts = {
'name': 'test-creds',
'user': self.super_django_user,
'default_username': '',
'ssh_key_data': '',
'ssh_key_unlock': '',
'ssh_password': '',
'sudo_password': '',
}
opts.update(kwargs)
self.credential = Credential.objects.create(**opts)
return self.credential
def create_test_project(self, playbook_content): def create_test_project(self, playbook_content):
self.project = self.make_projects(self.normal_django_user, 1, playbook_content)[0] self.project = self.make_projects(self.normal_django_user, 1, playbook_content)[0]
self.organization.projects.add(self.project) self.organization.projects.add(self.project)

View File

@@ -4,6 +4,7 @@ django-devserver==0.5.0
django-extensions==1.1.1 django-extensions==1.1.1
django-jsonfield==0.9.2 django-jsonfield==0.9.2
ipython==0.13.1 ipython==0.13.1
paramiko==1.10.0
# psycopg2==2.4.6 # psycopg2==2.4.6
python-dateutil==1.5 python-dateutil==1.5
PyYAML==3.10 PyYAML==3.10