diff --git a/ops/__init__.py b/ops/__init__.py index 77c6b82c4..b318cebc6 100644 --- a/ops/__init__.py +++ b/ops/__init__.py @@ -55,6 +55,7 @@ 'CollectMetricsEvent', 'CollectStatusEvent', 'ConfigChangedEvent', + 'ConfigMeta', 'ContainerBase', 'ContainerMeta', 'ContainerStorageMeta', @@ -198,6 +199,7 @@ CollectMetricsEvent, CollectStatusEvent, ConfigChangedEvent, + ConfigMeta, ContainerBase, ContainerMeta, ContainerStorageMeta, diff --git a/ops/model.py b/ops/model.py index 7ca4e1381..70ccc2d13 100644 --- a/ops/model.py +++ b/ops/model.py @@ -118,7 +118,7 @@ def __init__(self, meta: 'ops.charm.CharmMeta', backend: '_ModelBackend', relations: Dict[str, 'ops.RelationMeta'] = meta.relations self._relations = RelationMapping(relations, self.unit, self._backend, self._cache, broken_relation_id=broken_relation_id) - self._config = ConfigData(self._backend) + self._config = ConfigData(self._backend, meta.config) resources: Iterable[str] = meta.resources self._resources = Resources(list(resources), self._backend) self._pod = Pod(self._backend) @@ -1724,11 +1724,20 @@ class ConfigData(LazyMapping): This class should not be instantiated directly. It should be accessed via :attr:`Model.config`. """ - def __init__(self, backend: '_ModelBackend'): + def __init__(self, backend: '_ModelBackend', + config: Optional[Dict[str, 'ops.ConfigMeta']] = None): self._backend = backend + self._config = config def _load(self): - return self._backend.config_get() + data = self._backend.config_get() + if self._config: + # Convert any type:secret options to Secret objects. + # The simple types (bool, int, float, str) are correctly typed in the JSON. + for option in self._config.values(): + if option.type == "secret" and option.name in data: + data[option.name] = Secret(self._backend, data[option.name]) + return data class StatusBase: diff --git a/ops/testing.py b/ops/testing.py index d33ecc4ff..ecf76c785 100644 --- a/ops/testing.py +++ b/ops/testing.py @@ -60,7 +60,7 @@ from ops import charm, framework, model, pebble, storage from ops._private import yaml from ops.charm import CharmBase, CharmMeta, RelationRole -from ops.model import Container, RelationNotFoundError, _ConfigOption, _NetworkDict +from ops.model import Container, RelationNotFoundError, Secret, _ConfigOption, _NetworkDict from ops.pebble import ExecProcess ReadableBuffer = Union[bytes, str, StringIO, BytesIO, BinaryIO] @@ -1307,7 +1307,7 @@ def _emit_relation_changed(self, relation_id: int, app_or_unit: str): def _update_config( self, - key_values: Optional[Mapping[str, Union[str, int, float, bool]]] = None, + key_values: Optional[Mapping[str, Union[str, int, float, bool, Secret]]] = None, unset: Iterable[str] = (), ) -> None: """Update the config as seen by the charm. @@ -1330,6 +1330,9 @@ def _update_config( for key, value in key_values.items(): if key in config._defaults: if value is not None: + if self._meta.config[key].type == 'secret' and isinstance(value, str): + # Promote this to an actual Secret object. + value = Secret(self._backend, value) config._config_set(key, value) else: raise ValueError(f"unknown config option: '{key}'") @@ -1344,7 +1347,7 @@ def _update_config( def update_config( self, - key_values: Optional[Mapping[str, Union[str, int, float, bool]]] = None, + key_values: Optional[Mapping[str, Union[str, int, float, bool, Secret]]] = None, unset: Iterable[str] = (), ) -> None: """Update the config as seen by the charm. diff --git a/test/test_model.py b/test/test_model.py index c1f990c65..d0b3b8ae6 100644 --- a/test/test_model.py +++ b/test/test_model.py @@ -59,6 +59,10 @@ def setUp(self): type: int qux: type: boolean + secretfoo: + type: secret + secretbar: + type: secret ''') self.addCleanup(self.harness.cleanup) self.relation_id_db0 = self.harness.add_relation('db0', 'db') @@ -618,11 +622,17 @@ def test_relation_no_units(self): def test_config(self): self.harness._get_backend_calls(reset=True) - self.harness.update_config({'foo': 'foo', 'bar': 1, 'qux': True}) + secretfoo_id = self.harness.add_user_secret({"password": "xxxxxxxx"}) + secretbar_id = self.harness.add_user_secret({"certificate": "xxxxxxxx"}) + secretbar = self.model.get_secret(id=secretbar_id) + self.harness.update_config({'foo': 'foo', 'bar': 1, 'qux': True, + 'secretfoo': secretfoo_id, 'secretbar': secretbar}) self.assertEqual(self.model.config, { 'foo': 'foo', 'bar': 1, 'qux': True, + 'secretfoo': self.model.get_secret(id=secretfoo_id), + 'secretbar': secretbar, }) with self.assertRaises(TypeError): # Confirm that we cannot modify config values.