Skip to content
Merged
146 changes: 72 additions & 74 deletions .ai/specs/dcm-cli.spec.md

Large diffs are not rendered by default.

57 changes: 28 additions & 29 deletions .ai/test-plans/dcm-cli-unit.test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
- **Created:** 2026-03-09

Unit tests verify individual components in isolation. All external dependencies
(API Gateway, Policy Manager, Catalog Manager) are replaced with
`net/http/httptest` servers. Tests use direct command execution via Cobra's
(control-plane HTTP API) are replaced with `net/http/httptest` servers. Tests use direct command execution via Cobra's
`Execute()` with captured stdout/stderr, and direct function calls for pure
logic (config loading, output formatting, file parsing).

Expand Down Expand Up @@ -38,27 +37,27 @@ test classes. Instead:
- **Requirement:** REQ-CFG-010
- **Acceptance Criteria:** AC-CFG-010
- **Type:** Unit
- **Given:** A config file exists at a temporary path with `api-gateway-url: http://custom:9080`
- **Given:** A config file exists at a temporary path with `control-plane-url: http://custom:8080`
- **When:** Config is loaded with `--config` pointing to that file
- **Then:** The loaded config has `APIGatewayURL = "http://custom:9080"`
- **Then:** The loaded config has `ControlPlaneURL = "http://custom:8080"`

### TC-U002: Environment variable overrides config file

- **Requirement:** REQ-CFG-030, REQ-CFG-040
- **Acceptance Criteria:** AC-CFG-030, AC-CFG-040
- **Type:** Unit
- **Given:** A config file has `api-gateway-url: http://file:9080` AND `DCM_API_GATEWAY_URL=http://env:9080` is set
- **When:** Config is loaded without `--api-gateway-url` flag
- **Then:** The loaded config has `APIGatewayURL = "http://env:9080"`
- **Given:** A config file has `control-plane-url: http://file:8080` AND `DCM_CONTROL_PLANE_URL=http://env:8080` is set
- **When:** Config is loaded without `--control-plane-url` flag
- **Then:** The loaded config has `ControlPlaneURL = "http://env:8080"`

### TC-U003: CLI flag overrides environment variable and config file

- **Requirement:** REQ-CFG-040
- **Acceptance Criteria:** AC-CFG-040
- **Type:** Unit
- **Given:** `DCM_API_GATEWAY_URL=http://env:9080` is set AND config file has `api-gateway-url: http://file:9080`
- **When:** Config is loaded with `--api-gateway-url http://flag:9080`
- **Then:** The loaded config has `APIGatewayURL = "http://flag:9080"`
- **Given:** `DCM_CONTROL_PLANE_URL=http://env:8080` is set AND config file has `control-plane-url: http://file:8080`
- **When:** Config is loaded with `--control-plane-url http://flag:8080`
- **Then:** The loaded config has `ControlPlaneURL = "http://flag:8080"`

### TC-U004: Default values applied when no config specified

Expand All @@ -67,7 +66,7 @@ test classes. Instead:
- **Type:** Unit
- **Given:** No config file exists AND no environment variables are set AND no flags are provided
- **When:** Config is loaded
- **Then:** `APIGatewayURL` defaults to `"http://localhost:9080"` AND `OutputFormat` defaults to `"table"` AND `Timeout` defaults to `30` AND `TLSCACert` defaults to `""` AND `TLSClientCert` defaults to `""` AND `TLSClientKey` defaults to `""` AND `TLSSkipVerify` defaults to `false`
- **Then:** `ControlPlaneURL` defaults to `"http://localhost:8080"` AND `OutputFormat` defaults to `"table"` AND `Timeout` defaults to `30` AND `TLSCACert` defaults to `""` AND `TLSClientCert` defaults to `""` AND `TLSClientKey` defaults to `""` AND `TLSSkipVerify` defaults to `false`

### TC-U005: Missing config file does not cause failure

Expand Down Expand Up @@ -105,7 +104,7 @@ test classes. Instead:

