Platform Engineering Usage Guide¶
This guide explains how to consume platform products, create new applications, and build new products on the orchestrator architecture (ADR 007).
Table of Contents¶
- Concepts
- Quick Start
- Using Existing Platform Products
- Creating a New Application
- Creating a New Product Package
- Validation and Deployment
- Testing Patterns
Concepts¶
Infrastructure is modeled as platform products that teams request as namespaced Kubernetes resources (Crossplane v2 XRs — there is no separate Claim model).
| Term | Description |
|---|---|
| Primitive Product | A single reusable infrastructure abstraction (PostgresInstance, ObjectBucket). |
| Stack | A composite product orchestrating multiple children (ObservabilityStack). |
| XR | The namespaced composite resource a developer creates to request a product. |
| XRD | CompositeResourceDefinition (apiextensions.crossplane.io/v2). Defines the XR schema. |
| Composition | Thin Pipeline composition delegating to the orchestrator function. |
| Orchestrator | function-platform: the Go composition function that reconciles every kind. |
| Profile | Environment tier: development, staging, or production. Drives contract defaults. |
| Feature Toggle | Optional capability under spec.features.<name>.enabled (e.g. HA, backups). |
Quick Start¶
1. Bootstrap a local cluster¶
scripts/e2e-setup.sh platform-e2e # Crossplane, providers, ESO, CNPG, provider-helm, function-platform
scripts/team-setup.sh team-api # per-team namespace + helm ProviderConfig + RoleBinding
2. Install product APIs¶
kubectl --context kind-platform-e2e apply \
-f packages/postgres/dist/xrd.k8s.yaml \
-f packages/postgres/dist/composition.k8s.yaml
3. Develop¶
bash scripts/pre-push-checks.sh # contract check, lint, build, tests, synth, validate, go vet/test -race
npm run gen:contract # regenerate TS + Go after editing contract/contract.json
Using Existing Platform Products¶
See the dedicated runbooks for step-by-step instructions:
With kubectl¶
apiVersion: platform.7kgroup.org/v1alpha1
kind: PostgresInstance
metadata:
name: team-api-db
namespace: team-api
spec:
team: team-api
profile: development
costCenter: cc-12345
features:
ha:
enabled: true
Status is the contract (Crossplane v2 has no XR connection secrets):
kubectl -n team-api get postgresinstance team-api-db \
-o jsonpath='{.status.endpoint}{"\n"}{.status.phase}{"\n"}{.status.connectionSecretRef.name}{"\n"}'
# team-api-db-pg-rw.team-api.svc:5432
# Ready
# team-api-db-pg-app <- CNPG operator secret (keys: username, password, dbname, host, port, uri)
With the TypeScript Consumer SDK¶
import { App } from 'cdk8s';
import { TeamObservability, PostgresInstance } from '@7k-hiroba/platform-consumer';
const app = new App({ outdir: 'dist' });
new PostgresInstance(app, 'db', {
name: 'team-api-db',
namespace: 'team-api',
team: 'team-api',
profile: 'development',
costCenter: 'cc-12345',
});
new TeamObservability(app, 'obs', {
name: 'team-api-obs',
namespace: 'team-api',
team: 'team-api',
profile: 'development',
modules: {
grafana: { enabled: true, domain: 'grafana.team-api.example.com' },
loki: { enabled: false },
metrics: { enabled: true, backend: 'prometheus' },
alloy: { enabled: true },
},
});
app.synth();
npx ts-node src/main.ts && kubectl apply -f dist/
For products without a dedicated construct, use the generic PlatformXr construct
from the SDK.
Creating a New Application¶
An application repository consumes products through the SDK:
mkdir examples/my-service && cd examples/my-service
npm init -y
npm install cdk8s @7k-hiroba/platform-consumer ts-node typescript
Write src/main.ts (see the SDK example above), then:
npx ts-node src/main.ts
kubectl apply -f dist/
Creating a New Product Package¶
Products ship an XRD + thin Composition from a cdk8s package, plus a Go handler in the orchestrator.
1. Scaffold the package¶
npx create-platform-product --name redis --category database
2. Define the XRD¶
import { Chart } from 'cdk8s';
import { Construct } from 'constructs';
import { createPlatformXrd, createBaseSchema, API_GROUP, API_VERSION } from '@7k-hiroba/shared';
export class RedisInstanceXrd extends Chart {
constructor(scope: Construct, id: string) {
super(scope, id);
createPlatformXrd(
this,
'xrd',
{
group: API_GROUP,
version: API_VERSION, // v1alpha1
kind: 'RedisInstance',
plural: 'redisinstances',
singular: 'redisinstance',
scope: 'Namespaced',
},
{
...createBaseSchema(),
provider: { type: 'string', enum: ['aws', 'incluster'] },
},
['profile', 'team', 'costCenter'],
);
}
}
Schema rules imposed by the v2 XRD -> CRD converter:
- no
connectionSecretKeys(XR connection secrets do not exist in v2); - no
additionalPropertiescombined withpropertieson nested object fields — keep nested objects either structural (propertiesonly) or free-form.
3. Emit the thin Composition¶
import { createOrchestratedComposition } from '@7k-hiroba/shared';
// one Pipeline step invoking function-platform; no composition logic in TS
4. Implement the Go handler¶
Add functions/platform/handlers/redis.go:
- read the observed XR (
hc.OXR), resolve provider (spec.provider>PLATFORM_DEFAULT_*env > contract default); - emit desired composed resources with deterministic names;
- set explicit readiness:
platform.MarkReady(res, hc, name); - publish
status.phase/status.endpoint/status.connectionSecretRef; - for Helm workloads use the namespaced
helm.m.crossplane.io/v1beta1Release API andplatform.ResolveProviderConfig(oxr, "helm")(<team>-helm); - wire children from observed child
status— never from connection details (v2 drops XR-level connection secrets).
Register the handler in cmd/main.go and add tests in handlers/handlers_test.go.
5. Update the contract¶
Add the product to contract/contract.json (kind, plural, providers, profiles,
connection keys) and declare its operator dependencies so missing operators fail
fast with an actionable error:
"dependencies": {
"RedisInstance": {
"*": [{ "crd": "releases.helm.m.crossplane.io", "hint": "install provider-helm" }],
"aws": [{ "crd": "instances.elasticache.aws.m.upbound.io", "hint": "install provider-aws-elasticache" }]
}
}
Run npm run gen:contract. The --check gate in scripts/pre-push-checks.sh
enforces freshness.
Validation and Deployment¶
Local render validation¶
HIROBA_ROOT=$PWD bash scripts/render-validate.sh packages/postgres
Deploy to Kubernetes¶
kubectl apply -f packages/postgres/dist/xrd.k8s.yaml
kubectl apply -f packages/postgres/dist/composition.k8s.yaml
For Helm-backed products, each team namespace needs (created by
scripts/team-setup.sh): a namespaced ProviderConfig <team>-helm
(helm.m.crossplane.io/v1beta1) and a RoleBinding granting the provider-helm
service account admin in the namespace.
Live e2e on kind¶
scripts/e2e-setup.sh platform-e2e
scripts/team-setup.sh team-api
# apply XRDs/Compositions, create an XR, assert Ready + status.endpoint
Testing Patterns¶
- TS packages: synthesize the chart and assert the XRD/Composition shape
(
Testing.synth). Seepackages/postgres/test/unit/snapshot.test.ts. - Go handlers: table tests building a
HandlerContextwith observed composed resources; assert desired resources, readiness, and status. Seefunctions/platform/handlers/handlers_test.go. - Render:
scripts/render-validate.shrunscrossplane composition renderagainst fixtures.
npm run test:unit --workspace=@7k-hiroba/postgres
(cd functions/platform && go test -race ./...)