async def test_config_change_emits_event(config_handler: ConfigHandler, all_events: List[Message]) -> None: # Put a config all_events.clear() config_id = ConfigId("foo") cfg = await config_handler.put_config(ConfigEntity(config_id, dict(test=1))) message = await wait_for_message(all_events, CoreMessage.ConfigUpdated, Event) assert message.data["id"] == cfg.id assert message.data["revision"] == cfg.revision # Patch a config all_events.clear() cfg = await config_handler.patch_config( ConfigEntity(config_id, dict(foo=2))) message = await wait_for_message(all_events, CoreMessage.ConfigUpdated, Event) assert message.data["id"] == cfg.id assert message.data["revision"] == cfg.revision # Delete a config all_events.clear() await config_handler.delete_config(config_id) message = await wait_for_message(all_events, CoreMessage.ConfigDeleted, Event) assert message.data["id"] == config_id assert "revision" not in message.data
async def __update_config(self) -> None: # in case the internal configuration holds new properties, we update the existing config always. try: existing = await self.config_handler.get_config(ResotoCoreConfigId) empty = empty_config().json() updated = deep_merge(empty, existing.config) if existing else empty updated = migrate_config(updated) if existing is None or updated != existing.config: await self.config_handler.put_config( ConfigEntity(ResotoCoreConfigId, updated), False) log.info("Default resoto config updated.") except Exception as ex: log.error(f"Could not update resoto default configuration: {ex}", exc_info=ex) # make sure there is a default command configuration # note: this configuration is only created one time and never updated try: existing_commands = await self.config_handler.get_config( ResotoCoreCommandsConfigId) if existing_commands is None: await self.config_handler.put_config( ConfigEntity(ResotoCoreCommandsConfigId, CustomCommandsConfig().json()), False) log.info("Default resoto commands config updated.") except Exception as ex: log.error(f"Could not update resoto command configuration: {ex}", exc_info=ex)
async def test_config(config_handler: ConfigHandler) -> None: # list is empty on start assert [a async for a in config_handler.list_config_ids()] == [] config_id = ConfigId("test") # add one entry entity = ConfigEntity(config_id, {"test": True}) assert await config_handler.put_config(entity) == entity # get one entry assert await config_handler.get_config(config_id) == entity # patch the config assert await config_handler.patch_config( ConfigEntity(config_id, {"rest": False})) == ConfigEntity(config_id, { "test": True, "rest": False }) # list all configs assert [a async for a in config_handler.list_config_ids()] == ["test"] # delete the config assert await config_handler.delete_config(config_id) is None # list all configs assert [a async for a in config_handler.list_config_ids()] == []
async def test_config_validation(config_handler: ConfigHandler, config_model: List[Kind]) -> None: await config_handler.update_configs_model(config_model) valid_config = { "section": { "some_number": 32, "some_string": "test", "some_sub": { "num": 32 } } } # define the model await config_handler.put_config_validation(ConfigValidation("test", True)) # list all available models assert [a async for a in config_handler.list_config_validation_ids() ] == ["test"] # get the model model: ConfigValidation = await config_handler.get_config_validation( "test") # type: ignore assert model.external_validation is True # check the config against the model invalid_config = {"section": {"some_number": "no number"}} invalid_config_id = ConfigId("invalid_config") with pytest.raises(AttributeError) as reason: await config_handler.put_config( ConfigEntity(invalid_config_id, invalid_config)) assert "some_number is not valid: Expected type int32 but got str" in str( reason.value) # External validation turned on: config with name "invalid_config" is rejected by the configured worker await config_handler.put_config_validation( ConfigValidation(invalid_config_id, True)) with pytest.raises(AttributeError) as reason: # The config is actually valid, but the external validation will fail await config_handler.put_config( ConfigEntity(invalid_config_id, valid_config)) assert "Error executing task: Invalid Config ;)" in str(reason) # If external validation is turned off, the configuration can be updated await config_handler.put_config_validation( ConfigValidation(invalid_config_id, False)) await config_handler.put_config( ConfigEntity(invalid_config_id, valid_config))
async def patch_config(self, request: Request) -> StreamResponse: config_id = request.match_info["config_id"] patch = await self.json_from_request(request) updated = await self.config_handler.patch_config( ConfigEntity(config_id, patch)) headers = {"Resoto-Config-Revision": updated.revision} return await single_result(request, updated.config, headers)
async def patch_config(self, cfg: ConfigEntity) -> ConfigEntity: current = await self.cfg_db.get(cfg.id) current_config = current.config if current else {} coerced = await self.coerce_and_check_model(cfg.id, {**current_config, **cfg.config}) result = await self.cfg_db.update(ConfigEntity(cfg.id, coerced, current.revision if current else None)) await self.message_bus.emit_event(CoreMessage.ConfigUpdated, dict(id=result.id, revision=result.revision)) return result
def configs() -> List[ConfigEntity]: return [ ConfigEntity(f"id_{a}", { "some": a, "config": "test" }) for a in range(0, 10) ]
async def put_config(self, request: Request) -> StreamResponse: config_id = request.match_info["config_id"] validate = request.query.get("validate", "true").lower() != "false" config = await self.json_from_request(request) result = await self.config_handler.put_config( ConfigEntity(config_id, config), validate) headers = {"Resoto-Config-Revision": result.revision} return await single_result(request, result.config, headers)
async def put_config(self, cfg: ConfigEntity, validate: bool = True) -> ConfigEntity: coerced = await self.coerce_and_check_model(cfg.id, cfg.config, validate) existing = await self.cfg_db.get(cfg.id) if not existing or existing.config != cfg.config: result = await self.cfg_db.update(ConfigEntity(cfg.id, coerced, cfg.revision)) await self.message_bus.emit_event(CoreMessage.ConfigUpdated, dict(id=result.id, revision=result.revision)) return result else: return existing
async def test_config_yaml(config_handler: ConfigHandler, config_model: List[Kind]) -> None: await config_handler.update_configs_model(config_model) config = { "some_number": 32, "some_string": "test", "some_sub": { "num": 32 } } expect_comment = dedent(""" section: # Some number. # And some description. some_number: 32 # Some string. # And some description. some_string: 'test' # Some sub. # And some description. some_sub: # Some arbitrary number. num: 32 """).strip() expect_no_comment = dedent(""" another_section: some_number: 32 some_string: test some_sub: num: 32 """).strip() # config has section with attached model test_config_id = ConfigId("test") await config_handler.put_config( ConfigEntity(test_config_id, {"section": config})) assert expect_comment in (await config_handler.config_yaml(test_config_id) or "") # different section with no attached model nomodel_config_id = ConfigId("no_model") await config_handler.put_config( ConfigEntity(nomodel_config_id, {"another_section": config})) assert expect_no_comment in (await config_handler.config_yaml(nomodel_config_id) or "")
def test_config_entity_roundtrip() -> None: entity = ConfigEntity(ConfigId("test"), {"test": 1}, "test") again = from_js(to_js(entity), ConfigEntity) assert entity == again