From b39db745d4addd7aa4612a43cf3ebd12c4d206cd Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 11 Sep 2017 11:09:45 -0400 Subject: [PATCH] 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 --- .gitignore | 1 + awx/main/middleware.py | 15 ++++++++++-- awx/settings/production.py | 2 ++ awx/sso/middleware.py | 2 +- awx/ui/build/webpack.base.js | 15 ++++++++++++ awx/ui/client/installing.template.ejs | 35 +++++++++++++++++++++++++++ awx/ui/urls.py | 1 + awx/ui/views.py | 6 +++++ 8 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 awx/ui/client/installing.template.ejs diff --git a/.gitignore b/.gitignore index adafb7a59c..5678a7be76 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/awx/main/middleware.py b/awx/main/middleware.py index dfb08b20fb..2cae0961c3 100644 --- a/awx/main/middleware.py +++ b/awx/main/middleware.py @@ -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")) diff --git a/awx/settings/production.py b/awx/settings/production.py index 1d93ad43e6..7ad7a908b2 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -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: diff --git a/awx/sso/middleware.py b/awx/sso/middleware.py index 71e93725e9..7690dbd0ce 100644 --- a/awx/sso/middleware.py +++ b/awx/sso/middleware.py @@ -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. diff --git a/awx/ui/build/webpack.base.js b/awx/ui/build/webpack.base.js index e56cd0810d..5d565a334d 100644 --- a/awx/ui/build/webpack.base.js +++ b/awx/ui/build/webpack.base.js @@ -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 } }) diff --git a/awx/ui/client/installing.template.ejs b/awx/ui/client/installing.template.ejs new file mode 100644 index 0000000000..f9005def42 --- /dev/null +++ b/awx/ui/client/installing.template.ejs @@ -0,0 +1,35 @@ + + + + {% load staticfiles %} + + + + + + <% htmlWebpackPlugin.files.css.forEach(file => {%> + + <% }) %> + <% htmlWebpackPlugin.files.js.forEach(file => {%> + + <% }) %> + + + +
+
+
+ +

AWX is Upgrading

+

+ AWX is currently upgrading or installing, this page will refresh when done. +

+
+
+
+ + diff --git a/awx/ui/urls.py b/awx/ui/urls.py index ea4bf96291..f92787d28d 100644 --- a/awx/ui/urls.py +++ b/awx/ui/urls.py @@ -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'), ) diff --git a/awx/ui/views.py b/awx/ui/views.py index 0a47154615..8c1d2fb7a3 100644 --- a/awx/ui/views.py +++ b/awx/ui/views.py @@ -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()