Merge "Allow custom theme install"
This commit is contained in:
commit
4566f20b4c
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 }}'
|
||||
|
|
|
@ -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