UCP Architecture

March 13, 2026 ยท View on GitHub

UCP is the control-plane routing hub for Radius. It accepts ARM-style requests, identifies the target plane or provider, and either serves UCP behavior, reverse-proxies the request, or adapts it to an external system.

UCP owns request routing, plane-aware addressing, parts of protocol translation, and shared control-plane concerns such as API version handling. It is not the place for resource-type authoring or Kubernetes reconciliation logic.

Entry Points

cmd/ucpd/cmd/root.go reads a service config file, constructs UCP options, creates a logger, builds the server, and hands execution to shared hosting.

Quick Reference

TopicStart Here
Startupcmd/ucpd/cmd/root.go
HTTP/API wiringpkg/ucp/frontend/api/server.go
Top-level routespkg/ucp/frontend/api/routes.go
Proxy/adaptationpkg/ucp/frontend/controller, pkg/ucp/proxy
Resource IDspkg/ucp/resources
Test FocusPackages
Unit and route tests./pkg/ucp/frontend/..., ./pkg/ucp/proxy/...
Integration coverage./pkg/ucp/integrationtests/...
Broad safety check./pkg/ucp/...

Core Packages

PackageResponsibility
pkg/ucp/frontendHTTP handlers, route registration, API surface
pkg/ucp/proxyrequest forwarding and proxy helpers
pkg/ucp/awsAWS-specific adaptation logic
pkg/ucp/resourcesUCP resource ID parsing and construction
pkg/ucp/datamodelversion-agnostic UCP storage model
pkg/ucp/serverservice startup and hosting integration
pkg/ucp/configruntime configuration types

How It Works

The UCP process starts in cmd/ucpd/cmd/root.go, loads config, constructs runtime options, and builds a multi-service host via pkg/ucp/server/server.go.

At request time, the frontend under pkg/ucp/frontend decides whether the request terminates in UCP or is forwarded downstream. The architectural hinge is pkg/ucp/resources: most routing logic is only correct if plane, scope, and provider segments are parsed consistently.

For UCP-native or ARM-like targets, forwarding can be relatively direct. For non-ARM targets such as AWS, UCP also performs protocol adaptation rather than simple proxying.

Invariants And Constraints

  • UCP should stay focused on routing, identity of targets, and translation.
  • Resource-type authoring logic should remain in provider processes, primarily dynamic-rp.
  • Resource ID parsing and plane resolution need to stay consistent across all entry points.
  • Changes in proxy behavior often require checking call flows, headers, and API version handling together.

Change This Safely

Packages That Usually Move Together

  • pkg/ucp/frontend/api, pkg/ucp/frontend/controller, and pkg/ucp/proxy when routing behavior changes
  • pkg/ucp/resources and controller code when resource ID parsing or provider resolution changes
  • pkg/ucp/datamodel and pkg/ucp/api when persisted shape or API version conversions change

Suggested Test Scope

  • go test ./pkg/ucp/...
  • Pay particular attention to route, proxy, and integration-style tests under: pkg/ucp/frontend/..., pkg/ucp/proxy/..., and pkg/ucp/integrationtests/...

Package Dependency View

graph TD
  Root[cmd/ucpd/cmd]
  Options["pkg/ucp<br/>config and options"]
  Host["pkg/ucp/server<br/>plus hosting/components"]
  Frontend[pkg/ucp/frontend/api]
  Modules[pkg/ucp/frontend/modules]
  PlaneFrontends["pkg/ucp/frontend/aws<br/>pkg/ucp/frontend/azure<br/>pkg/ucp/frontend/radius"]
  FrontendControllers[pkg/ucp/frontend/controller/...]
  Proxy[pkg/ucp/proxy]
  Resources[pkg/ucp/resources]
  DataModel["pkg/ucp/datamodel<br/>and converter"]
  Backend[pkg/ucp/backend]
  BackendControllers[pkg/ucp/backend/controller/...]
  Initializer[pkg/ucp/initializer]
  Shared[pkg/armrpc + pkg/components + middleware]
  SDK[pkg/sdk and downstream clients]

  Root --> Options
  Root --> Host
  Host --> Frontend
  Host --> Backend
  Host --> Initializer
  Host --> Shared
  Frontend --> Modules
  Frontend --> PlaneFrontends
  Frontend --> FrontendControllers
  Frontend --> Resources
  Frontend --> DataModel
  Frontend --> Shared
  PlaneFrontends --> Modules
  PlaneFrontends --> FrontendControllers
  PlaneFrontends --> DataModel
  PlaneFrontends --> Shared
  FrontendControllers --> Resources
  FrontendControllers --> DataModel
  FrontendControllers --> Proxy
  Backend --> BackendControllers
  Backend --> DataModel
  Backend --> Shared
  Backend --> SDK
  BackendControllers --> DataModel
  BackendControllers --> Resources
  BackendControllers --> SDK
  Proxy --> Resources
  Proxy --> Shared
  Initializer --> SDK
  Initializer --> Shared

The important static seam is root -> host -> frontend/api module dispatch versus backend worker and initializer. UCP does not use the builder-driven namespace model used by the generic provider; it dispatches through plane modules and then drops into controller, proxy, and resource-ID logic.

Representative Flow

sequenceDiagram
  participant Client
  participant Router as chi router (configured at startup)
  participant CatchAll as /planes/{planeType} subrouter
  participant Module as plane module handler
  participant Controller as plane controller
  participant Downstream as provider or external plane

  Client->>Router: HTTP request
  Router->>CatchAll: match /planes/{planeType}/...
  CatchAll->>Module: look up handler by planeType
  Module->>Controller: route to controller via module routes
  Controller->>Downstream: proxy or adapt request

The representative UCP flow is plane dispatch. The frontend API service builds a module map, registers a catch-all route under /planes/{planeType}, then hands the request to the selected module handler. After that handoff, the plane module owns the rest of the path: direct UCP behavior, reverse proxying, or adaptation.