Pipeline Reference¶
The Pipeline CRD is the primary user-facing resource in kardinal-promoter. It defines the promotion path for one application: which Git repo contains the manifests, which environments exist, what order they promote in, and how each environment is configured.
Full Spec¶
apiVersion: kardinal.io/v1alpha1
kind: Pipeline
metadata:
name: <string> # Pipeline name, typically matches the application
namespace: <string> # Kubernetes namespace
spec:
git: # Git repository configuration
url: <string> # GitOps repo URL (HTTPS)
branch: <string> # Base branch (default: "main")
layout: <string> # "directory" (default) or "branch"
sourceBranch: <string> # Source branch for DRY templates (layout: branch only; default: spec.git.branch)
branchPrefix: <string> # Branch prefix for rendered envs (layout: branch only; default: "env/")
provider: <string> # "github" or "gitlab"
secretRef:
name: <string> # Secret containing the Git token
webhookMode: <string> # "webhook" (default) or "polling"
pollInterval: <duration> # Poll interval when webhookMode: polling (default: "30s")
environments: # Ordered list of environments
- name: <string> # Environment name (must be unique within the Pipeline)
path: <string> # Path in the GitOps repo (default: "environments/<name>")
dependsOn: [<string>, ...] # Environments this one depends on (default: previous in list)
update:
strategy: <string> # "kustomize" (default), "helm", "replace" (future)
approval: <string> # "auto" (default) or "pr-review"
# pr: <bool> # For approval: auto, set pr: true to create audit PRs
renderManifests: <bool> # When true, runs kustomize-build and commits rendered YAML to env branch (requires layout: branch)
health:
type: <string> # "resource" (default), "argocd", "flux", "argoRollouts", "flagger"
resource: # When type: resource
kind: <string> # Default: "Deployment"
name: <string> # Default: Pipeline metadata.name
namespace: <string> # Default: environment name
condition: <string> # Default: "Available"
argocd: # When type: argocd
name: <string> # Argo CD Application name
namespace: <string> # Default: "argocd"
flux: # When type: flux
name: <string> # Flux Kustomization name
namespace: <string> # Default: "flux-system"
argoRollouts: # When type: argoRollouts
name: <string> # Rollout name
namespace: <string> # Rollout namespace
flagger: # When type: flagger
name: <string> # Canary name
namespace: <string> # Canary namespace
cluster: <string> # kubeconfig Secret name for remote clusters
timeout: <duration> # Health check timeout (default: "10m")
delivery:
delegate: <string> # "none" (default), "argoRollouts" (implemented), "flagger" (implemented)
shard: <string> # Agent shard name for distributed mode (optional)
steps: # Custom promotion step sequence (optional, overrides defaults)
- uses: <string> # Step name (built-in or custom)
config: <map> # Step-specific configuration (for custom steps: url, timeout)
historyLimit: <int> # Number of Bundles to retain (default: 20)
Field Details¶
spec.git¶
| Field | Required | Default | Description |
|---|---|---|---|
url | Yes | HTTPS URL of the GitOps repository | |
branch | No | main | Base branch for manifest reads |
layout | No | directory | directory: environments as directories on one branch. branch: environments as separate branches (use with sourceBranch and branchPrefix for rendered manifests). |
sourceBranch | No | spec.git.branch | Only used with layout: branch. The branch containing DRY source templates. Promotions read from this branch before rendering. |
branchPrefix | No | env/ | Only used with layout: branch. Prefix for rendered environment branches. For branchPrefix: env/, the prod environment writes to env/prod. |
provider | Yes | github or gitlab. Selects the SCM provider for PR creation. | |
secretRef.name | Yes | Name of a Kubernetes Secret in the Pipeline's namespace containing a token field with a GitHub PAT or GitLab token. | |
webhookMode | No | webhook | webhook: react to GitHub webhook events for fast PR merge detection. polling: fall back to periodic polling (use in environments where inbound webhooks are not reachable). |
pollInterval | No | 30s | Polling interval when webhookMode: polling. Has no effect in webhook mode. |
spec.environments[]¶
| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | Environment name. Must be unique within the Pipeline. Used in PolicyGate matching (kardinal.io/applies-to label). | |
path | No | environments/<name> | Directory (layout: directory) or branch (layout: branch) in the GitOps repo containing the environment's manifests. |
dependsOn | No | Previous environment | List of environment names that must be Verified before this one starts. Default: sequential ordering (each depends on the previous). Specifying dependsOn enables parallel fan-out. |
wave | No | 0 (sequential) | Assigns this environment to a numbered deployment wave (K-06). Environments with the same wave number are promoted in parallel. Wave N automatically depends on all wave N-1 environments. Composable with dependsOn. |
update.strategy | No | kustomize | How to update image references in manifests. kustomize: runs kustomize edit set-image. helm: patches a configurable path in values.yaml. |
approval | No | auto | auto: push directly to the target branch, no PR. pr-review: open a PR with promotion evidence, wait for human merge. |
renderManifests | No | false | When true, runs kustomize-build after kustomize-set-image and commits rendered plain YAML to the environment branch. Requires layout: branch. Enables the rendered manifests pattern. |
health.type | No | auto-detected | Health verification adapter. Auto-detected on startup if omitted: checks for Argo CD Application CRD, then Flux Kustomization CRD, then falls back to Deployment condition. |
health.timeout | No | 10m | How long to wait for health verification before marking the step as Failed. |
health.cluster | No | (local cluster) | Name of a Kubernetes Secret containing a kubeconfig for a remote cluster. Used for multi-cluster health verification. |
health.labelSelector | No | (none) | Label selector for WatchKind mode (health.type=resource only). When set, watches all Deployments in the environment namespace that match these labels using krocodile's O(1) incremental cache. Example: {"app": "nginx", "kardinal.io/pipeline": "nginx-demo"}. When unset, a single Deployment named after the Pipeline is watched (default Watch behavior). Ignored for argocd, flux, argoRollouts, and flagger. |
delivery.delegate | No | none | Progressive delivery delegation. argoRollouts: watch Argo Rollouts Rollout status after promotion. flagger: watch Flagger Canary status. none: instant deploy (rolling update). |
shard | No | (none) | Agent shard name for distributed mode. When set, only a kardinal-agent started with --shard=<value> will reconcile this environment's PromotionSteps. When omitted, the control plane controller handles the step. |
steps | No | (inferred) | Custom promotion step sequence. When omitted, the default sequence is inferred from update.strategy, approval, and renderManifests. When specified, overrides the default entirely. See Promotion Steps. |
bake.minutes | No | (none) | Contiguous-healthy soak window in minutes (K-01). When set, the step must observe healthy deployment status continuously for this many minutes before transitioning to Verified. A health alarm resets the timer. |
bake.policy | No | reset-on-alarm | What to do when health fails during the bake window. reset-on-alarm: reset the elapsed timer to 0, stay in HealthChecking. fail-on-alarm: immediately apply onHealthFailure policy. |
onHealthFailure | No | none | What to do when health fails during bake with policy: fail-on-alarm (K-03). none: step → Failed (default behavior). abort: step → AbortedByAlarm; requires human intervention. rollback: create a rollback Bundle at the previous image version; step → RollingBack. |
spec.historyLimit¶
Number of Bundles (and their associated Graph objects) to retain per Pipeline. Older Bundles are garbage-collected. The Git PR history is permanent regardless of this setting.
Default: 20.
Health Check Defaults¶
When the health field is omitted or partially specified, the controller applies defaults:
| Field | Default |
|---|---|
type | Auto-detected (argocd if Application CRD exists, flux if Kustomization CRD exists, resource otherwise) |
resource.kind | Deployment |
resource.name | Pipeline.metadata.name |
resource.namespace | Environment name |
resource.condition | Available |
timeout | 10m |
This means a minimal environment definition with no health field works for the common case where the Deployment name matches the Pipeline name and the namespace matches the environment name.
Environment Ordering¶
Environments promote sequentially by default. Each environment depends on the one above it in the list.
environments:
- name: dev # depends on nothing (first)
- name: staging # depends on dev
- name: prod # depends on staging
For non-linear topologies, use dependsOn to express parallel fan-out:
environments:
- name: dev
- name: staging
- name: prod-us
dependsOn: [staging] # depends on staging, parallel with prod-eu
- name: prod-eu
dependsOn: [staging] # depends on staging, parallel with prod-us
Both prod-us and prod-eu start after staging is Verified. They run concurrently.
For converging topologies (all regions must pass before a final step):
environments:
- name: staging
- name: prod-us
dependsOn: [staging]
- name: prod-eu
dependsOn: [staging]
- name: post-deploy-validation
dependsOn: [prod-us, prod-eu] # waits for both regions
Wave Topology (K-06)¶
For multi-region deployments with many parallel environments, wave: is syntactic sugar for dependsOn. Environments with the same wave number are promoted in parallel. Wave N environments automatically depend on all wave N-1 environments.
environments:
- name: test # no wave — sequential (depends on nothing)
- name: staging # no wave — sequential (depends on test)
# Wave 1: prod-eu and prod-us start simultaneously after staging
- name: prod-eu
wave: 1
approval: pr-review
bake:
minutes: 720
- name: prod-us
wave: 1
approval: pr-review
bake:
minutes: 720
# Wave 2: prod-ap starts after BOTH prod-eu AND prod-us reach Verified
- name: prod-ap
wave: 2
approval: pr-review
bake:
minutes: 480
wave: and explicit dependsOn are composable — the final dependency set is the union of wave-derived edges and any explicit dependsOn entries. Non-wave environments (Wave == 0) continue to use the sequential default (each depends on the previous in the list).
See examples/wave-topology/pipeline.yaml for a complete example.
Git Layout: Directory vs Branch¶
Directory layout (default, recommended for small teams): all environments share one branch. Each environment is a directory.
main branch:
environments/
dev/
kustomization.yaml
staging/
kustomization.yaml
prod/
kustomization.yaml
Promotion updates the image tag in the target directory and pushes (auto) or opens a PR (pr-review) against the base branch.
Branch layout: each environment is a separate branch. Used for the rendered manifests pattern where DRY Kustomize source lives on one branch and rendered plain YAML lives on environment-specific branches.
main branch (DRY source):
environments/
dev/kustomization.yaml
staging/kustomization.yaml
prod/kustomization.yaml
env/dev branch (rendered):
deployment.yaml (plain YAML — no Kustomize)
service.yaml
env/staging branch (rendered):
deployment.yaml
service.yaml
env/prod branch (rendered):
deployment.yaml
service.yaml
With branch layout and renderManifests: true, the promotion sequence is: 1. Clone source branch (main) 2. Run kustomize-set-image against the overlay 3. Run kustomize-build to render plain YAML 4. Commit rendered output to env/<name>-incoming branch 5. Open PR from env/<name>-incoming to env/<name> 6. Wait for merge (or push directly for approval: auto) 7. Health check
Argo CD Applications must track the rendered branch (env/prod), not the source branch (main).
See Rendered Manifests for a complete setup guide.
Rendered Manifests: Inferred Step Sequence¶
When renderManifests: true is set on an environment, the default step sequence is:
approval | Step sequence |
|---|---|
auto | git-clone → kustomize-set-image → kustomize-build → git-commit → git-push → health-check |
pr-review | git-clone → kustomize-set-image → kustomize-build → git-commit → git-push → open-pr → wait-for-merge → health-check |
Without renderManifests: true, the standard sequence omits kustomize-build.
Integration Test Step (K-07)¶
The integration-test built-in step creates a Kubernetes Job in the target environment namespace, waits for it to complete, and writes the result to the step output accumulator. This is the most powerful quality gate after bake time — running real tests against the actual deployed service.
Configuration¶
The step reads its config from PromotionStep.spec.inputs:
| Input key | Required | Default | Description |
|---|---|---|---|
integration_test.image | Yes | Container image to run (e.g., ghcr.io/myorg/integration-tests:latest) | |
integration_test.command | No | container default | Space-separated command and args (e.g., ./run-tests.sh --env staging) |
integration_test.timeout | No | 30m | Maximum time to wait for Job completion (Go duration, e.g., 10m, 1h) |
Behavior¶
- First call: creates a
batch/v1 Jobin the environment namespace and returnsPending(reconciler requeues in 15s). - Subsequent calls: re-checks Job status. Returns
Pendingwhile running,Successwhen Job succeeds,Failedwhen Job fails. - Timeout: if
integration_test.timeoutelapses, the Job is deleted and the step returnsFailed. - Idempotent: multiple reconcile iterations never create duplicate Jobs (deterministic Job name from bundle+env).
- Cleanup: Jobs use
ttlSecondsAfterFinished: 3600so they self-delete after 1 hour.
Outputs¶
On success, the step populates: - integration_test.result: "passed" - integration_test.job: the Job name - integration_test.elapsed: time taken (e.g., "2m34s")
On failure: - integration_test.result: "failed" - integration_test.job: the Job name
Example¶
environments:
- name: test
steps:
- uses: git-clone
- uses: kustomize-set-image
- uses: git-commit
- uses: git-push
- uses: health-check
- uses: integration-test # runs after health check passes
See examples/integration-test/pipeline.yaml for a complete example.
Examples¶
Minimal (3 lines per environment, all defaults)¶
apiVersion: kardinal.io/v1alpha1
kind: Pipeline
metadata:
name: my-app
spec:
git:
url: https://github.com/myorg/gitops-repo
provider: github
secretRef: { name: github-token }
environments:
- name: dev
- name: staging
- name: prod
approval: pr-review
Multi-cluster with Argo Rollouts¶
apiVersion: kardinal.io/v1alpha1
kind: Pipeline
metadata:
name: my-app
spec:
git:
url: https://github.com/myorg/gitops-repo
provider: github
secretRef: { name: github-token }
environments:
- name: test
approval: auto
health:
type: argocd
argocd: { name: my-app-test }
- name: pre-prod
approval: pr-review
health:
type: argocd
argocd: { name: my-app-pre-prod }
- name: prod-us
dependsOn: [pre-prod]
approval: pr-review
health:
type: argoRollouts
argoRollouts: { name: my-app, namespace: prod }
delivery:
delegate: argoRollouts
- name: prod-eu
dependsOn: [pre-prod]
approval: pr-review
health:
type: argoRollouts
argoRollouts: { name: my-app, namespace: prod }
delivery:
delegate: argoRollouts
Flux-based with remote clusters¶
apiVersion: kardinal.io/v1alpha1
kind: Pipeline
metadata:
name: my-app
spec:
git:
url: https://github.com/myorg/gitops-repo
provider: github
secretRef: { name: github-token }
environments:
- name: dev
health:
type: flux
flux: { name: my-app-dev, namespace: flux-system }
- name: prod
approval: pr-review
health:
type: flux
flux: { name: my-app-prod, namespace: flux-system }
cluster: prod-cluster
Rendered manifests (branch layout with kustomize-build)¶
apiVersion: kardinal.io/v1alpha1
kind: Pipeline
metadata:
name: my-app
spec:
git:
url: https://github.com/myorg/gitops-repo
provider: github
secretRef: { name: github-token }
layout: branch
sourceBranch: main # DRY templates (Kustomize overlays)
branchPrefix: env/ # rendered branches: env/dev, env/staging, env/prod
environments:
- name: dev
approval: auto
renderManifests: true
health:
type: argocd
argocd: { name: my-app-dev }
- name: staging
approval: auto
renderManifests: true
health:
type: argocd
argocd: { name: my-app-staging }
- name: prod
approval: pr-review
renderManifests: true # PR diff shows rendered YAML, not template source
health:
type: argocd
argocd: { name: my-app-prod }
Argo CD Applications for the rendered-manifests pattern must track the env branch, not main: