Skip to main content

Expression Language Reference

The steps of a user-defined promotion process as well as a Stage's verification arguments may take advantage of expressions in their configuration.

info

The documentation on this page assumes a general familiarity with the concepts of Promotion Templates and Analysis Templates, as well as some knowledge of how a promotion process is defined as a sequence of discrete steps and how verification is defined in a Stage.

For more information on Promotion Templates, refer to the Promotion Templates Reference.

For detailed coverage of individual promotion steps, refer to the Promotion Steps Reference.

For information on Analysis Templates, refer to the Verification Guide and Analysis Templates Reference.

Syntax

All steps in a user-defined promotion processes (i.e. those described by a Promotion Template and PromotionTasks) support the use of an Expression Language as a means of dynamically resolving values in their configuration at promotion time.

In addition, Stage verification arguments may also use expressions to inject dynamic values into the AnalysisRun that is created from an AnalysisTemplate.

All expressions must be enclosed within the ${{ and }} delimiters. This is not universally true for all applications of expr-lang. Kargo selected these specific delimiters to mimic GitHub Actions expression syntax, which many users will already be familiar with.

Basic example:

config:
message: ${{ "Hello, world!" }}

The above example will be evaluated as the following:

config:
message: Hello, world!

The expr-lang language definition docs provide a comprehensive overview of the language's syntax and capabilities, so this reference will continue to focus only on Kargo-specific extensions and usage.

tip

You can test your expressions using the expr-lang playground.

The playground allows you to evaluate expressions against sample data and see the results in real-time. This is especially useful for debugging and validating your expressions before using them in your Kargo configuration.

Structure and Behavior

Config Blocks

In promotion steps, expressions appear within configuration blocks that can have nested values. Kargo will evaluate expressions just-in-time as each step of a promotion process is executed. It will only evaluate expressions within values of a configuration block and will not evaluate expressions within keys. Expressions in values are evaluated recursively, so expressions may be nested any number of levels deep within a configuration block.

config:
nested:
value: ${{ foo.bar }}
other: ${{ baz.qux }}

Variables

In promotion step and verification arguments, expressions appear in a flat list of argument name-value pairs. Each argument has a name and a single value that can contain an expression. Unlike configuration blocks, these arguments do not support nested values.

Validation

Kargo parses configuration blocks before evaluating expressions, so any configuration containing expressions must be well-formed YAML even prior to evaluation. Further validation (e.g. for adherence to a step-specific schema) is performed only after expressions are evaluated.

Types

Due to the requirement that configuration blocks be well-formed YAML, all fields containing expressions must be strings. Internally, all expressions will also evaluate to strings, however, Kargo will attempt to coerce the results to other valid JSON types (YAML is a superset of JSON) including object, array, number, boolean, and null before concluding that the evaluated expression should continue to be treated as a string.

This behavior should be unsurprising and perhaps even familiar to experienced YAML users, as YAML parsers behave in the same way. 42, for example, is interpreted as a JSON number unless it is explicitly quoted (i.e. "42") to specify that it should be interpreted as a string.

In practice, this means care should be taken to use Kargo's built-in quote() function in cases where an evaluated expression may appear to be a number or boolean, for instance, but should be treated as a string.

For example:

config:
numField: ${{ 40 + 2 }} # Will be treated as a number
strField: ${{ quote(40 + 2) }} # Will be treated as a string

The above example will be evaluated to the following:

config:
numField: 42
strField: "42"

Pre-Defined Variables

Kargo provides a number of pre-defined variables that are accessible within expressions. This section enumerates these variables, their structure, and use.

info

Expect other useful variables to be added in the future.

Promotion Variables

NameTypeDescription
ctxobjectContains contextual information about the promotion. See detailed structure below.
outputsobjectA map of output from previous promotion steps indexed by step aliases.
varsobjectA user-defined map of variable names to static values of any type. The map is derived from both the Stage's spec.vars field and the Promotion's spec.promotionTemplate.spec.vars field, with promotion template variables taking precedence over Stage-level variables for any conflicting names. Variable names must observe standard Go variable-naming rules. Variables values may, themselves, be defined using an expression. vars (contains previously defined variables) and ctx are available to expressions defining the values of variables, however, outputs and secrets are not.
taskobjectA map containing output from previous steps within the same PromotionTask under the outputs field, indexed by step aliases. Only available within (Cluster)PromotionTask steps.

