Merge branch 'release_2.4.0' into devel

* release_2.4.0: (94 commits)
  Fix up location of apt key for mongo
  Handle playbook_on_include events from v2 ansible
  if basic auth in headers, don't use cookie token
  Add a docker-ui container to the compose workflow
  Fix custom inventory scripts on org deletion
  adding check for missing session time from local storage
  Properly handle idle/expired session across tabs
  fixed system tracking date issues
  Improve postgres user creation
  Remove silly user check in postgres config
  Fix typo in `clean` target
  fixed sources scope regions issue
  fixed scan date being the earlier one when identical
  fixed the console errors for the sources scope
  fixed console error
  fixed standard out load for adhoc commands
  fixing custom inventory script error
  Revert "change list separator for adhoc host patterns"
  Revert "update awFeature ldap to enterprise_auth"
  Revert "Change delimiter from ':' to ','."
  ...
This commit is contained in:
Matthew Jones 2015-10-20 16:10:02 -04:00
commit 45982e50e6
46 changed files with 1163 additions and 380 deletions

View File

@ -201,7 +201,7 @@ clean-bundle:
# Remove temporary build files, compiled Python files.
clean: clean-rpm clean-deb clean-grunt clean-ui clean-tar clean-packer clean-bundle
rm -rf awx/lib/site-packages
rm- rf awx/lib/.deps_built
rm -rf awx/lib/.deps_built
rm -rf dist/*
rm -rf tmp
mkdir tmp
@ -700,5 +700,13 @@ docker-compose:
docker-compose -f tools/docker-compose.yml up --no-recreate
docker-compose-test:
cd tools && docker-compose run --service-ports tower /bin/bash
cd tools && docker-compose run --rm --service-ports tower /bin/bash
mongo-debug-ui:
docker run -it --rm --name mongo-express --link tools_mongo_1:mongo -e ME_CONFIG_OPTIONS_EDITORTHEME=ambiance -e ME_CONFIG_BASICAUTH_USERNAME=admin -e ME_CONFIG_BASICAUTH_PASSWORD=password -p 8081:8081 knickers/mongo-express
mongo-container:
docker run -it --link tools_mongo_1:mongo --rm mongo sh -c 'exec mongo "$MONGO_PORT_27017_TCP_ADDR:$MONGO_PORT_27017_TCP_PORT/system_tracking_dev"'
psql-container:
docker run -it --link tools_postgres_1:postgres --rm postgres:9.4.1 sh -c 'exec psql -h "$$POSTGRES_PORT_5432_TCP_ADDR" -p "$$POSTGRES_PORT_5432_TCP_PORT" -U postgres'

View File

@ -50,7 +50,10 @@ class TokenAuthentication(authentication.TokenAuthentication):
auth = TokenAuthentication._get_x_auth_token_header(request).split()
if not auth or auth[0].lower() != 'token':
auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != 'token':
# Prefer basic auth over cookie token
if auth and auth[0].lower() == 'basic':
return None
elif not auth or auth[0].lower() != 'token':
auth = TokenAuthentication._get_auth_token_cookie(request).split()
if not auth or auth[0].lower() != 'token':
return None

View File

@ -31,4 +31,17 @@ def feature_enabled(name):
return False
# Return the correct feature flag.
return get_license()['features'].get(name, False)
return license['features'].get(name, False)
def feature_exists(name):
"""Return True if the requested feature is enabled, False otherwise.
If the feature does not exist, raise KeyError.
"""
license = get_license()
# Sanity check: If there is no license, the feature is considered
# to be off.
if 'features' not in license:
return False
return name in license['features']

View File

@ -60,7 +60,7 @@ from awx.api.utils.decorators import paginated
from awx.api.filters import MongoFilterBackend
from awx.api.generics import get_view_name
from awx.api.generics import * # noqa
from awx.api.license import feature_enabled, LicenseForbids
from awx.api.license import feature_enabled, feature_exists, LicenseForbids
from awx.main.models import * # noqa
from awx.main.utils import * # noqa
from awx.api.permissions import * # noqa
@ -527,6 +527,11 @@ class AuthView(APIView):
data = SortedDict()
err_backend, err_message = request.session.get('social_auth_error', (None, None))
for name, backend in load_backends(settings.AUTHENTICATION_BACKENDS).items():
if (not feature_exists('enterprise_auth') and
not feature_enabled('ldap')) or \
(not feature_enabled('enterprise_auth') and
name in ['saml', 'radius']):
continue
login_url = reverse('social:begin', args=(name,))
complete_url = request.build_absolute_uri(reverse('social:complete', args=(name,)))
backend_data = {

View File

@ -262,6 +262,7 @@ class OrganizationAccess(BaseAccess):
self.user in obj.admins.all())
def can_delete(self, obj):
self.check_license(feature='multiple_organizations')
return self.can_change(obj, None)
class InventoryAccess(BaseAccess):
@ -672,22 +673,22 @@ class ProjectAccess(BaseAccess):
- I am on a team associated with the project.
- I have been explicitly granted permission to run/check jobs using the
project.
- I created it (for now?).
- I created the project but it isn't associated with an organization
I can change/delete when:
- I am a superuser.
- I am an admin in an organization associated with the project.
- I created it (for now?).
- I created the project but it isn't associated with an organization
'''
model = Project
def get_queryset(self):
qs = Project.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'modified_by', 'credential', 'current_update', 'last_update')
qs = qs.select_related('modified_by', 'credential', 'current_update', 'last_update')
if self.user.is_superuser:
return qs
team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True))
qs = qs.filter(Q(created_by=self.user) |
qs = qs.filter(Q(created_by=self.user, organizations__isnull=True) |
Q(organizations__admins__in=[self.user], organizations__active=True) |
Q(organizations__users__in=[self.user], organizations__active=True) |
Q(teams__in=team_ids))
@ -719,7 +720,7 @@ class ProjectAccess(BaseAccess):
def can_change(self, obj, data):
if self.user.is_superuser:
return True
if obj.created_by == self.user:
if obj.created_by == self.user and not obj.organizations.filter(active=True).count():
return True
if obj.organizations.filter(active=True, admins__in=[self.user]).exists():
return True
@ -1605,7 +1606,7 @@ class CustomInventoryScriptAccess(BaseAccess):
model = CustomInventoryScript
def get_queryset(self):
qs = self.model.objects.filter(active=True, organization__active=True).distinct()
qs = self.model.objects.filter(active=True).distinct()
if not self.user.is_superuser:
qs = qs.filter(Q(organization__admins__in=[self.user]) | Q(organization__users__in=[self.user]))
return qs

View File

@ -0,0 +1,34 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import logging
# Django
from django.db import transaction
from django.core.management.base import BaseCommand
from django.utils.timezone import now
# AWX
from awx.main.models import * # noqa
class Command(BaseCommand):
'''
Management command to cleanup expired auth tokens
'''
help = 'Cleanup expired auth tokens.'
def init_logging(self):
self.logger = logging.getLogger('awx.main.commands.cleanup_authtokens')
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
self.logger.addHandler(handler)
self.logger.propagate = False
@transaction.atomic
def handle(self, *args, **options):
self.init_logging()
tokens_removed = AuthToken.objects.filter(expires__lt=now())
self.logger.log(99, "Removing %d expired auth tokens" % tokens_removed.count())
tokens_removed.delete()

View File

@ -118,3 +118,10 @@ class Command(BaseCommand):
self.logger.log(99, "Removed %d items", n_deleted_items)
else:
self.logger.log(99, "Would have removed %d items", n_deleted_items)
tokens_removed = AuthToken.objects.filter(expires__lt=now())
if not self.dry_run:
self.logger.log(99, "Removed %d expired auth tokens" % tokens_removed.count())
tokens_removed.delete()
else:
self.logger.log(99, "Would have removed %d expired auth tokens" % tokens_removed.count())

View File

@ -883,14 +883,14 @@ class Command(NoArgsCommand):
all_obj = self.inventory
all_name = 'inventory'
db_variables = all_obj.variables_dict
if self.overwrite_vars or self.overwrite:
if self.overwrite_vars:
db_variables = self.all_group.variables
else:
db_variables.update(self.all_group.variables)
if db_variables != all_obj.variables_dict:
all_obj.variables = json.dumps(db_variables)
all_obj.save(update_fields=['variables'])
if self.overwrite_vars or self.overwrite:
if self.overwrite_vars:
self.logger.info('%s variables replaced from "all" group', all_name.capitalize())
else:
self.logger.info('%s variables updated from "all" group', all_name.capitalize())
@ -920,14 +920,14 @@ class Command(NoArgsCommand):
for group in self.inventory.groups.filter(name__in=group_names):
mem_group = self.all_group.all_groups[group.name]
db_variables = group.variables_dict
if self.overwrite_vars or self.overwrite:
if self.overwrite_vars:
db_variables = mem_group.variables
else:
db_variables.update(mem_group.variables)
if db_variables != group.variables_dict:
group.variables = json.dumps(db_variables)
group.save(update_fields=['variables'])
if self.overwrite_vars or self.overwrite:
if self.overwrite_vars:
self.logger.info('Group "%s" variables replaced', group.name)
else:
self.logger.info('Group "%s" variables updated', group.name)
@ -959,7 +959,7 @@ class Command(NoArgsCommand):
def _update_db_host_from_mem_host(self, db_host, mem_host):
# Update host variables.
db_variables = db_host.variables_dict
if self.overwrite_vars or self.overwrite:
if self.overwrite_vars:
db_variables = mem_host.variables
else:
db_variables.update(mem_host.variables)
@ -994,7 +994,7 @@ class Command(NoArgsCommand):
else:
self.logger.info('Host "%s" instance_id added', mem_host.name)
if 'variables' in update_fields:
if self.overwrite_vars or self.overwrite:
if self.overwrite_vars:
self.logger.info('Host "%s" variables replaced', mem_host.name)
else:
self.logger.info('Host "%s" variables updated', mem_host.name)

View File

@ -130,6 +130,7 @@ class CallbackReceiver(object):
'playbook_on_task_start',
'playbook_on_no_hosts_matched',
'playbook_on_no_hosts_remaining',
'playbook_on_include',
'playbook_on_import_for_host',
'playbook_on_not_import_for_host'):
parent = job_parent_events.get('playbook_on_play_start', None)

View File

@ -0,0 +1,519 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'CustomInventoryScript.organization'
db.alter_column(u'main_custominventoryscript', 'organization_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['main.Organization']))
def backwards(self, orm):
# Changing field 'CustomInventoryScript.organization'
db.alter_column(u'main_custominventoryscript', 'organization_id', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['main.Organization']))
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'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.activitystream': {
'Meta': {'object_name': 'ActivityStream'},
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
'object1': ('django.db.models.fields.TextField', [], {}),
'object2': ('django.db.models.fields.TextField', [], {}),
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'main.adhoccommand': {
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.adhoccommandevent': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
},
'main.authtoken': {
'Meta': {'object_name': 'AuthToken'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
},
'main.credential': {
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'security_token': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'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'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
'main.custominventoryscript': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.group': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
'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': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.instance': {
'Meta': {'object_name': 'Instance'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
},
'main.inventory': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventorysource': {
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'main.inventoryupdate': {
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.job': {
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", '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_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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
},
'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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.joborigin': {
'Meta': {'object_name': 'JobOrigin'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
},
'main.jobtemplate': {
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", '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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'ordering': "('name',)", '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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': "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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Project']"}),
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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']"})
},
'main.profile': {
'Meta': {'object_name': 'Profile'},
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
'main.project': {
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.projectupdate': {
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.schedule': {
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'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'}),
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.systemjob': {
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.systemjobtemplate': {
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.team': {
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'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': "orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.unifiedjob': {
'Meta': {'object_name': 'UnifiedJob'},
'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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.unifiedjobtemplate': {
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
}
}
complete_apps = ['main']

View File

@ -1281,7 +1281,9 @@ class CustomInventoryScript(CommonModelNameNotUnique):
'Organization',
related_name='custom_inventory_scripts',
help_text=_('Organization owning this inventory script'),
on_delete=models.CASCADE,
blank=False,
null=True,
on_delete=models.SET_NULL,
)
def get_absolute_url(self):

View File

@ -53,6 +53,12 @@ class Organization(CommonModel):
def __unicode__(self):
return self.name
def mark_inactive(self, save=True):
for script in self.custom_inventory_scripts.all():
script.organization = None
script.save()
super(Organization, self).mark_inactive(save=save)
class Team(CommonModelNameNotUnique):
'''

View File

@ -162,6 +162,7 @@ def handle_work_error(self, task_id, subtasks=None):
print('Executing error task id %s, subtasks: %s' %
(str(self.request.id), str(subtasks)))
first_task = None
first_task_id = None
first_task_type = ''
first_task_name = ''
if subtasks is not None:
@ -184,13 +185,14 @@ def handle_work_error(self, task_id, subtasks=None):
break
if first_task is None:
first_task = instance
first_task_id = instance.id
first_task_type = each_task['type']
first_task_name = instance_name
if instance.celery_task_id != task_id:
instance.status = 'failed'
instance.failed = True
instance.job_explanation = "Previous Task Failed: %s for %s with celery task id: %s" % \
(first_task_type, first_task_name, task_id)
instance.job_explanation = 'Previous Task Failed: {"task_type": "%s", "task_name": "%s", "task_id": "%s"}' % \
(first_task_type, first_task_name, first_task_id)
instance.save()
instance.socketio_emit_status("failed")
@ -636,12 +638,17 @@ class RunJob(BaseTask):
Build environment dictionary for ansible-playbook.
'''
plugin_dir = self.get_path_to('..', 'plugins', 'callback')
plugin_dirs = [plugin_dir]
if hasattr(settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and \
settings.AWX_ANSIBLE_CALLBACK_PLUGINS:
plugin_dirs.append(settings.AWX_ANSIBLE_CALLBACK_PLUGINS)
plugin_path = ':'.join(plugin_dirs)
env = super(RunJob, self).build_env(job, **kwargs)
# Set environment variables needed for inventory and job event
# callbacks to work.
env['JOB_ID'] = str(job.pk)
env['INVENTORY_ID'] = str(job.inventory.pk)
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_path
env['REST_API_URL'] = settings.INTERNAL_API_URL
env['REST_API_TOKEN'] = job.task_auth_token or ''
env['CALLBACK_CONSUMER_PORT'] = str(settings.CALLBACK_CONSUMER_PORT)

View File

@ -192,6 +192,20 @@ class BaseTestMixin(QueueTestMixin, MockCommonlySlowTestMixin):
self._temp_paths.append(license_path)
os.environ['AWX_LICENSE_FILE'] = license_path
def create_basic_license_file(self, instance_count=100, license_date=int(time.time() + 3600)):
writer = LicenseWriter(
company_name='AWX',
contact_name='AWX Admin',
contact_email='awx@example.com',
license_date=license_date,
instance_count=instance_count,
license_type='basic')
handle, license_path = tempfile.mkstemp(suffix='.json')
os.close(handle)
writer.write_file(license_path)
self._temp_paths.append(license_path)
os.environ['AWX_LICENSE_FILE'] = license_path
def create_expired_license_file(self, instance_count=1000, grace_period=False):
license_date = time.time() - 1
if not grace_period:

View File

@ -828,7 +828,7 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
host_names = set(new_inv.hosts.filter(active=True).values_list('name', flat=True))
self.assertEqual(expected_host_names, host_names)
expected_inv_vars = {'vara': 'A', 'varc': 'C'}
if overwrite or overwrite_vars:
if overwrite_vars:
expected_inv_vars.pop('varc')
self.assertEqual(new_inv.variables_dict, expected_inv_vars)
for host in new_inv.hosts.filter(active=True):
@ -846,7 +846,7 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
for group in new_inv.groups.filter(active=True):
if group.name == 'servers':
expected_vars = {'varb': 'B', 'vard': 'D'}
if overwrite or overwrite_vars:
if overwrite_vars:
expected_vars.pop('vard')
self.assertEqual(group.variables_dict, expected_vars)
children = set(group.children.filter(active=True).values_list('name', flat=True))

View File

@ -267,6 +267,11 @@ class OrganizationsTest(BaseTest):
# look at what we got back from the post, make sure we added an org
last_org = Organization.objects.order_by('-pk')[0]
self.assertTrue(data1['url'].endswith("/%d/" % last_org.pk))
# Test that not even super users can create an organization with a basic license
self.create_basic_license_file()
cant_org = dict(name='silly user org', description='4815162342')
self.post(self.collection(), cant_org, expect=402, auth=self.get_super_credentials())
def test_post_item_subobjects_projects(self):
@ -437,6 +442,10 @@ class OrganizationsTest(BaseTest):
# also check that DELETE on the collection doesn't work
self.delete(self.collection(), expect=405, auth=self.get_super_credentials())
# Test that not even super users can delete an organization with a basic license
self.create_basic_license_file()
self.delete(urls[2], expect=402, auth=self.get_super_credentials())
def test_invalid_post_data(self):
url = reverse('api:organization_list')
# API should gracefully handle data of an invalid type.

View File

@ -209,7 +209,7 @@ class ProjectsTest(BaseTransactionTest):
self.assertEquals(results['count'], 10)
# org admin
results = self.get(projects, expect=200, auth=self.get_normal_credentials())
self.assertEquals(results['count'], 10)
self.assertEquals(results['count'], 9)
# user on a team
results = self.get(projects, expect=200, auth=self.get_other_credentials())
self.assertEquals(results['count'], 5)
@ -300,6 +300,17 @@ class ProjectsTest(BaseTransactionTest):
got = self.get(proj_orgs, expect=200, auth=self.get_super_credentials())
self.assertEquals(got['count'], 2)
# Verify that creatorship doesn't imply access if access is removed
a_new_proj = self.make_project(created_by=self.other_django_user, playbook_content=TEST_PLAYBOOK)
self.organizations[0].admins.add(self.other_django_user)
self.organizations[0].projects.add(a_new_proj)
proj_detail = reverse('api:project_detail', args=(a_new_proj.pk,))
self.patch(proj_detail, data=dict(description="test"), expect=200, auth=self.get_other_credentials())
self.organizations[0].admins.remove(self.other_django_user)
self.patch(proj_detail, data=dict(description="test_now"), expect=403, auth=self.get_other_credentials())
self.delete(proj_detail, expect=403, auth=self.get_other_credentials())
a_new_proj.delete()
# =====================================================================
# TEAMS

View File

@ -426,6 +426,8 @@ class JobCallbackModule(BaseCallbackModule):
def v2_playbook_on_stats(self, stats):
self.playbook_on_stats(stats)
def v2_playbook_on_include(self, included_file):
self._log_event('playbook_on_include', included_file=included_file)
class AdHocCommandCallbackModule(BaseCallbackModule):
'''

View File

@ -35,7 +35,10 @@ import time
import datetime
from copy import deepcopy
from ansible import constants as C
from ansible.cache.base import BaseCacheModule
try:
from ansible.cache.base import BaseCacheModule
except:
from ansible.plugins.cache.base import BaseCacheModule
try:
import zmq
@ -46,7 +49,6 @@ except ImportError:
class CacheModule(BaseCacheModule):
def __init__(self, *args, **kwargs):
# Basic in-memory caching for typical runs
self._cache = {}
self._cache_prev = {}
@ -108,16 +110,15 @@ class CacheModule(BaseCacheModule):
module = self.identify_new_module(key, value)
# Assume ansible fact triggered the set if no new module found
facts = self.filter_ansible_facts(value) if not module else dict({ module : value[module]})
self._cache_prev = deepcopy(self._cache)
self._cache[key] = value
self._cache_prev = deepcopy(self._cache)
packet = {
'host': key,
'inventory_id': os.environ['INVENTORY_ID'],
'facts': facts,
'date_key': self.date_key,
}
# Emit fact data to tower for processing
self.socket.send_json(packet)
self.socket.recv()

View File

@ -66,7 +66,7 @@ Examples:
$ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a"
Use the GCE inventory script to print out instance specific information
$ plugins/inventory/gce.py --host my_instance
$ contrib/inventory/gce.py --host my_instance
Author: Eric Johnson <erjohnso@google.com>
Version: 0.0.1
@ -112,9 +112,9 @@ class GceInventory(object):
# Just display data for specific host
if self.args.host:
print self.json_format_dict(self.node_to_dict(
print(self.json_format_dict(self.node_to_dict(
self.get_instance(self.args.host)),
pretty=self.args.pretty)
pretty=self.args.pretty))
sys.exit(0)
# Otherwise, assume user wants all instances grouped
@ -237,7 +237,7 @@ class GceInventory(object):
'''Gets details about a specific instance '''
try:
return self.driver.ex_get_node(instance_name)
except Exception, e:
except Exception as e:
return None
def group_instances(self):
@ -257,7 +257,10 @@ class GceInventory(object):
tags = node.extra['tags']
for t in tags:
tag = 'tag_%s' % t
if t.startswith('group-'):
tag = t[6:]
else:
tag = 'tag_%s' % t
if groups.has_key(tag): groups[tag].append(name)
else: groups[tag] = [name]

View File

@ -153,6 +153,8 @@ import warnings
import collections
import ConfigParser
from six import iteritems
from ansible.constants import get_config, mk_boolean
try:
@ -167,6 +169,9 @@ except ImportError:
print('pyrax is required for this module')
sys.exit(1)
from time import time
NON_CALLABLES = (basestring, bool, dict, int, list, type(None))
@ -214,7 +219,7 @@ def host(regions, hostname):
print(json.dumps(hostvars, sort_keys=True, indent=4))
def _list(regions):
def _list_into_cache(regions):
groups = collections.defaultdict(list)
hostvars = collections.defaultdict(dict)
images = {}
@ -242,7 +247,7 @@ def _list(regions):
if cs is None:
warnings.warn(
'Connecting to Rackspace region "%s" has caused Pyrax to '
'return a NoneType. Is this a valid region?' % region,
'return None. Is this a valid region?' % region,
RuntimeWarning)
continue
for server in cs.servers.list():
@ -264,7 +269,7 @@ def _list(regions):
hostvars[server.name]['rax_region'] = region
for key, value in server.metadata.iteritems():
for key, value in iteritems(server.metadata):
groups['%s_%s_%s' % (prefix, key, value)].append(server.name)
groups['instance-%s' % server.id].append(server.name)
@ -334,7 +339,31 @@ def _list(regions):
if hostvars:
groups['_meta'] = {'hostvars': hostvars}
print(json.dumps(groups, sort_keys=True, indent=4))
with open(get_cache_file_path(regions), 'w') as cache_file:
json.dump(groups, cache_file)
def get_cache_file_path(regions):
regions_str = '.'.join([reg.strip().lower() for reg in regions])
ansible_tmp_path = os.path.join(os.path.expanduser("~"), '.ansible', 'tmp')
if not os.path.exists(ansible_tmp_path):
os.makedirs(ansible_tmp_path)
return os.path.join(ansible_tmp_path,
'ansible-rax-%s-%s.cache' % (
pyrax.identity.username, regions_str))
def _list(regions, refresh_cache=True):
if (not os.path.exists(get_cache_file_path(regions)) or
refresh_cache or
(time() - os.stat(get_cache_file_path(regions))[-1]) > 600):
# Cache file doesn't exist or older than 10m or refresh cache requested
_list_into_cache(regions)
with open(get_cache_file_path(regions), 'r') as cache_file:
groups = json.load(cache_file)
print(json.dumps(groups, sort_keys=True, indent=4))
def parse_args():
@ -344,6 +373,9 @@ def parse_args():
group.add_argument('--list', action='store_true',
help='List active servers')
group.add_argument('--host', help='List details about the specific host')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help=('Force refresh of cache, making API requests to'
'RackSpace (default: False - use cache files)'))
return parser.parse_args()
@ -382,7 +414,7 @@ def setup():
pyrax.keyring_auth(keyring_username, region=region)
else:
pyrax.set_credential_file(creds_file, region=region)
except Exception, e:
except Exception as e:
sys.stderr.write("%s: %s\n" % (e, e.message))
sys.exit(1)
@ -410,7 +442,7 @@ def main():
args = parse_args()
regions = setup()
if args.list:
_list(regions)
_list(regions, refresh_cache=args.refresh_cache)
elif args.host:
host(regions, args.host)
sys.exit(0)

View File

@ -28,6 +28,8 @@ take precedence over options present in the INI file. An INI file is not
required if these options are specified using environment variables.
'''
from __future__ import print_function
import collections
import json
import logging
@ -37,6 +39,8 @@ import sys
import time
import ConfigParser
from six import text_type
# Disable logging message trigged by pSphere/suds.
try:
from logging import NullHandler
@ -95,7 +99,7 @@ class VMwareInventory(object):
Saves the value to cache with the name given.
'''
if self.config.has_option('defaults', 'cache_dir'):
cache_dir = self.config.get('defaults', 'cache_dir')
cache_dir = os.path.expanduser(self.config.get('defaults', 'cache_dir'))
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
cache_file = os.path.join(cache_dir, name)
@ -115,7 +119,7 @@ class VMwareInventory(object):
else:
cache_max_age = 0
cache_stat = os.stat(cache_file)
if (cache_stat.st_mtime + cache_max_age) < time.time():
if (cache_stat.st_mtime + cache_max_age) >= time.time():
with open(cache_file) as cache:
return json.load(cache)
return default
@ -147,7 +151,7 @@ class VMwareInventory(object):
seen = seen or set()
if isinstance(obj, ManagedObject):
try:
obj_unicode = unicode(getattr(obj, 'name'))
obj_unicode = text_type(getattr(obj, 'name'))
except AttributeError:
obj_unicode = ()
if obj in seen:
@ -164,7 +168,7 @@ class VMwareInventory(object):
obj_info = self._get_obj_info(val, depth - 1, seen)
if obj_info != ():
d[attr] = obj_info
except Exception, e:
except Exception as e:
pass
return d
elif isinstance(obj, SudsObject):
@ -207,8 +211,8 @@ class VMwareInventory(object):
host_info[k] = v
try:
host_info['ipAddress'] = host.config.network.vnic[0].spec.ip.ipAddress
except Exception, e:
print >> sys.stderr, e
except Exception as e:
print(e, file=sys.stderr)
host_info = self._flatten_dict(host_info, prefix)
if ('%s_ipAddress' % prefix) in host_info:
host_info['ansible_ssh_host'] = host_info['%s_ipAddress' % prefix]

View File

@ -51,7 +51,7 @@ try:
from azure import WindowsAzureError
from azure.servicemanagement import ServiceManagementService
except ImportError as e:
print "failed=True msg='`azure` library required for this script'"
print("failed=True msg='`azure` library required for this script'")
sys.exit(1)
@ -70,8 +70,8 @@ class AzureInventory(object):
# Cache setting defaults.
# These can be overridden in settings (see `read_settings`).
cache_dir = os.path.expanduser('~')
self.cache_path_cache = '%s/.ansible-azure.cache' % cache_dir
self.cache_path_index = '%s/.ansible-azure.index' % cache_dir
self.cache_path_cache = os.path.join(cache_dir, '.ansible-azure.cache')
self.cache_path_index = os.path.join(cache_dir, '.ansible-azure.index')
self.cache_max_age = 0
# Read settings and parse CLI arguments
@ -103,15 +103,13 @@ class AzureInventory(object):
# Add the `['_meta']['hostvars']` information.
hostvars = {}
if len(data) > 0:
for host in set(reduce(lambda x, y: x + y,
[i for i in data.values()])):
if host is not None:
hostvars[host] = self.get_host(host, jsonify=False)
for host in set([h for hosts in data.values() for h in hosts if h]):
hostvars[host] = self.get_host(host, jsonify=False)
data['_meta'] = {'hostvars': hostvars}
# JSONify the data.
data_to_print = self.json_format_dict(data, pretty=True)
print data_to_print
print(data_to_print)
def get_host(self, hostname, jsonify=True):
"""Return information about the given hostname, based on what
@ -153,9 +151,9 @@ class AzureInventory(object):
# Cache related
if config.has_option('azure', 'cache_path'):
cache_path = os.path.expanduser(config.get('azure', 'cache_path'))
self.cache_path_cache = cache_path + '/ansible-azure.cache'
self.cache_path_index = cache_path + '/ansible-azure.index'
cache_path = os.path.expandvars(os.path.expanduser(config.get('azure', 'cache_path')))
self.cache_path_cache = os.path.join(cache_path, 'ansible-azure.cache')
self.cache_path_index = os.path.join(cache_path, 'ansible-azure.index')
if config.has_option('azure', 'cache_max_age'):
self.cache_max_age = config.getint('azure', 'cache_max_age')
@ -197,9 +195,9 @@ class AzureInventory(object):
for cloud_service in self.sms.list_hosted_services():
self.add_deployments(cloud_service)
except WindowsAzureError as e:
print "Looks like Azure's API is down:"
print
print e
print("Looks like Azure's API is down:")
print("")
print(e)
sys.exit(1)
def add_deployments(self, cloud_service):
@ -209,12 +207,10 @@ class AzureInventory(object):
try:
for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name,embed_detail=True).deployments.deployments:
self.add_deployment(cloud_service, deployment)
#if deployment.deployment_slot == "Production":
# self.add_deployment(cloud_service, deployment)
except WindowsAzureError as e:
print "Looks like Azure's API is down:"
print
print e
print("Looks like Azure's API is down:")
print("")
print(e)
sys.exit(1)
def add_deployment(self, cloud_service, deployment):

View File

@ -448,6 +448,9 @@ AWX_JOB_TEMPLATE_HISTORY = 10
# The directory in which proot will create new temporary directories for its root
AWX_PROOT_BASE_PATH = "/tmp"
# User definable ansible callback plugins
AWX_ANSIBLE_CALLBACK_PLUGINS = ""
# Enable Pendo on the UI, possible values are 'off', 'anonymous', and 'detailed'
PENDO_TRACKING_STATE = "off"

View File

@ -5,7 +5,7 @@
{% block style %}
{{ block.super }}
<link href="{{ STATIC_URL }}img/favicon.ico" rel="shortcut icon" />
<link href="{{ STATIC_URL }}assets/favicon.ico" rel="shortcut icon" />
<style type="text/css">
html body {
background: #ddd;
@ -183,7 +183,7 @@ html body .dropdown-submenu:hover>a {
{% endblock %}
{% block branding %}
<a class="brand" href="/api/"><img class="logo" src="{{ STATIC_URL }}img/tower_console_logo.png">{% block branding_title %}{% trans 'REST API' %}{% endblock %}</a>
<a class="brand" href="/api/"><img class="logo" src="{{ STATIC_URL }}assets/tower_console_logo.png">{% block branding_title %}{% trans 'REST API' %}{% endblock %}</a>
{% endblock %}
{% block userlinks %}
@ -196,7 +196,7 @@ html body .dropdown-submenu:hover>a {
{% block footer %}
<div id="footer">
<a href="http://www.ansible.com" target="_blank"><img class="towerlogo" src="{{ STATIC_URL }}img/tower_console_bug.png" /></a><br/>
<a href="http://www.ansible.com" target="_blank"><img class="towerlogo" src="{{ STATIC_URL }}assets/tower_console_bug.png" /></a><br/>
Copyright &copy; 2015 <a href="http://www.ansible.com" target="_blank">Ansible, Inc.</a> All rights reserved.
</div>
{% endblock %}

View File

@ -187,6 +187,9 @@ var tower = angular.module('Tower', [
.constant('AngularScheduler.useTimezone', true)
.constant('AngularScheduler.showUTCField', true)
.constant('$timezones.definitions.location', urlPrefix + 'lib/angular-tz-extensions/tz/data')
.config(['$pendolyticsProvider', function($pendolyticsProvider) {
$pendolyticsProvider.doNotAutoStart();
}])
.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
@ -989,7 +992,7 @@ var tower = angular.module('Tower', [
if (next.templateUrl !== (urlPrefix + 'login/loginBackDrop.partial.html')) {
$location.path('/login');
}
} else if ($rootScope.sessionTimer.isExpired()) {
} else if ($rootScope && $rootScope.sessionTimer && $rootScope.sessionTimer.isExpired()) {
// gets here on timeout
if (next.templateUrl !== (urlPrefix + 'login/loginBackDrop.partial.html')) {
$rootScope.sessionTimer.expireSession('idle');
@ -1019,7 +1022,7 @@ var tower = angular.module('Tower', [
// If browser refresh, set the user_is_superuser value
$rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser');
// when the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket)
if($location.$$url !== '/login'){
if(!_.contains($location.$$url, '/login')){
$rootScope.sessionTimer = Timer.init();
$rootScope.$emit('OpenSocket');
pendoService.issuePendoIdentity();

View File

@ -27,6 +27,14 @@ export function JobDetailController ($location, $rootScope, $filter, $scope, $co
scope.plays = [];
scope.$watch('job_status', function(job_status) {
if (job_status && job_status.explanation && job_status.explanation.split(":")[0] === "Previous Task Failed") {
var taskObj = JSON.parse(job_status.explanation.substring(job_status.explanation.split(":")[0].length + 1));
job_status.explanation = job_status.explanation.split(":")[0] + ". ";
job_status.explanation += "<code>" + taskObj.task_type + "-" + taskObj.task_id + " failed for " + taskObj.task_name + "</code>"
}
}, true);
scope.$watch('plays', function(plays) {
for (var play in plays) {
if (plays[play].elapsed) {

View File

@ -105,16 +105,14 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi
api_complete = true;
$('#pre-container-content').html(data.content);
current_range = data.range;
loaded_sections.push({
start: (data.range.start < 0) ? 0 : data.range.start,
end: data.range.end
});
//console.log('loaded start: ' + data.range.start + ' end: ' + data.range.end);
//console.log(data.content);
if (data.content !== "Waiting for results...") {
loaded_sections.push({
start: (data.range.start < 0) ? 0 : data.range.start,
end: data.range.end
});
}
$('#pre-container').scrollTop($('#pre-container').prop("scrollHeight"));
//console.log($('#pre-container-content').prop("scrollHeight"));
}
else {
api_complete = true;
@ -158,6 +156,8 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi
}
};
$(".StandardOut").height($("body").height() - 60);
// Note: could be ad_hoc_commands or jobs
var jobType = $location.path().replace(/^\//, '').split('/')[0];
Rest.setUrl(GetBasePath(jobType) + job_id + '/');

View File

@ -46,8 +46,10 @@ export default
sourceModel: 'credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
addRequired: false,
editRequired: false
awRequiredWhen: {
variable: "cloudCredentialRequired",
init: "false"
}
},
source_regions: {
label: 'Regions',

View File

@ -47,6 +47,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
});
}
}
scope.cloudCredentialRequired = false;
scope.$emit('sourceTypeOptionsReady');
})
.error(function (data, status) {
@ -243,6 +244,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
form = params.form,
kind, url, callback, invUrl;
scope.cloudCredentialRequired = false;
if (!Empty(scope.source)) {
if (scope.source.value === 'file') {
scope.sourcePathRequired = true;
@ -283,7 +286,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
}
if(scope.source.value==="custom"){
// need to filter the possible custom scripts by the organization defined for the current inventory
invUrl = GetBasePath('inventory_scripts') + '?organization='+scope.$parent.inventory.organization;
invUrl = GetBasePath('inventory_scripts');
LookUpInit({
url: invUrl,
scope: scope,
@ -313,6 +316,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
kind = 'aws';
} else {
kind = scope.source.value;
scope.cloudCredentialRequired = true;
}
url = GetBasePath('credentials') + '?cloud=true&kind=' + kind;
LookUpInit({
@ -1030,9 +1034,17 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
else if(fld === "inventory_script"){
// the API stores it as 'source_script', we call it inventory_script
data.summary_fields['inventory_script'] = data.summary_fields.source_script;
}
else if (data[fld] !== undefined) {
sources_scope.inventory_script = data.source_script;
master.inventory_script = sources_scope.inventory_script;
} else if (fld === "source_regions") {
if (data[fld] === "") {
sources_scope[fld] = data[fld];
master[fld] = sources_scope[fld];
} else {
sources_scope[fld] = data[fld].split(",");
master[fld] = sources_scope[fld];
}
} else if (data[fld] !== undefined) {
sources_scope[fld] = data[fld];
master[fld] = sources_scope[fld];
}
@ -1392,7 +1404,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
if (mode === 'edit' || (mode === 'add' && group_created)) {
Rest.put(data)
.success(function () {
if (properties_scope.variables) {
if (properties_scope.variables && properties_scope.variables !== "---") {
modal_scope.$emit('updateVariables', json_data, properties_scope.variable_url);
}
else {

View File

@ -99,7 +99,8 @@ export default
promise.then(function (response) {
config = response.license_info;
config.analytics_status = response.analytics_status;
if(config.analytics_status !== 'off'){
if(config.analytics_status === 'detailed' || config.analytics_status === 'anonymous'){
$pendolytics.bootstrap();
deferred.resolve(config);
}
else {
@ -114,7 +115,8 @@ export default
deferred.reject('Could not resolve pendo config.');
});
}
else if(config.analytics_status !== 'off'){
else if(config.analytics_status === 'detailed' || config.analytics_status === 'anonymous'){
$pendolytics.bootstrap();
deferred.resolve(config);
}
else {
@ -128,6 +130,8 @@ export default
this.getConfig().then(function(config){
var options = that.setPendoOptions(config);
that.setRole(options).then(function(options){
$log.debug('Pendo status is '+ config.analytics_status + '. Object below:');
$log.debug(options);
$pendolytics.identify(options);
}, function(reason){
// reject function for setRole

View File

@ -23,23 +23,35 @@
*/
export default
['$rootScope', '$cookieStore', 'transitionTo', 'CreateDialog', 'Authorization',
function ($rootScope, $cookieStore, transitionTo, CreateDialog, Authorization) {
'Store', '$interval',
function ($rootScope, $cookieStore, transitionTo, CreateDialog, Authorization,
Store, $interval) {
return {
sessionTime: null,
timeout: null,
getSessionTime: function () {
return (this.sessionTime) ? this.sessionTime : $cookieStore.get('sessionTime');
if(Store('sessionTime_'+$rootScope.current_user.id)){
return Store('sessionTime_'+$rootScope.current_user.id);
}
else {
return 0;
}
},
isExpired: function () {
isExpired: function (increase) {
var stime = this.getSessionTime(),
now = new Date().getTime();
if ((stime - now) <= 0) {
//expired
return true;
} else {
}
else if(increase){
return false;
}
else{
// not expired. move timer forward.
this.moveForward();
return false;
@ -52,7 +64,7 @@ export default
diff = stime-now;
if(diff < 61){
return true;
return diff;
}
else {
return false;
@ -76,76 +88,74 @@ export default
moveForward: function () {
var tm, t;
tm = ($AnsibleConfig) ? $AnsibleConfig.session_timeout : 1800;
tm = ($AnsibleConfig.session_timeout) ? $AnsibleConfig.session_timeout : 1800;
t = new Date().getTime() + (tm * 1000);
this.sessionTime = t;
$cookieStore.put('sessionTime', t);
Store('sessionTime_'+$rootScope.current_user.id, t);
$rootScope.sessionExpired = false;
$cookieStore.put('sessionExpired', false);
this.startTimers();
},
startTimers: function() {
var that = this,
tm = ($AnsibleConfig) ? $AnsibleConfig.session_timeout : 1800,
t = tm - 60;
startTimers: function(){
var that = this;
this.clearTimers();
// make a timeout that will go off in 30 mins to log them out
// unless they extend their time
$rootScope.endTimer = setTimeout(function(){
that.expireSession('idle');
}, tm * 1000);
// notify the user a minute before the end of their session that
// their session is about to expire
if($rootScope.idleTimer){
clearTimeout($rootScope.idleTimer);
}
$rootScope.idleTimer = setTimeout(function() {
if(that.isIdle() === true){
var buttons = [{
"label": "Continue",
"onClick": function() {
// make a rest call here to force the API to
// move the session time forward
Authorization.getUser();
that.moveForward();
$(this).dialog('close');
},
"class": "btn btn-primary",
"id": "idle-modal-button"
}];
if ($rootScope.removeIdleDialogReady) {
$rootScope.removeIdleDialogReady();
$rootScope.expireTimer = $interval(function() {
var idle = that.isIdle();
if(idle !== false){
if($('#idle-modal').is(':visible')){
$('#remaining_seconds').html(Math.round(idle));
}
$rootScope.removeIdleDialogReady = $rootScope.$on('IdleDialogReady', function() {
$('#idle-modal').show();
$('#idle-modal').dialog('open');
});
CreateDialog({
id: 'idle-modal' ,
title: "Idle Session",
scope: $rootScope,
buttons: buttons,
width: 470,
height: 240,
minWidth: 200,
callback: 'IdleDialogReady'
});
}
}, t * 1000);
else {
var buttons = [{
"label": "Continue",
"onClick": function() {
// make a rest call here to force the API to
// move the session time forward
Authorization.getUser();
that.moveForward();
$(this).dialog('close');
},
"class": "btn btn-primary",
"id": "idle-modal-button"
}];
if ($rootScope.removeIdleDialogReady) {
$rootScope.removeIdleDialogReady();
}
$rootScope.removeIdleDialogReady = $rootScope.$on('IdleDialogReady', function() {
$('#idle-modal').show();
$('#idle-modal').dialog('open');
});
CreateDialog({
id: 'idle-modal' ,
title: "Idle Session",
scope: $rootScope,
buttons: buttons,
width: 470,
height: 240,
minWidth: 200,
callback: 'IdleDialogReady'
});
}
}
else if(!idle){
if($('#idle-modal').is(':visible')){
$('#idle-modal').dialog('close');
}
}
if (that.isExpired(true)) {
if($('#idle-modal').dialog('isOpen')){
$('#idle-modal').dialog('close');
}
that.expireSession('idle');
}
}, 1000);
},
clearTimers: function(){
clearTimeout($rootScope.idleTimer);
clearTimeout($rootScope.endTimer);
$interval.cancel($rootScope.expireTimer);
},
init: function () {

View File

@ -165,6 +165,7 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', '$l
Authorization.getUser()
.success(function (data) {
Authorization.setUserInfo(data);
$rootScope.sessionTimer = Timer.init();
$rootScope.user_is_superuser = data.results[0].is_superuser;
scope.$emit('AuthorizationGetLicense');
})
@ -187,7 +188,6 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', '$l
.then(function (data) {
$('#login-modal').modal('hide');
Authorization.setToken(data.data.token, data.data.expires);
$rootScope.sessionTimer = Timer.init();
scope.$emit('AuthorizationGetUser');
},
function (data) {

View File

@ -1,47 +1,42 @@
<div class="tab-pane" id="jobs-stdout">
<div ng-cloak id="htmlTemplate">
<div class="row">
<div id="breadcrumb-container" class="col-md-6" style="position: relative;">
<ul class="ansible-breadcrumb" id="breadcrumb-list">
<li><a href="/#/jobs">Jobs</a></li>
<li><a href="/#/jobs/{{ job.id }}">{{ job.id }} - {{ job.name }}</a></li>
<li class="active"><a href="">Standard Out</a></li>
</ul>
</div>
<div id="home-list-actions" class="list-actions pull-right col-md-6">
<button type="button" class="btn btn-xs btn-primary ng-hide" ng-click="refresh()" id="refresh_btn" aw-tool-tip="Refresh the page" data-placement="top" ng-show="socketStatus == 'error'" data-original-title="" title=""><i class="fa fa-refresh fa-lg"></i> </button></div>
<div class="StandardOut">
<div class="StandardOut-heading">
<div class="row StandardOut-breadcrumbs">
<div id="breadcrumb-container" class="col-md-6" style="position: relative;">
<ul class="ansible-breadcrumb" id="breadcrumb-list">
<li><a href="/#/jobs">Jobs</a></li>
<li><a href="/#/jobs/{{ job.id }}">{{ job.id }} - {{ job.name }}</a></li>
<li class="active"><a href="">Standard Out</a></li>
</ul>
</div>
<div id="home-list-actions" class="list-actions pull-right col-md-6">
<button type="button" class="btn btn-xs btn-primary ng-hide" ng-click="refresh()" id="refresh_btn" aw-tool-tip="Refresh the page" data-placement="top" ng-show="socketStatus == 'error'" data-original-title="" title=""><i class="fa fa-refresh fa-lg"></i> </button></div>
</div>
<div class="row StandardOut-form">
<div class="col-md-12">
<div id="job-status"><label>Job Status</label> <i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="job-status"><label>Job Status</label> <i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}</div>
</div>
</div>
<div class="row">
<div class="panel panel-default job-stdout-panel">
<div class="panel-heading">
<div class="panel panel-default job-stdout-panel StandardOut-panel">
<div class="panel-heading StandardOut-panelHeading">
<h3 class="panel-title">Standard Output
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}" class="btn btn-primary btn-xs DownloadStandardOut DownloadStandardOut--onStandardOutPage" id="download-stdout-button" type="button" aw-tool-tip="Download standard out as a .txt file" data-placement="top" ng-show="job.status === 'cancelled' || job.status === 'failed' || job.status === 'error' || job.status === 'successful'">
<i class="fa fa-download DownloadStandardOut-icon DownloadStandardOut-icon--withText"></i>Download
</a>
</div>
<div class="panel-body stdout-panel-body">
<div class="row">
<div class="col-md-12">
<div class="scroll-spinner" id="stdoutMoreRowsTop">
<i class="fa fa-cog fa-spin"></i>
</div>
<div id="pre-container" class="body_background
body_foreground pre mono-space"
lr-infinite-scroll="stdOutScrollToTop"
scroll-threshold="300" data-direction="up" time-threshold="500">
<div id="pre-container-content"></div>
</div>
</div>
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
<i class="fa fa-cog fa-spin"></i>
</div>
<div class="panel-body stdout-panel-body StandardOut-panelBody">
<div id="pre-container" class="body_background
body_foreground pre mono-space StandardOut-preContainer"
lr-infinite-scroll="stdOutScrollToTop"
scroll-threshold="300" data-direction="up" time-threshold="500">
<div id="pre-container-content" class="StandardOut-preContent"></div>
</div>
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
<i class="fa fa-cog fa-spin"></i>
</div>
</div>
</div>

View File

@ -1,187 +1,183 @@
<div class="tab-pane" id="jobs-stdout">
<div ng-cloak id="htmlTemplate">
<div class="row">
<div id="breadcrumb-container" class="col-md-6"
style="position: relative;">
<ul class="ansible-breadcrumb" id="breadcrumb-list">
<li><a href="/#/jobs">Jobs</a></li>
<li class="active">
<a href="/#/ad_hoc_commands/{{ job.id }}">
{{ job.id }} - {{ job.name }}
</a>
</li>
</ul>
<div class="StandardOut">
<div class="row StandardOut-heading">
<div class="row StandardOut-breadcrumbs">
<div id="breadcrumb-container" class="col-md-6"
style="position: relative;">
<ul class="ansible-breadcrumb" id="breadcrumb-list">
<li><a href="/#/jobs">Jobs</a></li>
<li class="active">
<a href="/#/ad_hoc_commands/{{ job.id }}">
{{ job.id }} - {{ job.name }}
</a>
</li>
</ul>
</div>
<div id="home-list-actions"
class="list-actions pull-right col-md-6">
<button type="button" class="btn btn-xs btn-primary ng-hide"
ng-click="refresh()" id="refresh_btn"
aw-tool-tip="Refresh the page"
data-placement="top" ng-show="socketStatus == 'error'"
data-original-title="" title="">
<i class="fa fa-refresh fa-lg"></i>
</button>
</div>
</div>
<div id="home-list-actions"
class="list-actions pull-right col-md-6">
<button type="button" class="btn btn-xs btn-primary ng-hide"
ng-click="refresh()" id="refresh_btn"
aw-tool-tip="Refresh the page"
data-placement="top" ng-show="socketStatus == 'error'"
data-original-title="" title="">
<i class="fa fa-refresh fa-lg"></i>
</button>
<div class="StandardOut-form form-horizontal StandardOutDetails"
role="form" id="job-status-form">
<div class="form-group StandardOutDetails-detailRow">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Status</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="job.started">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Timing</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<div ng-show="job.started" id="started-time">
Started &nbsp;{{ job.started | date:'MM/dd/yy HH:mm:ss' }}
</div>
<div ng-show="job.finished" id="finished-time">
Finished &nbsp;{{ job.finished | date:'MM/dd/yy HH:mm:ss' }}
</div>
<div ng-show="job.finished" id="elapsed-time">
Elapsed &nbsp;{{ job.elapsed }} seconds
</div>
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="job.module_name">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Module Name</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ job.module_name }}
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="job.module_args">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Module Args</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent mono-space">{{ job.module_args }}
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="inventory_name">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Inventory</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<a href="{{ inventory_url }}"
aw-tool-tip="The inventory this command ran on."
data-placement="top">{{ inventory_name }}</a>
</div>
</div>
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="credential_name">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Credential</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<a href="{{ credential_url }}"
aw-tool-tip="The credential used to run this command."
data-placement="top">{{ credential_name }}</a>
</div>
</div>
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="created_by">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailContent">Launched By</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-9
StandardOutDetails-detailContent">
<a href="/#/users/{{ created_by.id }}"
aw-tool-tip="The user who ran this command."
data-placement="top">{{ created_by.username }}</a>
</div>
</div>
<!-- since zero is a falsy value, you need ng-show such that
the number is >= 0 -->
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="forks >= 0">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Forks</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ forks }}</div>
</div>
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="limit">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Limit</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ limit }}</div>
</div>
<!-- since zero is a falsy value, you need ng-show such that
the number is >= 0 -->
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="verbosity >= 0">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Verbosity</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ verbosity }}</div>
</div>
<div class="form-group StandardOutDetails-closedToggle">
<a class="col-sm-12 StandardOutDetails-closedToggleLink"
ng-show="isClosed" href="javascript:;"
ng-click="toggleClosedStatus()"> more <i class="fa fa-angle-down"></i>
</a>
<a class="col-sm-12 StandardOutDetails-closedToggleLink"
ng-show="!isClosed" href="javascript:;"
ng-click="toggleClosedStatus()"> less <i class="fa fa-angle-up"></i>
</a>
</div>
</div>
</div>
<div class="form-horizontal StandardOutDetails"
role="form" id="job-status-form">
<div class="form-group StandardOutDetails-detailRow">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Status</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="job.started">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Timing</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<div ng-show="job.started" id="started-time">
Started &nbsp;{{ job.started | date:'MM/dd/yy HH:mm:ss' }}
</div>
<div ng-show="job.finished" id="finished-time">
Finished &nbsp;{{ job.finished | date:'MM/dd/yy HH:mm:ss' }}
</div>
<div ng-show="job.finished" id="elapsed-time">
Elapsed &nbsp;{{ job.elapsed }} seconds
</div>
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="job.module_name">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Module Name</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ job.module_name }}
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="job.module_args">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Module Args</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent mono-space">{{ job.module_args }}
</div>
</div>
<div <div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="inventory_name">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Inventory</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<a href="{{ inventory_url }}"
aw-tool-tip="The inventory this command ran on."
data-placement="top">{{ inventory_name }}</a>
</div>
</div>
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="credential_name">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Credential</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">
<a href="{{ credential_url }}"
aw-tool-tip="The credential used to run this command."
data-placement="top">{{ credential_name }}</a>
</div>
</div>
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="created_by">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailContent">Launched By</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-9
StandardOutDetails-detailContent">
<a href="/#/users/{{ created_by.id }}"
aw-tool-tip="The user who ran this command."
data-placement="top">{{ created_by.username }}</a>
</div>
</div>
<!-- since zero is a falsy value, you need ng-show such that
the number is >= 0 -->
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="forks >= 0">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Forks</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ forks }}</div>
</div>
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="limit">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Limit</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ limit }}</div>
</div>
<!-- since zero is a falsy value, you need ng-show such that
the number is >= 0 -->
<div class="form-group StandardOutDetails-detailRow
StandardOutDetails-detailRow--closable"
ng-show="verbosity >= 0">
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
StandardOutDetails-detailLabel">Verbosity</label>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
StandardOutDetails-detailContent">{{ verbosity }}</div>
</div>
<div class="form-group StandardOutDetails-closedToggle">
<a class="col-sm-12 StandardOutDetails-closedToggleLink"
ng-show="isClosed" href="javascript:;"
ng-click="toggleClosedStatus()"> more <i class="fa fa-angle-down"></i>
</a>
<a class="col-sm-12 StandardOutDetails-closedToggleLink"
ng-show="!isClosed" href="javascript:;"
ng-click="toggleClosedStatus()"> less <i class="fa fa-angle-up"></i>
</a>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel panel-default StandardOut-panel">
<div class="panel-heading StandardOut-panelHeading">
<h3 class="panel-title">Standard Output
<a ng-href="/api/v1/ad_hoc_commands/{{ job.id }}/stdout?format=txt_download&token={{ token }}" class="btn btn-primary btn-xs DownloadStandardOut DownloadStandardOut--onStandardOutPage" id="download-stdout-button" type="button" aw-tool-tip="Download standard out as a .txt file" data-placement="top" ng-show="job.status === 'cancelled' || job.status === 'failed' || job.status === 'error' || job.status === 'successful'"><i class="fa fa-download DownloadStandardOut-icon DownloadStandardOut-icon--withText"></i>Download</a>
</h3>
</div>
<div class="panel-body stdout-panel-body">
<div class="row">
<div class="col-md-12">
<div class="scroll-spinner" id="stdoutMoreRowsTop">
<i class="fa fa-cog fa-spin"></i>
</div>
<div id="pre-container" class="body_background
body_foreground pre mono-space"
lr-infinite-scroll="stdOutScrollToTop"
scroll-threshold="300" data-direction="up" time-threshold="500">
<div id="pre-container-content"></div>
</div>
</div>
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
<i class="fa fa-cog fa-spin"></i>
</div>
<div class="panel-body stdout-panel-body StandardOut-panelBody">
<div id="pre-container" class="body_background
body_foreground pre mono-space StandardOut-preContainer"
lr-infinite-scroll="stdOutScrollToTop"
scroll-threshold="300" data-direction="up" time-threshold="500">
<div id="pre-container-content" class="StandardOut-preContent"></div>
</div>
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
<i class="fa fa-cog fa-spin"></i>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -21,7 +21,7 @@
return config;
},
responseError: function(rejection){
if( !_.isEmpty(rejection.data.detail) && rejection.data.detail === "Maximum per-user sessions reached"){
if( rejection.data && !_.isEmpty(rejection.data.detail) && rejection.data.detail === "Maximum per-user sessions reached"){
$rootScope.sessionTimer.expireSession('session_limit');
return $q.reject(rejection);
}

View File

@ -203,11 +203,11 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
$rootScope.sessionTimer.expireSession('idle');
}
$location.url('/login');
} else if (data.non_field_errors) {
} else if (data && data.non_field_errors) {
Alert('Error!', data.non_field_errors);
} else if (data.detail) {
} else if (data && data.detail) {
Alert(defaultMsg.hdr, defaultMsg.msg + ' ' + data.detail);
} else if (data.__all__) {
} else if (data && data.__all__) {
if (typeof data.__all__ === 'object' && Array.isArray(data.__all__)) {
Alert('Error!', data.__all__[0]);
}

View File

@ -0,0 +1,47 @@
/** @define StandardOut */
.StandardOut {
height: 100%;
display: flex;
flex-direction: column;
}
.StandardOut-header {
flex: initial;
}
.StandardOut-breadcrumbs {
padding-left: 15px;
}
.StandardOut-form {
padding-left: 15px;
}
.StandardOut-panel {
flex: 1 0 0;
display: flex;
flex-direction: column;
margin-bottom: -41px;
}
.StandardOut-panelHeading {
flex: initial;
}
.StandardOut-panelBody {
flex: 1 0 auto;
padding: 0;
position: relative;
}
.StandardOut-preContainer {
position: absolute;
height: 100%;
padding-left: 15px;
padding-right: 15px;
}
.StandardOut-preContent {
position: absolute;
}

View File

@ -5,6 +5,12 @@ export default
return _.pluck(nonEmptyResults, 'versions[0]');
}
// if the version that will be displayed on the left is before the
// version that will be displayed on the right, flip them
if (nonEmptyResults[0].versions[0].timestamp > nonEmptyResults[1].versions[0].timestamp) {
nonEmptyResults = nonEmptyResults.reverse();
}
var firstTimestamp = nonEmptyResults[0].versions[0].timestamp;
var hostIdsWithDupes =

