"""Commands to manage Cerbos policy metadata.""" import asyncio import json import typer from pdp import config from pdp.connectors.cerbos_policy_parser import CerbosPolicyParser from pdp.connectors.redis_client import JSONSerializer, RedisConnector from pdp.constants.constants import CACHE_ENTRY_CERBOS_POLICY_METADATA cli: typer.Typer = typer.Typer( short_help="Commands to manage Cerbos policy metadata", no_args_is_help=True ) async def _seed_policy_metadata_cache(policies_dir: str, verbose: bool) -> None: """Crawl policies, build the metadata database, and write it to Redis.""" redis_connector = RedisConnector( config.REDIS_URL, use_redis_cache=config.CACHE_USE_REDIS, ) if not config.CACHE_USE_REDIS: typer.secho( "WARNING: CACHE_USE_REDIS is false — writing to an in-memory fake, " "not Redis. The seeded entry will not persist. Set CACHE_USE_REDIS=true " "to seed a real Redis instance.", fg="yellow", ) parser = CerbosPolicyParser(policies_dir=policies_dir) database = parser.build_database() data = json.loads(database.to_json()) # Fail fast on an empty database (missing/empty policies_dir). RedisConnector.set # no-ops on a falsy item, so writing {} would silently leave the cache unseeded. if not data: typer.secho( f"No policies found in '{policies_dir}'; nothing was written to " f"cache key '{CACHE_ENTRY_CERBOS_POLICY_METADATA}'.", fg="red", err=True, ) raise typer.Exit(code=1) if verbose: pretty = json.dumps(data, indent=2, sort_keys=True) typer.echo( f"Inserting cerbos policy metadata into " f"'{CACHE_ENTRY_CERBOS_POLICY_METADATA}': {pretty}" ) # The seeded entry is refreshed on every deploy by Jenkins and is required # for the service to start, so it must not expire (ttl=None). wrote = await redis_connector.set( key=CACHE_ENTRY_CERBOS_POLICY_METADATA, item=data, serializer=JSONSerializer(), ttl=None, ) # Exit non-zero on a failed write so docker compose / Jenkins stop early # instead of starting a service backed by a missing cache entry. if not wrote: typer.secho( f"Failed to write policy metadata to cache key " f"'{CACHE_ENTRY_CERBOS_POLICY_METADATA}'.", fg="red", err=True, ) raise typer.Exit(code=1) typer.secho( f"Wrote policy metadata database ({len(data)} resource types) to " f"cache key '{CACHE_ENTRY_CERBOS_POLICY_METADATA}'.", fg="green", ) @cli.command( "seed_policy_metadata_cache", short_help="Build and cache the policy metadata DB.", ) def seed_policy_metadata_cache( policies_dir: str = typer.Option( "cerbos/policies", help="Path to the Cerbos policies directory.", ), verbose: bool = typer.Option( False, "--verbose", "-v", help="Pretty-print the policy metadata being stored.", ), ) -> None: """Crawl cerbos/policies, build PolicyMetadataDatabase, write to Redis.""" asyncio.run(_seed_policy_metadata_cache(policies_dir, verbose))