Resource Types
A ResourceType is to managed infrastructure what a ComponentType is to code. It's the template a developer references when they declare a Resource — a database, a queue, a cache, an object store — and it governs how that infrastructure is provisioned on the data plane and what it exposes back to the workloads that consume it. Authoring ResourceTypes is how you let developers say "I need a Postgres" and get a correctly-provisioned, correctly-wired database without filing an infrastructure ticket.
DevsPortal is built on the Control Plane Operator engine. ResourceType,
ClusterResourceType, Resource, ResourceReleaseBinding, the openchoreo.dev/v1alpha1 API
group, and the occ CLI are unchanged engine internals. Examples target the Fedshi
instance.
Author ResourceTypes from the portal's Create… templates — they open the in-portal YAML editor, pre-filled with the right kind. The occ/YAML form below is for scripting, review, and GitOps. ResourceType authoring is schema, output, and CEL work, so there's no no-YAML wizard — the portal gives you the editor and validation.
Where you author them
In the portal: open Create… in the left nav and pick the ClusterResourceType
template (scroll the Platform Resources section to find it), or the namespace-scoped
ResourceType template. It opens the in-portal YAML editor seeded with the kind, where you
fill in the parameter schema, resources, outputs, and retainPolicy described below.
Or with the occ CLI: write the manifest and apply it — see Managing them with the
CLI below.
Why you author them
ResourceTypes let you publish provisioning as a self-service product:
- Reusable provisioning templates — Postgres, NATS, Valkey, an S3 bucket, a Crossplane claim — authored once, used by any team.
- A schema for what developers control, so they tune the parts you allow (engine version, size) and nothing else.
- A declared output contract, so consumers wire hostnames, ports, and credentials into their containers by name — no guessing connection strings.
- Retention defaults, so an accidental delete in dev doesn't behave like one in prod.
A developer references a ResourceType from Resource.spec.type and supplies parameters; a
ResourceReleaseBinding then renders the template per environment, applies it to the data plane,
and surfaces the resulting outputs.
ResourceType vs ClusterResourceType
A ClusterResourceType is the cluster-scoped variant — same spec, platform-wide visibility.
Use it for templates meant to be shared across all organizations; use a namespace-scoped
ResourceType for one that's only relevant to a single org. The platform ships example
ClusterResourceTypes (postgres, valkey, nats) backed by in-cluster StatefulSets — fine for
local development, but not for production. For real environments you author your own
templates that target a production-grade provisioner (Crossplane, a cloud operator, ACK).
The anatomy of a ResourceType
| Part | Field | What it does |
|---|---|---|
| Parameter schema | parameters.openAPIV3Schema | Developer-set values captured in the immutable ResourceRelease (engine version, schema name) |
| Environment schema | environmentConfigs.openAPIV3Schema | Per-environment overrides via the binding (memory, storage size, admin-UI toggles) |
| Resources | resources | The Kubernetes manifests the provisioner emits on the data plane, rendered with CEL |
| Outputs | outputs | Named values (host, port, password) that consuming workloads bind into containers |
| Retention | retainPolicy | Default deletion behavior (Delete or Retain) for bindings of this type |
The parameters / environmentConfigs split mirrors ComponentTypes: parameters are identical
across environments for a given release; environment configs differ per environment while the
release snapshot stays unchanged. A cache can be 128Mi in dev and 2Gi in prod from the same
release.
Outputs: the consumer contract
Outputs are the whole reason a ResourceType is useful — they're how a provisioned database
hands its connection details to a workload. Each output has a unique name and exactly one
source kind:
| Source | Use for | What reaches the control plane |
|---|---|---|
value | Non-sensitive data (host, port, region, database name, composed URLs) | The resolved literal value |
secretKeyRef | Sensitive credentials (passwords, tokens, keys) | Only {name, key} of a data-plane Secret — the secret value never leaves the data plane |
configMapKeyRef | Non-sensitive runtime config from a data-plane ConfigMap (CA bundles, locale) | Only {name, key} of the ConfigMap |
This is a security-sensitive design point: declare credentials with secretKeyRef, never
value, so passwords stay on the data plane and only the reference transits the control plane.
Output expressions can use CEL, including applied.<id>.status.*, to surface fields the
provisioner populated — e.g. a Crossplane claim's status.connectionDetails.
A representative example
A valkey-cache ResourceType (Redis-protocol cache) backed by a StatefulSet, exposing host,
port, and password outputs.
apiVersion: openchoreo.dev/v1alpha1
kind: ResourceType
metadata:
name: valkey-cache
namespace: fedshi
spec:
parameters:
openAPIV3Schema:
type: object
properties:
version:
type: string
enum: ["7", "8"]
default: "8"
environmentConfigs:
openAPIV3Schema:
type: object
properties:
memory:
type: string
default: "128Mi"
# Default retention; bindings can override per environment
retainPolicy: Delete
# The consumer contract
outputs:
- name: host
value: "${metadata.name}.${metadata.namespace}.svc.cluster.local"
- name: port
value: "6379"
- name: password
secretKeyRef: # credential stays on the data plane
name: "${metadata.name}-creds"
key: password
# Manifests the provisioner emits on the data plane
resources:
- id: service
template:
apiVersion: v1
kind: Service
metadata:
name: ${metadata.name}
namespace: ${metadata.namespace}
labels: ${metadata.labels}
spec:
selector:
app: ${metadata.name}
ports:
- name: valkey
port: 6379
targetPort: 6379
- id: statefulset
# Custom readiness signal beyond the default heuristic
readyWhen: "${applied.statefulset.status.readyReplicas == applied.statefulset.status.replicas && applied.statefulset.status.replicas > 0}"
template:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ${metadata.name}
namespace: ${metadata.namespace}
labels: ${metadata.labels}
spec:
serviceName: ${metadata.name}
replicas: 1
selector:
matchLabels:
app: ${metadata.name}
template:
metadata:
labels:
app: ${metadata.name}
spec:
containers:
- name: valkey
image: valkey/valkey:${parameters.version}-alpine
resources:
limits:
memory: ${environmentConfigs.memory}
ports:
- containerPort: 6379
name: valkey
Two lifecycle fields shape each resource entry:
includeWhen— a boolean CEL expression evaluated at render time. Whenfalse, the entry is omitted and any previously-applied object is garbage-collected. Use it to make optional pieces conditional (${parameters.tlsEnabled}to emit a Certificate only when TLS is on).readyWhen— a boolean CEL expression evaluated after the object is applied, to define readiness when the default per-Kind heuristic doesn't match your provisioner's signal (the StatefulSet quorum check above, or a Crossplane claim'sReadycondition).
Production templates should generate credentials on the data plane — for example via an
ExternalSecret backed by a password generator — so the literal never transits the control plane.
The shipped postgres/valkey/nats examples demonstrate the full pattern; they use in-cluster
StatefulSets and are for local development, not production.
Retention
retainPolicy sets the default deletion behavior for bindings of the type:
Delete(default) — deleting aResourceReleaseBindingremoves the emitted data-plane manifests during finalization.Retain— the binding's finalizer holds on delete, preserving the underlying data-plane state until the policy is flipped back.
Individual environments override the default on the binding. The standard pattern: production
opts into Retain for non-recoverable state (databases, volumes) while dev and staging keep
Delete. This is the difference between a fat-fingered kubectl delete being an annoyance and
being a data-loss incident — set it deliberately for stateful types.
How developers consume it
A developer declares a Resource referencing your type, then binds its outputs into a workload
by name:
apiVersion: openchoreo.dev/v1alpha1
kind: Resource
metadata:
name: cart-cache
namespace: fedshi
spec:
owner:
projectName: checkout
type:
kind: ResourceType
name: valkey-cache
parameters:
version: "8"
---
apiVersion: openchoreo.dev/v1alpha1
kind: Workload
metadata:
name: cart-api
namespace: fedshi
spec:
owner:
projectName: checkout
componentName: cart-api
dependencies:
resources:
- ref: cart-cache
envBindings: # your output names → their env vars
host: REDIS_HOST
port: REDIS_PORT
password: REDIS_PASSWORD
The developer never sees the StatefulSet, the Secret, or the connection string — they request a
valkey-cache and bind the outputs you declared. Promotion into an environment is an explicit
ResourceReleaseBinding (created by a platform engineer or GitOps), which renders the template
with the combined parameters and environment overrides and resolves the outputs. The developer
side is documented in dependencies.
A ResourceType is for managed infrastructure a developer declares as a Resource. That's
different from a Trait, which overlays capabilities onto a component's own
workload. Reach for a ResourceType when the thing has its own lifecycle (a database); reach for a
Trait when you're augmenting the component (a volume mount, a sidecar).
Managing them with the CLI
occ clusterresourcetype list
occ clusterresourcetype get valkey-cache
occ resourcetype list -n fedshi
occ apply -f valkey-cache.yaml
Deep reference
The full CEL surface for resource templates and outputs (metadata, parameters,
environmentConfigs, dataplane, gateway, applied), the includeWhen/readyWhen
semantics, and the schema features are documented upstream:
- Authoring ResourceTypes.
- CEL reference — context variables and built-ins.
- API and CRDs — the DevsPortal pointer into the CRDs.
What's next
- Component types — the code-side counterpart, and the workloads that consume Resource outputs.
- Traits — when to use a capability overlay instead of a managed Resource.
- Developers: dependencies — how developers wire Resource outputs into containers.