From b07cf00de27c00848f926643a058b1e4a938a9d6 Mon Sep 17 00:00:00 2001 From: Sam Irons Date: Thu, 6 Aug 2026 10:14:19 +1000 Subject: [PATCH 1/3] Initial rewrite of variable substitution concept page and updates to inbound links --- .../deployment-and-runbook-processes.md | 2 +- .../deployments/notifications.md | 2 +- .../best-practices/deployments/variables.md | 2 +- .../terraform-output-variables/index.md | 2 +- .../email-notifications.md | 4 +- .../projects/variables/sensitive-variables.md | 2 +- .../projects/variables/system-variables.md | 4 +- .../variables/variable-substitutions.mdx | 493 ++++++------------ src/pages/docs/releases/deployment-changes.md | 2 +- 9 files changed, 177 insertions(+), 336 deletions(-) diff --git a/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md b/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md index 03babdc4d1..67d3f7460d 100644 --- a/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md +++ b/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md @@ -95,7 +95,7 @@ The deployment and runbook processes should be the same for all environments, ex When defining your runbook or deployment process, you can configure a run condition based on a variable. Generally, that feature is used to skip over a specific step when a particular condition occurs. For example, skip over a testing step if only .css or image files were deployed. -Octopus Deploy provides an [extended variable syntax](/docs/projects/variables/variable-substitutions/#extended-syntax) that includes support for if/elseif/else/then statements. While it is possible to have a complex if/then statement in run condition, we don't recommend it. Instead, we recommend you run a script and set an [output variable](/docs/projects/variables/output-variables). Use that output variable in the run condition. Having the decision made in a script means you can easily test and debug it. +Octopus Deploy provides [conditional variable syntax](/docs/projects/variables/variable-substitutions/#conditionals) that includes support for if/elseif/else/then statements. While it is possible to have a complex if/then statement in run condition, we don't recommend it. Instead, we recommend you run a script and set an [output variable](/docs/projects/variables/output-variables). Use that output variable in the run condition. Having the decision made in a script means you can easily test and debug it. ## Further reading diff --git a/src/pages/docs/best-practices/deployments/notifications.md b/src/pages/docs/best-practices/deployments/notifications.md index 556b4599c9..2024c9d9f8 100644 --- a/src/pages/docs/best-practices/deployments/notifications.md +++ b/src/pages/docs/best-practices/deployments/notifications.md @@ -53,7 +53,7 @@ Regardless of the notification technology (email, Slack, MS Teams, etc.), the re - Create a variable set called "Notifications" to house common variables used in those notifications. Some variables can include pending notification message, email subject, and deployment or runbook run result message. - Send notifications when pausing for approval or manual intervention to let the approver know a deployment is waiting for them. Example variable value: `#{Octopus.Project.Name} #{Octopus.Release.Number} to #{Octopus.Environment.Name} is awaiting approval.` -- Always send notifications at the end of each deployment informing interested parties of the deployment status. Use Octopus Deploy's built-in [extended variable syntax](/docs/projects/variables/variable-substitutions/#extended-syntax) to differentiate between successful and failed deployments. Example variable value: `#{Octopus.Project.Name} #{Octopus.Release.Number} to #{Octopus.Environment.Name} has #{if Octopus.Deployment.Error}failed#{else}completed successfully#{/if}` +- Always send notifications at the end of each deployment informing interested parties of the deployment status. Use Octopus Deploy's built-in [conditional variable syntax](/docs/projects/variables/variable-substitutions/#conditionals) to differentiate between successful and failed deployments. Example variable value: `#{Octopus.Project.Name} #{Octopus.Release.Number} to #{Octopus.Environment.Name} has #{if Octopus.Deployment.Error}failed#{else}completed successfully#{/if}` - Include a deep link in your message back to the deployment or runbook run. Example variable value `https://samples.octopus.app/app#/#{Octopus.Space.Id}/tasks/#{Octopus.Task.Id}` ## Further reading diff --git a/src/pages/docs/best-practices/deployments/variables.md b/src/pages/docs/best-practices/deployments/variables.md index f0f95990f3..2fd848ec77 100644 --- a/src/pages/docs/best-practices/deployments/variables.md +++ b/src/pages/docs/best-practices/deployments/variables.md @@ -90,7 +90,7 @@ Our recommendations for variable edit permissions are: - Variable edit permissions should be all or nothing, either a person can edit variables, or they cannot. Don't scope permissions to environments. Anyone responsible for the application should have permission to update variables (developers, lead developers, DB developers, etc.) along with operations (DBAs, web admins, sysadmins) who can create and update service accounts and passwords. - Variable Sets can be shared across multiple projects. Limit who can edit variable set variables to more experienced Octopus Deploy users, or people who understand "with great power comes great responsibility." Typically, we see senior or lead developers along with operations people who have these permissions. If you want to isolate an application, consider using [spaces](/docs/administration/spaces). - Leverage [sensitive variables](/docs/projects/variables/sensitive-variables) to encrypt and hide sensitive values such as usernames and passwords. Sensitive variables are write-only in the Octopus UI and Octopus API. -- Use [composite variables](/docs/projects/variables/variable-substitutions/#binding-variables) to combine sensitive and non-sensitive values. A typical use case is database connection strings. Each language has a specific syntax. In the screenshot below `Project.Database.ConnectionString` is the composite variable, with the username and password referenced by the composite variable, but they are separate sensitive variables. +- Use [composite variables](/docs/projects/variables/variable-substitutions/#composite-variables) to combine sensitive and non-sensitive values. A typical use case is database connection strings. Each language has a specific syntax. In the screenshot below `Project.Database.ConnectionString` is the composite variable, with the username and password referenced by the composite variable, but they are separate sensitive variables. :::figure ![composite variables](/docs/img/getting-started/best-practices/images/composite-variables.png) diff --git a/src/pages/docs/deployments/terraform/terraform-output-variables/index.md b/src/pages/docs/deployments/terraform/terraform-output-variables/index.md index 5588636379..1235c4e478 100644 --- a/src/pages/docs/deployments/terraform/terraform-output-variables/index.md +++ b/src/pages/docs/deployments/terraform/terraform-output-variables/index.md @@ -41,7 +41,7 @@ $value = $OctopusParameters["Octopus.Action[Apply Template].Output.TerraformValu $value = $OctopusParameters["Octopus.Action[Apply Template].Output.TerraformJsonOutputs[test]"] | ConvertFrom-Json | select -ExpandProperty value ``` -The syntax for accessing JSON variables as covered by our [documentation here](/docs/projects/variables/variable-substitutions/#json-parsing) applies to both `TerraformJsonOutputs` as well as `TerraformValueOutputs`. However, the latter is less useful as it can also be a primitive value. In this case Octostache won't know that it should deserialize the value and will provide you with a JSON encoded result. It is therefore recommended to prefer `TerraformJsonOutputs` where possible. The following syntax can be used to access the value using the binding syntax: +The syntax for accessing JSON variables as covered by our [documentation here](/docs/projects/variables/variable-substitutions/#json-variables) applies to both `TerraformJsonOutputs` as well as `TerraformValueOutputs`. However, the latter is less useful as it can also be a primitive value. In this case Octostache won't know that it should deserialize the value and will provide you with a JSON encoded result. It is therefore recommended to prefer `TerraformJsonOutputs` where possible. The following syntax can be used to access the value using the binding syntax: ``` #{Octopus.Action[Apply Template].Output.TerraformJsonOutputs[test].value} diff --git a/src/pages/docs/projects/built-in-step-templates/email-notifications.md b/src/pages/docs/projects/built-in-step-templates/email-notifications.md index 6f3d5f0e06..89ddb9a476 100644 --- a/src/pages/docs/projects/built-in-step-templates/email-notifications.md +++ b/src/pages/docs/projects/built-in-step-templates/email-notifications.md @@ -111,8 +111,8 @@ Email steps are added to deployment processes in the same way as other steps. Octopus will build the resulting recipient list during the deployment, remove duplicate email addresses, and send the email to each recipient. -6. Provide a subject line for the emails. The subject can contain Octopus [basic variable syntax](/docs/projects/variables/variable-substitutions/#basic-syntax-variablesubstitutionsyntax-basicsyntax). -7. Add the body of the email. The email can be sent in plain text or HTML, and you can use Octopus [extended variable syntax](/docs/projects/variables/variable-substitutions/#extended-syntax) to include information about the deployment in the email. See the [email template examples](#email-template-examples) below. +6. Provide a subject line for the emails. The subject can contain Octopus [variable substitution syntax](/docs/projects/variables/variable-substitutions/#variable-substitution). +7. Add the body of the email. The email can be sent in plain text or HTML, and you can use Octopus [variable substitution syntax](/docs/projects/variables/variable-substitutions) to include information about the deployment in the email. See the [email template examples](#email-template-examples) below. 8. You can set conditions to determine when the step should run. For instance: - Send the email only for successful deployments to certain environments. diff --git a/src/pages/docs/projects/variables/sensitive-variables.md b/src/pages/docs/projects/variables/sensitive-variables.md index 378bbfd552..1933889cb7 100644 --- a/src/pages/docs/projects/variables/sensitive-variables.md +++ b/src/pages/docs/projects/variables/sensitive-variables.md @@ -108,7 +108,7 @@ Here are some common pitfalls to avoid: - **Sensitivity is not transitive/infectious**: For example, imagine you have a sensitive variable called `DB.Password` and another variable called `DB.ConnectionString` with the value `Server=#{DB.Server};...;Password=#{DB.Password}`; the `DB.ConnectionString` does not become sensitive just because `DB.Password` is sensitive. However, if you happen to write the database connection string to the task log, the password component will be masked like this `Server=db01.my-company.com;...;Password=*****` which is probably the desired outcome. - **Avoid mixing binding expressions and sensitivity**: For example, if you have a variable called `Service.Credential` with the value `Password=#{Service.Password}` and make that variable sensitive, Octopus treats the **literal** value `Password=#{Service.Password}` as sensitive instead of treating the **evaluated** value as sensitive, which might be different to what you would expect. Instead, you should make the variable called `Service.Password` sensitive so the password itself will be encrypted in the database, and subsequently masked in any logs like this `Password=*****`. - **Avoid treating entire files as sensitive**: Imagine you're consuming a YAML file from a variable, and that YAML file contains a secret. Rather than treating the whole YAML file as sensitive, you should create two variables: one sensitive variable containing just the secret, and one non-sensitive variable for the YAML file which uses [variable substitution](/docs/projects/variables/variable-substitutions) to substitute in the sensitive variable. This gives Octopus a much tighter scope when looking for sensitive variables to mask. -- **Avoid sequences that are part of the variable substitution syntax**: For example, the sequence `##{` will be replaced by `#{` by logic that's part of [referencing variables](/docs/projects/variables/#use-variables-in-step-definitions) so you would need to escape it by modifying it to be `###{` which will result in `##{`, see also [variable substitution syntax](/docs/projects/variables/variable-substitutions). +- **Avoid sequences that are part of the variable substitution syntax**: For example, the sequence `##{` will be replaced by `#{` by logic that's part of [escaping variable syntax](/docs/projects/variables/variable-substitutions/#escaping-variable-syntax) so you would need to escape it by modifying it to be `###{` which will result in `##{`. - **Octopus is not a 2-way key vault**: use a [Secret Manager/Key Vault](#values-from-key-vaults) instead. ## Logging {#logging} diff --git a/src/pages/docs/projects/variables/system-variables.md b/src/pages/docs/projects/variables/system-variables.md index d2bfefef2f..18a3c6a82d 100644 --- a/src/pages/docs/projects/variables/system-variables.md +++ b/src/pages/docs/projects/variables/system-variables.md @@ -41,7 +41,7 @@ Release-level variables are drawn from the project and release being created, an ## Release package and build information variables -These variables expose the build information pushed from your build server for the packages in a release. They're populated only when build information has been pushed, and they're available only in project release notes, not in deployment steps. Each variable is a collection; see [Variable substitution syntax](/docs/projects/variables/variable-substitutions/) for how to iterate and index collections. +These variables expose the build information pushed from your build server for the packages in a release. They're populated only when build information has been pushed, and they're available only in project release notes, not in deployment steps. Each variable is a collection; see [Collection variables](/docs/projects/variables/variable-substitutions/#collection-variables) for how to iterate and index collections. | Variable | Description | Example | | --- | --- | --- | @@ -157,7 +157,7 @@ Deployment-level variables are drawn from the project and release being deployed ## Deployment change variables -These variables expose the changes included in a deployment, aggregated across the releases being deployed. They're available only where build information has been pushed and, for work items, an issue tracker integration is enabled. Several are JSON collections; see [Variable substitution syntax](/docs/projects/variables/variable-substitutions/) for how to iterate and index them. +These variables expose the changes included in a deployment, aggregated across the releases being deployed. They're available only where build information has been pushed and, for work items, an issue tracker integration is enabled. Several are JSON collections; see [JSON variables](/docs/projects/variables/variable-substitutions/#json-variables) for how to iterate and index them. | Variable | Description | Example | | --- | --- | --- | diff --git a/src/pages/docs/projects/variables/variable-substitutions.mdx b/src/pages/docs/projects/variables/variable-substitutions.mdx index 2864be5b38..e8b42c9d01 100644 --- a/src/pages/docs/projects/variables/variable-substitutions.mdx +++ b/src/pages/docs/projects/variables/variable-substitutions.mdx @@ -1,436 +1,277 @@ --- layout: src/layouts/Default.astro pubDate: 2023-01-01 -modDate: 2026-02-17 -title: Variable substitutions -icon: fa-solid fa-underline -description: Variable substitutions are a flexible way to adjust configuration based on your variables and the context of your deployment. +modDate: 2026-08-06 +title: Variable substitution syntax +sidebarLabel: Variable substitutions navOrder: 10 +description: How Octopus binds and evaluates variable references in scripts, files, and step settings, including collections, conditionals, and calculations. +subject: variable substitution, Octostache, variable syntax, collections, conditionals, calc, filters +type: concept +audience: [devops-eng, power-user] +image: +imageAlt: --- import VariableFiltersInOctostacheConditionals from 'src/shared-content/projects/deployment-process/variable-filters-in-octostache-conditionals.include.md'; -Variable substitutions are a flexible way to adjust configuration based on your [variables](/docs/projects/variables/) and the context of your [deployment](/docs/projects/deployment-process). You can often tame the number and complexity of your variables by breaking them down into simple variables and combining them together using expressions. +Octopus lets you reference variables in scripts, configuration files, and step settings, using a binding syntax powered by [Octostache](https://github.com/OctopusDeploy/Octostache), Octopus's open-source templating engine. -## Binding variables \{#binding-variables} +This page explains that syntax: how Octopus binds and evaluates a reference, how to compose a value from other variables, how to work with collections and conditionals, and how to reshape a value with a filter. -You can use Octopus's special binding syntax to reference a variable from within the value of another variable. This is sometimes referred to as using **Composite variables**, because you compose a variable value with other Octopus variables. In the following example, the `ConnectionString` variable references the variables `{Server}` and `{Database}`. +For the built-in variables you can reference, see [System variables](/docs/projects/variables/system-variables). -| Name | Value | Scope | -| ------------------ | ---------------------------------------------- | ----------------- | -| Server | SQL | Production, Test | -| Database | PDB001 | Production | -| Database | TDB001 | Test | -| ConnectionString | Server=#\{Server\}; Database=#\{Database\} | | +## Variable substitution -In regular variable declarations, binding to a non-existent value will yield an empty string, so evaluating `ConnectionString` in the *Dev* environment will yield `Server=;` because no `Database` or `Server` are defined for that environment. +Variable substitution is how Octopus replaces a reference in your text with the value of a variable at deployment or runbook run time. Understanding substitution lets you write one deployment process, script, or configuration file that adapts to each environment, target, or tenant instead of maintaining a copy for each. -If the file undergoing variable replacement includes a string that *shouldn't* be replaced, for example `#{NotToBeReplaced}`, you should include an extra hash (#) character to force the replacement to ignore the substitution and remove the extra #. +You reference a variable by wrapping its name in `#{` and `}`: -| Expression | Value | -| --------------------- | -------------------- | -| `##{NotToBeReplaced}` | `#{NotToBeReplaced}` | - -Variable substitution within a hash-delimited string looks like the following. Given the variable: - -| Name | Value | -| -------- | ------- | -| `Name` | `title` | - -`###{Name}` would evaluate to `#title`. - -:::div{.info} - -Also read about [common mistakes for variables](/docs/projects/variables/sensitive-variables/#avoiding-common-mistakes) for more information -::: - -## Using variables in step definitions \{#use-variables-in-step-definitions} - -Binding syntax can be used to dynamically change the values of deployment step settings. If [variables are scoped](/docs/projects/variables/getting-started/#scoping-variables), this makes it really easy to alter a deployment step settings based on the target environment. - -Most text fields that support binding to variables will have a variable insert button: - -:::figure -![Variable insert button on text fields that support variable binding](/docs/img/projects/variables/images/3278296.png) -::: - -For settings that support variables but aren't text (such as drop downs or check-boxes), a button is displayed to toggle custom expression modes: - -:::figure -![Toggling custom expression modes for settings that support variables but aren't text](/docs/img/projects/variables/images/3278297.png) -::: - -## Extended syntax \{#extended-syntax} - -Octopus supports an extended variable substitution syntax with capabilities similar to text templating languages. It's worth noting that this is now available everywhere whereas previously it was limited to certain scenarios. - -The capabilities of the extended syntax are: +``` +#{VariableName} +``` -- [Index Replacement](#index-replacement) -- [Calculation](#calculation) - `calc` -- [Conditionals](#conditionals) - `if`, `if-else` and `unless` -- [Repetition](#repetition) - `each` -- [Filters](#filters) - `HtmlEscape`, `Markdown` etc. -- [Differences from regular variable bindings](/docs/projects/variables/variable-filters/#differences-from-regular-bindings) -- [JSON Parsing](/docs/projects/variables/variable-filters/#json-parsing) +When Octopus runs the step, it evaluates the reference and replaces it with the variable's value. **Every value is a string**, even when it looks like a number or a boolean, because Octopus stores and substitutes all variables as text. -:::div{.hint} -[Octostache](https://github.com/OctopusDeploy/Octostache) is the open source component that powers this feature. -::: +Octopus resolves each reference using the most specifically [scoped](/docs/projects/variables/getting-started/#scoping-variables) value that applies to the current deployment, so the same reference can produce a different value in Development than it does in Production. -### Index replacement \{#index-replacement} +## Composite variables -Variable substitution inside an index makes it easy to dynamically retrieve variables within arrays/dictionaries. +A composite variable is a variable whose own value contains a reference to another variable, using the same `#{...}` binding syntax. Composing values this way lets you tame variable complexity by building a value out of simpler variables instead of maintaining a full string for every scope. Given the variables: -| Name | Value | Scope | -| ------------------- | ----------- | ----- | -| `MyPassword[Rob]` | `passwordX` | | -| `MyPassword[Steve]` | `passwordY` | | -| `MyPassword[Mary]` | `passwordZ` | | -| `UserName` | `Mary` | | - -`#{MyPassword[#{UserName}]}` would evaluate to `passwordZ`. - -### Calculations \{#calculation} +| Name | Value | Scope | +| --- | --- | --- | +| `Server` | `SQL` | Production, Test | +| `Database` | `PDB001` | Production | +| `Database` | `TDB001` | Test | +| `ConnectionString` | `Server=#{Server}; Database=#{Database}` | | -Basic mathematical calculations are supported in Octopus using the `calc` statement, and four main operators: +Evaluating `ConnectionString` in Production yields `Server=SQL; Database=PDB001`. Binding to a value that isn't defined for the current scope yields an empty string, so evaluating `ConnectionString` in a Dev environment (where neither `Server` nor `Database` is scoped) yields `Server=; Database=`. -- Addition - `+` -- Subtraction - `-` -- Multiplication - `*` -- Division - `/` - -:::div{.warning} -When using a variable on the left-hand-side of a divide (`/`) or subtraction (`-`) operation, -the variable name must be enclosed in braces (`{ ... }`) to ensure correct parsing, this ensures the operator symbol is recognized -as an operation, rather than part of the variable name. +:::div{.info} +Also read about [common mistakes for variables](/docs/projects/variables/sensitive-variables/#avoiding-common-mistakes) for more information. ::: -Given the variables: - -| Name | Value | Scope | -| --------------------- | -----------------| ----- | -| `IPOffset[Primary]` | `0` | | -| `IPOffset[Secondary]` | `180` | | -| `ScaleFactor` | `12` | | -| `Numbers` | `10,20,30,40,50` | | -| `My/Var` | `15` | | - -- `192.168.0.#{calc IPOffset[Primary] + 1}` would evaluate to `192.168.0.1` -- `192.168.0.#{calc IPOffset[Secondary] + 1}` would evaluate to `192.168.0.181` -- `#{calc 22 * ScaleFactor}` would evaluate to `264` -- `#{each i in Numbers}#{calc i + 5}#{/each}` would evaluate to `15 25 35 45 55` -- `#{calc {My/Var} / 3}` would evaluate to `5` -- `#{calc 2 * My/Var}` would evaluate to `30` -- `#{calc {My/Var} - 4}` would evaluate to `11` -- `#{calc 22 - My/Var}` would evaluate to `7` - -### Conditionals \{#conditionals} - -Two conditional statements are supported in Octopus - `if` and `unless`; these have identical syntax, but `if` evaluates only if the variable is *truthy*, while `unless` evaluates if the variable is *falsy*. The syntax for `if` and `unless` is as follows: +### Escaping variable syntax -`#{if VariableName}conditional statements#{/if}` +If the text undergoing substitution contains a literal sequence that looks like a reference but shouldn't be replaced, such as `#{NotToBeReplaced}`, add an extra `#` to escape it: -`#{unless VariableName}conditional statements#{/unless}` +| Expression | Value | +| --- | --- | +| `##{NotToBeReplaced}` | `#{NotToBeReplaced}` | -Let's look at an example. Given the variables: +Escaping compounds with the length of the hash sequence. Given the variable `Name` with the value `title`, `###{Name}` evaluates to `#title`: the leading `##` collapses to a literal `#`, and the remaining `#{Name}` is substituted normally. -| Name | Value | Scope | -| -------------- | ------- | ---------- | -| `DebugEnabled` | `True` | Dev | -| `DebugEnabled` | `False` | Production | +## Variable references -Then the following template: +A variable reference names the value you want Octopus to substitute, ranging from a plain variable name to a specific value selected out of a structured object. You can combine a reference with surrounding text to compose a new value: -```powershell - ``` - -The resulting text in the *Dev* environment will be: - -```xml - +#{Octopus.Environment.Name}-#{Octopus.Project.Name} ``` -And in *Production* it will be: +Some variables are structured, and you select a value from them using indexer notation, where the key inside the square brackets identifies the entry you want. Many system variables use this form to expose per-action, per-machine, or per-package values. For example, this reference selects the target roles for a specific action: -```xml - ``` - -You could achieve a similar result, with a different default/fallback behavior, using the unless syntax: - -```powershell - +#{Octopus.Action[Website].TargetRoles} ``` -#### Using variable filters in conditionals \{#conditional-filters} - - - -#### *Truthy* and *Falsy* Values \{#truthy-and-falsy} +The index itself can be another variable reference, which lets you look up a value dynamically rather than naming the key literally. Given the variables: -The `if`, `if-else` and `unless` statements consider a value to be *falsy* if it is undefined; an empty string; or (ignoring case and any leading or trailing whitespace) `False`, `No` or `0`. All other values are considered to be *truthy*. +| Name | Value | +| --- | --- | +| `MyPassword[Rob]` | `passwordX` | +| `MyPassword[Steve]` | `passwordY` | +| `MyPassword[Mary]` | `passwordZ` | +| `UserName` | `Mary` | -:::div{.warning} -**All variables are strings** -Note that when evaluating values, **all Octopus variables are strings** even if they look like numbers or other data types. -::: - -### Complex syntax - -Additional conditional statements are supported, including `==` and `!=`. +`#{MyPassword[#{UserName}]}` evaluates to `passwordZ`. -Using complex syntax you can have expressions like `#{if Octopus.Environment.Name == "Production"}...#{/if}` and `#{if Octopus.Environment.Name != "Production"}...#{/if}`, or: +Output variables extend indexer notation with a second index for the machine that produced the value. An [output variable](/docs/projects/variables/output-variables) set in one step is available to later steps using the action name and the machine name: -```text -#{if ATruthyVariable} - Do this if ATruthyVariable evaluates to true -#{else} - Do this if ATruthyVariable evaluates to false -#{/if} ``` - -#### OR Conditions - -It's common to want to check for more than one value in an Octopus variable. To achieve this, you can create an effective `OR` statement by combining an `if` with another `else` statement: - -```text -#{if Octopus.Environment.Name == "Development"} - Do this if it's Development -#{else} - #{if Octopus.Environment.Name == "Test"} - Do this if it's Test - #{else} - Do this if it's neither - #{/if} -#{/if} +#{Octopus.Action[Website].Output[WEBSVR01].Package.InstallationDirectoryPath} ``` -This is the equivalent of checking the Environment name for Development or Test. - -### Comparing one variable value with another - -Sometimes, you might want to compare one variable value with another. - -Given the variables: - -| Name | Value | Scope | -|-----------------------|----------|-------------| -| `Base.MaxLogLevel` | `ERROR` | | -| `Environment.LogLevel`| `DEBUG` | `Dev` | -| `Environment.LogLevel`| `INFO` | `Test` | -| `Environment.LogLevel`| `ERROR` | `Staging` | -| `Environment.LogLevel`| `ERROR` | `Production`| +## Collection variables -Using conditional syntax, you can compare the value in the `Base.MaxLogLevel` variable with the `Environment.LogLevel` variable value. +A collection variable holds many entries rather than a single value. Several system variables are collections, including the packages, builds, commits, and work items associated with a release, and the changes included in a deployment; see [System variables](/docs/projects/variables/system-variables) for the full list. Knowing how to read a collection lets you work with all of them, because they share the same iteration and indexing syntax. -Using the template: +You iterate over a collection with an `#{each}` block. Octopus repeats the content between `#{each}` and `#{/each}` once per entry, binding each entry to the name you choose so you can reference its properties: -```text -#{if Environment.LogLevel == Base.MaxLogLevel}We are at the MAX!#{else}We have room to grow!#{/if} ``` - -The resulting text in both *Dev and Test* will be: - -```text -We have room to grow! +#{each package in Octopus.Release.Package} + This release contains #{package.PackageId} #{package.Version} +#{/each} ``` -And in both *Staging and Production* it will be: +Collections can be nested. An entry in one collection can itself hold a collection, which you iterate the same way: -```text -We are at the MAX! +``` +#{each package in Octopus.Release.Package} + #{each commit in package.Commits} + - #{commit.CommitId}: #{commit.Comment} + #{/each} +#{/each} ``` -:::div{.hint} -Note both operands **don't** include the Octostache syntax denoting them as a variable e.g. `#{Environment.LogLevel}`. This is because within a conditional expression Octostache is already able to evaluate the operand as a variable value. -::: - -### Run conditions - -Conditions can be used to control whether a given step in a deployment process actually runs. In this scenario the conditional statement should return true/false, depending on your requirements. - -Some examples are: - -`#{if Octopus.Environment.Name == "Production"}true#{/if}` would run the step only in Production. - -`#{if Octopus.Environment.Name != "Production"}true#{/if}` would run the step in all environments other than Production. - -`#{unless Octopus.Action[StepName].Output.HasRun == "True"}true#{/unless}` would run the step unless it has run before. This would be useful for preventing something like an email step from executing every time an auto deploy executed for new machines in an environment. It would be used in conjunction with the step calling `Set-OctopusVariable -name "HasRun" -value "True"` when it does run. - -### Repetition \{#repetition} +You can also select a single entry directly using indexer notation. Some collections are keyed by an identifier, such as a package ID: -The `each` statement supports repetition over a set of variables, or over the individual values in a variable separated with commas. +``` +#{Octopus.Release.Package[Acme.Web].Version} +``` -#### Iterating over sets of values +Others are keyed by a zero-based integer index, so the first entry is at index 0: -More complex sets of related values are handled using multiple variables: +``` +#{Octopus.Release.Builds[0].BuildUrl} +``` -| Name | Value | Scope | -| ------------------------- | ---------------------- | ----- | -| `Endpoint[A].Address` | `http://a.example.com` | | -| `Endpoint[A].Description` | `Primary` | | -| `Endpoint[B].Address` | `http://b.example.com` | | -| `Endpoint[B].Description` | `Replica` | | +A collection doesn't have to come from a system variable. You can define your own keyed collection using multiple variables that share an index prefix, then iterate it the same way. Given the variables: -Given the template: +| Name | Value | +| --- | --- | +| `Endpoint[A].Address` | `http://a.example.com` | +| `Endpoint[A].Description` | `Primary` | +| `Endpoint[B].Address` | `http://b.example.com` | +| `Endpoint[B].Description` | `Replica` | -```powershell -Listening on: +``` #{each endpoint in Endpoint} - - Endpoint #{endpoint} at #{endpoint.Address} is #{endpoint.Description} + - #{endpoint} at #{endpoint.Address} is #{endpoint.Description} #{/each} ``` -The result will be: +A variable containing a comma-separated list also iterates directly, without needing an index at all: -```powershell -Listening on: - - Endpoint A at http://a.example.com is Primary - - Endpoint B at http://b.example.com is Replica ``` - -#### Complex syntax with sets of values - -Sometimes, you might want to compare one variable value with another contained in a set of values. - -Given the variables: - -| Name | Value | -|--------------------|---------------------------------------------------------------------------------------------------------| -| `WidgetIdSelector` | `Widget-2` | -| `MyWidgets` | `{"One":{"WidgetId":"Widget-1","Name":"Widget-One"},"Two":{"WidgetId":"Widget-2","Name":"Widget-Two"}}` | - -Using complex syntax, you can iterate over the values in the `MyWidgets` variable and find the entry with the value specified in the second variable `WidgetIdSelector`. - -Using the template: - -```text -#{each w in MyWidgets} -'#{w.Value.WidgetId}': #{if w.Value.WidgetId == WidgetIdSelector}This is my Widget!#{else}No widget matched :(#{/if} +#{each endpoint in "http://a.example.com,http://b.example.com"} + - #{endpoint} #{/each} ``` -The resulting text will be: +### JSON variables -```text -'Widget-1': No widget matched :( -'Widget-2': This is my Widget! -``` +Octostache parses a JSON-formatted variable value natively, exposing its properties for substitution the same way a system-variable collection does. Given the variable `Custom.MyJson` with the value `{Name: "t-shirt", Sizes: [{size: "small", price: 15.00}, {size: "large", price: 20.00}]}`, `#{Custom.MyJson.Name}` evaluates to `t-shirt`, and `#{Custom.MyJson.Sizes[0].price}` evaluates to `15.00`. -:::div{.hint} -**Tips:** +A JSON array or object iterates using the same `#{each}` syntax as any other collection: -- Note both operands **don't** include the Octostache syntax denoting them as a variable e.g. `#{WidgetIdSelector}`. This is because within a conditional expression Octostache is already able to evaluate the operands as variable values. -- The template references `.Value` which is a property available when using [JSON repetition](/docs/projects/variables/variable-filters/#repetition-over-json). +``` +#{each item in Custom.MyJson.Sizes} + - #{item.size}: #{item.price} +#{/each} +``` -::: +When iterating a JSON object rather than an array, each entry exposes `.Key` and `.Value` properties for the object's key and the value at that key. -#### Iterating over comma-separated values +## Loop iteration variables -Given the variable: +Inside an `#{each}` block, Octopus makes a set of variables available that report the position of the current entry in the collection being iterated. This topic lists them. All values are strings. -| Name | Value | Scope | -| ----------- | --------------------------------------------- | ----- | -| `Endpoints` | `http://a.example.com,http://b.example.com` | | +| Variable | Description | +| --- | --- | +| `Octopus.Template.Each.Index` | The zero-based index of the current entry in the iteration. | +| `Octopus.Template.Each.First` | `True` if the entry is the first in the collection, otherwise `False`. | +| `Octopus.Template.Each.Last` | `True` if the entry is the last in the collection, otherwise `False`. | -And the template: +Given the variable `Endpoints` with the comma-separated value `SV1,SV2,SV3`, the following template prints a marker on only the first and last entries: -```powershell -Listening on: +``` #{each endpoint in Endpoints} - - #{endpoint} +#{if Octopus.Template.Each.First}First: #{endpoint}#{/if} +#{if Octopus.Template.Each.Last}Last: #{endpoint}#{/if} #{/each} ``` -The resulting text will be: +This produces: -```powershell -Listening on: - - http://a.example.com - - http://b.example.com +``` +First: SV1 +Last: SV3 ``` -#### Special variables \{#special-variables} +## Calculations -Within the context of an iteration template, some special variables are available. +Octopus supports basic arithmetic on variable values using the `calc` statement, so you can derive a value like an IP offset or a scaled count without pre-computing it yourself. Four operators are supported: addition (`+`), subtraction (`-`), multiplication (`*`), and division (`/`). -| Name | Description | -| ----------------------------- | ------------------------------------------------------------------------- | -| `Octopus.Template.Each.Index` | Zero-based index of the iteration count | -| `Octopus.Template.Each.First` | `"True" if the element is the first in the collection`, otherwise "False" | -| `Octopus.Template.Each.Last` | "True" if the element is the last in the collection, otherwise "False" | +Given the variables: -Given the variable created as an index (comma separated): +| Name | Value | +| --- | --- | +| `IPOffset[Primary]` | `0` | +| `ScaleFactor` | `12` | +| `Numbers` | `10,20,30,40,50` | -| Name | Value | -| ----------- | ---------------------------------------- | -| `Endpoints` | `SV1,SV2,SV3` | +- `192.168.0.#{calc IPOffset[Primary] + 1}` evaluates to `192.168.0.1` +- `#{calc 22 * ScaleFactor}` evaluates to `264` +- `#{each i in Numbers}#{calc i + 5}#{/each}` evaluates to `15 25 35 45 55` -And the template: +When a variable appears on the left-hand side of a divide or subtract operation, enclose its name in braces so the operator symbol isn't parsed as part of the variable name: `#{calc {IPOffset[Primary]} - 4}`. -```powershell -#{each endpoint in Endpoints} +## Conditionals -#{if Octopus.Template.Each.First} - write-host 'This is the first item in the Index : ' #{endpoint} -#{/if} +A conditional includes or excludes text based on a variable's value, letting you vary a script or configuration file by context without maintaining a separate copy for each case. Octopus evaluates the condition when the step runs and keeps only the matching branch. -#{if Octopus.Template.Each.Last} - write-host 'This is the last item in the Index : ' #{endpoint} +Two conditional statements are supported: `if`, which evaluates its content when the variable is truthy, and `unless`, which evaluates when the variable is falsy. A value is falsy if it's undefined, an empty string, or (ignoring case and surrounding whitespace) `False`, `No`, or `0`; every other value is truthy. + +``` +#{if VariableName}conditional statements#{/if} +#{unless VariableName}conditional statements#{/unless} +``` + +An `#{if}` block can include an `#{else}` branch for the case where the condition doesn't hold: + +``` +#{if Octopus.Environment.Name == "Production"} + ProductionValue +#{else} + DefaultValue #{/if} ``` -The resulting text will be: +### Complex syntax -```powershell -This is the first item in the Index : SV1 +Beyond a truthy/falsy check, a conditional can compare values directly using `==` and `!=`, for example `#{if Octopus.Environment.Name == "Production"}...#{/if}`. Neither operand uses `#{...}` syntax inside the comparison — Octostache already evaluates each operand as a variable's value there, and the same comparison works against another variable's value instead of a literal: `#{if Environment.LogLevel == Base.MaxLogLevel}...#{/if}`. -This is the last item in the Index : SV3 -``` +Combining `if` and `else` lets you express an effective OR across more than two cases: -### Further examples +``` +#{if Octopus.Environment.Name == "Development"} + Do this if it's Development +#{else} + #{if Octopus.Environment.Name == "Test"} + Do this if it's Test + #{else} + Do this if it's neither + #{/if} +#{/if} +``` -If you're struggling with a specific syntax or OctoStache construct, you can find more examples in the unit tests defined for the library on GitHub: -[OctoStache Tests UsageFixture](https://github.com/OctopusDeploy/Octostache/blob/master/source/Octostache.Tests/UsageFixture.cs). + -### Filters \{#filters} +### Run conditions -The following filters are available: +A conditional expression can also control whether a step in a deployment process runs at all — for example, `#{if Octopus.Environment.Name == "Production"}true#{/if}` runs a step only in Production. See [Conditions](/docs/projects/steps/conditions) for how run conditions are configured on a step. -- ToLower -- ToUpper -- ToBase64 -- HtmlEscape -- XmlEscape -- JsonEscape -- YamlSingleQuoteEscape -- YamlDoubleQuoteEscape -- PropertiesKeyEscape -- PropertiesKeyEscape -- Markdown -- NowDate -- NowDateUtc -- Format -- Replace -- Trim -- Truncate -- Substring +## Filters -The filters can be invoked in the following way: +A filter transforms a variable's value as Octopus substitutes it, so you can reshape a value for its destination without changing the underlying variable. Apply a filter with a pipe after the variable name: -```powershell -#{Octopus.Environment.Name | ToLower} +``` +#{Octopus.Release.Notes | Markdown} ``` -For more information, see [Variable Filters](/docs/projects/variables/variable-filters). +Filters cover common transformations such as changing case, escaping values for HTML or JSON, and rendering Markdown. For the full set of filters and what each one does, see [Variable filters](/docs/projects/variables/variable-filters). -## Older versions +## Version notes -The `calc` operator is available from Octopus Deploy **2023.2** onwards. +- The `calc` operator is available from Octopus Server 2023.2. -## Learn more +## Related links -- [Variable blog posts](https://octopus.com/blog/tag/variables/1) +- [System variables](/docs/projects/variables/system-variables) +- [Variable filters](/docs/projects/variables/variable-filters) +- [Output variables](/docs/projects/variables/output-variables) +- [Custom scripts](/docs/deployments/custom-scripts) +- [Variables](/docs/projects/variables) diff --git a/src/pages/docs/releases/deployment-changes.md b/src/pages/docs/releases/deployment-changes.md index edb6be2d29..b362c46a29 100644 --- a/src/pages/docs/releases/deployment-changes.md +++ b/src/pages/docs/releases/deployment-changes.md @@ -125,7 +125,7 @@ The following template only shows the most recent change being deployed, includi #{/each} ``` -This is achieved using the Octopus [special variables](https://octopus.com/docs/projects/variables/variable-substitutions#special-variables), specifically `Octopus.Template.Each.Last` and `Octopus.Template.Each.First` to include a header for each section, and will render like this: +This is achieved using the Octopus [loop iteration variables](https://octopus.com/docs/projects/variables/variable-substitutions#loop-iteration-variables), specifically `Octopus.Template.Each.Last` and `Octopus.Template.Each.First` to include a header for each section, and will render like this: :::figure ![Deployment notes rendered using the Octopus.Template.Each.Last variable](/docs/img/releases/images/deployment-notes-template-each-last-example.png) From 9c51f8a95bc2668debdd4325e4854cfe327bb83d Mon Sep 17 00:00:00 2001 From: Sam Irons Date: Thu, 6 Aug 2026 10:30:51 +1000 Subject: [PATCH 2/3] Rewrite variable substitution syntax docs to new content standards and fix resulting cross-references and markdown lint issues --- .../deployment-and-runbook-processes.md | 16 ++++--- .../best-practices/deployments/variables.md | 27 ++++++----- .../terraform-output-variables/index.md | 6 +-- .../email-notifications.md | 48 ++++++++++--------- .../projects/variables/sensitive-variables.md | 16 ++++--- .../variables/variable-substitutions.mdx | 40 ++++++++-------- src/pages/docs/releases/deployment-changes.md | 19 ++++---- 7 files changed, 89 insertions(+), 83 deletions(-) diff --git a/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md b/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md index 67d3f7460d..5453140ecd 100644 --- a/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md +++ b/src/pages/docs/best-practices/deployments/deployment-and-runbook-processes.md @@ -15,13 +15,14 @@ Deployment and runbook processes are the series of steps you specify to either d Aside from the intended usage, there is little difference between a deployment process and a runbook process. Both can use the same steps. Both can scope steps to run on specific environments. Because of that, this section can apply to both the runbooks and deployment processes. -When you run a runbook or do a deployment, all the output is written to the Task Log. +When you run a runbook or do a deployment, all the output is written to the Task Log. :::figure ![task log after a deployment](/docs/img/getting-started/best-practices/images/task-log.png) ::: -The top of the task log allows you to change the information shown. +The top of the task log allows you to change the information shown. + - By default, the log level is set to Info, which means only Info, Warnings, and Errors messages are shown. Changing to Verbose will show all messages. - By default, the screen will only show the 20 most recent messages for each step. Changing the log tail to all will show all messages. - Clicking on the **RAW** button will show you all the messages without any formatting. @@ -34,6 +35,7 @@ The task log represents the deployment or runbook run details but is typically u ::: The summary includes: + - List of all steps and any important messages. - The task history, which includes who triggered it and when it was triggered. - The list of previous deployments to this environment for this project. @@ -50,7 +52,7 @@ Both the task log and artifacts can be downloaded; there is no need to create ar ## Logging -You can never have too much logging when running custom PowerShell, Python, or Bash scripts in your deployment or runbook process. +You can never have too much logging when running custom PowerShell, Python, or Bash scripts in your deployment or runbook process. [Octopus Deploy](/docs/deployments/custom-scripts/logging-messages-in-scripts) supplies built-in logging utilities you can leverage in your scripts. This is one of the few areas where it is okay to directly reference these functions instead of passing them in as parameters. @@ -60,15 +62,15 @@ We also recommend leveraging the different logging levels as Octopus treats each - Information: This is shown in the task log by default. Useful for logging status messages to the user. - Warning: Messages are highlighted in yellow in the task log. Helpful if something isn't quite right, but the script was able to recover. - Error: Messages are highlighted in red in the task log and task summary. This message type is for what it says on the tin, error messages. -- Highlight: Messages are highlighted in blue in the task log and task summary. Use these for important messages you want to let the user know about. +- Highlight: Messages are highlighted in blue in the task log and task summary. Use these for important messages you want to let the user know about. Putting information required for approvals in a log file can make it difficult to find. Consider artifacts as an alternative. ## Step naming and descriptions -We recommend having clear, concise names for your steps. The step name is what appears in the task summary and task log. +We recommend having clear, concise names for your steps. The step name is what appears in the task summary and task log. -By default, Octopus Deploy will only run a step if the previous step is successful. You can override that behavior, so it always runs, only runs on failure, or runs on a variable condition. If you override that default behavior, then the name should include that in the step name. +By default, Octopus Deploy will only run a step if the previous step is successful. You can override that behavior, so it always runs, only runs on failure, or runs on a variable condition. If you override that default behavior, then the name should include that in the step name. Starting in **Octopus Deploy 2020.5**, you can add a description field to each step. We highly recommend using that to help other users of your deployment process understand what each step is doing. @@ -95,7 +97,7 @@ The deployment and runbook processes should be the same for all environments, ex When defining your runbook or deployment process, you can configure a run condition based on a variable. Generally, that feature is used to skip over a specific step when a particular condition occurs. For example, skip over a testing step if only .css or image files were deployed. -Octopus Deploy provides [conditional variable syntax](/docs/projects/variables/variable-substitutions/#conditionals) that includes support for if/elseif/else/then statements. While it is possible to have a complex if/then statement in run condition, we don't recommend it. Instead, we recommend you run a script and set an [output variable](/docs/projects/variables/output-variables). Use that output variable in the run condition. Having the decision made in a script means you can easily test and debug it. +Octopus Deploy provides [conditional variable syntax](/docs/projects/variables/variable-substitutions/#conditionals) that includes support for if/elseif/else/then statements. While it is possible to have a complex if/then statement in run condition, we don't recommend it. Instead, we recommend you run a script and set an [output variable](/docs/projects/variables/output-variables). Use that output variable in the run condition. Having the decision made in a script means you can easily test and debug it. ## Further reading diff --git a/src/pages/docs/best-practices/deployments/variables.md b/src/pages/docs/best-practices/deployments/variables.md index 2fd848ec77..6e26e16f15 100644 --- a/src/pages/docs/best-practices/deployments/variables.md +++ b/src/pages/docs/best-practices/deployments/variables.md @@ -12,11 +12,11 @@ hideInThisSection: true There are multiple levels of variables in Octopus Deploy: -1. Project -2. Tenant -3. Step Templates -4. Variable Set -5. System Variables +1. Project +2. Tenant +3. Step Templates +4. Variable Set +5. System Variables Project, Tenant, and Step Template variables are associated with their specific item and cannot be shared. Variable Sets can be shared between 1 to N Projects and Tenants. System variables are variables provided by Octopus Deploy you can use during deployments. @@ -30,8 +30,8 @@ Octopus Deploy provides the ability to replace values in your configuration file In addition to having the above levels of variables, there are also two categories of variables. -1. Variables used in configuration file replacement (connection strings, version number, etc.) -2. Variables specific to the deployment or runbook run (output variables, messages, accounts, etc.) +1. Variables used in configuration file replacement (connection strings, version number, etc.) +2. Variables specific to the deployment or runbook run (output variables, messages, accounts, etc.) ## Variable naming @@ -50,10 +50,10 @@ These naming conventions only apply to variables used for a deployment or runboo One of Octopus Deploy's most used features is environmental variable scoping. And with good reason, having the same process, only needing a specific value such as a connection string or domain name changed, ensures consistency during deployment. -However, that has led some customers to attempt to make Octopus Deploy something other than what it is. Octopus Deploy is not a configuration management tool, secret store, or feature flag provider. Store the variables required for Octopus Deploy to successfully deploy your application, along with a minimum amount of configuration variables. +However, that has led some customers to attempt to make Octopus Deploy something other than what it is. Octopus Deploy is not a configuration management tool, secret store, or feature flag provider. Store the variables required for Octopus Deploy to successfully deploy your application, along with a minimum amount of configuration variables. :::div{.hint} -Changing a feature flag or secret stored in Octopus Deploy requires you to deploy or run a runbook to update the file manually. Leverage best-in-breed tools for storing secrets or feature flags that were designed with that use case in mind. Octopus Deploy should store the necessary connection information to those platforms as sensitive variables. It should update the appropriate configuration file entries or set environmental variables to connect successfully to those tools. +Changing a feature flag or secret stored in Octopus Deploy requires you to deploy or run a runbook to update the file manually. Leverage best-in-breed tools for storing secrets or feature flags that were designed with that use case in mind. Octopus Deploy should store the necessary connection information to those platforms as sensitive variables. It should update the appropriate configuration file entries or set environmental variables to connect successfully to those tools. ::: Some examples of configuration variables include: @@ -64,7 +64,7 @@ Some examples of configuration variables include: - Server ports - Service URLs -There are also variables only Octopus Deploy knows about. These include the release version number, environment name, and deployment date. +There are also variables only Octopus Deploy knows about. These include the release version number, environment name, and deployment date. For configurations that differ per environment, our recommendation is to use a combination of Octopus Deploy and configuration files stored in source control. You'd have three levels of configuration files: @@ -87,10 +87,11 @@ Octopus Deploy can set an environment variable or configuration value during dep A common scenario we've talked to customers about is restricting variable edit access to specific environments. For example, a developer can edit any variables scoped to **development** and **test** environments, but not **staging** or **production** environments. On paper this makes sense, in practice this causes messy handovers and claims of "it worked on my machine." The developers working on the application know all the various settings and variables required for their application to work. Our recommendations for variable edit permissions are: -- Variable edit permissions should be all or nothing, either a person can edit variables, or they cannot. Don't scope permissions to environments. Anyone responsible for the application should have permission to update variables (developers, lead developers, DB developers, etc.) along with operations (DBAs, web admins, sysadmins) who can create and update service accounts and passwords. + +- Variable edit permissions should be all or nothing, either a person can edit variables, or they cannot. Don't scope permissions to environments. Anyone responsible for the application should have permission to update variables (developers, lead developers, DB developers, etc.) along with operations (DBAs, web admins, sysadmins) who can create and update service accounts and passwords. - Variable Sets can be shared across multiple projects. Limit who can edit variable set variables to more experienced Octopus Deploy users, or people who understand "with great power comes great responsibility." Typically, we see senior or lead developers along with operations people who have these permissions. If you want to isolate an application, consider using [spaces](/docs/administration/spaces). -- Leverage [sensitive variables](/docs/projects/variables/sensitive-variables) to encrypt and hide sensitive values such as usernames and passwords. Sensitive variables are write-only in the Octopus UI and Octopus API. -- Use [composite variables](/docs/projects/variables/variable-substitutions/#composite-variables) to combine sensitive and non-sensitive values. A typical use case is database connection strings. Each language has a specific syntax. In the screenshot below `Project.Database.ConnectionString` is the composite variable, with the username and password referenced by the composite variable, but they are separate sensitive variables. +- Leverage [sensitive variables](/docs/projects/variables/sensitive-variables) to encrypt and hide sensitive values such as usernames and passwords. Sensitive variables are write-only in the Octopus UI and Octopus API. +- Use [composite variables](/docs/projects/variables/variable-substitutions/#composite-variables) to combine sensitive and non-sensitive values. A typical use case is database connection strings. Each language has a specific syntax. In the screenshot below `Project.Database.ConnectionString` is the composite variable, with the username and password referenced by the composite variable, but they are separate sensitive variables. :::figure ![composite variables](/docs/img/getting-started/best-practices/images/composite-variables.png) diff --git a/src/pages/docs/deployments/terraform/terraform-output-variables/index.md b/src/pages/docs/deployments/terraform/terraform-output-variables/index.md index 1235c4e478..b211c5b77d 100644 --- a/src/pages/docs/deployments/terraform/terraform-output-variables/index.md +++ b/src/pages/docs/deployments/terraform/terraform-output-variables/index.md @@ -25,7 +25,7 @@ The JSON representation of the output variable is the result of calling `terrafo While the value only output (which would appear in the logs as a message similar to `Saving variable "Octopus.Action[Apply Template].Output.TerraformValueOutputs[test]" with the value only of "test"`) would look similar to this: -``` +```text "hi there" ``` @@ -43,6 +43,6 @@ $value = $OctopusParameters["Octopus.Action[Apply Template].Output.TerraformJson The syntax for accessing JSON variables as covered by our [documentation here](/docs/projects/variables/variable-substitutions/#json-variables) applies to both `TerraformJsonOutputs` as well as `TerraformValueOutputs`. However, the latter is less useful as it can also be a primitive value. In this case Octostache won't know that it should deserialize the value and will provide you with a JSON encoded result. It is therefore recommended to prefer `TerraformJsonOutputs` where possible. The following syntax can be used to access the value using the binding syntax: -``` +```text #{Octopus.Action[Apply Template].Output.TerraformJsonOutputs[test].value} -``` \ No newline at end of file +``` diff --git a/src/pages/docs/projects/built-in-step-templates/email-notifications.md b/src/pages/docs/projects/built-in-step-templates/email-notifications.md index 89ddb9a476..1d8302ae9c 100644 --- a/src/pages/docs/projects/built-in-step-templates/email-notifications.md +++ b/src/pages/docs/projects/built-in-step-templates/email-notifications.md @@ -22,38 +22,40 @@ Before you can add email steps to your deployment processes, you need to add you To add your SMTP configuration navigate to **Configuration ➜ SMTP** and set the following values: -| Property | Description | Example | -| ------------------ | ------------------------------------ | ----------- | -| SMTP Host | The DNS hostname for your SMTP server. | smtp.example.com | -| SMTP Port | The TCP port for your SMTP server. | 25 | -| Timeout | The timeout for SMTP operations. Value is in milliseconds. | 12000 (12 seconds) | -| Use SSL/TLS | This option controls whether or not Octopus enforces using an SSL/TLS-wrapped connection. | True | -| From Address | The address which all emails will be sent 'From'. | octopus@mydomain.com | -| Credentials | Optional SMTP login / password if your SMTP server requires authentication. | mylogin@mydomain.com / SuperSecretPa$$word | +| Property | Description | Example | +| --- | --- | --- | +| SMTP Host | The DNS hostname for your SMTP server. | smtp.example.com | +| SMTP Port | The TCP port for your SMTP server. | 25 | +| Timeout | The timeout for SMTP operations. Value is in milliseconds. | 12000 (12 seconds) | +| Use SSL/TLS | This option controls whether or not Octopus enforces using an SSL/TLS-wrapped connection. | True | +| From Address | The address which all emails will be sent 'From'. | `octopus@mydomain.com` | +| Credentials | Optional SMTP login / password if your SMTP server requires authentication. | `mylogin@mydomain.com` / `SuperSecretPa$$word` | Click **Save and test** to save the SMTP configuration and verify the values are valid: :::figure -![](/docs/img/projects/built-in-step-templates/images/smtp-configuration.png) +![SMTP configuration page](/docs/img/projects/built-in-step-templates/images/smtp-configuration.png) ::: You will be prompted for an email address to send a test email to. Enter a test email address and click **Ok**. A *Send test email* task will start to verify your SMTP Configuration: :::figure -![](/docs/img/projects/built-in-step-templates/images/smtp-verify-task.png) +![Send test email task verifying the SMTP configuration](/docs/img/projects/built-in-step-templates/images/smtp-verify-task.png) ::: ### Google OAuth 2.0 Credentials + Optionally you can use Workload Identity Federation and OAuth 2.0 for Google SMTP authentication. To do this, set the following values: -| Property | Description | Example | -| ------------------ | ------------------------------------ | ----------- | -| Audience | The audience set on the Workload Identity Federation | `https://iam.googleapis.com/projects/{project-id}/locations/global/workloadIdentityPools/{pool-id}/providers/{provider-id}` | -| Service Account | The email of the service account which has been granted access | service-account-name@{project-id}.iam.gserviceaccount.com | +| Property | Description | Example | +| --- | --- | --- | +| Audience | The audience set on the Workload Identity Federation | `https://iam.googleapis.com/projects/{project-id}/locations/global/workloadIdentityPools/{pool-id}/providers/{provider-id}` | +| Service Account | The email of the service account which has been granted access | `service-account-name@{project-id}.iam.gserviceaccount.com` | See the [Google cloud documentation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers) for instructions on creating and configuring a Workload Identity Federation. When setting up the Workload Identity Federation: + - When granting access to the service account, the principal must have the subject attribute name set to `smtp`. Example: `https://iam.googleapis.com/projects/{project-id}/locations/global/workloadIdentityPools/{pool-id}/subject/smtp`. - The service account must have domain wide delegation with an OAuth scope of `https://mail.google.com/`, see [documentation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority) on how to set this up. @@ -65,14 +67,15 @@ Support for Microsoft OAuth 2.0 authentication requires Octopus Server version 2 Optionally for Microsoft SMTP authentication, you can use Federated Credentials and OAuth 2.0. To do this, set the following values: -| Property | Description | Example | -| ------------------ | ------------------------------------ | ----------- | -| Audience | The audience set on the Federated Credential | Defaults to `api://AzureADTokenExchange` | -| Permission Scopes | The scopes to be included in the authentication token | Defaults to `https://outlook.office365.com/.default` | -| Client ID | The Azure Active Directory Application ID/Client ID | GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | -| Tenant ID | The Azure Active Directory Tenant ID | GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | +| Property | Description | Example | +| --- | --- | --- | +| Audience | The audience set on the Federated Credential | Defaults to `api://AzureADTokenExchange` | +| Permission Scopes | The scopes to be included in the authentication token | Defaults to `https://outlook.office365.com/.default` | +| Client ID | The Azure Active Directory Application ID/Client ID | GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | +| Tenant ID | The Azure Active Directory Tenant ID | GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | For OAuth 2.0 you will need to: + 1. Set up a Microsoft Entra ID App Registration. - See [documentation on registering an application](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=federated-credential%2Cexpose-a-web-api#register-an-application). - Set the configuration properties `Client ID` and `Tenant ID` with the values from your registered application. @@ -93,7 +96,6 @@ From 2025.3, you can specify custom Permission Scopes to be included in the OAut This supports the use of Azure Communication Services (ACS). To use this ensure your SMTP Username in Azure matches your specified `From Address`. More information can be found in the [ACS documentation](https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/email/send-email-smtp/send-email-smtp-oauth). ::: - ## Add an email step Email steps are added to deployment processes in the same way as other steps. @@ -118,7 +120,7 @@ Email steps are added to deployment processes in the same way as other steps. - Send the email only for successful deployments to certain environments. - Send a specific email for failed deployments. - Send an email based on the value of a variable expression which works well with [output variables](/docs/projects/variables/output-variables). -9. Save the deployment process. +9. Save the deployment process. ## Email template examples @@ -158,7 +160,7 @@ To use the template in your projects, replace `nuget.org` with the DNS name of y The output of the template will be an HTML email like: :::figure -![](/docs/img/projects/built-in-step-templates/images/email-output.png) +![Example HTML email produced by the deployment summary template](/docs/img/projects/built-in-step-templates/images/email-output.png) ::: ### Step status summary template diff --git a/src/pages/docs/projects/variables/sensitive-variables.md b/src/pages/docs/projects/variables/sensitive-variables.md index 1933889cb7..5ca21ad2fb 100644 --- a/src/pages/docs/projects/variables/sensitive-variables.md +++ b/src/pages/docs/projects/variables/sensitive-variables.md @@ -17,7 +17,7 @@ Sensitive variables can be sourced from either: ## Values from a Secret Manager/Key Vault {#values-from-key-vaults} -Storing sensitive values in Octopus solves many problems, but it's [not a two-way key vault](#how-octopus-handles-sensitive-variables). +Storing sensitive values in Octopus solves many problems, but it's [not a two-way key vault](#how-octopus-handles-sensitive-variables). For this, there are a number of Secret Manager and Key Vault tools available. They also offer additional functionality such as automatic secret rotation and versioning. Octopus supports the retrieval of sensitive values from a number of Secret Manager/Key Vaults through the use of [Community step templates](/docs/projects/community-step-templates) that extend the functionality of Octopus to integrate with them. @@ -44,13 +44,13 @@ View working examples of all of our Secrets Management community step templates ## Sensitive variables stored in Octopus {#sensitive-variables-in-octopus} -Variables, such as passwords or API keys can be marked as **sensitive**. +Variables, such as passwords or API keys can be marked as **sensitive**. -Just like non-sensitive variables they can [reference other variables](/docs/projects/variables/#use-variables-in-step-definitions) but be careful with any part of your sensitive variable that could unintentionally be interpreted as an attempted substitution. See also, other [common mistakes](#avoiding-common-mistakes). +Just like non-sensitive variables they can [reference other variables](/docs/projects/variables/variable-substitutions/#composite-variables) but be careful with any part of your sensitive variable that could unintentionally be interpreted as an attempted substitution. See also, other [common mistakes](#avoiding-common-mistakes). ### Configuring sensitive variables {#configure-sensitive-variables} -To make a variable a **sensitive variable**, either select **Change Type** when entering the value and select **Sensitive**, or enter the variable editor when you are creating or editing the variable. +To make a variable a **sensitive variable**, either select **Change Type** when entering the value and select **Sensitive**, or enter the variable editor when you are creating or editing the variable. If using the variable editor, on the variable value, click **Open editor**: @@ -74,7 +74,7 @@ When dealing with sensitive variables, Octopus encrypts these values using: - **AES-256** encryption when they are stored in the Octopus database in versions 2024.4 and newer. - **AES-128** encryption when they are stored in the Octopus database in versions prior to 2024.4. -- **AES-128 encryption** any time they are in transmission, or when they are stored on a deployment target as part of a deployment. +- **AES-128 encryption** any time they are in transmission, or when they are stored on a deployment target as part of a deployment. You can use these sensitive values in your deployment process just like normal [variables](/docs/projects/variables), with two notable exceptions: @@ -93,7 +93,7 @@ Another common example is building a *composite* value using the [variable subst In this case you should at least make the `DB.Password` variable sensitive so it will be encrypted in the database and masked from any Octopus task log messages like this: -`Server=db01.my-company.com;Database=my-database;User=my-user;Password=*****`. +`Server=db01.my-company.com;Database=my-database;User=my-user;Password=*****`. You could also make `DB.Username` or any of the other components of this template sensitive. @@ -125,13 +125,14 @@ Write-Output "Hello, the password is $Password" Octopus would mask the value from the deployment log, leaving: -``` +```text Hello, the password is ***** ``` Note that this method isn't 100% foolproof. Here are a couple of scenarios that you should be extra-careful about if logging sensitive variables: ### Common language in secrets + If your top secret password is "broke", and someone happened to deploy with a PowerShell script with: ```powershell @@ -147,6 +148,7 @@ Or watch the things you gave your life to, *******en The obvious solution is, don't use passwords that are likely to occur in normal logging/language, and avoid writing the values of your secure variables to logs anyway. ### `echo` on Unix-based systems + It's very easy to [unintentionally modify a variable when using `echo`](https://stackoverflow.com/q/29378566/16866455), particularly if the variable contains new-lines or other escape characters. In particular, you should [always use double-quotes](https://stackoverflow.com/a/29378567/16866455) around what you `echo`, to prevent unintended processing of variable contents. diff --git a/src/pages/docs/projects/variables/variable-substitutions.mdx b/src/pages/docs/projects/variables/variable-substitutions.mdx index e8b42c9d01..a6b4591996 100644 --- a/src/pages/docs/projects/variables/variable-substitutions.mdx +++ b/src/pages/docs/projects/variables/variable-substitutions.mdx @@ -14,9 +14,9 @@ imageAlt: --- import VariableFiltersInOctostacheConditionals from 'src/shared-content/projects/deployment-process/variable-filters-in-octostache-conditionals.include.md'; -Octopus lets you reference variables in scripts, configuration files, and step settings, using a binding syntax powered by [Octostache](https://github.com/OctopusDeploy/Octostache), Octopus's open-source templating engine. +Octopus lets you reference variables in scripts, configuration files, and step settings, using a binding syntax powered by [Octostache](https://github.com/OctopusDeploy/Octostache), Octopus's open-source templating engine. -This page explains that syntax: how Octopus binds and evaluates a reference, how to compose a value from other variables, how to work with collections and conditionals, and how to reshape a value with a filter. +This page explains that syntax: how Octopus binds and evaluates a reference, how to compose a value from other variables, how to work with collections and conditionals, and how to reshape a value with a filter. For the built-in variables you can reference, see [System variables](/docs/projects/variables/system-variables). @@ -26,11 +26,11 @@ Variable substitution is how Octopus replaces a reference in your text with the You reference a variable by wrapping its name in `#{` and `}`: -``` +```text #{VariableName} ``` -When Octopus runs the step, it evaluates the reference and replaces it with the variable's value. **Every value is a string**, even when it looks like a number or a boolean, because Octopus stores and substitutes all variables as text. +When Octopus runs the step, it evaluates the reference and replaces it with the variable's value. **Every value is a string**, even when it looks like a number or a boolean, because Octopus stores and substitutes all variables as text. Octopus resolves each reference using the most specifically [scoped](/docs/projects/variables/getting-started/#scoping-variables) value that applies to the current deployment, so the same reference can produce a different value in Development than it does in Production. @@ -67,13 +67,13 @@ Escaping compounds with the length of the hash sequence. Given the variable `Nam A variable reference names the value you want Octopus to substitute, ranging from a plain variable name to a specific value selected out of a structured object. You can combine a reference with surrounding text to compose a new value: -``` +```text #{Octopus.Environment.Name}-#{Octopus.Project.Name} ``` Some variables are structured, and you select a value from them using indexer notation, where the key inside the square brackets identifies the entry you want. Many system variables use this form to expose per-action, per-machine, or per-package values. For example, this reference selects the target roles for a specific action: -``` +```text #{Octopus.Action[Website].TargetRoles} ``` @@ -90,7 +90,7 @@ The index itself can be another variable reference, which lets you look up a val Output variables extend indexer notation with a second index for the machine that produced the value. An [output variable](/docs/projects/variables/output-variables) set in one step is available to later steps using the action name and the machine name: -``` +```text #{Octopus.Action[Website].Output[WEBSVR01].Package.InstallationDirectoryPath} ``` @@ -100,7 +100,7 @@ A collection variable holds many entries rather than a single value. Several sys You iterate over a collection with an `#{each}` block. Octopus repeats the content between `#{each}` and `#{/each}` once per entry, binding each entry to the name you choose so you can reference its properties: -``` +```text #{each package in Octopus.Release.Package} This release contains #{package.PackageId} #{package.Version} #{/each} @@ -108,7 +108,7 @@ You iterate over a collection with an `#{each}` block. Octopus repeats the conte Collections can be nested. An entry in one collection can itself hold a collection, which you iterate the same way: -``` +```text #{each package in Octopus.Release.Package} #{each commit in package.Commits} - #{commit.CommitId}: #{commit.Comment} @@ -118,13 +118,13 @@ Collections can be nested. An entry in one collection can itself hold a collecti You can also select a single entry directly using indexer notation. Some collections are keyed by an identifier, such as a package ID: -``` +```text #{Octopus.Release.Package[Acme.Web].Version} ``` Others are keyed by a zero-based integer index, so the first entry is at index 0: -``` +```text #{Octopus.Release.Builds[0].BuildUrl} ``` @@ -137,7 +137,7 @@ A collection doesn't have to come from a system variable. You can define your ow | `Endpoint[B].Address` | `http://b.example.com` | | `Endpoint[B].Description` | `Replica` | -``` +```text #{each endpoint in Endpoint} - #{endpoint} at #{endpoint.Address} is #{endpoint.Description} #{/each} @@ -145,7 +145,7 @@ A collection doesn't have to come from a system variable. You can define your ow A variable containing a comma-separated list also iterates directly, without needing an index at all: -``` +```text #{each endpoint in "http://a.example.com,http://b.example.com"} - #{endpoint} #{/each} @@ -157,7 +157,7 @@ Octostache parses a JSON-formatted variable value natively, exposing its propert A JSON array or object iterates using the same `#{each}` syntax as any other collection: -``` +```text #{each item in Custom.MyJson.Sizes} - #{item.size}: #{item.price} #{/each} @@ -177,7 +177,7 @@ Inside an `#{each}` block, Octopus makes a set of variables available that repor Given the variable `Endpoints` with the comma-separated value `SV1,SV2,SV3`, the following template prints a marker on only the first and last entries: -``` +```text #{each endpoint in Endpoints} #{if Octopus.Template.Each.First}First: #{endpoint}#{/if} #{if Octopus.Template.Each.Last}Last: #{endpoint}#{/if} @@ -186,7 +186,7 @@ Given the variable `Endpoints` with the comma-separated value `SV1,SV2,SV3`, the This produces: -``` +```text First: SV1 Last: SV3 ``` @@ -215,14 +215,14 @@ A conditional includes or excludes text based on a variable's value, letting you Two conditional statements are supported: `if`, which evaluates its content when the variable is truthy, and `unless`, which evaluates when the variable is falsy. A value is falsy if it's undefined, an empty string, or (ignoring case and surrounding whitespace) `False`, `No`, or `0`; every other value is truthy. -``` +```text #{if VariableName}conditional statements#{/if} #{unless VariableName}conditional statements#{/unless} ``` An `#{if}` block can include an `#{else}` branch for the case where the condition doesn't hold: -``` +```text #{if Octopus.Environment.Name == "Production"} ProductionValue #{else} @@ -236,7 +236,7 @@ Beyond a truthy/falsy check, a conditional can compare values directly using `== Combining `if` and `else` lets you express an effective OR across more than two cases: -``` +```text #{if Octopus.Environment.Name == "Development"} Do this if it's Development #{else} @@ -258,7 +258,7 @@ A conditional expression can also control whether a step in a deployment process A filter transforms a variable's value as Octopus substitutes it, so you can reshape a value for its destination without changing the underlying variable. Apply a filter with a pipe after the variable name: -``` +```text #{Octopus.Release.Notes | Markdown} ``` diff --git a/src/pages/docs/releases/deployment-changes.md b/src/pages/docs/releases/deployment-changes.md index b362c46a29..ac19ef4bcc 100644 --- a/src/pages/docs/releases/deployment-changes.md +++ b/src/pages/docs/releases/deployment-changes.md @@ -9,7 +9,7 @@ navOrder: 130 When a release is deployed to an environment, you can think of this as deploying all changes contained in all releases since the previous deployment to the environment. -Deployment changes summarize these changes by rolling up the [release notes](/docs/releases/release-notes) from each release. +Deployment changes summarize these changes by rolling up the [release notes](/docs/releases/release-notes) from each release. :::figure ![Deployment changes](/docs/img/releases/images/deployment-notes.png) @@ -32,7 +32,7 @@ We strongly recommend using pre-release versions for releases that aren't intend It can be useful to access the changes associated with a deployment in the deployment process. [Deployment change variables](/docs/projects/variables/system-variables/#deployment-changes) are available during a deployment. -A common usage of this is in the [email step](/docs/projects/built-in-step-templates/email-notifications). +A common usage of this is in the [email step](/docs/projects/built-in-step-templates/email-notifications). :::figure ![Deployment notes variables in email step](/docs/img/releases/images/deployment-notes-email-step.png) @@ -40,7 +40,7 @@ A common usage of this is in the [email step](/docs/projects/built-in-step-templ In scenarios where you want to use release notes templates and email steps together, there can be some complications depending on the layout of the email content you need. The easiest option is to use the release notes directly from the releases: -``` +```text Deployment contained releases:
#{each change in Octopus.Deployment.Changes}

#{change.Version}

@@ -51,7 +51,7 @@ Deployment contained releases:
This outputs the details per release, with the work items appearing per release. If you wanted a single set of release notes, with a list of work items below it, you'd have to omit the work item details from the release notes template itself and use an email body like follows: -``` +```text Deployment contained releases:
#{each change in Octopus.Deployment.Changes}

#{change.Version}

@@ -74,7 +74,7 @@ Starting from **Octopus 2019.9** you can specify a template for deployment chang The output is also available for use during a deployment by using the `Octopus.Deployment.ChangesMarkdown` variable. The content of the email above could be used as the deployment changes template for the project, and the email step could be simplified to: -``` +```text Deployment contained releases:
#{Octopus.Deployment.ChangesMarkdown | MarkdownToHtml} ``` @@ -87,7 +87,7 @@ All environment and tenant related variables are available to the template. The default template, when the field in the project settings is left blank, is as follows: -``` +```text #{each release in Octopus.Deployment.Changes} **Release #{release.Version}** @@ -100,11 +100,11 @@ The default template, when the field in the project settings is left blank, is a ### Showing only certain changes -There might be instances where you want to customize the deployment notes such that they only show the last change deployed. +There might be instances where you want to customize the deployment notes such that they only show the last change deployed. The following template only shows the most recent change being deployed, including release, package and commit information: -``` +```text #{each change in Octopus.Deployment.Changes} #{if Octopus.Template.Each.Last == "True"} *Release #{change.Version}* @@ -135,9 +135,8 @@ This is achieved using the Octopus [loop iteration variables](https://octopus.co The `Octopus.Deployment.Targets` variable is available to the deployment changes template, but it is not available during the deployment. It contains a dictionary of `ID` and `Name` values for all targets in the scope of the deployment (keyed by ID). You can iterate over the targets in a template as follows -``` +```text #{each target in Octopus.Deployment.Targets} - #{target.Name} #{/each} ``` - From c0d597c4df6986dabbbe65729047e81fe229e0e5 Mon Sep 17 00:00:00 2001 From: Sam Irons Date: Thu, 6 Aug 2026 15:42:56 +1000 Subject: [PATCH 3/3] added back an anchor link that short.io is using --- src/pages/docs/projects/variables/variable-substitutions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/docs/projects/variables/variable-substitutions.mdx b/src/pages/docs/projects/variables/variable-substitutions.mdx index a6b4591996..fcf2fa588a 100644 --- a/src/pages/docs/projects/variables/variable-substitutions.mdx +++ b/src/pages/docs/projects/variables/variable-substitutions.mdx @@ -96,7 +96,7 @@ Output variables extend indexer notation with a second index for the machine tha ## Collection variables -A collection variable holds many entries rather than a single value. Several system variables are collections, including the packages, builds, commits, and work items associated with a release, and the changes included in a deployment; see [System variables](/docs/projects/variables/system-variables) for the full list. Knowing how to read a collection lets you work with all of them, because they share the same iteration and indexing syntax. +A collection variable holds many entries rather than a single value. Several system variables are collections, including the packages, builds, commits, and work items associated with a release, and the changes included in a deployment; see [System variables](/docs/projects/variables/system-variables) for the full list. Knowing how to read a collection lets you work with all of them, because they share the same iteration and indexing syntax. You iterate over a collection with an `#{each}` block. Octopus repeats the content between `#{each}` and `#{/each}` once per entry, binding each entry to the name you choose so you can reference its properties: