GitHub - nathants/libaws: just ship on aws

GitHub

18 min read Original article ↗

Why

AWS is amazing, but it's hard to ship fast unless you're an expert.

I want to write down best practices as code, then forget about them so I can just ship.

I want to serve http, react to database writes, and use cron to schedule actions.

I want to push data S3 => EC2 => S3 with ephemeral Spot instances that live for seconds.

Did you know that EC2 is billed by the second, that Spot is 1/5 the price, and that Spot in Local Zones is 1/2 that price?

It's basically free. Oh and no egress fees between EC2 and S3 in the same region. Lambda's not the only thing that scales to zero. AWS is pretty awesome.

AWS should:

  • Have fewer knobs
  • Have sane defaults
  • Be easy to use
  • Be hard to screw up
  • Be fast
  • Be fun
  • Have a tldr

It should be easy for a lambda to react to:

It should be easy to create:

How

Declare and deploy groups of related AWS infrastructure as infrastructure sets:

What

A simpler way to declare AWS infrastructure that is easy to use and extend.

There are two ways to use it:

The primary entrypoints are:

  • infra-ensure: deploy an infrastructure set.

    libaws infra-ensure ./infra.yaml --preview
    libaws infra-ensure ./infra.yaml
  • infra-ls: view infrastructure sets.

  • infra-ensure --quick: quickly update Lambda code.

    libaws infra-ensure ./infra.yaml --quick LAMBDA_NAME
  • infra-ensure --build-only / --from-build: build Lambda artifacts on one host, deploy them on another.

    libaws infra-ensure ./infra.yaml --build-only ./build
    libaws infra-ensure ./infra.yaml --from-build ./build
  • infra-rm: remove an infrastructure set.

    libaws infra-rm ./infra.yaml --preview
    libaws infra-rm ./infra.yaml

infra-ensure is a positive assertion. It asserts that some named infrastructure exists, and is configured correctly, creating or updating it if needed.

Other commands fall into two categories:

  • Mutate AWS state: ensure, new, and rm.
  • View AWS state: ls, get, scan, and describe.

AWS SDK, Pulumi, Terraform, CloudFormation, and Serverless

Compared to the full AWS API, systems declared as infrastructure sets:

If you want to use the full AWS API, there are many great tools:

Readme Index

Install

CLI

go install github.com/nathants/libaws@latest

export PATH=$PATH:$(go env GOPATH)/bin

Go API

go get github.com/nathants/libaws@latest

TLDR

Define an Infrastructure Set

>> cd examples/simple/go/s3 && tree
.
├── infra.yaml
└── main.go
name: test-infraset-${uid}

s3:
  test-bucket-${uid}:
    attr:
      - acl=private

lambda:
  test-lambda-${uid}:
    entrypoint: main.go
    attr:
      - memory=128
      - timeout=60
    policy:
      - AWSLambdaBasicExecutionRole
    trigger:
      - type: s3
        attr:
          - test-bucket-${uid}
package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

func handleRequest(_ context.Context, e events.S3Event) (events.APIGatewayProxyResponse, error) {
	for _, record := range e.Records {
		fmt.Println(record.S3.Object.Key)
	}
	return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}

func main() {
	lambda.Start(handleRequest)
}

Ensure the Infrastructure Set

View the Infrastructure Set

infra-ls lists the account. Select one exact set with --infraset NAME, or filter names with a positional substring; these selectors cannot be combined:

libaws infra-ls --infraset my-project

Depth-based colors by YAML

Trigger the Infrastructure Set

Quickly Update Lambda Code

Build and Deploy Lambda Artifacts Separately

Build the infrastructure set's Go Lambda code into a directory of artifacts, then deploy those exact bytes from the directory:

libaws infra-ensure ./infra.yaml --build-only ./build
libaws infra-ensure ./infra.yaml --from-build ./build

--build-only writes each artifact and a manifest.json recording its name, file, sha256, and the entrypoint Git checkout's commit and dirty state. It never accesses AWS. --from-build uses only hash-verified artifacts: it never compiles, and it never reads Lambda entrypoint sources. Only Go Lambdas are built into artifacts; Python and container Lambdas are unaffected, and Python Lambdas still package from their entrypoint and requirements on the deploy host. A missing or mismatched artifact fails before any AWS call, as does --from-build when the selected infrastructure set needs no artifact. An empty directory value, a --from-build path without a manifest, and an unwritable --build-only directory are errors. Both flags also combine with --quick LAMBDA_NAME.

