Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CONNECTOR = ROOT / "plugins" / "1c" / "connector"
|
||||
PARSER = ROOT / "plugins" / "1c" / "parser"
|
||||
|
||||
|
||||
def require(condition: bool, message: str, failures: list[str]) -> None:
|
||||
if not condition:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
def read_yaml(path: Path) -> dict[str, Any]:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
data = yaml.safe_load(handle)
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
|
||||
def read_toml(path: Path) -> dict[str, Any]:
|
||||
with path.open("rb") as handle:
|
||||
data = tomllib.load(handle)
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
|
||||
def run_checks() -> dict[str, Any]:
|
||||
failures: list[str] = []
|
||||
required = [
|
||||
CONNECTOR / "adapter_1c_server.py",
|
||||
CONNECTOR / "contracts" / "openapi.yaml",
|
||||
CONNECTOR / "policies" / "read-only-query.yaml",
|
||||
CONNECTOR / "policies" / "sql-base-access-policy.yaml",
|
||||
CONNECTOR / "policies" / "change-workflow.yaml",
|
||||
CONNECTOR / "policies" / "config-layer-write-policy.yaml",
|
||||
CONNECTOR / "Dockerfile",
|
||||
CONNECTOR / "docker-compose.yml",
|
||||
CONNECTOR / ".env.example",
|
||||
CONNECTOR / "pyproject.toml",
|
||||
CONNECTOR / "service.yaml",
|
||||
CONNECTOR / "README.md",
|
||||
PARSER / "__init__.py",
|
||||
PARSER / "payload.py",
|
||||
PARSER / "cas_payload.py",
|
||||
]
|
||||
missing = [str(path.relative_to(ROOT)) for path in required if not path.exists()]
|
||||
require(not missing, f"missing standalone connector files: {missing}", failures)
|
||||
|
||||
service = read_yaml(CONNECTOR / "service.yaml") if (CONNECTOR / "service.yaml").exists() else {}
|
||||
compose = read_yaml(CONNECTOR / "docker-compose.yml") if (CONNECTOR / "docker-compose.yml").exists() else {}
|
||||
pyproject = read_toml(CONNECTOR / "pyproject.toml") if (CONNECTOR / "pyproject.toml").exists() else {}
|
||||
openapi = read_yaml(CONNECTOR / "contracts" / "openapi.yaml") if (CONNECTOR / "contracts" / "openapi.yaml").exists() else {}
|
||||
access_policy = read_yaml(CONNECTOR / "policies" / "sql-base-access-policy.yaml") if (CONNECTOR / "policies" / "sql-base-access-policy.yaml").exists() else {}
|
||||
|
||||
require(service.get("id") == "onec-adapter-connector", "service.yaml must identify onec-adapter-connector", failures)
|
||||
require(service.get("status") == "standalone-ready", "service.yaml status must be standalone-ready", failures)
|
||||
require((service.get("runtime") or {}).get("entrypoint") == "adapter_1c_server.py", "service entrypoint must be adapter_1c_server.py", failures)
|
||||
require("contracts/openapi.yaml" == (service.get("contracts") or {}).get("openapi"), "service must point to connector OpenAPI contract", failures)
|
||||
registered_policies = (service.get("contracts") or {}).get("policies") or []
|
||||
require("policies/sql-base-access-policy.yaml" in registered_policies, "service must register SQL base access policy", failures)
|
||||
|
||||
base_settings = access_policy.get("base_settings") if isinstance(access_policy.get("base_settings"), dict) else {}
|
||||
read_scope = access_policy.get("read_scope") if isinstance(access_policy.get("read_scope"), dict) else {}
|
||||
write_scope = access_policy.get("write_scope") if isinstance(access_policy.get("write_scope"), dict) else {}
|
||||
identity = access_policy.get("sql_identity_management") if isinstance(access_policy.get("sql_identity_management"), dict) else {}
|
||||
require(access_policy.get("status") == "active", "SQL base access policy must be active", failures)
|
||||
require(base_settings.get("selector") == "base_id", "SQL settings must be selected by base_id", failures)
|
||||
require(set(base_settings.get("required_fields") or []) == {"server", "database", "user"}, "SQL base settings must require server, database, and user", failures)
|
||||
require(read_scope.get("application_data") == "read_only", "application data must be read-only", failures)
|
||||
require(read_scope.get("metadata_structure") == "read_only", "metadata structure must be readable without mutation", failures)
|
||||
require(set((write_scope.get("allowed") or {}).values()) == {"ConfigSave", "ConfigCASSave"}, "only ConfigSave and ConfigCASSave may be write targets", failures)
|
||||
require(identity.get("mode") == "forbidden", "SQL identity management must be forbidden", failures)
|
||||
|
||||
project = pyproject.get("project") if isinstance(pyproject.get("project"), dict) else {}
|
||||
require(project.get("name") == "onec-adapter-connector", "pyproject project.name must be onec-adapter-connector", failures)
|
||||
scripts = project.get("scripts") if isinstance(project.get("scripts"), dict) else {}
|
||||
require(scripts.get("onec-adapter") == "adapter_1c_server:main", "pyproject must expose onec-adapter script", failures)
|
||||
dependencies = project.get("dependencies") if isinstance(project.get("dependencies"), list) else []
|
||||
require(any(str(dep).startswith("pymssql") for dep in dependencies), "pyproject must include pymssql dependency", failures)
|
||||
|
||||
services = compose.get("services") if isinstance(compose.get("services"), dict) else {}
|
||||
adapter_service = services.get("onec-adapter") if isinstance(services.get("onec-adapter"), dict) else {}
|
||||
build = adapter_service.get("build") if isinstance(adapter_service.get("build"), dict) else {}
|
||||
require(build.get("context") == "..", "docker-compose build context must include parser sibling", failures)
|
||||
require(build.get("dockerfile") == "connector/Dockerfile", "docker-compose must use connector/Dockerfile", failures)
|
||||
require(bool(adapter_service.get("healthcheck")), "docker-compose must define a healthcheck", failures)
|
||||
|
||||
require(openapi.get("openapi") == "3.1.0", "connector OpenAPI must parse as 3.1.0", failures)
|
||||
paths = openapi.get("paths") if isinstance(openapi.get("paths"), dict) else {}
|
||||
require("/health" in paths, "connector OpenAPI must expose /health", failures)
|
||||
require("/metadata/write-plan" in paths, "connector OpenAPI must expose /metadata/write-plan", failures)
|
||||
|
||||
return {
|
||||
"schema": "onec_connector_standalone_check.v1",
|
||||
"status": "ok" if not failures else "failed",
|
||||
"failures": failures,
|
||||
"checks": {
|
||||
"required_files": not missing,
|
||||
"service_manifest": service.get("id"),
|
||||
"pyproject": project.get("name"),
|
||||
"compose_service": "onec-adapter" in services,
|
||||
"openapi": openapi.get("openapi"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Check 1C connector standalone-ready service packaging.")
|
||||
parser.add_argument("--print", action="store_true")
|
||||
args = parser.parse_args()
|
||||
report = run_checks()
|
||||
if args.print or report["status"] != "ok":
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print("1C connector standalone status: ok")
|
||||
return 0 if report["status"] == "ok" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user