New files: - PLATFORMHUB.md: index for Platform Hub guidance - platformhub/architecture.md: Git→PlatformHub→Projects model - platformhub/ocl-syntax.md: complete OCL reference for templates - platformhub/process-template-patterns.md: 5 proven patterns - platformhub/gotchas.md: every error hit and how to fix it - platformhub/api-reference.md: API vs UI capabilities - best-practices/octopus-process-templates.md: consolidated best practices Learned from building PlatformHub-Demo (30 microservices, 3 clouds). Key discoveries: step templates are space-scoped (can't cross-reference), worker_pool parameter is mandatory, publishing/sharing is UI-only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.3 KiB
OCL Syntax Reference for Process Templates
Grammar Rules
OCL (Octopus Configuration Language) follows a strict format. The EBNF grammar is defined at https://github.com/OctopusDeploy/Ocl.
Core Rules
- Name,
=, and value must be on the same line - Block name, labels, and
{must be on the same line - Closing
}must be on its own line (except empty blocks likevalue "default" {}) - Strings use double quotes, cannot contain unescaped
" - Multi-line strings use heredoc:
<<-EOT/EOT - Arrays use
["item1", "item2"] - Dictionaries use
{ key = "value" }(one entry per line inside braces)
Name Restrictions
Template and step names can only contain: letters, numbers, periods, commas, dashes, underscores, hashes.
Invalid: Run Security Scan (SAST/SCA) — parentheses and slashes not allowed
Valid: Run Security Scan - SAST and SCA
Process Template File Structure
name = "Template Display Name"
description = "What this template does"
# Parameters (consumed by projects, referenced in steps)
parameter "param_name" {
display_settings = {
Octopus.ControlType = "SingleLineText"
}
help_text = "Description for UI"
label = "Display Label"
value "default_value" {} # Optional default
}
# Worker pool parameter (REQUIRED — every template needs this)
parameter "worker_pool" {
display_settings = {
Octopus.ControlType = "WorkerPool"
}
help_text = "Worker pool for server-side steps"
label = "Worker Pool"
}
# Steps (executed in order)
step "step-slug" {
name = "Step Display Name"
start_trigger = "StartAfterPrevious" # or "StartWithPrevious"
action {
action_type = "Octopus.Script"
properties = {
Octopus.Action.RunOnServer = "true"
Octopus.Action.Script.ScriptSource = "Inline"
Octopus.Action.Script.Syntax = "PowerShell"
Octopus.Action.Script.ScriptBody = <<-EOT
Write-Host "Hello from #{Octopus.Project.Name}"
EOT
OctopusUseBundledTooling = "False"
}
worker_pool_variable = "worker_pool"
}
}
Parameter Types
| Type | Octopus.ControlType |
Can Default | Notes |
|---|---|---|---|
| Single-line text | SingleLineText |
Yes | Most common for string values |
| Multi-line text | MultiLineText |
Yes | For scripts, YAML, JSON |
| Sensitive | Sensitive |
Yes | Encrypted in DB |
| Checkbox | Checkbox |
Yes | Boolean |
| Dropdown | Select |
Yes | Predefined options |
| Target Tags | TargetTags |
No | For Kubernetes/deployment targets |
| Worker Pool | WorkerPool |
No | Required on every template |
| Package | (package) | No | Container image references |
| Environments | (environments) | No | |
| Channels | (channels) | No |
Default Values
# String default
parameter "cloud_target" {
display_settings = { Octopus.ControlType = "SingleLineText" }
help_text = "Cloud provider"
label = "Cloud Target"
value "gcp" {} # Default value in empty block
}
# No default (must be set by consuming project)
parameter "target_tags" {
display_settings = { Octopus.ControlType = "TargetTags" }
help_text = "K8s target tags"
label = "Target Tags"
}
Step Structure
Inline Script Step (most common in process templates)
step "step-slug" {
name = "Display Name"
start_trigger = "StartAfterPrevious"
action {
action_type = "Octopus.Script"
properties = {
Octopus.Action.RunOnServer = "true"
Octopus.Action.Script.ScriptSource = "Inline"
Octopus.Action.Script.Syntax = "PowerShell" # or "Bash"
Octopus.Action.Script.ScriptBody = <<-EOT
# PowerShell script here
$project = $OctopusParameters['Octopus.Project.Name']
Write-Host "Deploying $project"
EOT
OctopusUseBundledTooling = "False"
}
worker_pool_variable = "worker_pool"
}
}
Deployment Target Step (runs on targets, not server)
step "deploy-step" {
name = "Deploy to K8s"
start_trigger = "StartAfterPrevious"
properties = {
Octopus.Action.TargetRoles = "#{target_tags}" # References parameter
}
action {
action_type = "Octopus.Script"
properties = {
Octopus.Action.RunOnServer = "false" # Runs on target
Octopus.Action.Script.ScriptSource = "Inline"
Octopus.Action.Script.Syntax = "PowerShell"
Octopus.Action.Script.ScriptBody = <<-EOT
Write-Host "Deploying to target"
EOT
OctopusUseBundledTooling = "False"
}
worker_pool_variable = "worker_pool"
}
}
Kubernetes Raw YAML Step
step "k8s-deploy" {
name = "Apply Kubernetes YAML"
properties = {
Octopus.Action.TargetRoles = "#{target_tags}"
}
action {
action_type = "Octopus.KubernetesDeployRawYaml"
properties = {
Octopus.Action.GitRepository.FilePathFilters = "#{{yaml_path}}"
Octopus.Action.GitRepository.Source = "External"
Octopus.Action.Kubernetes.DeploymentTimeout = "180"
Octopus.Action.Kubernetes.ResourceStatusCheck = "True"
Octopus.Action.Kubernetes.ServerSideApply.Enabled = "True"
Octopus.Action.Kubernetes.ServerSideApply.ForceConflicts = "True"
Octopus.Action.KubernetesContainers.Namespace = "#{namespace}"
Octopus.Action.Script.ScriptSource = "GitRepository"
}
worker_pool_variable = "worker_pool"
git_dependencies {
default_branch = "main"
file_path_filters = ["#{yaml_path}"]
git_credential_type = "Anonymous"
repository_uri = "#{repo_url}"
}
}
}
Referencing Variables
Octopus System Variables
Use $OctopusParameters['Variable.Name'] in PowerShell scripts:
$project = $OctopusParameters['Octopus.Project.Name']
$release = $OctopusParameters['Octopus.Release.Number']
$env = $OctopusParameters['Octopus.Environment.Name']
$channel = $OctopusParameters['Octopus.Release.Channel.Name']
Template Parameters
In OCL properties (outside scripts): #{parameter_name}
In PowerShell scripts: $OctopusParameters['parameter_name']
# In OCL properties
Octopus.Action.TargetRoles = "#{target_tags}"
# In script body
$cloudTarget = $OctopusParameters['cloud_target']
Double-bracing escape: In some OCL properties, use #{{param}} to escape Octopus variable substitution:
Octopus.Action.GitRepository.FilePathFilters = "#{{yaml_path}}"
Output Variables
Set output variables from scripts for use in later steps:
Write-Host "##octopus[setVariable name='CoveragePercent' value='87']"
Read in subsequent steps:
$coverage = $OctopusParameters['Octopus.Action[run-unit-tests].Output.CoveragePercent']
Channel Scoping
Scope steps to specific channels (for dual-image patterns):
action {
action_type = "Octopus.Script"
channels = ["non-prod"] # Only runs in this channel
properties = { ... }
}
Environment Scoping
action {
environments = ["production"] # Only in prod
excluded_environments = ["development"] # Not in dev
}
Step Properties Reference
| Property | Type | Values | Default |
|---|---|---|---|
step.condition |
enum | Success, Failure, Always, Variable |
Success |
step.start_trigger |
enum | StartAfterPrevious, StartWithPrevious |
StartAfterPrevious |
step.package_requirement |
enum | LetOctopusDecide, BeforePackageAcquisition, AfterPackageAcquisition |
LetOctopusDecide |
action.channels |
string[] | channel slugs | all |
action.environments |
string[] | environment slugs | all |
action.excluded_environments |
string[] | environment slugs | none |
action.is_disabled |
bool | False |
|
action.is_required |
bool | False |
|
action.notes |
string | step description | |
action.worker_pool |
string | worker pool slug | |
action.worker_pool_variable |
string | variable name |