1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-28 10:55:30 +03:00

Merge branch 'release_3.1.1' into stable

* release_3.1.1:
  Updating changelog for 3.1.1 release
  Fix an issue where notifications might not always be triggered
  give a default other than None
  Respect the user provided rabbitmq vhost value
  Changes to improve the AMI publish process
  Add unsupported primary pre-flight check
  Removed console
  Fixed bug where typing in scm credential when adding/editing project wouldn't work
  Encode default search param
  update asgi_amqp version
  remove uneeded post commit wrapper
  all job status change websockets events should happen after the job hits the database
  Revert "emit job status over socket after database commit"
  emit job status over socket after database commit
  requeue websocket messages that don't (yet) have an established user
  Force setting the password of the rabbitmq user
  rabbit_mq_password required
  host event stdout changes
  Bump version for 3.1.1 release
This commit is contained in:
Matthew Jones 2017-03-07 10:29:47 -05:00
commit 2b2c947ac5
17 changed files with 60 additions and 17 deletions

View File

@ -5,7 +5,7 @@ import os
import sys
import warnings
__version__ = '3.1.0'
__version__ = '3.1.1'
__all__ = ['__version__']

View File

@ -322,5 +322,5 @@ register(
help_text=_('Useful to uniquely identify Tower instances.'),
category=_('Logging'),
category_slug='logging',
default=None,
default='',
)

View File

@ -2,10 +2,11 @@ import json
import logging
import urllib
from channels import Group
from channels import Group, channel_layers
from channels.sessions import channel_session
from channels.handler import AsgiRequest
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.auth.models import User
@ -49,11 +50,19 @@ def ws_disconnect(message):
@channel_session
def ws_receive(message):
from awx.main.access import consumer_access
channel_layer_settings = channel_layers.configs[message.channel_layer.alias]
max_retries = channel_layer_settings.get('RECEIVE_MAX_RETRY', settings.CHANNEL_LAYER_RECEIVE_MAX_RETRY)
user_id = message.channel_session.get('user_id', None)
if user_id is None:
logger.error("No valid user found for websocket.")
retries = message.content.get('connect_retries', 0) + 1
message.content['connect_retries'] = retries
message.reply_channel.send({"text": json.dumps({"error": "no valid user"})})
retries_left = max_retries - retries
if retries_left > 0:
message.channel_layer.send(message.channel.name, message.content)
else:
logger.error("No valid user found for websocket.")
return None
user = User.objects.get(pk=user_id)

View File

@ -880,7 +880,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
workflow_node_id=self.workflow_node_id))
return websocket_data
def websocket_emit_status(self, status):
def _websocket_emit_status(self, status):
status_data = dict(unified_job_id=self.id, status=status)
status_data.update(self.websocket_emit_data())
status_data['group_name'] = 'jobs'
@ -890,6 +890,9 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
status_data['group_name'] = "workflow_events"
emit_channel_notification('workflow_events-' + str(self.workflow_job_id), status_data)
def websocket_emit_status(self, status):
connection.on_commit(lambda: self._websocket_emit_status(status))
def notification_data(self):
return dict(id=self.id,
name=self.name,

View File

@ -382,7 +382,7 @@ class TaskManager():
))
task_obj.save()
_send_notification_templates(task_obj, 'failed')
connection.on_commit(lambda: task_obj.websocket_emit_status('failed'))
task_obj.websocket_emit_status('failed')
logger.error("Task %s appears orphaned... marking as failed" % task)

View File

@ -218,7 +218,11 @@ def _send_notification_templates(instance, status_str):
raise ValueError(_("status_str must be either succeeded or failed"))
notification_templates = instance.get_notification_templates()
if notification_templates:
all_notification_templates = set(notification_templates.get('success', []) + notification_templates.get('any', []))
if status_str == 'succeeded':
notification_template_type = 'success'
else:
notification_template_type = 'error'
all_notification_templates = set(notification_templates.get(notification_template_type, []) + notification_templates.get('any', []))
if len(all_notification_templates):
try:
(notification_subject, notification_body) = getattr(instance, 'build_notification_%s_message' % status_str)()

View File

@ -866,6 +866,11 @@ TOWER_SETTINGS_MANIFEST = {}
LOG_AGGREGATOR_ENABLED = False
# The number of retry attempts for websocket session establishment
# If you're encountering issues establishing websockets in clustered Tower,
# raising this value can help
CHANNEL_LAYER_RECEIVE_MAX_RETRY = 10
# Logging configuration.
LOGGING = {
'version': 1,

View File

@ -137,6 +137,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
},
ngShow: "scm_type && scm_type.value !== 'manual'",
sourceModel: 'credential',
awLookupType: 'scm_credential',
sourceField: 'name',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)',
subForm: 'sourceSubForm'

View File

