Skip to main content
Version: v5.7.0

The Azure pack (packs:azure)

packs:azure

The packs:azure entitlement gates opschain-pack-azure, the signed commercial content pack for Microsoft Azure: an enterprise azure:* action surface built on the official Azure SDK (azure-sdk-for-go's ARM runtime) via the pack's own azu tool — never a wrapper around the vendor CLI. The pack's signed manifest names requires: packs:azure, so the entitlement in your licence must spell exactly that; like every packs:<name> grant it is validated by shape and minted per customer (see entitlements).

The pack mounts like any other action pack — a packs: entry in the template's .opschain/packs.yaml naming the pack remote and a pinned revision (opschain docs getting-started/2-4-using-packs) — or installs into the pack store as a signed bundle. Both doors enforce the manifest: installing a signed bundle verifies its signature and its required entitlement, and running any azure:* action without packs:azure refuses with the named pack refusal before anything dials Azure.

The shipped actions​

  • azure:api — dial ANY ARM resource provider: method + path (an ARM path, full resource id or operation URL) + api_version + body JSON (inline or from a workspace file), with long-running operations drained through the poller (wait, wait_timeout; wait: false surfaces an operation_url instead), auto-drained pagination (paginate, max_pages), over-budget results landed as workspace files, and a uniform exit-code taxonomy (usage, validation, not-found, conflict, throttled, authorization).
  • azure:<service>:<client>_<operation> — one generated typed action per SDK operation, 1,700+ across compute, network, storage, resources, sql, keyvault, aks, dns, monitor and authorization, carrying the exact path template, HTTP method and api-version the SDK client dials; long-running methods carry wait/wait_timeout, paged listings carry paginate/max_pages. The full generated table, including every excluded operation with its reason, ships as CATALOG.md inside the pack.
  • azure:whoami — resolve the subscription and principal the handed-over credentials dial as. Run it first: a broken credential hand-over fails here, loudly, before anything mutates.
  • azure:vm_run — provision one Linux machine (image urn or id, size, an existing subnet; optional public IP, tags, cloud-init user data). Settles to Succeeded and publishes vm_id, state, addresses — and host_target (ssh_user@address), ready to hand to the infrastructure pack's host_register so the new machine becomes a governed infrastructure host node.
  • azure:vm_state — start / stop / deallocate / restart / delete, settled through the poller; azure:vm_describe — the read half: power state, addresses, size and tags.
  • azure:blob_put / azure:blob_get — stream a workspace file to or from blob storage, bearer-token authorised on the storage scope — no account keys, no SAS tokens in YAML.
  • azure:resource_group_create / azure:resource_group_delete — the deployment boundary, delete drained to completion.
  • azure:deployment_apply — apply an ARM template deployment, drained to a terminal provisioning state; deployment outputs publish as output_<name> step outputs.
  • azure:operation_wait — drain an operation_url a wait: false dial surfaced earlier.
  • azure:ensure — pre-warm the pinned azu binary on a runner (source, version, url, sha256; publishes azu_path, azu_version). Never a precondition — see below.

Every step resolves the pinned azu binary itself (PATH, then the runner cache, then the pack's own bundled tools/ directory, then a sha256-verified download — never an unpinned public URL). The ensure step each action carries, and azure:ensure on its own, pre-warm a runner; neither is a precondition: the platform dispatches step by step, so sibling steps of one action may execute on different runners of a group, and a step never trusts a path a sibling published. With no channel available a step refuses by name, pointing at azure:ensure and the signed bundle. A pack installed from its signed bundle carries the tool, so a fresh runner needs no toolchain and no download configuration.

Worked examples​

Each example is a complete declarative action file (.opschain/actions/<name>.yaml) composing the pack's actions with uses: + with: (opschain docs orchestration). Nothing secret appears in any of them — credential custody is server-side (see the next section) — and subscription_id and location are omitted wherever the node's azure.subscription_id and azure.location properties should carry them.

Prove the identity first (azure:whoami)​

Open anything that mutates with the liveness probe: a broken hand-over fails here, loudly, and the identity lands as ordinary step outputs.

# .opschain/actions/preflight.yaml
action: preflight
description: Prove the credential hand-over before anything mutates
steps:
- name: identity
uses: azure:whoami
- name: announce
run: echo "dialling subscription ${{ steps.identity.outputs.subscription_id }} as principal ${{ steps.identity.outputs.principal_id }}"

A boundary, a machine, a governed host (azure:resource_group_create, azure:vm_run)​

The resource group is the deployment boundary; the machine settles to Succeeded, and the published host_target (ssh_user@address) seeds the infrastructure pack's membership hand-over. The SSH public key is a public value — secrets never ride inputs.

# .opschain/actions/provision_app.yaml
action: provision_app
description: Create the group, provision a machine, register the governed host
inputs:
ssh_public_key: { type: string, required: true } # a public value
subnet_id: { type: string, required: true }
steps:
- name: group
uses: azure:resource_group_create
with: { name: app-dev, tags: service=app }
- name: provision
uses: azure:vm_run
with:
resource_group: app-dev
name: app-vm-01
size: Standard_B2s
image: Canonical:ubuntu-24_04-lts:server:latest
admin_username: opschain
ssh_public_key: '${{ inputs.ssh_public_key }}'
subnet_id: '${{ inputs.subnet_id }}'
- name: register
uses: infrastructure:host_register
with:
parent: /projects/acme/environments/dev
code: app_vm_01
address: '${{ steps.provision.outputs.private_ip }}'
properties: '{"rpc":{"target":"${{ steps.provision.outputs.host_target }}"}}'

Power transitions and the read half (azure:vm_state, azure:vm_describe)​

One verb per call — start, stop, deallocate, restart or delete — settled through the poller; the describe step reads the settled power state back.

# .opschain/actions/park_app.yaml
action: park_app
description: Deallocate the machine and read its settled power state back
steps:
- name: park
uses: azure:vm_state
with: { resource_group: app-dev, name: app-vm-01, state: deallocate }
- name: read_back
uses: azure:vm_describe
with: { resource_group: app-dev, name: app-vm-01 }
- name: record
run: echo "power state ${{ steps.read_back.outputs.state }}"

Stage artefacts (azure:blob_put / azure:blob_get)​

Both stream between the step workspace and blob storage on a bearer token scoped to storage — no account keys, no SAS tokens, nothing in the YAML.

# .opschain/actions/stage_release.yaml
action: stage_release
description: Ship the built artefact out and pull the live manifest in
steps:
- name: build
run: tar -czf release.tar.gz build/
- name: ship
uses: azure:blob_put
with: { account: acmereleases, container: web, blob: release.tar.gz, file: release.tar.gz }
- name: manifest
uses: azure:blob_get
with: { account: acmereleases, container: web, blob: manifest.json, file: manifest.json }
- name: record
run: echo "shipped etag ${{ steps.ship.outputs.etag }} (${{ steps.ship.outputs.bytes }} bytes)"

ARM template deployments (azure:deployment_apply)​

template and parameters take JSON inline or as @file: workspace references; the deployment drains to a terminal provisioning state and its template outputs publish as output_<name> step outputs — a template output named subnetId lands as output_subnet_id.

# .opschain/actions/apply_network.yaml
action: apply_network
description: Apply the network template and consume its outputs
steps:
- name: apply
uses: azure:deployment_apply
with:
resource_group: app-dev
name: network
template: '@file:templates/network.json'
parameters: '@file:templates/network.dev.json'
- name: record
run: echo "deployment ${{ steps.apply.outputs.provisioning_state }}; subnet ${{ steps.apply.outputs.output_subnet_id }}"

The full-coverage lane (azure:api)​

Any ARM resource provider by path + api_version (method defaults to GET): each top-level response field publishes as a snake_case step output, and a list response auto-drains its nextLink up to max_pages.

# .opschain/actions/inspect_vm.yaml
action: inspect_vm
description: Read the machine's model and instance view in one dial
steps:
- name: view
uses: azure:api
with:
path: /resourceGroups/app-dev/providers/Microsoft.Compute/virtualMachines/app-vm-01
api_version: "2025-04-01"
query: '{"$expand":"instanceView"}'

A long-running dial drains through the poller by default; wait: "false" surfaces operation_url instead, and azure:operation_wait settles it later — kick work off, do other work, then drain.

# .opschain/actions/rolling_restart.yaml
action: rolling_restart
description: Kick a restart, then settle the operation when it matters
steps:
- name: kick
uses: azure:api
with:
method: POST
path: /resourceGroups/app-dev/providers/Microsoft.Compute/virtualMachines/app-vm-01/restart
api_version: "2025-04-01"
wait: "false"
- name: drain
uses: azure:operation_wait
with:
url: '${{ steps.kick.outputs.operation_url }}'

A generated typed action (azure:compute:virtual_machines_instance_view)​

Every generated action is the same lane with the SDK operation's path parameters as typed inputs, carrying the exact path template, HTTP method and api-version the SDK client dials.

# .opschain/actions/instance_view.yaml
action: instance_view
description: Read the run-time state of a machine over the typed surface
steps:
- name: view
uses: azure:compute:virtual_machines_instance_view
with:
resource_group_name: app-dev
vm_name: app-vm-01

Close the boundary (azure:resource_group_delete)​

Destructive and drained to completion — everything inside the group goes with it.

# .opschain/actions/tear_down.yaml
action: tear_down
description: Delete the group and everything in it
steps:
- name: delete
uses: azure:resource_group_delete
with: { name: app-dev }

Credentials custody​

Reference-first: secrets are custodied as vault references and resolved by the platform into masked environment variables (tenant, client id, client secret, certificate or federated-token variables), or the runner carries workload or managed identity and no variables at all. azu dials only the credential that is configured: when a client secret, certificate (file or inline) or federated token file is set, that one mode is built through its own constructor and a mode that cannot be built refuses by name with the cause — it never falls through to workload or managed identity. The ambient chain (workload identity, then managed identity) is used only when no explicit variable is set. Each dial logs one value-free line naming which credential channels were present (kind and length only). An unresolved vault reference refuses the dial naming the variable — the value is never echoed. Every action resolves the subscription from the node's azure.subscription_id property unless overridden in with:, and azure.location seeds locations the same way.

Entitlement and lapse behaviour​

The signed-pack gate, uniformly: a LimePoint-signed pack whose verified manifest requires packs:azure is gated on that feature at install and at every action run — the refusal names the pack, the mount and the missing entitlement, and lands a customer-visible licence warning event. The grant is layered by a pack-tier licence document beside your base licence and stops contributing once that document expires past its grace window: installed content and stored configuration are never removed, reads stay up, and everything lights back up when a renewed licence is applied.

Composition with other packs​

  • The infrastructure pack closes the loop: azure:vm_run publishes host_target, which infrastructure:host_register (packs:infrastructure) turns into a registered, governed infrastructure host node — provision on Azure, then operate over the brokered transport family.
  • The AWS sibling is the AWS pack (packs:aws): the same shape — a governed api lane, a generated typed catalog and provisioning hand-over — for Amazon Web Services.

Where the rest lives​

  • Entitlements — the pack vocabulary and refusal shapes.
  • The pack store — installing signed bundles.
  • The infrastructure pack — registering provisioned machines as governed hosts.
  • opschain docs getting-started/2-4-using-packs — mounting a pack onto a template.