| Environment Variable | Value | Expected Config Field |
|------------------------|------------|-----------------------|
| `DCM_API_GATEWAY_URL` | `http://e:9080` | `APIGatewayURL` |
| `DCM_CONTROL_PLANE_URL` | `http://e:8080` | `ControlPlaneURL` |
| `DCM_OUTPUT_FORMAT` | `json` | `OutputFormat` |
| `DCM_TIMEOUT` | `60` | `Timeout` |
| `DCM_TLS_CA_CERT` | `/path/ca.pem` | `TLSCACert` |
Expand Down Expand Up @@ -289,7 +288,7 @@ test classes. Instead:
- **Type:** Unit
- **Given:** The root command is created
- **When:** `dcm --help` is executed
- **Then:** Flags `--api-gateway-url`, `--output`/`-o`, `--timeout`, `--config`, `--tls-ca-cert`, `--tls-client-cert`, `--tls-client-key`, and `--tls-skip-verify` are listed
- **Then:** Flags `--control-plane-url`, `--output`/`-o`, `--timeout`, `--config`, `--tls-ca-cert`, `--tls-client-cert`, `--tls-client-key`, and `--tls-skip-verify` are listed

### TC-U022: Exit code 0 on success

Expand Down Expand Up @@ -1198,7 +1197,7 @@ test classes. Instead:
- **Requirement:** REQ-XC-TLS-010
- **Acceptance Criteria:** AC-XC-TLS-010
- **Type:** Unit
- **Given:** The API Gateway URL is `https://localhost:<port>` pointing to a TLS-enabled httptest server
- **Given:** The control-plane URL is `https://localhost:<port>` pointing to a TLS-enabled httptest server
- **When:** `dcm policy list --tls-skip-verify` is executed
- **Then:** The request MUST succeed over TLS AND the mock server receives the request

Expand All @@ -1207,7 +1206,7 @@ test classes. Instead:
- **Requirement:** REQ-XC-TLS-020
- **Acceptance Criteria:** AC-XC-TLS-020
- **Type:** Unit
- **Given:** The API Gateway URL is `http://localhost:<port>` AND `--tls-skip-verify` is set AND `--tls-ca-cert /some/path` is set
- **Given:** The control-plane URL is `http://localhost:<port>` AND `--tls-skip-verify` is set AND `--tls-ca-cert /some/path` is set
- **When:** `dcm policy list` is executed against a non-TLS httptest server
- **Then:** The request MUST succeed without TLS AND TLS flags MUST be silently ignored

Expand All @@ -1217,7 +1216,7 @@ test classes. Instead:
- **Acceptance Criteria:** AC-XC-TLS-030
- **Type:** Unit
- **Given:** A TLS-enabled httptest server with a self-signed certificate AND the CA cert is written to a temp file
- **When:** `dcm policy list --api-gateway-url https://... --tls-ca-cert /tmp/ca.pem` is executed
- **When:** `dcm policy list --control-plane-url https://... --tls-ca-cert /tmp/ca.pem` is executed
- **Then:** The TLS handshake MUST succeed using the provided CA certificate

### TC-U091: Mutual TLS with client certificate and key
Expand All @@ -1235,7 +1234,7 @@ test classes. Instead:
- **Acceptance Criteria:** AC-XC-TLS-050
- **Type:** Unit
- **Given:** A TLS-enabled httptest server with a self-signed certificate AND no CA cert is provided
- **When:** `dcm policy list --api-gateway-url https://... --tls-skip-verify` is executed
- **When:** `dcm policy list --control-plane-url https://... --tls-skip-verify` is executed
- **Then:** The request MUST succeed despite the untrusted certificate

### TC-U093: Incomplete mTLS config — cert without key
Expand All @@ -1261,7 +1260,7 @@ test classes. Instead:
- **Requirement:** REQ-XC-TLS-070
- **Acceptance Criteria:** AC-XC-TLS-070
- **Type:** Unit
- **Given:** `--tls-ca-cert /nonexistent/ca.pem` is provided AND the API Gateway URL uses `https://`
- **Given:** `--tls-ca-cert /nonexistent/ca.pem` is provided AND the control-plane URL uses `https://`
- **When:** The CLI attempts to load the CA certificate
- **Then:** The CLI MUST exit with code 1 AND display a clear error message

Expand All @@ -1270,7 +1269,7 @@ test classes. Instead:
- **Requirement:** REQ-XC-TLS-070
- **Acceptance Criteria:** AC-XC-TLS-070
- **Type:** Unit
- **Given:** `--tls-client-cert /nonexistent/cert.pem` and `--tls-client-key /tmp/valid-key.pem` are provided AND the API Gateway URL uses `https://`
- **Given:** `--tls-client-cert /nonexistent/cert.pem` and `--tls-client-key /tmp/valid-key.pem` are provided AND the control-plane URL uses `https://`
- **When:** The CLI attempts to load the client certificate
- **Then:** The CLI MUST exit with code 1 AND display a clear error message