Delete the Infrastructure Set

Usage

Explore the CLI

Explore a CLI Entrypoint

For ec2-ssh, ec2-scp, ec2-rsync, and ec2-wait-ssh, -p/--preview only lists targets. Across the CLI, -p always means --preview; --private-ip, --profile, and --period have no short aliases.

ec2-rm-ami AMI_ID deregisters an owned AMI and asks EC2 to delete its associated snapshots, preserving snapshots used by other AMIs. --preview/-p lists verified backing snapshots as conditional deletions because sharing is checked at deletion time. Snapshot-deletion failures return an error even if deregistration succeeded; their snapshot IDs identify any remaining cleanup. An AMI ID that exists but is not owned by this account fails instead of being treated as absent.

Explore the Go API

Use editor completion, the API reference, or local documentation:

go doc github.com/nathants/libaws/lib
go doc github.com/nathants/libaws/lib.InfraListSet

Explore Simple Examples

Explore Complex Examples

  • Coexisting infrastructure sets: exact-set inventory while an independent set is being removed.

  • Literal S3 keys: object/version CLI operations without path normalization.

  • DynamoDB indexes: provisioned global indexes and local-index projections.

  • S3 append-only: append-only storage with separate writer and reader IAM users.

  • S3-EC2:

    • Write to S3 in-bucket
    • Which triggers Lambda
    • Which launches EC2 Spot
    • Which reads from in-bucket, writes to out-bucket, and terminates

Explore External Examples

Infrastructure Set

An infrastructure set is defined by YAML or Go struct and contains:

Typical Usage

  • Use infra-ensure to deploy an infrastructure set.

    libaws infra-ensure ./infra.yaml --preview
    libaws infra-ensure ./infra.yaml
  • Use infra-ls to view infrastructure sets.

  • Use infra-ensure --quick LAMBDA_NAME to quickly update Lambda code.

    libaws infra-ensure ./infra.yaml --quick LAMBDA_NAME
  • Use infra-rm to remove an infrastructure set.

    libaws infra-rm ./infra.yaml --preview
    libaws infra-rm ./infra.yaml

Design

  • There is no implicit coordination.

    • If you aren't already serializing your infrastructure mutations, lock around DynamoDB.
  • No databases for infrastructure state. There are only two state locations:

    • AWS.
    • Your code.
  • AWS infrastructure is uniquely identified by name.

    • All AWS infrastructure share a private namespace scoped to account/region. Use good names.
    • Except S3, which shares a public namespace scoped to Earth. Use better names.
  • Mutative operations manipulate AWS state.

    • Mutative operations are idempotent. If they fail due to a transient error, run them again.
    • Mutative operations can --preview. No output means no changes.
  • ensure are mutative operations that create or update infrastructure.

  • rm are mutative operations that delete infrastructure.

  • ls, get, scan, and describe operations are non-mutative.

  • Multiple infrastructure sets can be deployed into the same account/region.

Tradeoffs

  • ensure operations are positive assertions. They assert that some named infrastructure exists, and is configured correctly, creating or updating it if needed.

    • When possible, an existing same-named resource is automatically adopted and converged to the requested configuration.

    • Positive assertions CANNOT remove top-level infrastructure, but CAN remove configuration from them.

    • Removing a trigger, policy, or allow WILL remove that from the lambda.

    • Removing policy, or allow WILL remove that from the instance-profile.

    • Removing a security-group WILL remove that from the vpc.

    • Removing a rule WILL remove that from the security-group.

    • Removing an attr WILL remove that from a sqs, s3, dynamodb, or lambda.

    • Removing a keypair, vpc, instance-profile, sqs, s3, dynamodb, or lambda WON'T remove that from the account/region.

      • The operator decides IF and WHEN top-level infrastructure should be deleted, then uses an rm operation to do so.

      • As a convenience, infra-rm will remove ALL infrastructure CURRENTLY declared in an infra.yaml.

      • If a declared Lambda is already gone, infra-rm removes verifiably owned triggers and preserves ambiguous or independently owned resources.

  • When using ensure operations, no output means no changes.

    • For large infrastructure sets, this can mean a minute or two without output if no changes are needed.

    • To see a lot of output instead of none, set this environment variable:

  • Since ensure operations are idempotent, if you encounter errors like rate limits, just try again.

  • infra-ls is designed to list AWS accounts managed with infra-ensure. It will not work well in other scenarios.