Context (ctx) Object Structure

The ctx object has the following structure:

ctx
├── project: string # The name of the Project
├── stage: string # The name of the Stage
├── promotion: string # The name of the Promotion
├── targetFreight
│ ├── name: string # The name of the Freight that is initiated this Promotion
│ └── origin
│ └── name: string # The name of the Warehouse that contains the Freight
└── meta
└── promotion
└── actor: string # The creator of the Promotion

The following example promotion process clones a repository and checks out two branches to different directories, uses Kustomize with source from one branch to render some Kubernetes manifests that it commits to the other branch, and pushes back to the repository. These steps make extensive use of the pre-defined variables ctx, outputs, and vars.

promotionTemplate:
spec:
vars:
- name: gitRepo
value: https://github.com/example/repo.git
- name: srcPath
value: ./src
- name: outPath
value: ./out
- name: targetBranch
value: stage/${{ ctx.stage }}
steps:
- uses: git-clone
config:
repoURL: ${{ vars.gitRepo }}
checkout:
- fromFreight: true
path: ${{ vars.srcPath }}
- branch: ${{ vars.targetBranch }}
create: true
path: ${{ vars.outPath }}
- uses: git-clear
config:
path: ${{ vars.outPath }}
- uses: kustomize-set-image
as: update-image
config:
path: ${{ vars.srcPath }}/base
images:
- image: public.ecr.aws/nginx/nginx
- uses: kustomize-build
config:
path: ${{ vars.srcPath }}/stages/${{ ctx.stage }}
outPath: ${{ vars.outPath }}
- uses: git-commit
as: commit
config:
path: ${{ vars.outPath }}
message: ${{ outputs['update-image'].commitMessage }}
- uses: git-push
config:
path: ${{ vars.outPath }}
targetBranch: ${{ vars.targetBranch }}
- uses: argocd-update
config:
apps:
- name: example-${{ ctx.stage }}
sources:
- repoURL: ${{ vars.gitRepo }}
desiredRevision: ${{ outputs.commit.commit }}
info

Since the usage of expressions and pre-defined variables effectively parameterizes the promotion process, the same promotion process can be reused in other Projects or Stages with few, if any, modifications (other than the definition of the static variables).

Verification Variables

NameTypeDescription
ctxobjectContains contextual information about the stage. See structure below.
varsobjectA user-defined map of variable names to static values of any type. The map is derived from the Stage's spec.vars field. Variable names must observe standard Go variable-naming rules. Stage-level variables can be referenced in verification arguments to pass dynamic values to AnalysisRuns.
note

Verification processes evaluate a Stage's current state and, while frequently executed immediately following a promotion, are not intrinsically part of the promotion process itself. Therefore, promotion-level variables (such as those defined in spec.promotionTemplate.spec.vars) and promotion context (like outputs from promotion steps) are not accessible during verification. Only Stage-level variables and context are available.

Context (ctx) Object Structure for Verification

The ctx object for verification has the following structure:

ctx
├── project: string # The name of the Project
└── stage: string # The name of the Stage

Functions

Besides the built-in functions provided by expr-lang itself, Kargo provides a number of additional functions that can be used within expressions in promotion steps and verification arguments.

These functions allow you to access Kubernetes resources, manipulate strings, and retrieve information about the current promotion context, among other things. They are designed to be used in conjunction with the pre-defined variables to create dynamic and flexible promotion processes and verification steps.

quote(value)

The quote() function converts any value to its string representation. It has one required argument:

  • value (Required): A value of any type to be converted to a string.

This is useful for scenarios where an expression evaluates to a non-string JSON type, but you wish to treat it as a string regardless. For string inputs, it produces clean output without visible quotation marks.

Example:

config:
numField: ${{ 40 + 2 }} # Will be treated as a number (42)
strField: ${{ quote(40 + 2) }} # Will be treated as a string ("42")
rawField: ${{ quote("string") }} # Will result in "string"

unsafeQuote(value)

The unsafeQuote() function converts any value to its string representation. It has one required argument:

  • value (Required): A value of any type to be converted to a string.

