def preflight(action): """ Decorator to run a handler before the main hook phase. preflight hooks are used to initialize state for use by the main hooks (including hooks that exist in other layers). They can also be used to validate the environment, blocking the unit and aborting the hook if something this layer is responsible for is broken (eg. a service configuration option set to an invalid value). We need abort before the main reactive loop, or we risk failing to run handlers that rely on @when_file_changed, reactive.helpers.data_changed or other state tied to charmhelpers.core.unitdata transactions. """ _id = _short_action_id(action) hookenv.atstart(hookenv.log, "preflight handler: {}".format(_id)) hookenv.atstart(action) return action
def __init__(self, relation_key='coordinator', peer_relation_name=None): '''Instatiate a Coordinator. Data is stored on the peer relation and in leadership storage under the provided relation_key. The peer relation is identified by peer_relation_name, and defaults to the first one found in metadata.yaml. ''' # Most initialization is deferred, since invoking hook tools from # the constructor makes testing hard. self.key = relation_key self.relname = peer_relation_name hookenv.atstart(self.initialize) # Ensure that handle() is called, without placing that burden on # the charm author. They still need to do this manually if they # are not using a hook framework. hookenv.atstart(self.handle)
def preflight(action): ''' Decorator to run a handler before the main hook phase. preflight hooks are used to initialize state for use by the main hooks (including hooks that exist in other layers). They can also be used to validate the environment, blocking the unit and aborting the hook if something this layer is responsible for is broken (eg. a service configuration option set to an invalid value). We need abort before the main reactive loop, or we risk failing to run handlers that rely on @when_file_changed, reactive.helpers.data_changed or other state tied to charmhelpers.core.unitdata transactions. ''' _id = _short_action_id(action) hookenv.atstart(hookenv.log, 'preflight handler: {}'.format(_id)) hookenv.atstart(action) return action
def __init__(self, relation_key='coordinator', peer_relation_name=None): '''Instatiate a Coordinator. Data is stored on the peers relation and in leadership storage under the provided relation_key. The peers relation is identified by peer_relation_name, and defaults to the first one found in metadata.yaml. ''' # Most initialization is deferred, since invoking hook tools from # the constructor makes testing hard. self.key = relation_key self.relname = peer_relation_name hookenv.atstart(self.initialize) # Ensure that handle() is called, without placing that burden on # the charm author. They still need to do this manually if they # are not using a hook framework. hookenv.atstart(self.handle)
reactive.helpers.toggle_state('leadership.is_leader', is_leader) previous = unitdata.kv().getrange('leadership.settings.', strip=True) current = hookenv.leader_get() # Handle deletions. for key in set(previous.keys()) - set(current.keys()): current[key] = None any_changed = False for key, value in current.items(): reactive.helpers.toggle_state('leadership.changed.{}'.format(key), value != previous.get(key)) if value != previous.get(key): any_changed = True reactive.helpers.toggle_state('leadership.set.{}'.format(key), value is not None) reactive.helpers.toggle_state('leadership.changed', any_changed) unitdata.kv().update(current, prefix='leadership.settings.') # Per https://github.com/juju-solutions/charms.reactive/issues/33, # this module may be imported multiple times so ensure the # initialization hook is only registered once. I have to piggy back # onto the namespace of a module imported before reactive discovery # to do this. if not hasattr(reactive, '_leadership_registered'): hookenv.atstart(initialize_leadership_state) reactive._leadership_registered = True
# there is no other way to inform the operator that they have # invalid configuration. raise SystemExit(0) sources = config.get('install_sources') keys = config.get('install_keys') if reactive.helpers.data_changed('apt.configure_sources', (sources, keys)): fetch.configure_sources(update=False, sources_var='install_sources', keys_var='install_keys') reactive.set_state('apt.needs_update') extra_packages = sorted(config.get('extra_packages', '').split()) if extra_packages: queue_install(extra_packages) # Per https://github.com/juju-solutions/charms.reactive/issues/33, # this module may be imported multiple times so ensure the # initialization hook is only registered once. I have to piggy back # onto the namespace of a module imported before reactive discovery # to do this. if not hasattr(reactive, '_apt_registered'): # We need to register this to run every hook, not just during install # and config-changed, to protect against race conditions. If we don't # do this, then the config in the hook environment may show updates # to running hooks well before the config-changed hook has been invoked # and the intialization provided an opertunity to be run. hookenv.atstart(configure_sources) reactive._apt_registered = True
def queue_layer_packages(): """Add packages listed in build-time layer options.""" # Both basic and apt layer. basic layer will have already installed # its defined packages, but rescheduling it here gets the apt layer # state set and they will pinned as any other apt layer installed # package. opts = layer.options() for section in ['basic', 'apt']: if section in opts and 'packages' in opts[section]: charms.apt.queue_install(opts[section]['packages']) # Per https://github.com/juju-solutions/charms.reactive/issues/33, # this module may be imported multiple times so ensure the # initialization hook is only registered once. I have to piggy back # onto the namespace of a module imported before reactive discovery # to do this. if not hasattr(reactive, '_apt_registered'): # We need to register this to run every hook, not just during install # and config-changed, to protect against race conditions. If we don't # do this, then the config in the hook environment may show updates # to running hooks well before the config-changed hook has been invoked # and the intialization provided an opertunity to be run. hookenv.atstart(hookenv.log, 'Initializing Apt Layer') hookenv.atstart(clear_removed_package_states) hookenv.atstart(configure_sources) hookenv.atstart(queue_layer_packages) reactive._apt_registered = True
def remove_snap_proxy_conf(path): if os.path.exists(path): os.remove(path) def ensure_path(): # Per Bug #1662856, /snap/bin may be missing from $PATH. Fix this. if '/snap/bin' not in os.environ['PATH'].split(':'): os.environ['PATH'] += ':/snap/bin' # Per https://github.com/juju-solutions/charms.reactive/issues/33, # this module may be imported multiple times so ensure the # initialization hook is only registered once. I have to piggy back # onto the namespace of a module imported before reactive discovery # to do this. if not hasattr(reactive, '_snap_registered'): # We need to register this to run every hook, not just during install # and config-changed, to protect against race conditions. If we don't # do this, then the config in the hook environment may show updates # to running hooks well before the config-changed hook has been invoked # and the intialization provided an opertunity to be run. hookenv.atstart(hookenv.log, 'Initializing Snap Layer') hookenv.atstart(ensure_snapd) hookenv.atstart(ensure_path) hookenv.atstart(update_snap_proxy) hookenv.atstart(install) reactive._snap_registered = True
from charmhelpers.core import hookenv from charms.reactive import trace hookenv.atstart(trace.install_tracer, trace.LogTracer())
if not value: continue if key not in value['conversations']: continue value['conversations'].remove(key) value['conversations'].extend(new_keys) set_flag(flag, value) @cmdline.subcommand() def relation_call(method, relation_name=None, flag=None, state=None, *args): """Invoke a method on the class implementing a relation via the CLI""" if relation_name: relation = relation_from_name(relation_name) if relation is None: raise ValueError('Relation not found: %s' % relation_name) elif flag or state: relation = relation_from_flag(flag or state) if relation is None: raise ValueError('Relation not found: %s' % (flag or state)) else: raise ValueError('Must specify either relation_name or flag') result = getattr(relation, method)(*args) if isinstance(relation, RelationBase) and method == 'conversations': # special case for conversations to make them work from CLI result = [c.scope for c in result] return result hookenv.atstart(RelationBase._startup)
return else: if data_changed("layer.vault-kv.config", config): set_flag("layer.vault-kv.config.changed") def manage_app_kv_flags(): try: app_kv = vault_kv.VaultAppKV() for key in app_kv.keys(): app_kv._manage_flags(key) except vault_kv.VaultNotReady: vault_kv.VaultAppKV._clear_all_flags() def update_app_kv_hashes(): try: app_kv = vault_kv.VaultAppKV() if app_kv.any_changed(): if hookenv.is_leader(): # force hooks to run on non-leader units hookenv.leader_set({"vault-kv-nonce": host.pwgen(8)}) # Update the local unit hashes at successful exit app_kv.update_hashes() except vault_kv.VaultNotReady: return hookenv.atstart(manage_app_kv_flags) hookenv.atexit(update_app_kv_hashes)
def test_manage_calls_atstart(self): cb = mock.MagicMock() hookenv.atstart(cb) manager = services.ServiceManager() manager.manage() self.assertTrue(cb.called)
def queue_layer_packages(): """Add packages listed in build-time layer options.""" # Both basic and apt layer. basic layer will have already installed # its defined packages, but rescheduling it here gets the apt layer # state set and they will pinned as any other apt layer installed # package. opts = layer.options() for section in ['basic', 'apt']: if section in opts and 'packages' in opts[section]: charms.apt.queue_install(opts[section]['packages']) # Per https://github.com/juju-solutions/charms.reactive/issues/33, # this module may be imported multiple times so ensure the # initialization hook is only registered once. I have to piggy back # onto the namespace of a module imported before reactive discovery # to do this. if not hasattr(reactive, '_apt_registered'): # We need to register this to run every hook, not just during install # and config-changed, to protect against race conditions. If we don't # do this, then the config in the hook environment may show updates # to running hooks well before the config-changed hook has been invoked # and the intialization provided an opertunity to be run. hookenv.atstart(hookenv.log, 'Initializing Apt Layer') hookenv.atstart(clear_removed_package_states) hookenv.atstart(configure_sources) hookenv.atstart(queue_layer_packages) hookenv.atstart(charms.apt.reset_application_version) reactive._apt_registered = True
universal_newlines=True, ) except subprocess.CalledProcessError as e: raise InvalidBundleError("Proxy ID from header did not match store assertion: " + e.output) register_trigger(when="config.changed.snapd_refresh", clear_flag="snap.refresh.set") @when_not("snap.refresh.set") @when("snap.installed.core") def change_snapd_refresh(): """Set the system refresh.timer option""" ensure_snapd_min_version("2.31") timer = hookenv.config()["snapd_refresh"] was_set = reactive.is_flag_set("snap.refresh.was-set") if timer or was_set: snap.set_refresh_timer(timer) reactive.toggle_flag("snap.refresh.was-set", timer) reactive.set_flag("snap.refresh.set") # Bootstrap. We don't use standard reactive handlers to ensure that # everything is bootstrapped before any charm handlers are run. hookenv.atstart(hookenv.log, "Initializing Snap Layer") hookenv.atstart(ensure_snapd) hookenv.atstart(ensure_path) hookenv.atstart(update_snap_proxy) hookenv.atstart(configure_snap_store_proxy) hookenv.atstart(install)
check_call([ "/usr/bin/python3", "-m", "venv", "--prompt", ENV_NAME, str(ENV_DIR) ]) # venv.create(str(ENV_DIR), with_pip=True) set_flag("venv.active") @when("venv.active", "venv.ready") def reconfigure(): cfg = options.get("venv") if "packages" in cfg: if helpers.data_changed("venv-packages", cfg["packages"]): clear_flag("venv.ready") if helpers.data_changed("venv-name", ENV_NAME): clear_flag("venv.active") @when("venv.active") @when_not("venv.ready") def add_pip_to_venv(): cfg = options.get("venv") for package in cfg["packages"]: pip_install(package) set_flag("venv.ready") hookenv.atstart(init)
""" Whether this collection can be modified. """ return self.raw_data.writeable def get(self, key, default=None): if key not in self.raw_data: return default return self[key] def __getitem__(self, key): value = self.raw_data[key] if not value: return value try: return json.loads(value) except Exception: # Catch json.JSONDecodeError when we drop Python 3.4 support. return value def __setitem__(self, key, value): self.raw_data[key] = json.dumps(value, sort_keys=True) def setdefault(self, key, value): if key not in self: self[key] = value return self[key] hookenv.atstart(Endpoint._startup)
raise InvalidBundleError( 'Proxy ID from header did not match store assertion: ' + e.output) register_trigger(when='config.changed.snapd_refresh', clear_flag='snap.refresh.set') @when_not('snap.refresh.set') @when('snap.installed.core') def change_snapd_refresh(): """Set the system refresh.timer option""" ensure_snapd_min_version('2.31') timer = hookenv.config()['snapd_refresh'] was_set = reactive.is_flag_set('snap.refresh.was-set') if timer or was_set: snap.set_refresh_timer(timer) reactive.toggle_flag('snap.refresh.was-set', timer) reactive.set_flag('snap.refresh.set') # Bootstrap. We don't use standard reactive handlers to ensure that # everything is bootstrapped before any charm handlers are run. hookenv.atstart(hookenv.log, 'Initializing Snap Layer') hookenv.atstart(ensure_snapd) hookenv.atstart(ensure_path) hookenv.atstart(update_snap_proxy) hookenv.atstart(configure_snap_store_proxy) hookenv.atstart(install) hookenv.atstart(check_refresh_available)
# Set reactive state for requested locks. for lock in requested: log('Requested {} lock'.format(lock), hookenv.DEBUG) charms.reactive.set_state('coordinator.requested.{}'.format(lock)) # Set reactive state for locks that have been granted. for lock in granted: log('Granted {} lock'.format(lock), hookenv.DEBUG) charms.reactive.set_state('coordinator.granted.{}'.format(lock)) # Remove reactive state for locks that have been released. for lock in (previously_granted - granted): log('Dropped {} lock'.format(lock), hookenv.DEBUG) charms.reactive.remove_state('coordinator.granted.{}'.format(lock)) # Remove requested state for locks no longer requested and not granted. for lock in (previously_requested - requested - granted): log('Request for {} lock was dropped'.format(lock), hookenv.DEBUG) charms.reactive.remove_state('coordinator.requested.{}'.format(lock)) # Per https://github.com/juju-solutions/charms.reactive/issues/33, # this module may be imported multiple times so ensure the # initialization hook is only registered once. I have to piggy back # onto the namespace of a module imported before reactive discovery # to do this. if not hasattr(charms.reactive, '_coordinator_registered'): hookenv.atstart(initialize_coordinator_state) charms.reactive._coordinator_registered = True
self._flush() self._active_handlers = next_handlers _tracer = None def install_tracer(tracer): global _tracer _tracer = tracer # Disable tracing when we hit atexit, to avoid spam from layers # such as when the base layer tears down its automatic flags. hookenv.atexit(install_tracer, NullTracer()) def tracer(): global _tracer return _tracer # On import, the NullTracer is installed to avoid unit tests having # to mock hookenv.log. install_tracer(NullTracer()) # Install the LogTracer by default, after discovery, before running # handlers. If this is too noisy for some, we can make it optional # via layer.yaml. Using hookenv.atstart, because the tests already # mock it. hookenv.atstart(install_tracer, LogTracer())