Add Octopus Deploy Platform Hub knowledge base

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>
This commit is contained in:
Paul O'Reilly
2026-03-25 09:54:09 +13:00
parent 752fdfd82e
commit 6c0f2db169
8 changed files with 1116 additions and 0 deletions

View File

@@ -20,3 +20,5 @@ Generalised best practices extracted from real project work. Each topic file is
- [Test-Driven Development](best-practices/test-driven-development.md) — Edge case discovery, property-based testing, mutation testing, AI agent testing patterns, test architecture
- [Networking & Infrastructure](best-practices/networking.md) — nftables safety, systemd socket activation, Docker forwarding, TLS SNI vs Host header, wildcard certs
- [Docker UID Matching](best-practices/docker-uid-matching.md) — UID wrapper entrypoint for mounted volumes, gosu pattern, when to use vs K8s securityContext
- [Octopus Process Templates](best-practices/octopus-process-templates.md) — OCL syntax, step template references, channel scoping, parameters, versioning, Platform Hub patterns
- [Platform Hub Knowledge Base](PLATFORMHUB.md) — Comprehensive guide: architecture, OCL syntax, template patterns, gotchas, API reference

11
PLATFORMHUB.md Normal file
View File

@@ -0,0 +1,11 @@
# Octopus Deploy Platform Hub — Knowledge Base
Practical guidance for working with Octopus Deploy's Platform Hub, learned from building the PlatformHub-Demo project (a mock 30-microservice FinTech marketplace). This is a thin index — detail lives in `platformhub/`.
## Topics
- [Architecture Overview](platformhub/architecture.md) — How Platform Hub fits together: Git repo, OCL files, spaces, publishing
- [OCL Syntax Reference](platformhub/ocl-syntax.md) — Complete OCL format guide with examples for process templates
- [Process Template Patterns](platformhub/process-template-patterns.md) — Proven patterns for different deployment types (Go, Python, Node.js, Payment, Database)
- [Gotchas and Troubleshooting](platformhub/gotchas.md) — Every error we hit and how to fix it
- [API Reference](platformhub/api-reference.md) — What can and cannot be done via API vs UI

View File

