dynamic-rp Architecture
September 18, 2026 ยท View on GitHub
dynamic-rp is the generic resource provider for resource types that do not
have their own dedicated provider implementation in this repository.
This process owns generic resource lifecycle handling for dynamic types. It is the main place for authoring and handling Radius resource types through a generic provider model.
Entry Points
- Binary entry: cmd/dynamic-rp/main.go
- Cobra root: cmd/dynamic-rp/cmd/root.go
- Config loading: pkg/dynamicrp/config.go
- Option construction: pkg/dynamicrp/options.go
- Server bootstrap: pkg/dynamicrp/server/server.go
Quick Reference
| Topic | Start Here |
|---|---|
| Startup | cmd/dynamic-rp/cmd/root.go |
| Host composition | pkg/dynamicrp/server/server.go |
| API service | pkg/dynamicrp/frontend/service.go |
| Route wiring | pkg/dynamicrp/frontend/routes.go |
| Async backend | pkg/dynamicrp/backend/service.go |
| Test Focus | Packages |
|---|---|
| Frontend/read-path behavior | ./pkg/dynamicrp/frontend/... |
| Backend/processor behavior | ./pkg/dynamicrp/backend/... |
| Integration coverage | ./pkg/dynamicrp/integrationtest/... |
| Broad safety check | ./pkg/dynamicrp/... |
Core Packages
| Package | Responsibility |
|---|---|
pkg/dynamicrp/frontend | request handling and API surface |
pkg/dynamicrp/backend | backend processing and async work |
pkg/dynamicrp/datamodel | dynamic resource persistence model |
pkg/dynamicrp/api | versioned API types |
pkg/dynamicrp/server | process bootstrap and hosting |
How It Works
The process starts in cmd/dynamic-rp/cmd/root.go, which reads config, constructs runtime options, and builds a host through pkg/dynamicrp/server/server.go.
Dynamic RP exists so Radius resource types can be authored without a bespoke RP implementation for each type, so its route and backend layers need to stay type-agnostic.
Change dynamic-rp when the behavior applies across dynamic resource types,
such as generic lifecycle orchestration, shared persistence behavior, generic
async handling, common validation and metadata behavior, or new Radius resource
type authoring patterns.
Invariants And Constraints
- Keep the implementation generic and type-agnostic where possible.
- Avoid leaking dedicated provider behavior into the dynamic provider.
- Treat persistence, queueing, and secrets as shared runtime dependencies rather than business logic destinations.
Change This Safely
Scoped containerImages Bicep Hook
The Bicep driver has a hook used only by Radius.Compute/containerImages. When a recipe returns an imageBuild object, the driver loads the script embedded in the recipe, adds the operator-configured registry, passes the object's fields as command-line flags, and waits for the script to push the image. The script and imageBuild output can change together without a Radius driver change. Other resource types ignore this output, and recipe parameters cannot provide the script.
ARM/Bicep cannot call BuildKit directly, so dynamic-rp runs the script where the in-Pod BuildKit endpoint is available. The registry and credential Secret name come from the registered Recipe, which prevents developer overrides from redirecting credentials. Radius reads the Secret from the recipe runtime namespace on the selected cluster, including RADIUS_TARGET_KUBECONFIG, and does not fall back when that target is invalid. The build runs on every recipe execution and stores no state.
Shared BuildKit Capacity
All containerImages operations in one dynamic-rp Pod share its BuildKit sidecar and memory cgroup. The chart configures BuildKit's native OCI worker scheduler through dynamicrp.buildkit.maxParallelism, which defaults to one concurrent build step across all active solves. A generated buildkitd.toml carries the setting, and its Pod-template checksum restarts dynamic-rp when the value changes because BuildKit reads daemon configuration only at startup.
Git build sources need no local storage. For local sources, dynamicrp.buildkit.localContexts.existingClaim mounts an operator-managed PVC read-only into dynamic-rp at /var/radius/build-contexts; the chart does not create or populate the claim.
Keep this limit independent from workerServer.maxOperationConcurrency. The worker setting bounds all Dynamic RP operations, while BuildKit's scheduler bounds the memory-intensive execution steps within and across image builds. Increasing BuildKit parallelism requires profiling representative cold builds and sizing the sidecar's memory request and limit with sufficient headroom. This is a concurrency boundary, not per-build isolation: one build can still exceed the configured memory limit.
Packages That Usually Move Together
pkg/dynamicrp/frontendandpkg/dynamicrp/backendwhen request handling and async behavior are linkedpkg/dynamicrp/apiandpkg/dynamicrp/datamodelwhen resource shape changespkg/dynamicrp/server, config, and options code when process bootstrap changes
Suggested Test Scope
go test ./pkg/dynamicrp/...- Pay particular attention to frontend, backend/controller, backend/processor,
and integration tests under
pkg/dynamicrp/integrationtest/...
Package Dependency View
graph TD Root[cmd/dynamic-rp/cmd] Options["pkg/dynamicrp<br/>config and options"] Host["pkg/dynamicrp/server<br/>plus hosting/components"] Frontend[pkg/dynamicrp/frontend] Backend[pkg/dynamicrp/backend] BackendController[pkg/dynamicrp/backend/controller] API[pkg/dynamicrp/api] DataModel["pkg/dynamicrp/datamodel<br/>and converter"] Shared[pkg/armrpc + pkg/components + middleware] UCPClients[pkg/ucp/api/v20231001preview + pkg/sdk] Crypto[pkg/crypto/encryption] Recipes[pkg/recipes/engine] Kube[Kubernetes provider and runtime client] Root --> Options Root --> Host Host --> Frontend Host --> Backend Host --> Shared Frontend --> API Frontend --> DataModel Frontend --> Shared Frontend --> UCPClients Frontend --> Crypto Frontend --> Kube Backend --> BackendController Backend --> DataModel Backend --> Shared Backend --> Recipes Backend --> UCPClients Backend --> Kube BackendController --> DataModel BackendController --> Recipes BackendController --> UCPClients BackendController --> Shared Crypto --> Kube
The important static seam is root -> host -> frontend generic routes versus
backend default controller registration. Dynamic RP is organized around a
generic request model rather than a large set of resource-specific setup
packages.
Representative Flow
sequenceDiagram
participant UCP
participant API as dynamic-rp frontend
participant Route as frontend/routes.go
participant DefaultAsync as default async PUT/DELETE controller
participant Store as resource database
participant Status as status manager
participant Queue
participant Worker as backend worker
participant DefaultCtrl as default dynamic controller
participant Processor as dynamic processor / recipes
UCP->>API: PUT or DELETE dynamic resource request
API->>Route: match generic route
Route->>DefaultAsync: default async handler
opt PUT request
DefaultAsync->>DefaultAsync: apply defaults and validate plaintext properties
break Schema-invalid PUT
DefaultAsync-->>API: 400 InvalidRequestContent
API-->>UCP: synchronous error (resource unchanged)
end
DefaultAsync->>DefaultAsync: encrypt sensitive fields
end
DefaultAsync->>Store: persist resource state
DefaultAsync->>Status: create status + queue message
Status->>Queue: enqueue request
API-->>UCP: ARM async response
Queue->>Worker: dequeue request
Worker->>DefaultCtrl: default controller from registry
DefaultCtrl->>Processor: validate/process resource
The representative Dynamic RP flow is generic request-to-default-controller handoff. The frontend builds generic routes and default async handlers, then the backend worker resolves the operation through a default controller factory instead of a resource-specific registration table.
PUT update filters run in the order defaults, plaintext schema validation, encryption, before the shared controller saves the resource or queues an operation. A schema-invalid create returns HTTP 400 with InvalidRequestContent and leaves no resource. A rejected update leaves the existing properties, provisioning state, metadata, and ETag unchanged. Valid PUTs and DELETEs retain their asynchronous behavior; the backend also retains validation of stored, encrypted resource data.
Plaintext validation enforces the declared constraints on sensitive fields before encryption can replace their values with encrypted objects. Redacted values returned by GET are not instructions to retain old secrets: supplied null values follow the schema's nullability rules. Validation errors identify schema-declared top-level fields without exposing submitted sensitive values or nested object keys. This ordering prevents new invalid writes; it does not repair invalid properties persisted by earlier versions.