def test_from_environ(self): # JUJU_VERSION is not set v = JujuVersion.from_environ() self.assertEqual(v, JujuVersion('0.0.0')) os.environ['JUJU_VERSION'] = 'no' with self.assertRaisesRegex(RuntimeError, 'not a valid Juju version'): JujuVersion.from_environ() os.environ['JUJU_VERSION'] = '2.8.0' v = JujuVersion.from_environ() self.assertEqual(v, JujuVersion('2.8.0'))
def relation_set(self, relation_id, key, value, is_app): if not isinstance(is_app, bool): raise TypeError( 'is_app parameter to relation_set must be a boolean') if is_app: version = JujuVersion.from_environ() if not version.has_app_data(): raise RuntimeError( 'setting application data is not supported on Juju version {}' .format(version)) args = [ 'relation-set', '-r', str(relation_id), '{}={}'.format(key, value) ] if is_app: args.append('--app') try: return self._run(*args) except ModelError as e: if 'relation not found' in str(e): raise RelationNotFoundError() from e raise
def __init__(self, charm_dir: Path): self._charm_dir = charm_dir self._exec_path = Path(os.environ.get('JUJU_DISPATCH_PATH', sys.argv[0])) dispatch = charm_dir / 'dispatch' if JujuVersion.from_environ().is_dispatch_aware() and _exe_path(dispatch) is not None: self._init_dispatch() else: self._init_legacy()
def _should_use_controller_storage(db_path: Path, meta: ops.charm.CharmMeta) -> bool: """Figure out whether we want to use controller storage or not.""" # if you've previously used local state, carry on using that if db_path.exists(): logger.debug("Using local storage: %s already exists", db_path) return False # if you're not in k8s you don't need controller storage if 'kubernetes' not in meta.series: logger.debug("Using local storage: not a kubernetes charm") return False # are we in a new enough Juju? cur_version = JujuVersion.from_environ() if cur_version.has_controller_storage(): logger.debug("Using controller storage: JUJU_VERSION=%s", cur_version) return True else: logger.debug("Using local storage: JUJU_VERSION=%s", cur_version) return False
def main(charm_class: ops.charm.CharmBase, use_juju_for_storage: bool = None): """Setup the charm and dispatch the observed event. The event name is based on the way this executable was called (argv[0]). Args: charm_class: your charm class. use_juju_for_storage: whether to use controller-side storage. If not specified then kubernetes charms that haven't previously used local storage and that are running on a new enough Juju default to controller-side storage, otherwise local storage is used. """ charm_dir = _get_charm_dir() model_backend = ops.model._ModelBackend() debug = ('JUJU_DEBUG' in os.environ) setup_root_logging(model_backend, debug=debug) logger.debug("Operator Framework %s up and running.", ops.__version__) dispatcher = _Dispatcher(charm_dir) dispatcher.run_any_legacy_hook() metadata = (charm_dir / 'metadata.yaml').read_text() actions_meta = charm_dir / 'actions.yaml' if actions_meta.exists(): actions_metadata = actions_meta.read_text() else: actions_metadata = None if not yaml.__with_libyaml__: logger.debug( 'yaml does not have libyaml extensions, using slower pure Python yaml loader' ) meta = ops.charm.CharmMeta.from_yaml(metadata, actions_metadata) model = ops.model.Model(meta, model_backend) charm_state_path = charm_dir / CHARM_STATE_FILE if use_juju_for_storage and not ops.storage.juju_backend_available(): # raise an exception; the charm is broken and needs fixing. msg = 'charm set use_juju_for_storage=True, but Juju version {} does not support it' raise RuntimeError(msg.format(JujuVersion.from_environ())) if use_juju_for_storage is None: use_juju_for_storage = _should_use_controller_storage( charm_state_path, meta) if use_juju_for_storage: if dispatcher.is_restricted_context(): # TODO: jam 2020-06-30 This unconditionally avoids running a collect metrics event # Though we eventually expect that juju will run collect-metrics in a # non-restricted context. Once we can determine that we are running collect-metrics # in a non-restricted context, we should fire the event as normal. logger.debug( '"%s" is not supported when using Juju for storage\n' 'see: https://github.com/canonical/operator/issues/348', dispatcher.event_name) # Note that we don't exit nonzero, because that would cause Juju to rerun the hook return store = ops.storage.JujuStorage() else: store = ops.storage.SQLiteStorage(charm_state_path) framework = ops.framework.Framework(store, charm_dir, meta, model) framework.set_breakpointhook() try: sig = inspect.signature(charm_class) try: sig.bind(framework) except TypeError: msg = ( "the second argument, 'key', has been deprecated and will be " "removed after the 0.7 release") warnings.warn(msg, DeprecationWarning) charm = charm_class(framework, None) else: charm = charm_class(framework) dispatcher.ensure_event_links(charm) # TODO: Remove the collect_metrics check below as soon as the relevant # Juju changes are made. Also adjust the docstring on # EventBase.defer(). # # Skip reemission of deferred events for collect-metrics events because # they do not have the full access to all hook tools. if not dispatcher.is_restricted_context(): framework.reemit() _emit_charm_event(charm, dispatcher.event_name) framework.commit() finally: framework.close()