Merge "Allow custom theme install"

This commit is contained in:
Zuul 2018-07-12 14:39:24 +00:00 committed by Gerrit Code Review
commit 4566f20b4c
16 changed files with 144 additions and 10 deletions

View File

@ -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

View File

@ -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:

View File

@ -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'):

View File

@ -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)

View File

@ -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))

View File

@ -38,3 +38,8 @@ requires:
peers:
cluster:
interface: openstack-dashboard-ha
resources:
theme:
type: file
filename: theme.tgz
description: "Custom dashboard theme"

View File

@ -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 }}'

View 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>

View File

@ -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 }}'

View File

@ -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>

View File

@ -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 }}'

View File

@ -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>

View File

@ -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>

View File

@ -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()

View File

@ -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,

View File

@ -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')