Expand All @@ -1279,7 +1278,7 @@ test classes. Instead:
- **Requirement:** REQ-XC-TLS-080
- **Acceptance Criteria:** AC-XC-TLS-030
- **Type:** Unit
- **Given:** The API Gateway URL uses `https://` AND no `--tls-ca-cert` is provided
- **Given:** The control-plane URL uses `https://` AND no `--tls-ca-cert` is provided
- **When:** The TLS transport is configured
- **Then:** The system default CA bundle MUST be used (RootCAs is nil in tls.Config)

Expand Down Expand Up @@ -1348,7 +1347,7 @@ test classes. Instead:
- **Requirement:** REQ-XC-ERR-040
- **Acceptance Criteria:** AC-XC-ERR-030
- **Type:** Unit
- **Given:** The API Gateway URL points to a closed/non-existent server
- **Given:** The control-plane URL points to a closed/non-existent server
- **When:** `dcm policy list` is executed
- **Then:** The CLI displays a connection error message AND exits with code 1

Expand Down Expand Up @@ -1437,19 +1436,19 @@ dedicated test class or `Describe` block.
- **Requirement:** REQ-XC-CLI-010, REQ-XC-CLI-030
- **Acceptance Criteria:** AC-XC-CLI-010
- **Type:** Unit
- **Given:** The API Gateway URL is `http://localhost:9080`
- **Given:** The control-plane URL is `http://localhost:8080`
- **When:** The Policy Manager client is created
- **Then:** The client base URL is `http://localhost:9080/api/v1alpha1`
- **Then:** The client base URL is `http://localhost:8080/api/v1alpha1`
- **Referenced by:** TC-U026 (create policy verifies request goes to correct URL path)

#### TC-U065: Catalog Manager client instantiated with correct URL

- **Requirement:** REQ-XC-CLI-020, REQ-XC-CLI-030
- **Acceptance Criteria:** AC-XC-CLI-010
- **Type:** Unit
- **Given:** The API Gateway URL is `http://localhost:9080`
- **Given:** The control-plane URL is `http://localhost:8080`
- **When:** The Catalog Manager client is created
- **Then:** The client base URL is `http://localhost:9080/api/v1alpha1`
- **Then:** The client base URL is `http://localhost:8080/api/v1alpha1`
- **Referenced by:** TC-U042 (list service types verifies request goes to correct URL path)

#### TC-U066: Policy Manager generated client used for policy operations
Expand Down Expand Up @@ -1477,9 +1476,9 @@ dedicated test class or `Describe` block.
- **Requirement:** REQ-XC-CLI-025, REQ-XC-CLI-030
- **Acceptance Criteria:** AC-XC-CLI-010
- **Type:** Unit
- **Given:** The API Gateway URL is `http://localhost:9080`
- **Given:** The control-plane URL is `http://localhost:8080`
- **When:** The SP Resource Manager client is created
- **Then:** The client base URL is `http://localhost:9080/api/v1alpha1`
- **Then:** The client base URL is `http://localhost:8080/api/v1alpha1`
- **Referenced by:** TC-U121 (list SP resources verifies request goes to correct URL path)

#### TC-U131: SP Resource Manager generated client used for SP resource operations
Expand All @@ -1497,9 +1496,9 @@ dedicated test class or `Describe` block.
- **Requirement:** REQ-XC-CLI-026, REQ-XC-CLI-030
- **Acceptance Criteria:** AC-XC-CLI-010
- **Type:** Unit
- **Given:** The API Gateway URL is `http://localhost:9080`
- **Given:** The control-plane URL is `http://localhost:8080`
- **When:** The SP Manager client is created
- **Then:** The client base URL is `http://localhost:9080/api/v1alpha1`
- **Then:** The client base URL is `http://localhost:8080/api/v1alpha1`
- **Referenced by:** TC-U139 (list SP providers verifies request goes to correct URL path)

#### TC-U149: SP Manager generated client used for SP provider operations
Expand Down
25 changes: 19 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