View File

@ -4,8 +4,10 @@ function resolveEmptyVersions(service, _, candidate, previousResult) {
candidate = _.merge({}, candidate);
if (_.isEmpty(candidate.versions)) {
var originalStartDate = candidate.dateRange.from.clone();
// theoretically, returning no versions, but also returning only a single version for _a particular date_ acts as an empty version problem. If there is only one version returned, you'll need to check previous dates as well.
if (_.isEmpty(candidate.versions) || candidate.versions.length === 1) {
// this marks the end of the date put in the datepicker. this needs to be the end, rather than the beginning of the date, because you may have returned one valid version on the date the datepicker had in it.
var originalStartDate = candidate.dateRange.to.clone();
if (!_.isUndefined(previousResult)) {
candidate.dateRange.from = moment(previousResult.versions[0].timestamp);

View File

@ -15,7 +15,8 @@
<span class="u-unbold">on</span>
</span>
<span class="FactDataTableHeading-host" ng-if="!leftDataNoScans">{{leftHostname}}</span>
<span class="FactDataTableHeading-date">{{leftScanDate|longDate}}</span>
<span class="FactDataTableHeading-date" ng-if="!singleResultView">{{leftScanDate|longDate}}</span>
<span class="FactDataTableHeading-date" ng-if="singleResultView">{{rightScanDate|longDate}}</span>
</h3>
<h3 class="FactDataTable-column FactDataTableHeading" ng-if="!singleResultView">
<span ng-if="rightDataNoScans">No scans for {{rightHostname}} on</span>

View File

@ -91,7 +91,7 @@ function controller($rootScope,
if (_.isEmpty(data[0]) && _.isEmpty(data[1])) {
return _.reject({
name: 'NoScanData',
message: 'There was insufficient scan data for both of the dates you selected. Please try selecting a different date or module.',
message: 'There was insufficient scan data for the date you selected. Please try selecting a different date or module.',
dateValues:
{ leftDate: $scope.leftDate.clone(),
rightDate: $scope.rightDate.clone()

View File

@ -58,7 +58,7 @@
<!-- Password Dialog -->
<div id="password-modal" style="display: none;"></div>
<div id="idle-modal" style="display:none">Your session will expire in 1 minute, would you like to continue?</div>
<div id="idle-modal" style="display:none">Your session will expire in <span id="remaining_seconds"></span> seconds, would you like to continue?</div>
<!-- Generic Form dialog -->
<div id="form-modal" class="modal fade">

View File

@ -18,4 +18,4 @@ exclude=.tox,awx/lib/site-packages,awx/plugins/inventory/ec2.py,awx/plugins/inve
[flake8]
ignore=E201,E203,E221,E225,E231,E241,E251,E261,E265,E302,E303,E501,W291,W391,W293,E731
exclude=.tox,awx/lib/site-packages,awx/plugins/inventory/ec2.py,awx/plugins/inventory/gce.py,awx/plugins/inventory/vmware.py,awx/plugins/inventory/windows_azure.py,awx/plugins/inventory/openstack.py,awx/ui,awx/api/urls.py,awx/main/migrations,awx/main/tests/data,node_modules/,awx/projects/,tools/docker
exclude=.tox,awx/lib/site-packages,awx/plugins/inventory/ec2.py,awx/plugins/inventory/gce.py,awx/plugins/inventory/vmware.py,awx/plugins/inventory/windows_azure.py,awx/plugins/inventory/openstack.py,awx/plugins/inventory/rax.py,awx/ui,awx/api/urls.py,awx/main/migrations,awx/main/tests/data,node_modules/,awx/projects/,tools/docker

View File

@ -20,4 +20,11 @@ redis:
mongo:
image: mongo:3.0
# ports:
# - 27017:27017
# - 27017:27017
dockerui:
image: dockerui/dockerui
ports:
- "9000:9000"
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock

View File

@ -31,26 +31,35 @@
with_items: ami_info.results
- name: Copy AMI to desired regions
ec2_ami_copy:
source_region: '{{ src_region }}'
region: '{{ item }}'
source_image_id: '{{ ami_info.results[0].ami_id }}'
name: '{{ ami_info.results[0].name }}'
description: '{{ ami_info.results[0].description }}'
tags: '{{ ami_info.results[0].tags }}'
wait: true
command: 'ec2-copy-image --source-region {{ src_region }} --source-ami-id {{ src_ami_id }} --region {{ item }} --client-token {{ src_ami_id }}-{{ item }}'
with_items: dst_regions
register: ami_copy
- name: Make image publicly available
command: 'ec2-modify-image-attribute {{ item.image_id }} --launch-permission -a all'
with_items: ami_copy.results
# ec2_ami_copy:
# source_region: '{{ src_region }}'
# region: '{{ item }}'
# source_image_id: '{{ ami_info.results[0].ami_id }}'
# name: '{{ ami_info.results[0].name }}'
# description: '{{ ami_info.results[0].description }}'
# tags: '{{ ami_info.results[0].tags }}'
# wait: true
# with_items: dst_regions
# register: ami_copy
- debug:
var: ami_copy.results
- name: Wait for AMI's to become available
command: 'ec2-describe-images --region {{ item.item }} --filter state=available {{ item.stdout[-12:] }}'
until: ami_available.stdout.find("available") != -1
retries: 10
delay: 120
with_items: ami_copy.results
register: ami_available
- name: Make image publicly available
command: 'ec2-modify-image-attribute --region {{ item.item }} {{ item.stdout[-12:] }} --launch-permission -a all'
with_items: ami_copy.results
- name: Display AMI launch URL
debug:
msg: 'https://console.aws.amazon.com/ec2/home?region={{ item.item }}#launchAmi={{ item.image_id }}'
msg: 'https://console.aws.amazon.com/ec2/home?region={{ item.item }}#launchAmi={{ item.stdout[-12:] }}'
with_items: ami_copy.results
when: ami_copy is defined