The DCM CLI (dcm) is the primary user-facing command-line interface for interacting with the DCM (Data Center Management) control plane. It provides commands for managing policies, service types, catalog items, catalog item instances, and service provider resources through the control-plane monolith API.
This specification covers the v1alpha1 API surface, matching the control-plane route prefix /api/v1alpha1.
| Document | Description |
|---|---|
| Control-plane OpenAPI | api/*/v1alpha1/openapi.yaml in dcm-project/control-plane |
| AEP Standards | aep.dev - API Enhancement Proposals |
| RFC 7807 | Problem Details for HTTP APIs |
| RFC 7396 | JSON Merge Patch |
┌─────────┐ ┌────────────────────────────────────────────────────┐
│ │ │ control-plane monolith │
│ dcm │─────────────▶│ (port 8080, /api/v1alpha1/*) │
│ CLI │ HTTP / HTTPS │ │
│ │ │ Policy Manager · Catalog Manager · SP Manager │
│ │ │ │
└─────────┘ └────────────────────────────────────────────────────┘
The CLI communicates exclusively through the control plane (port 8080). When the control plane URL uses an https:// scheme, the CLI establishes a TLS connection. When the URL uses http://, TLS is skipped entirely. These managers run in-process in the monolith (formerly separate services). CLI commands call paths under /api/v1alpha1:
/api/v1alpha1/policies/*→ Policy Manager/api/v1alpha1/service-types/*→ Catalog Manager/api/v1alpha1/catalog-items/*→ Catalog Manager/api/v1alpha1/catalog-item-instances/*→ Catalog Manager/api/v1alpha1/service-type-instances/*→ SP Resource Manager/api/v1alpha1/providers/*→ SP Manager
cmd/dcm/
main.go ← Entry point, root command setup
internal/
config/ ← Configuration loading/saving
output/ ← Output formatting (table/json/yaml)
commands/
root.go ← Root command, global flags
version.go ← Version command
policy.go ← Policy command group
catalog_service_type.go ← Catalog service-type command group
catalog_item.go ← Catalog item command group
catalog_instance.go ← Catalog instance command group
sp.go ← SP parent command group
sp_resource.go ← SP resource command group
completion.go ← Shell completion command
| Component | Responsibility |
|---|---|
cmd/dcm/main.go |
Bootstrap, wire dependencies, execute root command |
internal/config |
Load config from file/env/flags with precedence |
internal/output |
Format responses as table, JSON, or YAML |
internal/commands |
Cobra command definitions, flag binding, client invocation |
internal/version |
Build-time version info injected via ldflags |
The CLI uses generated clients from the control-plane repo (policy/client, catalog/client, sp/client) (oapi-codegen generated) as Go module dependencies. No hand-written HTTP client is needed.
Default location: ~/.dcm/config.yaml
control-plane-url: http://localhost:8080
output-format: table
timeout: 30
tls-ca-cert: ""
tls-client-cert: ""
tls-client-key: ""
tls-skip-verify: false| Variable | Description | Default |
|---|---|---|
DCM_CONTROL_PLANE_URL |
Control plane API base URL | http://localhost:8080 |
DCM_OUTPUT_FORMAT |
Output format (table, json, yaml) |
table |
DCM_TIMEOUT |
Request timeout in seconds | 30 |
DCM_CONFIG |
Path to config file | ~/.dcm/config.yaml |
DCM_TLS_CA_CERT |
Path to CA certificate file for TLS verification | "" |
DCM_TLS_CLIENT_CERT |
Path to client certificate file for mTLS | "" |
DCM_TLS_CLIENT_KEY |
Path to client private key file for mTLS | "" |
DCM_TLS_SKIP_VERIFY |
Skip TLS certificate verification (true/false) |
false |
Configuration values are resolved in the following order (highest to lowest priority):
- Command-line flags (
--control-plane-url,--output,--timeout) - Environment variables (
DCM_CONTROL_PLANE_URL, etc.) - Configuration file (
~/.dcm/config.yaml) - Built-in defaults
These flags are available on all commands:
| Flag | Short | Description |
|---|---|---|
--control-plane-url |
Control plane API URL | |
--output |
-o |
Output format: table, json, yaml |
--timeout |
Request timeout in seconds | |
--config |
Path to config file | |
--tls-ca-cert |
Path to CA certificate file for TLS verification | |
--tls-client-cert |
Path to client certificate file for mTLS | |
--tls-client-key |
Path to client private key file for mTLS | |
--tls-skip-verify |
Skip TLS certificate verification |
dcm
├── policy # Policy management
│ ├── create
│ ├── list
│ ├── get
│ ├── update
│ └── delete
├── catalog
│ ├── service-type # Service type management
│ │ ├── list
│ │ └── get
│ ├── item # Catalog item management
│ │ ├── create
│ │ ├── list
│ │ ├── get
│ │ └── delete
│ └── instance # Catalog item instance management
│ ├── create
│ ├── list
│ ├── get
│ └── delete
├── sp
│ └── resource # SP resource management (read-only)
│ ├── list
│ └── get
├── completion # Shell autocompletion
└── version # Print version info
Create a new policy.
| Flag | Required | Description |
|---|---|---|
--from-file |
Yes | Path to policy YAML/JSON file |
--id |
No | Client-specified policy ID (DNS-1123 format) |
# Create a policy from file
dcm policy create --from-file policy.yaml
# Create with client-specified ID
dcm policy create --from-file policy.yaml --id my-policyPolicy file format:
display_name: "Require CPU Limits"
description: "Ensures all containers have CPU limits set"
policy_type: GLOBAL
label_selector:
environment: production
priority: 100
rego_code: |
package dcm.policy
default allow = false
allow { input.request.resource.limits.cpu != "" }
enabled: trueExample output (table):
ID DISPLAY NAME TYPE PRIORITY ENABLED CREATED
my-policy Require CPU Limits GLOBAL 100 true 2026-03-09T10:00:00Z
List policies with optional filtering and ordering.
| Flag | Required | Description |
|---|---|---|
--filter |
No | CEL filter expression (e.g., policy_type='GLOBAL') |
--order-by |
No | Order field and direction (e.g., priority asc) |
--page-size |
No | Maximum results per page |
--page-token |
No | Token for next page |
# List all policies
dcm policy list
# List enabled global policies ordered by priority
dcm policy list --filter "policy_type='GLOBAL' AND enabled=true" --order-by "priority asc"
# List with pagination
dcm policy list --page-size 10
# JSON output
dcm policy list -o jsonExample output (table):
ID DISPLAY NAME TYPE PRIORITY ENABLED CREATED
my-policy Require CPU Limits GLOBAL 100 true 2026-03-09T10:00:00Z
other-pol Memory Limits USER 500 true 2026-03-08T15:30:00Z
Get a single policy by ID.
| Argument | Required | Description |
|---|---|---|
POLICY_ID |
Yes | Policy ID |
dcm policy get POLICY_ID
# YAML output
dcm policy get POLICY_ID -o yamlUpdate an existing policy (JSON Merge Patch - RFC 7396).
| Argument | Required | Description |
|---|---|---|
POLICY_ID |
Yes | Policy ID |
| Flag | Required | Description |
|---|---|---|
--from-file |
Yes | Path to patch YAML/JSON file |
dcm policy update POLICY_ID --from-file patch.yamlPatch file (only mutable fields):
priority: 50
enabled: false
rego_code: |
package dcm.policy
default allow = trueDelete a policy by ID.
| Argument | Required | Description |
|---|---|---|
POLICY_ID |
Yes | Policy ID |
dcm policy delete POLICY_IDExample output:
Policy "my-policy" deleted successfully.
List service types with optional pagination.
| Flag | Required | Description |
|---|---|---|
--page-size |
No | Maximum results per page |
--page-token |
No | Token for next page |
dcm catalog service-type list
dcm catalog service-type list --page-size 5Get a single service type by ID.
| Argument | Required | Description |
|---|---|---|
SERVICE_TYPE_ID |
Yes | Service type ID |
dcm catalog service-type get SERVICE_TYPE_IDCreate a new catalog item.
| Flag | Required | Description |
|---|---|---|
--from-file |
Yes | Path to catalog item YAML/JSON file |
--id |
No | Client-specified catalog item ID |
dcm catalog item create --from-file item.yaml
dcm catalog item create --from-file item.yaml --id my-catalog-itemCatalog item file format:
api_version: v1alpha1
display_name: "Small Container"
spec:
service_type: container
fields:
- path: spec.replicas
display_name: "Replica Count"
editable: true
default: "1"
validation_schema:
type: integer
minimum: 1
maximum: 10
- path: spec.container.image
display_name: "Container Image"
editable: trueExample output (table):
ID UID DISPLAY NAME CREATED
my-catalog-item b2c3d4e5-f6a7-8901-bcde-f12345678901 Small Container 2026-03-09T10:00:00Z
List catalog items with optional filtering and pagination.
| Flag | Required | Description |
|---|---|---|
--service-type |
No | Filter by service type |
--page-size |
No | Maximum results per page |
--page-token |
No | Token for next page |
dcm catalog item list
dcm catalog item list --service-type containerGet a single catalog item by ID.
| Argument | Required | Description |
|---|---|---|
CATALOG_ITEM_ID |
Yes | Catalog item ID |
dcm catalog item get CATALOG_ITEM_ID
dcm catalog item get CATALOG_ITEM_ID -o yamlDelete a catalog item by ID.
| Argument | Required | Description |
|---|---|---|
CATALOG_ITEM_ID |
Yes | Catalog item ID |
dcm catalog item delete CATALOG_ITEM_IDCreate a new catalog item instance.
| Flag | Required | Description |
|---|---|---|
--from-file |
Yes | Path to instance YAML/JSON file |
--id |
No | Client-specified instance ID |
dcm catalog instance create --from-file instance.yamlInstance file format:
api_version: v1alpha1
display_name: "My App Instance"
spec:
catalog_item_id: my-catalog-item
user_values:
- path: spec.replicas
value: "3"
- path: spec.container.image
value: "nginx:latest"Example output (table):
ID UID DISPLAY NAME CATALOG ITEM CREATED
my-instance c3d4e5f6-a7b8-9012-cdef-123456789012 My App Instance my-catalog-item 2026-03-09T10:00:00Z
List catalog item instances with optional pagination.
| Flag | Required | Description |
|---|---|---|
--page-size |
No | Maximum results per page |
--page-token |
No | Token for next page |
dcm catalog instance list
dcm catalog instance list -o jsonGet a single catalog item instance by ID.
| Argument | Required | Description |
|---|---|---|
INSTANCE_ID |
Yes | Instance ID |
dcm catalog instance get INSTANCE_IDDelete a catalog item instance by ID.
| Argument | Required | Description |
|---|---|---|
INSTANCE_ID |
Yes | Instance ID |
dcm catalog instance delete INSTANCE_IDList SP resources (service type instances) with optional filtering and pagination.
| Flag | Required | Description |
|---|---|---|
--provider |
No | Filter by provider |
--page-size |
No | Maximum results per page |
--page-token |
No | Token for next page |
dcm sp resource list
dcm sp resource list --provider kubevirt-123
dcm sp resource list --page-size 5Example output (table):
ID PROVIDER STATUS CREATED
my-instance kubevirt-123 ACTIVE 2026-03-09T10:00:00Z
other-instance openstack-456 PENDING 2026-03-08T15:30:00Z
Get a single SP resource by instance ID.
| Argument | Required | Description |
|---|---|---|
INSTANCE_ID |
Yes | Service type instance ID |
dcm sp resource get INSTANCE_ID
dcm sp resource get INSTANCE_ID -o yamlGenerate shell autocompletion scripts.
| Argument | Required | Description |
|---|---|---|
SHELL |
Yes | Shell type: bash, zsh, fish, or powershell |
# Bash
source <(dcm completion bash)
# Zsh
dcm completion zsh > "${fpath[1]}/_dcm"
# Fish
dcm completion fish | source
# PowerShell
dcm completion powershell | Out-String | Invoke-ExpressionPrint CLI version and build information.
dcm versionExample output:
dcm version 0.1.0
commit: abc1234
built: 2026-03-09T10:00:00Z
go: go1.25.5
Manages CLI configuration with file persistence and environment/flag overrides.
package config
type Config struct {
ControlPlaneURL string `yaml:"control-plane-url" mapstructure:"control-plane-url"`
OutputFormat string `yaml:"output-format" mapstructure:"output-format"`
Timeout int `yaml:"timeout" mapstructure:"timeout"`
TLSCACert string `yaml:"tls-ca-cert" mapstructure:"tls-ca-cert"`
TLSClientCert string `yaml:"tls-client-cert" mapstructure:"tls-client-cert"`
TLSClientKey string `yaml:"tls-client-key" mapstructure:"tls-client-key"`
TLSSkipVerify bool `yaml:"tls-skip-verify" mapstructure:"tls-skip-verify"`
}
// Load reads configuration from file, environment, and flag overrides.
func Load() (*Config, error)Formats API responses for display.
package output
type Format string
const (
FormatTable Format = "table"
FormatJSON Format = "json"
FormatYAML Format = "yaml"
)
type Formatter interface {
// FormatOne formats a single resource.
FormatOne(resource any) error
// FormatList formats a list of resources with optional pagination info.
FormatList(resources any, nextPageToken string) error
// FormatMessage formats a simple status message.
FormatMessage(msg string) error
}
// New creates a Formatter for the given format writing to the given writer.
func New(format Format, w io.Writer) FormatterCobra command definitions. Each file registers its command tree and wires generated client calls.
// root.go
func NewRootCommand() *cobra.Command
// policy.go
func newPolicyCommand() *cobra.Command // parent: dcm policy
func newPolicyCreateCommand() *cobra.Command // dcm policy create
func newPolicyListCommand() *cobra.Command // dcm policy list
func newPolicyGetCommand() *cobra.Command // dcm policy get
func newPolicyUpdateCommand() *cobra.Command // dcm policy update
func newPolicyDeleteCommand() *cobra.Command // dcm policy delete
// catalog_service_type.go
func newCatalogServiceTypeCommand() *cobra.Command // parent: dcm catalog service-type
func newServiceTypeListCommand() *cobra.Command
func newServiceTypeGetCommand() *cobra.Command
// catalog_item.go
func newCatalogItemCommand() *cobra.Command // parent: dcm catalog item
func newCatalogItemCreateCommand() *cobra.Command
func newCatalogItemListCommand() *cobra.Command
func newCatalogItemGetCommand() *cobra.Command
func newCatalogItemDeleteCommand() *cobra.Command
// catalog_instance.go
func newCatalogInstanceCommand() *cobra.Command // parent: dcm catalog instance
func newCatalogInstanceCreateCommand() *cobra.Command
func newCatalogInstanceListCommand() *cobra.Command
func newCatalogInstanceGetCommand() *cobra.Command
func newCatalogInstanceDeleteCommand() *cobra.Command
// sp.go
func newSPCommand() *cobra.Command // parent: dcm sp
// sp_resource.go
func newSPResourceCommand() *cobra.Command // parent: dcm sp resource
func newSPResourceListCommand() *cobra.Command
func newSPResourceGetCommand() *cobra.Command
// completion.go
func newCompletionCommand() *cobra.Command // dcm completion [bash|zsh|fish|powershell]Build-time version information injected via linker flags.
package version
var (
Version = "dev"
Commit = "unknown"
BuildTime = "unknown"
)
type Info struct {
Version string
Commit string
BuildTime string
GoVersion string
}
func Get() InfoThe CLI imports generated client packages from the control-plane monorepo:
- pkg/policy/client — Policy Manager client
- pkg/catalog/client — Catalog Manager client
- pkg/sp/client/resource_manager — SP Resource Manager client
- pkg/sp/client/provider — SP Manager client
These are oapi-codegen generated clients providing typed API access. Key interfaces:
// Policy client (from control-plane/pkg/policy/client)
type ClientInterface interface {
CreatePolicy(ctx context.Context, params *CreatePolicyParams, body CreatePolicyJSONRequestBody, ...) (*http.Response, error)
ListPolicies(ctx context.Context, params *ListPoliciesParams, ...) (*http.Response, error)
GetPolicy(ctx context.Context, policyId string, ...) (*http.Response, error)
UpdatePolicyWithApplicationMergePatchPlusJSONBody(ctx context.Context, policyId string, body UpdatePolicyApplicationMergePatchPlusJSONRequestBody, ...) (*http.Response, error)
DeletePolicy(ctx context.Context, policyId string, ...) (*http.Response, error)
}
// Catalog client (from control-plane/pkg/catalog/client)
type ClientInterface interface {
CreateServiceType(ctx context.Context, params *CreateServiceTypeParams, body CreateServiceTypeJSONRequestBody, ...) (*http.Response, error)
ListServiceTypes(ctx context.Context, params *ListServiceTypesParams, ...) (*http.Response, error)
GetServiceType(ctx context.Context, serviceTypeId string, ...) (*http.Response, error)
// ... similar for CatalogItem and CatalogItemInstance operations
}Clients are instantiated with the control-plane URL and a configured HTTP client. When the control-plane URL uses https://, the HTTP client is configured with a TLS transport based on the TLS settings (CA cert, client cert/key, skip verify). When the URL uses http://, TLS is not configured.
httpClient := buildHTTPClient(cfg) // configures TLS transport when URL is https
policyClient, _ := policyclient.NewClient(cfg.ControlPlaneURL + "/api/v1alpha1",
policyclient.WithHTTPClient(httpClient))
catalogClient, _ := catalogclient.NewClient(cfg.ControlPlaneURL + "/api/v1alpha1",
catalogclient.WithHTTPClient(httpClient))
sprmClient, _ := sprmclient.NewClient(cfg.ControlPlaneURL + "/api/v1alpha1",
sprmclient.WithHTTPClient(httpClient))dcm-cli/
├── .ai/
│ └── specs/
│ └── dcm-cli.spec.md
├── cmd/
│ └── dcm/
│ └── main.go
├── internal/
│ ├── config/
│ │ ├── config.go
│ │ └── config_test.go
│ ├── output/
│ │ ├── formatter.go
│ │ ├── table.go
│ │ ├── json.go
│ │ ├── yaml.go
│ │ └── formatter_test.go
│ ├── commands/
│ │ ├── root.go
│ │ ├── root_test.go
│ │ ├── version.go
│ │ ├── policy.go
│ │ ├── policy_test.go
│ │ ├── catalog_service_type.go
│ │ ├── catalog_service_type_test.go
│ │ ├── catalog_item.go
│ │ ├── catalog_item_test.go
│ │ ├── catalog_instance.go
│ │ ├── catalog_instance_test.go
│ │ ├── sp.go
│ │ ├── sp_resource.go
│ │ ├── sp_resource_test.go
│ │ ├── completion.go
│ │ └── completion_test.go
│ └── version/
│ └── version.go
├── test/
│ └── e2e/
│ ├── policy_test.go
│ ├── catalog_test.go
│ └── e2e_suite_test.go
├── .github/
│ └── workflows/
│ ├── ci.yaml
│ ├── lint.yaml
│ ├── check-clean-commits.yaml
│ ├── release.yaml
│ └── tag-release.yaml
├── .goreleaser.yaml
├── CLAUDE.md
├── Containerfile
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── README.md
└── tools.go
User invokes command
│
├─▶ Cobra parses flags and arguments
│
├─▶ Viper resolves config (flags → env → file → defaults)
│
├─▶ Build HTTP client (configure TLS transport if URL is https://)
│
├─▶ Create generated client with control-plane URL and HTTP client
│
├─▶ Execute API call via generated client
│
├─▶ Check response status
│ ├─ Success → format and display response
│ └─ Error → parse RFC 7807 problem details, display error, set exit code
│
└─▶ Exit
dcm policy create --from-file policy.yaml
│
├─▶ Read and parse policy.yaml (YAML or JSON)
├─▶ POST /api/v1alpha1/policies (with optional ?id=<id>)
├─▶ Display created policy
└─▶ Exit 0
dcm policy list --filter "enabled=true" --order-by "priority asc"
│
├─▶ GET /api/v1alpha1/policies?filter=enabled%3Dtrue&order_by=priority+asc
├─▶ Display policy table
├─▶ If next_page_token present, show pagination hint
└─▶ Exit 0
dcm policy get <id>
│
├─▶ GET /api/v1alpha1/policies/<id>
├─▶ Display policy details
└─▶ Exit 0
dcm policy update <id> --from-file patch.yaml
│
├─▶ Read and parse patch.yaml
├─▶ PATCH /api/v1alpha1/policies/<id>
├─▶ Display updated policy
└─▶ Exit 0
dcm policy delete <id>
│
├─▶ DELETE /api/v1alpha1/policies/<id>
├─▶ Display success message
└─▶ Exit 0
dcm catalog instance create --from-file instance.yaml
│
├─▶ Read and parse instance.yaml
│ Contains: catalog_item_id, user_values
├─▶ POST /api/v1alpha1/catalog-item-instances
├─▶ Display created instance
└─▶ Exit 0
While --page-size is an optional parameter, services may impose a default value. Always check if
the response included next_page_token
When a list response includes next_page_token, the CLI displays it for manual follow-up:
dcm policy list --page-size 2
Output:
ID DISPLAY NAME TYPE PRIORITY ENABLED CREATED
my-policy Require CPU Limits GLOBAL 100 true 2026-03-09T10:00:00Z
other-pol Memory Limits USER 500 true 2026-03-08T15:30:00Z
Next page: dcm policy list --page-size 2 --page-token eyJvZmZzZXQiOjJ9
For JSON/YAML output, next_page_token is included in the response object.
TLS is determined automatically by the control-plane URL scheme:
http://— TLS is not used. All TLS-related flags and config are ignored.https://— TLS is enabled. The CLI constructs atls.Configand uses it for the HTTP transport.
| Option | Description |
|---|---|
--tls-ca-cert |
Path to a PEM-encoded CA certificate file. Used to verify the server's certificate. When not set, the system's default CA bundle is used. |
--tls-client-cert |
Path to a PEM-encoded client certificate file for mutual TLS (mTLS). Must be used together with --tls-client-key. |
--tls-client-key |
Path to a PEM-encoded client private key file for mTLS. Must be used together with --tls-client-cert. |
--tls-skip-verify |
When set, the CLI skips server certificate verification. Not recommended for production. |
- If
--tls-client-certis set,--tls-client-keyMUST also be set (and vice versa). The CLI MUST exit with code 2 if only one is provided. - TLS flags are silently ignored when the control-plane URL uses
http://. - If the CA cert, client cert, or client key file does not exist or is not readable, the CLI MUST exit with code 1 with a clear error message.
TLS options follow the same precedence as other configuration:
- CLI flags (
--tls-ca-cert,--tls-client-cert,--tls-client-key,--tls-skip-verify) - Environment variables (
DCM_TLS_CA_CERT,DCM_TLS_CLIENT_CERT,DCM_TLS_CLIENT_KEY,DCM_TLS_SKIP_VERIFY) - Configuration file (
tls-ca-cert,tls-client-cert,tls-client-key,tls-skip-verify) - Built-in defaults (empty strings,
false)
| Category | Description | Exit Code |
|---|---|---|
| Configuration error | Invalid config file, missing required config | 1 |
| Connection error | Cannot reach control plane | 1 |
| API error | Backend returned an error response | 1 |
| Input error | Invalid flags, missing arguments, bad file | 2 |
| Timeout | Request exceeded timeout | 1 |
API errors follow RFC 7807 Problem Details format. The CLI parses the response and displays a human-readable error:
Error: NOT_FOUND - Policy "nonexistent" not found.
Status: 404
Detail: The requested policy resource does not exist.
For JSON/YAML output, the full Problem Details object is printed:
{
"type": "NOT_FOUND",
"status": 404,
"title": "Policy \"nonexistent\" not found.",
"detail": "The requested policy resource does not exist."
}| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Runtime error (API error, connection error, timeout) |
| 2 | Usage error (invalid flags, missing arguments) |
build # Build dcm binary to bin/dcm
test # Run unit tests with Ginkgo
test-e2e # Run E2E tests (requires live stack)
fmt # Format Go code
vet # Run go vet
lint # Run linter
clean # Remove build artifacts
tidy # Run go mod tidyVersion information is injected at build time via ldflags:
VERSION ?= $(shell git describe --tags --always --dirty)
COMMIT ?= $(shell git rev-parse --short HEAD)
BUILD_TIME ?= $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
LDFLAGS = -X github.com/dcm-project/dcm-cli/internal/version.Version=$(VERSION) \
-X github.com/dcm-project/dcm-cli/internal/version.Commit=$(COMMIT) \
-X github.com/dcm-project/dcm-cli/internal/version.BuildTime=$(BUILD_TIME)
build:
go build -ldflags "$(LDFLAGS)" -o bin/dcm ./cmd/dcmReleases are automated via GoReleaser and GitHub Actions. When a v* tag is pushed, the tag-release workflow runs CI tests and linting, then triggers the release workflow which uses GoReleaser to:
- Build
dcmbinaries for linux, darwin, and windows (amd64 and arm64) - Inject version, commit, and build time via ldflags
- Create a GitHub release with archives and checksums
- Include the LICENSE in each archive
To create a release:
git tag v0.1.0
git push origin v0.1.0Multi-stage UBI9 build matching other DCM services:
# Build stage
FROM registry.access.redhat.com/ubi9/go-toolset:1.25.5 AS builder
WORKDIR /opt/app-root/src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o dcm ./cmd/dcm
# Runtime stage
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
COPY --from=builder /opt/app-root/src/dcm /usr/local/bin/dcm
USER 1001
ENTRYPOINT ["dcm"]module github.com/dcm-project/dcm-cli
go 1.25.5
| Dependency | Purpose |
|---|---|
github.com/spf13/cobra |
CLI framework |
github.com/spf13/viper |
Configuration management |
gopkg.in/yaml.v3 |
YAML parsing/output |
github.com/dcm-project/control-plane |
Generated API clients (policy, catalog, SP) |
github.com/onsi/ginkgo/v2 |
Test framework (test dependency) |
github.com/onsi/gomega |
Test matchers (test dependency) |
//go:build tools
package tools
import (
_ "github.com/onsi/ginkgo/v2/ginkgo"
)- Framework: Ginkgo + Gomega
- Location:
*_test.gofiles alongside source - Mocking:
net/http/httptestfor HTTP-level mocking of generated client calls - Coverage areas:
- Configuration loading and precedence
- Output formatting (table, JSON, YAML)
- Command flag parsing and validation
- API response handling and error parsing
- Input file parsing (YAML/JSON)
Example test pattern:
var _ = Describe("Policy Commands", func() {
var (
server *httptest.Server
// ...
)
BeforeEach(func() {
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Mock API responses
}))
})
AfterEach(func() {
server.Close()
})
Describe("policy create", func() {
It("should create a policy from a YAML file", func() {
// ...
})
})
})Running tests:
make test # All unit tests
go test -run TestName ./internal/commands # Specific test- Location:
test/e2e/ - Build tag:
//go:build e2e(excluded frommake test) - Requirements: Live DCM stack (control-plane on :8080)
- Framework: Ginkgo + Gomega
- Scope: Full command execution against real services
Running E2E tests:
make test-e2e # Requires DCM_CONTROL_PLANE_URL pointing to live stack- Policy CRUD operations (create, list, get, update, delete)
- Service type read operations (list, get)
- Catalog item operations (create, list, get, delete)
- Catalog item instance operations (create, list, get, delete)
- SP resource read operations (list, get)
- Version display
- Output formatting (table, JSON, YAML)
- Configuration via file, environment variables, and flags
- Pagination support for list operations
- TLS support with custom CA certificates, client certificates (mTLS), and skip-verify
- Shell autocompletion generation (bash, zsh, fish, powershell)
- Container image for distribution
- Authentication and authorization (no auth in v1alpha1 control-plane API)
- Interactive/wizard-style resource creation
- Watch/streaming operations
- Plugin/extension system
- Offline mode or local caching
- Bulk operations
- Resource diff/dry-run
- Health check command for control-plane connectivity