DCM CLI (`dcm`) is a Go-based command-line tool for interacting with the DCM (Data Center Management) control plane. It communicates through the API Gateway (KrakenD on port 9080) to reach the PolicyManager and CatalogManager backends. The CLI uses generated clients from `policy-manager/pkg/client` and `catalog-manager/pkg/client` (oapi-codegen generated) as Go module dependencies.
DCM CLI (`dcm`) is a Go-based command-line tool for interacting with the DCM (Data Center Management) control plane. It communicates directly with the control-plane monolith on port 8080. The CLI uses oapi-codegen generated clients from the [control-plane](https://github.com/dcm-project/control-plane/tree/main/pkg) repo as a Go module dependency.

Generated client packages:

- [pkg/policy/client](https://github.com/dcm-project/control-plane/tree/main/pkg/policy/client) — Policy Manager
- [pkg/catalog/client](https://github.com/dcm-project/control-plane/tree/main/pkg/catalog/client) — Catalog Manager
- [pkg/sp/client/resource_manager](https://github.com/dcm-project/control-plane/tree/main/pkg/sp/client/resource_manager) — SP Resource Manager
- [pkg/sp/client/provider](https://github.com/dcm-project/control-plane/tree/main/pkg/sp/client/provider) — SP Manager

## Build and Development Commands

Expand Down Expand Up @@ -54,10 +61,14 @@ make test-e2e

- **internal/commands/**: Cobra command definitions
- `root.go`: Root command with global flags
- `helpers.go`: Client constructors, HTTP/TLS helpers, input file parsing
- `policy.go`: Policy CRUD commands
- `catalog_service_type.go`: Service type list/get commands
- `catalog_item.go`: Catalog item create/list/get/delete commands
- `catalog_instance.go`: Catalog instance create/list/get/delete commands
- `catalog_instance.go`: Catalog instance create/list/get/delete/rehydrate commands
- `sp_resource.go`: SP resource list/get commands
- `sp_provider.go`: SP provider list/get commands
- `completion.go`: Shell completion
- `version.go`: Version display command

- **internal/version/**: Build-time version info injected via ldflags
Expand All @@ -70,15 +81,15 @@ make test-e2e

The project uses Ginkgo as the test framework with Gomega matchers. HTTP-level mocking uses `net/http/httptest`.

E2E tests live under `test/e2e/` and use the `e2e` build tag (`//go:build e2e`). They require a live DCM stack with `DCM_API_GATEWAY_URL` set.
E2E tests live under `test/e2e/` and use the `e2e` build tag (`//go:build e2e`). They require a live DCM stack with `DCM_CONTROL_PLANE_URL` set.

## Key Conventions

1. **Cobra commands**: Each resource group (policy, catalog service-type, catalog item, catalog instance) has its own file with subcommands. Policy supports create/list/get/update/delete. Catalog item and catalog instance do not support update.
1. **Cobra commands**: Each resource group (policy, catalog service-type, catalog item, catalog instance, sp resource, sp provider) has its own file with subcommands. Policy supports create/list/get/update/delete. Catalog item and catalog instance do not support update. SP commands are read-only (list/get).

2. **Generated clients**: Import `github.com/dcm-project/policy-manager/pkg/client` and `github.com/dcm-project/catalog-manager/pkg/client`. No hand-written HTTP client code.
2. **Generated clients**: Import from `github.com/dcm-project/control-plane/pkg/...` (see links in Project Overview). Client constructors live in `helpers.go`. No hand-written HTTP client code.

3. **Configuration precedence**: CLI flags > environment variables (`DCM_API_GATEWAY_URL`, `DCM_OUTPUT_FORMAT`, `DCM_TIMEOUT`, `DCM_CONFIG`) > config file (`~/.dcm/config.yaml`) > built-in defaults.
3. **Configuration precedence**: CLI flags > environment variables (`DCM_CONTROL_PLANE_URL`, `DCM_OUTPUT_FORMAT`, `DCM_TIMEOUT`, `DCM_CONFIG`) > config file (`~/.dcm/config.yaml`) > built-in defaults.

4. **Output formatting**: All commands support `--output/-o` flag with `table` (default), `json`, and `yaml` formats.

Expand All @@ -89,3 +100,5 @@ E2E tests live under `test/e2e/` and use the `e2e` build tag (`//go:build e2e`).
7. **Version injection**: Build-time ldflags set `internal/version.Version`, `internal/version.Commit`, `internal/version.BuildTime`.

8. **Commit conventions**: All commit messages must include a `Co-Authored-By:` line. The `git commit` command must always use the `--signoff` flag (e.g., `git commit --signoff`).

9. **Documentation sync**: When changing behavior, flags, env vars, module paths, or architecture, update `README.md`, `CLAUDE.md`, `.ai/specs/`, and `.ai/test-plans/`. Leave `.ai/checkpoints/` unchanged — they are historical dev notes.
Loading