diff --git a/Makefile b/Makefile
index 4e76fa2422..e086036bf7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-release = ansibleworks-1.2b1
+RELEASE = ansibleworks-1.2b2
clean:
rm -rf build *.egg-info
@@ -81,16 +81,15 @@ release_build:
release_ball: clean
make release_build
(cd ../ansible-doc; make)
- -(rm -rf $(release))
- mkdir -p $(release)/dist
+ -(rm -rf $(RELEASE))
+ mkdir -p $(RELEASE)/dist
cp -a dist/* $(release)/dist
- mkdir -p $(release)/setup
- cp -a setup/* $(release)/setup
- mkdir -p $(release)/docs
- cp -a ../ansible-doc/*.pdf $(release)/docs
- tar -cvf $(release)-all.tar $(release)
+ mkdir -p $(RELEASE)/setup
+ cp -a setup/* $(RELEASE)/setup
+ mkdir -p $(RELEASE)/docs
+ cp -a ../ansible-doc/*.pdf $(RELEASE)/docs
+ tar -cvf $(RELEASE)-all.tar $(RELEASE)
-clean:
+release_clean:
-(rm *.tar)
- -(rm -rf ($release))
-
+ -(rm -rf ($RELEASE))
diff --git a/ansibleworks/main/access.py b/ansibleworks/main/access.py
index 1b5548effb..d621b3bbec 100644
--- a/ansibleworks/main/access.py
+++ b/ansibleworks/main/access.py
@@ -139,18 +139,6 @@ class UserAccess(BaseAccess):
return bool(self.user.is_superuser or
obj.organizations.filter(admins__in=[self.user]).count())
-class TagAccess(BaseAccess):
-
- model = Tag
-
- def can_read(self, obj):
- # anybody can read tags, we won't show much detail other than the names
- return True
-
- def can_add(self, data):
- # anybody can make up tags
- return True
-
class OrganizationAccess(BaseAccess):
model = Organization
@@ -259,6 +247,11 @@ class HostAccess(BaseAccess):
# Checks for admin or change permission on inventory.
return check_user_access(self.user, Inventory, 'change', inventory, None)
+ def can_change(self, obj, data):
+ # Checks for admin or change permission on inventory, controls whether
+ # the user can edit variable data.
+ return check_user_access(self.user, Inventory, 'change', obj.inventory, None)
+
class GroupAccess(BaseAccess):
model = Group
@@ -275,34 +268,9 @@ class GroupAccess(BaseAccess):
def can_change(self, obj, data):
# Checks for admin or change permission on inventory, controls whether
- # the user can attach subgroups
+ # the user can attach subgroups or edit variable data.
return check_user_access(self.user, Inventory, 'change', obj.inventory, None)
-class VariableDataAccess(BaseAccess):
-
- model = VariableData
-
- def can_read(self, obj):
- if obj.host:
- inventory = obj.host.inventory
- elif obj.group:
- inventory = obj.group.inventory
- else:
- return False
- return check_user_access(self.user, Inventory, 'read', inventory)
-
- def can_change(self, obj, data):
- if obj.host:
- inventory = obj.host.inventory
- elif obj.group:
- inventory = obj.group.inventory
- else:
- return False
- return check_user_access(self.user, Inventory, 'change', inventory)
-
- def can_delete(self, obj):
- return False
-
class CredentialAccess(BaseAccess):
model = Credential
@@ -538,12 +506,10 @@ class JobEventAccess(BaseAccess):
model = JobEvent
register_access(User, UserAccess)
-register_access(Tag, TagAccess)
register_access(Organization, OrganizationAccess)
register_access(Inventory, InventoryAccess)
register_access(Host, HostAccess)
register_access(Group, GroupAccess)
-register_access(VariableData, VariableDataAccess)
register_access(Credential, CredentialAccess)
register_access(Team, TeamAccess)
register_access(Project, ProjectAccess)
diff --git a/ansibleworks/main/admin.py b/ansibleworks/main/admin.py
index 1d388d078e..9e64cf74ae 100644
--- a/ansibleworks/main/admin.py
+++ b/ansibleworks/main/admin.py
@@ -58,25 +58,23 @@ class OrganizationAdmin(BaseModelAdmin):
(_('Members'), {'fields': ('users', 'admins',)}),
(_('Projects'), {'fields': ('projects',)}),
(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by',
- 'audit_trail',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
)
- readonly_fields = ('created', 'created_by', 'audit_trail')
- filter_horizontal = ('users', 'admins', 'projects', 'tags')
+ readonly_fields = ('created', 'created_by')
+ filter_horizontal = ('users', 'admins', 'projects')
class InventoryHostInline(admin.StackedInline):
model = Host
extra = 0
fields = ('name', 'description', 'active', 'tags')
- filter_horizontal = ('tags',)
class InventoryGroupInline(admin.StackedInline):
model = Group
extra = 0
fields = ('name', 'description', 'active', 'parents', 'hosts', 'tags')
- filter_horizontal = ('parents', 'hosts', 'tags')
+ filter_horizontal = ('parents', 'hosts')
class InventoryAdmin(BaseModelAdmin):
@@ -85,15 +83,14 @@ class InventoryAdmin(BaseModelAdmin):
fieldsets = (
(None, {'fields': (('name', 'active'), 'organization', 'description',)}),
(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by', 'audit_trail',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
)
- readonly_fields = ('created', 'created_by', 'audit_trail')
- filter_horizontal = ('tags',)
+ readonly_fields = ('created', 'created_by')
inlines = [InventoryHostInline, InventoryGroupInline]
-class TagAdmin(BaseModelAdmin):
-
- list_display = ('name',)
+#class TagAdmin(BaseModelAdmin):
+#
+# list_display = ('name',)
#class AuditTrailAdmin(admin.ModelAdmin):
#
@@ -101,13 +98,6 @@ class TagAdmin(BaseModelAdmin):
# not currently on model, so disabling for now.
# filter_horizontal = ('tags',)
-class VariableDataInline(admin.StackedInline):
-
- model = VariableData
- extra = 0
- max_num = 1
- # FIXME: Doesn't yet work as inline due to the way the OneToOne field is
- # defined.
class JobHostSummaryInline(admin.TabularInline):
@@ -136,9 +126,9 @@ class JobEventInline(admin.StackedInline):
class JobHostSummaryInlineForHost(JobHostSummaryInline):
fields = ('job', 'changed', 'dark', 'failures', 'ok', 'processed',
- 'skipped')
+ 'skipped', 'failed')
readonly_fields = ('job', 'changed', 'dark', 'failures', 'ok', 'processed',
- 'skipped')
+ 'skipped', 'failed')
class JobEventInlineForHost(JobEventInline):
@@ -149,17 +139,15 @@ class HostAdmin(BaseModelAdmin):
list_display = ('name', 'inventory', 'description', 'active')
list_filter = ('inventory', 'active')
- #form = HostAdminForm
fieldsets = (
- (None, {'fields': (('name', 'active'), 'inventory', 'description', 'variable_data',
+ (None, {'fields': (('name', 'active'), 'inventory', 'description',
+ 'variables',
)}),
(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by', 'audit_trail',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
)
- readonly_fields = ('created', 'created_by', 'audit_trail')
- filter_horizontal = ('tags',)
+ readonly_fields = ('created', 'created_by')
# FIXME: Edit reverse of many to many for groups.
- #inlines = [VariableDataInline]
inlines = [JobHostSummaryInlineForHost, JobEventInlineForHost]
class GroupAdmin(BaseModelAdmin):
@@ -167,18 +155,12 @@ class GroupAdmin(BaseModelAdmin):
list_display = ('name', 'description', 'active')
fieldsets = (
(None, {'fields': (('name', 'active'), 'inventory', 'description',
- 'parents')}),
+ 'parents', 'variables')}),
(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by', 'audit_trail',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
)
- readonly_fields = ('created', 'created_by', 'audit_trail')
- filter_horizontal = ('parents', 'hosts', 'tags')
- #inlines = [VariableDataInline]
-
-class VariableDataAdmin(BaseModelAdmin):
-
- list_display = ('name', 'description', 'active')
- filter_horizontal = ('tags',)
+ readonly_fields = ('created', 'created_by')
+ filter_horizontal = ('parents', 'hosts')
class CredentialAdmin(BaseModelAdmin):
@@ -187,16 +169,15 @@ class CredentialAdmin(BaseModelAdmin):
(_('Auth Info'), {'fields': (('ssh_username', 'ssh_password'),
'ssh_key_data', 'ssh_key_unlock',
('sudo_username', 'sudo_password'))}),
- #(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by', 'audit_trail',)}),
+ (_('Tags'), {'fields': ('tags',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
)
- readonly_fields = ('created', 'created_by', 'audit_trail')
- filter_horizontal = ('tags',)
+ readonly_fields = ('created', 'created_by')
class TeamAdmin(BaseModelAdmin):
list_display = ('name', 'description', 'active')
- filter_horizontal = ('projects', 'users', 'tags')
+ filter_horizontal = ('projects', 'users')
class ProjectAdmin(BaseModelAdmin):
@@ -205,11 +186,9 @@ class ProjectAdmin(BaseModelAdmin):
(None, {'fields': (('name', 'active'), 'description', 'local_path',
'get_playbooks_display')}),
(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by', 'audit_trail',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
)
- readonly_fields = ('created', 'created_by', 'audit_trail',
- 'get_playbooks_display')
- filter_horizontal = ('tags',)
+ readonly_fields = ('created', 'created_by', 'get_playbooks_display')
form = ProjectAdminForm
def get_playbooks_display(self, obj):
@@ -221,7 +200,6 @@ class ProjectAdmin(BaseModelAdmin):
class PermissionAdmin(BaseModelAdmin):
list_display = ('name', 'description', 'active')
- filter_horizontal = ('tags',)
class JobTemplateAdmin(BaseModelAdmin):
@@ -232,17 +210,15 @@ class JobTemplateAdmin(BaseModelAdmin):
'get_create_link_display', 'get_jobs_link_display')}),
(_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook',
'credential', 'job_type')}),
- (_('More Options'), {'fields': ('forks', 'limit',
- 'verbosity', 'extra_vars'),
+ (_('More Options'), {'fields': ('forks', 'limit', 'verbosity',
+ 'extra_vars', 'job_tags', 'host_config_key'),
'classes': ('collapse',)}),
- #(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by',
- 'audit_trail',)}),
+ (_('Tags'), {'fields': ('tags',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
)
- readonly_fields = ('created', 'created_by', 'audit_trail',
- 'get_create_link_display', 'get_jobs_link_display')
+ readonly_fields = ('created', 'created_by', 'get_create_link_display',
+ 'get_jobs_link_display')
form = JobTemplateAdminForm
- #filter_horizontal = ('tags',)
def get_create_link_display(self, obj):
if not obj or not obj.pk:
@@ -272,6 +248,8 @@ class JobTemplateAdmin(BaseModelAdmin):
create_opts['verbosity'] = obj.verbosity
if obj.extra_vars:
create_opts['extra_vars'] = obj.extra_vars
+ if obj.job_tags:
+ create_opts['job_tags'] = obj.job_tags
create_url += '?%s' % urllib.urlencode(create_opts)
return format_html('{1}', create_url, 'Create Job')
get_create_link_display.short_description = _('Create Job')
@@ -291,9 +269,9 @@ class JobTemplateAdmin(BaseModelAdmin):
class JobHostSummaryInlineForJob(JobHostSummaryInline):
fields = ('host', 'changed', 'dark', 'failures', 'ok', 'processed',
- 'skipped')
+ 'skipped', 'failed')
readonly_fields = ('host', 'changed', 'dark', 'failures', 'ok',
- 'processed', 'skipped')
+ 'processed', 'skipped', 'failed')
class JobEventInlineForJob(JobEventInline):
@@ -310,13 +288,12 @@ class JobAdmin(BaseModelAdmin):
(_('Job Parameters'), {'fields': ('inventory', 'project', 'playbook',
'credential', 'job_type')}),
(_('More Options'), {'fields': ('forks', 'limit', 'verbosity',
- 'extra_vars'),
+ 'extra_vars', 'job_tags'),
'classes': ('collapse',)}),
(_('Start Job'), {'fields': ('start_job', 'ssh_password',
'sudo_password', 'ssh_key_unlock')}),
- #(_('Tags'), {'fields': ('tags',)}),
- (_('Audit Trail'), {'fields': ('created', 'created_by',
- 'audit_trail',)}),
+ (_('Tags'), {'fields': ('tags',)}),
+ (_('Audit'), {'fields': ('created', 'created_by',)}),
(_('Job Status'), {'fields': (('status', 'failed', 'cancel_job'),
'get_result_stdout_display',
'get_result_traceback_display',
@@ -325,8 +302,7 @@ class JobAdmin(BaseModelAdmin):
readonly_fields = ('status', 'failed', 'get_job_template_display',
'get_result_stdout_display',
'get_result_traceback_display', 'celery_task_id',
- 'created', 'created_by', 'audit_trail',)
- filter_horizontal = ('tags',)
+ 'created', 'created_by')
form = JobAdminForm
inlines = [JobHostSummaryInlineForJob, JobEventInlineForJob]
@@ -336,7 +312,7 @@ class JobAdmin(BaseModelAdmin):
ro_fields.extend(['name', 'description', 'job_template',
'inventory', 'project', 'playbook', 'credential',
'job_type', 'forks', 'limit',
- 'verbosity', 'extra_vars'])
+ 'verbosity', 'extra_vars', 'job_tags'])
return ro_fields
def get_fieldsets(self, request, obj=None):
@@ -386,16 +362,13 @@ class JobAdmin(BaseModelAdmin):
get_result_traceback_display.short_description = _('Traceback')
get_result_traceback_display.allow_tags = True
-
-# FIXME: Add the rest of the models...
-
admin.site.register(Organization, OrganizationAdmin)
admin.site.register(Inventory, InventoryAdmin)
-admin.site.register(Tag, TagAdmin)
+#admin.site.register(Tag, TagAdmin)
#admin.site.register(AuditTrail, AuditTrailAdmin)
admin.site.register(Host, HostAdmin)
admin.site.register(Group, GroupAdmin)
-admin.site.register(VariableData, VariableDataAdmin)
+#admin.site.register(VariableData, VariableDataAdmin)
admin.site.register(Team, TeamAdmin)
admin.site.register(Project, ProjectAdmin)
admin.site.register(Credential, CredentialAdmin)
diff --git a/ansibleworks/main/base_views.py b/ansibleworks/main/base_views.py
index f674b08853..ad3b703104 100644
--- a/ansibleworks/main/base_views.py
+++ b/ansibleworks/main/base_views.py
@@ -214,7 +214,7 @@ class BaseSubList(BaseList):
class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
def pre_save(self, obj):
- if type(obj) not in [ User, Tag, AuditTrail ]:
+ if type(obj) not in [ User ]:
obj.created_by = self.request.user
def destroy(self, request, *args, **kwargs):
@@ -247,73 +247,3 @@ class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
def put_filter(self, request, *args, **kwargs):
''' scrub any fields the user cannot/should not put, based on user context. This runs after read-only serialization filtering '''
pass
-
-class VariableBaseDetail(BaseDetail):
- '''
- an object that is always 1 to 1 with the foreign key of another object
- and does not have it's own key, such as HostVariableDetail
- '''
-
- def destroy(self, request, *args, **kwargs):
- raise PermissionDenied()
-
- def put(self, request, *args, **kwargs):
- # FIXME: lots of overlap between put and get here, need to refactor
-
- through_obj = self.__class__.parent_model.objects.get(pk=kwargs['pk'])
-
- #has_permission = Inventory._has_permission_types(request.user, through_obj.inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
- #if not has_permission:
- # raise PermissionDenied()
- if not check_user_access(request.user, Inventory, 'change', through_obj.inventory, None):
- raise PermissionDenied
-
- this_object = None
-
- if hasattr(request.DATA, 'dict'):
- data = request.DATA.dict()
- else:
- data = request.DATA
-
- try:
- this_object = getattr(through_obj, self.__class__.reverse_relationship, None)
- except:
- pass
-
- if this_object is None:
- this_object = self.__class__.model.objects.create(data=python_json.dumps(data))
- else:
- this_object.data = python_json.dumps(data)
- this_object.save()
- setattr(through_obj, self.__class__.reverse_relationship, this_object)
- through_obj.save()
-
- return Response(status=status.HTTP_200_OK, data=python_json.loads(this_object.data))
-
-
- def get(self, request, *args, **kwargs):
-
- # if null, recreate a blank object
- through_obj = self.__class__.parent_model.objects.get(pk=kwargs['pk'])
- this_object = None
-
- try:
- this_object = getattr(through_obj, self.__class__.reverse_relationship, None)
- except Exception, e:
- pass
-
- if this_object is None:
- new_args = {}
- new_args['data'] = python_json.dumps(dict())
- this_object = self.__class__.model.objects.create(**new_args)
- setattr(through_obj, self.__class__.reverse_relationship, this_object)
- through_obj.save()
-
- #has_permission = Inventory._has_permission_types(request.user, through_obj.inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
- #if not has_permission:
- # raise PermissionDenied()
- if not check_user_access(request.user, Inventory, 'read', through_obj.inventory):
- raise PermissionDenied
-
- return Response(status=status.HTTP_200_OK, data=python_json.loads(this_object.data))
-
diff --git a/ansibleworks/main/forms.py b/ansibleworks/main/forms.py
index 887835641a..410a8e9f28 100644
--- a/ansibleworks/main/forms.py
+++ b/ansibleworks/main/forms.py
@@ -29,46 +29,6 @@ class PlaybookSelect(forms.Select):
opt = opt.replace('">', '" class="project-%s">' % obj.project.pk)
return opt
-class HostAdminForm(forms.ModelForm):
-
- class Meta:
- model = Host
-
- vdata = JSONFormField(label=_('Variable data'), required=False, widget=forms.Textarea(attrs={'class': 'vLargeTextField'}))
-
- def __init__(self, *args, **kwargs):
- super(HostAdminForm, self).__init__(*args, **kwargs)
- if self.instance.variable_data:
- print repr(self.instance.variable_data.data)
- self.initial['vdata'] = self.instance.variable_data.data
-
- def save(self, commit=True):
- instance = super(HostAdminForm, self).save(commit=commit)
- save_m2m = getattr(self, 'save_m2m', lambda: None)
- vdata = self.cleaned_data.get('vdata', '')
- def new_save_m2m():
- save_m2m()
- if not instance.variable_data:
- instance.variable_data = VariableData.objects.create(data=vdata)
- instance.save()
- else:
- variable_data = instance.variable_data
- # FIXME!!!
- #variable_data.data = vdata
- #variable_data.save()
- if commit:
- new_save_m2m()
- else:
- self.save_m2m = new_save_m2m
- return instance
-
-class GroupForm(forms.ModelForm):
-
- class Meta:
- model = Host
-
- variable_data = JSONFormField(required=False, widget=forms.Textarea(attrs={'class': 'vLargeTextField'}))
-
class ProjectAdminForm(forms.ModelForm):
'''Custom admin form for Projects.'''
diff --git a/ansibleworks/main/management/commands/acom_callback_event.py b/ansibleworks/main/management/commands/acom_callback_event.py
index be7c89f5ae..a228dea27c 100755
--- a/ansibleworks/main/management/commands/acom_callback_event.py
+++ b/ansibleworks/main/management/commands/acom_callback_event.py
@@ -8,6 +8,7 @@ from optparse import make_option
import os
import sys
from django.core.management.base import NoArgsCommand, CommandError
+from django.db import transaction
class Command(NoArgsCommand):
'''
@@ -30,12 +31,13 @@ class Command(NoArgsCommand):
help='JSON-formatted callback event data'),
)
+ @transaction.commit_on_success
def handle_noargs(self, **options):
from ansibleworks.main.models import Job, JobEvent
event_type = options.get('event_type', None)
if not event_type:
raise CommandError('No event specified')
- if event_type not in [x[0] for x in JobEvent.EVENT_TYPES]:
+ if event_type not in [x[0] for x in JobEvent.EVENT_CHOICES]:
raise CommandError('Unsupported event')
event_data_file = options.get('event_data_file', None)
event_data_json = options.get('event_data_json', None)
diff --git a/ansibleworks/main/management/commands/acom_inventory.py b/ansibleworks/main/management/commands/acom_inventory.py
index f9db23314c..a107502e71 100755
--- a/ansibleworks/main/management/commands/acom_inventory.py
+++ b/ansibleworks/main/management/commands/acom_inventory.py
@@ -32,8 +32,8 @@ class Command(NoArgsCommand):
'hosts': list(group.hosts.values_list('name', flat=True)),
'children': list(group.children.values_list('name', flat=True)),
}
- if group.variable_data is not None:
- group_info['vars'] = json.loads(group.variable_data.data)
+ if group.variables:
+ group_info['vars'] = group.variables_dict
group_info = dict(filter(lambda x: bool(x[1]), group_info.items()))
if group_info.keys() in ([], ['hosts']):
@@ -51,8 +51,8 @@ class Command(NoArgsCommand):
except Host.DoesNotExist:
raise CommandError('Host %s not found in the given inventory' % hostname)
hostvars = {}
- if host.variable_data is not None:
- hostvars = json.loads(host.variable_data.data)
+ if host.variables:
+ hostvars = host.variables_dict
self.stdout.write(json.dumps(hostvars, indent=indent))
def handle_noargs(self, **options):
diff --git a/ansibleworks/main/migrations/0002_v12b2_changes.py b/ansibleworks/main/migrations/0002_v12b2_changes.py
new file mode 100644
index 0000000000..18421fbde0
--- /dev/null
+++ b/ansibleworks/main/migrations/0002_v12b2_changes.py
@@ -0,0 +1,664 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+ '''
+ Schema migration for AnsibleWorks 1.2-b2 release.
+ - Adds variables field on Host and Group models.
+ - Adds job_tags and host_config_key fields on JobTemplate.
+ - Adds job_tags, job_args, job_cwd, job_env fields on Job.
+ - Adds failed field on JobHostSummary.
+ - Adds play, task, parent and hosts fields on JobEvent.
+
+ NOTE: This migration has been manually edited!
+ '''
+
+ def forwards(self, orm):
+
+ # Adding field 'Host.variables'
+ db.add_column(u'main_host', 'variables',
+ self.gf('django.db.models.fields.TextField')(default='', blank=True, null=True),
+ keep_default=False)
+
+ # Adding field 'Group.variables'
+ db.add_column(u'main_group', 'variables',
+ self.gf('django.db.models.fields.TextField')(default='', blank=True, null=True),
+ keep_default=False)
+
+ # Adding field 'JobTemplate.job_tags'
+ db.add_column(u'main_jobtemplate', 'job_tags',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'JobTemplate.host_config_key'
+ db.add_column(u'main_jobtemplate', 'host_config_key',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True, null=True),
+ keep_default=False)
+
+ # Adding field 'Job.job_tags'
+ db.add_column(u'main_job', 'job_tags',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'Job.job_args'
+ db.add_column(u'main_job', 'job_args',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'Job.job_cwd'
+ db.add_column(u'main_job', 'job_cwd',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'Job.job_env'
+ db.add_column(u'main_job', 'job_env',
+ self.gf('jsonfield.fields.JSONField')(default={}, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'JobHostSummary.failed'
+ db.add_column(u'main_jobhostsummary', 'failed',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'JobEvent.play'
+ db.add_column(u'main_jobevent', 'play',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True, null=True),
+ keep_default=False)
+
+ # Adding field 'JobEvent.task'
+ db.add_column(u'main_jobevent', 'task',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True, null=True),
+ keep_default=False)
+
+ # Adding field 'JobEvent.parent'
+ db.add_column(u'main_jobevent', 'parent',
+ self.gf('django.db.models.fields.related.ForeignKey')(related_name='children', on_delete=models.SET_NULL, default=None, to=orm['main.JobEvent'], blank=True, null=True),
+ keep_default=False)
+
+ # Adding M2M table for field hosts on 'JobEvent'
+ m2m_table_name = db.shorten_name(u'main_jobevent_hosts')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('jobevent', models.ForeignKey(orm['main.jobevent'], null=False)),
+ ('host', models.ForeignKey(orm['main.host'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['jobevent_id', 'host_id'])
+
+ # Removing M2M table for field tags on 'Job'
+ db.delete_table(db.shorten_name(u'main_job_tags'))
+
+ # Removing M2M table for field audit_trail on 'Job'
+ db.delete_table(db.shorten_name(u'main_job_audit_trail'))
+
+ # Removing M2M table for field tags on 'Inventory'
+ db.delete_table(db.shorten_name(u'main_inventory_tags'))
+
+ # Removing M2M table for field audit_trail on 'Inventory'
+ db.delete_table(db.shorten_name(u'main_inventory_audit_trail'))
+
+ # Removing M2M table for field tags on 'Host'
+ db.delete_table(db.shorten_name(u'main_host_tags'))
+
+ # Removing M2M table for field audit_trail on 'Host'
+ db.delete_table(db.shorten_name(u'main_host_audit_trail'))
+
+ # Removing M2M table for field tags on 'Group'
+ db.delete_table(db.shorten_name(u'main_group_tags'))
+
+ # Removing M2M table for field audit_trail on 'Group'
+ db.delete_table(db.shorten_name(u'main_group_audit_trail'))
+
+ # Removing M2M table for field audit_trail on 'Credential'
+ db.delete_table(db.shorten_name(u'main_credential_audit_trail'))
+
+ # Removing M2M table for field tags on 'Credential'
+ db.delete_table(db.shorten_name(u'main_credential_tags'))
+
+ # Removing M2M table for field tags on 'JobTemplate'
+ db.delete_table(db.shorten_name(u'main_jobtemplate_tags'))
+
+ # Removing M2M table for field audit_trail on 'JobTemplate'
+ db.delete_table(db.shorten_name(u'main_jobtemplate_audit_trail'))
+
+ # Removing M2M table for field tags on 'Team'
+ db.delete_table(db.shorten_name(u'main_team_tags'))
+
+ # Removing M2M table for field audit_trail on 'Team'
+ db.delete_table(db.shorten_name(u'main_team_audit_trail'))
+
+ # Removing M2M table for field tags on 'Project'
+ db.delete_table(db.shorten_name(u'main_project_tags'))
+
+ # Removing M2M table for field audit_trail on 'Project'
+ db.delete_table(db.shorten_name(u'main_project_audit_trail'))
+
+ # Removing M2M table for field tags on 'Permission'
+ db.delete_table(db.shorten_name(u'main_permission_tags'))
+
+ # Removing M2M table for field audit_trail on 'Permission'
+ db.delete_table(db.shorten_name(u'main_permission_audit_trail'))
+
+ # Removing M2M table for field tags on 'VariableData'
+ db.delete_table(db.shorten_name(u'main_variabledata_tags'))
+
+ # Removing M2M table for field audit_trail on 'VariableData'
+ db.delete_table(db.shorten_name(u'main_variabledata_audit_trail'))
+
+ # Removing M2M table for field tags on 'Organization'
+ db.delete_table(db.shorten_name(u'main_organization_tags'))
+
+ # Removing M2M table for field audit_trail on 'Organization'
+ db.delete_table(db.shorten_name(u'main_organization_audit_trail'))
+
+ # Deleting model 'Tag'
+ db.delete_table(u'main_tag')
+
+ # Deleting model 'AuditTrail'
+ db.delete_table(u'main_audittrail')
+
+ def backwards(self, orm):
+
+ # Deleting field 'Host.variables'
+ db.delete_column(u'main_host', 'variables')
+
+ # Deleting field 'Group.variables'
+ db.delete_column(u'main_group', 'variables')
+
+ # Deleting field 'JobTemplate.job_tags'
+ db.delete_column(u'main_jobtemplate', 'job_tags')
+
+ # Deleting field 'JobTemplate.host_config_key'
+ db.delete_column(u'main_jobtemplate', 'host_config_key')
+
+ # Deleting field 'Job.job_tags'
+ db.delete_column(u'main_job', 'job_tags')
+
+ # Deleting field 'Job.job_args'
+ db.delete_column(u'main_job', 'job_args')
+
+ # Deleting field 'Job.job_cwd'
+ db.delete_column(u'main_job', 'job_cwd')
+
+ # Deleting field 'Job.job_env'
+ db.delete_column(u'main_job', 'job_env')
+
+ # Deleting field 'JobHostSummary.failed'
+ db.delete_column(u'main_jobhostsummary', 'failed')
+
+ # Deleting field 'JobEvent.play'
+ db.delete_column(u'main_jobevent', 'play')
+
+ # Deleting field 'JobEvent.task'
+ db.delete_column(u'main_jobevent', 'task')
+
+ # Deleting field 'JobEvent.parent'
+ db.delete_column(u'main_jobevent', 'parent_id')
+
+ # Removing M2M table for field hosts on 'JobEvent'
+ db.delete_table(db.shorten_name(u'main_jobevent_hosts'))
+
+ # Adding model 'AuditTrail'
+ db.create_table(u'main_audittrail', (
+ ('comment', self.gf('django.db.models.fields.TextField')()),
+ ('modified_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.SET_NULL, blank=True)),
+ ('delta', self.gf('django.db.models.fields.TextField')()),
+ ('tag', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Tag'], null=True, on_delete=models.SET_NULL, blank=True)),
+ ('detail', self.gf('django.db.models.fields.TextField')()),
+ ('resource_type', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ))
+ db.send_create_signal('main', ['AuditTrail'])
+
+ # Adding model 'Tag'
+ db.create_table(u'main_tag', (
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
+ ))
+ db.send_create_signal('main', ['Tag'])
+
+ # Adding M2M table for field tags on 'Job'
+ m2m_table_name = db.shorten_name(u'main_job_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('job', models.ForeignKey(orm['main.job'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['job_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Job'
+ m2m_table_name = db.shorten_name(u'main_job_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('job', models.ForeignKey(orm['main.job'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['job_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Inventory'
+ m2m_table_name = db.shorten_name(u'main_inventory_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('inventory', models.ForeignKey(orm['main.inventory'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['inventory_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Inventory'
+ m2m_table_name = db.shorten_name(u'main_inventory_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('inventory', models.ForeignKey(orm['main.inventory'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['inventory_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Host'
+ m2m_table_name = db.shorten_name(u'main_host_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('host', models.ForeignKey(orm['main.host'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['host_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Host'
+ m2m_table_name = db.shorten_name(u'main_host_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('host', models.ForeignKey(orm['main.host'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['host_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Group'
+ m2m_table_name = db.shorten_name(u'main_group_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('group', models.ForeignKey(orm['main.group'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['group_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Group'
+ m2m_table_name = db.shorten_name(u'main_group_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('group', models.ForeignKey(orm['main.group'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['group_id', 'audittrail_id'])
+
+ # Adding M2M table for field audit_trail on 'Credential'
+ m2m_table_name = db.shorten_name(u'main_credential_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('credential', models.ForeignKey(orm['main.credential'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['credential_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Credential'
+ m2m_table_name = db.shorten_name(u'main_credential_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('credential', models.ForeignKey(orm['main.credential'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['credential_id', 'tag_id'])
+
+ # Adding M2M table for field tags on 'JobTemplate'
+ m2m_table_name = db.shorten_name(u'main_jobtemplate_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['jobtemplate_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'JobTemplate'
+ m2m_table_name = db.shorten_name(u'main_jobtemplate_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['jobtemplate_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Team'
+ m2m_table_name = db.shorten_name(u'main_team_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('team', models.ForeignKey(orm['main.team'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['team_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Team'
+ m2m_table_name = db.shorten_name(u'main_team_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('team', models.ForeignKey(orm['main.team'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['team_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Project'
+ m2m_table_name = db.shorten_name(u'main_project_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('project', models.ForeignKey(orm[u'main.project'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['project_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Project'
+ m2m_table_name = db.shorten_name(u'main_project_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('project', models.ForeignKey(orm[u'main.project'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['project_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Permission'
+ m2m_table_name = db.shorten_name(u'main_permission_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('permission', models.ForeignKey(orm['main.permission'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['permission_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Permission'
+ m2m_table_name = db.shorten_name(u'main_permission_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('permission', models.ForeignKey(orm['main.permission'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['permission_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'VariableData'
+ m2m_table_name = db.shorten_name(u'main_variabledata_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('variabledata', models.ForeignKey(orm['main.variabledata'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['variabledata_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'VariableData'
+ m2m_table_name = db.shorten_name(u'main_variabledata_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('variabledata', models.ForeignKey(orm['main.variabledata'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['variabledata_id', 'audittrail_id'])
+
+ # Adding M2M table for field tags on 'Organization'
+ m2m_table_name = db.shorten_name(u'main_organization_tags')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('organization', models.ForeignKey(orm['main.organization'], null=False)),
+ ('tag', models.ForeignKey(orm['main.tag'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['organization_id', 'tag_id'])
+
+ # Adding M2M table for field audit_trail on 'Organization'
+ m2m_table_name = db.shorten_name(u'main_organization_audit_trail')
+ db.create_table(m2m_table_name, (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('organization', models.ForeignKey(orm['main.organization'], null=False)),
+ ('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
+ ))
+ db.create_unique(m2m_table_name, ['organization_id', 'audittrail_id'])
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'main.credential': {
+ 'Meta': {'object_name': 'Credential'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'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'}),
+ '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', 'null': 'True'}),
+ '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'}),
+ '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'}),
+ 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', 'null': 'True'}),
+ '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'}),
+ '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'}),
+ 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']"})
+ },
+ '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', 'null': 'True', 'blank': 'True'}),
+ 'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+ 'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'}),
+ 'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', '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'}),
+ 'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
+ 'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
+ 'result_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'},
+ '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'", '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', 'null': 'True'}),
+ 'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True', 'null': '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', 'null': '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', 'null': 'True', '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'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
+ },
+ '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']"})
+ },
+ 'main.variabledata': {
+ 'Meta': {'object_name': 'VariableData'},
+ '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\': \'variabledata\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ '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'})
+ },
+ 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']
\ No newline at end of file
diff --git a/ansibleworks/main/migrations/0003_v12b2_changes.py b/ansibleworks/main/migrations/0003_v12b2_changes.py
new file mode 100644
index 0000000000..37ac78162d
--- /dev/null
+++ b/ansibleworks/main/migrations/0003_v12b2_changes.py
@@ -0,0 +1,387 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+
+class Migration(DataMigration):
+ '''
+ Data migration for AnsibleWorks 1.2-b2 release.
+ - Update variables from VariableData.data for Host and Group models.
+ - Update new char/text field values to be empty string if they are null.
+ - Update failed flag for existing JobHostSummary models.
+ - Update parent field for existing JobEvent models.
+ - Update hosts for existing JobEvent models.
+ '''
+
+ def forwards(self, orm):
+
+ for host in orm.Host.objects.all():
+ if host.variable_data:
+ host.variables = host.variable_data.data
+ else:
+ host.variables = ''
+ host.save()
+
+ for group in orm.Group.objects.all():
+ if group.variable_data:
+ group.variables = group.variable_data.data
+ else:
+ group.variables = ''
+ group.save()
+
+ for job_template in orm.JobTemplate.objects.all():
+ changed = False
+ if job_template.host_config_key is None:
+ job_template.host_config_key = ''
+ changed = True
+ if job_template.job_tags is None:
+ job_template.job_tags = ''
+ changed = True
+ if changed:
+ job_template.save()
+
+ for job in orm.Job.objects.all():
+ changed = False
+ if job.job_tags is None:
+ job.job_tags = ''
+ changed = True
+ if job.job_args is None:
+ job.job_args = ''
+ changed = True
+ if job.job_cwd is None:
+ job.job_cwd = ''
+ changed = True
+ if job.job_env is None:
+ job.job_env = ''
+ changed = True
+ if changed:
+ job.save()
+
+ for job_host_summary in orm.JobHostSummary.objects.all():
+ if job_host_summary.failures or job_host_summary.dark:
+ job_host_summary.failed = True
+ job_host_summary.save()
+
+ for job_event in orm.JobEvent.objects.all():
+ job_event.play = job_event.event_data.get('play', '')
+ job_event.task = job_event.event_data.get('task', '')
+ job_event.parent = None
+ parent_events = set()
+ if job_event.event in ('playbook_on_play_start',
+ 'playbook_on_stats',
+ 'playbook_on_vars_prompt'):
+ parent_events.add('playbook_on_start')
+ elif job_event.event in ('playbook_on_notify', 'playbook_on_setup',
+ 'playbook_on_task_start',
+ 'playbook_on_no_hosts_matched',
+ 'playbook_on_no_hosts_remaining',
+ 'playbook_on_import_for_host',
+ 'playbook_on_not_import_for_host'):
+ parent_events.add('playbook_on_play_start')
+ elif job_event.event.startswith('runner_on_'):
+ parent_events.add('playbook_on_setup')
+ parent_events.add('playbook_on_task_start')
+ if parent_events:
+ try:
+ qs = job_event.job.job_events.all()
+ qs = qs.filter(pk__lt=job_event.pk,
+ event__in=parent_events)
+ job_event.parent = qs.order_by('-pk')[0]
+ except IndexError:
+ pass
+ job_event.save()
+
+ def update_job_event_hosts(orm, job_event, extra_hosts=None):
+ extra_hosts = extra_hosts or []
+ hostnames = set()
+ if job_event.event_data.get('host', ''):
+ hostnames.add(job_event.event_data['host'])
+ if job_event.event == 'playbook_on_stats':
+ try:
+ for v in job_event.event_data.values():
+ hostnames.update(v.keys())
+ except AttributeError:
+ pass
+ if job_event.host:
+ job_event.hosts.add(job_event.host)
+ for hostname in hostnames:
+ try:
+ host = job_event.job.inventory.hosts.get(name=hostname)
+ except orm.Host.DoesNotExist:
+ continue
+ job_event.hosts.add(host)
+ for host in extra_hosts:
+ job_event.hosts.add(host)
+ if job_event.parent:
+ update_job_event_hosts(orm, job_event.parent,
+ job_event.hosts.all())
+
+ for job_event in orm.JobEvent.objects.all():
+ update_job_event_hosts(orm, job_event)
+
+ def backwards(self, orm):
+
+ for host in orm.Host.objects.all():
+ if host.variable_data:
+ variable_data = host.variable_data
+ variable_data.data = host.variables
+ variable_data.save()
+ else:
+ host.variable_data = orm.VariableData.objects.create(data=host.variables)
+ host.save()
+
+ for group in orm.Group.objects.all():
+ if group.variable_data:
+ variable_data = group.variable_data
+ variable_data.data = group.variables
+ variable_data.save()
+ else:
+ group.variable_data = orm.VariableData.objects.create(data=group.variables)
+ group.save()
+
+ 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'}),
+ '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']"}),
+ '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'}),
+ 'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', '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'}),
+ 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'}),
+ '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'}),
+ 'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', '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'}),
+ 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']"})
+ },
+ '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', 'null': 'True', 'blank': 'True'}),
+ 'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+ 'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'}),
+ 'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', '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'}),
+ 'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
+ 'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
+ 'result_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'},
+ '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'", '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', 'null': 'True', 'blank': 'True'}),
+ 'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', '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', 'null': 'True', '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', 'null': 'True', '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'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
+ },
+ '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']"})
+ },
+ 'main.variabledata': {
+ 'Meta': {'object_name': 'VariableData'},
+ '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\': \'variabledata\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ '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'})
+ },
+ 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']
+ symmetrical = True
diff --git a/ansibleworks/main/migrations/0004_v12b2_changes.py b/ansibleworks/main/migrations/0004_v12b2_changes.py
new file mode 100644
index 0000000000..7af5157022
--- /dev/null
+++ b/ansibleworks/main/migrations/0004_v12b2_changes.py
@@ -0,0 +1,343 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+ '''
+ Schema migration for AnsibleWorks 1.2-b2 release.
+ - Remove variable_data field on Host and Group models.
+ - Remove VariableData model.
+ - Remove null=True on new char fields previously added.
+
+ NOTE: This migration has been manually edited!
+ '''
+
+ def forwards(self, orm):
+
+ # Changing field 'Job.job_cwd'
+ db.alter_column(u'main_job', 'job_cwd', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+ # Changing field 'Job.job_tags'
+ db.alter_column(u'main_job', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+ # Changing field 'Job.job_env'
+ db.alter_column(u'main_job', 'job_env', self.gf('jsonfield.fields.JSONField')())
+
+ # Changing field 'Job.job_args'
+ db.alter_column(u'main_job', 'job_args', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+ # Deleting field 'Host.variable_data'
+ db.delete_column(u'main_host', 'variable_data_id')
+
+ # Changing field 'Host.variables'
+ db.alter_column(u'main_host', 'variables', self.gf('django.db.models.fields.TextField')())
+
+ # Deleting field 'Group.variable_data'
+ db.delete_column(u'main_group', 'variable_data_id')
+
+ # Changing field 'Group.variables'
+ db.alter_column(u'main_group', 'variables', self.gf('django.db.models.fields.TextField')())
+
+ # Changing field 'JobTemplate.job_tags'
+ db.alter_column(u'main_jobtemplate', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+ # Changing field 'JobTemplate.host_config_key'
+ db.alter_column(u'main_jobtemplate', 'host_config_key', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+ # Changing field 'JobEvent.play'
+ db.alter_column(u'main_jobevent', 'play', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+ # Changing field 'JobEvent.task'
+ db.alter_column(u'main_jobevent', 'task', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+ # Deleting model 'VariableData'
+ db.delete_table(u'main_variabledata')
+
+ def backwards(self, orm):
+
+ # Adding model 'VariableData'
+ db.create_table(u'main_variabledata', (
+ ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('data', self.gf('django.db.models.fields.TextField')(default='')),
+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'variabledata', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
+ ))
+ db.send_create_signal('main', ['VariableData'])
+
+ # Changing field 'Job.job_cwd'
+ db.alter_column(u'main_job', 'job_cwd', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
+
+ # Changing field 'Job.job_tags'
+ db.alter_column(u'main_job', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
+
+ # Changing field 'Job.job_env'
+ db.alter_column(u'main_job', 'job_env', self.gf('jsonfield.fields.JSONField')(null=True))
+
+ # Changing field 'Job.job_args'
+ db.alter_column(u'main_job', 'job_args', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
+
+ # Adding field 'Host.variable_data'
+ db.add_column(u'main_host', 'variable_data',
+ self.gf('django.db.models.fields.related.OneToOneField')(related_name='host', null=True, on_delete=models.SET_NULL, default=None, to=orm['main.VariableData'], blank=True, unique=True),
+ keep_default=False)
+
+ # Changing field 'Host.variables'
+ db.alter_column(u'main_host', 'variables', self.gf('django.db.models.fields.TextField')(null=True))
+
+ # Adding field 'Group.variable_data'
+ db.add_column(u'main_group', 'variable_data',
+ self.gf('django.db.models.fields.related.OneToOneField')(related_name='group', null=True, on_delete=models.SET_NULL, default=None, to=orm['main.VariableData'], blank=True, unique=True),
+ keep_default=False)
+
+ # Changing field 'Group.variables'
+ db.alter_column(u'main_group', 'variables', self.gf('django.db.models.fields.TextField')(null=True))
+
+ # Changing field 'JobTemplate.job_tags'
+ db.alter_column(u'main_jobtemplate', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
+
+ # Changing field 'JobTemplate.host_config_key'
+ db.alter_column(u'main_jobtemplate', 'host_config_key', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
+
+ # Changing field 'JobEvent.play'
+ db.alter_column(u'main_jobevent', 'play', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
+
+ # Changing field 'JobEvent.task'
+ db.alter_column(u'main_jobevent', 'task', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
+
+ 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'}),
+ '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'}),
+ 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'}),
+ 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']"})
+ },
+ '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'}),
+ 'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
+ 'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
+ 'result_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'},
+ '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'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
+ },
+ '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']
\ No newline at end of file
diff --git a/ansibleworks/main/models/__init__.py b/ansibleworks/main/models/__init__.py
index 80f2117285..46637aa533 100644
--- a/ansibleworks/main/models/__init__.py
+++ b/ansibleworks/main/models/__init__.py
@@ -1,6 +1,7 @@
# Copyright (c) 2013 AnsibleWorks, Inc.
# All Rights Reserved.
+import json
import os
import shlex
from django.conf import settings
@@ -13,6 +14,7 @@ from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.utils.timezone import now
from jsonfield import JSONField
+from taggit.managers import TaggableManager
from djcelery.models import TaskMeta
from rest_framework.authtoken.models import Token
import yaml
@@ -108,10 +110,10 @@ class PrimordialModel(models.Model):
description = models.TextField(blank=True, default='')
created_by = models.ForeignKey('auth.User', on_delete=SET_NULL, null=True, related_name='%s(class)s_created', editable=False) # not blank=False on purpose for admin!
created = models.DateTimeField(auto_now_add=True)
- tags = models.ManyToManyField('Tag', related_name='%(class)s_by_tag', blank=True)
- audit_trail = models.ManyToManyField('AuditTrail', related_name='%(class)s_by_audit_trail', blank=True)
active = models.BooleanField(default=True)
+ tags = TaggableManager(blank=True)
+
def __unicode__(self):
return unicode("%s-%s"% (self.name, self.id))
@@ -131,39 +133,6 @@ class CommonModelNameNotUnique(PrimordialModel):
name = models.CharField(max_length=512, unique=False)
-class Tag(models.Model):
- '''
- any type of object can be given a search tag
- '''
-
- class Meta:
- app_label = 'main'
-
- name = models.CharField(max_length=512)
-
- def __unicode__(self):
- return unicode(self.name)
-
- def get_absolute_url(self):
- return reverse('main:tags_detail', args=(self.pk,))
-
-class AuditTrail(models.Model):
- '''
- changing any object records the change
- '''
-
- class Meta:
- app_label = 'main'
-
- resource_type = models.CharField(max_length=64)
- modified_by = models.ForeignKey('auth.User', on_delete=SET_NULL, null=True, blank=True)
- delta = models.TextField() # FIXME: switch to JSONField
- detail = models.TextField()
- comment = models.TextField()
-
- # FIXME: this looks like this should be a ManyToMany
- tag = models.ForeignKey('Tag', on_delete=SET_NULL, null=True, blank=True)
-
class Organization(CommonModel):
'''
organizations are the basic unit of multi-tenancy divisions
@@ -206,7 +175,7 @@ class Host(CommonModelNameNotUnique):
app_label = 'main'
unique_together = (("name", "inventory"),)
- variable_data = models.OneToOneField('VariableData', null=True, default=None, blank=True, on_delete=SET_NULL, related_name='host')
+ variables = models.TextField(blank=True, default='')
inventory = models.ForeignKey('Inventory', null=False, related_name='hosts')
last_job = models.ForeignKey('Job', blank=True, null=True, default=None, on_delete=models.SET_NULL, related_name='hosts_as_last_job+')
last_job_host_summary = models.ForeignKey('JobHostSummary', blank=True, null=True, default=None, on_delete=models.SET_NULL, related_name='hosts_as_last_job_summary+')
@@ -217,13 +186,37 @@ class Host(CommonModelNameNotUnique):
def get_absolute_url(self):
return reverse('main:hosts_detail', args=(self.pk,))
+ @property
+ def variables_dict(self):
+ # FIXME: Add YAML support.
+ return json.loads(self.variables or '{}')
+
+ @property
+ def all_groups(self):
+ '''
+ Return all groups of which this host is a member, avoiding infinite
+ recursion in the case of cyclical group relations.
+ '''
+ qs = self.groups.distinct()
+ for group in self.groups.all():
+ qs = qs | group.all_parents
+ return qs
+
+ @property
+ def has_active_failures(self):
+ return self.last_job_host_summary and self.last_job_host_summary.failed
+
# Use .job_host_summaries.all() to get jobs affecting this host.
# Use .job_events.all() to get events affecting this host.
# Use .job_host_summaries.order_by('-pk')[0] to get the last result.
+ # To get all hosts with active failures:
+ # Host.objects.filter(last_job_host_summary__failed=True)
+
class Group(CommonModelNameNotUnique):
'''
- A group of managed nodes. May belong to multiple groups
+ A group containing managed hosts. A group or host may belong to multiple
+ groups.
'''
class Meta:
@@ -231,8 +224,9 @@ class Group(CommonModelNameNotUnique):
unique_together = (("name", "inventory"),)
inventory = models.ForeignKey('Inventory', null=False, related_name='groups')
+ # Can also be thought of as: parents == member_of, children == members
parents = models.ManyToManyField('self', symmetrical=False, related_name='children', blank=True)
- variable_data = models.OneToOneField('VariableData', null=True, default=None, blank=True, on_delete=SET_NULL, related_name='group')
+ variables = models.TextField(blank=True, default='')
hosts = models.ManyToManyField('Host', related_name='groups', blank=True)
def __unicode__(self):
@@ -242,12 +236,60 @@ class Group(CommonModelNameNotUnique):
return reverse('main:groups_detail', args=(self.pk,))
@property
- def all_hosts(self):
- qs = self.hosts.distinct()
- for group in self.children.exclude(pk=self.pk):
- qs = qs | group.all_hosts
+ def variables_dict(self):
+ # FIXME: Add YAML support.
+ return json.loads(self.variables or '{}')
+
+ def get_all_parents(self, except_pks=None):
+ '''
+ Return all parents of this group recursively, avoiding infinite
+ recursion in the case of cyclical relations. The group itself will be
+ excluded unless there is a cycle leading back to it.
+ '''
+ except_pks = except_pks or set()
+ except_pks.add(self.pk)
+ qs = self.parents.distinct()
+ for group in self.parents.exclude(pk__in=except_pks):
+ qs = qs | group.get_all_parents(except_pks)
return qs
+ @property
+ def all_parents(self):
+ return self.get_all_parents()
+
+ def get_all_children(self, except_pks=None):
+ '''
+ Return all children of this group recursively, avoiding infinite
+ recursion in the case of cyclical relations. The group itself will be
+ excluded unless there is a cycle leading back to it.
+ '''
+ except_pks = except_pks or set()
+ except_pks.add(self.pk)
+ qs = self.children.distinct()
+ for group in self.children.exclude(pk__in=except_pks):
+ qs = qs | group.get_all_children(except_pks)
+ return qs
+
+ @property
+ def all_children(self):
+ return self.get_all_children()
+
+ def get_all_hosts(self, except_group_pks=None):
+ '''
+ Return all hosts associated with this group or any of its children,
+ avoiding infinite recursion in the case of cyclical group relations.
+ '''
+ except_group_pks = except_group_pks or set()
+ except_group_pks.add(self.pk)
+ qs = self.hosts.distinct()
+ for group in self.children.exclude(pk__in=except_group_pks):
+ qs = qs | group.get_all_hosts(except_group_pks)
+ return qs
+
+ @property
+ def all_hosts(self):
+ return self.get_all_hosts()
+
@property
def job_host_summaries(self):
return JobHostSummary.objects.filter(host__in=self.all_hosts)
@@ -256,27 +298,9 @@ class Group(CommonModelNameNotUnique):
def job_events(self):
return JobEvent.objects.filter(host__in=self.all_hosts)
-# FIXME: audit nullables
-# FIXME: audit cascades
-
-class VariableData(CommonModelNameNotUnique):
- '''
- A set of host or group variables
- '''
-
- class Meta:
- app_label = 'main'
- verbose_name_plural = _('variable data')
-
- #host = models.OneToOneField('Host', null=True, default=None, blank=True, on_delete=SET_NULL, related_name='variable_data')
- #group = models.OneToOneField('Group', null=True, default=None, blank=True, on_delete=SET_NULL, related_name='variable_data')
- data = models.TextField(default='')
-
- def __unicode__(self):
- return '%s = %s' % (self.name, self.data)
-
- def get_absolute_url(self):
- return reverse('main:variable_detail', args=(self.pk,))
+ @property
+ def has_active_failures(self):
+ return bool(self.all_hosts.filter(last_job_host_summary__failed=True).count())
class Credential(CommonModelNameNotUnique):
'''
@@ -537,6 +561,16 @@ class JobTemplate(CommonModel):
blank=True,
default='',
)
+ job_tags = models.CharField(
+ max_length=1024,
+ blank=True,
+ default='',
+ )
+ host_config_key = models.CharField(
+ max_length=1024,
+ blank=True,
+ default='',
+ )
def create_job(self, **kwargs):
'''
@@ -555,6 +589,7 @@ class JobTemplate(CommonModel):
kwargs.setdefault('limit', self.limit)
kwargs.setdefault('verbosity', self.verbosity)
kwargs.setdefault('extra_vars', self.extra_vars)
+ kwargs.setdefault('job_tags', self.job_tags)
job = Job(**kwargs)
if save_job:
job.save()
@@ -633,6 +668,11 @@ class Job(CommonModel):
blank=True,
default='',
)
+ job_tags = models.CharField(
+ max_length=1024,
+ blank=True,
+ default='',
+ )
cancel_flag = models.BooleanField(
blank=True,
default=False,
@@ -647,6 +687,23 @@ class Job(CommonModel):
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='',
@@ -797,6 +854,7 @@ class JobHostSummary(models.Model):
ok = models.PositiveIntegerField(default=0)
processed = models.PositiveIntegerField(default=0)
skipped = models.PositiveIntegerField(default=0)
+ failed = models.BooleanField(default=False)
def __unicode__(self):
return '%s changed=%d dark=%d failures=%d ok=%d processed=%d skipped=%s' % \
@@ -807,6 +865,7 @@ class JobHostSummary(models.Model):
return reverse('main:job_host_summary_detail', args=(self.pk,))
def save(self, *args, **kwargs):
+ self.failed = bool(self.dark or self.failures)
super(JobHostSummary, self).save(*args, **kwargs)
self.update_host_last_job_summary()
@@ -826,33 +885,58 @@ class JobEvent(models.Model):
An event/message logged from the callback when running a job.
'''
- EVENT_TYPES = [
- ('runner_on_failed', _('Runner on Failed')),
- ('runner_on_ok', _('Runner on OK')),
- ('runner_on_error', _('Runner on Error')),
- ('runner_on_skipped', _('Runner on Skipped')),
- ('runner_on_unreachable', _('Runner on Unreachable')),
- ('runner_on_no_hosts', _('Runner on No Hosts')),
- ('runner_on_async_poll', _('Runner on Async Poll')),
- ('runner_on_async_ok', _('Runner on Async OK')),
- ('runner_on_async_failed', _('Runner on Async Failed')),
- ('playbook_on_start', _('Playbook on Start')),
- ('playbook_on_notify', _('Playbook on Notify')),
- ('playbook_on_task_start', _('Playbook on Task Start')),
- ('playbook_on_vars_prompt', _('Playbook on Vars Prompt')),
- ('playbook_on_setup', _('Playbook on Setup')),
- ('playbook_on_import_for_host', _('Playbook on Import for Host')),
- ('playbook_on_not_import_for_host', _('Playbook on Not Import for Host')),
- ('playbook_on_play_start', _('Playbook on Play Start')),
- ('playbook_on_stats', _('Playbook on Stats')),
- ]
+ # Playbook events will be structured to form the following hierarchy:
+ # - playbook_on_start (once for each playbook file)
+ # - playbook_on_vars_prompt (for each play, but before play starts, we
+ # currently don't handle responding to these prompts)
+ # - playbook_on_play_start
+ # - playbook_on_import_for_host
+ # - playbook_on_not_import_for_host
+ # - playbook_on_no_hosts_matched
+ # - playbook_on_no_hosts_remaining
+ # - playbook_on_setup
+ # - runner_on*
+ # - playbook_on_task_start
+ # - runner_on_failed
+ # - runner_on_ok
+ # - runner_on_error
+ # - runner_on_skipped
+ # - runner_on_unreachable
+ # - runner_on_no_hosts
+ # - runner_on_async_poll
+ # - runner_on_async_ok
+ # - runner_on_async_failed
+ # - runner_on_file_diff
+ # - playbook_on_notify
+ # - playbook_on_stats
- FAILED_EVENTS = [
- 'runner_on_failed',
- 'runner_on_error',
- 'runner_on_unreachable',
- 'runner_on_async_failed',
+ EVENT_TYPES = [
+ # (level, event, verbose name, failed)
+ (3, 'runner_on_failed', _('Runner on Failed'), True),
+ (3, 'runner_on_ok', _('Runner on OK'), False),
+ (3, 'runner_on_error', _('Runner on Error'), True),
+ (3, 'runner_on_skipped', _('Runner on Skipped'), False),
+ (3, 'runner_on_unreachable', _('Runner on Unreachable'), True),
+ (3, 'runner_on_no_hosts', _('Runner on No Hosts'), False),
+ (3, 'runner_on_async_poll', _('Runner on Async Poll'), False),
+ (3, 'runner_on_async_ok', _('Runner on Async OK'), False),
+ (3, 'runner_on_async_failed', _('Runner on Async Failed'), True),
+ (3, 'runner_on_file_diff', _('Runner on File Diff'), False),
+ (0, 'playbook_on_start', _('Playbook on Start'), False),
+ (2, 'playbook_on_notify', _('Playbook on Notify'), False),
+ (2, 'playbook_on_no_hosts_matched', _('Playbook on No Hosts Matched'), False),
+ (2, 'playbook_on_no_hosts_remaining', _('Playbook on No Hosts Remaining'), False),
+ (2, 'playbook_on_task_start', _('Playbook on Task Start'), False),
+ (1, 'playbook_on_vars_prompt', _('Playbook on Vars Prompt'), False),
+ (2, 'playbook_on_setup', _('Playbook on Setup'), False),
+ (2, 'playbook_on_import_for_host', _('Playbook on Import for Host'), False),
+ (2, 'playbook_on_not_import_for_host', _('Playbook on Not Import for Host'), False),
+ (1, 'playbook_on_play_start', _('Playbook on Play Start'), False),
+ (1, 'playbook_on_stats', _('Playbook on Stats'), False),
]
+ FAILED_EVENTS = [x[1] for x in EVENT_TYPES if x[3]]
+ EVENT_CHOICES = [(x[1], x[2]) for x in EVENT_TYPES]
+ LEVEL_FOR_EVENT = dict([(x[1], x[0]) for x in EVENT_TYPES])
class Meta:
app_label = 'main'
@@ -868,7 +952,7 @@ class JobEvent(models.Model):
)
event = models.CharField(
max_length=100,
- choices=EVENT_TYPES,
+ choices=EVENT_CHOICES,
)
event_data = JSONField(
blank=True,
@@ -878,9 +962,32 @@ class JobEvent(models.Model):
default=False,
)
host = models.ForeignKey(
+ 'Host',
+ related_name='job_events_as_primary_host',
+ blank=True,
+ null=True,
+ default=None,
+ on_delete=models.SET_NULL,
+ )
+ hosts = models.ManyToManyField(
'Host',
related_name='job_events',
blank=True,
+ )
+ play = models.CharField(
+ max_length=1024,
+ blank=True,
+ default='',
+ )
+ task = models.CharField(
+ max_length=1024,
+ blank=True,
+ default='',
+ )
+ parent = models.ForeignKey(
+ 'self',
+ related_name='children',
+ blank=True,
null=True,
default=None,
on_delete=models.SET_NULL,
@@ -892,25 +999,72 @@ class JobEvent(models.Model):
def __unicode__(self):
return u'%s @ %s' % (self.get_event_display(), self.created.isoformat())
+ @property
+ def event_level(self):
+ return self.LEVEL_FOR_EVENT.get(self.event, 0)
+
+ def _find_parent(self):
+ parent_events = set()
+ if self.event in ('playbook_on_play_start', 'playbook_on_stats',
+ 'playbook_on_vars_prompt'):
+ parent_events.add('playbook_on_start')
+ elif self.event in ('playbook_on_notify', 'playbook_on_setup',
+ 'playbook_on_task_start',
+ 'playbook_on_no_hosts_matched',
+ 'playbook_on_no_hosts_remaining',
+ 'playbook_on_import_for_host',
+ 'playbook_on_not_import_for_host'):
+ parent_events.add('playbook_on_play_start')
+ elif self.event.startswith('runner_on_'):
+ parent_events.add('playbook_on_setup')
+ parent_events.add('playbook_on_task_start')
+ if parent_events:
+ try:
+ qs = self.job.job_events.all()
+ if self.pk:
+ qs = qs.filter(pk__lt=self.pk, event__in=parent_events)
+ else:
+ qs = qs.filter(event__in=parent_events)
+ return qs.order_by('-pk')[0]
+ except IndexError:
+ pass
+ return None
+
def save(self, *args, **kwargs):
+ self.failed = bool(self.event in self.FAILED_EVENTS)
try:
if not self.host and self.event_data.get('host', ''):
self.host = self.job.inventory.hosts.get(name=self.event_data['host'])
except (Host.DoesNotExist, AttributeError):
pass
- self.failed = bool(self.event in self.FAILED_EVENTS)
+ self.play = self.event_data.get('play', '')
+ self.task = self.event_data.get('task', '')
+ self.parent = self._find_parent()
super(JobEvent, self).save(*args, **kwargs)
+ self.update_hosts()
self.update_host_summary_from_stats()
- self.update_host_last_job()
- def update_host_last_job(self):
- if self.host:
- update_fields = []
- if self.host.last_job != self.job:
- self.host.last_job = self.job
- update_fields.append('last_job')
- if update_fields:
- self.host.save(update_fields=update_fields)
+ def update_hosts(self, extra_hosts=None):
+ extra_hosts = extra_hosts or []
+ hostnames = set()
+ if self.event_data.get('host', ''):
+ hostnames.add(self.event_data['host'])
+ if self.event == 'playbook_on_stats':
+ try:
+ for v in self.event_data.values():
+ hostnames.update(v.keys())
+ except AttributeError: # In case event_data or v isn't a dict.
+ pass
+ for hostname in hostnames:
+ try:
+ host = self.job.inventory.hosts.get(name=hostname)
+ except Host.DoesNotExist:
+ continue
+ self.hosts.add(host)
+ for host in extra_hosts:
+ self.hosts.add(host)
+ if self.parent:
+ self.parent.update_hosts(self.hosts.all())
def update_host_summary_from_stats(self):
if self.event != 'playbook_on_stats':
diff --git a/ansibleworks/main/rbac.py b/ansibleworks/main/rbac.py
index 90f20a55e1..d9b54f2246 100644
--- a/ansibleworks/main/rbac.py
+++ b/ansibleworks/main/rbac.py
@@ -46,8 +46,12 @@ class CustomRbac(permissions.BasePermission):
if not obj:
return True # FIXME: For some reason this needs to return True
# because it is first called with obj=None?
- return check_user_access(request.user, view.model, 'change', obj,
- request.DATA)
+ if getattr(view, 'is_variable_data', False):
+ return check_user_access(request.user, view.model, 'change', obj,
+ {'variables': request.DATA})
+ else:
+ return check_user_access(request.user, view.model, 'change', obj,
+ request.DATA)
def _check_delete_permissions(self, request, view, obj=None):
if not obj:
diff --git a/ansibleworks/main/serializers.py b/ansibleworks/main/serializers.py
index 9a592df2a9..1f1c5ae50c 100644
--- a/ansibleworks/main/serializers.py
+++ b/ansibleworks/main/serializers.py
@@ -1,6 +1,9 @@
# Copyright (c) 2013 AnsibleWorks, Inc.
# All Rights Reserved.
+# Python
+import json
+
# Django
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
@@ -126,28 +129,16 @@ class OrganizationSerializer(BaseSerializer):
def get_related(self, obj):
res = super(OrganizationSerializer, self).get_related(obj)
res.update(dict(
- audit_trail = reverse('main:organizations_audit_trail_list', args=(obj.pk,)),
+ #audit_trail = reverse('main:organizations_audit_trail_list', args=(obj.pk,)),
projects = reverse('main:organizations_projects_list', args=(obj.pk,)),
inventories = reverse('main:organizations_inventories_list', args=(obj.pk,)),
users = reverse('main:organizations_users_list', args=(obj.pk,)),
admins = reverse('main:organizations_admins_list', args=(obj.pk,)),
- tags = reverse('main:organizations_tags_list', args=(obj.pk,)),
+ #tags = reverse('main:organizations_tags_list', args=(obj.pk,)),
teams = reverse('main:organizations_teams_list', args=(obj.pk,)),
))
return res
-class AuditTrailSerializer(BaseSerializer):
-
- class Meta:
- model = AuditTrail
- fields = ('url', 'id', 'modified_by', 'delta', 'detail', 'comment')
-
- def get_related(self, obj):
- res = super(AuditTrailSerializer, self).get_related(obj)
- if obj.modified_by:
- res['modified_by'] = reverse('main:users_detail', args=(obj.modified_by.pk,))
- return res
-
class ProjectSerializer(BaseSerializer):
playbooks = serializers.Field(source='playbooks')
@@ -197,9 +188,11 @@ class InventorySerializer(BaseSerializer):
class HostSerializer(BaseSerializer):
+ has_active_failures = serializers.Field(source='has_active_failures')
+
class Meta:
model = Host
- fields = BASE_FIELDS + ('inventory',)
+ fields = BASE_FIELDS + ('inventory', 'variables', 'has_active_failures')
def get_related(self, obj):
res = super(HostSerializer, self).get_related(obj)
@@ -218,9 +211,11 @@ class HostSerializer(BaseSerializer):
class GroupSerializer(BaseSerializer):
+ has_active_failures = serializers.Field(source='has_active_failures')
+
class Meta:
model = Group
- fields = BASE_FIELDS + ('inventory',)
+ fields = BASE_FIELDS + ('inventory', 'variables', 'has_active_failures')
def get_related(self, obj):
res = super(GroupSerializer, self).get_related(obj)
@@ -235,6 +230,28 @@ class GroupSerializer(BaseSerializer):
))
return res
+class BaseVariableDataSerializer(BaseSerializer):
+
+ def to_native(self, obj):
+ ret = super(BaseVariableDataSerializer, self).to_native(obj)
+ return json.loads(ret.get('variables', '') or '{}')
+
+ def from_native(self, data, files):
+ data = {'variables': json.dumps(data)}
+ return super(BaseVariableDataSerializer, self).from_native(data, files)
+
+class HostVariableDataSerializer(BaseVariableDataSerializer):
+
+ class Meta:
+ model = Host
+ fields = ('variables',)
+
+class GroupVariableDataSerializer(BaseVariableDataSerializer):
+
+ class Meta:
+ model = Group
+ fields = ('variables',)
+
class TeamSerializer(BaseSerializer):
class Meta:
@@ -319,42 +336,13 @@ class UserSerializer(BaseSerializer):
))
return res
-class TagSerializer(BaseSerializer):
-
- class Meta:
- model = Tag
- fields = ('id', 'url', 'name')
-
- def get_related(self, obj):
- res = super(TagSerializer, self).get_related(obj)
- res.pop('created_by', None)
- return res
-
-class VariableDataSerializer(BaseSerializer):
-
- class Meta:
- model = VariableData
- fields = BASE_FIELDS + ('data',)
-
- def get_related(self, obj):
- res = super(VariableDataSerializer, self).get_related(obj)
- try:
- res['host'] = reverse('main:hosts_detail', args=(obj.host.pk,))
- except Host.DoesNotExist:
- pass
- try:
- res['group'] = reverse('main:groups_detail', args=(obj.group.pk,))
- except Group.DoesNotExist:
- pass
- return res
-
class JobTemplateSerializer(BaseSerializer):
class Meta:
model = JobTemplate
fields = BASE_FIELDS + ('job_type', 'inventory', 'project', 'playbook',
'credential', 'forks', 'limit', 'verbosity',
- 'extra_vars')
+ 'extra_vars', 'job_tags')
def get_related(self, obj):
res = super(JobTemplateSerializer, self).get_related(obj)
@@ -383,7 +371,7 @@ class JobSerializer(BaseSerializer):
fields = BASE_FIELDS + ('job_template', 'job_type', 'inventory',
'project', 'playbook', 'credential',
'forks', 'limit', 'verbosity', 'extra_vars',
- 'status', 'failed', 'result_stdout',
+ 'job_tags', 'status', 'failed', 'result_stdout',
'result_traceback',
'passwords_needed_to_start')
@@ -424,6 +412,7 @@ class JobSerializer(BaseSerializer):
data.setdefault('limit', job_template.limit)
data.setdefault('verbosity', job_template.verbosity)
data.setdefault('extra_vars', job_template.extra_vars)
+ data.setdefault('job_tags', job_template.job_tags)
return super(JobSerializer, self).from_native(data, files)
class JobHostSummarySerializer(BaseSerializer):
@@ -431,7 +420,8 @@ class JobHostSummarySerializer(BaseSerializer):
class Meta:
model = JobHostSummary
fields = ('id', 'url', 'job', 'host', 'summary_fields', 'related',
- 'changed', 'dark', 'failures', 'ok', 'processed', 'skipped')
+ 'changed', 'dark', 'failures', 'ok', 'processed', 'skipped',
+ 'failed')
def get_related(self, obj):
res = super(JobHostSummarySerializer, self).get_related(obj)
diff --git a/ansibleworks/main/tasks.py b/ansibleworks/main/tasks.py
index 9f50871c54..5e9841c09a 100644
--- a/ansibleworks/main/tasks.py
+++ b/ansibleworks/main/tasks.py
@@ -124,6 +124,8 @@ class RunJob(Task):
args.append('-%s' % ('v' * min(3, job.verbosity)))
if job.extra_vars:
args.extend(['-e', job.extra_vars])
+ if job.job_tags:
+ args.extend(['-t', job.job_tags])
args.append(job.playbook) # relative path to project.local_path
ssh_key_path = kwargs.get('ssh_key_path', '')
if ssh_key_path:
@@ -192,6 +194,8 @@ class RunJob(Task):
raise RuntimeError('project local_path %s cannot be found' %
project.local_path)
env = self.build_env(job, **kwargs)
+ job = self.update_job(job_pk, job_args=args, job_cwd=cwd,
+ job_env=env)
status, stdout = self.run_pexpect(job_pk, args, cwd, env,
kwargs['passwords'])
except Exception:
diff --git a/ansibleworks/main/tests/commands.py b/ansibleworks/main/tests/commands.py
index 5016cbc7ad..6f65f5d83c 100644
--- a/ansibleworks/main/tests/commands.py
+++ b/ansibleworks/main/tests/commands.py
@@ -111,23 +111,23 @@ class AcomInventoryTest(BaseCommandTest):
hosts = []
for x in xrange(10):
if n > 0:
- variable_data = VariableData.objects.create(data=json.dumps({'ho': 'hum-%d' % x}))
+ variables = json.dumps({'ho': 'hum-%d' % x})
else:
- variable_data = None
+ variables = ''
host = inventory.hosts.create(name='host-%02d-%02d.example.com' % (n, x),
inventory=inventory,
- variable_data=variable_data)
+ variables=variables)
hosts.append(host)
self.hosts.extend(hosts)
groups = []
for x in xrange(5):
if n > 0:
- variable_data = VariableData.objects.create(data=json.dumps({'gee': 'whiz-%d' % x}))
+ variables = json.dumps({'gee': 'whiz-%d' % x})
else:
- variable_data = None
+ variables = ''
group = inventory.groups.create(name='group-%d' % x,
inventory=inventory,
- variable_data=variable_data)
+ variables=variables)
groups.append(group)
group.hosts.add(hosts[x])
group.hosts.add(hosts[x + 5])
@@ -184,9 +184,9 @@ class AcomInventoryTest(BaseCommandTest):
group = inventory.groups.get(name=k)
self.assertEqual(set(v.get('hosts', [])),
set(group.hosts.values_list('name', flat=True)))
- if group.variable_data:
+ if group.variables:
self.assertEqual(v.get('vars', {}),
- json.loads(group.variable_data.data))
+ json.loads(group.variables))
if k == 'group-3':
self.assertEqual(set(v.get('children', [])),
set(group.children.values_list('name', flat=True)))
@@ -211,7 +211,7 @@ class AcomInventoryTest(BaseCommandTest):
host=host.name)
self.assertEqual(result, None)
data = json.loads(stdout)
- self.assertEqual(data, json.loads(host.variable_data.data))
+ self.assertEqual(data, json.loads(host.variables))
def test_invalid_host(self):
# Valid host, but not part of the specified inventory.
diff --git a/ansibleworks/main/tests/inventory.py b/ansibleworks/main/tests/inventory.py
index 2e9d486669..e3675d2f15 100644
--- a/ansibleworks/main/tests/inventory.py
+++ b/ansibleworks/main/tests/inventory.py
@@ -250,7 +250,7 @@ class InventoryTest(BaseTest):
# attempting to get a variable object creates it, even though it does not already exist
vdata_url = "/api/v1/hosts/%s/variable_data/" % (added_by_collection_a['id'])
got = self.get(vdata_url, expect=200, auth=self.get_super_credentials())
- self.assertEquals(got, dict())
+ self.assertEquals(got, {})
# super user can create variable objects
# an org admin can create variable objects (defers to inventory permissions)
@@ -267,16 +267,16 @@ class InventoryTest(BaseTest):
self.put(vdata_url, data=vars_a, expect=403, auth=self.get_nobody_credentials())
# a normal user with inventory write permissions can edit variable objects
- vdata_url = "/api/v1/hosts/1/variable_data/"
- got = self.put(vdata_url, data=vars_b, expect=200, auth=self.get_normal_credentials())
- self.assertEquals(got, vars_b)
+ #vdata_url = "/api/v1/hosts/1/variable_data/"
+ #got = self.put(vdata_url, data=vars_b, expect=200, auth=self.get_normal_credentials())
+ #self.assertEquals(got, vars_b)
# this URL is not one end users will use, but is what you get back from a put
# as a result, it also needs to be access controlled and working. You will not
# be able to put to it.
- backend_url = '/api/v1/variable_data/1/'
- got = self.get(backend_url, expect=200, auth=self.get_normal_credentials())
- got = self.put(backend_url, data=dict(), expect=403, auth=self.get_super_credentials())
+ #backend_url = '/api/v1/variable_data/1/'
+ #got = self.get(backend_url, expect=200, auth=self.get_normal_credentials())
+ #got = self.put(backend_url, data=dict(), expect=403, auth=self.get_super_credentials())
###################################################
# VARIABLES -> GROUPS
@@ -443,7 +443,96 @@ class InventoryTest(BaseTest):
# on a group resource, I can see related resources for variables, inventories, and children
# and these work
-
-
-
+ def test_group_parents_and_children(self):
+ # Test for various levels of group parent/child relations, with hosts,
+ # to verify that helper properties return the correct querysets.
+ # Group A is parent of B, B is parent of C, C is parent of D. Group E
+ # is part of the inventory, but outside of the ABCD tree.
+ g_a = self.inventory_a.groups.create(name='A')
+ g_b = self.inventory_a.groups.create(name='B')
+ g_b.parents.add(g_a)
+ g_c = self.inventory_a.groups.create(name='C')
+ g_c.parents.add(g_b)
+ g_d = self.inventory_a.groups.create(name='D')
+ g_d.parents.add(g_c)
+ g_e = self.inventory_a.groups.create(name='E')
+ # Each group "X" contains one host "x".
+ h_a = self.inventory_a.hosts.create(name='a')
+ h_a.groups.add(g_a)
+ h_b = self.inventory_a.hosts.create(name='b')
+ h_b.groups.add(g_b)
+ h_c = self.inventory_a.hosts.create(name='c')
+ h_c.groups.add(g_c)
+ h_d = self.inventory_a.hosts.create(name='d')
+ h_d.groups.add(g_d)
+ h_e = self.inventory_a.hosts.create(name='e')
+ h_e.groups.add(g_e)
+ # Test all_children property on groups.
+ self.assertEqual(set(g_a.all_children.values_list('pk', flat=True)),
+ set([g_b.pk, g_c.pk, g_d.pk]))
+ self.assertEqual(set(g_b.all_children.values_list('pk', flat=True)),
+ set([g_c.pk, g_d.pk]))
+ self.assertEqual(set(g_c.all_children.values_list('pk', flat=True)),
+ set([g_d.pk]))
+ self.assertEqual(set(g_d.all_children.values_list('pk', flat=True)),
+ set([]))
+ self.assertEqual(set(g_e.all_children.values_list('pk', flat=True)),
+ set([]))
+ # Test all_parents property on groups.
+ self.assertEqual(set(g_a.all_parents.values_list('pk', flat=True)),
+ set([]))
+ self.assertEqual(set(g_b.all_parents.values_list('pk', flat=True)),
+ set([g_a.pk]))
+ self.assertEqual(set(g_c.all_parents.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk]))
+ self.assertEqual(set(g_d.all_parents.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk, g_c.pk]))
+ self.assertEqual(set(g_e.all_parents.values_list('pk', flat=True)),
+ set([]))
+ # Test all_hosts property on groups.
+ self.assertEqual(set(g_a.all_hosts.values_list('pk', flat=True)),
+ set([h_a.pk, h_b.pk, h_c.pk, h_d.pk]))
+ self.assertEqual(set(g_b.all_hosts.values_list('pk', flat=True)),
+ set([h_b.pk, h_c.pk, h_d.pk]))
+ self.assertEqual(set(g_c.all_hosts.values_list('pk', flat=True)),
+ set([h_c.pk, h_d.pk]))
+ self.assertEqual(set(g_d.all_hosts.values_list('pk', flat=True)),
+ set([h_d.pk]))
+ self.assertEqual(set(g_e.all_hosts.values_list('pk', flat=True)),
+ set([h_e.pk]))
+ # Test all_groups property on hosts.
+ self.assertEqual(set(h_a.all_groups.values_list('pk', flat=True)),
+ set([g_a.pk]))
+ self.assertEqual(set(h_b.all_groups.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk]))
+ self.assertEqual(set(h_c.all_groups.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk, g_c.pk]))
+ self.assertEqual(set(h_d.all_groups.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk, g_c.pk, g_d.pk]))
+ self.assertEqual(set(h_e.all_groups.values_list('pk', flat=True)),
+ set([g_e.pk]))
+ # Now create a circular relationship from D back to A.
+ g_a.parents.add(g_d)
+ # All groups "ABCD" should be parents of each other, and children of
+ # each other, and contain all hosts "abcd".
+ for g in [g_a, g_b, g_c, g_d]:
+ self.assertEqual(set(g.all_children.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk, g_c.pk, g_d.pk]))
+ self.assertEqual(set(g.all_parents.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk, g_c.pk, g_d.pk]))
+ self.assertEqual(set(g.all_hosts.values_list('pk', flat=True)),
+ set([h_a.pk, h_b.pk, h_c.pk, h_d.pk]))
+ # All hosts "abcd" should be members of all groups "ABCD".
+ for h in [h_a, h_b, h_c, h_d]:
+ self.assertEqual(set(h.all_groups.values_list('pk', flat=True)),
+ set([g_a.pk, g_b.pk, g_c.pk, g_d.pk]))
+ # Group E and host e should not be affected.
+ self.assertEqual(set(g_e.all_children.values_list('pk', flat=True)),
+ set([]))
+ self.assertEqual(set(g_e.all_parents.values_list('pk', flat=True)),
+ set([]))
+ self.assertEqual(set(g_e.all_hosts.values_list('pk', flat=True)),
+ set([h_e.pk]))
+ self.assertEqual(set(h_e.all_groups.values_list('pk', flat=True)),
+ set([g_e.pk]))
diff --git a/ansibleworks/main/tests/jobs.py b/ansibleworks/main/tests/jobs.py
index 1a9520f9f0..e73710793b 100644
--- a/ansibleworks/main/tests/jobs.py
+++ b/ansibleworks/main/tests/jobs.py
@@ -779,7 +779,8 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.TransactionTestCase):
self.assertFalse(response['passwords_needed_to_start'])
response = self.post(url, {}, expect=202)
job = Job.objects.get(pk=job.pk)
- self.assertEqual(job.status, 'successful')
+ self.assertEqual(job.status, 'successful',
+ job.result_stdout)
else:
self.assertFalse(response['can_start'])
response = self.post(url, {}, expect=405)
diff --git a/ansibleworks/main/tests/organizations.py b/ansibleworks/main/tests/organizations.py
index 3c661f0251..4e891f5c3c 100644
--- a/ansibleworks/main/tests/organizations.py
+++ b/ansibleworks/main/tests/organizations.py
@@ -79,7 +79,7 @@ class OrganizationsTest(BaseTest):
# check that the related URL functionality works
related = response['results'][0]['related']
- for x in [ 'audit_trail', 'projects', 'users', 'admins', 'tags' ]:
+ for x in ['projects', 'users', 'admins']:
self.assertTrue(x in related and related[x].endswith("/%s/" % x), "looking for %s in related" % x)
# normal credentials == 200, get only organizations of which user is a member
@@ -163,7 +163,8 @@ class OrganizationsTest(BaseTest):
org1_users = self.get(org1_users_url, expect=200, auth=self.get_super_credentials())
self.assertEquals(org1_users['count'], 1)
- def test_get_item_subobjects_tags(self):
+ def _test_get_item_subobjects_tags(self):
+ # FIXME: Update to support taggit!
# put some tags on the org
org1 = Organization.objects.get(pk=2)
@@ -181,7 +182,8 @@ class OrganizationsTest(BaseTest):
self.assertEquals(org1_tags['count'], 2)
org1_tags = self.get(org1_tags_url, expect=403, auth=self.get_other_credentials())
- def test_get_item_subobjects_audit_trail(self):
+ def _test_get_item_subobjects_audit_trail(self):
+ # FIXME: Update to support whatever audit trail framework is used.
url = '/api/v1/organizations/2/audit_trail/'
self.get(url, expect=200, auth=self.get_normal_credentials())
# FIXME: verify that some audit trail records are auto-created on save AND post
@@ -291,7 +293,8 @@ class OrganizationsTest(BaseTest):
admins = self.get(url, expect=200, auth=self.get_normal_credentials())
self.assertEqual(admins['count'], 1)
- def test_post_item_subobjects_tags(self):
+ def _test_post_item_subobjects_tags(self):
+ # FIXME: Update to support taggit!
tag = Tag.objects.create(name='blippy')
url = '/api/v1/organizations/2/tags/'
@@ -305,7 +308,8 @@ class OrganizationsTest(BaseTest):
tags = self.get(url, expect=200, auth=self.get_normal_credentials())
self.assertEqual(tags['count'], 0)
- def test_post_item_subobjects_audit_trail(self):
+ def _test_post_item_subobjects_audit_trail(self):
+ # FIXME: Update to support whatever audit trail framework is used.
# audit trails are system things, and no user can post to them.
url = '/api/v1/organizations/2/audit_trail/'
self.post(url, dict(id=1), expect=405, auth=self.get_super_credentials())
diff --git a/ansibleworks/main/urls.py b/ansibleworks/main/urls.py
index 73da7f1ac2..7a01659775 100644
--- a/ansibleworks/main/urls.py
+++ b/ansibleworks/main/urls.py
@@ -13,12 +13,10 @@ def url(regex, view, kwargs=None, name=None, prefix=''):
organizations_urls = patterns('ansibleworks.main.views',
url(r'^$', 'organizations_list'),
url(r'^(?P[0-9]+)/$', 'organizations_detail'),
- url(r'^(?P[0-9]+)/audit_trail/$', 'organizations_audit_trail_list'),
url(r'^(?P[0-9]+)/users/$', 'organizations_users_list'),
url(r'^(?P[0-9]+)/admins/$', 'organizations_admins_list'),
url(r'^(?P[0-9]+)/inventories/$', 'organizations_inventories_list'),
url(r'^(?P[0-9]+)/projects/$', 'organizations_projects_list'),
- url(r'^(?P[0-9]+)/tags/$', 'organizations_tags_list'),
url(r'^(?P[0-9]+)/teams/$', 'organizations_teams_list'),
)
@@ -40,12 +38,6 @@ projects_urls = patterns('ansibleworks.main.views',
url(r'^(?P[0-9]+)/organizations/$', 'projects_organizations_list'),
)
-audit_trails_urls = patterns('ansibleworks.main.views',
- #url(r'^$', 'audit_trails_list'),
- #url(r'^(?P[0-9]+)/$', 'audit_trails_detail'),
- # ... and ./audit_trails/ on all resources
-)
-
teams_urls = patterns('ansibleworks.main.views',
url(r'^$', 'teams_list'),
url(r'^(?P[0-9]+)/$', 'teams_detail'),
@@ -82,11 +74,6 @@ groups_urls = patterns('ansibleworks.main.views',
url(r'^(?P[0-9]+)/job_host_summaries/$', 'group_job_host_summary_list'),
)
-variable_data_urls = patterns('ansibleworks.main.views',
- url(r'^(?P[0-9]+)/$', 'variable_detail'),
- # See also variable_data resources on hosts/groups.
-)
-
credentials_urls = patterns('ansibleworks.main.views',
url(r'^$', 'credentials_list'),
url(r'^(?P[0-9]+)/$', 'credentials_detail'),
@@ -125,11 +112,6 @@ job_events_urls = patterns('ansibleworks.main.views',
url(r'^(?P[0-9]+)/$', 'job_event_detail'),
)
-tags_urls = patterns('ansibleworks.main.views',
- url(r'^(?P[0-9]+)/$', 'tags_detail'),
- # ... and tag relations on all resources
-)
-
v1_urls = patterns('ansibleworks.main.views',
url(r'^$', 'api_v1_root_view'),
url(r'^authtoken/$', 'auth_token_view'),
@@ -137,19 +119,16 @@ v1_urls = patterns('ansibleworks.main.views',
url(r'^organizations/', include(organizations_urls)),
url(r'^users/', include(users_urls)),
url(r'^projects/', include(projects_urls)),
- url(r'^audit_trails/', include(audit_trails_urls)),
url(r'^teams/', include(teams_urls)),
url(r'^inventories/', include(inventory_urls)),
url(r'^hosts/', include(hosts_urls)),
url(r'^groups/', include(groups_urls)),
- url(r'^variable_data/', include(variable_data_urls)),
url(r'^credentials/', include(credentials_urls)),
url(r'^permissions/', include(permissions_urls)),
url(r'^job_templates/', include(job_templates_urls)),
url(r'^jobs/', include(jobs_urls)),
url(r'^job_host_summaries/', include(job_host_summary_urls)),
url(r'^job_events/', include(job_events_urls)),
- url(r'^tags/', include(tags_urls)),
)
urlpatterns = patterns('ansibleworks.main.views',
diff --git a/ansibleworks/main/views.py b/ansibleworks/main/views.py
index d4fb41a499..2498200957 100644
--- a/ansibleworks/main/views.py
+++ b/ansibleworks/main/views.py
@@ -133,23 +133,6 @@ class OrganizationsDetail(BaseDetail):
serializer_class = OrganizationSerializer
permission_classes = (CustomRbac,)
-class OrganizationsAuditTrailList(BaseSubList):
-
- model = AuditTrail
- serializer_class = AuditTrailSerializer
- permission_classes = (CustomRbac,)
- parent_model = Organization
- relationship = 'audit_trail'
- postable = False
-
- def get_queryset(self):
- ''' to list tags in the organization, I must be a superuser or org admin '''
- organization = Organization.objects.get(pk=self.kwargs['pk'])
- if not (self.request.user.is_superuser or self.request.user in organization.admins.all()):
- # FIXME: use: organization.can_user_administrate(...) ?
- raise PermissionDenied()
- return AuditTrail.objects.filter(organization_by_audit_trail__in = [ organization ])
-
class OrganizationsInventoriesList(BaseSubList):
model = Inventory
@@ -221,25 +204,6 @@ class OrganizationsProjectsList(BaseSubList):
raise PermissionDenied()
return Project.objects.filter(organizations__in = [ organization ])
-class OrganizationsTagsList(BaseSubList):
-
- model = Tag
- serializer_class = TagSerializer
- permission_classes = (CustomRbac,)
- parent_model = Organization # for sub list
- relationship = 'tags' # " "
- postable = True
- inject_primary_key_on_post_as = 'organization'
- filter_fields = ('name',)
-
- def get_queryset(self):
- ''' to list tags in the organization, I must be a superuser or org admin '''
- organization = Organization.objects.get(pk=self.kwargs['pk'])
- if not (self.request.user.is_superuser or self.request.user in organization.admins.all()):
- # FIXME: use: organization.can_user_administrate(...) ?
- raise PermissionDenied()
- return Tag.objects.filter(organization_by_tag__in = [ organization ])
-
class OrganizationsTeamsList(BaseSubList):
model = Team
@@ -433,12 +397,6 @@ class ProjectsOrganizationsList(BaseSubList):
raise PermissionDenied()
return Organization.objects.filter(projects__in = [ project ])
-class TagsDetail(BaseDetail):
-
- model = Tag
- serializer_class = TagSerializer
- permission_classes = (CustomRbac,)
-
class UsersList(BaseList):
model = User
@@ -889,32 +847,19 @@ class InventoryRootGroupsList(BaseSubList):
all_ids = base.values_list('id', flat=True)
return base.exclude(parents__pk__in = all_ids)
-class GroupsVariableDetail(VariableBaseDetail):
+class HostsVariableDetail(BaseDetail):
- model = VariableData
- serializer_class = VariableDataSerializer
+ model = Host
+ serializer_class = HostVariableDataSerializer
permission_classes = (CustomRbac,)
- parent_model = Group
- reverse_relationship = 'variable_data'
- relationship = 'group'
+ is_variable_data = True # Special flag for RBAC
-class HostsVariableDetail(VariableBaseDetail):
+class GroupsVariableDetail(BaseDetail):
- model = VariableData
- serializer_class = VariableDataSerializer
+ model = Group
+ serializer_class = GroupVariableDataSerializer
permission_classes = (CustomRbac,)
- parent_model = Host
- reverse_relationship = 'variable_data'
- relationship = 'host'
-
-class VariableDetail(BaseDetail):
-
- model = VariableData
- serializer_class = VariableDataSerializer
- permission_classes = (CustomRbac,)
-
- def put(self, request, *args, **kwargs):
- raise PermissionDenied()
+ is_variable_data = True # Special flag for RBAC
class JobTemplateList(BaseList):
diff --git a/ansibleworks/plugins/callback/acom_callback.py b/ansibleworks/plugins/callback/acom_callback.py
index cea57ccee1..30e880ce8f 100644
--- a/ansibleworks/plugins/callback/acom_callback.py
+++ b/ansibleworks/plugins/callback/acom_callback.py
@@ -39,10 +39,31 @@ class CallbackModule(object):
Callback module for logging ansible-playbook events to the database.
'''
+ # These events should never have an associated play.
+ EVENTS_WITHOUT_PLAY = [
+ 'playbook_on_start',
+ 'playbook_on_stats',
+ ]
+ # These events should never have an associated task.
+ EVENTS_WITHOUT_TASK = EVENTS_WITHOUT_PLAY + [
+ 'playbook_on_setup',
+ 'playbook_on_notify',
+ 'playbook_on_import_for_host',
+ 'playbook_on_not_import_for_host',
+ 'playbook_on_no_hosts_matched',
+ 'playbook_on_no_hosts_remaining',
+ ]
+
def __init__(self):
self.callback_script = os.getenv('ACOM_CALLBACK_EVENT_SCRIPT')
def _log_event(self, event, **event_data):
+ play = getattr(getattr(self, 'play', None), 'name', '')
+ if play and event not in self.EVENTS_WITHOUT_PLAY:
+ event_data['play'] = play
+ task = getattr(getattr(self, 'task', None), 'name', '')
+ if task and event not in self.EVENTS_WITHOUT_TASK:
+ event_data['task'] = task
event_data_json = json.dumps(event_data)
cmdline = [self.callback_script, '-e', event, '-d', event_data_json]
subprocess.check_call(cmdline)
@@ -79,11 +100,20 @@ class CallbackModule(object):
def runner_on_async_failed(self, host, res, jid):
self._log_event('runner_on_async_failed', host=host, res=res, jid=jid)
+ def runner_on_file_diff(self, host, diff):
+ self._log_event('runner_on_file_diff', host=host, diff=diff)
+
def playbook_on_start(self):
self._log_event('playbook_on_start')
def playbook_on_notify(self, host, handler):
- self._log_event('playbook_on_notify')
+ self._log_event('playbook_on_notify', host=host, handler=handler)
+
+ def playbook_on_no_hosts_matched(self):
+ self._log_event('playbook_on_no_hosts_matched')
+
+ def playbook_on_no_hosts_remaining(self):
+ self._log_event('playbook_on_no_hosts_remaining')
def playbook_on_task_start(self, name, is_conditional):
self._log_event('playbook_on_task_start', name=name,
diff --git a/ansibleworks/settings/defaults.py b/ansibleworks/settings/defaults.py
index 0f10a011c7..a19c2569b1 100644
--- a/ansibleworks/settings/defaults.py
+++ b/ansibleworks/settings/defaults.py
@@ -29,9 +29,9 @@ REST_FRAMEWORK = {
'PAGINATE_BY': 25,
'PAGINATE_BY_PARAM': 'page_size',
'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
- 'rest_framework.authentication.TokenAuthentication',
)
}
@@ -137,6 +137,7 @@ INSTALLED_APPS = (
'django_extensions',
'djcelery',
'kombu.transport.django',
+ 'taggit',
'ansibleworks.main',
'ansibleworks.ui',
)
diff --git a/requirements.txt b/requirements.txt
index c4da567330..9e86883e7b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,6 +7,7 @@ django-devserver
django-extensions
django-filter
django-jsonfield
+django-taggit
djangorestframework
ipython
markdown
diff --git a/setup.py b/setup.py
index cc9e49448c..8e0b494b93 100755
--- a/setup.py
+++ b/setup.py
@@ -26,10 +26,10 @@ setup(
install_requires=[
'Django>=1.5',
'django-celery',
- 'django-devserver',
'django-extensions',
'django-filter',
'django-jsonfield',
+ 'django-taggit',
'djangorestframework',
'pexpect',
'python-dateutil',
@@ -40,10 +40,10 @@ setup(
#tests_require=[
# 'Django>=1.5',
# 'django-celery',
- # 'django-devserver',
# 'django-extensions',
# 'django-filter',
# 'django-jsonfield',
+ # 'django-taggit',
# 'django-setuptest',
# 'djangorestframework',
# 'pexpect',