The AWS pack (packs:aws)
packs:aws
The packs:aws entitlement gates opschain-pack-aws, the signed
commercial content pack for Amazon Web Services: an enterprise aws:*
action surface built on the official AWS SDK (aws-sdk-go-v2) via the
pack's own awsu tool — never a wrapper around the vendor CLI. The
pack's signed manifest names requires: packs:aws, 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 aws:* action without
packs:aws refuses with the named pack refusal before anything dials
AWS.
The shipped actions
aws:api— dial ANY operation of any allowlisted service (cloudformation, ec2, eks, elbv2, iam, lambda, rds, route53, s3, sts):service,operation,params(a JSON object keyed by the operation's input fields), optionalassume_role_arn/external_id, auto-drained pagination (paginate,max_pages), waiters (wait,wait_timeout), streaming or over-budget results landed as workspace files, and a uniform exit-code taxonomy (usage, validation, not-found, throttled, access-denied).aws:<service>:<operation>— one generated typed action per SDK operation, 1,600+ across the ten services, with inputs derived from the operation's input struct and required-ness from the SDK's own validators;aws:<service>:wait_<waiter>covers every SDK waiter. The full generated table, including every excluded operation with its reason, ships asCATALOG.mdinside the pack.aws:whoami— resolve the caller identity the handed-over credentials dial as (account, arn, user_id). Run it first: a broken credential hand-over fails here, loudly, before anything mutates.aws:ec2_run_instance— provision one instance (image and instance type; optional subnet, key pair, security groups, tags, cloud-init user data). Waits until running by default and publishesinstance_id,state, addresses — andhost_target(ssh_user@address), ready to hand to the infrastructure pack'shost_registerso the new machine becomes a governed infrastructure host node.aws:ec2_instance_state— start / stop / reboot / terminate with a settled-state wait by default;aws:ec2_describe_instance— the read half: state, addresses, placement and tags as typed outputs.aws:s3_put/aws:s3_get— stage one file out to, or fetch one object into, the step workspace; etag, size and path published.aws:ensure— pre-warm the pinnedawsubinary (PATH, the runner cache, the pack's own bundledtools/directory, or a sha256-verified download from a configured mirror; a public URL is never a default). A pack installed from its signed bundle carries the tool, so a fresh runner needs no toolchain and no download configuration. The other actions call it implicitly.aws:cli— gated escape hatch: one AWS CLI command on a runner that already carries theawsbinary, for genuine gaps the native actions do not cover yet. The step log announces the lane.
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 region is omitted wherever the node's
aws.region property should carry it.
Prove the identity first (aws: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: aws:whoami
- name: announce
run: echo "dialling account ${{ steps.identity.outputs.account }} as ${{ steps.identity.outputs.arn }}"
Provision and hand over (aws:ec2_run_instance)
The instance settles to running by default, and the published
host_target (ssh_user@address) seeds the infrastructure pack's
membership hand-over, so the new machine lands as a governed host node.
# .opschain/actions/provision_web.yaml
action: provision_web
description: Provision a web instance and register it as a governed host
steps:
- name: provision
uses: aws:ec2_run_instance
with:
image_id: ami-0abcdef1234567890
instance_type: t3.micro
name: web-01
tags: service=web,env=dev
- name: register
uses: infrastructure:host_register
with:
parent: /projects/acme/environments/dev
code: web_01
address: '${{ steps.provision.outputs.private_ip }}'
properties: '{"rpc":{"target":"${{ steps.provision.outputs.host_target }}"}}'
Lifecycle verbs (aws:ec2_instance_state)
One verb per call — start, stop, reboot or terminate — settled
to the verb's target state before the step succeeds.
# .opschain/actions/stop_web.yaml
action: stop_web
description: Stop an instance and settle to stopped
inputs:
instance_id: { type: string, required: true }
steps:
- name: stop
uses: aws:ec2_instance_state
with: { instance_id: '${{ inputs.instance_id }}', state: stop }
Stage artefacts (aws:s3_put / aws:s3_get)
s3_put ships a workspace file out, s3_get pulls an object in, and
both publish typed outputs for later steps to consume.
# .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: aws:s3_put
with: { bucket: acme-releases, key: web/release.tar.gz, file: release.tar.gz, sse: AES256 }
- name: manifest
uses: aws:s3_get
with: { bucket: acme-releases, key: web/manifest.json, file: manifest.json }
- name: record
run: echo "shipped etag ${{ steps.ship.outputs.etag }}; manifest at ${{ steps.manifest.outputs.path }}"
The full-coverage lane (aws:api)
Any operation of an allowlisted service: params is a JSON object
keyed by the operation's input fields, pagination auto-drains up to
max_pages, and each top-level response field publishes as a
snake_case step output (volumes here, compact JSON).
# .opschain/actions/audit_volumes.yaml
action: audit_volumes
description: List every unencrypted EBS volume in the region
steps:
- name: volumes
uses: aws:api
with:
service: ec2
operation: DescribeVolumes
params: '{"Filters":[{"Name":"encrypted","Values":["false"]}]}'
max_pages: "5"
The same lane drives every SDK waiter: name the waiter in wait:
instead of an operation: and the step parks until the condition
holds, or fails when wait_timeout passes.
# .opschain/actions/await_running.yaml
action: await_running
description: Park until the instance settles running
inputs:
instance_id: { type: string, required: true }
steps:
- name: until_running
uses: aws:api
with:
service: ec2
wait: instance_running
params: '{"InstanceIds":["${{ inputs.instance_id }}"]}'
wait_timeout: 15m
A generated typed action (aws:s3:list_objects_v2)
Every generated action is the same lane with the operation's input
struct as typed inputs — here bucket is required and prefix
optional, exactly as the SDK validates them; the drained pages merge
into one contents output.
# .opschain/actions/list_releases.yaml
action: list_releases
description: List the staged release objects
steps:
- name: listing
uses: aws:s3:list_objects_v2
with:
bucket: acme-releases
prefix: web/
max_pages: "3"
Pre-warm the tool (aws:ensure)
Every other aws:* action calls it implicitly; call it directly to
pre-warm a runner. For source: auto the channels resolve in order —
an awsu already on PATH, the runner cache, the pack's own bundled
tools/ directory (a pack installed from its signed bundle carries the
release matrix, digest-checked against the pack's pin before install),
then a configured mirror URL. Every install verifies against the
pack-pinned sha256, and a public URL is never a default.
# .opschain/actions/prewarm.yaml
action: prewarm
description: Pre-warm the pinned awsu binary from the site mirror
steps:
- name: ensure
uses: aws:ensure
with:
source: url
url: https://mirror.example.internal/awsu
- name: report
run: echo "awsu ${{ steps.ensure.outputs.awsu_version }} ready at ${{ steps.ensure.outputs.awsu_path }}"
The gated escape hatch (aws:cli)
For a genuine preview-surface gap only — here a service outside the
aws:api allowlist. It drives an aws binary the runner image already
carries (the pack never installs one), announces the lane in the step
log, and publishes exit_code plus a size-capped stdout. Prefer a
native action: a gap worth this lane is a gap worth reporting.
# .opschain/actions/preview_gap.yaml
action: preview_gap
description: Reach a surface the native actions do not cover yet
steps:
- name: preview
uses: aws:cli
with:
command: sqs list-queues
- name: record
run: echo "lane exited ${{ steps.preview.outputs.exit_code }}"
Credentials custody
Nothing secret is ever an action input, node property or argv.
Credentials are custodied as vault references; the platform resolves
them and hands the values to the runner as masked environment variables,
or the runner carries an ambient role (instance profile / IRSA) and no
variables at all. Each dial logs one value-free line naming which
credential channels were present (kind and length only), and an
unresolved vault reference refuses the dial by name — it is never echoed.
Every action resolves region from the node's aws.region property
unless overridden in with: (falling back to the SDK's ambient
resolution).
Entitlement and lapse behaviour
The signed-pack gate, uniformly: a LimePoint-signed pack whose verified
manifest requires packs:aws 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:
aws:ec2_run_instancepublisheshost_target, whichinfrastructure:host_register(packs:infrastructure) turns into a registered, governed infrastructure host node — provision on AWS, then operate over the brokered transport family. - The Azure sibling is the Azure pack
(
packs:azure): the same shape — a governedapilane, a generated typed catalog and provisioning hand-over — for Microsoft Azure.
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.