@ -33,7 +33,7 @@
</div>
<div class="HostEvent-field">
<span class="HostEvent-field--label">MODULE</span>
<span class="HostEvent-field--content">{{module_name}}</span>
<span class="HostEvent-field--content HostEvent-field--monospaceContent">{{module_name}}</span>
</div>
</div>

View File

@ -126,6 +126,9 @@
max-width: 13em;
flex: 0 1 13em;
}
.HostEvent-field--monospaceContent{
font-family: monospace;
}
.HostEvent-details--left, .HostEvent-details--right{
flex: 1 1 47%;
}
@ -175,7 +178,7 @@
border-right: 1px solid #ccc;
border-bottom-left-radius: 5px;
color: #999;
font-family: monospace;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
position: fixed;
padding: 4px 3px 0 5px;
text-align: right;

View File

@ -52,9 +52,9 @@
// grab standard out & standard error if present from the host
// event's "res" object, for things like Ansible modules
try{
$scope.module_name = hostEvent.event_data.res.invocation.module_name || hostEvent.event_data.task_action || "No result found";
$scope.stdout = hostEvent.event_data.res.stdout;
$scope.stderr = hostEvent.event_data.res.stderr;
$scope.module_name = hostEvent.event_data.task_action || "No result found";
$scope.stdout = hostEvent.event_data.res.stdout ? hostEvent.event_data.res.stdout : hostEvent.event_data.res.stdout === "" ? " " : undefined;
$scope.stderr = hostEvent.event_data.res.stderr ? hostEvent.event_data.res.stderr : hostEvent.event_data.res.stderr === "" ? " " : undefined;
$scope.json = hostEvent.event_data.res;
}
catch(err){

View File

@ -482,6 +482,7 @@ function(ConfigurationUtils, i18n, $rootScope) {
autopopulateLookup,
modelKey = attrs.ngModel,
modelName = attrs.source,
lookupType = attrs.awlookuptype,
watcher = attrs.awRequiredWhen || undefined,
watchBasePath;
@ -516,10 +517,14 @@ function(ConfigurationUtils, i18n, $rootScope) {
}
else {
basePath = GetBasePath(elm.attr('data-basePath')) || elm.attr('data-basePath');
switch(modelName) {
let switchType = lookupType ? lookupType : modelName;
switch(switchType) {
case 'credential':
query = '?kind=ssh&role_level=use_role';
break;
case 'scm_credential':
query = '?kind=scm&role_level=use_role';
break;
case 'network_credential':
query = '?kind=net&role_level=use_role';
break;
@ -601,7 +606,7 @@ function(ConfigurationUtils, i18n, $rootScope) {
query = elm.attr('data-query');
query = query.replace(/\:value/, encodeURIComponent(viewValue));
let base = ctrl.$name.split('_name')[0];
let base = lookupType ? lookupType : ctrl.$name.split('_name')[0];
if (attrs.watchbasepath !== undefined && scope[attrs.watchbasepath] !== undefined) {
basePath = scope[attrs.watchbasepath];
query += '&role_level=use_role';
@ -612,6 +617,9 @@ function(ConfigurationUtils, i18n, $rootScope) {
case 'credential':
query += '&kind=ssh&role_level=use_role';
break;
case 'scm_credential':
query += '&kind=scm&role_level=use_role';
break;
case 'network_credential':
query += '&kind=net&role_level=use_role';
break;

View File

@ -1384,6 +1384,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html += `data-basePath="${field.basePath}"`;
html += `data-source="${field.sourceModel}"`;
html += `data-query="?${field.sourceField}__iexact=:value"`;
html += (field.awLookupType !== undefined) ? ` data-awLookupType=${field.awLookupType} ` : "";
html += (field.autopopulateLookup !== undefined) ? ` autopopulateLookup=${field.autopopulateLookup} ` : "";
html += (field.watchBasePath !== undefined) ? ` watchBasePath=${field.watchBasePath} ` : "";
html += `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }"`;

View File

@ -110,7 +110,7 @@ export default ['$stateParams', '$scope', '$state', 'QuerySet', 'GetBasePath', '
function setDefaults(term) {
if ($scope.list.defaultSearchParams) {
return $scope.list.defaultSearchParams(term);
return $scope.list.defaultSearchParams(encodeURIComponent(term));
} else {
return {
search: encodeURIComponent(term)

View File

@ -0,0 +1,9 @@
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -5,7 +5,7 @@
-e git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
apache-libcloud==1.3.0
appdirs==1.4.2
asgi-amqp==0.4.0
asgi-amqp==0.4.1
azure==2.0.0rc6
backports.ssl-match-hostname==3.5.0.1
boto==2.45.0

View File

@ -14,7 +14,7 @@ amqp==1.4.9 # via kombu
anyjson==0.3.3 # via kombu
apache-libcloud==1.3.0
appdirs==1.4.2
asgi-amqp==0.4.0
asgi-amqp==0.4.1
asgiref==1.0.0 # via asgi-amqp, channels, daphne
attrs==16.3.0 # via service-identity
autobahn==0.17.0 # via daphne