resync hooks/charmhelpers
This commit is contained in:
parent
7ab345fb05
commit
91b801b958
@ -21,7 +21,9 @@
|
|||||||
# Charm Helpers Developers <juju@lists.ubuntu.com>
|
# Charm Helpers Developers <juju@lists.ubuntu.com>
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
from distutils.version import LooseVersion
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
@ -242,29 +244,7 @@ class Config(dict):
|
|||||||
self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
|
self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
|
||||||
if os.path.exists(self.path):
|
if os.path.exists(self.path):
|
||||||
self.load_previous()
|
self.load_previous()
|
||||||
|
atexit(self._implicit_save)
|
||||||
def __getitem__(self, key):
|
|
||||||
"""For regular dict lookups, check the current juju config first,
|
|
||||||
then the previous (saved) copy. This ensures that user-saved values
|
|
||||||
will be returned by a dict lookup.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return dict.__getitem__(self, key)
|
|
||||||
except KeyError:
|
|
||||||
return (self._prev_dict or {})[key]
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
prev_keys = []
|
|
||||||
if self._prev_dict is not None:
|
|
||||||
prev_keys = self._prev_dict.keys()
|
|
||||||
return list(set(prev_keys + list(dict.keys(self))))
|
|
||||||
|
|
||||||
def load_previous(self, path=None):
|
def load_previous(self, path=None):
|
||||||
"""Load previous copy of config from disk.
|
"""Load previous copy of config from disk.
|
||||||
@ -283,6 +263,9 @@ class Config(dict):
|
|||||||
self.path = path or self.path
|
self.path = path or self.path
|
||||||
with open(self.path) as f:
|
with open(self.path) as f:
|
||||||
self._prev_dict = json.load(f)
|
self._prev_dict = json.load(f)
|
||||||
|
for k, v in self._prev_dict.items():
|
||||||
|
if k not in self:
|
||||||
|
self[k] = v
|
||||||
|
|
||||||
def changed(self, key):
|
def changed(self, key):
|
||||||
"""Return True if the current value for this key is different from
|
"""Return True if the current value for this key is different from
|
||||||
@ -314,13 +297,13 @@ class Config(dict):
|
|||||||
instance.
|
instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self._prev_dict:
|
|
||||||
for k, v in six.iteritems(self._prev_dict):
|
|
||||||
if k not in self:
|
|
||||||
self[k] = v
|
|
||||||
with open(self.path, 'w') as f:
|
with open(self.path, 'w') as f:
|
||||||
json.dump(self, f)
|
json.dump(self, f)
|
||||||
|
|
||||||
|
def _implicit_save(self):
|
||||||
|
if self.implicit_save:
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def config(scope=None):
|
def config(scope=None):
|
||||||
@ -587,10 +570,14 @@ class Hooks(object):
|
|||||||
hooks.execute(sys.argv)
|
hooks.execute(sys.argv)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config_save=True):
|
def __init__(self, config_save=None):
|
||||||
super(Hooks, self).__init__()
|
super(Hooks, self).__init__()
|
||||||
self._hooks = {}
|
self._hooks = {}
|
||||||
self._config_save = config_save
|
|
||||||
|
# For unknown reasons, we allow the Hooks constructor to override
|
||||||
|
# config().implicit_save.
|
||||||
|
if config_save is not None:
|
||||||
|
config().implicit_save = config_save
|
||||||
|
|
||||||
def register(self, name, function):
|
def register(self, name, function):
|
||||||
"""Register a hook"""
|
"""Register a hook"""
|
||||||
@ -598,13 +585,16 @@ class Hooks(object):
|
|||||||
|
|
||||||
def execute(self, args):
|
def execute(self, args):
|
||||||
"""Execute a registered hook based on args[0]"""
|
"""Execute a registered hook based on args[0]"""
|
||||||
|
_run_atstart()
|
||||||
hook_name = os.path.basename(args[0])
|
hook_name = os.path.basename(args[0])
|
||||||
if hook_name in self._hooks:
|
if hook_name in self._hooks:
|
||||||
self._hooks[hook_name]()
|
try:
|
||||||
if self._config_save:
|
self._hooks[hook_name]()
|
||||||
cfg = config()
|
except SystemExit as x:
|
||||||
if cfg.implicit_save:
|
if x.code is None or x.code == 0:
|
||||||
cfg.save()
|
_run_atexit()
|
||||||
|
raise
|
||||||
|
_run_atexit()
|
||||||
else:
|
else:
|
||||||
raise UnregisteredHookError(hook_name)
|
raise UnregisteredHookError(hook_name)
|
||||||
|
|
||||||
@ -732,13 +722,79 @@ def leader_get(attribute=None):
|
|||||||
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
def leader_set(settings=None, **kwargs):
|
def leader_set(settings=None, **kwargs):
|
||||||
"""Juju leader set value(s)"""
|
"""Juju leader set value(s)"""
|
||||||
log("Juju leader-set '%s'" % (settings), level=DEBUG)
|
# Don't log secrets.
|
||||||
|
# log("Juju leader-set '%s'" % (settings), level=DEBUG)
|
||||||
cmd = ['leader-set']
|
cmd = ['leader-set']
|
||||||
settings = settings or {}
|
settings = settings or {}
|
||||||
settings.update(kwargs)
|
settings.update(kwargs)
|
||||||
for k, v in settings.iteritems():
|
for k, v in settings.items():
|
||||||
if v is None:
|
if v is None:
|
||||||
cmd.append('{}='.format(k))
|
cmd.append('{}='.format(k))
|
||||||
else:
|
else:
|
||||||
cmd.append('{}={}'.format(k, v))
|
cmd.append('{}={}'.format(k, v))
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
@cached
|
||||||
|
def juju_version():
|
||||||
|
"""Full version string (eg. '1.23.3.1-trusty-amd64')"""
|
||||||
|
# Per https://bugs.launchpad.net/juju-core/+bug/1455368/comments/1
|
||||||
|
jujud = glob.glob('/var/lib/juju/tools/machine-*/jujud')[0]
|
||||||
|
return subprocess.check_output([jujud, 'version'],
|
||||||
|
universal_newlines=True).strip()
|
||||||
|
|
||||||
|
|
||||||
|
@cached
|
||||||
|
def has_juju_version(minimum_version):
|
||||||
|
"""Return True if the Juju version is at least the provided version"""
|
||||||
|
return LooseVersion(juju_version()) >= LooseVersion(minimum_version)
|
||||||
|
|
||||||
|
|
||||||
|
_atexit = []
|
||||||
|
_atstart = []
|
||||||
|
|
||||||
|
|
||||||
|
def atstart(callback, *args, **kwargs):
|
||||||
|
'''Schedule a callback to run before the main hook.
|
||||||
|
|
||||||
|
Callbacks are run in the order they were added.
|
||||||
|
|
||||||
|
This is useful for modules and classes to perform initialization
|
||||||
|
and inject behavior. In particular:
|
||||||
|
- Run common code before all of your hooks, such as logging
|
||||||
|
the hook name or interesting relation data.
|
||||||
|
- Defer object or module initialization that requires a hook
|
||||||
|
context until we know there actually is a hook context,
|
||||||
|
making testing easier.
|
||||||
|
- Rather than requiring charm authors to include boilerplate to
|
||||||
|
invoke your helper's behavior, have it run automatically if
|
||||||
|
your object is instantiated or module imported.
|
||||||
|
|
||||||
|
This is not at all useful after your hook framework as been launched.
|
||||||
|
'''
|
||||||
|
global _atstart
|
||||||
|
_atstart.append((callback, args, kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def atexit(callback, *args, **kwargs):
|
||||||
|
'''Schedule a callback to run on successful hook completion.
|
||||||
|
|
||||||
|
Callbacks are run in the reverse order that they were added.'''
|
||||||
|
_atexit.append((callback, args, kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def _run_atstart():
|
||||||
|
'''Hook frameworks must invoke this before running the main hook body.'''
|
||||||
|
global _atstart
|
||||||
|
for callback, args, kwargs in _atstart:
|
||||||
|
callback(*args, **kwargs)
|
||||||
|
del _atstart[:]
|
||||||
|
|
||||||
|
|
||||||
|
def _run_atexit():
|
||||||
|
'''Hook frameworks must invoke this after the main hook body has
|
||||||
|
successfully completed. Do not invoke it if the hook fails.'''
|
||||||
|
global _atexit
|
||||||
|
for callback, args, kwargs in reversed(_atexit):
|
||||||
|
callback(*args, **kwargs)
|
||||||
|
del _atexit[:]
|
||||||
|
@ -128,15 +128,18 @@ class ServiceManager(object):
|
|||||||
"""
|
"""
|
||||||
Handle the current hook by doing The Right Thing with the registered services.
|
Handle the current hook by doing The Right Thing with the registered services.
|
||||||
"""
|
"""
|
||||||
hook_name = hookenv.hook_name()
|
hookenv._run_atstart()
|
||||||
if hook_name == 'stop':
|
try:
|
||||||
self.stop_services()
|
hook_name = hookenv.hook_name()
|
||||||
else:
|
if hook_name == 'stop':
|
||||||
self.reconfigure_services()
|
self.stop_services()
|
||||||
self.provide_data()
|
else:
|
||||||
cfg = hookenv.config()
|
self.reconfigure_services()
|
||||||
if cfg.implicit_save:
|
self.provide_data()
|
||||||
cfg.save()
|
except SystemExit as x:
|
||||||
|
if x.code is None or x.code == 0:
|
||||||
|
hookenv._run_atexit()
|
||||||
|
hookenv._run_atexit()
|
||||||
|
|
||||||
def provide_data(self):
|
def provide_data(self):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user