from http import HTTPStatus
from pathlib import Path
from time import time
-from typing import Any, Optional, Set, Union, cast
+from typing import Any, List, Optional, Set, Union, cast
-from aiohttp import ETag, web
+from aiohttp import web
from aiohttp.web import middleware
from aiohttp.web_app import Application
from aiohttp.web_response import json_response
from knot_resolver_manager.datamodel.management_schema import ManagementSchema
from knot_resolver_manager.exceptions import CancelStartupExecInsteadException, KresManagerException
from knot_resolver_manager.kresd_controller import get_best_controller_implementation
+from knot_resolver_manager.utils import ignore_exceptions_optional
from knot_resolver_manager.utils.async_utils import readfile
from knot_resolver_manager.utils.functional import Result
from knot_resolver_manager.utils.modeling import ParsedTree, parse, parse_yaml
"""
Route handler for changing resolver configuration
"""
+ # There are a lot of local variables in here, but they are usually immutable (almost SSA form :) )
+ # pylint: disable=too-many-locals
# parse the incoming data
document_path = request.match_info["path"]
- etags = request.if_match
- last: ParsedTree = self.config_store.get().get_unparsed_data()
- update_with: ParsedTree = parse(await request.text(), request.content_type)
+ getheaders = ignore_exceptions_optional(List[str], None, KeyError)(request.headers.getall)
+ etags = getheaders("if-match")
+ not_etags = getheaders("if-none-match")
+ current_config: ParsedTree = self.config_store.get().get_unparsed_data()
+ if request.method == "GET":
+ update_with: Optional[ParsedTree] = None
+ else:
+ update_with = parse(await request.text(), request.content_type)
- if etags is not None and last.etag not in map(str, etags):
- return web.Response(status=HTTPStatus.PRECONDITION_FAILED)
+ # stop processing if etags
+ def strip_quotes(s: str) -> str:
+ return s.strip('"')
- op = cast(Literal["get", "post", "delete", "patch", "put"], request.method.lower())
- root, to_return = last.query(op, document_path, update_with)
+ status = HTTPStatus.NOT_MODIFIED if request.method in ("GET", "HEAD") else HTTPStatus.PRECONDITION_FAILED
+ if etags is not None and current_config.etag not in map(strip_quotes, etags):
+ return web.Response(status=status)
+ if not_etags is not None and current_config.etag in map(strip_quotes, not_etags):
+ return web.Response(status=status)
- # validate config
- config_validated = KresConfig(root)
+ # run query
+ op = cast(Literal["get", "post", "delete", "patch", "put"], request.method.lower())
+ new_config, to_return = current_config.query(op, document_path, update_with)
- # apply config
- await self.config_store.update(config_validated)
+ # update the config
+ if request.method != "GET":
+ # validate
+ config_validated = KresConfig(new_config)
+ # apply
+ await self.config_store.update(config_validated)
# return success
- res = web.Response(status=HTTPStatus.OK, text=str(to_return))
- res.etag = ETag(config_validated.get_unparsed_data().etag)
+ resp_text: Optional[str] = str(to_return) if to_return is not None else None
+ res = web.Response(status=HTTPStatus.OK, text=resp_text)
+ res.headers.add("ETag", f'"{new_config.etag}"')
return res
async def _handler_metrics(self, _request: web.Request) -> web.Response:
self.app.add_routes(
[
web.get("/", self._handler_index),
- web.post(r"/config{path:.*}", self._handler_config_query),
- web.put(r"/config{path:.*}", self._handler_config_query),
- web.patch(r"/config{path:.*}", self._handler_config_query),
- web.get(r"/config{path:.*}", self._handler_config_query),
- web.delete(r"/config{path:.*}", self._handler_config_query),
+ web.post(r"/v1/config{path:.*}", self._handler_config_query),
+ web.put(r"/v1/config{path:.*}", self._handler_config_query),
+ web.patch(r"/v1/config{path:.*}", self._handler_config_query),
+ web.get(r"/v1/config{path:.*}", self._handler_config_query),
+ web.delete(r"/v1/config{path:.*}", self._handler_config_query),
web.post("/stop", self._handler_stop),
web.get("/schema", self._handler_schema),
web.get("/schema/ui", self._handle_view_schema),