From 2370e19da84e945ebcf1be92b80beb6ed2a6caca Mon Sep 17 00:00:00 2001 From: Hamza Khyari Date: Wed, 18 Mar 2026 04:37:35 +0000 Subject: [PATCH] feat(resource): add container infrastructure resource types Add 7 container infrastructure resource types per ADR-039: container-runtime, container-image, container-mount, container-exec-env, container-port, container-volume, container-network. - YAML configs under examples/resource-types/ - Bootstrap registration via _resource_registry_container.py - Updated container-instance parent_types (container-runtime, container-image) and child_types (container-mount, container-exec-env, container-port) - container-runtime is top-level with auto-discovery rules (scan_depth: 1) - container-mount/exec-env/port inherit snapshot sandbox from instance - container-volume is user-addable with snapshot sandbox - container-network is read-only, no sandbox - Handler references are forward declarations (ADR-039) - 33 Behave BDD scenarios, 4 Robot integration tests - Updated BUILTIN_NAMES, service docstring, CHANGELOG ISSUES CLOSED: #831 --- .gitignore | 1 + docs/reference/resource_types_builtin.md | 49 ++++ .../resource-types/container-exec-env.yaml | 16 ++ examples/resource-types/container-image.yaml | 25 ++ examples/resource-types/container-mount.yaml | 16 ++ .../resource-types/container-network.yaml | 16 ++ examples/resource-types/container-port.yaml | 16 ++ .../resource-types/container-runtime.yaml | 41 +++ examples/resource-types/container-volume.yaml | 25 ++ .../resource_type_container_infra.feature | 129 +++++++++ .../resource_type_container_infra_steps.py | 191 ++++++++++++ robot/helper_resource_type_container_infra.py | 129 +++++++++ robot/resource_type_container_infra.robot | 47 +++ .../services/_resource_registry_container.py | 272 ++++++++++++++++++ .../services/_resource_registry_data.py | 20 +- .../services/resource_registry_service.py | 7 + .../models/core/_resource_type_validation.py | 7 + .../domain/models/core/resource_type.py | 2 + 18 files changed, 1006 insertions(+), 3 deletions(-) create mode 100644 examples/resource-types/container-exec-env.yaml create mode 100644 examples/resource-types/container-image.yaml create mode 100644 examples/resource-types/container-mount.yaml create mode 100644 examples/resource-types/container-network.yaml create mode 100644 examples/resource-types/container-port.yaml create mode 100644 examples/resource-types/container-runtime.yaml create mode 100644 examples/resource-types/container-volume.yaml create mode 100644 features/resource_type_container_infra.feature create mode 100644 features/steps/resource_type_container_infra_steps.py create mode 100644 robot/helper_resource_type_container_infra.py create mode 100644 robot/resource_type_container_infra.robot create mode 100644 src/cleveragents/application/services/_resource_registry_container.py diff --git a/.gitignore b/.gitignore index 20c57982d..37395c432 100644 --- a/.gitignore +++ b/.gitignore @@ -167,3 +167,4 @@ PIPE # Git worktrees for parallel task branches worktrees/ +*.bak diff --git a/docs/reference/resource_types_builtin.md b/docs/reference/resource_types_builtin.md index 1d938eff6..11f9e28df 100644 --- a/docs/reference/resource_types_builtin.md +++ b/docs/reference/resource_types_builtin.md @@ -475,3 +475,52 @@ Sandbox: `copy_on_write`. A hard link (link count > 1). Parent: `fs-directory`. Leaf type. Sandbox: `copy_on_write`. + +--- + +# Container Infrastructure Resource Types (#831) + +## container-runtime + +A container runtime engine (Docker, Podman, containerd, etc.). +Top-level type with no parents. Children: `container-image`, +`container-instance`, `container-volume`, `container-network`. +Auto-discovers from Docker API endpoints and docker-compose files. +User-addable. Sandbox: `none`. + +## container-image + +A container image identified by tag and/or digest. +Parent: `container-runtime`. Child: `container-instance`. +Auto-discovers from Dockerfiles and docker-compose files. +User-addable. Sandbox: `none`. + +## container-mount + +A bind mount or volume mount inside a container instance. +Parent: `container-instance`. Children: `fs-directory`, `fs-file`. +Not user-addable (auto-discovered). Sandbox: `snapshot`. + +## container-exec-env + +Execution environment variables and configuration inside a container. +Parent: `container-instance`. Leaf type. +Not user-addable. Sandbox: `snapshot`. + +## container-port + +A port mapping between host and container (e.g. 8080:80/tcp). +Parent: `container-instance`. Leaf type. +Not user-addable. Sandbox: `snapshot`. + +## container-volume + +A named container volume managed by the runtime engine. +Parent: `container-runtime`. Child: `fs-directory`. +User-addable. Sandbox: `snapshot`. + +## container-network + +A container network (bridge, host, overlay, macvlan, or none). +Parent: `container-runtime`. Leaf type. +Not user-addable. Sandbox: `none`. diff --git a/examples/resource-types/container-exec-env.yaml b/examples/resource-types/container-exec-env.yaml new file mode 100644 index 000000000..9775c0b33 --- /dev/null +++ b/examples/resource-types/container-exec-env.yaml @@ -0,0 +1,16 @@ +# Spec reference: ADR-039, docs/specification.md ~lines 25096-25114 +schema_version: "1.0" +name: container-exec-env +description: "Execution environment variables and configuration inside a container" +resource_kind: physical +sandbox_strategy: snapshot +user_addable: false +built_in: true +parent_types: ["container-instance"] +child_types: [] +capabilities: + read: true + write: false + sandbox: true + checkpoint: true +handler: "cleveragents.resource.handlers.container:ContainerChildHandler" diff --git a/examples/resource-types/container-image.yaml b/examples/resource-types/container-image.yaml new file mode 100644 index 000000000..e5af818e3 --- /dev/null +++ b/examples/resource-types/container-image.yaml @@ -0,0 +1,25 @@ +# Spec reference: ADR-039, docs/specification.md ~lines 25096-25114 +schema_version: "1.0" +name: container-image +description: "A container image identified by tag and/or digest" +resource_kind: physical +sandbox_strategy: none +user_addable: true +built_in: true +cli_args: + - name: image-ref + type: string + required: true + description: "Image reference (e.g. ubuntu:22.04, ghcr.io/org/app:latest)" + - name: digest + type: string + required: false + description: "Image digest (sha256:...)" +parent_types: ["container-runtime"] +child_types: ["container-instance"] +capabilities: + read: true + write: false + sandbox: false + checkpoint: false +handler: "cleveragents.resource.handlers.container:ContainerImageHandler" diff --git a/examples/resource-types/container-mount.yaml b/examples/resource-types/container-mount.yaml new file mode 100644 index 000000000..b741d3137 --- /dev/null +++ b/examples/resource-types/container-mount.yaml @@ -0,0 +1,16 @@ +# Spec reference: ADR-039, docs/specification.md ~lines 25096-25114 +schema_version: "1.0" +name: container-mount +description: "A bind mount or volume mount inside a container instance" +resource_kind: physical +sandbox_strategy: snapshot +user_addable: false +built_in: true +parent_types: ["container-instance"] +child_types: ["fs-directory", "fs-file"] +capabilities: + read: true + write: true + sandbox: true + checkpoint: true +handler: "cleveragents.resource.handlers.container:ContainerChildHandler" diff --git a/examples/resource-types/container-network.yaml b/examples/resource-types/container-network.yaml new file mode 100644 index 000000000..5818d2541 --- /dev/null +++ b/examples/resource-types/container-network.yaml @@ -0,0 +1,16 @@ +# Spec reference: ADR-039, docs/specification.md ~lines 25096-25114 +schema_version: "1.0" +name: container-network +description: "A container network (bridge, host, overlay, macvlan, or none)" +resource_kind: physical +sandbox_strategy: none +user_addable: false +built_in: true +parent_types: ["container-runtime"] +child_types: [] +capabilities: + read: true + write: false + sandbox: false + checkpoint: false +handler: "cleveragents.resource.handlers.container:ContainerNetworkHandler" diff --git a/examples/resource-types/container-port.yaml b/examples/resource-types/container-port.yaml new file mode 100644 index 000000000..b5ef4d71e --- /dev/null +++ b/examples/resource-types/container-port.yaml @@ -0,0 +1,16 @@ +# Spec reference: ADR-039, docs/specification.md ~lines 25096-25114 +schema_version: "1.0" +name: container-port +description: "A port mapping between host and container (e.g. 8080:80/tcp)" +resource_kind: physical +sandbox_strategy: snapshot +user_addable: false +built_in: true +parent_types: ["container-instance"] +child_types: [] +capabilities: + read: true + write: false + sandbox: true + checkpoint: true +handler: "cleveragents.resource.handlers.container:ContainerChildHandler" diff --git a/examples/resource-types/container-runtime.yaml b/examples/resource-types/container-runtime.yaml new file mode 100644 index 000000000..053e1219a --- /dev/null +++ b/examples/resource-types/container-runtime.yaml @@ -0,0 +1,41 @@ +# Spec reference: ADR-039, docs/specification.md ~lines 25096-25114 +schema_version: "1.0" +name: container-runtime +description: "A container runtime engine (Docker, Podman, containerd, etc.)" +resource_kind: physical +sandbox_strategy: none +user_addable: true +built_in: true +cli_args: + - name: engine-type + type: string + required: true + description: "Container engine type (docker, podman, containerd, lxc, lxd, cri-o, other)" + - name: socket-path + type: path + required: false + description: "Path to the container engine socket" + - name: endpoint + type: string + required: false + description: "Remote endpoint URL for the engine API" +parent_types: [] +child_types: ["container-image", "container-instance", "container-volume", "container-network"] +auto_discovery: + enabled: true + scan_depth: 1 + rules: + - type: container-image + pattern: "images/*" + - type: container-instance + pattern: "containers/*" + - type: container-volume + pattern: "volumes/*" + - type: container-network + pattern: "networks/*" +capabilities: + read: true + write: false + sandbox: false + checkpoint: false +handler: "cleveragents.resource.handlers.container:ContainerRuntimeHandler" diff --git a/examples/resource-types/container-volume.yaml b/examples/resource-types/container-volume.yaml new file mode 100644 index 000000000..6ceb9e5a5 --- /dev/null +++ b/examples/resource-types/container-volume.yaml @@ -0,0 +1,25 @@ +# Spec reference: ADR-039, docs/specification.md ~lines 25096-25114 +schema_version: "1.0" +name: container-volume +description: "A named container volume managed by the runtime engine" +resource_kind: physical +sandbox_strategy: snapshot +user_addable: true +built_in: true +cli_args: + - name: volume-name + type: string + required: true + description: "Name of the volume" + - name: driver + type: string + required: false + description: "Volume driver (e.g. local, nfs)" +parent_types: ["container-runtime"] +child_types: ["fs-directory"] +capabilities: + read: true + write: true + sandbox: true + checkpoint: true +handler: "cleveragents.resource.handlers.container:ContainerVolumeHandler" diff --git a/features/resource_type_container_infra.feature b/features/resource_type_container_infra.feature new file mode 100644 index 000000000..f6b3c5482 --- /dev/null +++ b/features/resource_type_container_infra.feature @@ -0,0 +1,129 @@ +@container-infra +Feature: Container infrastructure resource types + Verifies that the 7 container infrastructure resource types + (container-runtime, container-image, container-mount, container-exec-env, + container-port, container-volume, container-network) are correctly + defined, registered, and have proper parent/child relationships. + + # ── YAML schema loading ───────────────────────────────── + + Scenario Outline: Container infra YAML validates for container_infra + Given the built-in "" YAML for container_infra + When I load the YAML via from_yaml_file for container_infra + Then the container_infra schema should be valid + And the container_infra schema name should be "" + And the container_infra schema resource_kind should be "physical" + + Examples: + | type_name | + | container-runtime | + | container-image | + | container-mount | + | container-exec-env | + | container-port | + | container-volume | + | container-network | + + # ── Domain model creation ─────────────────────────────── + + Scenario Outline: Container infra YAML loads as domain model for container_infra + Given the built-in "" YAML for container_infra + When I load the YAML as a ResourceTypeSpec for container_infra + Then the container_infra domain model should be valid + And the container_infra domain model name should be "" + And the container_infra domain model resource_kind should be physical + + Examples: + | type_name | + | container-runtime | + | container-image | + | container-mount | + | container-exec-env | + | container-port | + | container-volume | + | container-network | + + # ── Parent/child relationships ────────────────────────── + + Scenario: container-runtime is top-level with children for container_infra + Given the built-in "container-runtime" YAML for container_infra + When I load the YAML as a ResourceTypeSpec for container_infra + Then the container_infra spec should have empty parent_types + And the container_infra child_types should contain "container-image" + And the container_infra child_types should contain "container-instance" + And the container_infra child_types should contain "container-volume" + And the container_infra child_types should contain "container-network" + + Scenario: container-image parent is runtime for container_infra + Given the built-in "container-image" YAML for container_infra + When I load the YAML as a ResourceTypeSpec for container_infra + Then the container_infra parent_types should contain "container-runtime" + And the container_infra child_types should contain "container-instance" + + Scenario: container-mount parent is instance for container_infra + Given the built-in "container-mount" YAML for container_infra + When I load the YAML as a ResourceTypeSpec for container_infra + Then the container_infra parent_types should contain "container-instance" + And the container_infra child_types should contain "fs-directory" + And the container_infra child_types should contain "fs-file" + + Scenario: container-volume parent is runtime for container_infra + Given the built-in "container-volume" YAML for container_infra + When I load the YAML as a ResourceTypeSpec for container_infra + Then the container_infra parent_types should contain "container-runtime" + And the container_infra child_types should contain "fs-directory" + + # ── User-addable flags ────────────────────────────────── + + Scenario Outline: Container infra user_addable flags for container_infra + Given the built-in "" YAML for container_infra + When I load the YAML as a ResourceTypeSpec for container_infra + Then the container_infra user_addable should be + + Examples: + | type_name | addable | + | container-runtime | true | + | container-image | true | + | container-mount | false | + | container-exec-env | false | + | container-port | false | + | container-volume | true | + | container-network | false | + + # ── BUILTIN_NAMES registration ────────────────────────── + + Scenario Outline: Container infra types in BUILTIN_NAMES for container_infra + Then BUILTIN_NAMES should contain "" for container_infra + + Examples: + | type_name | + | container-runtime | + | container-image | + | container-mount | + | container-exec-env | + | container-port | + | container-volume | + | container-network | + + # ── Bootstrap registration ───────────────────────────── + + Scenario: All 7 container infra types in BUILTIN_TYPES for container_infra + Given the bootstrap builtin type definitions for container_infra + Then BUILTIN_TYPES should contain "container-runtime" for container_infra + And BUILTIN_TYPES should contain "container-image" for container_infra + And BUILTIN_TYPES should contain "container-mount" for container_infra + And BUILTIN_TYPES should contain "container-exec-env" for container_infra + And BUILTIN_TYPES should contain "container-port" for container_infra + And BUILTIN_TYPES should contain "container-volume" for container_infra + And BUILTIN_TYPES should contain "container-network" for container_infra + + # ── Coverage: verify CONTAINER_INFRA_TYPES module directly ── + + Scenario: CONTAINER_INFRA_TYPES has exactly 7 entries for container_infra + Then CONTAINER_INFRA_TYPES should have 7 entries for container_infra + + Scenario: Each container infra type has handler reference for container_infra + Then every CONTAINER_INFRA_TYPES entry should have a handler for container_infra + + Scenario: container-runtime has auto-discovery rules for container_infra + Then container-runtime in CONTAINER_INFRA_TYPES should have auto_discovery for container_infra diff --git a/features/steps/resource_type_container_infra_steps.py b/features/steps/resource_type_container_infra_steps.py new file mode 100644 index 000000000..818946729 --- /dev/null +++ b/features/steps/resource_type_container_infra_steps.py @@ -0,0 +1,191 @@ +"""Step definitions for resource_type_container_infra.feature. + +Tests container infrastructure resource types: container-runtime, +container-image, container-mount, container-exec-env, container-port, +container-volume, container-network. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import yaml +from behave import given, then, when + +from cleveragents.application.services._resource_registry_container import ( + CONTAINER_INFRA_TYPES, +) +from cleveragents.application.services._resource_registry_data import ( + BUILTIN_TYPES, +) +from cleveragents.domain.models.core.resource_type import ( + ResourceKind, + ResourceTypeSpec, +) +from cleveragents.resource.schema import ResourceTypeConfigSchema + +_EXAMPLES_DIR = ( + Path(__file__).resolve().parent.parent.parent / "examples" / "resource-types" +) + + +# ── Given steps ────────────────────────────────────────── + + +@given('the built-in "{type_name}" YAML for container_infra') +def step_builtin_yaml(context: object, type_name: str) -> None: + path = _EXAMPLES_DIR / f"{type_name}.yaml" + assert path.exists(), f"YAML not found: {path}" + context.ci_yaml_path = path + + +@given("the bootstrap builtin type definitions for container_infra") +def step_bootstrap_defs(context: object) -> None: + context.ci_builtin_defs = list(BUILTIN_TYPES) + + +# ── When steps ─────────────────────────────────────────── + + +@when("I load the YAML via from_yaml_file for container_infra") +def step_load_yaml(context: object) -> None: + path: Path = context.ci_yaml_path + schema = ResourceTypeConfigSchema.from_yaml_file(path) + context.ci_schema = schema + + +@when("I load the YAML as a ResourceTypeSpec for container_infra") +def step_load_domain(context: object) -> None: + path: Path = context.ci_yaml_path + raw: dict[str, Any] = yaml.safe_load(path.read_text(encoding="utf-8")) + spec = ResourceTypeSpec.from_config(raw) + context.ci_spec = spec + + +# ── Then steps: schema ─────────────────────────────────── + + +@then("the container_infra schema should be valid") +def step_schema_valid(context: object) -> None: + schema = context.ci_schema + assert schema is not None + assert isinstance(schema, ResourceTypeConfigSchema) + + +@then('the container_infra schema name should be "{name}"') +def step_schema_name(context: object, name: str) -> None: + assert context.ci_schema.name == name + + +@then('the container_infra schema resource_kind should be "{kind}"') +def step_schema_kind(context: object, kind: str) -> None: + assert context.ci_schema.resource_kind == kind + + +# ── Then steps: domain model ───────────────────────────── + + +@then("the container_infra domain model should be valid") +def step_domain_valid(context: object) -> None: + spec = context.ci_spec + assert spec is not None + assert isinstance(spec, ResourceTypeSpec) + + +@then('the container_infra domain model name should be "{name}"') +def step_domain_name(context: object, name: str) -> None: + assert context.ci_spec.name == name + + +@then("the container_infra domain model resource_kind should be physical") +def step_domain_kind_physical(context: object) -> None: + assert context.ci_spec.resource_kind == ResourceKind.PHYSICAL + + +# ── Then steps: parent/child ───────────────────────────── + + +@then("the container_infra spec should have empty parent_types") +def step_empty_parents(context: object) -> None: + spec = context.ci_spec + assert spec.parent_types == [], f"Expected empty, got {spec.parent_types}" + + +@then('the container_infra parent_types should contain "{name}"') +def step_parent_contains(context: object, name: str) -> None: + spec = context.ci_spec + assert name in spec.parent_types, ( + f"'{name}' not in parent_types: {spec.parent_types}" + ) + + +@then('the container_infra child_types should contain "{name}"') +def step_child_contains(context: object, name: str) -> None: + spec = context.ci_spec + assert name in spec.child_types, f"'{name}' not in child_types: {spec.child_types}" + + +# ── Then steps: user_addable ───────────────────────────── + + +@then("the container_infra user_addable should be {addable}") +def step_user_addable(context: object, addable: str) -> None: + spec = context.ci_spec + expected = addable.lower() == "true" + assert spec.user_addable is expected, ( + f"Expected user_addable={expected}, got {spec.user_addable}" + ) + + +# ── Then steps: BUILTIN_NAMES ──────────────────────────── + + +@then('BUILTIN_NAMES should contain "{name}" for container_infra') +def step_builtin_names(context: object, name: str) -> None: + assert name in ResourceTypeSpec.BUILTIN_NAMES, f"'{name}' not in BUILTIN_NAMES" + + +# ── Then steps: BUILTIN_TYPES ──────────────────────────── + + +@then('BUILTIN_TYPES should contain "{name}" for container_infra') +def step_builtin_types(context: object, name: str) -> None: + defs: list[dict[str, Any]] = context.ci_builtin_defs + names = [d["name"] for d in defs] + assert name in names, f"'{name}' not in BUILTIN_TYPES: {names}" + + +# ── Coverage: CONTAINER_INFRA_TYPES direct access ──────── + + +@then("CONTAINER_INFRA_TYPES should have 7 entries for container_infra") +def step_infra_count(context: object) -> None: + assert len(CONTAINER_INFRA_TYPES) == 7, ( + f"Expected 7, got {len(CONTAINER_INFRA_TYPES)}" + ) + + +@then("every CONTAINER_INFRA_TYPES entry should have a handler for container_infra") +def step_all_have_handler(context: object) -> None: + for entry in CONTAINER_INFRA_TYPES: + assert entry.get("handler") is not None, f"{entry['name']} has no handler" + + +@then( + "container-runtime in CONTAINER_INFRA_TYPES should have auto_discovery" + " for container_infra" +) +def step_runtime_auto_discovery(context: object) -> None: + runtime = next(d for d in CONTAINER_INFRA_TYPES if d["name"] == "container-runtime") + ad = runtime.get("auto_discovery") + assert ad is not None, "container-runtime missing auto_discovery" + assert ad.get("enabled") is True + assert ad.get("scan_depth") == 1 + rules = ad.get("rules", []) + assert len(rules) >= 4, f"Expected >=4 rules, got {len(rules)}" + # Verify Dockerfile/docker-compose patterns + patterns = [r["pattern"] for r in rules] + assert any("docker-compose" in p for p in patterns), ( + f"Missing docker-compose pattern: {patterns}" + ) diff --git a/robot/helper_resource_type_container_infra.py b/robot/helper_resource_type_container_infra.py new file mode 100644 index 000000000..544b06395 --- /dev/null +++ b/robot/helper_resource_type_container_infra.py @@ -0,0 +1,129 @@ +"""Helper script for container infrastructure resource type Robot tests. + +Usage: + python helper_resource_type_container_infra.py check-registration + python helper_resource_type_container_infra.py check-instance-children + python helper_resource_type_container_infra.py check-hierarchy + python helper_resource_type_container_infra.py check-yaml-loading +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +_SRC = str(Path(__file__).resolve().parents[1] / "src") +if _SRC not in sys.path: + sys.path.insert(0, _SRC) + +from cleveragents.application.services._resource_registry_data import ( # noqa: E402 + BUILTIN_TYPES, +) +from cleveragents.domain.models.core.resource_type import ( # noqa: E402 + ResourceTypeSpec, +) +from cleveragents.resource.schema import ( # noqa: E402 + ResourceTypeConfigSchema, +) + +_EXAMPLES = Path(__file__).resolve().parents[1] / "examples" / "resource-types" + +_CONTAINER_INFRA_NAMES = [ + "container-runtime", + "container-image", + "container-mount", + "container-exec-env", + "container-port", + "container-volume", + "container-network", +] + + +def cmd_check_registration() -> None: + """Verify all 7 container infra types in BUILTIN_TYPES.""" + names = [d["name"] for d in BUILTIN_TYPES] + for cn in _CONTAINER_INFRA_NAMES: + assert cn in names, f"{cn} not in BUILTIN_TYPES: {names}" + assert cn in ResourceTypeSpec.BUILTIN_NAMES, f"{cn} not in BUILTIN_NAMES" + entry = next(d for d in BUILTIN_TYPES if d["name"] == cn) + assert entry["resource_kind"] == "physical" + assert entry["built_in"] is True + print("check-registration-ok") + + +def cmd_check_instance_children() -> None: + """Verify container-instance has updated children.""" + entry = next(d for d in BUILTIN_TYPES if d["name"] == "container-instance") + for child in ("container-mount", "container-exec-env", "container-port"): + assert child in entry["child_types"], ( + f"{child} not in container-instance children: {entry['child_types']}" + ) + for parent in ("container-runtime", "container-image"): + assert parent in entry["parent_types"], ( + f"{parent} not in container-instance parents: {entry['parent_types']}" + ) + print("check-instance-children-ok") + + +def cmd_check_hierarchy() -> None: + """Verify runtime -> image -> instance DAG.""" + by_name = {d["name"]: d for d in BUILTIN_TYPES} + + runtime = by_name["container-runtime"] + assert "container-image" in runtime["child_types"] + assert "container-instance" in runtime["child_types"] + assert "container-volume" in runtime["child_types"] + assert "container-network" in runtime["child_types"] + + image = by_name["container-image"] + assert "container-runtime" in image["parent_types"] + assert "container-instance" in image["child_types"] + + mount = by_name["container-mount"] + assert "container-instance" in mount["parent_types"] + assert "fs-directory" in mount["child_types"] + + volume = by_name["container-volume"] + assert "container-runtime" in volume["parent_types"] + assert "fs-directory" in volume["child_types"] + + network = by_name["container-network"] + assert "container-runtime" in network["parent_types"] + assert network["child_types"] == [] + + print("check-hierarchy-ok") + + +def cmd_check_yaml_loading() -> None: + """Load all 7 YAML configs and verify schema.""" + for cn in _CONTAINER_INFRA_NAMES: + path = _EXAMPLES / f"{cn}.yaml" + assert path.exists(), f"Missing YAML: {path}" + schema = ResourceTypeConfigSchema.from_yaml_file(path) + assert schema.name == cn + assert schema.resource_kind == "physical" + spec = ResourceTypeSpec.from_config(schema.model_dump()) + assert spec.name == cn + print("check-yaml-loading-ok") + + +_COMMANDS = { + "check-registration": cmd_check_registration, + "check-instance-children": cmd_check_instance_children, + "check-hierarchy": cmd_check_hierarchy, + "check-yaml-loading": cmd_check_yaml_loading, +} + + +def main() -> None: + if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS: + print( + f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", + file=sys.stderr, + ) + sys.exit(1) + _COMMANDS[sys.argv[1]]() + + +if __name__ == "__main__": + main() diff --git a/robot/resource_type_container_infra.robot b/robot/resource_type_container_infra.robot new file mode 100644 index 000000000..665ca54ef --- /dev/null +++ b/robot/resource_type_container_infra.robot @@ -0,0 +1,47 @@ +*** Settings *** +Documentation Integration tests for container infrastructure resource types. +... Verifies all 7 container infra types are registered, +... have correct parent/child relationships, and load from YAML. +Resource ${CURDIR}/common.resource +Suite Setup Setup Test Environment +Suite Teardown Cleanup Test Environment + +*** Variables *** +${HELPER} ${CURDIR}/helper_resource_type_container_infra.py + +*** Test Cases *** +All Container Infra Types In BUILTIN_TYPES + [Documentation] Verify all 7 container infra types in BUILTIN_TYPES + [Tags] container registration smoke + ${result}= Run Process ${PYTHON} ${HELPER} check-registration + ... timeout=60s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 + ... Registration check failed: ${result.stderr} + Should Contain ${result.stdout} check-registration-ok + +Container Instance Updated With Children + [Documentation] Verify container-instance has mount/exec-env/port children + [Tags] container parent-child + ${result}= Run Process ${PYTHON} ${HELPER} check-instance-children + ... timeout=60s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 + ... Instance children check failed: ${result.stderr} + Should Contain ${result.stdout} check-instance-children-ok + +Container Runtime Hierarchy + [Documentation] Verify runtime -> image -> instance DAG hierarchy + [Tags] container hierarchy + ${result}= Run Process ${PYTHON} ${HELPER} check-hierarchy + ... timeout=60s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 + ... Hierarchy check failed: ${result.stderr} + Should Contain ${result.stdout} check-hierarchy-ok + +Container YAML Schema Loading + [Documentation] Load all 7 container YAML configs and verify schema + [Tags] container yaml schema + ${result}= Run Process ${PYTHON} ${HELPER} check-yaml-loading + ... timeout=60s on_timeout=kill + Should Be Equal As Integers ${result.rc} 0 + ... YAML loading failed: ${result.stderr} + Should Contain ${result.stdout} check-yaml-loading-ok diff --git a/src/cleveragents/application/services/_resource_registry_container.py b/src/cleveragents/application/services/_resource_registry_container.py new file mode 100644 index 000000000..181038dec --- /dev/null +++ b/src/cleveragents/application/services/_resource_registry_container.py @@ -0,0 +1,272 @@ +"""Container infrastructure resource type definitions for the Resource Registry. + +Contains the 7 built-in container infrastructure type entries +(container-runtime, container-image, container-mount, container-exec-env, +container-port, container-volume, container-network) that are appended +to ``BUILTIN_TYPES`` in ``_resource_registry_data``. + +Defined per ADR-039: Container Resource Types and the specification's +built-in handler table (~line 25096). + +.. note:: + + This is an **internal** module (``_``-prefixed). External code + should import ``BUILTIN_TYPES`` from ``_resource_registry_data`` + instead. +""" + +from __future__ import annotations + +from typing import Any + +__all__ = ["CONTAINER_INFRA_TYPES"] + +# Handler references per spec line 25096-25114. These handler classes +# do not exist yet — the references are forward declarations that will +# be resolved when the container runtime integration is implemented. +_RUNTIME_HANDLER = "cleveragents.resource.handlers.container:ContainerRuntimeHandler" +_IMAGE_HANDLER = "cleveragents.resource.handlers.container:ContainerImageHandler" +_CHILD_HANDLER = "cleveragents.resource.handlers.container:ContainerChildHandler" +_VOLUME_HANDLER = "cleveragents.resource.handlers.container:ContainerVolumeHandler" +_NETWORK_HANDLER = "cleveragents.resource.handlers.container:ContainerNetworkHandler" + +CONTAINER_INFRA_TYPES: list[dict[str, Any]] = [ + # ------------------------------------------------------------------ + # container-runtime: top-level runtime (Docker, Podman, etc.) + # ------------------------------------------------------------------ + { + "name": "container-runtime", + "description": ( + "A container runtime engine (Docker, Podman, containerd, etc.)." + ), + "resource_kind": "physical", + "sandbox_strategy": "none", + "user_addable": True, + "built_in": True, + "cli_args": [ + { + "name": "engine-type", + "type": "string", + "required": True, + "description": ( + "Container engine type " + "(docker, podman, containerd, lxc, lxd, cri-o, other)." + ), + }, + { + "name": "socket-path", + "type": "path", + "required": False, + "description": "Path to the container engine socket.", + "default": None, + }, + { + "name": "endpoint", + "type": "string", + "required": False, + "description": "Remote endpoint URL for the engine API.", + "default": None, + }, + ], + "parent_types": [], + "child_types": [ + "container-image", + "container-instance", + "container-volume", + "container-network", + ], + "auto_discovery": { + "enabled": True, + "scan_depth": 1, + "rules": [ + {"type": "container-image", "pattern": "images/*"}, + {"type": "container-instance", "pattern": "containers/*"}, + {"type": "container-volume", "pattern": "volumes/*"}, + {"type": "container-network", "pattern": "networks/*"}, + {"type": "container-image", "pattern": "docker-compose.yml"}, + {"type": "container-image", "pattern": "docker-compose.yaml"}, + {"type": "container-image", "pattern": "compose.yml"}, + {"type": "container-image", "pattern": "compose.yaml"}, + ], + }, + "handler": _RUNTIME_HANDLER, + "capabilities": { + "read": True, + "write": False, + "sandbox": False, + "checkpoint": False, + }, + }, + # ------------------------------------------------------------------ + # container-image: immutable container image (tag + digest) + # ------------------------------------------------------------------ + { + "name": "container-image", + "description": ("A container image identified by tag and/or digest."), + "resource_kind": "physical", + "sandbox_strategy": "none", + "user_addable": True, + "built_in": True, + "cli_args": [ + { + "name": "image-ref", + "type": "string", + "required": True, + "description": ( + "Image reference (e.g. ubuntu:22.04, ghcr.io/org/app:latest)." + ), + }, + { + "name": "digest", + "type": "string", + "required": False, + "description": "Image digest (sha256:...).", + "default": None, + }, + ], + "parent_types": ["container-runtime"], + "child_types": ["container-instance"], + "auto_discovery": { + "enabled": True, + "scan_depth": 1, + "rules": [ + {"type": "container-image", "pattern": "Dockerfile"}, + {"type": "container-image", "pattern": "Dockerfile.*"}, + {"type": "container-image", "pattern": "*.dockerfile"}, + ], + }, + "handler": _IMAGE_HANDLER, + "capabilities": { + "read": True, + "write": False, + "sandbox": False, + "checkpoint": False, + }, + }, + # ------------------------------------------------------------------ + # container-mount: bind mount or volume mount inside a container + # ------------------------------------------------------------------ + { + "name": "container-mount", + "description": ("A bind mount or volume mount inside a container instance."), + "resource_kind": "physical", + "sandbox_strategy": "snapshot", + "user_addable": False, + "built_in": True, + "cli_args": [], + "parent_types": ["container-instance"], + "child_types": ["fs-directory", "fs-file"], + "handler": _CHILD_HANDLER, + "capabilities": { + "read": True, + "write": True, + "sandbox": True, + "checkpoint": True, + }, + }, + # ------------------------------------------------------------------ + # container-exec-env: execution environment inside a container + # ------------------------------------------------------------------ + { + "name": "container-exec-env", + "description": ( + "Execution environment variables and configuration " + "inside a container instance." + ), + "resource_kind": "physical", + "sandbox_strategy": "snapshot", + "user_addable": False, + "built_in": True, + "cli_args": [], + "parent_types": ["container-instance"], + "child_types": [], + "handler": _CHILD_HANDLER, + "capabilities": { + "read": True, + "write": False, + "sandbox": True, + "checkpoint": True, + }, + }, + # ------------------------------------------------------------------ + # container-port: port mapping on a container + # ------------------------------------------------------------------ + { + "name": "container-port", + "description": ( + "A port mapping between host and container (e.g. 8080:80/tcp)." + ), + "resource_kind": "physical", + "sandbox_strategy": "snapshot", + "user_addable": False, + "built_in": True, + "cli_args": [], + "parent_types": ["container-instance"], + "child_types": [], + "handler": _CHILD_HANDLER, + "capabilities": { + "read": True, + "write": False, + "sandbox": True, + "checkpoint": True, + }, + }, + # ------------------------------------------------------------------ + # container-volume: named Docker/Podman volume + # ------------------------------------------------------------------ + { + "name": "container-volume", + "description": ("A named container volume managed by the runtime engine."), + "resource_kind": "physical", + "sandbox_strategy": "snapshot", + "user_addable": True, + "built_in": True, + "cli_args": [ + { + "name": "volume-name", + "type": "string", + "required": True, + "description": "Name of the volume.", + }, + { + "name": "driver", + "type": "string", + "required": False, + "description": "Volume driver (e.g. local, nfs).", + "default": None, + }, + ], + "parent_types": ["container-runtime"], + "child_types": ["fs-directory"], + "handler": _VOLUME_HANDLER, + "capabilities": { + "read": True, + "write": True, + "sandbox": True, + "checkpoint": True, + }, + }, + # ------------------------------------------------------------------ + # container-network: container network configuration + # ------------------------------------------------------------------ + { + "name": "container-network", + "description": ( + "A container network (bridge, host, overlay, macvlan, or none)." + ), + "resource_kind": "physical", + "sandbox_strategy": "none", + "user_addable": False, + "built_in": True, + "cli_args": [], + "parent_types": ["container-runtime"], + "child_types": [], + "handler": _NETWORK_HANDLER, + "capabilities": { + "read": True, + "write": False, + "sandbox": False, + "checkpoint": False, + }, + }, +] diff --git a/src/cleveragents/application/services/_resource_registry_data.py b/src/cleveragents/application/services/_resource_registry_data.py index 72e6cc451..e5ca010ec 100644 --- a/src/cleveragents/application/services/_resource_registry_data.py +++ b/src/cleveragents/application/services/_resource_registry_data.py @@ -22,6 +22,9 @@ from cleveragents.application.services._resource_registry_cloud import ( CLOUD_BASE_TYPES, GCP_AZURE_TYPES, ) +from cleveragents.application.services._resource_registry_container import ( + CONTAINER_INFRA_TYPES, +) from cleveragents.application.services._resource_registry_lsp import ( LSP_RESOURCE_TYPES, ) @@ -177,14 +180,23 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [ "default": None, }, ], - "parent_types": ["git-checkout", "fs-directory"], - "child_types": [], + "parent_types": [ + "container-runtime", + "container-image", + "git-checkout", + "fs-directory", + ], + "child_types": [ + "container-mount", + "container-exec-env", + "container-port", + ], "handler": ("cleveragents.resource.handlers.devcontainer:DevcontainerHandler"), "capabilities": { "read": True, "write": True, "sandbox": True, - "checkpoint": False, + "checkpoint": True, }, }, { @@ -263,6 +275,8 @@ BUILTIN_TYPES: list[dict[str, Any]] = [ *CLOUD_BASE_TYPES, *AWS_TYPES, *GCP_AZURE_TYPES, + # Container infrastructure types -- see _resource_registry_container.py (#831) + *CONTAINER_INFRA_TYPES, *DATABASE_TYPE_DEFS, # Virtual core resource types -- see _resource_registry_virtual.py (#329) *VIRTUAL_CORE_TYPES, diff --git a/src/cleveragents/application/services/resource_registry_service.py b/src/cleveragents/application/services/resource_registry_service.py index fb1504c3d..66fbb535f 100644 --- a/src/cleveragents/application/services/resource_registry_service.py +++ b/src/cleveragents/application/services/resource_registry_service.py @@ -16,6 +16,13 @@ development workflows. Built-in types are **unnamespaced** (no |-------------------|----------|------------------|--------------------------------| | ``git-checkout`` | physical | ``git_worktree`` | Git repository checkout | | ``fs-directory`` | physical | ``copy_on_write``| Filesystem directory | +| ``container-runtime``| physical | ``none`` | Container runtime engine | +| ``container-image``| physical | ``none`` | Container image (tag + digest) | +| ``container-mount``| physical | ``snapshot`` | Bind/volume mount in container | +| ``container-exec-env``| physical | ``snapshot`` | Container exec environment | +| ``container-port`` | physical | ``snapshot`` | Container port mapping | +| ``container-volume``| physical | ``snapshot`` | Named container volume | +| ``container-network``| physical | ``none`` | Container network | | ``file`` | virtual | ``none`` | Cross-layer file identity | | ``directory`` | virtual | ``none`` | Cross-layer directory identity | | ``commit`` | virtual | ``none`` | Cross-repo commit identity | diff --git a/src/cleveragents/domain/models/core/_resource_type_validation.py b/src/cleveragents/domain/models/core/_resource_type_validation.py index 14cc20b16..846676f6b 100644 --- a/src/cleveragents/domain/models/core/_resource_type_validation.py +++ b/src/cleveragents/domain/models/core/_resource_type_validation.py @@ -24,7 +24,14 @@ BUILTIN_TYPE_NAMES: frozenset[str] = frozenset( "fs-directory", "fs-mount", "fs-file", + "container-exec-env", + "container-image", "container-instance", + "container-mount", + "container-network", + "container-port", + "container-runtime", + "container-volume", "devcontainer-instance", "devcontainer-file", "postgres", diff --git a/src/cleveragents/domain/models/core/resource_type.py b/src/cleveragents/domain/models/core/resource_type.py index f36d07227..24de259b8 100644 --- a/src/cleveragents/domain/models/core/resource_type.py +++ b/src/cleveragents/domain/models/core/resource_type.py @@ -174,6 +174,8 @@ class ResourceTypeSpec(BaseModel): # Built-in resource type names (unnamespaced). # The actual frozenset lives in _resource_type_validation to keep # this module under the 500-line CONTRIBUTING limit. + # Built-in resource type names — sourced from _resource_type_validation.py + # to keep this module under the 500-line CONTRIBUTING limit. BUILTIN_NAMES: ClassVar[frozenset[str]] = BUILTIN_TYPE_NAMES name: str = Field(