Skip to content

Commit

Permalink
Merge pull request #213 from nautobot/u/snaselj-config-schema
Browse files Browse the repository at this point in the history
App Config Schema
  • Loading branch information
snaselj authored Jan 26, 2024
2 parents 3ee746f + 18bb0c0 commit fbb7c77
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 49 deletions.
48 changes: 4 additions & 44 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
uses: "networktocode/gh-action-setup-poetry-environment@v4"
- name: "Linting: yamllint"
run: "poetry run invoke yamllint"
pylint:
check-in-docker:
needs:
- "bandit"
- "ruff"
Expand Down Expand Up @@ -136,53 +136,13 @@ jobs:
run: "cp development/creds.example.env development/creds.env"
- name: "Linting: pylint"
run: "poetry run invoke pylint"
check-migrations:
needs:
- "bandit"
- "ruff"
- "flake8"
- "poetry"
- "yamllint"
- "black"
runs-on: "ubuntu-22.04"
strategy:
fail-fast: true
matrix:
python-version: ["3.11"]
nautobot-version: ["2.0.0"]
env:
INVOKE_NAUTOBOT_FIREWALL_MODELS_PYTHON_VER: "${{ matrix.python-version }}"
INVOKE_NAUTOBOT_FIREWALL_MODELS_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
- name: "Setup environment"
uses: "networktocode/gh-action-setup-poetry-environment@v4"
- name: "Set up Docker Buildx"
id: "buildx"
uses: "docker/setup-buildx-action@v3"
- name: "Build"
uses: "docker/build-push-action@v5"
with:
builder: "${{ steps.buildx.outputs.name }}"
context: "./"
push: false
load: true
tags: "${{ env.APP_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}"
file: "./development/Dockerfile"
cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}"
cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}"
build-args: |
NAUTOBOT_VER=${{ matrix.nautobot-version }}
PYTHON_VER=${{ matrix.python-version }}
- name: "Copy credentials"
run: "cp development/creds.example.env development/creds.env"
- name: "Checking: App Config"
run: "poetry run invoke validate-app-config"
- name: "Checking: migrations"
run: "poetry run invoke check-migrations"
unittest:
needs:
- "pylint"
- "check-migrations"
- "check-in-docker"
strategy:
fail-fast: true
matrix:
Expand Down
3 changes: 3 additions & 0 deletions changes/213.added
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added `invoke generate-app-config-schema` command to generate a JSON schema for the App config.
Added `invoke validate-app-config` command to validate the App config against the schema.
Added App config JSON schema.
64 changes: 64 additions & 0 deletions development/app_config_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""App Config Schema Generator and Validator."""
import json
from importlib import import_module
from os import getenv
from pathlib import Path
from urllib.parse import urlparse

import jsonschema
import toml
from django.conf import settings
from to_json_schema.to_json_schema import SchemaBuilder


def _enrich_object_schema(schema, defaults, required):
schema["additionalProperties"] = False
for key, value in schema["properties"].items():
if required and key in required:
value["required"] = True
default_value = defaults and defaults.get(key, None)
if value["type"] == "object" and "properties" in value:
_enrich_object_schema(value, default_value, None)
elif default_value is not None:
value["default"] = default_value


def _main():
pyproject = toml.loads(Path("pyproject.toml").read_text())
url = urlparse(pyproject["tool"]["poetry"]["repository"])
_, owner, repository = url.path.split("/")
package_name = pyproject["tool"]["poetry"]["packages"][0]["include"]
app_config = settings.PLUGINS_CONFIG[package_name] # type: ignore
schema_path = Path(package_name) / "app-config-schema.json"
command = getenv("APP_CONFIG_SCHEMA_COMMAND", "")
if command == "generate":
schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": f"https://raw.githubusercontent.com/{owner}/{repository}/develop/{package_name}/app-config-schema.json",
"$comment": "TBD: Update $id, replace `develop` with the future release tag",
**SchemaBuilder().to_json_schema(app_config), # type: ignore
}
app_config = import_module(package_name).config
_enrich_object_schema(schema, app_config.default_settings, app_config.required_settings)
schema_path.write_text(json.dumps(schema, indent=4) + "\n")
print(f"\n==================\nGenerated schema:\n\n{schema_path}\n")
print(
"WARNING: Review and edit the generated file before committing.\n"
"\n"
"Its content is inferred from:\n"
"\n"
"- The current configuration in `PLUGINS_CONFIG`\n"
"- `NautobotAppConfig.default_settings`\n"
"- `NautobotAppConfig.required_settings`"
)
elif command == "validate":
schema = json.loads(schema_path.read_text())
jsonschema.validate(app_config, schema)
print(
f"\n==================\nValidated configuration using the schema:\n{schema_path}\nConfiguration is valid."
)
else:
raise RuntimeError(f"Unknown command: {command}")


_main()
30 changes: 30 additions & 0 deletions nautobot_firewall_models/app-config-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/nautobot/nautobot-app-firewall-models/develop/nautobot_firewall_models/app-config-schema.json",
"$comment": "TBD: Update $id, replace `develop` with the future release tag",
"type": "object",
"properties": {
"allowed_status": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"Active"
]
},
"capirca_remark_pass": {
"type": "boolean",
"default": true
},
"capirca_os_map": {
"type": "object",
"default": {}
},
"protect_on_delete": {
"type": "boolean",
"default": true
}
},
"additionalProperties": false
}
16 changes: 15 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ mkdocs-version-annotations = "1.0.0"
mkdocstrings = "0.22.0"
mkdocstrings-python = "1.5.2"
towncrier = "~23.6.0"
to-json-schema = "*"
jsonschema = "*"

[tool.poetry.extras]
all = [
Expand Down
46 changes: 42 additions & 4 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,27 @@ def docker_compose(context, command, **kwargs):
def run_command(context, command, **kwargs):
"""Wrapper to run a command locally or inside the nautobot container."""
if is_truthy(context.nautobot_firewall_models.local):
if "command_env" in kwargs:
kwargs["env"] = {
**kwargs.get("env", {}),
**kwargs.pop("command_env"),
}
context.run(command, **kwargs)
else:
# Check if nautobot is running, no need to start another nautobot container to run a command
docker_compose_status = "ps --services --filter status=running"
results = docker_compose(context, docker_compose_status, hide="out")
if "nautobot" in results.stdout:
compose_command = f"exec nautobot {command}"
compose_command = "exec"
else:
compose_command = f"run --rm --entrypoint '{command}' nautobot"
compose_command = "run --rm --entrypoint=''"

if "command_env" in kwargs:
command_env = kwargs.pop("command_env")
for key, value in command_env.items():
compose_command += f' --env="{key}={value}"'

compose_command += f" -- nautobot {command}"

pty = kwargs.pop("pty", True)

Expand Down Expand Up @@ -330,14 +342,15 @@ def logs(context, service="", follow=False, tail=0):
# ACTIONS
# ------------------------------------------------------------------------------
@task(help={"file": "Python file to execute"})
def nbshell(context, file=""):
def nbshell(context, file="", env={}, plain=False):
"""Launch an interactive nbshell session."""
command = [
"nautobot-server",
"nbshell",
"--plain" if plain else "",
f"< '{file}'" if file else "",
]
run_command(context, " ".join(command), pty=not bool(file))
run_command(context, " ".join(command), pty=not bool(file), command_env=env)


@task
Expand Down Expand Up @@ -801,8 +814,33 @@ def tests(context, failfast=False, keepdb=False, lint_only=False):
pylint(context)
print("Running mkdocs...")
build_and_check_docs(context)
print("Checking app config schema...")
validate_app_config(context)
if not lint_only:
print("Running unit tests...")
unittest(context, failfast=failfast, keepdb=keepdb)
unittest_coverage(context)
print("All tests have passed!")


@task
def generate_app_config_schema(context):
"""Generate the app config schema from the current app config.
WARNING: Review and edit the generated file before committing.
Its content is inferred from:
- The current configuration in `PLUGINS_CONFIG`
- `NautobotAppConfig.default_settings`
- `NautobotAppConfig.required_settings`
"""
start(context, service="nautobot")
nbshell(context, file="development/app_config_schema.py", env={"APP_CONFIG_SCHEMA_COMMAND": "generate"})


@task
def validate_app_config(context):
"""Validate the app config based on the app config schema."""
start(context, service="nautobot")
nbshell(context, plain=True, file="development/app_config_schema.py", env={"APP_CONFIG_SCHEMA_COMMAND": "validate"})

0 comments on commit fbb7c77

Please sign in to comment.