Skip to content

Commit

Permalink
Merge !1512: datamodel: additional headers for json-schema
Browse files Browse the repository at this point in the history
  • Loading branch information
vcunat committed Sep 11, 2024
2 parents 49f0ed4 + 1985767 commit f318fd8
Show file tree
Hide file tree
Showing 11 changed files with 1,786 additions and 18 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
/doc/html
/doc/kresd.8
/doc/texinfo
/doc/_static/config.schema.json
/doc/_static/schema_doc*
/doc/config-schema-body.md
/ephemeral_key.pem
Expand Down
1,703 changes: 1,703 additions & 0 deletions doc/_static/config.schema.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions python/knot_resolver/client/commands/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import List, Optional, Tuple, Type

from knot_resolver.client.command import Command, CommandArgs, CompWords, register_command
from knot_resolver.datamodel.config_schema import KresConfig
from knot_resolver.datamodel import kres_config_json_schema
from knot_resolver.utils.requests import request


Expand Down Expand Up @@ -46,7 +46,7 @@ def run(self, args: CommandArgs) -> None:
sys.exit(1)
schema = response.body
else:
schema = json.dumps(KresConfig.json_schema(), indent=4)
schema = json.dumps(kres_config_json_schema(), indent=4)

if self.file:
with open(self.file, "w") as f:
Expand Down
4 changes: 2 additions & 2 deletions python/knot_resolver/datamodel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .config_schema import KresConfig
from .config_schema import KresConfig, kres_config_json_schema

__all__ = ["KresConfig"]
__all__ = ["KresConfig", "kres_config_json_schema"]
36 changes: 31 additions & 5 deletions python/knot_resolver/datamodel/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import socket
from typing import Any, Dict, List, Literal, Optional, Tuple, Union

from knot_resolver.constants import API_SOCK_PATH_DEFAULT, RUN_DIR_DEFAULT, WORKERS_MAX_DEFAULT
from knot_resolver.constants import API_SOCK_PATH_DEFAULT, RUN_DIR_DEFAULT, VERSION, WORKERS_MAX_DEFAULT
from knot_resolver.datamodel.cache_schema import CacheSchema
from knot_resolver.datamodel.dns64_schema import Dns64Schema
from knot_resolver.datamodel.dnssec_schema import DnssecSchema
from knot_resolver.datamodel.forward_schema import ForwardSchema
from knot_resolver.datamodel.globals import Context, get_global_validation_context, set_global_validation_context
from knot_resolver.datamodel.local_data_schema import LocalDataSchema, RPZSchema, RuleSchema
from knot_resolver.datamodel.logging_schema import LoggingSchema
from knot_resolver.datamodel.lua_schema import LuaSchema
Expand Down Expand Up @@ -37,7 +38,7 @@ def _cpu_count() -> Optional[int]:
return cpus


def _default_max_worker_count() -> int:
def _workers_max_count() -> int:
c = _cpu_count()
if c:
return c * 10
Expand Down Expand Up @@ -110,7 +111,7 @@ class Raw(ConfigSchema):
hostname: Optional[EscapedStr] = None
rundir: WritableDir = lazy_default(WritableDir, str(RUN_DIR_DEFAULT))
workers: Union[Literal["auto"], IntPositive] = IntPositive(1)
max_workers: IntPositive = IntPositive(_default_max_worker_count())
max_workers: IntPositive = IntPositive(WORKERS_MAX_DEFAULT)
management: ManagementSchema = lazy_default(ManagementSchema, {"unix-socket": str(API_SOCK_PATH_DEFAULT)})
webmgmt: Optional[WebmgmtSchema] = None
options: OptionsSchema = OptionsSchema()
Expand Down Expand Up @@ -174,8 +175,11 @@ def _dns64(self, obj: Raw) -> Any:

def _validate(self) -> None:
# enforce max-workers config
if int(self.workers) > int(self.max_workers):
raise ValueError(f"can't run with more workers then the configured maximum {self.max_workers}")
workers_max = _workers_max_count()
if int(self.workers) > workers_max:
raise ValueError(
f"can't run with more workers then the recommended maximum {workers_max} or hardcoded {WORKERS_MAX_DEFAULT}"
)

