CRD Specs
PromotionStrategy
The PromotionStrategy is the user's interface to controlling how changes are promoted through their environments. In this CR, the user configures the list of live hydrated environment branches in their order of promotion. They'll also configure the checks which must pass between promotion steps.
apiVersion: promoter.argoproj.io/v1alpha1
kind: PromotionStrategy
metadata:
name: example-promotion-strategy
spec:
gitRepositoryRef:
name: example-git-repo
activeCommitStatuses:
- key: argocd-app-health
proposedCommitStatuses:
- key: security-scan
environments:
- branch: environment/dev
- branch: environment/test
- branch: environment/prod
autoMerge: false
activeCommitStatuses:
- key: performance-test
proposedCommitStatuses:
- key: deployment-freeze
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
# The Ready condition indicates that the resource has been successfully reconciled, when there is an error during
# reconciliation, the condition will be False with a reason of ReconciliationError. When we successfully reconcile the resource,
# the condition will be True with a reason of ReconciliationSuccess. The Ready condition is essentially a way to show reconciliation
# errors to the user. This condition exists on all resources that have reconciliation logic.
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess, ReconciliationError, ChangeTransferPolicyNotReady, or PreviousEnvironmentCommitStatusNotReady
status: "True" # "True," "False," or "Unknown"
observedGeneration: 123
environments:
- branch: environment/dev
# The proposed and active fields are pulled directly from the status of the environment's ChangeTransferPolicy resource.
proposed:
dry:
author: "Author Name <author@example.com>"
body: "Body of the commit message (i.e. excluding the subject line)"
commitTime: 2023-10-01T00:00:00Z
repoURL: "https://git.example.com/org/repo.git"
sha: "abcdef1234567890abcdef1234567890abcdef12"
subject: "chore: Example commit subject line"
references:
# An array of reference commits that where used to create the dry commit. This is used generally via CI systems to add tracing information to the commit.
# For example, in a code/deployment repo setup this could contain the commit from the code repo that triggered the deployment commit in the deployment repo.
- commit:
author: '"Zach Aller" <code@example.com>'
body: |-
Commit message of the code commit
Signed-off-by: Author Name <author@example.com>
date: '2025-07-19T17:50:18Z'
repoURL: https://github.com/org/repo
sha: 9d5ccef278218dea4caa903bb6abb9ed974a1d90
subject: This change fixes a bug in the code
hydrated:
# The hydrated field contains the same fields as proposed.dry, but the contents correspond to a hydrated commit
# instead of a dry commit.
note:
# Dry SHA from the hydrator git note on the proposed hydrated commit (only drySha is populated today)
drySha: "abcdef1234567890abcdef1234567890abcdef12"
commitStatuses:
- key: example-key
phase: pending # pending, success, or failure
url: https://example.com/checks/example-key
description: Waiting for check
active:
# The active field contains the same fields as proposed.
pullRequest:
id: "848"
state: open # open, closed, or merged; empty when externallyMergedOrClosed is true
url: https://github.com/org/repo/pull/848
prCreationTime: 2023-10-01T00:00:00Z
history:
# The history field contains a snapshot of each promotion that has occurred in the environment. The most recent promotion
# is at the front of the list. The fields here are similar to those in proposed and active top level fields. They only differ in
# that the proposed field does not contain a dry field, because in the context of history proposed becomes active when promoted.
- active:
# same as top level active
proposed:
# same as top level proposed but without dry field because in the context of history proposed.dry becomes active when promoted.
pullRequest:
# The pullRequest field contains information about the pull request that was merged to create this history item.
id: '848'
prCreationTime: '2025-08-04T19:50:15Z'
url: https://github.com/org/repo/pull/848
lastHealthyDryShas:
- sha: "abcdef1234567890abcdef1234567890abcdef12"
time: 2023-10-01T00:00:00Z
- branch: environment/test
# same fields as dev
- branch: environment/prod
# same fields as dev
ChangeTransferPolicy
A ChangeTransferPolicy represents a pair hydrated environment branch pair: the proposed environment branch and the live environment branch. When a new commit appears in the proposed branch, the ChangeTransferPolicy will open a PR against the live branch. When all the configured checks pass, the ChangeTransferPolicy will merge the PR.
A PromotionStrategy will create a ChangeTransferPolicy for each configured environment. For each environment besides the
first one, the PromotionStrategy controller will inject a proposedCommitStatus to represent the active status of the
previous environment. This is how the PromotionStrategy ensures that the environment PRs are merged in order, respecting
the previous environments' active commit statuses.
The Events page documents the Kubernetes events produced by ChangeTransferPolicies. PromotionStrategy and ChangeTransferPolicy controllers set standard labels on related resources; see Labels.
apiVersion: promoter.argoproj.io/v1alpha1
kind: ChangeTransferPolicy
metadata:
name: environment
spec:
gitRepositoryRef:
name: example-git-repository
proposedBranch: environment/dev-next
activeBranch: environment/dev
# Defaults to true when omitted
autoMerge: true
activeCommitStatuses:
- key: argocd-app-health
proposedCommitStatuses:
- key: security-scan
- key: promoter-previous-environment
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
# The Ready condition indicates that the resource has been successfully reconciled, when there is an error during
# reconciliation, the condition will be False with a reason of ReconciliationError. When we successfully reconcile the resource,
# the condition will be True with a reason of ReconciliationSuccess. The Ready condition is essentially a way to show reconciliation
# errors to the user. This condition exists on all resources that have reconciliation logic.
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess ReconciliationError, or PullRequestNotReady
status: "True" # "True," "False," or "Unknown"
observedGeneration: 123
proposed:
dry:
author: "Author Name <author@example.com>"
body: "Body of the commit message (i.e. excluding the subject line)"
commitTime: 2023-10-01T00:00:00Z
repoURL: "https://git.example.com/org/repo.git"
sha: "abcdef1234567890abcdef1234567890abcdef12"
subject: "chore: Example commit subject line"
hydrated:
# The hydrated field contains the same fields as proposed.dry.
note:
# Only drySha is populated from the hydrator git note
drySha: "abcdef1234567890abcdef1234567890abcdef12"
commitStatuses:
- key: example-key
phase: pending # pending, success, or failure
url: https://example.com/checks/example-key
description: Waiting for check
active:
dry:
sha: "fedcba0987654321fedcba0987654321fedcba09"
commitTime: 2023-09-15T00:00:00Z
repoURL: "https://git.example.com/org/repo.git"
subject: "chore: Currently deployed commit"
hydrated:
# same structure as proposed.dry
commitStatuses:
- key: argocd-app-health
phase: success
pullRequest:
id: "848"
state: open # open, closed, or merged; empty when externallyMergedOrClosed is true
url: https://github.com/org/repo/pull/848
prCreationTime: 2023-10-01T00:00:00Z
# prMergeTime: set when merged
# externallyMergedOrClosed: true # PR closed/merged outside the controller
history:
# Recent promotions merged by the controller (newest first, at most 5 entries).
- active:
dry:
sha: "fedcba0987654321fedcba0987654321fedcba09"
proposed:
hydrated:
sha: "abcdef1234567890abcdef1234567890abcdef12"
commitStatuses:
- key: example-key
phase: success
pullRequest:
id: "847"
state: merged
prCreationTime: 2023-09-01T00:00:00Z
prMergeTime: 2023-09-01T01:00:00Z
url: https://github.com/org/repo/pull/847
PullRequest
A PullRequest is a thin wrapper around the SCM's pull request API. ChangeTransferPolicies use PullRequests to manage promotions. PullRequests carry promotion-strategy, change-transfer-policy, and environment labels for correlation; see Labels.
apiVersion: promoter.argoproj.io/v1alpha1
kind: PullRequest
metadata:
name: example-proposed-commit
spec:
gitRepositoryRef:
name: example-git-repository
targetBranch:
sourceBranch:
title:
description:
commit:
# The commit message that will be written for the commit that's made when merging the PR
message: "example message"
# The commit SHA that must be at the head of the source branch for the merge to succeed.
# This prevents race conditions where a different commit gets merged than intended.
mergeSha: abc123def456789012345678901234567890abcd
# Must be closed, merged, or open. Default is open.
# Must be set to "open" when initially created, and cannot be set to "closed" or "merged" unless status.id is set
# (which the controller should do automatically as long as there are no errors).
state:
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
# The Ready condition indicates that the resource has been successfully reconciled, when there is an error during
# reconciliation, the condition will be False with a reason of ReconciliationError. When we successfully reconcile the resource,
# the condition will be True with a reason of ReconciliationSuccess. The Ready condition is essentially a way to show reconciliation
# errors to the user. This condition exists on all resources that have reconciliation logic.
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess or ReconciliationError
status: "True" # "True," "False," or "Unknown"
observedGeneration: 123
id: "848"
state: open # open, closed, or merged; empty when externallyMergedOrClosed is true
url: https://github.com/org/repo/pull/848
prCreationTime: 2023-10-01T00:00:00Z
# externallyMergedOrClosed: true
CommitStatus
A CommitStatus is a thin wrapper for the SCM's commit status API. CommitStatuses are the primary source of truth for
promotion gates. In the ideal case, the CommitStatus will write its state to the SCM's API so that the appropriate
checkmarks/failures appear in the SCM's UI. But even if the SCM API calls fail, the ChangeTransferPolicy controller will
use the contents of the CommitStatuses spec fields.
Controllers label CommitStatuses with three standard labels (gate key, environment branch, and parent gate). See Labels for label keys, derived parent-gate labels, and troubleshooting queries.
apiVersion: promoter.argoproj.io/v1alpha1
kind: CommitStatus
metadata:
name: example-commit-status
labels:
# This label is used to define the "key" of the commit status. The value is referenced via the `key` field in a
# PromotionStrategy's (or ChangeTransferPolicy's) `activeCommitStatuses` or `proposedCommitStatuses` field.
#
# CommitStatuses should be unique per key/sha combination. For example, there may be one CommitStatus with the key
# `example` for shas abc123 and def456, but there should not be two CommitStatuses with the key `example` for sha
# abc123.
promoter.argoproj.io/commit-status: example
spec:
gitRepositoryRef:
name: example-git-repo
sha: abcdef1234567890abcdef1234567890abcdef12
# SCM status name; PromotionStrategy/ChangeTransferPolicy selectors reference this via key
name: argocd-app-health
description: Argo CD application `example-app` is healthy
# Can be pending, success, failure. Default is pending.
phase: success
# Optional URL to link to more information about the commit status.
url: https://argocd.example.com/applications/example-app
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
# The Ready condition indicates that the resource has been successfully reconciled, when there is an error during
# reconciliation, the condition will be False with a reason of ReconciliationError. When we successfully reconcile the resource,
# the condition will be True with a reason of ReconciliationSuccess. The Ready condition is essentially a way to show reconciliation
# errors to the user. This condition exists on all resources that have reconciliation logic.
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess or ReconciliationError
status: "True" # "True," "False," or "Unknown"
observedGeneration: 123
id: example-commit-status-id
phase: success # pending, success, or failure
sha: abcdef1234567890abcdef1234567890abcdef12
GitRepository
A GitRepository represents a single git repository. It references an ScmProvider to enable access via some configured auth mechanism.
apiVersion: promoter.argoproj.io/v1alpha1
kind: GitRepository
metadata:
name: example-git-repository
spec:
# Only one of the git providers should be specified.
github:
name:
owner:
gitlab:
name:
namespace:
projectId:
forgejo:
name:
owner:
gitea:
name:
owner:
bitbucketCloud:
owner:
name:
azureDevOps:
name:
project:
# fake:
# name: example
# owner: example
scmProviderRef:
kind: ScmProvider # or ClusterScmProvider
name: example-scm-provider
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess or ReconciliationError
status: "True"
observedGeneration: 123
ScmProvider
An ScmProvider represents a scm instance (such as github). It references a Secret to enable access via some configured auth mechanism.
apiVersion: promoter.argoproj.io/v1alpha1
kind: ScmProvider
metadata:
name: example-scm-provider
spec:
secretRef:
name: example-scm-provider-secret
# You must specify exactly one provider. Multiple are shown here as examples.
# If you do not need to specify any sub-fields, just set the field to {}.
github:
domain: github.example.com # Optional, leave empty for default github.com
appID: 1234
installationID: 1234 # Optional, will query ListInstallations if not provided
gitlab:
domain: gitlab.com # Optional
forgejo:
domain: forgejo.example.com
gitea:
domain: gitea.example.com
bitbucketCloud: {}
azureDevOps:
organization: example-organization
domain: dev.azure.com # Optional
# fake: {} # testing only
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess or ReconciliationError
status: "True"
observedGeneration: 123
ClusterScmProvider
A ClusterScmProvider represents a SCM instance (such as GitHub). ClusterScmProvider is the cluster-scoped alternative to the ScmProvider. It references a Secret in the same namespace where the promoter is running to enable access via some configured auth mechanism. A ClusterScmProvider can be referenced by any GitRepository in the cluster, regardless of namespace.
apiVersion: promoter.argoproj.io/v1alpha1
kind: ClusterScmProvider
metadata:
name: example-cluster-scm-provider
spec:
secretRef:
# Secret must be in the same namespace where the promoter is running
name: example-cluster-scm-provider-secret
# You must specify either github, gitlab, forgejo, or bitbucketCloud. Multiple are provided here as examples.
# If you do not need to specify any sub-fields, just set the field to {}.
github:
domain: github.example.com # Optional, leave empty for default github.com
appID: 1234
installationID: 1234 # Optional, will query ListInstallations if not provided
gitlab:
domain: gitlab.com # Optional
forgejo:
domain: forgejo.example.com
bitbucketCloud: {}
azureDevOps:
organization: example-org
domain: dev.azure.com # Optional
gitea:
domain: gitea.example.com
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess or ReconciliationError
status: "True"
observedGeneration: 123
ArgoCDCommitStatus
An ArgoCDCommitStatus is used as a way to aggregate all the Argo CD Applications that are being used in the promotion strategy. It is used to check the status of the Argo CD Applications that are being used in the promotion strategy.
apiVersion: promoter.argoproj.io/v1alpha1
kind: ArgoCDCommitStatus
metadata:
name: argocdcommitstatus-sample
spec:
# Gate key for PromotionStrategy; default argocd-health
key: argocd-health
applicationSelector:
matchLabels:
app: demo
promotionStrategyRef:
name: argocon-demo
url:
template: |
{{- $baseURL := "https://dev.argocd.local" -}}
{{- if eq .Environment "environment/development" -}}
{{- $baseURL = "https://dev.argocd.local" -}}
{{- else if eq .Environment "environment/staging" -}}
{{- $baseURL = "https://staging.argocd.local" -}}
{{- else if eq .Environment "environment/production" -}}
{{- $baseURL = "https://prod.argocd.local" -}}
{{- end -}}
{{- $labels := "" -}}
{{- range $key, $value := .ArgoCDCommitStatus.Spec.ApplicationSelector.MatchLabels -}}
{{- $labels = printf "%s%s=%s," $labels $key $value -}}
{{- end -}}
{{- printf "%s/applications?labels=%s" $baseURL (urlQueryEscape $labels) -}}
options:
- missingkey=zero
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
# The Ready condition indicates that the resource has been successfully reconciled, when there is an error during
# reconciliation, the condition will be False with a reason of ReconciliationError. When we successfully reconcile the resource,
# the condition will be True with a reason of ReconciliationSuccess. The Ready condition is essentially a way to show reconciliation
# errors to the user. This condition exists on all resources that have reconciliation logic.
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess, ReconciliationError, or CommitStatusesNotReady
status: "True" # "True," "False," or "Unknown"
observedGeneration: 123
# applicationsSelected is a list of applications that match the applicationSelector.
applicationsSelected:
- environment: environments/dev
name: example-app-name
namespace: example-app-namespace
phase: success # pending, success, or failure
sha: abcdef1234567890abcdef1234567890abcdef12
lastTransitionTime: 2023-10-01T00:00:00Z
clusterName: "" # an empty cluster name means "local cluster"
- environment: environments/prod
name: example-app-name
namespace: example-app-namespace
phase: success # pending, success, or failure
sha: abcdef1234567890abcdef1234567890abcdef12
lastTransitionTime: 2023-10-01T00:00:00Z
clusterName: "prod"
TimedCommitStatus
A TimedCommitStatus provides time-based gating for environment promotions. It monitors how long commits have been running in specified environments and creates CommitStatus resources (as active commit statuses) based on configured duration requirements.
This enables "soak time" or "bake time" policies where changes must run successfully in environments for a minimum duration before being promoted.
apiVersion: promoter.argoproj.io/v1alpha1
kind: TimedCommitStatus
metadata:
name: webservice-tier-1
namespace: default
spec:
# Gate key for PromotionStrategy; default timer
key: timer
# Reference to the PromotionStrategy this TimedCommitStatus monitors
promotionStrategyRef:
name: webservice-tier-1
# List of environments to monitor for time-based gating
# For each environment, the controller will:
# 1. Check how long the active commit has been running
# 2. Create a CommitStatus for the CURRENT environment's active SHA
# 3. Set the CommitStatus phase based on whether duration is met
environments:
# Monitor development environment
# Creates an active CommitStatus that gates promotions from development
- branch: environment/development
duration: 1h
# Monitor staging environment
# Creates an active CommitStatus that gates promotions from staging
- branch: environment/staging
duration: 4h
# Monitor production environment
# Creates an active CommitStatus for production (useful for audit/compliance)
- branch: environment/production
duration: 24h
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess or ReconciliationError
status: "True"
observedGeneration: 123
# Status is populated by the controller
environments:
- branch: environment/development
# The active commit SHA being monitored
sha: abcdef1234567890abcdef1234567890abcdef12
# When this commit was deployed to the environment
commitTime: "2024-01-15T10:00:00Z"
# The configured required duration
requiredDuration: 1h
# Maximum time remaining until the gate is satisfied (0 when phase is success)
atMostDurationRemaining: 14m30s
# Current gate status: "pending" or "success"
phase: pending
- branch: environment/staging
sha: abcdef1234567890abcdef1234567890abcdef99
commitTime: "2024-01-14T06:00:00Z"
requiredDuration: 4h
atMostDurationRemaining: 0s
phase: success
GitCommitStatus
A GitCommitStatus evaluates commit data with a custom expression and creates CommitStatus resources for promotion gating. See Git Commit Status for configuration, expression variables, and examples.
apiVersion: promoter.argoproj.io/v1alpha1
kind: GitCommitStatus
metadata:
name: no-revert-in-active
spec:
promotionStrategyRef:
name: example-promotion-strategy
# Matched against proposedCommitStatuses / activeCommitStatuses key in PromotionStrategy
key: revert-check
description: Block promotions if active commit is a revert
# active (default) validates the deployed commit; proposed validates the incoming commit
target: active
expression: '!(Commit.Subject startsWith "Revert" || Commit.Body startsWith "Revert")'
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess # ReconciliationSuccess or ReconciliationError
status: "True"
observedGeneration: 123
environments:
- branch: environment/dev
proposedHydratedSha: abcdef1234567890abcdef1234567890abcdef12
activeHydratedSha: fedcba0987654321fedcba0987654321fedcba09
targetedSha: fedcba0987654321fedcba0987654321fedcba09
phase: success # pending, success, or failure
expressionResult: true
WebRequestCommitStatus
A WebRequestCommitStatus gates promotions on external HTTP/HTTPS API validation. It makes HTTP requests to configurable endpoints, evaluates a validation expression against the response, and creates or updates CommitStatus resources. It supports polling mode (fixed interval) or trigger mode (expression-based triggering). See the Web Request Commit Status documentation for full configuration, examples, and template variables.
apiVersion: promoter.argoproj.io/v1alpha1
kind: WebRequestCommitStatus
metadata:
name: external-approval
namespace: default
spec:
# Reference to the PromotionStrategy this WebRequestCommitStatus applies to
promotionStrategyRef:
name: my-app
# Unique key for this validation; matched against proposedCommitStatuses or activeCommitStatuses
key: external-approval
# Human-readable description shown in the SCM (Go templates supported). Optional.
descriptionTemplate: "Waiting for external approval"
# URL for the commit status target link in the SCM (Go templates supported). Optional.
urlTemplate: ""
# Which SHA to report on: "proposed" (default) or "active". Optional.
reportOn: proposed
# HTTP request configuration; URL, headers, and body support Go templates
httpRequest:
urlTemplate: "https://approvals.example.com/api/check/{{ range .PromotionStrategy.Status.Environments }}{{ if eq .Branch $.Branch }}{{ .Proposed.Hydrated.Sha }}{{ end }}{{ end }}"
methodTemplate: GET
# Optional: header name -> template value
headerTemplates: {}
# Optional: request body template
bodyTemplate: ""
timeout: 30s
# Optional: authentication (basic, bearer, oauth2, or tls with secretRef); omit if none
# When the HTTP response is considered successful (phase success)
success:
when:
expression: 'Response != nil ? (Response.StatusCode == 200 && Response.Body.approved == true) : Phase == "success"'
# output:
# expression: "" # optional; expr returning map, stored in status.successOutput (available as SuccessOutput)
# Exactly one of polling or trigger must be set.
mode:
# context: environments (default) — one HTTP request per environment; status.environments[] below.
# Use context: promotionstrategy for one shared request; then status.promotionStrategyContext is set instead.
context: environments
# Polling: single optional field. Interval (default 1m) controls how often the HTTP request runs.
polling:
interval: 2m
# Trigger: use this instead of polling for expression-based triggering.
# trigger:
# requeueDuration: 1m # optional, default 1m
# when:
# variables: # optional; expr returning a map, exposed as Variables to expression + output below
# expression: '{ "ready": true }'
# expression: "Variables.ready" # required; boolean expr deciding whether to fire the request
# output:
# expression: '{ tracked: Variables.ready }' # optional; map stored in status.triggerOutput
# response: # optional
# output:
# expression: "" # expr returning map, stored in status.responseOutput
status:
observedGeneration: 123 # compare with metadata.generation to detect stale status
conditions:
# The Ready condition indicates that the resource has been successfully reconciled.
- type: Ready
lastTransitionTime: 2023-10-01T00:00:00Z
message: Reconciliation succeeded
reason: ReconciliationSuccess
status: "True"
observedGeneration: 123
# Status per environment; populated by the controller
environments:
- branch: environment/development
reportedSha: abc123def456789012345678901234567890abcd
lastSuccessfulSha: abc123def456789012345678901234567890abcd
phase: success
lastRequestTime: "2024-01-15T10:30:00Z"
lastResponseStatusCode: 200
# triggerOutput (trigger mode): output from trigger.when.output.expression (available as TriggerOutput in expressions/templates)
# responseOutput (trigger + response.output): output from response.output.expression (available as ResponseOutput in expressions/templates)
# successOutput: output from success.when.output.expression (available as SuccessOutput in expressions/templates)
# --- promotionstrategy context (illustrative; use a separate WebRequestCommitStatus resource) ---
# spec:
# promotionStrategyRef: { name: my-app }
# key: pipeline-gate
# descriptionTemplate: "Gate: {{ .Phase }} (use .PromotionStrategy for branch-specific text)"
# reportOn: proposed
# httpRequest:
# urlTemplate: "https://deployments.example.com/apps/{{ .PromotionStrategy.Spec.RepositoryReference.Name }}/status"
# method: GET
# success:
# when:
# # Boolean (all envs same phase) or object { defaultPhase, environments } — see docs/commit-status-controllers/web-request.md
# expression: "Response.StatusCode == 200"
# mode:
# context: promotionstrategy
# polling:
# interval: 2m
# status:
# observedGeneration: 123
# conditions:
# - type: Ready
# reason: ReconciliationSuccess
# status: "True"
# observedGeneration: 123
# # status.environments is empty when context is promotionstrategy
# promotionStrategyContext:
# phasePerBranch:
# - branch: environment/dev
# phase: success
# - branch: environment/staging
# phase: pending
# lastSuccessfulShas:
# - branch: environment/dev
# lastSuccessfulSha: abcdef1234567890abcdef1234567890abcdef12
# lastRequestTime: "2024-01-15T10:30:00Z"
# lastResponseStatusCode: 200
# # triggerOutput / responseOutput / successOutput: JSON maps from expr output expressions
ControllerConfiguration
A ControllerConfiguration is used to configure the behavior of the promoter.
A global ControllerConfiguration is deployed alongside the controller and applies to all promotions.
All fields are required, but defaults are provided in the installation manifests.
# ControllerConfiguration defines the global settings for all promoter controllers.
# Each controller has its own configuration section with WorkQueue settings that control
# how frequently resources are reconciled, how many can run concurrently, and how
# rate limiting is applied to prevent overwhelming external systems.
apiVersion: promoter.argoproj.io/v1alpha1
kind: ControllerConfiguration
metadata:
name: example-controller-configuration
spec:
# PromotionStrategy controller manages promotion strategies and creates ChangeTransferPolicies
promotionStrategy:
workQueue:
# How often to automatically requeue resources for reconciliation
requeueDuration: "5m"
# Maximum number of concurrent reconcile operations for this controller
maxConcurrentReconciles: 3
# Rate limiter controls retry behavior for failed reconciliations
rateLimiter:
# Exponential backoff: start at 1s, max out at 1 minute
exponentialFailure:
baseDelay: "1s"
maxDelay: "1m"
# ChangeTransferPolicy controller handles the actual promotion logic and creates PRs
changeTransferPolicy:
workQueue:
requeueDuration: "5m"
maxConcurrentReconciles: 5
rateLimiter:
# MaxOf combiner: use the maximum delay from multiple rate limiters
# This combines per-item exponential backoff with a global rate limit
maxOf:
- exponentialFailure:
baseDelay: "1s"
maxDelay: "2m"
- bucket:
# Allow 10 operations per second with bursts up to 20
qps: 10
bucket: 20
# PullRequest controller manages pull request lifecycle
pullRequest:
# Template configuration for generating PR titles and descriptions.
# Template data has access to: .ChangeTransferPolicy, .PromotionStrategy
template:
title: "Promote {{ trunc 7 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }} to `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}`"
description: |
This PR is promoting the environment branch `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}`.
**Changes:**
- Current SHA: {{ .ChangeTransferPolicy.Status.Active.Dry.Sha }}
- Proposed SHA: {{ .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}
workQueue:
requeueDuration: "5m"
maxConcurrentReconciles: 3
rateLimiter:
# FastSlow: retry quickly for the first 3 attempts, then slow down
# Good for handling transient SCM API errors
fastSlow:
fastDelay: "500ms"
slowDelay: "30s"
maxFastAttempts: 3
# CommitStatus controller updates commit status based on policies
commitStatus:
workQueue:
requeueDuration: "2m"
maxConcurrentReconciles: 5
rateLimiter:
exponentialFailure:
baseDelay: "500ms"
maxDelay: "1m"
# ArgoCDCommitStatus controller syncs ArgoCD application status to commit statuses
argocdCommitStatus:
watchLocalApplications: true
workQueue:
requeueDuration: "5m"
maxConcurrentReconciles: 10
rateLimiter:
# Bucket rate limiter: control overall request rate to external APIs
bucket:
qps: 20
bucket: 50
# TimedCommitStatus controller enforces soak-time gates via CommitStatus resources
timedCommitStatus:
workQueue:
requeueDuration: "1m"
maxConcurrentReconciles: 5
rateLimiter:
exponentialFailure:
baseDelay: "500ms"
maxDelay: "1m"
# GitCommitStatus controller evaluates commit expressions and creates CommitStatus resources
gitCommitStatus:
workQueue:
requeueDuration: "2m"
maxConcurrentReconciles: 5
rateLimiter:
exponentialFailure:
baseDelay: "500ms"
maxDelay: "1m"
# WebRequestCommitStatus controller runs HTTP requests and reports commit status from response
webRequestCommitStatus:
workQueue:
requeueDuration: "2m"
maxConcurrentReconciles: 5
rateLimiter:
exponentialFailure:
baseDelay: "500ms"
maxDelay: "1m"
Status Conditions
Every CRD which is reconciled has a status.conditions field. Each CRD currently only populates a single Ready
condition. If the Ready condition is True, then it means that 1) reconciliation of the resource has completed
successfully, and 2) all child resources also had a Ready condition of True.
Observed generation
Reconciled CRDs (all resources in this document except ControllerConfiguration) set status.observedGeneration
to the metadata.generation that produced the current status. When it equals metadata.generation, status is current;
when it is lower, reconciliation has not caught up yet (or the last apply failed — see the Ready condition). Each
Ready condition also has its own observedGeneration for the generation that condition reflects; on a failed apply,
top-level status.observedGeneration may stay pinned to the last successful reconcile while the condition records the
attempted generation.
Condition Reasons
All CRDs may have the following condition reasons:
ReconciliationSuccessReconciliationError
ArgoCDCommitStatus
The ArgoCDCommitStatus CRD may also have the following condition reasons:
CommitStatusesNotReady
ChangeTransferPolicy
The ChangeTransferPolicy CRD may also have the following condition reasons:
PullRequestNotReady
PromotionStrategy
The PromotionStrategy CRD may also have the following condition reasons:
PreviousEnvironmentCommitStatusNotReadyChangeTransferPolicyNotReady
Finalizers
GitOps Promoter uses Kubernetes finalizers to ensure resources are deleted in the correct order, preventing orphaned resources and ensuring proper cleanup of external resources (like pull requests in the SCM).
All finalizers are managed automatically by the controllers. You do not need to set them manually via GitOps.
For a complete finalizer table (including ChangeTransferPolicy cross-resource finalizers), risks of manual removal, and how to report stuck deletes, see Finalizers. Contributors adding finalizers should read Using Finalizers.
PullRequest Finalizer
Finalizer: pullrequest.promoter.argoproj.io/finalizer
When a PullRequest is deleted, the finalizer ensures that the pull request is properly closed on the SCM before the Kubernetes resource is removed. This prevents orphaned pull requests in your SCM.
ChangeTransferPolicy-owned PullRequest finalizer: changetransferpolicy.promoter.argoproj.io/pullrequest-finalizer
Set on PullRequests managed by a ChangeTransferPolicy so the CTP controller can copy PR status before the PullRequest resource is removed.
ChangeTransferPolicy cleanup finalizer: changetransferpolicy.promoter.argoproj.io/finalizer
Set on ChangeTransferPolicy while owned PullRequests may still carry the pullrequest finalizer above; cleared after cleanup during deletion.
GitRepository Finalizer
Finalizer: gitrepository.promoter.argoproj.io/finalizer
The GitRepository finalizer prevents deletion of the GitRepository while any PullRequest resources still reference it. This ensures that PullRequests can authenticate to the SCM to close themselves properly before the GitRepository is removed.
ScmProvider and ClusterScmProvider Finalizers
ScmProvider Finalizer: scmprovider.promoter.argoproj.io/finalizer
ClusterScmProvider Finalizer: clusterscmprovider.promoter.argoproj.io/finalizer
These finalizers prevent deletion of the SCM provider while any GitRepository resources still reference it. Additionally, the ScmProvider and ClusterScmProvider controllers manage finalizers on the Secret resources they reference:
Secret Finalizer (ScmProvider): scmprovider.promoter.argoproj.io/secret-finalizer
Secret Finalizer (ClusterScmProvider): clusterscmprovider.promoter.argoproj.io/secret-finalizer
This ensures that the Secret containing authentication credentials is not deleted while it's still needed by the SCM provider.
Deletion Order
When you delete a PromotionStrategy and its associated resources, the finalizers ensure deletion happens in this order:
- PullRequest - Closes the PR on the SCM
- GitRepository - Can be deleted once all PullRequests referencing the GitRepository are gone
- ScmProvider/ClusterScmProvider - Can be deleted once all GitRepositories referencing the Provider are gone
- Secret - Can be deleted once all ScmProviders/ClusterScmProviders referencing the Secret are gone
If you attempt to delete resources out of order, Kubernetes will mark them for deletion but they will remain in a "Terminating" state until their dependent resources are removed. This is normal and expected behavior.
Labels
Built-in controllers set promoter labels on ChangeTransferPolicy, PullRequest, and CommitStatus objects so promotion checks and cleanup can list related resources. Each gate-created CommitStatus gets three standard labels: promoter.argoproj.io/commit-status, promoter.argoproj.io/environment, and a derived parent-gate key (for example promoter.argoproj.io/argo-cd-commit-status).
Label keys are defined in api/v1alpha1/constants.go; parent-gate label keys are derived from the gate Kind. Branch and name values are sanitized with KubeSafeLabel before they are stored.
See Labels for the full reference, useful kubectl queries, and troubleshooting when gating does not match expectations. For how commit-status labels relate to PromotionStrategy selectors, see Gating Promotions.
Validation Conventions
Commit SHA Format
All SHA fields in the CRDs support both SHA-1 (40 characters) and SHA-256 (64 characters) lowercase hexadecimal hashes. This ensures compatibility with both traditional and modern Git repositories while maintaining reliable comparisons between SHAs.
Background on Git Hash Functions
Git is transitioning from SHA-1 to SHA-256 as documented in the Git hash function transition plan. While most Git repositories currently use SHA-1 (40-character hashes), newer repositories can be created with SHA-256 (64-character hashes). This project supports both formats to ensure compatibility during and after the transition period.
The validation rules differ based on whether the field is required or optional:
Required SHA fields (in spec):
# +kubebuilder:validation:MinLength=40
# +kubebuilder:validation:MaxLength=64
# +kubebuilder:validation:Pattern=`^([a-f0-9]{40}|[a-f0-9]{64})$`
Optional SHA fields (omitempty):
# +kubebuilder:validation:MaxLength=64
# +kubebuilder:validation:Pattern=`^([a-f0-9]{40}|[a-f0-9]{64})$`
Rationale:
- MinLength=40: Makes the field truly required (enforces non-empty for required fields)
- MaxLength=64: Accommodates both SHA-1 (40 chars) and SHA-256 (64 chars)
- Pattern=^([a-f0-9]{40}|[a-f0-9]{64})$: Validates exact format - either 40 or 64 lowercase hex characters
Optional fields omit MinLength so they can be empty, but when provided, they must match one of the two valid SHA formats (40 or 64 characters).