mirror of
https://github.com/ansible/awx.git
synced 2026-01-14 03:10:42 -03:30
For AC-132. Work in progress on project SCM support.
This commit is contained in:
parent
89f5182935
commit
fc68955bad
@ -173,17 +173,30 @@ class TeamAdmin(BaseModelAdmin):
|
||||
list_display = ('name', 'description', 'active')
|
||||
filter_horizontal = ('projects', 'users')
|
||||
|
||||
class ProjectUpdateInline(admin.StackedInline):
|
||||
|
||||
model = ProjectUpdate
|
||||
extra = 0
|
||||
can_delete = True
|
||||
fields = ('created', 'status', 'result_stdout')
|
||||
readonly_fields = ('created', 'status', 'result_stdout')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
class ProjectAdmin(BaseModelAdmin):
|
||||
|
||||
list_display = ('name', 'description', 'active')
|
||||
fieldsets = (
|
||||
(None, {'fields': (('name', 'active'), 'description', 'local_path',
|
||||
'get_playbooks_display')}),
|
||||
(_('SCM'), {'fields': ('scm_type', 'scm_url', 'scm_branch')}),
|
||||
(_('Tags'), {'fields': ('tags',)}),
|
||||
(_('Audit'), {'fields': ('created', 'created_by',)}),
|
||||
)
|
||||
readonly_fields = ('created', 'created_by', 'get_playbooks_display')
|
||||
form = ProjectAdminForm
|
||||
inlines = [ProjectUpdateInline]
|
||||
|
||||
def get_playbooks_display(self, obj):
|
||||
return '<br/>'.join([format_html('{0}', x) for x in
|
||||
|
||||
359
awx/main/migrations/0009_v13_changes.py
Normal file
359
awx/main/migrations/0009_v13_changes.py
Normal file
@ -0,0 +1,359 @@
|
||||
# -*- 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 model 'ProjectUpdate'
|
||||
db.create_table(u'main_projectupdate', (
|
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
|
||||
('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='project_updates', to=orm['main.Project'])),
|
||||
('cancel_flag', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('status', self.gf('django.db.models.fields.CharField')(default='new', max_length=20)),
|
||||
('failed', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('job_args', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
|
||||
('job_cwd', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
|
||||
('job_env', self.gf('jsonfield.fields.JSONField')(default={}, blank=True)),
|
||||
('result_stdout', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
|
||||
('result_traceback', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
|
||||
('celery_task_id', self.gf('django.db.models.fields.CharField')(default='', max_length=100, blank=True)),
|
||||
))
|
||||
db.send_create_signal('main', ['ProjectUpdate'])
|
||||
|
||||
# Adding field 'Project.scm_type'
|
||||
db.add_column(u'main_project', 'scm_type',
|
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=8, null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Project.scm_url'
|
||||
db.add_column(u'main_project', 'scm_url',
|
||||
self.gf('django.db.models.fields.URLField')(default='', max_length=1024, null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Project.scm_branch'
|
||||
db.add_column(u'main_project', 'scm_branch',
|
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=256, null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Project.scm_clean'
|
||||
db.add_column(u'main_project', 'scm_clean',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Project.scm_username'
|
||||
db.add_column(u'main_project', 'scm_username',
|
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=256, null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Project.scm_password'
|
||||
db.add_column(u'main_project', 'scm_password',
|
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Project.scm_key_data'
|
||||
db.add_column(u'main_project', 'scm_key_data',
|
||||
self.gf('django.db.models.fields.TextField')(default='', null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Project.scm_key_unlock'
|
||||
db.add_column(u'main_project', 'scm_key_unlock',
|
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'ProjectUpdate'
|
||||
db.delete_table(u'main_projectupdate')
|
||||
|
||||
# Deleting field 'Project.scm_type'
|
||||
db.delete_column(u'main_project', 'scm_type')
|
||||
|
||||
# Deleting field 'Project.scm_url'
|
||||
db.delete_column(u'main_project', 'scm_url')
|
||||
|
||||
# Deleting field 'Project.scm_branch'
|
||||
db.delete_column(u'main_project', 'scm_branch')
|
||||
|
||||
# Deleting field 'Project.scm_clean'
|
||||
db.delete_column(u'main_project', 'scm_clean')
|
||||
|
||||
# Deleting field 'Project.scm_username'
|
||||
db.delete_column(u'main_project', 'scm_username')
|
||||
|
||||
# Deleting field 'Project.scm_password'
|
||||
db.delete_column(u'main_project', 'scm_password')
|
||||
|
||||
# Deleting field 'Project.scm_key_data'
|
||||
db.delete_column(u'main_project', 'scm_key_data')
|
||||
|
||||
# Deleting field 'Project.scm_key_unlock'
|
||||
db.delete_column(u'main_project', 'scm_key_unlock')
|
||||
|
||||
|
||||
models = {
|
||||
u'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'auth.permission': {
|
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
u'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
u'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'main.credential': {
|
||||
'Meta': {'object_name': 'Credential'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
|
||||
},
|
||||
'main.group': {
|
||||
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
|
||||
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||
},
|
||||
'main.host': {
|
||||
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
|
||||
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
|
||||
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||
},
|
||||
'main.inventory': {
|
||||
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
|
||||
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'main.job': {
|
||||
'Meta': {'object_name': 'Job'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
|
||||
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
|
||||
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
|
||||
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
|
||||
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||
},
|
||||
'main.jobevent': {
|
||||
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
|
||||
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
|
||||
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
|
||||
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
|
||||
},
|
||||
u'main.jobhostsummary': {
|
||||
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
|
||||
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
|
||||
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||
},
|
||||
'main.jobtemplate': {
|
||||
'Meta': {'object_name': 'JobTemplate'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
|
||||
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||
},
|
||||
'main.organization': {
|
||||
'Meta': {'object_name': 'Organization'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||
},
|
||||
'main.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
|
||||
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
|
||||
},
|
||||
u'main.project': {
|
||||
'Meta': {'object_name': 'Project'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'null': 'True', 'blank': 'True'}),
|
||||
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'scm_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
|
||||
'scm_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||
'scm_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'null': 'True', 'blank': 'True'}),
|
||||
'scm_url': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||
'scm_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'main.projectupdate': {
|
||||
'Meta': {'object_name': 'ProjectUpdate'},
|
||||
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': u"orm['main.Project']"}),
|
||||
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'})
|
||||
},
|
||||
'main.team': {
|
||||
'Meta': {'object_name': 'Team'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
|
||||
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||
},
|
||||
u'taggit.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
|
||||
},
|
||||
u'taggit.taggeditem': {
|
||||
'Meta': {'object_name': 'TaggedItem'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
|
||||
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['main']
|
||||
@ -19,6 +19,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.timezone import now
|
||||
from django.utils.text import slugify
|
||||
|
||||
# Django-JSONField
|
||||
from jsonfield import JSONField
|
||||
@ -29,11 +30,12 @@ from taggit.managers import TaggableManager
|
||||
# Django-Celery
|
||||
from djcelery.models import TaskMeta
|
||||
|
||||
__all__ = ['PrimordialModel', 'Organization', 'Team', 'Project', 'Credential',
|
||||
'Inventory', 'Host', 'Group', 'Permission', 'JobTemplate', 'Job',
|
||||
'JobHostSummary', 'JobEvent', 'PERM_INVENTORY_ADMIN',
|
||||
'PERM_INVENTORY_READ', 'PERM_INVENTORY_WRITE',
|
||||
'PERM_INVENTORY_DEPLOY', 'PERM_INVENTORY_CHECK']
|
||||
__all__ = ['PrimordialModel', 'Organization', 'Team', 'Project',
|
||||
'ProjectUpdate', 'Credential', 'Inventory', 'Host', 'Group',
|
||||
'Permission', 'JobTemplate', 'Job', 'JobHostSummary', 'JobEvent',
|
||||
'PERM_INVENTORY_ADMIN', 'PERM_INVENTORY_READ',
|
||||
'PERM_INVENTORY_WRITE', 'PERM_INVENTORY_DEPLOY',
|
||||
'PERM_INVENTORY_CHECK', 'JOB_STATUS_CHOICES']
|
||||
|
||||
logger = logging.getLogger('awx.main.models')
|
||||
|
||||
@ -59,6 +61,16 @@ PERMISSION_TYPE_CHOICES = [
|
||||
(PERM_INVENTORY_CHECK, _('Deploy To Inventory (Dry Run)')),
|
||||
]
|
||||
|
||||
JOB_STATUS_CHOICES = [
|
||||
('new', _('New')), # Job has been created, but not started.
|
||||
('pending', _('Pending')), # Job has been queued, but is not yet running.
|
||||
('running', _('Running')), # Job is currently running.
|
||||
('successful', _('Successful')), # Job completed successfully.
|
||||
('failed', _('Failed')), # Job completed, but with failures.
|
||||
('error', _('Error')), # The job was unable to run.
|
||||
('canceled', _('Canceled')), # The job was canceled before completion.
|
||||
]
|
||||
|
||||
class PrimordialModel(models.Model):
|
||||
'''
|
||||
common model for all object types that have these standard fields
|
||||
@ -470,6 +482,13 @@ class Project(CommonModel):
|
||||
A project represents a playbook git repo that can access a set of inventories
|
||||
'''
|
||||
|
||||
SCM_TYPE_CHOICES = [
|
||||
('', _('Manual')),
|
||||
('git', _('Git')),
|
||||
('hg', _('Mercurial')),
|
||||
('svn', _('Subversion')),
|
||||
]
|
||||
|
||||
# this is not part of the project, but managed with perms
|
||||
# inventories = models.ManyToManyField('Inventory', blank=True, related_name='projects')
|
||||
|
||||
@ -483,7 +502,7 @@ class Project(CommonModel):
|
||||
if os.path.exists(settings.PROJECTS_ROOT):
|
||||
paths = [x for x in os.listdir(settings.PROJECTS_ROOT)
|
||||
if os.path.isdir(os.path.join(settings.PROJECTS_ROOT, x))
|
||||
and not x.startswith('.')]
|
||||
and not x.startswith('.') and not x.startswith('_')]
|
||||
qs = Project.objects.filter(active=True)
|
||||
used_paths = qs.values_list('local_path', flat=True)
|
||||
return [x for x in paths if x not in used_paths]
|
||||
@ -495,20 +514,97 @@ class Project(CommonModel):
|
||||
# Not unique for now, otherwise "deletes" won't allow reusing the
|
||||
# same path for another active project.
|
||||
#unique=True,
|
||||
blank=True,
|
||||
help_text=_('Local path (relative to PROJECTS_ROOT) containing '
|
||||
'playbooks and related files for this project.')
|
||||
)
|
||||
#scm_type = models.CharField(max_length=64)
|
||||
#default_playbook = models.CharField(max_length=1024)
|
||||
scm_type = models.CharField(
|
||||
max_length=8,
|
||||
choices=SCM_TYPE_CHOICES,
|
||||
blank=True,
|
||||
null=True,
|
||||
default='',
|
||||
verbose_name=_('SCM Type'),
|
||||
)
|
||||
scm_url = models.URLField(
|
||||
max_length=1024,
|
||||
blank=True,
|
||||
null=True,
|
||||
default='',
|
||||
verbose_name=_('SCM URL'),
|
||||
)
|
||||
scm_branch = models.CharField(
|
||||
max_length=256,
|
||||
blank=True,
|
||||
null=True,
|
||||
default='',
|
||||
verbose_name=_('SCM Branch'),
|
||||
help_text=_('Specific branch, tag or commit to checkout.'),
|
||||
)
|
||||
scm_clean = models.BooleanField(
|
||||
default=False,
|
||||
)
|
||||
scm_username = models.CharField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default='',
|
||||
max_length=256,
|
||||
verbose_name=_('Username'),
|
||||
help_text=_('SCM username for this project.'),
|
||||
)
|
||||
scm_password = models.CharField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default='',
|
||||
max_length=1024,
|
||||
verbose_name=_('Password'),
|
||||
help_text=_('SCM password (or "ASK" to prompt the user).'),
|
||||
)
|
||||
scm_key_data = models.TextField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default='',
|
||||
verbose_name=_('SSH private key'),
|
||||
help_text=_('RSA or DSA private key to be used instead of password.'),
|
||||
)
|
||||
scm_key_unlock = models.CharField(
|
||||
max_length=1024,
|
||||
null=True,
|
||||
blank=True,
|
||||
default='',
|
||||
verbose_name=_('SSH key unlock'),
|
||||
help_text=_('Passphrase to unlock SSH private key if encrypted (or '
|
||||
'"ASK" to prompt the user).'),
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(Project, self).save(*args, **kwargs)
|
||||
if self.scm_type and not self.local_path.startswith('_'):
|
||||
slug_name = slugify(unicode(self.name)).replace(u'-', u'_')
|
||||
self.local_path = u'_%d__%s' % (self.pk, slug_name)
|
||||
self.save(update_fields=['local_path'])
|
||||
|
||||
def update(self):
|
||||
if self.scm_type:
|
||||
project_update = self.project_updates.create()
|
||||
project_update.start()
|
||||
return project_update
|
||||
|
||||
@property
|
||||
def last_update(self):
|
||||
try:
|
||||
return self.project_updates.order_by('-modified')[0]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:project_detail', args=(self.pk,))
|
||||
|
||||
def get_project_path(self):
|
||||
def get_project_path(self, check_if_exists=True):
|
||||
local_path = os.path.basename(self.local_path)
|
||||
if local_path and not local_path.startswith('.'):
|
||||
proj_path = os.path.join(settings.PROJECTS_ROOT, local_path)
|
||||
if os.path.exists(proj_path):
|
||||
if not check_if_exists or os.path.exists(proj_path):
|
||||
return proj_path
|
||||
|
||||
@property
|
||||
@ -543,6 +639,118 @@ class Project(CommonModel):
|
||||
results.append(playbook)
|
||||
return results
|
||||
|
||||
class ProjectUpdate(models.Model):
|
||||
'''
|
||||
Job for tracking internal project updates.
|
||||
'''
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
|
||||
created = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
)
|
||||
modified = models.DateTimeField(
|
||||
auto_now=True,
|
||||
)
|
||||
project = models.ForeignKey(
|
||||
'Project',
|
||||
related_name='project_updates',
|
||||
on_delete=models.CASCADE,
|
||||
editable=False,
|
||||
)
|
||||
cancel_flag = models.BooleanField(
|
||||
blank=True,
|
||||
default=False,
|
||||
editable=False,
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=JOB_STATUS_CHOICES,
|
||||
default='new',
|
||||
editable=False,
|
||||
)
|
||||
failed = models.BooleanField(
|
||||
default=False,
|
||||
editable=False,
|
||||
)
|
||||
job_args = models.CharField(
|
||||
max_length=1024,
|
||||
blank=True,
|
||||
default='',
|
||||
editable=False,
|
||||
)
|
||||
job_cwd = models.CharField(
|
||||
max_length=1024,
|
||||
blank=True,
|
||||
default='',
|
||||
editable=False,
|
||||
)
|
||||
job_env = JSONField(
|
||||
blank=True,
|
||||
default={},
|
||||
editable=False,
|
||||
)
|
||||
result_stdout = models.TextField(
|
||||
blank=True,
|
||||
default='',
|
||||
editable=False,
|
||||
)
|
||||
result_traceback = models.TextField(
|
||||
blank=True,
|
||||
default='',
|
||||
editable=False,
|
||||
)
|
||||
celery_task_id = models.CharField(
|
||||
max_length=100,
|
||||
blank=True,
|
||||
default='',
|
||||
editable=False,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.failed = bool(self.status in ('failed', 'error', 'canceled'))
|
||||
super(ProjectUpdate, self).save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def celery_task(self):
|
||||
try:
|
||||
if self.celery_task_id:
|
||||
return TaskMeta.objects.get(task_id=self.celery_task_id)
|
||||
except TaskMeta.DoesNotExist:
|
||||
pass
|
||||
|
||||
@property
|
||||
def can_start(self):
|
||||
return bool(self.status == 'new')
|
||||
|
||||
def start(self, **kwargs):
|
||||
from awx.main.tasks import RunProjectUpdate
|
||||
if not self.can_start:
|
||||
return False
|
||||
self.status = 'pending'
|
||||
self.save(update_fields=['status'])
|
||||
task_result = RunProjectUpdate().delay(self.pk, **kwargs)
|
||||
# Reload project update from database so we don't clobber results
|
||||
# from RunProjectUpdate (mainly from tests when using Django 1.4.x).
|
||||
project_update = ProjectUpdate.objects.get(pk=self.pk)
|
||||
# The TaskMeta instance in the database isn't created until the worker
|
||||
# starts processing the task, so we can only store the task ID here.
|
||||
project_update.celery_task_id = task_result.task_id
|
||||
project_update.save(update_fields=['celery_task_id'])
|
||||
return True
|
||||
|
||||
@property
|
||||
def can_cancel(self):
|
||||
return bool(self.status in ('pending', 'running'))
|
||||
|
||||
def cancel(self):
|
||||
if self.can_cancel:
|
||||
if not self.cancel_flag:
|
||||
self.cancel_flag = True
|
||||
self.save(update_fields=['cancel_flag'])
|
||||
return self.cancel_flag
|
||||
|
||||
class Permission(CommonModelNameNotUnique):
|
||||
'''
|
||||
A permission allows a user, project, or team to be able to use an inventory source.
|
||||
@ -702,16 +910,6 @@ class Job(CommonModelNameNotUnique):
|
||||
('scheduled', _('Scheduled')),
|
||||
]
|
||||
|
||||
STATUS_CHOICES = [
|
||||
('new', _('New')), # Job has been created, but not started.
|
||||
('pending', _('Pending')), # Job has been queued, but is not yet running.
|
||||
('running', _('Running')), # Job is currently running.
|
||||
('successful', _('Successful')), # Job completed successfully.
|
||||
('failed', _('Failed')), # Job completed, but with failures.
|
||||
('error', _('Error')), # The job was unable to run.
|
||||
('canceled', _('Canceled')), # The job was canceled before completion.
|
||||
]
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
|
||||
@ -783,7 +981,7 @@ class Job(CommonModelNameNotUnique):
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
choices=JOB_STATUS_CHOICES,
|
||||
default='new',
|
||||
editable=False,
|
||||
)
|
||||
|
||||
@ -195,7 +195,7 @@ class ProjectSerializer(BaseSerializer):
|
||||
valid_local_paths = Project.get_local_path_choices()
|
||||
if self.object:
|
||||
valid_local_paths.append(self.object.local_path)
|
||||
if attrs[source] not in valid_local_paths:
|
||||
if source in attrs and attrs[source] not in valid_local_paths:
|
||||
raise serializers.ValidationError('Invalid path choice')
|
||||
return attrs
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import logging
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# Pexpect
|
||||
@ -20,33 +21,31 @@ from celery import Task
|
||||
from django.conf import settings
|
||||
|
||||
# AWX
|
||||
from awx.main.models import Job
|
||||
from awx.main.models import Job, ProjectUpdate
|
||||
|
||||
__all__ = ['RunJob']
|
||||
__all__ = ['RunJob', 'RunProjectUpdate']
|
||||
|
||||
logger = logging.getLogger('awx.main.tasks')
|
||||
|
||||
class RunJob(Task):
|
||||
'''
|
||||
Celery task to run a job using ansible-playbook.
|
||||
'''
|
||||
class BaseTask(Task):
|
||||
|
||||
name = None
|
||||
model = None
|
||||
|
||||
name = 'run_job'
|
||||
|
||||
def update_job(self, job_pk, **job_updates):
|
||||
def update_model(self, pk, **updates):
|
||||
'''
|
||||
Reload Job from database and update the given fields.
|
||||
Reload model from database and update the given fields.
|
||||
'''
|
||||
job = Job.objects.get(pk=job_pk)
|
||||
if job_updates:
|
||||
instance = self.model.objects.get(pk=pk)
|
||||
if updates:
|
||||
update_fields = []
|
||||
for field, value in job_updates.items():
|
||||
setattr(job, field, value)
|
||||
for field, value in updates.items():
|
||||
setattr(instance, field, value)
|
||||
update_fields.append(field)
|
||||
if field == 'status':
|
||||
update_fields.append('failed')
|
||||
job.save(update_fields=update_fields)
|
||||
return job
|
||||
instance.save(update_fields=update_fields)
|
||||
return instance
|
||||
|
||||
def get_path_to(self, *args):
|
||||
'''
|
||||
@ -54,21 +53,141 @@ class RunJob(Task):
|
||||
'''
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), *args))
|
||||
|
||||
def build_ssh_key_path(self, job, **kwargs):
|
||||
def build_ssh_key_path(self, instance, **kwargs):
|
||||
'''
|
||||
Create a temporary file containing the SSH private key.
|
||||
'''
|
||||
creds = job.credential
|
||||
if creds and creds.ssh_key_data:
|
||||
ssh_key_data = getattr(instance, 'ssh_key_data', '')
|
||||
if not ssh_key_data:
|
||||
credential = getattr(instance, 'credential', None)
|
||||
ssh_key_data = getattr(credential, 'ssh_key_data', '')
|
||||
if ssh_key_data:
|
||||
# FIXME: File permissions?
|
||||
handle, path = tempfile.mkstemp()
|
||||
f = os.fdopen(handle, 'w')
|
||||
f.write(creds.ssh_key_data)
|
||||
f.write(ssh_key_data)
|
||||
f.close()
|
||||
return path
|
||||
else:
|
||||
return ''
|
||||
|
||||
def build_passwords(self, instance, **kwargs):
|
||||
'''
|
||||
Build a dictionary of passwords responding to prompts.
|
||||
'''
|
||||
return {}
|
||||
|
||||
def build_env(self, instance, **kwargs):
|
||||
'''
|
||||
Build environment dictionary for ansible-playbook.
|
||||
'''
|
||||
env = dict(os.environ.items())
|
||||
# Add ANSIBLE_* settings to the subprocess environment.
|
||||
for attr in dir(settings):
|
||||
if attr == attr.upper() and attr.startswith('ANSIBLE_'):
|
||||
env[attr] = str(getattr(settings, attr))
|
||||
# Also set environment variables configured in AWX_TASK_ENV setting.
|
||||
for key, value in settings.AWX_TASK_ENV.items():
|
||||
env[key] = str(value)
|
||||
# Set environment variables needed for inventory and job event
|
||||
# callbacks to work.
|
||||
env['ANSIBLE_NOCOLOR'] = '1' # Prevent output of escape sequences.
|
||||
return env
|
||||
|
||||
def build_args(self, instance, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def build_cwd(self, instance, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_password_prompts(self):
|
||||
'''
|
||||
Return a dictionary of prompt regular expressions and password lookup
|
||||
keys.
|
||||
'''
|
||||
return {
|
||||
r'Enter passphrase for .*:': 'ssh_key_unlock',
|
||||
r'Bad passphrase, try again for .*:': '',
|
||||
}
|
||||
|
||||
def run_pexpect(self, pk, args, cwd, env, passwords):
|
||||
'''
|
||||
Run the given command using pexpect to capture output and provide
|
||||
passwords when requested.
|
||||
'''
|
||||
status, stdout = 'error', ''
|
||||
logfile = cStringIO.StringIO()
|
||||
logfile_pos = logfile.tell()
|
||||
child = pexpect.spawn(args[0], args[1:], cwd=cwd, env=env)
|
||||
child.logfile_read = logfile
|
||||
canceled = False
|
||||
last_stdout_update = time.time()
|
||||
expect_list = []
|
||||
expect_passwords = {}
|
||||
for n, item in enumerate(self.get_password_prompts().items()):
|
||||
expect_list.append(item[0])
|
||||
expect_passwords[n] = passwords.get(item[1], '')
|
||||
expect_list.extend([pexpect.TIMEOUT, pexpect.EOF])
|
||||
while child.isalive():
|
||||
result_id = child.expect(expect_list, timeout=2)
|
||||
if result_id in expect_passwords:
|
||||
child.sendline(expect_passwords[result_id])
|
||||
updates = {}
|
||||
if logfile_pos != logfile.tell():
|
||||
updates['result_stdout'] = logfile.getvalue()
|
||||
last_stdout_update = time.time()
|
||||
instance = self.update_model(pk, **updates)
|
||||
if instance.cancel_flag:
|
||||
child.close(True)
|
||||
canceled = True
|
||||
#elif (time.time() - last_stdout_update) > 30: # FIXME: Configurable idle timeout?
|
||||
# print 'canceling...'
|
||||
# child.close(True)
|
||||
# canceled = True
|
||||
if canceled:
|
||||
status = 'canceled'
|
||||
elif child.exitstatus == 0:
|
||||
status = 'successful'
|
||||
else:
|
||||
status = 'failed'
|
||||
stdout = logfile.getvalue()
|
||||
return status, stdout
|
||||
|
||||
def run(self, pk, **kwargs):
|
||||
'''
|
||||
Run the job/task using ansible-playbook and capture its output.
|
||||
'''
|
||||
instance = self.update_model(pk, status='running')
|
||||
status, stdout, tb = 'error', '', ''
|
||||
try:
|
||||
kwargs['ssh_key_path'] = self.build_ssh_key_path(instance, **kwargs)
|
||||
kwargs['passwords'] = self.build_passwords(instance, **kwargs)
|
||||
args = self.build_args(instance, **kwargs)
|
||||
cwd = self.build_cwd(instance, **kwargs)
|
||||
env = self.build_env(instance, **kwargs)
|
||||
instance = self.update_model(pk, job_args=json.dumps(args),
|
||||
job_cwd=cwd, job_env=env)
|
||||
status, stdout = self.run_pexpect(pk, args, cwd, env,
|
||||
kwargs['passwords'])
|
||||
except Exception:
|
||||
tb = traceback.format_exc()
|
||||
finally:
|
||||
if kwargs.get('ssh_key_path', ''):
|
||||
try:
|
||||
os.remove(kwargs['ssh_key_path'])
|
||||
except IOError:
|
||||
pass
|
||||
self.update_model(pk, status=status, result_stdout=stdout,
|
||||
result_traceback=tb)
|
||||
|
||||
class RunJob(BaseTask):
|
||||
'''
|
||||
Celery task to run a job using ansible-playbook.
|
||||
'''
|
||||
|
||||
name = 'run_job'
|
||||
model = Job
|
||||
|
||||
def build_passwords(self, job, **kwargs):
|
||||
'''
|
||||
Build a dictionary of passwords for SSH private key, SSH user and sudo.
|
||||
@ -87,22 +206,12 @@ class RunJob(Task):
|
||||
Build environment dictionary for ansible-playbook.
|
||||
'''
|
||||
plugin_dir = self.get_path_to('..', 'plugins', 'callback')
|
||||
env = dict(os.environ.items())
|
||||
# question: when running over CLI, generate a random ID or grab next, etc?
|
||||
# answer: TBD
|
||||
# Add ANSIBLE_* settings to the subprocess environment.
|
||||
for attr in dir(settings):
|
||||
if attr == attr.upper() and attr.startswith('ANSIBLE_'):
|
||||
env[attr] = str(getattr(settings, attr))
|
||||
# Also set environment variables configured in AWX_TASK_ENV setting.
|
||||
for key, value in settings.AWX_TASK_ENV.items():
|
||||
env[key] = str(value)
|
||||
env = super(RunJob, self).build_env(job, **kwargs)
|
||||
# Set environment variables needed for inventory and job event
|
||||
# callbacks to work.
|
||||
env['JOB_ID'] = str(job.pk)
|
||||
env['INVENTORY_ID'] = str(job.inventory.pk)
|
||||
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir
|
||||
env['ANSIBLE_NOCOLOR'] = '1' # Prevent output of escape sequences.
|
||||
env['REST_API_URL'] = settings.INTERNAL_API_URL
|
||||
env['REST_API_TOKEN'] = job.task_auth_token or ''
|
||||
return env
|
||||
@ -154,79 +263,88 @@ class RunJob(Task):
|
||||
args = ['ssh-agent', 'sh', '-c', cmd]
|
||||
return args
|
||||
|
||||
def run_pexpect(self, job_pk, args, cwd, env, passwords):
|
||||
'''
|
||||
Run the job using pexpect to capture output and provide passwords when
|
||||
requested.
|
||||
'''
|
||||
status, stdout = 'error', ''
|
||||
logfile = cStringIO.StringIO()
|
||||
logfile_pos = logfile.tell()
|
||||
child = pexpect.spawn(args[0], args[1:], cwd=cwd, env=env)
|
||||
child.logfile_read = logfile
|
||||
job_canceled = False
|
||||
while child.isalive():
|
||||
expect_list = [
|
||||
r'Enter passphrase for .*:',
|
||||
r'Bad passphrase, try again for .*:',
|
||||
r'sudo password.*:',
|
||||
r'SSH password:',
|
||||
r'Password:',
|
||||
pexpect.TIMEOUT,
|
||||
pexpect.EOF,
|
||||
]
|
||||
result_id = child.expect(expect_list, timeout=2)
|
||||
if result_id == 0:
|
||||
child.sendline(passwords.get('ssh_key_unlock', ''))
|
||||
elif result_id == 1:
|
||||
child.sendline('')
|
||||
elif result_id == 2:
|
||||
child.sendline(passwords.get('sudo_password', ''))
|
||||
elif result_id in (3, 4):
|
||||
child.sendline(passwords.get('ssh_password', ''))
|
||||
job_updates = {}
|
||||
if logfile_pos != logfile.tell():
|
||||
job_updates['result_stdout'] = logfile.getvalue()
|
||||
job = self.update_job(job_pk, **job_updates)
|
||||
if job.cancel_flag:
|
||||
child.close(True)
|
||||
job_canceled = True
|
||||
if job_canceled:
|
||||
status = 'canceled'
|
||||
elif child.exitstatus == 0:
|
||||
status = 'successful'
|
||||
else:
|
||||
status = 'failed'
|
||||
stdout = logfile.getvalue()
|
||||
return status, stdout
|
||||
|
||||
def run(self, job_pk, **kwargs):
|
||||
'''
|
||||
Run the job using ansible-playbook and capture its output.
|
||||
'''
|
||||
job = self.update_job(job_pk, status='running')
|
||||
status, stdout, tb = 'error', '', ''
|
||||
try:
|
||||
kwargs['ssh_key_path'] = self.build_ssh_key_path(job, **kwargs)
|
||||
kwargs['passwords'] = self.build_passwords(job, **kwargs)
|
||||
args = self.build_args(job, **kwargs)
|
||||
cwd = job.project.get_project_path()
|
||||
def build_cwd(self, job, **kwargs):
|
||||
cwd = job.project.get_project_path()
|
||||
if not cwd:
|
||||
root = settings.PROJECTS_ROOT
|
||||
if not cwd:
|
||||
raise RuntimeError('project local_path %s cannot be found in %s' %
|
||||
(job.project.local_path, root))
|
||||
env = self.build_env(job, **kwargs)
|
||||
job = self.update_job(job_pk, job_args=json.dumps(args),
|
||||
job_cwd=cwd, job_env=env)
|
||||
status, stdout = self.run_pexpect(job_pk, args, cwd, env,
|
||||
kwargs['passwords'])
|
||||
except Exception:
|
||||
tb = traceback.format_exc()
|
||||
finally:
|
||||
if kwargs.get('ssh_key_path', ''):
|
||||
try:
|
||||
os.remove(kwargs['ssh_key_path'])
|
||||
except IOError:
|
||||
pass
|
||||
self.update_job(job_pk, status=status, result_stdout=stdout,
|
||||
result_traceback=tb)
|
||||
raise RuntimeError('project local_path %s cannot be found in %s' %
|
||||
(job.project.local_path, root))
|
||||
return cwd
|
||||
|
||||
def get_password_prompts(self):
|
||||
d = super(RunJob, self).get_password_prompts()
|
||||
d.update({
|
||||
r'sudo password.*:': 'sudo_password',
|
||||
r'SSH password:': 'ssh_password',
|
||||
r'Password:': 'ssh_password',
|
||||
})
|
||||
return d
|
||||
|
||||
class RunProjectUpdate(BaseTask):
|
||||
|
||||
name = 'run_project_update'
|
||||
model = ProjectUpdate
|
||||
|
||||
def build_passwords(self, project_update, **kwargs):
|
||||
'''
|
||||
Build a dictionary of passwords for SSH private key.
|
||||
'''
|
||||
passwords = {}
|
||||
project = project_update.project
|
||||
value = project.scm_key_unlock
|
||||
if value not in ('', 'ASK'):
|
||||
passwords['ssh_key_unlock'] = value
|
||||
passwords['scm_username'] = project.scm_username
|
||||
passwords['scm_password'] = project.scm_password
|
||||
return passwords
|
||||
|
||||
def build_env(self, project_update, **kwargs):
|
||||
'''
|
||||
Build environment dictionary for ansible-playbook.
|
||||
'''
|
||||
env = super(RunProjectUpdate, self).build_env(project_update, **kwargs)
|
||||
return env
|
||||
|
||||
def build_args(self, project_update, **kwargs):
|
||||
'''
|
||||
Build command line argument list for running ansible-playbook,
|
||||
optionally using ssh-agent for public/private key authentication.
|
||||
'''
|
||||
args = ['ansible-playbook', '-i', 'localhost,']
|
||||
args.append('-%s' % ('v' * 3))
|
||||
# FIXME
|
||||
project = project_update.project
|
||||
extra_vars = {
|
||||
'project_path': project.get_project_path(check_if_exists=False),
|
||||
'scm_type': project.scm_type,
|
||||
'scm_url': project.scm_url,
|
||||
'scm_branch': project.scm_branch or 'HEAD',
|
||||
'scm_clean': project.scm_clean,
|
||||
'scm_username': project.scm_username,
|
||||
'scm_password': project.scm_password,
|
||||
}
|
||||
args.extend(['-e', json.dumps(extra_vars)])
|
||||
args.append('project_update.yml')
|
||||
|
||||
ssh_key_path = kwargs.get('ssh_key_path', '')
|
||||
subcmds = [
|
||||
('ssh-add', '-D'),
|
||||
args,
|
||||
]
|
||||
if ssh_key_path:
|
||||
subcmds.insert(1, ('ssh-add', ssh_key_path))
|
||||
cmd = ' && '.join([subprocess.list2cmdline(x) for x in subcmds])
|
||||
args = ['ssh-agent', 'sh', '-c', cmd]
|
||||
return args
|
||||
|
||||
def build_cwd(self, project_update, **kwargs):
|
||||
return self.get_path_to('..', 'playbooks')
|
||||
|
||||
def get_password_prompts(self):
|
||||
d = super(RunProjectUpdate, self).get_password_prompts()
|
||||
d.update({
|
||||
r'Username for.*:': 'scm_username',
|
||||
r'Password for.*:': 'scm_password',
|
||||
r'Are you sure you want to continue connecting (yes/no)\?': 'yes',
|
||||
})
|
||||
return d
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
from awx.main.tests.organizations import OrganizationsTest
|
||||
from awx.main.tests.users import UsersTest
|
||||
from awx.main.tests.inventory import InventoryTest
|
||||
from awx.main.tests.projects import ProjectsTest
|
||||
from awx.main.tests.projects import ProjectsTest, ProjectUpdatesTest
|
||||
from awx.main.tests.commands import *
|
||||
from awx.main.tests.scripts import *
|
||||
from awx.main.tests.tasks import RunJobTest
|
||||
|
||||
@ -777,7 +777,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
|
||||
# Sue can start a job (when passwords are already saved) as long as the
|
||||
# status is new. Reverse list so "new" will be last.
|
||||
for status in reversed([x[0] for x in Job.STATUS_CHOICES]):
|
||||
for status in reversed([x[0] for x in JOB_STATUS_CHOICES]):
|
||||
job.status = status
|
||||
job.save()
|
||||
with self.current_user(self.user_sue):
|
||||
@ -863,7 +863,7 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
self.check_invalid_auth(url, methods=('post',))
|
||||
|
||||
# sue can cancel the job, but only when it is pending or running.
|
||||
for status in [x[0] for x in Job.STATUS_CHOICES]:
|
||||
for status in [x[0] for x in JOB_STATUS_CHOICES]:
|
||||
job.status = status
|
||||
job.save()
|
||||
with self.current_user(self.user_sue):
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import urlparse
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
import django.test
|
||||
from django.test.client import Client
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.utils import override_settings
|
||||
|
||||
# AWX
|
||||
from awx.main.models import *
|
||||
from awx.main.tests.base import BaseTest
|
||||
from awx.main.tests.base import BaseTest, BaseTransactionTest
|
||||
|
||||
TEST_PLAYBOOK = '''- hosts: mygroup
|
||||
gather_facts: false
|
||||
@ -607,3 +612,170 @@ class ProjectsTest(BaseTest):
|
||||
self.delete(url2, expect=403, auth=self.get_other_credentials())
|
||||
self.delete(url2, expect=204, auth=self.get_super_credentials())
|
||||
self.delete(url2, expect=404, auth=self.get_other_credentials())
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True,
|
||||
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
|
||||
ANSIBLE_TRANSPORT='local')
|
||||
class ProjectUpdatesTest(BaseTransactionTest):
|
||||
|
||||
def setUp(self):
|
||||
super(ProjectUpdatesTest, self).setUp()
|
||||
self.setup_users()
|
||||
self.skipTest('blah')
|
||||
|
||||
def create_project(self, **kwargs):
|
||||
project = Project.objects.create(**kwargs)
|
||||
project_path = project.get_project_path(check_if_exists=False)
|
||||
self._temp_project_dirs.append(project_path)
|
||||
return project
|
||||
|
||||
def update_url_auth(self, url, username=None, password=None):
|
||||
parts = urlparse.urlsplit(url)
|
||||
|
||||
|
||||
def check_project_update(self, project, should_fail=False):
|
||||
print project.local_path
|
||||
pu = project.update()
|
||||
self.assertTrue(pu)
|
||||
pu = ProjectUpdate.objects.get(pk=pu.pk)
|
||||
print pu.status
|
||||
if should_fail:
|
||||
self.assertEqual(pu.status, 'failed', pu.result_stdout)
|
||||
else:
|
||||
self.assertEqual(pu.status, 'successful', pu.result_stdout)
|
||||
#print pu.result_traceback
|
||||
#print pu.result_stdout
|
||||
#print
|
||||
|
||||
def change_file_in_project(self, project):
|
||||
project_path = project.get_project_path()
|
||||
self.assertTrue(project_path)
|
||||
for root, dirs, files in os.walk(project_path):
|
||||
for f in files:
|
||||
if f.startswith('.'):
|
||||
continue
|
||||
path = os.path.join(root, f)
|
||||
file(path, 'wb').write('CHANGED FILE')
|
||||
return
|
||||
self.fail('no file found to change!')
|
||||
|
||||
def check_project_scm(self, project):
|
||||
# Initial checkout.
|
||||
self.check_project_update(project)
|
||||
# Update to existing checkout.
|
||||
self.check_project_update(project)
|
||||
# Change file then update (with scm_clean=False).
|
||||
self.assertFalse(project.scm_clean)
|
||||
self.change_file_in_project(project)
|
||||
self.check_project_update(project, should_fail=True)
|
||||
# Set scm_clean=True then try to update again.
|
||||
project.scm_clean = True
|
||||
project.save()
|
||||
self.check_project_update(project)
|
||||
|
||||
def test_public_git_project_over_https(self):
|
||||
scm_url = getattr(settings, 'TEST_GIT_PUBLIC_HTTPS',
|
||||
'https://github.com/ansible/ansible.github.com.git')
|
||||
if not all([scm_url]):
|
||||
self.skipTest('no public git repo defined for https!')
|
||||
project = self.create_project(
|
||||
name='my public git project over https',
|
||||
scm_type='git',
|
||||
scm_url=scm_url,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
def test_private_git_project_over_https(self):
|
||||
scm_url = getattr(settings, 'TEST_GIT_PRIVATE_HTTPS', '')
|
||||
scm_username = getattr(settings, 'TEST_GIT_USERNAME', '')
|
||||
scm_password = getattr(settings, 'TEST_GIT_PASSWORD', '')
|
||||
if not all([scm_url, scm_username, scm_password]):
|
||||
self.skipTest('no private git repo defined for https!')
|
||||
project = self.create_project(
|
||||
name='my private git project over https',
|
||||
scm_type='git',
|
||||
scm_url=scm_url,
|
||||
scm_username=scm_username,
|
||||
scm_password=scm_password,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
def test_private_git_project_over_ssh(self):
|
||||
scm_url = getattr(settings, 'TEST_GIT_PRIVATE_SSH', '')
|
||||
scm_key_data = getattr(settings, 'TEST_GIT_KEY_DATA', '')
|
||||
if not all([scm_url, scm_key_data]):
|
||||
self.skipTest('no private git repo defined for ssh!')
|
||||
project = self.create_project(
|
||||
name='my private git project over ssh',
|
||||
scm_type='git',
|
||||
scm_url=scm_url,
|
||||
scm_key_data=scm_key_data,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
def test_public_hg_project_over_https(self):
|
||||
scm_url = getattr(settings, 'TEST_HG_PUBLIC_HTTPS',
|
||||
'https://bitbucket.org/cchurch/django-hotrunner')
|
||||
if not all([scm_url]):
|
||||
self.skipTest('no public hg repo defined for https!')
|
||||
project = self.create_project(
|
||||
name='my public hg project over https',
|
||||
scm_type='hg',
|
||||
scm_url=scm_url,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
def test_private_hg_project_over_https(self):
|
||||
scm_url = getattr(settings, 'TEST_HG_PRIVATE_HTTPS', '')
|
||||
scm_username = getattr(settings, 'TEST_HG_USERNAME', '')
|
||||
scm_password = getattr(settings, 'TEST_HG_PASSWORD', '')
|
||||
if not all([scm_url, scm_username, scm_password]):
|
||||
self.skipTest('no private hg repo defined for https!')
|
||||
project = self.create_project(
|
||||
name='my private hg project over https',
|
||||
scm_type='hg',
|
||||
scm_url=scm_url,
|
||||
scm_username=scm_username,
|
||||
scm_password=scm_password,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
def test_private_hg_project_over_ssh(self):
|
||||
scm_url = getattr(settings, 'TEST_HG_PRIVATE_SSH', '')
|
||||
scm_key_data = getattr(settings, 'TEST_HG_KEY_DATA', '')
|
||||
if not all([scm_url, scm_key_data]):
|
||||
self.skipTest('no private hg repo defined for ssh!')
|
||||
project = self.create_project(
|
||||
name='my private hg project over ssh',
|
||||
scm_type='hg',
|
||||
scm_url=scm_url,
|
||||
scm_key_data=scm_key_data,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
def test_public_svn_project_over_https(self):
|
||||
scm_url = getattr(settings, 'TEST_SVN_PUBLIC_HTTPS',
|
||||
'https://projects.ninemoreminutes.com/svn/django-site-utils/')
|
||||
if not all([scm_url]):
|
||||
self.skipTest('no public svn repo defined for https!')
|
||||
project = self.create_project(
|
||||
name='my public svn project over https',
|
||||
scm_type='svn',
|
||||
scm_url=scm_url,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
def test_private_svn_project_over_https(self):
|
||||
scm_url = getattr(settings, 'TEST_SVN_PRIVATE_HTTPS', '')
|
||||
scm_username = getattr(settings, 'TEST_SVN_USERNAME', '')
|
||||
scm_password = getattr(settings, 'TEST_SVN_PASSWORD', '')
|
||||
if not all([scm_url, scm_username, scm_password]):
|
||||
self.skipTest('no private svn repo defined for https!')
|
||||
project = self.create_project(
|
||||
name='my private svn project over https',
|
||||
scm_type='svn',
|
||||
scm_url=scm_url,
|
||||
scm_username=scm_username,
|
||||
scm_password=scm_password,
|
||||
)
|
||||
self.check_project_scm(project)
|
||||
|
||||
37
awx/playbooks/project_update.yml
Normal file
37
awx/playbooks/project_update.yml
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
|
||||
# The following variables will be set by the runner of this playbook:
|
||||
# project_path: PROJECTS_DIR/_local_path_
|
||||
# scm_type: git|hg|svn
|
||||
# scm_url: https://server/repo
|
||||
# scm_branch: HEAD
|
||||
# scm_clean: true/false
|
||||
|
||||
- hosts: all
|
||||
connection: local
|
||||
gather_facts: false
|
||||
tasks:
|
||||
|
||||
# Git Tasks
|
||||
- name: update project using git
|
||||
git: dest={{project_path}} repo={{scm_url}} version={{scm_branch}} force={{scm_clean}}
|
||||
when: scm_type == 'git'
|
||||
async: 0
|
||||
poll: 5
|
||||
tags: git
|
||||
|
||||
# Mercurial Tasks
|
||||
- name: update project using hg
|
||||
hg: dest={{project_path}} repo={{scm_url}} version={{scm_branch}} force={{scm_clean}}
|
||||
when: scm_type == 'hg'
|
||||
async: 0
|
||||
poll: 5
|
||||
tags: hg
|
||||
|
||||
# Subversion Tasks
|
||||
- name: update project using svn
|
||||
subversion: dest={{project_path}} repo={{scm_url}} revision={{scm_branch}} force={{scm_clean}}
|
||||
when: scm_type == 'svn'
|
||||
async: 0
|
||||
poll: 5
|
||||
tags: svn
|
||||
@ -115,3 +115,29 @@ LOGGING['handlers']['syslog'] = {
|
||||
# the celery task.
|
||||
#AWX_TASK_ENV['FOO'] = 'BAR'
|
||||
|
||||
# Define these variables to enable more complete testing of project support for
|
||||
# SCM updates.
|
||||
try:
|
||||
path = os.path.expanduser(os.path.expandvars('~/.ssh/id_rsa.pub'))
|
||||
TEST_SSH_KEY_DATA = file(path, 'rb').read()
|
||||
except OSError:
|
||||
TEST_SSH_KEY_DATA = ''
|
||||
|
||||
TEST_GIT_USERNAME = ''
|
||||
TEST_GIT_PASSWORD = ''
|
||||
TEST_GIT_KEY_DATA = TEST_SSH_KEY_DATA
|
||||
TEST_GIT_PUBLIC_HTTPS = 'https://github.com/ansible/ansible-examples.git'
|
||||
TEST_GIT_PRIVATE_HTTPS = 'https://github.com/ansible/ansible-doc.git'
|
||||
TEST_GIT_PRIVATE_SSH = 'git@github.com:ansible/ansible-doc.git'
|
||||
|
||||
TEST_HG_USERNAME = ''
|
||||
TEST_HG_PASSWORD = ''
|
||||
TEST_HG_KEY_DATA = TEST_SSH_KEY_DATA
|
||||
TEST_HG_PUBLIC_HTTPS = 'https://bitbucket.org/cchurch/django-hotrunner'
|
||||
TEST_HG_PRIVATE_HTTPS = ''
|
||||
TEST_HG_PRIVATE_SSH = ''
|
||||
|
||||
TEST_SVN_USERNAME = ''
|
||||
TEST_SVN_PASSWORD = ''
|
||||
TEST_SVN_PUBLIC_HTTPS = 'https://projects.ninemoreminutes.com/svn/django-site-utils/trunk/'
|
||||
TEST_SVN_PRIVATE_HTTPS = ''
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user