Configuration
App CRD spec — source types, destination, sync policy, and status fields
Configuration
The App custom resource (cd.wardn.space/v1alpha1, resource apps, short name app) is the core abstraction in Wardn. It defines what to deploy (a pluggable source), how to deploy it (syncPolicy, freeze), and where (destination).
Renamed CRD. The previous GitApplication kind was replaced by App — same API group, richer spec. All examples below use the current schema.
Full Example — Git source
apiVersion: cd.wardn.space/v1alpha1
kind: App
metadata:
name: my-app
namespace: org-my-org # the org's management-cluster namespace
spec:
source:
type: Git # Git | Image | HelmChart | Catalog
git:
repoURL: https://github.com/myorg/my-app
branch: main
path: manifests
manifestType: Auto # Auto | Raw | Helm | Kustomize
destination:
namespace: default # required — where manifests are applied
cluster: staging-cluster # optional — empty = management cluster
syncPolicy:
automated: true
prune: true
# selfHeal: true # 📋 declared, not wired in the reconciler
# requiresApproval: true
freeze: false # hold all syncs; health still reconciles
notifications:
slack:
webhookURL: https://hooks.slack.com/services/T.../B.../xxx
onSyncSuccess: true
onSyncFailure: true
# onHealthDegraded: true # 📋 declared, not wired in the dispatcher
webhook:
enabled: true
# secretRef: webhook-secret # 📋 reserved; signature validation not wiredSource
spec.source selects where manifests come from. A CEL rule enforces that exactly one block matching type is set.
type | Block | Status |
|---|---|---|
Git | source.git | ✅ Implemented |
Image | source.image | ✅ Implemented |
HelmChart | source.helmChart | 📋 Schema reserved — no resolver yet |
Catalog | source.catalog | 📋 Schema reserved — no resolver yet |
source.git
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
repoURL | string | Yes | — | Git repository URL (clone runs the git binary — public repos only today) |
branch | string | No | main | Git branch to track |
path | string | No | . | Directory containing manifests |
manifestType | enum | No | Auto | Auto detects kustomization.yaml → Kustomize, Chart.yaml → Helm, else Raw |
helm | object | No | — | Helm config for manifestType: Helm (see below) |
kustomize | object | No | — | Kustomize overrides layered onto kustomization.yaml |
substitution | map | No | — | Flux-style ${VAR} map applied post-render |
source.git.helm
| Field | Type | Description |
|---|---|---|
valuesFile | string | Values file path relative to path |
releaseName | string | Release name (defaults to app name) |
namespace | string | Release namespace |
inlineValues | string | Raw YAML applied on top of valuesFile |
📋 Helm rendering is not implemented. manifestType: Helm (or a detected Chart.yaml) currently fails the reconcile. The helm config block exists for the future implementation.
source.git.kustomize
| Field | Type | Description |
|---|---|---|
images | []string | Image overrides (newName=newTag entries) |
namePrefix | string | Prepended to all rendered resource names |
components | []string | Kustomize component paths to include |
source.image
No-YAML deploys — the operator renders a Deployment + Service (+ optional Ingress/HPA) from a container image.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
registryRef | string | No | — | Name of a registered wardn registry credential |
repository | string | Yes | — | Image repository (e.g. ghcr.io/acme/api); with registryRef, relative to the registry host |
tag | string | Yes | — | Image tag |
tagPolicy | enum | No | Pinned | Pinned resolves the tag once per sync; Follow redeploys when the digest moves (server tag poller) |
pullSecretRef | string | No | — | Target-cluster imagePullSecrets secret name |
workload | object | No | — | Rendered Deployment config (below) |
ingress | object | No | — | enabled, host, tlsSecretRef |
autoscaling | object | No | — | enabled, min, max, cpuPercent (HPA) |
source.image.workload
| Field | Type | Default | Description |
|---|---|---|---|
port | int32 | 80 | Container port |
replicas | int32 | 1 | Replica count |
env | []EnvVar | — | {name, value} literal, {name, secretRef:{name,key}} from an existing Secret, or {name, store:{id,path,key}} from a wardn secret store |
resources | object | 100m/128Mi requests | requests/limits maps (cpu/memory keys) |
probe | object | TCP probe on port | disabled, path (switches to httpGet), initialDelaySeconds, periodSeconds, failureThreshold |
source.helmChart — 📋 planned
Schema exists (repoURL, chart, version, values) but no resolver is registered — a HelmChart app fails with an unsupported-source error.
source.catalog — 📋 planned
Schema exists (template, params) but is not implemented.
Destination
| Field | Type | Required | Description |
|---|---|---|---|
namespace | string | Yes | Target namespace on the cluster |
cluster | string | No | Registered cluster name; empty = the management cluster the operator runs on |
Remote clusters are resolved at reconcile time — the operator fetches the kubeconfig from the server (/api/v1/internal/clusters/:name/kubeconfig), so no credential secrets sit on the management cluster.
Sync Policy
| Field | Type | Default | Status |
|---|---|---|---|
automated | bool | false | ✅ Sync on every reconcile when upstream changed (periodic resync, no manual trigger needed) |
prune | bool | false | ✅ Delete resources that left the tracked set |
selfHeal | bool | false | 📋 Planned, not wired — the reconciler never reads it. Automated sync re-applies upstream diffs; out-of-band kubectl edits to unchanged objects are not reverted today |
requiresApproval | bool | false | ✅ Stages syncs as Held; a human releases via POST /apps/{ns}/{name}/approve |
Prune deletes cluster resources that exist under the app's managed label but are no longer in the rendered set. Scope the app to a dedicated namespace before enabling.
Freeze
spec.freeze: true blocks all syncs — manual and automated — while health still reconciles. Settable via POST /apps/{ns}/{name}/freeze|unfreeze; audited as app.freeze / app.unfreeze.
Notifications
Slack only today — one POST per event, no retries or dedup.
| Field | Type | Status |
|---|---|---|
slack.webhookURL | string | ✅ Incoming webhook URL |
onSyncSuccess | bool | ✅ |
onSyncFailure | bool | ✅ |
onHealthDegraded | bool | 📋 Planned, not wired — the dispatcher never reads it |
Teams/Email/Discord channels and message templates are planned — see the Roadmap.
Webhook
| Field | Type | Status |
|---|---|---|
enabled | bool | ✅ Required — pushes only match apps with webhook.enabled: true |
secretRef | string | 📋 Planned — HMAC signature validation isn't wired (any POST can trigger a match today) |
The operator's receiver listens on :8082 — POST /webhook/github ✅ works; POST /webhook/gitlab 📋 accepts requests but doesn't parse payloads yet.
Operation — the manual-action contract
spec.operation is written by the wardn API (never by the operator) to request an async action. The operator consumes it and records the outcome in status.operationState.
| Field | Type | Description |
|---|---|---|
kind | enum | Sync, Deploy, Rollback, Restart, Approve |
initiatedAt | time | Request timestamp — a new value means a new request |
id | string | Correlation UUID the server stamps; echoed in operationState |
revision | string | Target revision for Rollback / Deploy |
triggeredBy | string | Requesting identity (user id, webhook:github) |
Sync asserts desired state — a Pinned tag stays on its recorded digest. Deploy re-resolves the tag live, so the same tag pulls a moved digest. Restart rolls pods via a template annotation bump.
Status Fields
status:
syncStatus: Synced # Synced | OutOfSync | Syncing | Unknown
healthStatus: Healthy # Healthy | Degraded | Progressing | Missing | Unknown
healthReason: "" # e.g. "Deployment api: 2/5 ready"
lastSyncAt: "2024-01-15T10:30:00Z"
revision: abc1234 # last revision APPLIED — the rollback marker
observedRevision: def5678 # newest revision seen upstream
previousRevisions:
- def5678
- ghi9012
objectHash: sha256:… # hash of the rendered set at last apply
url: https://app.example.com
operationState:
kind: Sync
initiatedAt: "…"
id: "uuid"
phase: Succeeded # Succeeded | Failed | Held
revision: abc1234
finishedAt: "…"
resources:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: default
syncStatus: Synced
healthStatus: Healthy
message: ""
conditions:
- type: Ready
status: "True"revision vs observedRevision: revision only moves on successful apply; observedRevision tracks branch HEAD. An OutOfSync app shows a newer observed sha than applied sha — and after a rollback, observed stays ahead until the next sync.
| Status Field | Description |
|---|---|
syncStatus | Whether applied state matches the rendered source |
healthStatus / healthReason | Aggregate resource health + the first failing reason |
lastSyncAt | Timestamp of last successful apply |
revision / observedRevision | Applied sha vs newest upstream sha |
previousRevisions | Recent applied revisions (rollback history) |
objectHash | sha256 of the last rendered set — lets spec edits trigger resync on the same upstream sha |
resources | Per-resource apiVersion/kind/name/namespace/syncStatus/healthStatus/message |
url | Public address when an image-source ingress is enabled |
operationState | Outcome of the last consumed spec.operation |
conditions | Kubernetes-style conditions (Ready, SyncError) |
Known Limitations
| Area | Status | Detail |
|---|---|---|
selfHeal | 📋 Planned | Declared in schema; the reconciler never reads it |
onHealthDegraded | 📋 Planned | Declared in schema; the dispatcher never reads it |
manifestType: Helm | 📋 Planned | Fails the reconcile — renderer not wired |
HelmChart / Catalog sources | 📋 Planned | Schema reserved; no resolvers |
| Git credentials | 📋 Planned | Clone uses the git binary — public repos only |
Webhook secretRef | 📋 Planned | GitHub push payloads are not signature-validated |
syncWaves / hooks / ignoreDifferences | 📋 Planned | Not in the schema — see the Roadmap |
Related
- Sync Policies — automated sync, prune, freeze, approval gate
- Health Checks — how Wardn evaluates resource health
- Manifest Management — Raw and Kustomize rendering
- CRD Reference — complete type definitions