@@ -0,0 +1,221 @@
# Octopus Deploy Process Templates
Best practices for creating and managing Octopus Deploy process templates using OCL (Octopus Configuration Language) in Platform Hub.
## Key Concepts
- **Step templates** (action templates) are reusable individual steps, created via API or UI, stored in a space's library
- **Process templates** are reusable multi-step deployment processes, stored as OCL files in Platform Hub's Git repo
- **Project templates** compose process templates into full project configurations (feature coming soon)
- Process templates live in `.octopus/process-templates/<slug>.ocl` in the Platform Hub Git repo
- Projects consume process templates via `process_template` blocks in their `deployment_process.ocl`
## OCL File Structure
### Process Template File
```hcl
name = "Deploy to Kubernetes - Helm"
description = "Standard Helm-based Kubernetes deployment with pre-validation and smoke tests"
# Parameters — values supplied by consuming projects
parameter "target_tags" {
display_settings = {
Octopus.ControlType = "TargetTags"
}
help_text = "Kubernetes target tags"
label = "Target Tags"
}
parameter "cloud_target" {
display_settings = {
Octopus.ControlType = "SingleLineText"
}
help_text = "Cloud provider (gcp, aws, azure)"
label = "Cloud Target"
value "gcp" {} # default value
}
# Steps — ordered deployment steps
step "deploy-helm" {
name = "Deploy via Helm"
properties = {
Octopus.Action.TargetRoles = "#{target_tags}"
}
action {
action_type = "Octopus.Script"
properties = {
Octopus.Action.Script.ScriptSource = "Inline"
Octopus.Action.Script.Syntax = "PowerShell"
Octopus.Action.Script.ScriptBody = "Write-Host 'Deploying...'"
}
worker_pool_variable = ""
}
}
```
### How Projects Consume Process Templates
In a project's `deployment_process.ocl`:
```hcl
process_template "deploy-app" {
name = "Deploy Application"
process_template_slug = "deploy-to-kubernetes-helm"
version_mask = "1.X" # auto-update minor/patch
parameter "target_tags" {
value = "kubernetes,production"
}
parameter "cloud_target" {
value = "aws"
}
}
```
## OCL Syntax Rules
From the EBNF grammar (https://github.com/OctopusDeploy/Ocl):
- **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 like `value "default" {}`)
- **Strings** use double quotes, cannot contain unescaped `"`
- **Multi-line strings** use heredoc: `<<-EOF` / `EOF` (indented variant)
- **Arrays** use `["item1", "item2"]`
- **Dictionaries** use `{ key = value }` (one entry per line inside braces)
## Referencing Step Templates
Step templates are referenced by **ID and version** in the action's properties, NOT by name:
```hcl
step "run-tests" {
name = "Run Unit Tests"
action {
# Reference a step template instead of action_type
properties = {
Octopus.Action.Template.Id = "ActionTemplates-62"
Octopus.Action.Template.Version = "1"
# Step template parameter values
Language = "go"
CloudTarget = "gcp"
}
worker_pool_variable = ""
}
}
```
When NOT using a step template, define `action_type` directly:
```hcl
action {
action_type = "Octopus.Script"
properties = { ... }
}
```
## Channel Scoping
Scope steps to specific channels using the `channels` attribute on the action block. Uses **channel slugs** (auto-generated from names):
```hcl
action {
action_type = "Octopus.Script"
channels = ["non-prod"] # only runs in non-prod channel
properties = { ... }
}
```
## Package References
```hcl
# Container image for worker execution
container {
feed = "registered-dockerhub" # feed slug, not ID
image = "octopusdeploy/worker-tools:ubuntu.22.04"
}
# Package reference in a step
packages "MyPackage" {
acquisition_location = "NotAcquired" # Server | ExecutionTarget | NotAcquired
feed = "platformhub-non-prod" # feed slug
package_id = "paul-oreilly-octopus/nonprod/listing-service"
properties = {
SelectionMode = "immediate"
}
}
```
## Parameter Types
| Control Type | `Octopus.ControlType` Value | Can Have Default |
|---|---|---|
| Single-line text | `SingleLineText` | Yes |
| Multi-line text | `MultiLineText` | Yes |
| Sensitive/password | `Sensitive` | Yes (encrypted) |
| Checkbox | `Checkbox` | Yes |
| Dropdown | `Select` | Yes |
| AWS/Azure/GCP Account | `AWSAccount` etc. | Yes |
| Worker Pool | (worker pool) | No |
| Package | (package) | No |
| Target Tags | `TargetTags` | No |
| Environments | (environments) | No |
| Channels | (channels) | No |
## 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 | |
## Versioning
- Process templates use **semantic versioning** (major.minor.patch)
- `version_mask = "1.X"` in consuming projects auto-updates on minor/patch changes
- **Major version bumps** require explicit upgrade by consuming projects
- Keep major bumps for breaking changes (parameter renames, step removals)
- Minor/patch for new optional parameters, script improvements, bug fixes
## Gotchas
1. **Step templates are space-scoped.** Process templates in Platform Hub cannot reference `ActionTemplates-*` IDs from other spaces. If you need reusable steps, use inline `action_type = "Octopus.Script"` in the process template OCL. Step templates are useful within a single space's projects, but not for cross-space process templates.
2. **Process template names** cannot contain parentheses, slashes, or ampersands — only letters, numbers, periods, commas, dashes, underscores, and hashes.
3. **Heredoc for multi-line scripts** — use `<<-EOT` / `EOT` for PowerShell scripts that contain double quotes. The `-` prefix allows indented closing tags.
4. **Every step needs a worker pool.** Process templates must have a `worker_pool` parameter (type `WorkerPool`), and every action must set `worker_pool_variable = "worker_pool"` referencing it. Without this, the template will fail to parse with "A step must specify a worker pool parameter".
5. **Publishing and sharing is UI-only.** Process template sharing (which spaces can see/use a template) is stored in the Octopus database, not in Git/OCL. You must publish and share each template through the UI. There is no API or CLI for this currently.
## Best Practices
1. **One template per deployment pattern**, not per cloud. Use parameters to vary cloud-specific behaviour.
2. **Use step templates for reusable individual steps**, process templates for reusable multi-step workflows.
3. **Parameters should have sensible defaults** where possible — reduces friction for consuming projects.
4. **Use `TargetTags` parameter type** for Kubernetes target selection rather than hardcoding roles.
5. **Name templates with the action, not the technology**: "Deploy to Kubernetes" not "Helm Chart Deployer".
6. **Keep descriptions updated** — they appear in the UI when browsing templates.
7. **Process templates cannot reference the project's own Git repo** for scripts — use inline scripts or external URLs.
8. **Test templates** by creating a test project that consumes them before sharing widely.
## References
- OCL Syntax: https://octopus.com/docs/projects/version-control/ocl-file-format
- Config as Code Reference: https://octopus.com/docs/projects/version-control/config-as-code-reference
- Process Templates: https://octopus.com/docs/platform-hub/templates/process-templates
- Template Parameters: https://octopus.com/docs/platform-hub/templates/parameters
- Publishing & Sharing: https://octopus.com/docs/platform-hub/templates/publishing-and-sharing
- Best Practices: https://octopus.com/docs/platform-hub/templates/process-templates/best-practices
- Troubleshooting: https://octopus.com/docs/platform-hub/templates/process-templates/troubleshooting
- OCL Grammar (EBNF): https://github.com/OctopusDeploy/Ocl