Compared to quote() this function is considered "unsafe" because it adds escaped quotes around input values that are already considered strings. For non-string values, it behaves similarly to quote().

Example:

config:
numField: ${{ 40 + 2 }} # Will be treated as a number (42)
strField: ${{ unsafeQuote(40 + 2) }} # Will result in "42"
rawField: ${{ unsafeQuote("string") }} # Will result in "\"string\""

configMap(name)

The configMap() function returns the Data field (a map[string]string) of a Kubernetes ConfigMap with the specified name from the Project's namespace. If no such ConfigMap exists, an empty map is returned.

Example:

config:
repoURL: ${{ configMap('my-config').repoURL }}

secret(name)

The secret() function returns the Data field of a Kubernetes Secret with the specified name from the Project's namespace decoded into a map[string]string. If no such Secret exists, an empty map is returned.

Examples:

config:
headers:
- name: Authorization
value: Bearer ${{ secret('slack').token }}

warehouse(name)

The warehouse() function returns a FreightOrigin object representing a Warehouse. It has one required argument:

  • name (Required): A string representing the name of a Warehouse resource in the same Project as the Promotion being executed.

The returned FreightOrigin object has the following fields:

FieldDescription
KindThe kind of the FreightOrigin. Always equals Warehouse for this function.
NameThe name of the Warehouse resource.

The FreightOrigin object can be used as an optional argument to the commitFrom(), imageFrom(), or chartFrom() functions to disambiguate the desired source of an artifact when necessary. These functions return nil when relevant Freight is not found from the FreightCollection.

tip

You can handle nil values gracefully in Expr using its nil coalescing and optional chaining features.

commitFrom(repoURL, [freightOrigin])

The commitFrom() function returns a corresponding GitCommit object from the Promotion or Stage their FreightCollection. It has one required and one optional argument:

  • repoURL (Required): The URL of a Git repository.
  • freightOrigin (Optional): A FreightOrigin object (obtained from warehouse()) to specify which Warehouse should provide the commit information.

The returned GitCommit object has the following fields:

FieldDescription
RepoURLThe URL of the Git repository the commit originates from.
IDThe ID of the Git commit.
BranchThe branch of the repository where this commit was found. Only present if the Warehouse's Git subscription is configured to track branches.
TagThe tag of the repository where this commit was found. Only present if the Warehouse's Git subscription is configured to track tags.
MessageThe first line of the commit message (up to 80 characters).
AuthorThe name and email address of the commit author.
CommitterThe name and email address of the committer.

The optional freightOrigin argument should be used when a Stage requests Freight from multiple origins (Warehouses) and more than one can provide a GitCommit object from the specified repository.

If a commit is not found from the FreightCollection, returns nil.

Examples:

config:
commitID: ${{ commitFrom("https://github.com/example/repo.git").ID }}
config:
commitID: ${{ commitFrom("https://github.com/example/repo.git", warehouse("my-warehouse")).ID }}

imageFrom(repoURL, [freightOrigin])

The imageFrom() function returns a corresponding Image object from the Promotion or Stage their FreightCollection. It has one required and one optional argument:

  • repoURL (Required): The URL of a container image repository.
  • freightOrigin (Optional): A FreightOrigin object (obtained from warehouse()) to specify which Warehouse should provide the image information.

The returned Image object has the following fields:

FieldDescription
RepoURLThe URL of the container image repository the image originates from.
TagThe tag of the image.
DigestThe digest of the image.
AnnotationsA map of annotations discovered for the image.

The optional freightOrigin argument should be used when a Stage requests Freight from multiple origins (Warehouses) and more than one can provide a Image object from the specified repository.

If an image is not found from the FreightCollection, returns nil.

Examples:

config:
imageTag: ${{ imageFrom("public.ecr.aws/nginx/nginx").Tag }}
config:
imageTag: ${{ imageFrom("public.ecr.aws/nginx/nginx", warehouse("my-warehouse")).Tag }}

chartFrom(repoURL, [chartName], [freightOrigin])

