Integrate a migration-detector middleware

This attempts to detect if there are migrations in-progress and will
force display an interstitial page in the process that attempts to
load the index page every 10s until it succeeds.

This is only attached in production settings so the development
environment can proceed even if the migrations haven't been applied yet
This commit is contained in:
Matthew Jones 2017-09-11 11:09:45 -04:00
parent 0e04b8e4d4
commit b39db745d4
No known key found for this signature in database
GPG Key ID: 76A4C17A97590C1C
8 changed files with 74 additions and 3 deletions

1
.gitignore vendored
View File

@ -21,6 +21,7 @@ awx/ui/static
awx/ui/build_test
awx/ui/client/languages
awx/ui/templates/ui/index.html
awx/ui/templates/ui/installing.html
# Tower setup playbook testing
setup/test/roles/postgresql

View File

@ -9,11 +9,13 @@ import six
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.db import IntegrityError
from django.db.migrations.executor import MigrationExecutor
from django.db import IntegrityError, connection
from django.utils.functional import curry
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from django.apps import apps
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from awx.main.models import ActivityStream
from awx.api.authentication import TokenAuthentication
@ -162,3 +164,12 @@ class URLModificationMiddleware(object):
if request.path_info != new_path:
request.path = request.path.replace(request.path_info, new_path)
request.path_info = new_path
class MigrationRanCheckMiddleware(object):
def process_request(self, request):
executor = MigrationExecutor(connection)
plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
if bool(plan) and 'migrations_notran' not in request.path:
return redirect(reverse("ui:migrations_notran"))

View File

@ -87,6 +87,8 @@ settings_files = os.path.join(settings_dir, '*.py')
settings_file = os.environ.get('AWX_SETTINGS_FILE',
'/etc/tower/settings.py')
MIDDLEWARE_CLASSES = ('awx.main.middleware.MigrationRanCheckMiddleware',) + MIDDLEWARE_CLASSES
# Attempt to load settings from /etc/tower/settings.py first, followed by
# /etc/tower/conf.d/*.py.
try:

View File

@ -34,7 +34,7 @@ class SocialAuthMiddleware(SocialAuthExceptionMiddleware):
if not hasattr(request, 'successful_authenticator'):
request.successful_authenticator = None
if not request.path.startswith('/sso/'):
if not request.path.startswith('/sso/') and 'migrations_notran' not in request.path:
# If token isn't present but we still have a user logged in via Django
# sessions, log them out.

View File

@ -25,6 +25,8 @@ const APP_ENTRY = path.join(SOURCE_PATH, 'app.js');
const VENDOR_ENTRY = path.join(SOURCE_PATH, 'vendor.js');
const INDEX_ENTRY = path.join(CLIENT_PATH, 'index.template.ejs');
const INDEX_OUTPUT = path.join(UI_PATH, 'templates/ui/index.html');
const INSTALL_RUNNING_ENTRY = path.join(CLIENT_PATH, 'installing.template.ejs')
const INSTALL_RUNNING_OUTPUT = path.join(UI_PATH, 'templates/ui/installing.html');
const THEME_ENTRY = path.join(LIB_PATH, 'theme', 'index.less');
const OUTPUT = 'js/[name].[hash].js';
const CHUNKS = ['vendor', 'app'];
@ -168,6 +170,19 @@ let base = {
moduleA.files.sort((fileA, fileB) => fileA.includes('js') ? -1 : 1)
moduleB.files.sort((fileA, fileB) => fileA.includes('js') ? -1 : 1)
return moduleA.names[0] === 'vendor' ? -1 : 1
}
}),
new HtmlWebpackPlugin({
alwaysWriteToDisk: true,
template: INSTALL_RUNNING_ENTRY,
filename: INSTALL_RUNNING_OUTPUT,
inject: false,
chunks: CHUNKS,
chunksSortMode: (moduleA, moduleB) => {
moduleA.files.sort((fileA, fileB) => fileA.includes('js') ? -1 : 1)
moduleB.files.sort((fileA, fileB) => fileA.includes('js') ? -1 : 1)
return moduleA.names[0] === 'vendor' ? -1 : 1
}
})

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en" ng-app="awApp">
<head>
{% load staticfiles %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="{{ STATIC_URL }}assets/favicon.ico?v={{version}}" />
<title ng-bind="tabTitle"></title>
<% htmlWebpackPlugin.files.css.forEach(file => {%>
<link href="{{ STATIC_URL }}<%= file %>" rel="stylesheet" />
<% }) %>
<% htmlWebpackPlugin.files.js.forEach(file => {%>
<script src="{{ STATIC_URL }}<%= file %>"></script>
<% }) %>
<script>
setInterval(function() {
window.location = '/#/index.html';
}, 10000);
</script>
</head>
<body>
<div class="jumbotron">
<div class="container-fluid" id="content-container">
<div class="span4">
<img style="float:left" src="{% static 'assets/logo-header.svg' %}" width="200"/>
<div class="content-heading"><h1>AWX is Upgrading</h1></div>
<p>
AWX is currently upgrading or installing, this page will refresh when done.
</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -6,5 +6,6 @@ from django.conf.urls import *
urlpatterns = patterns('awx.ui.views',
url(r'^$', 'index', name='index'),
url(r'^migrations_notran/$', 'migrations_notran', name='migrations_notran'),
url(r'^portal/$', 'portal_redirect', name='portal_redirect'),
)

View File

@ -20,3 +20,9 @@ class PortalRedirectView(RedirectView):
url = '/#/portal'
portal_redirect = PortalRedirectView.as_view()
class MigrationsNotran(TemplateView):
template_name = 'ui/installing.html'
migrations_notran = MigrationsNotran.as_view()