Skip to content

Automation standard

Standard scope

Applies to: all HCS-governed repos, base rules; scope overlays noted inline where applicable Domain: automation Status: Active

All automation across HCS projects follows a pipeline-first model. If you run it more than once, it belongs in a pipeline.


Pipeline-first principle

Ad-hoc scripts run locally are fine for one-off operations. Any operation that:

  • Needs to run on a schedule
  • Needs to run on a trigger (push, PR, merge)
  • Touches Azure resources in a repeatable way
  • Produces artifacts that other systems consume
  • Needs an audit trail

...belongs in a pipeline. Script it, pipeline it, version it.


CI compute — which standard governs what

Three standards divide CI/CD compute along a strict boundary, and no tier table or runner label appears in more than one of them:

  • This standard (automation.md) governs pipeline design — when a pipeline runs, what triggers it, how it handles secrets, and what gates it needs. It does not name specific compute.
  • CI Runners governs the registered CI runner fleet — the GitHub Actions self-hosted runners and GitLab CI runners that pipelines defined here actually execute on.
  • Build Environments governs build hosts you invoke directly — WSL, bld-01 — for work that is not CI-registered at all.

If you're asking "what agent label do I put in my YAML," the answer is in CI Runners, not here.


ADO Pipelines vs. GitHub Actions

Use the right tool for the job:

Use ADO Pipelines forUse GitHub Actions for
Anything touching Azure (deploy, provision, configure)Repo-level CI: build, test, lint, publish
ADO-integrated workflows (work item updates, test plans)Publishing packages to NuGet, PyPI, PSGallery
Windows-agent tasks that need domain-joined runnersGitHub-native actions (dependabot, code scanning)
Pipelines that consume ADO Variable GroupsOpen-source repo workflows
Anything requiring approval gatesPR validation on public repos

In mixed scenarios (ADO project with a GitHub mirror), ADO runs the deployment pipeline and GitHub Actions runs CI. They do not overlap.


GitLab integration

This section is TierPoint-scope only — see the platform-boundary note in Governance. HCS-owned projects (CloudGrange, Turner Legacy, Azure Local, etc.) are GitHub/ADO; they never use GitLab, and nothing below applies to them.

GitLab repos live under tierpoint/prodtech (tp-* and others) for TierPoint client-facing work. GitLab CI pipelines target the HCS-tenant shared runner. See CI Runners for what that runner is, how it's registered, and its labels/tags. This avoids any GitLab premium tier dependency for CI.

For event-driven GitLab→ADO integration (when ADO deployment pipelines must be triggered by a GitLab push):

  1. GitLab webhook fires on push/merge to configured repos
  2. Webhook target is an Azure Function (consumption plan, free tier) in the HCS subscription
  3. The Azure Function translates the GitLab event into an ADO pipeline trigger via the ADO REST API
  4. ADO pipeline runs with the appropriate inputs

Idempotency

Every pipeline and every script called from a pipeline must be idempotent. That means:

  • Safe to re-run with the same inputs and produce the same outcome
  • No side effects that accumulate on re-run (no duplicate resources, no duplicate entries)
  • Existence checks before creates: check if the resource exists before attempting to create it
  • Upsert patterns over create-or-fail patterns

If a pipeline fails halfway through, re-running it should finish the job — not create a mess that requires manual cleanup.


Cloud authentication in pipelines

Use credentialless workload identity before retrieving a secret:

  • Azure: use a managed identity or an approved federated service connection.
  • AWS: the pipeline assumes a least-privilege IAM role through OIDC or another approved workload-federation mechanism. Do not create an IAM user's access key for a pipeline.
  • EC2 workloads: attach an IAM instance profile; never install AWS access keys on the host.
  • Human AWS administration: use IAM Identity Center through aws sso login. Human SSO sessions never run unattended inside a pipeline.

The AWS IAM Identity Center directory Region and the workload deployment Region are separate settings. Do not choose a deployment Region merely because it hosts the SSO directory.

Secret handling in pipelines

When a workload cannot use managed identity or federation, the accepted pipeline pattern is:

  1. The secret lives in the vault owned by the workload's scope.
  2. A KV-linked ADO Variable Group maps the secret to a pipeline variable name.
  3. The pipeline references the Variable Group.
  4. The pipeline consumes the variable as an environment variable or task input.
yaml
variables:
  - group: platform-prd-secrets

steps:
  - task: PowerShell@2
    inputs:
      filePath: scripts/Invoke-AuthenticatedOperation.ps1
      pwsh: true
    env:
      GITHUB_TOKEN: $(hcs-github-org-pat)

Never:

  • Inline secrets in pipeline YAML
  • Store secret values in pipeline variable definitions (non-KV-linked)
  • Echo or log secret values
  • Pass secrets as positional arguments to scripts (they appear in process lists)
  • Export temporary AWS SSO or assumed-role credentials into Key Vault or a variable group
  • Use a human IAM Identity Center session as a pipeline credential

Use named parameters or environment variables to pass secrets to scripts:

yaml
- task: PowerShell@2
  inputs:
    script: scripts/Deploy-Something.ps1
  env:
    GITHUB_TOKEN: $(hcs-github-org-pat)

Pipeline YAML location