View File

@@ -0,0 +1,189 @@
# Platform Hub API Reference
What can and cannot be managed via the Octopus Deploy API versus the UI.
## API-Manageable Resources
These can be created, read, updated, and deleted via the REST API:
| Resource | Endpoint | Notes |
|----------|----------|-------|
| Spaces | `POST /api/spaces` | Requires `SpaceManagersTeamMembers` |
| Environments | `POST /api/{SpaceId}/environments` | |
| Lifecycles | `POST /api/{SpaceId}/lifecycles` | Include phase definitions |
| Project Groups | `POST /api/{SpaceId}/projectgroups` | |
| Docker Feeds | `POST /api/{SpaceId}/feeds` | `FeedType: "Docker"` |
| Step Templates | `POST /api/{SpaceId}/actiontemplates` | Space-scoped |
| Projects | `POST /api/{SpaceId}/projects` | |
| Channels | `POST /api/{SpaceId}/channels` | With lifecycle assignment |
| Releases | `POST /api/{SpaceId}/releases` | With channel and package selection |
| Runbooks | `POST /api/{SpaceId}/runbooks` | |
| Deployment Processes | `GET/PUT /api/{SpaceId}/deploymentprocesses/{id}` | |
| Variables | `GET/PUT /api/{SpaceId}/variables/{id}` | |
## UI-Only Operations
These **cannot** be done via API or CLI — must use the Octopus web UI:
| Operation | Why |
|-----------|-----|
| **Publish process template** (create version) | Platform Hub feature, no API endpoint |
| **Share process template** with spaces | Platform Hub feature, no API endpoint |
| **Configure Platform Hub Git connection** | Instance-level setting |
| **Convert project to Config as Code** | `POST .../git/convert` exists but requires Git credentials interactively |
## Common API Patterns
### Authentication
All API calls require the `X-Octopus-ApiKey` header:
```bash
API_KEY=$(grep 'value:' ~/dev/claude/secrets/taniwha.octopus.app/api_key | awk '{print $2}')
curl -s -H "X-Octopus-ApiKey: $API_KEY" \
"https://taniwha.octopus.app/api/Spaces-103/environments"
```
### Create a Space
```bash
curl -s -H "X-Octopus-ApiKey: $API_KEY" \
-H "Content-Type: application/json" \
-X POST "https://taniwha.octopus.app/api/spaces" \
-d '{
"Name": "My Space",
"SpaceManagersTeamMembers": ["Users-21"],
"IsDefault": false
}'
```
Note: `SpaceManagersTeamMembers` must not be empty — at least one user or team required.
### Create Environments
```bash
for env in Development Staging Production; do
curl -s -H "X-Octopus-ApiKey: $API_KEY" \
-H "Content-Type: application/json" \
-X POST "https://taniwha.octopus.app/api/$SPACE_ID/environments" \
-d "{\"Name\": \"$env\"}"
done
```
### Create Lifecycles with Auto-Deploy Phases
```bash
# Non-Prod: Dev → Staging (auto-deploy both)
curl -s -H "X-Octopus-ApiKey: $API_KEY" \
-H "Content-Type: application/json" \
-X POST "https://taniwha.octopus.app/api/$SPACE_ID/lifecycles" \
-d '{
"Name": "Non-Prod",
"Phases": [
{
"Name": "Development",
"AutomaticDeploymentTargets": ["Environments-104"],
"OptionalDeploymentTargets": [],
"MinimumEnvironmentsBeforePromotion": 0,
"IsOptionalPhase": false
},
{
"Name": "Staging",
"AutomaticDeploymentTargets": ["Environments-105"],
"OptionalDeploymentTargets": [],
"MinimumEnvironmentsBeforePromotion": 0,
"IsOptionalPhase": false
}
]
}'
```
### Create Docker Feed (GHCR)
```bash
curl -s -H "X-Octopus-ApiKey: $API_KEY" \
-H "Content-Type: application/json" \
-X POST "https://taniwha.octopus.app/api/$SPACE_ID/feeds" \
-d '{
"FeedType": "Docker",
"Name": "GHCR Non-Prod",
"FeedUri": "https://ghcr.io"
}'
```
### Create Step Template (Action Template)
```bash
curl -s -H "X-Octopus-ApiKey: $API_KEY" \
-H "Content-Type: application/json" \
-X POST "https://taniwha.octopus.app/api/$SPACE_ID/actiontemplates" \
-d '{
"Name": "Run Unit Tests",
"Description": "Language-specific unit test suite",
"ActionType": "Octopus.Script",
"Properties": {
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "PowerShell",
"Octopus.Action.Script.ScriptBody": "Write-Host \"Running tests...\""
},
"Parameters": [
{
"Name": "Language",
"Label": "Programming Language",
"DefaultValue": "go",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
}
]
}'
```
### Create Release via API (CI-driven)
```bash
curl -s -X POST \
-H "X-Octopus-ApiKey: $API_KEY" \
-H "Content-Type: application/json" \
"https://taniwha.octopus.app/api/$SPACE_ID/releases" \
-d '{
"ProjectId": "Projects-XXX",
"ChannelId": "Channels-XXX",
"Version": "1.2.3+202603251530",
"SelectedPackages": [{
"ActionName": "Deploy Non-Prod Image",
"PackageReferenceName": "service-image",
"Version": "1.2.3+202603251530"
}]
}'
```
## Idempotent Script Pattern
For automation scripts that may be re-run:
```bash
# Check if resource exists before creating
existing=$(curl -s -H "X-Octopus-ApiKey: $API_KEY" \
"https://taniwha.octopus.app/api/$SPACE_ID/projectgroups?take=100" \
| python3 -c "
import sys, json
name = sys.argv[1]
for item in json.load(sys.stdin).get('Items', []):
if item['Name'] == name:
print(item['Id'])
break
" "Marketplace" 2>/dev/null || echo "")
if [[ -n "$existing" ]]; then
echo "Already exists: $existing"
else
# Create it
curl -s -X POST ...
fi
```
## References
- [Octopus REST API](https://octopus.com/docs/octopus-rest-api)
- [API Examples (GitHub)](https://github.com/OctopusDeploy/OctopusDeploy-Api)

119
platformhub/architecture.md Normal file
View File

@@ -0,0 +1,119 @@
# Platform Hub Architecture
## What Is Platform Hub?
Platform Hub is Octopus Deploy's mechanism for creating reusable deployment process templates that can be shared across spaces. It enables platform engineering teams to define standard deployment pipelines that project teams consume.
## Key Concepts
### The Three Layers
```
┌─────────────────────────────────────────────────────────────┐
│ Platform Hub Git Repo │
│ (e.g., github.com/org/taniwha.platform_hub) │
│ │
│ .octopus/process-templates/ │
│ ├── standard-go-service-deploy.ocl │
│ ├── python-ml-service-deploy.ocl │
│ ├── payment-service-deploy.ocl │
│ └── ... │
│ │
│ Defines: steps, parameters, scripts │
│ Stored in: Git (OCL files) │
└──────────────────────────┬──────────────────────────────────┘
│ Octopus reads OCL from Git
┌─────────────────────────────────────────────────────────────┐
│ Platform Hub (Octopus Instance) │
│ │
│ Publishing & Sharing: │
│ - Version management (semantic versioning) │
│ - Space visibility (which spaces can use each template) │
│ - Stored in: Octopus database (NOT Git) │
└──────────────────────────┬──────────────────────────────────┘
│ Projects reference templates
┌─────────────────────────────────────────────────────────────┐
│ Project (in any shared space) │
│ │
│ deployment_process.ocl: │
│ process_template "deploy-my-app" { │
│ process_template_slug = "standard-go-service-deploy" │
│ version_mask = "1.X" │
│ parameter "cloud_target" { value = "aws" } │
│ } │
│ │
│ Stored in: Project's Git repo (if CaC) or database │
└─────────────────────────────────────────────────────────────┘
```
### What Lives Where
| Artifact | Storage | Managed By |
|----------|---------|------------|
| Process template OCL (steps, scripts, parameters) | Git repo | Engineers via commits |
| Template versioning (major.minor.patch) | Octopus database | UI (publish action) |
| Template sharing (space visibility) | Octopus database | UI only — no API/CLI |
| Step templates (ActionTemplates) | Octopus database, per-space | API or UI |
| Project consumption of templates | Project Git repo (CaC) or DB | Engineers or API |
### Step Templates vs Process Templates
These are **different things** that complement each other:
| | Step Templates | Process Templates |
|---|---|---|
| **Scope** | Single step (one action) | Multi-step pipeline |
| **Storage** | Octopus DB, per-space | Git repo (OCL) |
| **Created via** | API or UI | Git commit |
| **Referenced by** | `Octopus.Action.Template.Id` | `process_template_slug` |
| **Cross-space** | No — space-scoped | Yes — via Platform Hub sharing |
| **Use in process templates** | Cannot reference cross-space | N/A |
**Critical:** Process templates in Platform Hub **cannot reference step templates from other spaces**. If you have reusable step templates in Space A, a process template in Platform Hub cannot use them. Instead, process templates must use inline `action_type` definitions.
## Git Repo Structure
The Platform Hub repo has a fixed structure that Octopus expects:
```
.octopus/
process-templates/
template-slug-name.ocl # One file per process template
another-template.ocl
README.md # Optional documentation
```
- Files must be in `.octopus/process-templates/`
- File extension must be `.ocl`
- The filename becomes the template slug (minus `.ocl`)
- Octopus reads from the `main` branch (configurable)
## Versioning and Updates
- Templates use **semantic versioning** (major.minor.patch)
- Publishing creates a new version
- Consuming projects use `version_mask` to control updates:
- `"1.X"` — auto-update on minor/patch, manual upgrade for major
- `"1.2.X"` — auto-update on patch only
- `"1.2.3"` — pinned to exact version
- **Major version bumps** are breaking changes: parameter renames, step removals
- **Minor/patch** for non-breaking: new optional params, script improvements, bug fixes
## Setting Up a New Platform Hub
1. Create a Git repo (GitHub, Gitea, etc.)
2. Create `.octopus/process-templates/` directory
3. Add OCL files (see [OCL Syntax Reference](ocl-syntax.md))
4. In Octopus: configure Platform Hub to point at the Git repo
5. Templates appear in the UI after Octopus reads the repo
6. Publish each template (UI) to create a version
7. Share each template with desired spaces (UI)
## References
- [Process Templates Docs](https://octopus.com/docs/platform-hub/templates/process-templates)
- [Template Parameters](https://octopus.com/docs/platform-hub/templates/parameters)
- [Publishing & Sharing](https://octopus.com/docs/platform-hub/templates/publishing-and-sharing)
- [Best Practices](https://octopus.com/docs/platform-hub/templates/process-templates/best-practices)

160
platformhub/gotchas.md Normal file
View File

@@ -0,0 +1,160 @@
# Platform Hub Gotchas and Troubleshooting
Every error encountered while building the PlatformHub-Demo project, and how to fix it.
## OCL Parse Errors
### "Action Template with ID 'ActionTemplates-XX' and version v0 specified in OCL was not found"
**Cause:** Process template references a step template (ActionTemplate) by ID, but step templates are space-scoped. Platform Hub operates at instance level and can't see step templates in individual spaces.
**Fix:** Don't reference step templates in process template OCL. Use inline `action_type` definitions instead:
```hcl
# WRONG — cross-space reference
action {
properties = {
Octopus.Action.Template.Id = "ActionTemplates-64"
Octopus.Action.Template.Version = "0"
}
}
# RIGHT — inline action
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 "Inline script here"
EOT
OctopusUseBundledTooling = "False"
}
worker_pool_variable = "worker_pool"
}
```
### "A step must specify a worker pool parameter"
**Cause:** Every step in a process template must have a worker pool, either directly or via a variable. The template itself must define a `WorkerPool` parameter.
**Fix:** Add a `worker_pool` parameter to the template and reference it from every action:
```hcl
# Add this parameter to the template
parameter "worker_pool" {
display_settings = {
Octopus.ControlType = "WorkerPool"
}
help_text = "Worker pool for server-side steps"
label = "Worker Pool"
}
# On EVERY action (both RunOnServer=true and RunOnServer=false):
action {
action_type = "Octopus.Script"
properties = { ... }
worker_pool_variable = "worker_pool" # Must reference the parameter
}
```
Note: `worker_pool_variable = ""` (empty string) will NOT work. It must reference an actual parameter name.
### "Name value contains invalid characters"
**Cause:** Template or step names contain characters outside the allowed set.
**Allowed:** Letters, numbers, periods, commas, dashes, underscores, hashes
**Not allowed:** Parentheses `()`, slashes `/`, ampersands `&`, colons `:`, at-signs `@`
```
# WRONG
name = "Run Security Scan (SAST/SCA)"
# RIGHT
name = "Run Security Scan - SAST and SCA"
```
## API and Scripting Issues
### Step template names created as numbers instead of text
**Cause:** The bash variable `GROUPS` is pre-set by bash completion scripts (contains numeric group IDs). Assigning to `GROUPS` in a script that sources other files will silently use the pre-existing value.
**Fix:** Use a unique variable name that won't collide with shell builtins or completion scripts:
```bash
# WRONG — GROUPS may already be set by bash completion
GROUPS=("Marketplace" "Payments" "Orders")
# RIGHT — use a unique name
PROJECT_GROUPS=("Marketplace" "Payments" "Orders")
```
### Action template (step template) creation: HTTP 400 with name errors
**Cause:** Same name restriction as process templates. The API enforces the same character rules.
### Octopus API POST with nested command substitution loses values
**Cause:** Nested `$(...)` in bash can mangle variable expansion. Avoid constructing JSON inside `$(octopus_post "/endpoint" "$(python3 ...)")`.
**Fix:** Build the JSON body in a separate variable first:
```bash
# WRONG — nested substitution
response=$(octopus_post "/projectgroups" "$(python3 -c "..." "$name")")
# RIGHT — build body first
body="{\"Name\": \"$name\"}"
response=$(octopus_post "/projectgroups" "$body")
```
## Publishing and Sharing
### Templates visible in Platform Hub but not in project spaces
**Cause:** Templates must be explicitly published (versioned) and shared with target spaces. Both operations are UI-only — there is no API or CLI for this.
**Fix:** For each template:
1. Go to Platform Hub in the Octopus UI
2. Click the template → Publish (creates a version)
3. Click Share → select target spaces → Save
### Template changes not reflected in consuming projects
**Cause:** Template versioning. Changes in Git are picked up by Platform Hub but won't affect projects until a new version is published. Projects with `version_mask = "1.X"` will auto-update on minor/patch publications.
## OCL Syntax Specifics
### Heredoc for multi-line scripts
PowerShell scripts with double quotes need heredoc syntax:
```hcl
Octopus.Action.Script.ScriptBody = <<-EOT
$name = $OctopusParameters['Octopus.Project.Name']
Write-Host "Deploying $name"
EOT
```
The `<<-EOT` variant (with dash) allows the closing `EOT` to be indented. Without the dash, `EOT` must be at column 0.
### Parameter references: single vs double braces
- `#{param_name}` — standard Octopus variable substitution (most properties)
- `#{{param_name}}` — escaped substitution (used in some properties like `GitRepository.FilePathFilters`)
### PowerShell variable access in scripts
Inside script bodies, use `$OctopusParameters['Variable.Name']`, not `#{Variable.Name}`:
```powershell
# In script body:
$project = $OctopusParameters['Octopus.Project.Name']
$cloud = $OctopusParameters['cloud_target']
# NOT this (only works in OCL properties, not script text):
# #{Octopus.Project.Name}
```

276
platformhub/ocl-syntax.md Normal file
View File

@@ -0,0 +1,276 @@
# 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 like `value "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
```hcl
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
```hcl
# 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)
```hcl
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)
```hcl
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
```hcl
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:
```powershell
$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']`
```hcl
# 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:
```hcl
Octopus.Action.GitRepository.FilePathFilters = "#{{yaml_path}}"
```
### Output Variables
Set output variables from scripts for use in later steps:
```powershell
Write-Host "##octopus[setVariable name='CoveragePercent' value='87']"
```
Read in subsequent steps:
```powershell
$coverage = $OctopusParameters['Octopus.Action[run-unit-tests].Output.CoveragePercent']
```
## Channel Scoping
Scope steps to specific channels (for dual-image patterns):
```hcl
action {
action_type = "Octopus.Script"
channels = ["non-prod"] # Only runs in this channel
properties = { ... }
}
```
## Environment Scoping
```hcl
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 | |
## References
- [OCL Format Specification](https://octopus.com/docs/projects/version-control/ocl-file-format)
- [Config as Code Reference](https://octopus.com/docs/projects/version-control/config-as-code-reference)
- [OCL Grammar (EBNF)](https://github.com/OctopusDeploy/Ocl)

View File

@@ -0,0 +1,138 @@
# Process Template Patterns
Proven patterns for different deployment types, extracted from the PlatformHub-Demo project (30 microservices, 3 clouds, 8 teams).
## Design Principles
1. **One template per deployment pattern**, not per cloud or per service. Use parameters to vary behaviour.
2. **Parameters for everything that varies** between consuming projects: cloud target, language, database type, target tags.
3. **Sensible defaults** on parameters to reduce friction — consuming projects only override what differs.
4. **Every template needs a `worker_pool` parameter** (type `WorkerPool`) — this is mandatory.
5. **Inline scripts, not step template references** — process templates can't see step templates in other spaces.
## Template Catalogue
### Standard Go Service Deploy
**Use for:** Go microservices without special compliance or database requirements.
**Steps:** Unit tests → Security scan → Container image scan → Helm deploy → Monitoring
**Parameters:** `target_tags`, `cloud_target` (default: gcp), `worker_pool`
**Services example:** listing-service, search-service, category-service, config-service, order-service
Key characteristics:
- Go test output with race detection and coverage
- Semgrep SAST + Trivy SCA
- Distroless base image scan + SBOM generation
- Helm upgrade with cluster mapping by cloud target
- Prometheus ServiceMonitor + Grafana dashboard + SLO alerting
### Python ML Service Deploy
**Use for:** Python services, especially ML/data pipeline services with model dependencies.
**Steps:** Unit tests → Integration tests → Security scan → Container image scan → Helm deploy → Monitoring
**Parameters:** `target_tags`, `cloud_target` (default: gcp), `worker_pool`
**Services example:** recommendation-service, fraud-service, moderation-service, ml-platform, analytics-pipeline
Key characteristics:
- pytest with coverage table
- Docker-compose integration tests (model validation, data pipeline tests)
- Python-specific SAST (Semgrep + Bandit) + SCA (Safety + pip-audit)
- GPU resource requests in Helm deploy
- ML-specific monitoring: inference latency, model drift, batch queue depth
### Payment Service Deploy
**Use for:** Payment-path services requiring PCI-DSS compliance and zero-downtime deployment.
**Steps:** Unit tests → Security scan → Container image scan → PCI-DSS check → Blue-green deploy → Monitoring → Rollback check
**Parameters:** `target_tags`, `cloud_target` (default: aws), `worker_pool`
**Services example:** payment-gateway, wallet-service, ledger-service, payout-service, escrow-service
Key characteristics:
- Enhanced security scan with Gitleaks secret detection
- 6-check PCI-DSS compliance gate (image signature, SBOM, secrets, TLS, network policy, audit logging)
- Blue-green deployment with canary traffic phases (10% → 100%)
- 4-nines SLO (99.99%), payment-specific alerting (circuit breaker, refund rate)
- Post-deploy health validation with automatic rollback decision
### Database Service Deploy
**Use for:** Services with database dependencies requiring schema migrations.
**Steps:** Unit tests → Security scan → Database migration → Container image scan → Helm deploy → Monitoring
**Parameters:** `target_tags`, `cloud_target` (default: gcp), `database_type` (default: postgres), `language` (default: go), `worker_pool`
**Services example:** user-service, ledger-service, order-service (any service with a DB)
Key characteristics:
- Language-aware unit tests (Go/Python/Node switch)
- SQL injection-focused security rules
- Pre-migration snapshot with cloud-appropriate tooling (pg_dump, mysqldump, mongodump)
- Versioned migration with per-migration timing
- Post-migration validation (schema version, constraint check, data integrity)
- DB-specific monitoring: connection pool, slow queries, migration version mismatch
### Node.js Service Deploy
**Use for:** Node.js services including API gateways, messaging, and notification services.
**Steps:** Unit tests → Security scan → Container image scan → Helm deploy → Feature flag seeding → Monitoring
**Parameters:** `target_tags`, `cloud_target` (default: azure), `worker_pool`
**Services example:** messaging-service, notification-service, api-gateway
Key characteristics:
- Jest-style test output with coverage table
- npm audit + Trivy SCA
- Feature flag seeding with environment-aware rollout percentages
- Cloud-specific secrets manager integration (Azure Key Vault / AWS SM / GCP SM)
- Node.js-specific monitoring: event loop lag, heap usage, WebSocket connections
## How Projects Consume Templates
In a project's `deployment_process.ocl`:
```hcl
process_template "deploy-listing-service" {
name = "Deploy Listing Service"
process_template_slug = "standard-go-service-deploy"
version_mask = "1.X" # Auto-update minor/patch
parameter "target_tags" {
value = "gke-marketplace"
}
parameter "cloud_target" {
value = "gcp"
}
parameter "worker_pool" {
value = "WorkerPools-1"
}
}
```
## Choosing the Right Template
```
Does the service handle payments or financial data?
└── YES → Payment Service Deploy
Does the service have database migrations?
└── YES → Database Service Deploy
Is the service Python-based (ML, data, analytics)?
└── YES → Python ML Service Deploy
Is the service Node.js-based?
└── YES → Node.js Service Deploy
Default → Standard Go Service Deploy
```
## Template-to-Service Mapping (PlatformHub-Demo)
| Template | Services |
|----------|----------|
| Standard Go Service | listing, search, search-indexer, category, media, config, event-bus, shipping, observability-collector, reporting, review, dispute |
| Python ML Service | recommendation, fraud, moderation, ml-platform, analytics-pipeline |
| Payment Service | payment-gateway, wallet, ledger, payout, escrow, kyc |
| Database Service | user, auth, user-activity, order (overlay with others) |
| Node.js Service | messaging, notification, api-gateway |
Note: Some services (like `order-service`) could use either Standard Go or Database Service depending on whether DB migrations are part of the deployment process.