chore: consolidate platform split checks
This commit is contained in:
@@ -27,6 +27,7 @@ from govoplan_core.core.module_installer import (
|
||||
update_module_installer_request,
|
||||
update_module_installer_daemon_status,
|
||||
)
|
||||
from govoplan_core.core.module_license import issue_module_license, module_license_diagnostics
|
||||
from govoplan_core.core.module_package_catalog import sign_module_package_catalog, validate_module_package_catalog
|
||||
from govoplan_core.core.module_management import (
|
||||
configured_enabled_modules,
|
||||
@@ -80,10 +81,69 @@ def main() -> int:
|
||||
parser.add_argument("--catalog-signing-key-id", help="Key id to record when signing a package catalog.")
|
||||
parser.add_argument("--catalog-signing-private-key", type=Path, help="PEM Ed25519 private key used by --sign-package-catalog.")
|
||||
parser.add_argument("--catalog-output", type=Path, help="Output path for --sign-package-catalog. Defaults to overwriting the input file.")
|
||||
parser.add_argument("--validate-license", nargs="?", const="", metavar="PATH", help="Validate a license JSON file, or the configured license when PATH is omitted.")
|
||||
parser.add_argument("--require-trusted-license", action="store_true", help="Require license validation to trust an Ed25519 signature.")
|
||||
parser.add_argument("--license-trusted-key", action="append", default=[], metavar="KEY_ID=BASE64_PUBLIC_KEY", help="Trusted Ed25519 license public key; may be repeated.")
|
||||
parser.add_argument("--license-required-feature", action="append", default=[], help="License feature to check; may be repeated.")
|
||||
parser.add_argument("--issue-license", type=Path, metavar="PATH", help="Write a signed offline license JSON file.")
|
||||
parser.add_argument("--license-id", help="License id for --issue-license.")
|
||||
parser.add_argument("--license-subject", help="License subject/customer for --issue-license.")
|
||||
parser.add_argument("--license-feature", action="append", default=[], help="Feature entitlement to include in --issue-license; may be repeated.")
|
||||
parser.add_argument("--license-valid-from", help="License valid_from timestamp. Defaults to now.")
|
||||
parser.add_argument("--license-valid-until", help="License valid_until timestamp.")
|
||||
parser.add_argument("--license-signing-key-id", help="Signing key id for --issue-license.")
|
||||
parser.add_argument("--license-signing-private-key", type=Path, help="PEM Ed25519 private key for --issue-license.")
|
||||
parser.add_argument("--license-issuer", help="Optional issuer string for --issue-license.")
|
||||
parser.add_argument("--license-notes", help="Optional operator note for --issue-license.")
|
||||
args = parser.parse_args()
|
||||
|
||||
runtime_dir = args.runtime_dir or default_installer_runtime_dir(args.database_url)
|
||||
try:
|
||||
if args.issue_license:
|
||||
if not args.license_id or not args.license_subject or not args.license_valid_until:
|
||||
raise ModuleInstallerError("--issue-license requires --license-id, --license-subject, and --license-valid-until.")
|
||||
if not args.license_signing_key_id or not args.license_signing_private_key:
|
||||
raise ModuleInstallerError("--issue-license requires --license-signing-key-id and --license-signing-private-key.")
|
||||
if not [item for item in args.license_feature if item.strip()]:
|
||||
raise ModuleInstallerError("--issue-license requires at least one --license-feature.")
|
||||
try:
|
||||
path = issue_module_license(
|
||||
path=args.issue_license,
|
||||
license_id=args.license_id,
|
||||
subject=args.license_subject,
|
||||
features=args.license_feature,
|
||||
valid_from=args.license_valid_from,
|
||||
valid_until=args.license_valid_until,
|
||||
signing_key_id=args.license_signing_key_id,
|
||||
signing_private_key_path=args.license_signing_private_key,
|
||||
issuer=args.license_issuer,
|
||||
notes=args.license_notes,
|
||||
)
|
||||
except (OSError, ValueError) as exc:
|
||||
raise ModuleInstallerError(str(exc)) from exc
|
||||
_print_result(
|
||||
{
|
||||
"issued": True,
|
||||
"path": str(path),
|
||||
"license_id": args.license_id,
|
||||
"subject": args.license_subject,
|
||||
"features": list(dict.fromkeys(item.strip() for item in args.license_feature if item.strip())),
|
||||
"valid_until": args.license_valid_until,
|
||||
"key_id": args.license_signing_key_id,
|
||||
},
|
||||
output_format=args.format,
|
||||
)
|
||||
return 0
|
||||
if args.validate_license is not None:
|
||||
path = Path(args.validate_license).expanduser() if args.validate_license else None
|
||||
result = module_license_diagnostics(
|
||||
path,
|
||||
require_trusted=args.require_trusted_license,
|
||||
trusted_keys=_trusted_keys_from_args(args.license_trusted_key, flag_name="--license-trusted-key"),
|
||||
required_features=args.license_required_feature,
|
||||
)
|
||||
_print_result(result, output_format=args.format)
|
||||
return 0 if result.get("valid") and not result.get("missing_features") else 1
|
||||
if args.sign_package_catalog:
|
||||
if not args.catalog_signing_key_id or not args.catalog_signing_private_key:
|
||||
raise ModuleInstallerError("--sign-package-catalog requires --catalog-signing-key-id and --catalog-signing-private-key.")
|
||||
@@ -101,7 +161,7 @@ def main() -> int:
|
||||
path,
|
||||
require_trusted=args.require_signed_catalog,
|
||||
approved_channels=tuple(args.approved_catalog_channel),
|
||||
trusted_keys=_trusted_catalog_keys_from_args(args.catalog_trusted_key),
|
||||
trusted_keys=_trusted_keys_from_args(args.catalog_trusted_key, flag_name="--catalog-trusted-key"),
|
||||
)
|
||||
_print_result(result, output_format=args.format)
|
||||
return 0 if result.get("valid") else 1
|
||||
@@ -322,6 +382,7 @@ def _process_request(*, request: dict[str, object], args: argparse.Namespace, ru
|
||||
health_urls=_list_option(options, "health_urls") or _list_option(options, "health_url") or ([args.health_url] if args.health_url else []),
|
||||
health_timeout_seconds=_float_option(options, "health_timeout_seconds", args.health_timeout_seconds),
|
||||
health_interval_seconds=_float_option(options, "health_interval_seconds", args.health_interval_seconds),
|
||||
request_context=_request_context(request),
|
||||
)
|
||||
update_module_installer_request(
|
||||
runtime_dir=runtime_dir,
|
||||
@@ -362,6 +423,18 @@ def _request_options_from_args(args: argparse.Namespace) -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def _request_context(request: dict[str, object]) -> dict[str, object]:
|
||||
context: dict[str, object] = {
|
||||
"request_id": request.get("request_id"),
|
||||
"requested_by": request.get("requested_by"),
|
||||
}
|
||||
if request.get("retry_of"):
|
||||
context["retry_of"] = request["retry_of"]
|
||||
if isinstance(request.get("trace"), dict):
|
||||
context["trace"] = request["trace"]
|
||||
return {key: value for key, value in context.items() if value is not None}
|
||||
|
||||
|
||||
def _str_option(options: object, key: str) -> str | None:
|
||||
if not isinstance(options, dict):
|
||||
return None
|
||||
@@ -403,14 +476,14 @@ def _list_option(options: object, key: str) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
def _trusted_catalog_keys_from_args(values: list[str]) -> dict[str, str] | None:
|
||||
def _trusted_keys_from_args(values: list[str], *, flag_name: str) -> dict[str, str] | None:
|
||||
if not values:
|
||||
return None
|
||||
keys: dict[str, str] = {}
|
||||
for value in values:
|
||||
key_id, separator, public_key = value.partition("=")
|
||||
if not separator or not key_id.strip() or not public_key.strip():
|
||||
raise ModuleInstallerError("--catalog-trusted-key must use KEY_ID=BASE64_PUBLIC_KEY.")
|
||||
raise ModuleInstallerError(f"{flag_name} must use KEY_ID=BASE64_PUBLIC_KEY.")
|
||||
keys[key_id.strip()] = public_key.strip()
|
||||
return keys
|
||||
|
||||
|
||||
Reference in New Issue
Block a user