infra.yaml

Use an infra.yaml file to declare an infrastructure set. The schema is as follows:

name: VALUE
lambda:
  VALUE:
    entrypoint: VALUE
    policy:     [VALUE ...]
    allow:      [VALUE ...]
    attr:       [VALUE ...]
    require:    [VALUE ...]
    env:        [VALUE ...]
    include:    [VALUE ...]
    trigger:
      - type: VALUE
        attr: [VALUE ...]
s3:
  VALUE:
    attr: [VALUE ...]
user:
  VALUE:
    allow:  [VALUE ...]
    policy: [VALUE ...]
dynamodb:
  VALUE:
    key:  [VALUE ...]
    attr: [VALUE ...]
sqs:
  VALUE:
    attr: [VALUE ...]
vpc:
  VALUE:
    security-group:
      VALUE:
        rule: [VALUE ...]
keypair:
  VALUE:
    pubkey-content: VALUE
instance-profile:
  VALUE:
    allow: [VALUE ...]
    policy: [VALUE ...]

Environment Variable Substitution

Anywhere in infra.yaml you can substitute environment variables from the caller's environment:

  • Example:

    s3:
      test-bucket-${uid}:
        attr:
          - versioning=${versioning}

The following variables are defined during deployment, and are useful in allow declarations:

  • ${API_ID} the ID of the API Gateway v2 API created by an api trigger.

  • ${WEBSOCKET_ID} the ID of the API Gateway v2 websocket created by a websocket trigger.

Name

Defines the name of the infrastructure set.

  • Schema:

  • Example:

S3

Defines a S3 bucket:

  • The following attributes can be defined:

    • acl=VALUE, values: public | private, default: private
    • versioning=VALUE, values: true | false, default: false
    • appendonly=VALUE, values: true | false, default: false
    • metrics=VALUE, values: true | false, default: false
    • cors=VALUE, values: true | false, default: false
    • ttldays=VALUE, values: 0 | n, default: 0. Bucket-wide expiration in days.
    • allow_put=VALUE, values: $principal.amazonaws.com
  • Managed buckets require TLS, use SSE-S3 encryption, and reject SSE-C.

  • S3 CLI paths preserve literal keys, including dot segments and repeated slashes; s3-ls --quiet prints full bucket/key paths.

  • appendonly=true requires If-None-Match: * for object creation and multipart completion, denies deletion, and cannot be combined with expiration.

  • Setting cors=true uses * for allowed origins. To specify one or more explicit origins, do this instead:

    • corsorigin=http://localhost:8080
    • corsorigin=https://example.com
  • Bucket ACL can only be set at creation. Restore missing public-access-block settings before re-ensuring a bucket.

  • Schema:

  • Example:

    s3:
      test-bucket:
        attr:
          - versioning=true
          - acl=public

IAM user