ADO pipeline YAML lives under .ado/ in the repo root. This is the single correct location — there is no alternative directory. The live public documentation pipeline (.ado/docs-deploy.yml) is wired to this path, and every ADO pipeline definition in every HCS repo uses it.

  • ADO pipeline YAML lives under .ado/ in the repo root
  • GitHub Actions workflow YAML lives under .github/workflows/
  • Pipeline files are committed to the repo and version-controlled — no pipelines defined only in the UI
repo-root/
├── .ado/
│   ├── build.yml
│   └── deploy.yml
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── publish.yml

Manual trigger requirement

Every pipeline must have a manual trigger option in addition to any automatic triggers. In ADO YAML pipelines, workflow_dispatch equivalent is trigger: none + manual run capability, or a scheduled trigger with a manual override. In GitHub Actions, add workflow_dispatch: to every workflow.

yaml
# GitHub Actions
on:
  push:
    branches: [main]
  workflow_dispatch:       # always include this
yaml
# ADO
trigger:
  branches:
    include:
      - main
# Manual runs are available via the ADO UI on any pipeline — no extra config needed

Approval gates

Any pipeline that deploys to prd must have an approval gate before the deployment stage. Configure in ADO under Environments → Approvals and checks. Self-approval is acceptable for solo projects.


Multi-tool interoperability

Complex deployments combine multiple IaC tools. The rule: all tools read from the same config source. A single infrastructure.yml (or equivalent) drives Bicep parameter files, Terraform variable files, PowerShell scripts, and Ansible playbooks — no value is defined in more than one place.

infrastructure.yml           ← single source of truth

       ├── Bicep parameters  (generated by Export-BicepParams.ps1)
       ├── Terraform tfvars  (generated by Export-TerraformVars.ps1)
       ├── Ansible inventory (generated by Export-AnsibleInventory.ps1)
       └── PowerShell config (read directly at runtime)

Tool selection guidelines:

ScenarioPreferred tool
Azure resource provisioning (VMs, VNets, RGs, KVs)Bicep
Multi-cloud or state-heavy deploymentsTerraform
OS configuration, role installation, domain joinPowerShell (DSC or Invoke- scripts)
Configuration management at scaleAnsible
Hybrid: Azure + OS configurationBicep (Azure) + PowerShell (OS) — orchestrated by ADO pipeline

When combining tools, the ADO pipeline is the orchestrator. It runs Bicep first (Azure resources), then PowerShell (OS configuration), using outputs from Bicep (resource IDs, IPs) as inputs to PowerShell.

yaml
# ADO pipeline — hybrid deployment
stages:
  - stage: Provision
    jobs:
      - job: BicepDeploy
        steps:
          - task: AzureCLI@2
            inputs:
              scriptType: pscore
              scriptLocation: inlineScript
              inlineScript: |
                az deployment group create `
                  --resource-group rg-hcs-platform-prd-eus-01 `
                  --template-file infra/main.bicep `
                  --parameters @infra/parameters.json

  - stage: Configure
    dependsOn: Provision
    jobs:
      - job: OSConfig
        steps:
          - task: PowerShell@2
            inputs:
              filePath: scripts/Invoke-NodeConfiguration.ps1
              arguments: -ConfigPath config/variables.yml

Never split what belongs together. If two tools deploy to the same resource, there is an ownership conflict — resolve it by giving one tool ownership of that resource entirely.


Pipeline Notifications

When a pipeline needs to notify a user via email (such as a nightly report or discovery alert):

  • Do not use third-party email providers or hardcode SMTP credentials.
  • Deploy an Azure Logic App (using an HTTP Request trigger) with an Office 365 Outlook API connection.
  • Use Bicep to deploy/update the Logic App from within the pipeline run, outputting its trigger URL (Webhook URL).
  • Pass the Webhook URL to the script performing the work, and use Invoke-RestMethod to POST the payload.
  • Note: The API Connection must be authorized manually in the Azure Portal the first time it is deployed.

Scope overlays

The rules above are the base (hcs) automation standard: ADO Pipelines for anything touching Azure, GitHub Actions for repo-level CI, and the HCS-tenant shared runner for both. Two scopes run a different primary CI platform and carry overlays that describe it. Overlays add platform-specific detail on top of the base pipeline-first, idempotency, and secret-handling rules — those rules are universal. Overlay content is authored in each scope's own repo and resolved at read time (see docs/standards/_scopes/README.md).

ScopeCI platform overlayWhere it lives
azurelocalGitHub Actions as the primary CI surface (release-please flow, GitHub-native workflows), still using the HCS self-hosted runnerAzureLocal platform repo, staged into _scopes/azurelocal/automation.md
tierpoint-prodtechGitLab CI as the primary pipeline surface — a 4-stage pipeline (validate / plan / deploy / test) on the HCS-tenant shared Linux runner (see CI Runners for the tag)prodtech docs repo, staged into _scopes/tierpoint-prodtech/automation.md

Do not reproduce overlay content here. The base secret-handling model (Key Vault → KV-linked variable group → pipeline variable) and the manual-trigger and idempotency requirements apply in every scope regardless of CI platform.

Copyright © Hybrid Cloud Solutions LLC — Kristopher Turner