# sanity check
cpu_count = _cpu_count()
Expand Down Expand Up @@ -234,3 +238,25 @@ def get_rundir_without_validation(data: Dict[str, Any]) -> WritableDir:
"""

return WritableDir(data["rundir"] if "rundir" in data else RUN_DIR_DEFAULT, object_path="/rundir")


def kres_config_json_schema() -> Dict[str, Any]:
"""
At this moment, to create any instance of 'ConfigSchema' even with default values, it is necessary to set the global context.
In the case of generating a JSON schema, strict validation must be turned off, otherwise it may happen that the creation of the JSON schema fails,
It may fail due to non-existence of the directory/file or their rights.
This should be fixed in the future. For more info, see 'datamodel.globals.py' module.
"""

context = get_global_validation_context()
set_global_validation_context(Context(None, False))

schema = KresConfig.json_schema(
schema_id=f"https://www.knot-resolver.cz/documentation/v{VERSION}/_static/config.schema.json",
title="Knot Resolver configuration JSON schema",
description=f"Version Knot Resolver {VERSION}",
)
# setting back to previous values
set_global_validation_context(context)

return schema
4 changes: 4 additions & 0 deletions python/knot_resolver/datamodel/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ def set_global_validation_context(context: Context) -> None:
_global_context = context


def get_global_validation_context() -> Context:
return _global_context


def reset_global_validation_context() -> None:
global _global_context
_global_context = Context(None)
Expand Down
3 changes: 2 additions & 1 deletion python/knot_resolver/manager/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from knot_resolver.controller import get_best_controller_implementation
from knot_resolver.controller.exceptions import SubprocessControllerExecException
from knot_resolver.controller.registered_workers import command_single_registered_worker
from knot_resolver.datamodel import kres_config_json_schema
from knot_resolver.datamodel.cache_schema import CacheClearRPCSchema
from knot_resolver.datamodel.config_schema import KresConfig, get_rundir_without_validation
from knot_resolver.datamodel.globals import Context, set_global_validation_context
Expand Down Expand Up @@ -282,7 +283,7 @@ async def _handler_cache_clear(self, request: web.Request) -> web.Response:

async def _handler_schema(self, _request: web.Request) -> web.Response:
return web.json_response(
KresConfig.json_schema(), headers={"Access-Control-Allow-Origin": "*"}, dumps=partial(json.dumps, indent=4)
kres_config_json_schema(), headers={"Access-Control-Allow-Origin": "*"}, dumps=partial(json.dumps, indent=4)
)

async def _handle_view_schema(self, _request: web.Request) -> web.Response:
Expand Down
23 changes: 20 additions & 3 deletions python/knot_resolver/utils/modeling/base_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,14 +754,31 @@ def __eq__(self, o: object) -> bool:
return True

@classmethod
def json_schema(cls: Type["BaseSchema"], include_schema_definition: bool = True) -> Dict[Any, Any]:
def json_schema(
cls: Type["BaseSchema"],
schema_id: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
include_schema_definition: bool = True,
) -> Dict[Any, Any]:
if cls._LAYER is not None:
return cls._LAYER.json_schema(include_schema_definition=include_schema_definition)
return cls._LAYER.json_schema(
schema_id=schema_id,
title=title,
description=description,
include_schema_definition=include_schema_definition,
)

schema: Dict[Any, Any] = {}
if include_schema_definition:
schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"
if cls.__doc__ is not None:
if schema_id is not None:
schema["$id"] = schema_id
if title is not None:
schema["title"] = title
if description is not None:
schema["description"] = description
elif cls.__doc__ is not None:
schema["description"] = _split_docstring(cls.__doc__)[0]
schema["type"] = "object"
schema["properties"] = _get_properties_schema(cls)
Expand Down
5 changes: 1 addition & 4 deletions scripts/meson/make-doc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
set -o errexit -o nounset
cd "$(dirname "${0}")/../.."

# generate JSON schema for the manager's declarative config
## the following python command should hopefully run without any dependencies except for standard python
mkdir -p doc/_static/
python3 -m python.knot_resolver.client schema > doc/_static/config.schema.json
# convert JSON schema to html
generate-schema-doc --config expand_buttons=true doc/_static/config.schema.json doc/_static/schema_doc.html

# generating the user documentation
Expand Down
6 changes: 6 additions & 0 deletions scripts/poe-tasks/check
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ python setup.py --help > /dev/null
check_rv $?
echo

# check that doc/_static/config.schema.json is the latest
echo -e "${yellow}Checking doc/_static/config.schema.json${reset}"
python -m knot_resolver.client schema | diff - doc/_static/config.schema.json
check_rv $?
echo

# fancy messages at the end :)
if test "$aggregate_rv" -eq "0"; then
echo -e "${green}Everything looks great!${reset}"
Expand Down
15 changes: 15 additions & 0 deletions tests/packaging/interactive/schema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -e

kresctl schema
if [ "$?" -ne "0" ]; then
echo "Failed to generate JSON schema with 'kresctl'"
exit 1
fi

kresctl schema --live
if [ "$?" -ne "0" ]; then
echo "Failed to get JSON schema from the running resolver"
exit 1
fi

0 comments on commit f318fd8

Please sign in to comment.