Defines an IAM user without creating credentials.

  • allow entries use SERVICE:ACTION RESOURCE.

  • policy entries name existing managed policies.

  • Undeclared inline and attached policies are removed.

  • Create the user's sole access key explicitly with libaws iam-ensure-user-api-key USER.

  • Schema:

    user:
      VALUE:
        allow:
          - SERVICE:ACTION RESOURCE
        policy:
          - VALUE
  • Example:

    user:
      backup-writer:
        allow:
          - s3:PutObject arn:aws:s3:::backup-bucket/*

DynamoDB

Defines a DynamoDB table:

  • Specify key as:

    • NAME:ATTR_TYPE:KEY_TYPE
  • The following attributes can be defined:

    • read=VALUE, provisioned read capacity, default: 0
    • write=VALUE, provisioned write capacity, default: 0
    • ttl=ATTR_NAME, optional expiration attribute.
  • On global indices the following attributes can be defined:

    • projection=VALUE, projection type, default: ALL
    • read=VALUE, provisioned read capacity, default: 0
    • write=VALUE, provisioned write capacity, default: 0
  • On local indices the following attributes can be defined:

    • projection=VALUE, projection type, default: ALL
  • Schema:

    dynamodb:
      VALUE:
        key:
          - NAME:ATTR_TYPE:KEY_TYPE
        attr:
          - VALUE
        global-index:
          VALUE:
            key:
              - NAME:ATTR_TYPE:KEY_TYPE
            non-key:
              - NAME
            attr:
              - VALUE
        local-index:
          VALUE:
            key:
              - NAME:ATTR_TYPE:KEY_TYPE
            non-key:
              - NAME
            attr:
              - VALUE
  • Example:

    dynamodb:
      stream-table:
        key:
          - userid:s:hash
          - timestamp:n:range
        attr:
          - stream=keys_only
      auth-table:
        key:
          - id:s:hash
        attr:
          - write=50
          - read=150
  • Example global secondary index:

    dynamodb:
      test-table:
        key:
          - id:s:hash
        global-index:
          test-index:
            key:
              - hometown:s:hash
  • Example local secondary index:

    dynamodb:
      test-table:
        key:
          - id:s:hash
          - created:n:range
        local-index:
          test-index:
            key:
              - id:s:hash
              - hometown:s:range

SQS

Defines a SQS queue:

  • The following attributes can be defined:

    • delay=VALUE, delay seconds, default: 0
    • size=VALUE, maximum message size bytes, default: 1048576
    • retention=VALUE, message retention period seconds, default: 345600
    • wait=VALUE, receive wait time seconds, default: 0
    • timeout=VALUE, visibility timeout seconds, default: 30
  • Schema:

    sqs:
      VALUE:
        attr:
          - VALUE
  • Example:

    sqs:
      test-queue:
        attr:
          - delay=20
          - timeout=300

Keypair

Defines an EC2 keypair.

  • Schema:

    keypair:
      VALUE:
        pubkey-content: VALUE
  • Example:

    keypair:
      test-keypair:
        pubkey-content: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICVp11Z99AySWfbLrMBewZluh7cwLlkjifGH5u22RXor

VPC

Defines a default-like VPC with an Internet Gateway and public access.

ec2-new --vpc NAME_OR_ID selects subnets in zones offering --type: all matching VPC subnets for --spot, or one at random for on-demand. Explicit --subnets "subnet-A subnet-B" overrides this selection.

Before --init, the first detected NVMe instance-store disk is formatted as whole-device ext4 and mounted at /mnt, writable by the login user. EBS disks are excluded. Existing partitions, signatures, device use, an occupied /mnt, or failed safety reads abort initialization. Without instance store, no disk changes are made. The mount is recorded by filesystem UUID.

  • Schema:

  • Example:

Security Group

Defines a security group on a VPC.

  • Schema:

    vpc:
      VALUE:
        security-group:
          VALUE:
            rule:
              - PROTO:PORT:SOURCE
  • Example:

    vpc:
      test-vpc:
        security-group:
          test-sg:
            rule:
              - tcp:22:0.0.0.0/0

Instance Profile

Defines an EC2 instance profile.

  • Schema:

    instance-profile:
      VALUE:
        allow:
          - SERVICE:ACTION ARN
        policy:
          - VALUE
  • Example:

    instance-profile:
      test-profile:
        allow:
          - s3:* *
        policy:
          - AWSLambdaBasicExecutionRole

Lambda

Defines a Lambda.

  • Schema:

  • Example:

Entrypoint

Defines the code of the Lambda. It is one of:

  • A Python file.

  • A Go file. Libaws builds its complete package.

  • An ECR container URI ending in @sha256: followed by 64 lowercase hexadecimal characters.

  • Schema:

    lambda:
      VALUE:
        entrypoint: VALUE
  • Example:

    lambda:
      test-lambda:
        entrypoint: main.go

Attr

Defines Lambda attributes. The following can be defined:

  • concurrency=VALUE, reserved concurrency; omit for the unreserved pool or use 0 to disable the function.

  • memory=VALUE, RAM in megabytes, default: 128

  • timeout=VALUE, timeout in seconds, default: 300

  • logs-ttl-days=VALUE, CloudWatch Logs retention in days; 0 disables expiration, default: 7

  • Schema:

    lambda:
      VALUE:
        attr:
          - KEY=VALUE
  • Example:

    lambda:
      test-lambda:
        attr:
          - concurrency=100
          - memory=256
          - timeout=60
          - logs-ttl-days=1

Policy

Defines policies on the Lambda's IAM role.

  • Schema:

    lambda:
      VALUE:
        policy:
          - VALUE
  • Example:

    lambda:
      test-lambda:
        policy:
          - AWSLambdaBasicExecutionRole

Allow

Defines allows on the Lambda's IAM role.

  • Schema:

    lambda:
      VALUE:
        allow:
          - SERVICE:ACTION ARN
  • Example:

    lambda:
      test-lambda:
        allow:
          - s3:* *
          - dynamodb:* arn:aws:dynamodb:*:*:table/test-table

Env

Defines environment variables on the Lambda:

  • Names must match [a-zA-Z][a-zA-Z0-9_]+ and may not be repeated.

  • The environment JSON is limited to 4 KiB; the complete configuration update to 5 KiB.

  • Values may be empty and are preserved exactly.

  • Schema:

    lambda:
      VALUE:
        env:
          - KEY=VALUE
  • Example:

    lambda:
      test-lambda:
        env:
          - kind=production

Include

Defines extra content to include in the Lambda zip. Relative paths resolve beside infra.yaml:

  • This is ignored when entrypoint is an ECR container URI.

  • Schema:

    lambda:
      VALUE:
        include:
          - VALUE
  • Example:

    lambda:
      test-lambda:
        include:
          - ./cacerts.crt
          - ../frontend/public/*

Require

Defines dependencies to install with pip in the virtualenv zip.

  • This is ignored unless the entrypoint is a Python file.

  • Relative paths, including -rrequirements.txt and local packages, resolve from the directory containing infra.yaml.

  • For source dependencies, add --build-constraint=build-requirements.txt with exact build-backend pins.

  • Schema:

    lambda:
      VALUE:
        require:
          - VALUE
  • Example:

    lambda:
      test-lambda:
        require:
          - fastapi==0.76.0

Trigger

Defines triggers for the Lambda:

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: VALUE
            attr:
              - VALUE
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: dynamodb
            attr:
              - test-table
              - start=latest

Trigger Types

SES

Defines an SES email trigger.

  • Required attrs: dns=DOMAIN and bucket=BUCKET; optional: prefix=PREFIX.

  • Route53 and SES must already be configured for the domain, and the bucket must allow puts from SES.

  • The domain must be ASCII and at most 64 characters because it names the SES rule set.

  • Each infrastructure set may declare one SES trigger. Ensuring it replaces the active regional rule set.

  • Ensure rejects a same-named rule set containing other rules.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: ses
            attr:
              - VALUE
  • Example:

    s3:
      my-bucket:
        attr:
          - allow_put=ses.amazonaws.com
    
    lambda:
      test-lambda:
        trigger:
          - type: ses
            attr:
              - dns=my-email-domain.com
              - bucket=my-bucket
              - prefix=emails/

API

Defines an API Gateway v2 HTTP API:

  • Add a custom domain with attr: domain=api.example.com

  • Add a custom domain and update Route53 with attr: dns=api.example.com

    • Use dns=*.example.com to accept any subdomain

    • This domain, or its parent domain, must already exist as a hosted zone in Route53.

    • An exact or matching wildcard ACM certificate must already exist.

  • infra-rm removes exclusively managed domains and aliases; shared domains, routing-rule domains, hosted zones, and certificates remain.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: api
            attr:
              - VALUE
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: api
            attr:
              - dns=api.example.com

URL

Defines a Lambda function URL trigger with streaming HTTP responses.

  • No attributes are required.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: url
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: url

Websocket

Defines an API Gateway v2 websocket API:

  • Add a custom domain with attr: domain=ws.example.com

  • Add a custom domain and update Route53 with attr: dns=ws.example.com

    • This domain, or its parent domain, must already exist as a hosted zone in route53-ls.

    • An exact or matching wildcard ACM certificate must already exist.

  • infra-rm removes exclusively managed domains and aliases; shared domains, routing-rule domains, hosted zones, and certificates remain.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: websocket
            attr:
              - VALUE
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: websocket
            attr:
              - dns=ws.example.com

S3

Defines an S3 trigger:

  • The only attribute must be a nonempty bucket name.

  • Object creation and deletion invoke the trigger.

  • Ensure rejects an existing conflicting notification instead of creating duplicate invocations.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: s3
            attr:
              - VALUE
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: s3
            attr:
              - test-bucket

DynamoDB

Defines a DynamoDB trigger:

  • The first attribute must be the table name.

  • The following trigger attributes can be defined:

    • batch=VALUE, maximum batch size, default: 100
    • parallel=VALUE, parallelization factor, default: 1
    • retry=VALUE, maximum retry attempts, default: -1
    • window=VALUE, maximum batching window in seconds, default: 0
    • start=VALUE, required starting position: latest | trim_horizon; immutable on existing mappings.
  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: dynamodb
            attr:
              - VALUE
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: dynamodb
            attr:
              - test-table
              - start=trim_horizon

SQS

Defines a SQS trigger. Ensuring a declared trigger re-enables a disabled mapping:

  • The first attribute must be a nonempty queue name.

  • The following trigger attributes can be defined:

    • batch=VALUE, maximum batch size, default: 10
    • window=VALUE, maximum batching window in seconds, default: 0
  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: sqs
            attr:
              - VALUE
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: sqs
            attr:
              - test-queue

Schedule

Defines a schedule trigger:

  • The only attribute must be the schedule expression.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: schedule
            attr:
              - VALUE
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: schedule
            attr:
              - rate(24 hours)

ECR

Defines an ECR trigger:

  • Successful image actions from any ECR repository invoke the trigger; delivery is best-effort.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: ecr
  • Example:

    lambda:
      test-lambda:
        trigger:
          - type: ecr

Alarm

Defines a CloudWatch alarm that invokes the declaring Lambda when the monitored Lambda records at least the specified number of invocations in one minute.

  • The following attributes are required:

    • name=VALUE, account-and-region-unique alarm name, 1–255 ASCII letters, digits, dots, hyphens, or underscores.
    • lambda-invocations=VALUE, Lambda to monitor.
    • at-least=VALUE/minute, positive integer invocation threshold, maximum: 2147483647.
  • Missing data does not breach the alarm.

  • Schema:

    lambda:
      VALUE:
        trigger:
          - type: alarm
            attr:
              - name=VALUE
              - lambda-invocations=VALUE
              - at-least=VALUE/minute
  • Example:

    lambda:
      alert-handler:
        trigger:
          - type: alarm
            attr:
              - name=beta-invocations-runaway
              - lambda-invocations=beta
              - at-least=300/minute

Bash Completion

source completions.d/libaws.sh

Extending

Drop down to the AWS Go SDK and implement what you need.

Extend an existing mutative operation or add a new one.

  • Make sure that mutative operations are IDEMPOTENT and can be PREVIEWED.

You will find examples in cmd/ and lib/ that can provide a good place to start.

You can reuse many existing operations like:

Alternatively, lift and shift to other infrastructure automation tooling. ls and describe operations will give you all the information you need.

Testing

Tests require Go 1.27+, Python 3.12+, uv, rootless Docker, and the check tools. Use scratch-account credentials and a permanent, publicly delegated Route53 zone in that account:

export LIBAWS_TEST_ACCOUNT=$ACCOUNT_NUM
export LIBAWS_TEST_DOMAIN=scratch.example.com
make test

Set LIBAWS_TEST_JOBS to change concurrency (default: 4; 1 for sequential runs). Do not overlap live suites in one account.

Tests retain shared certificate, DNS, and API-domain fixtures; see testing guidance for details.

R2 tests are opt-in: set LIBAWS_R2_TEST_ACCOUNT to your R2_ACCOUNT_ID and supply R2_ACCESS_KEY_ID and R2_ACCESS_KEY_SECRET. They create and remove unique libaws-testing-* buckets.

Run one example with:

make
export PATH="$PWD:$PATH"
cd examples/simple/python/api
uv run --locked python -u test.py