The chartFrom() function returns a corresponding Chart object from the Promotion or Stage their FreightCollection. It has one required and two optional arguments:

  • repoURL (Required): The URL of a Helm chart repository.
  • chartName (Optional): The name of the chart (required for HTTP/S repositories, not needed for OCI registries).
  • freightOrigin (Optional): A FreightOrigin object (obtained from warehouse()) to specify which Warehouse should provide the chart information.

The returned Chart object has the following fields:

FieldDescription
RepoURLThe URL of the Helm chart repository the chart originates from. For HTTP/S repositories, this is the URL of the repository. For OCI repositories, this is the URL of the container image repository including the chart's name.
NameThe name of the Helm chart. Only present for HTTP/S repositories.
VersionThe version of the Helm chart.

For Helm charts stored in OCI registries, the URL should be the full path to the repository within that registry.

For Helm charts stored in classic (HTTP/S) repositories, which can store multiple different charts within a single repository, the chartName argument must be provided to specify the name of the chart within the repository.

The optional freightOrigin argument should be used when a Stage requests Freight from multiple origins (Warehouses) and more than one can provide a Chart object from the specified repository.

If a chart is not found from the FreightCollection, returns nil.

Examples:

# OCI registry
config:
chartVersion: ${{ chartFrom("oci://example.com/my-chart").Version }}
# OCI registry with specific warehouse
config:
chartVersion: ${{ chartFrom("oci://example.com/my-chart", warehouse("my-warehouse")).Version }}
# HTTP/S repository
config:
chartVersion: ${{ chartFrom("https://example.com/charts", "my-chart").Version }}
# HTTP/S repository with specific warehouse
config:
chartVersion: ${{ chartFrom("https://example.com/charts", "my-chart", warehouse("my-warehouse")).Version }}

success()

The success() function checks the status of all preceding steps and returns true if none of them have failed or errored and false otherwise.

Examples:

config:
wasSuccessful: ${{ success() }}

failure()

The failure() function checks the status of all preceding steps and returns true if any of them have failed or errored and false otherwise.

Examples:

config:
wasFailure: ${{ failure() }}

always()

The always() function unconditionally returns true.

Examples:

config:
alwaysTrue: ${{ always() }}

status(stepAlias)

The status(stepAlias) function retrieves the status of a specific step by its alias within the current promotion context; returning its value as a string.

Examples:

config:
status: ${{ status("my-step-alias") }}

semverDiff(version1, version2)

The semverDiff() function compares two semantic version strings and returns a string indicating the magnitude of difference between them. Possible return values include, and are limited to:

Return ValueDescription
MajorThe major version components differ.
MinorThe minor version components differ (major versions are the same).
PatchThe patch version components differ (major and minor versions are the same).
MetadataOnly the build metadata differs (major, minor, and patch versions are the same).
NoneThe versions are identical.
IncomparableOne or both arguments are not valid semantic versions.
info

The function uses the Semantic Versioning specification to parse and compare versions. It supports versions with or without the v prefix, as well as prerelease and build metadata components.

Example:

# Open a pull request for major version changes of an image; push directly
# otherwise...

# Presume steps not shown have read and updated the version number of the image
# referenced by some manifest.

- uses: git-push
as: direct-push
if: ${{ semverDiff(imageFrom(vars.imageRepo).Tag, outputs['read-version'].currentVersion) != 'Major' }}
config:
repoURL: ${{ vars.gitRepo }}
targetBranch: stage/${{ ctx.stage }}
message: ${{ semverDiff(imageFrom(vars.imageRepo).Tag, outputs['read-version'].currentVersion) }} update of ${{ vars.imageRepo }}

- uses: git-push
as: indirect-push
if: ${{ semverDiff(imageFrom(vars.imageRepo).Tag, outputs['read-version'].currentVersion) == 'Major' }}
config:
repoURL: ${{ vars.gitRepo }}
generateTargetBranch: true
message: Major update of ${{ vars.imageRepo }}

- uses: git-open-pr
if: ${{ semverDiff(imageFrom(vars.imageRepo).Tag, outputs['read-version'].currentVersion) == 'Major' }}
config:
repoURL: https://github.com/example/config-repo.git
sourceBranch: ${{ outputs['indirect-push'].branch }}
targetBranch: stage/${{ ctx.stage }}
title: Major update of ${{ vars.imageRepo }}
labels:
- breaking-change
- needs-review