Allow custom theme install
Adds a setting custom-theme which operats similar to ubuntu-theme and default-theme. The provided resource is placed in the themes folder and apache is setup to serve static content for the theme. This leaves the default theme untouched allowing the custom theme to override files based on the built in horizon theme capabilities. For details on theming capabilities see: https://docs.openstack.org/horizon/latest/configuration/themes.html gnuoy: retry logic for unrelated test updated after a number of CI failures. Closes-Bug: #1778284 Change-Id: I91ad19e8aad5c0e0773d42fa4f085cbcecb82458
This commit is contained in:
parent
889089153f
commit
e5d9c95724
19
README.md
19
README.md
@ -93,3 +93,22 @@ to also deploy the dashboard with load balancing proxy such as HAProxy:
|
||||
|
||||
This option potentially provides better scale-out than using the charm in
|
||||
conjunction with the hacluster charm.
|
||||
|
||||
|
||||
Custom Theme
|
||||
============
|
||||
This charm supports providing a custom theme as documented in the [themes
|
||||
configuration]. In order to enable this capability the configuration options
|
||||
'ubuntu-theme' and 'default-theme' must both be turned off and the option
|
||||
'custom-theme' turned on.
|
||||
|
||||
Once the option is enabled a custom theme can be provided via a juju resource.
|
||||
The resource should be a .tgz file with the contents of your custom theme. If
|
||||
the file 'local_settings.py' is included it will be sourced.
|
||||
|
||||
juju attach-resource openstack-dashboard theme=theme.tgz
|
||||
|
||||
Repeating the attach-resource will update the theme and turning off the
|
||||
custom-theme option will return to the default.
|
||||
|
||||
[themes]: https://docs.openstack.org/horizon/latest/configuration/themes.html
|
||||
|
@ -175,6 +175,13 @@ options:
|
||||
.
|
||||
NOTE: This setting is supported >= OpenStack Liberty and
|
||||
this setting is mutually exclusive to ubuntu-theme.
|
||||
custom-theme:
|
||||
type: boolean
|
||||
default: False
|
||||
description: |
|
||||
Use a custom theme supplied as a resource.
|
||||
NOTE: This setting is supported >= OpenStack Mitaka and
|
||||
this setting is mutually exclustive to ubuntu-theme and default-theme.
|
||||
secret:
|
||||
type: string
|
||||
default:
|
||||
|
@ -182,6 +182,7 @@ class HorizonContext(OSContextGenerator):
|
||||
"webroot": config('webroot') or '/',
|
||||
"ubuntu_theme": bool_from_string(config('ubuntu-theme')),
|
||||
"default_theme": config('default-theme'),
|
||||
"custom_theme": config('custom-theme'),
|
||||
"secret": config('secret') or pwgen(),
|
||||
'support_profile': config('profile')
|
||||
if config('profile') in ['cisco'] else None,
|
||||
@ -210,7 +211,8 @@ class ApacheContext(OSContextGenerator):
|
||||
'http_port': 70,
|
||||
'https_port': 433,
|
||||
'enforce_ssl': False,
|
||||
'hsts_max_age_seconds': config('hsts-max-age-seconds')
|
||||
'hsts_max_age_seconds': config('hsts-max-age-seconds'),
|
||||
"custom_theme": config('custom-theme'),
|
||||
}
|
||||
|
||||
if config('enforce-ssl'):
|
||||
|
@ -64,6 +64,7 @@ from horizon_utils import (
|
||||
restart_on_change,
|
||||
assess_status,
|
||||
db_migration,
|
||||
check_custom_theme,
|
||||
)
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
get_iface_for_address,
|
||||
@ -110,6 +111,7 @@ def upgrade_charm():
|
||||
apt_install(filter_installed_packages(determine_packages()), fatal=True)
|
||||
update_nrpe_config()
|
||||
CONFIGS.write_all()
|
||||
check_custom_theme()
|
||||
|
||||
|
||||
@hooks.hook('config-changed')
|
||||
@ -150,6 +152,7 @@ def config_changed():
|
||||
save_script_rc(**env_vars)
|
||||
update_nrpe_config()
|
||||
CONFIGS.write_all()
|
||||
check_custom_theme()
|
||||
open_port(80)
|
||||
open_port(443)
|
||||
|
||||
|
@ -17,6 +17,7 @@ import horizon_contexts
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import tarfile
|
||||
from collections import OrderedDict
|
||||
|
||||
import charmhelpers.contrib.openstack.context as context
|
||||
@ -36,7 +37,8 @@ from charmhelpers.contrib.openstack.utils import (
|
||||
)
|
||||
from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
log
|
||||
log,
|
||||
resource_get,
|
||||
)
|
||||
from charmhelpers.core.host import (
|
||||
cmp_pkgrevno,
|
||||
@ -86,6 +88,9 @@ ROUTER_SETTING = ('/usr/share/openstack-dashboard/openstack_dashboard/enabled/'
|
||||
KEYSTONEV3_POLICY = ('/usr/share/openstack-dashboard/openstack_dashboard/conf/'
|
||||
'keystonev3_policy.json')
|
||||
TEMPLATES = 'templates'
|
||||
CUSTOM_THEME_DIR = ("/usr/share/openstack-dashboard/openstack_dashboard/"
|
||||
"themes/custom")
|
||||
LOCAL_DIR = '/usr/share/openstack-dashboard/openstack_dashboard/local/'
|
||||
|
||||
CONFIG_FILES = OrderedDict([
|
||||
(LOCAL_SETTINGS, {
|
||||
@ -414,3 +419,27 @@ def db_migration():
|
||||
subcommand = 'syncdb'
|
||||
cmd = ['/usr/share/openstack-dashboard/manage.py', subcommand, '--noinput']
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def check_custom_theme():
|
||||
if not config('custom-theme'):
|
||||
log('No custom theme configured, exiting')
|
||||
return
|
||||
try:
|
||||
os.mkdir(CUSTOM_THEME_DIR)
|
||||
except OSError as e:
|
||||
if e.errno is 17:
|
||||
pass # already exists
|
||||
theme_file = resource_get('theme')
|
||||
log('Retreived resource: {}'.format(theme_file))
|
||||
if theme_file:
|
||||
with tarfile.open(theme_file, 'r:gz') as in_file:
|
||||
in_file.extractall(CUSTOM_THEME_DIR)
|
||||
custom_settings = '{}/local_settings.py'.format(CUSTOM_THEME_DIR)
|
||||
if os.path.isfile(custom_settings):
|
||||
try:
|
||||
os.symlink(custom_settings, LOCAL_DIR + 'custom_theme.py')
|
||||
except OSError as e:
|
||||
if e.errno is 17:
|
||||
pass # already exists
|
||||
log('Custom theme updated'.format(theme_file))
|
||||
|
@ -38,3 +38,8 @@ requires:
|
||||
peers:
|
||||
cluster:
|
||||
interface: openstack-dashboard-ha
|
||||
resources:
|
||||
theme:
|
||||
type: file
|
||||
filename: theme.tgz
|
||||
description: "Custom dashboard theme"
|
||||
|
@ -861,6 +861,13 @@ if '{{ default_theme }}' not in [el[0] for el in AVAILABLE_THEMES]:
|
||||
'themes/{{ default_theme }}'),
|
||||
]
|
||||
DEFAULT_THEME = '{{ default_theme }}'
|
||||
{% elif custom_theme %}
|
||||
AVAILABLE_THEMES = []
|
||||
try:
|
||||
from custom_theme import *
|
||||
except ImportError:
|
||||
pass
|
||||
AVAILABLE_THEMES += [ ('custom', 'custom', 'themes/custom') ]
|
||||
{% endif %}
|
||||
|
||||
WEBROOT = '{{ webroot }}'
|
||||
|
14
templates/mitaka/openstack-dashboard.conf
Normal file
14
templates/mitaka/openstack-dashboard.conf
Normal file
@ -0,0 +1,14 @@
|
||||
WSGIScriptAlias {{ webroot }} /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi
|
||||
WSGIDaemonProcess horizon user=horizon group=horizon processes={{ processes }} threads=10
|
||||
WSGIProcessGroup horizon
|
||||
{% if custom_theme %}
|
||||
Alias /static/custom /usr/share/openstack-dashboard/openstack_dashboard/themes/custom/static/
|
||||
Alias /static/themes/custom /usr/share/openstack-dashboard/openstack_dashboard/themes/custom/static/
|
||||
{% endif %}
|
||||
Alias /static /usr/share/openstack-dashboard/openstack_dashboard/static/
|
||||
Alias /horizon/static /usr/share/openstack-dashboard/openstack_dashboard/static/
|
||||
|
||||
<Directory /usr/share/openstack-dashboard/openstack_dashboard/wsgi>
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
@ -900,6 +900,13 @@ if '{{ default_theme }}' not in [el[0] for el in AVAILABLE_THEMES]:
|
||||
'themes/{{ default_theme }}'),
|
||||
]
|
||||
DEFAULT_THEME = '{{ default_theme }}'
|
||||
{% elif custom_theme %}
|
||||
AVAILABLE_THEMES = []
|
||||
try:
|
||||
from custom_theme import *
|
||||
except ImportError:
|
||||
pass
|
||||
AVAILABLE_THEMES += [ ('custom', 'custom', 'themes/custom') ]
|
||||
{% endif %}
|
||||
|
||||
WEBROOT = '{{ webroot }}'
|
||||
|
@ -1,6 +1,9 @@
|
||||
WSGIScriptAlias {{ webroot }} /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi
|
||||
WSGIDaemonProcess horizon user=horizon group=horizon processes={{ processes }} threads=10
|
||||
WSGIProcessGroup horizon
|
||||
{% if custom_theme %}
|
||||
Alias /static/themes/custom /usr/share/openstack-dashboard/openstack_dashboard/themes/custom/static/
|
||||
{% endif %}
|
||||
Alias /static /usr/share/openstack-dashboard/openstack_dashboard/static/
|
||||
Alias /horizon/static /usr/share/openstack-dashboard/openstack_dashboard/static/
|
||||
<Directory /usr/share/openstack-dashboard/openstack_dashboard/wsgi>
|
||||
|
@ -902,6 +902,13 @@ if '{{ default_theme }}' not in [el[0] for el in AVAILABLE_THEMES]:
|
||||
'themes/{{ default_theme }}'),
|
||||
]
|
||||
DEFAULT_THEME = '{{ default_theme }}'
|
||||
{% elif custom_theme %}
|
||||
AVAILABLE_THEMES = []
|
||||
try:
|
||||
from custom_theme import *
|
||||
except ImportError:
|
||||
pass
|
||||
AVAILABLE_THEMES += [ ('custom', 'custom', 'themes/custom') ]
|
||||
{% endif %}
|
||||
|
||||
WEBROOT = '{{ webroot }}'
|
||||
|
@ -1,6 +1,9 @@
|
||||
WSGIScriptAlias {{ webroot }} /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi
|
||||
WSGIDaemonProcess horizon user=horizon group=horizon processes={{ processes }} threads=10
|
||||
WSGIProcessGroup horizon
|
||||
{% if custom_theme %}
|
||||
Alias /static/themes/custom /usr/share/openstack-dashboard/openstack_dashboard/themes/custom/static/
|
||||
{% endif %}
|
||||
Alias /static /var/lib/openstack-dashboard/static/
|
||||
Alias /horizon/static /var/lib/openstack-dashboard/static/
|
||||
<Directory /usr/share/openstack-dashboard/openstack_dashboard/wsgi>
|
||||
|
@ -1,6 +1,9 @@
|
||||
WSGIScriptAlias {{ webroot }} /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi
|
||||
WSGIDaemonProcess horizon user=horizon group=horizon processes={{ processes }} threads=10
|
||||
WSGIProcessGroup horizon
|
||||
{% if custom_theme %}
|
||||
Alias /static/themes/custom /usr/share/openstack-dashboard/openstack_dashboard/themes/custom/static/
|
||||
{% endif %}
|
||||
Alias /static /var/lib/openstack-dashboard/static/
|
||||
Alias /horizon/static /var/lib/openstack-dashboard/static/
|
||||
<Directory /usr/share/openstack-dashboard/openstack_dashboard/wsgi>
|
||||
|
@ -230,7 +230,7 @@ class OpenstackDashboardBasicDeployment(OpenStackAmuletDeployment):
|
||||
# add retry logic to unwedge the gate. This issue
|
||||
# should be revisited and root caused properly when time
|
||||
# allows.
|
||||
@retry_on_exception(1)
|
||||
@retry_on_exception(2, base_delay=2)
|
||||
def do_request():
|
||||
response = urllib2.urlopen('http://%s/horizon' % (dashboard_ip))
|
||||
return response.read()
|
||||
|
@ -64,7 +64,8 @@ class TestHorizonContexts(CharmTestCase):
|
||||
self.assertEqual(horizon_contexts.ApacheContext()(),
|
||||
{'http_port': 70, 'https_port': 433,
|
||||
'enforce_ssl': False,
|
||||
'hsts_max_age_seconds': 0})
|
||||
'hsts_max_age_seconds': 0,
|
||||
'custom_theme': False})
|
||||
|
||||
def test_Apachecontext_enforce_ssl(self):
|
||||
self.test_config.set('enforce-ssl', True)
|
||||
@ -72,7 +73,8 @@ class TestHorizonContexts(CharmTestCase):
|
||||
self.assertEquals(horizon_contexts.ApacheContext()(),
|
||||
{'http_port': 70, 'https_port': 433,
|
||||
'enforce_ssl': True,
|
||||
'hsts_max_age_seconds': 0})
|
||||
'hsts_max_age_seconds': 0,
|
||||
'custom_theme': False})
|
||||
|
||||
def test_Apachecontext_enforce_ssl_no_cert(self):
|
||||
self.test_config.set('enforce-ssl', True)
|
||||
@ -80,7 +82,8 @@ class TestHorizonContexts(CharmTestCase):
|
||||
self.assertEquals(horizon_contexts.ApacheContext()(),
|
||||
{'http_port': 70, 'https_port': 433,
|
||||
'enforce_ssl': False,
|
||||
'hsts_max_age_seconds': 0})
|
||||
'hsts_max_age_seconds': 0,
|
||||
'custom_theme': False})
|
||||
|
||||
def test_Apachecontext_hsts_max_age_seconds(self):
|
||||
self.test_config.set('enforce-ssl', True)
|
||||
@ -89,7 +92,8 @@ class TestHorizonContexts(CharmTestCase):
|
||||
self.assertEquals(horizon_contexts.ApacheContext()(),
|
||||
{'http_port': 70, 'https_port': 433,
|
||||
'enforce_ssl': True,
|
||||
'hsts_max_age_seconds': 15768000})
|
||||
'hsts_max_age_seconds': 15768000,
|
||||
'custom_theme': False})
|
||||
|
||||
@patch.object(horizon_contexts, 'get_ca_cert', lambda: None)
|
||||
@patch('os.chmod')
|
||||
@ -125,6 +129,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -150,6 +155,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -175,6 +181,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -200,6 +207,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': False,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -226,6 +234,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': False,
|
||||
'default_theme': 'material',
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -255,6 +264,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -280,6 +290,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'foo', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -305,6 +316,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -335,6 +347,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": True,
|
||||
@ -360,6 +373,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -385,6 +399,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -411,6 +426,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -437,6 +453,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
@ -463,6 +480,7 @@ class TestHorizonContexts(CharmTestCase):
|
||||
'default_role': 'Member', 'webroot': '/horizon',
|
||||
'ubuntu_theme': True,
|
||||
'default_theme': None,
|
||||
'custom_theme': False,
|
||||
'secret': 'secret',
|
||||
'support_profile': None,
|
||||
"neutron_network_dvr": False,
|
||||
|
@ -132,11 +132,13 @@ class TestHorizonHooks(CharmTestCase):
|
||||
)
|
||||
self.assertTrue(self.apt_install.called)
|
||||
|
||||
@patch('horizon_hooks.check_custom_theme')
|
||||
@patch.object(hooks, 'determine_packages')
|
||||
@patch.object(utils, 'path_hash')
|
||||
@patch.object(utils, 'service')
|
||||
def test_upgrade_charm_hook(self, _service, _hash,
|
||||
_determine_packages):
|
||||
_determine_packages,
|
||||
_custom_theme):
|
||||
_determine_packages.return_value = []
|
||||
side_effects = []
|
||||
[side_effects.append(None) for f in RESTART_MAP.keys()]
|
||||
@ -155,6 +157,7 @@ class TestHorizonHooks(CharmTestCase):
|
||||
call('start', 'haproxy'),
|
||||
]
|
||||
self.assertEqual(ex, _service.call_args_list)
|
||||
self.assertTrue(_custom_theme.called)
|
||||
|
||||
def test_ha_joined_complete_config(self):
|
||||
conf = {
|
||||
@ -258,8 +261,9 @@ class TestHorizonHooks(CharmTestCase):
|
||||
self.assertTrue(self.update_dns_ha_resource_params.called)
|
||||
self.relation_set.assert_called_with(**args)
|
||||
|
||||
@patch('horizon_hooks.check_custom_theme')
|
||||
@patch('horizon_hooks.keystone_joined')
|
||||
def test_config_changed_no_upgrade(self, _joined):
|
||||
def test_config_changed_no_upgrade(self, _joined, _custom_theme):
|
||||
def relation_ids_side_effect(rname):
|
||||
return {
|
||||
'websso-trusted-dashboard': [
|
||||
@ -295,13 +299,16 @@ class TestHorizonHooks(CharmTestCase):
|
||||
self.assertTrue(self.save_script_rc.called)
|
||||
self.assertTrue(self.CONFIGS.write_all.called)
|
||||
self.open_port.assert_has_calls([call(80), call(443)])
|
||||
self.assertTrue(_custom_theme.called)
|
||||
|
||||
def test_config_changed_do_upgrade(self):
|
||||
@patch('horizon_hooks.check_custom_theme')
|
||||
def test_config_changed_do_upgrade(self, _custom_theme):
|
||||
self.relation_ids.return_value = []
|
||||
self.test_config.set('openstack-origin', 'cloud:precise-grizzly')
|
||||
self.openstack_upgrade_available.return_value = True
|
||||
self._call_hook('config-changed')
|
||||
self.assertTrue(self.do_openstack_upgrade.called)
|
||||
self.assertTrue(_custom_theme.called)
|
||||
|
||||
def test_keystone_joined_in_relation(self):
|
||||
self._call_hook('identity-service-relation-joined')
|
||||
|
Loading…
Reference in New Issue
Block a user