Changed how Project local_path is configured, added option to specify playbook on JobTemplate and Job, updated admin and tests.

This commit is contained in:
Chris Church
2013-04-19 15:40:08 -04:00
parent 86de2c8846
commit bc1f3e320e
14 changed files with 534 additions and 53 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,8 @@
lib/settings/local_settings.py lib/settings/local_settings.py
lib/acom.sqlite3 lib/acom.sqlite3
lib/projects
lib/public/media
lib/public/static
env/* env/*
*.py[c,o] *.py[c,o]
*.swp *.swp

View File

@@ -46,6 +46,9 @@ if 'test' in sys.argv or 'ACOM_TEST_DATABASE_NAME' in os.environ:
} }
} }
# Absolute filesystem path to the directory to host projects (with playbooks).
# This directory should not be web-accessible.
PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects')
# Local time zone for this installation. Choices can be found here: # Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name

View File

@@ -206,8 +206,22 @@ class TeamAdmin(BaseModelAdmin):
class ProjectAdmin(BaseModelAdmin): class ProjectAdmin(BaseModelAdmin):
list_display = ('name', 'description', 'active') list_display = ('name', 'description', 'active')
fieldsets = (
(None, {'fields': (('name', 'active'), 'description', 'local_path',
'get_available_playbooks_display')}),
(_('Tags'), {'fields': ('tags',)}),
(_('Audit Trail'), {'fields': ('creation_date', 'created_by', 'audit_trail',)}),
)
readonly_fields = ('creation_date', 'created_by', 'audit_trail',
'get_available_playbooks_display')
filter_horizontal = ('tags',) filter_horizontal = ('tags',)
def get_available_playbooks_display(self, obj):
return '<br/>'.join([format_html('{0}', x) for x in
obj.available_playbooks])
get_available_playbooks_display.short_description = _('Available playbooks')
get_available_playbooks_display.allow_tags = True
class PermissionAdmin(BaseModelAdmin): class PermissionAdmin(BaseModelAdmin):
list_display = ('name', 'description', 'active') list_display = ('name', 'description', 'active')
@@ -220,14 +234,15 @@ class JobTemplateAdmin(BaseModelAdmin):
fieldsets = ( fieldsets = (
(None, {'fields': ('name', 'active', 'description', (None, {'fields': ('name', 'active', 'description',
'get_create_link_display', 'get_jobs_link_display')}), 'get_create_link_display', 'get_jobs_link_display')}),
(_('Job Parameters'), {'fields': ('inventory', 'project', 'credential', (_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook',
'job_type')}), 'credential', 'job_type')}),
#(_('Tags'), {'fields': ('tags',)}), #(_('Tags'), {'fields': ('tags',)}),
(_('Audit Trail'), {'fields': ('creation_date', 'created_by', (_('Audit Trail'), {'fields': ('creation_date', 'created_by',
'audit_trail',)}), 'audit_trail',)}),
) )
readonly_fields = ('creation_date', 'created_by', 'audit_trail', readonly_fields = ('creation_date', 'created_by', 'audit_trail',
'get_create_link_display', 'get_jobs_link_display') 'get_create_link_display', 'get_jobs_link_display')
form = JobTemplateAdminForm
#filter_horizontal = ('tags',) #filter_horizontal = ('tags',)
def get_create_link_display(self, obj): def get_create_link_display(self, obj):
@@ -242,10 +257,10 @@ class JobTemplateAdmin(BaseModelAdmin):
create_opts['inventory'] = obj.inventory.pk create_opts['inventory'] = obj.inventory.pk
if obj.project: if obj.project:
create_opts['project'] = obj.project.pk create_opts['project'] = obj.project.pk
if 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.user:
# create_opts['user'] = obj.user.pk
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')
@@ -274,11 +289,11 @@ class JobEventInlineForJob(JobEventInline):
class JobAdmin(BaseModelAdmin): class JobAdmin(BaseModelAdmin):
list_display = ('name', 'job_template', 'status') list_display = ('name', 'job_template', 'project', 'playbook', 'status')
fieldsets = ( fieldsets = (
(None, {'fields': ('name', 'job_template', 'description')}), (None, {'fields': ('name', 'job_template', 'description')}),
(_('Job Parameters'), {'fields': ('inventory', 'project', 'credential', (_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook',
'job_type')}), 'credential', 'job_type')}),
#(_('Tags'), {'fields': ('tags',)}), #(_('Tags'), {'fields': ('tags',)}),
(_('Audit Trail'), {'fields': ('creation_date', 'created_by', (_('Audit Trail'), {'fields': ('creation_date', 'created_by',
'audit_trail',)}), 'audit_trail',)}),
@@ -292,13 +307,14 @@ class JobAdmin(BaseModelAdmin):
'get_result_traceback_display', 'celery_task_id', 'get_result_traceback_display', 'celery_task_id',
'creation_date', 'created_by', 'audit_trail',) 'creation_date', 'created_by', 'audit_trail',)
filter_horizontal = ('tags',) filter_horizontal = ('tags',)
form = JobAdminForm
inlines = [JobHostSummaryInlineForJob, JobEventInlineForJob] inlines = [JobHostSummaryInlineForJob, JobEventInlineForJob]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
ro_fields = list(super(JobAdmin, self).get_readonly_fields(request, obj)) ro_fields = list(super(JobAdmin, self).get_readonly_fields(request, obj))
if obj and obj.pk: if obj and obj.pk:
ro_fields.extend(['name', 'description', 'job_template', ro_fields.extend(['name', 'description', 'job_template',
'inventory', 'project', 'credential', 'user', 'inventory', 'project', 'playbook', 'credential',
'job_type']) 'job_type'])
return ro_fields return ro_fields

View File

@@ -4,6 +4,29 @@ from django.utils.translation import ugettext_lazy as _
from jsonfield.fields import JSONFormField from jsonfield.fields import JSONFormField
from lib.main.models import * from lib.main.models import *
EMPTY_CHOICE = ('', '---------')
class PlaybookOption(object):
def __init__(self, project, playbook):
self.project, self.playbook = project, playbook
def __unicode__(self):
return self.playbook
class PlaybookSelect(forms.Select):
'''Custom select widget for playbooks related to a project.'''
def render_option(self, selected_choices, option_value, obj):
opt = super(PlaybookSelect, self).render_option(selected_choices,
option_value,
unicode(obj))
# Add a class with the project ID so JS can filter the options.
if hasattr(obj, 'project'):
opt = opt.replace('">', '" class="project-%s">' % obj.project.pk)
return opt
class HostAdminForm(forms.ModelForm): class HostAdminForm(forms.ModelForm):
class Meta: class Meta:
@@ -45,3 +68,30 @@ class GroupForm(forms.ModelForm):
variable_data = JSONFormField(required=False, widget=forms.Textarea(attrs={'class': 'vLargeTextField'})) variable_data = JSONFormField(required=False, widget=forms.Textarea(attrs={'class': 'vLargeTextField'}))
class JobTemplateAdminForm(forms.ModelForm):
'''Custom admin form for creating/editing JobTemplates.'''
playbook = forms.ChoiceField(choices=[EMPTY_CHOICE], required=False,
widget=PlaybookSelect)
class Meta:
model = JobTemplate
def __init__(self, *args, **kwargs):
super(JobTemplateAdminForm, self).__init__(*args, **kwargs)
playbook_choices = []
for project in Project.objects.all():
for playbook in project.available_playbooks:
playbook_choices.append((playbook,
PlaybookOption(project, playbook)))
self.fields['playbook'].choices = [EMPTY_CHOICE] + playbook_choices
class JobAdminForm(JobTemplateAdminForm):
'''Custom admin form for creating Jobs.'''
class Meta:
model = Job
def __init__(self, *args, **kwargs):
super(JobAdminForm, self).__init__(*args, **kwargs)
self.fields['playbook'].required = True

View File

@@ -0,0 +1,318 @@
# -*- 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):
# Deleting field 'Job.user'
db.delete_column(u'main_job', 'user_id')
# Adding field 'Job.playbook'
db.add_column(u'main_job', 'playbook',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024),
keep_default=False)
# Deleting field 'JobTemplate.user'
db.delete_column(u'main_jobtemplate', 'user_id')
# Adding field 'JobTemplate.playbook'
db.add_column(u'main_jobtemplate', 'playbook',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
# Deleting field 'Project.local_repository'
db.delete_column(u'main_project', 'local_repository')
# Deleting field 'Project.scm_type'
db.delete_column(u'main_project', 'scm_type')
# Deleting field 'Project.default_playbook'
db.delete_column(u'main_project', 'default_playbook')
# Adding field 'Project.local_path'
db.add_column(u'main_project', 'local_path',
self.gf('django.db.models.fields.FilePathField')(default='', path='/Users/chris/Sandbox/ansible-commander/lib/projects', unique=True, max_length=1024),
keep_default=False)
def backwards(self, orm):
# Adding field 'Job.user'
db.add_column(u'main_job', 'user',
self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['auth.User']),
keep_default=False)
# Deleting field 'Job.playbook'
db.delete_column(u'main_job', 'playbook')
# Adding field 'JobTemplate.user'
db.add_column(u'main_jobtemplate', 'user',
self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', on_delete=models.SET_NULL, default=None, to=orm['auth.User'], blank=True, null=True),
keep_default=False)
# Deleting field 'JobTemplate.playbook'
db.delete_column(u'main_jobtemplate', 'playbook')
# Adding field 'Project.local_repository'
db.add_column(u'main_project', 'local_repository',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024),
keep_default=False)
# Adding field 'Project.scm_type'
db.add_column(u'main_project', 'scm_type',
self.gf('django.db.models.fields.CharField')(default='', max_length=64),
keep_default=False)
# Adding field 'Project.default_playbook'
db.add_column(u'main_project', 'default_playbook',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024),
keep_default=False)
# Deleting field 'Project.local_path'
db.delete_column(u'main_project', 'local_path')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.audittrail': {
'Meta': {'object_name': 'AuditTrail'},
'comment': ('django.db.models.fields.TextField', [], {}),
'delta': ('django.db.models.fields.TextField', [], {}),
'detail': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'resource_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Tag']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'credential_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'default_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'credential_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'host_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'host_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'host'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'inventory_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'inventory_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'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': "'pending'", 'max_length': '20'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"})
},
'main.jobevent': {
'Meta': {'object_name': 'JobEvent'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': "''", 'blank': 'True'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobtemplate_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Inventory']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobtemplate_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"})
},
'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

@@ -14,6 +14,9 @@
# 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 os
from django.conf import settings
from django.db import models, DatabaseError from django.db import models, DatabaseError
from django.db.models import CASCADE, SET_NULL, PROTECT from django.db.models import CASCADE, SET_NULL, PROTECT
from django.db.models.signals import post_save from django.db.models.signals import post_save
@@ -26,6 +29,7 @@ import exceptions
from jsonfield import JSONField from jsonfield import JSONField
from djcelery.models import TaskMeta from djcelery.models import TaskMeta
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
import yaml
# TODO: jobs and events model TBD # TODO: jobs and events model TBD
# TODO: reporting model TBD # TODO: reporting model TBD
@@ -641,9 +645,18 @@ class Project(CommonModel):
# this is not part of the project, but managed with perms # this is not part of the project, but managed with perms
# inventories = models.ManyToManyField('Inventory', blank=True, related_name='projects') # inventories = models.ManyToManyField('Inventory', blank=True, related_name='projects')
local_repository = models.CharField(max_length=1024) local_path = models.FilePathField(
scm_type = models.CharField(max_length=64) path=settings.PROJECTS_ROOT,
default_playbook = models.CharField(max_length=1024) recursive=False,
allow_files=False,
allow_folders=True,
max_length=1024,
unique=True,
help_text=_('Local path (relative to PROJECTS_ROOT) containing '
'playbooks and related files for this project.')
)
#scm_type = models.CharField(max_length=64)
#default_playbook = models.CharField(max_length=1024)
def get_absolute_url(self): def get_absolute_url(self):
import lib.urls import lib.urls
@@ -673,6 +686,36 @@ class Project(CommonModel):
def can_user_delete(cls, user, obj): def can_user_delete(cls, user, obj):
return cls.can_user_administrate(user, obj, None) return cls.can_user_administrate(user, obj, None)
@property
def available_playbooks(self):
playbooks = []
if self.local_path and os.path.exists(self.local_path):
for dirpath, dirnames, filenames in os.walk(self.local_path):
for filename in filenames:
if os.path.splitext(filename)[-1] != '.yml':
continue
playbook = os.path.join(dirpath, filename)
# Filter any invalid YAML files.
try:
data = yaml.safe_load(file(playbook).read())
except (IOError, yaml.YAMLError):
continue
# Filter files that do not have either hosts or top-level
# includes.
try:
if 'hosts' not in data[0] and 'include' not in data[0]:
continue
except (IndexError, KeyError):
continue
playbook = os.path.relpath(playbook, self.local_path)
# Filter files in a roles subdirectory.
if 'roles' in playbook.split(os.sep):
continue
# Filter files in a tasks subdirectory.
if 'tasks' in playbook.split(os.sep):
continue
playbooks.append(playbook)
return playbooks
class Permission(CommonModelNameNotUnique): class Permission(CommonModelNameNotUnique):
''' '''
@@ -753,6 +796,11 @@ class JobTemplate(CommonModel):
default=None, default=None,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )
playbook = models.CharField(
max_length=1024,
blank=True,
default='',
)
# project has one default playbook but really should have a list of playbooks and flags ... # project has one default playbook but really should have a list of playbooks and flags ...
# ssh-agent bash # ssh-agent bash
@@ -895,6 +943,9 @@ class Job(CommonModel):
null=True, null=True,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )
playbook = models.CharField(
max_length=1024,
)
status = models.CharField( status = models.CharField(
max_length=20, max_length=20,
choices=STATUS_CHOICES, choices=STATUS_CHOICES,

View File

@@ -70,7 +70,7 @@ class ProjectSerializer(BaseSerializer):
class Meta: class Meta:
model = Project model = Project
fields = ('url', 'id', 'name', 'description', 'creation_date', 'local_repository', 'default_playbook', 'scm_type') fields = ('url', 'id', 'name', 'description', 'creation_date', 'local_path')#, 'default_playbook', 'scm_type')
def get_related(self, obj): def get_related(self, obj):
# FIXME: add related resources: inventories # FIXME: add related resources: inventories

View File

@@ -49,16 +49,16 @@ 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')
playbook = job.project.default_playbook 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')
cmdline.append(playbook) cmdline.append(job.playbook) # relative path to project.local_path
# FIXME: How to cancel/interrupt job? (not that important for now) # 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, 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' status = 'successful' if proc.returncode == 0 else 'failed'
except Exception: except Exception:

View File

@@ -16,7 +16,11 @@
import datetime import datetime
import json import json
import os
import shutil
import tempfile
from django.conf import settings
from django.contrib.auth.models import User as DjangoUser from django.contrib.auth.models import User as DjangoUser
import django.test import django.test
from django.test.client import Client from django.test.client import Client
@@ -31,6 +35,13 @@ class BaseTestMixin(object):
def setUp(self): def setUp(self):
super(BaseTestMixin, self).setUp() super(BaseTestMixin, self).setUp()
self.object_ctr = 0 self.object_ctr = 0
self._temp_project_dirs = []
def tearDown(self):
super(BaseTestMixin, self).tearDown()
for project_dir in self._temp_project_dirs:
if os.path.exists(project_dir):
shutil.rmtree(project_dir, True)
def make_user(self, username, password, super_user=False): def make_user(self, username, password, super_user=False):
django_user = None django_user = None
@@ -50,13 +61,24 @@ class BaseTestMixin(object):
)) ))
return results return results
def make_projects(self, created_by, count=1): def make_projects(self, created_by, count=1, playbook_content=''):
results = [] results = []
for x in range(0, count): for x in range(0, count):
self.object_ctr = self.object_ctr + 1 self.object_ctr = self.object_ctr + 1
# Create temp project directory.
project_dir = tempfile.mkdtemp(dir=settings.PROJECTS_ROOT)
self._temp_project_dirs.append(project_dir)
# Create temp playbook in project (if playbook content is given).
if playbook_content:
handle, playbook_path = tempfile.mkstemp(suffix='.yml',
dir=project_dir)
test_playbook_file = os.fdopen(handle, 'w')
test_playbook_file.write(playbook_content)
test_playbook_file.close()
results.append(Project.objects.create( results.append(Project.objects.create(
name="proj%s-%s" % (x, self.object_ctr), description="proj%s" % x, scm_type='git', name="proj%s-%s" % (x, self.object_ctr), description="proj%s" % x,
default_playbook='foo.yml', local_repository='/checkout', created_by=created_by #scm_type='git', default_playbook='foo.yml',
local_path=project_dir, created_by=created_by
)) ))
return results return results

View File

@@ -71,13 +71,9 @@ class JobsTest(BaseTest):
self.team.users.add(self.other_django_user) self.team.users.add(self.other_django_user)
self.team.users.add(self.other2_django_user) self.team.users.add(self.other2_django_user)
self.project = Project.objects.create( self.project = self.make_projects(self.normal_django_user, 1,
name = 'testProject', playbook_content='')[0]
created_by = self.normal_django_user, self.organization.projects.add(self.project)
local_repository = '/tmp/',
scm_type = 'git',
default_playbook = 'site.yml',
)
# other django user is on the project team and can deploy # other django user is on the project team and can deploy
self.permission1 = Permission.objects.create( self.permission1 = Permission.objects.create(

View File

@@ -16,6 +16,7 @@
import os import os
import shutil
import tempfile import tempfile
from django.conf import settings from django.conf import settings
from django.test.utils import override_settings from django.test.utils import override_settings
@@ -53,10 +54,9 @@ class RunJobTest(BaseCeleryTest):
def setUp(self): def setUp(self):
super(RunJobTest, self).setUp() super(RunJobTest, self).setUp()
self.test_project_path = None
self.setup_users() self.setup_users()
self.organization = self.make_organizations(self.super_django_user, 1)[0] self.organization = self.make_organizations(self.super_django_user, 1)[0]
self.project = self.make_projects(self.normal_django_user, 1)[0]
self.organization.projects.add(self.project)
self.inventory = Inventory.objects.create(name='test-inventory', self.inventory = Inventory.objects.create(name='test-inventory',
description='description for test-inventory', description='description for test-inventory',
organization=self.organization) organization=self.organization)
@@ -71,27 +71,33 @@ class RunJobTest(BaseCeleryTest):
def tearDown(self): def tearDown(self):
super(RunJobTest, self).tearDown() super(RunJobTest, self).tearDown()
os.environ.pop('ACOM_TEST_DATABASE_NAME', None) os.environ.pop('ACOM_TEST_DATABASE_NAME', None)
os.remove(self.test_playbook) if self.test_project_path:
shutil.rmtree(self.test_project_path, True)
def create_test_playbook(self, s): def create_test_project(self, playbook_content):
handle, self.test_playbook = tempfile.mkstemp(suffix='.yml', prefix='playbook-') self.project = self.make_projects(self.normal_django_user, 1, playbook_content)[0]
test_playbook_file = os.fdopen(handle, 'w') self.organization.projects.add(self.project)
test_playbook_file.write(s)
test_playbook_file.close() def create_test_job(self, **kwargs):
self.project.default_playbook = self.test_playbook opts = {
self.project.save() 'name': 'test-job',
'inventory': self.inventory,
'project': self.project,
'playbook': self.project.available_playbooks[0],
}
opts.update(kwargs)
return Job.objects.create(**opts)
def test_run_job(self): def test_run_job(self):
self.create_test_playbook(TEST_PLAYBOOK) self.create_test_project(TEST_PLAYBOOK)
job = Job.objects.create(name='test-job', inventory=self.inventory, job = self.create_test_job()
project=self.project)
self.assertEqual(job.status, 'pending') self.assertEqual(job.status, 'pending')
self.assertEqual(set(job.hosts.values_list('pk', flat=True)), self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
set([self.host.pk])) set([self.host.pk]))
job = Job.objects.get(pk=job.pk) job = Job.objects.get(pk=job.pk)
#print 'stdout:', launch_job_status.result_stdout #print 'stdout:', job.result_stdout
#print 'stderr:', launch_job_status.result_stderr #print 'stderr:', job.result_stderr
#print launch_job_status.status #print job.status
#print settings.DATABASES #print settings.DATABASES
self.assertEqual(job.status, 'successful') self.assertEqual(job.status, 'successful')
self.assertTrue(job.result_stdout) self.assertTrue(job.result_stdout)
@@ -114,9 +120,8 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.processed_hosts.count(), 1) self.assertEqual(job.processed_hosts.count(), 1)
def test_check_job(self): def test_check_job(self):
self.create_test_playbook(TEST_PLAYBOOK) self.create_test_project(TEST_PLAYBOOK)
job = Job.objects.create(name='test-job', inventory=self.inventory, job = self.create_test_job(job_type='check')
project=self.project, job_type='check')
self.assertEqual(job.status, 'pending') self.assertEqual(job.status, 'pending')
self.assertEqual(set(job.hosts.values_list('pk', flat=True)), self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
set([self.host.pk])) set([self.host.pk]))
@@ -139,9 +144,8 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.processed_hosts.count(), 1) self.assertEqual(job.processed_hosts.count(), 1)
def test_run_job_that_fails(self): def test_run_job_that_fails(self):
self.create_test_playbook(TEST_PLAYBOOK2) self.create_test_project(TEST_PLAYBOOK2)
job = Job.objects.create(name='test-job', inventory=self.inventory, job = self.create_test_job()
project=self.project)
self.assertEqual(job.status, 'pending') self.assertEqual(job.status, 'pending')
self.assertEqual(set(job.hosts.values_list('pk', flat=True)), self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
set([self.host.pk])) set([self.host.pk]))
@@ -163,9 +167,8 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.processed_hosts.count(), 1) self.assertEqual(job.processed_hosts.count(), 1)
def test_check_job_where_task_would_fail(self): def test_check_job_where_task_would_fail(self):
self.create_test_playbook(TEST_PLAYBOOK2) self.create_test_project(TEST_PLAYBOOK2)
job = Job.objects.create(name='test-job', inventory=self.inventory, job = self.create_test_job(job_type='check')
project=self.project, job_type='check')
self.assertEqual(job.status, 'pending') self.assertEqual(job.status, 'pending')
self.assertEqual(set(job.hosts.values_list('pk', flat=True)), self.assertEqual(set(job.hosts.values_list('pk', flat=True)),
set([self.host.pk])) set([self.host.pk]))
@@ -187,5 +190,3 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.unreachable_hosts.count(), 0) self.assertEqual(job.unreachable_hosts.count(), 0)
self.assertEqual(job.skipped_hosts.count(), 1) self.assertEqual(job.skipped_hosts.count(), 1)
self.assertEqual(job.processed_hosts.count(), 1) self.assertEqual(job.processed_hosts.count(), 1)

View File

@@ -102,6 +102,10 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'public', 'media') # FIXME: Is this where we
# Examples: "http://media.lawrence.com", "http://example.com/media/" # Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
# Absolute filesystem path to the directory to host projects (with playbooks).
# This directory should not be web-accessible.
PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects')
SITE_ID = 1 SITE_ID = 1
# Make this unique, and don't share it with anybody. # Make this unique, and don't share it with anybody.

View File

@@ -131,6 +131,22 @@ pre.result-display {
{% block blockbots %} {% block blockbots %}
{{ block.super }} {{ block.super }}
<script type="text/javascript">
var django = django || {};
if (django.jQuery) {
(function($) {
// Update playbook list based on project selected.
function onProjectChange() {
var project_pk = $('select[name="project"]').val() || 0;
$('select[name="playbook"] span option').unwrap();
$('select[name="playbook"] option:not(.project-' + project_pk + '):not([value=""])').wrap('<span/>');
}
$(function() {
$('select[name="project"]').each(onProjectChange).change(onProjectChange);
})
})(django.jQuery);
}
</script>
{% endblock %} {% endblock %}
{% block branding %} {% block branding %}

View File

@@ -6,6 +6,7 @@ django-jsonfield==0.9.2
ipython==0.13.1 ipython==0.13.1
# psycopg2==2.4.6 # psycopg2==2.4.6
python-dateutil==1.5 python-dateutil==1.5
PyYAML==3.10
South==0.7.6 South==0.7.6
requests requests
djangorestframework djangorestframework