Skip to content

08 — CPCRUD Operations (08_cpcrud_operations.py)

Demonstrates offline JSON Schema validation (client.cpcrud.validate()), deterministic planning (client.cpcrud.plan()), and idempotent execution (client.cpcrud.apply()) using examples/crud_example.yaml.

Declarative Template (examples/crud_example.yaml)

examples/crud_example.yaml
# CPCRUD v2 example template: hosts, networks, TCP service, group, access rule, and NAT rule.
#
# Validated offline against `checkpoint_ops_schema.json` via `client.cpcrud.validate()`.
# Adjust `mgmt_name`, `domain`, `layer`, and `package` to match your target environment.

version: 1
mgmt_name: "mdsH5b"
domain: "Domain4"
on_name_conflict: "update"
on_ip_conflict: "reuse"

objects:
  - type: "host"
    name: "cpcrud-example-host-1"
    ipv4-address: "198.51.100.10"
    comments: "CPCRUD example host"
    color: "dark green"

  - type: "network"
    name: "cpcrud-example-net-1"
    subnet4: "198.51.100.0"
    mask-length4: 24
    comments: "CPCRUD example network"
    color: "blue"

services:
  - type: "service-tcp"
    name: "cpcrud-example-tcp-8443"
    port: "8443"
    comments: "CPCRUD example custom HTTPS port"

groups:
  - type: "group"
    name: "cpcrud-example-group"
    members:
      - "cpcrud-example-host-1"
      - "cpcrud-example-net-1"
    comments: "CPCRUD example group"

rules:
  - type: "access-rule"
    layer: "FPCR_UAT_Active Network"
    position: "bottom"
    name: "cpcrud-example-access-rule"
    source:
      - "cpcrud-example-host-1"
    destination:
      - "Any"
    service:
      - "cpcrud-example-tcp-8443"
    rule_action: "Drop"
    comments: "CPCRUD example access rule"

  - type: "nat-rule"
    package: "FPCR_UAT_Active"
    position: "top"
    name: "cpcrud-example-nat-rule"
    method: "hide"
    original-source: "cpcrud-example-host-1"
    original-destination: "Any"
    original-service: "Any"
    translated-source: "cpcrud-example-host-1"
    translated-destination: "Original"
    translated-service: "Original"
    comments: "CPCRUD example NAT rule"

Source Code (examples/08_cpcrud_operations.py)

examples/08_cpcrud_operations.py
"""Example 08 — Declarative CPCRUD operations (`validate`, `plan`, `apply`, and idempotency).

Run:
    uv run examples/08_cpcrud_operations.py

Demonstrates:
1. Loading `examples/crud_example.yaml` and validating it offline (`client.cpcrud.validate`).
2. Planning (`await client.cpcrud.plan`) and applying (`await client.cpcrud.apply`) the template.
3. Re-planning and re-applying the exact same template to prove idempotency (Pass 2 produces
   `unchanged` / `reuse` outcomes and zero `create` actions).
4. Wrapping execution in `_session_guard.guarded_session` so the lab domain is automatically
   reverted to its pre-run baseline revision on exit.
"""

from __future__ import annotations

import asyncio
import logging
import os
import sys
from pathlib import Path

import yaml
from dotenv import load_dotenv

load_dotenv(".env.test", override=True)
load_dotenv(".env.lib", override=True)
load_dotenv(".env.secrets", override=True)
logging.basicConfig(level=logging.WARNING)

sys.path.insert(0, str(Path(__file__).parent))
from _session_guard import guarded_session  # noqa: E402

from arodonata import ArodonataClient, ArodonataSettings  # noqa: E402

YAML_FILE = Path(__file__).parent / "crud_example.yaml"


async def main() -> None:
    """Validate, plan, and apply `examples/crud_example.yaml` twice to demonstrate idempotency."""
    settings = ArodonataSettings()
    mgmt_name = settings.mgmt_names[0]
    domain = os.getenv("EXAMPLE_DOMAIN") or os.getenv("TEST_DOMAIN_A", "Domain4")

    raw_template = yaml.safe_load(YAML_FILE.read_text(encoding="utf-8"))
    raw_template["mgmt_name"] = mgmt_name
    raw_template["domain"] = domain

    async with ArodonataClient(settings=settings) as client:
        # 1. Offline JSON Schema validation (zero network calls)
        print("[Step 1/4] Validating template against offline CPCRUD JSON Schema...", flush=True)
        errors = client.cpcrud.validate(raw_template)
        assert not errors, f"Template validation failed: {errors}"
        print("Template passed offline JSON Schema validation.")

        print(f"[Step 2/4] Capturing baseline revision on {mgmt_name}:{domain}...", flush=True)
        async with guarded_session(client, mgmt_name, domains=[domain]):
            for run in (1, 2):
                step_num = run + 1
                print(
                    f"[Step {step_num}/4] Planning and applying CPCRUD template (Run {run}/2)...",
                    flush=True,
                )
                plan = await client.cpcrud.plan(raw_template)
                print(f"\n--- Run {run}: Planned {len(plan.actions)} action(s) ---")
                for act in plan.actions:
                    print(
                        f"  plan:  [{act.mgmt_name}:{act.domain_name}] {act.outcome.value:9} {act.object_type} {act.name}"
                    )

                report = await client.cpcrud.apply(
                    plan,
                    session_name=f"cpcrud-example-run-{run}",
                )
                print(
                    f"Run {run} apply report: success={report.success} "
                    f"created={report.created_count} updated={report.updated_count} "
                    f"unchanged={report.unchanged_count} failed={report.failed_count}"
                )
                for res in report.results:
                    print(
                        f"  apply: [{res.mgmt_name}:{res.domain_name}] {res.outcome.value:9} {res.object_type} {res.name}"
                    )
            print(f"\nReverting {mgmt_name}:{domain} to baseline revision...", flush=True)


if __name__ == "__main__":
    asyncio.run(main())