Skip to content

JSChronicles/anvil

anvil

pytest ruff


Introduction

Anvil is a declarative, multi-organization AWS execution engine for running consistent, repeatable tasks across large numbers of AWS accounts, with explicit guarantees around ordering, isolation, and observability.

It provides a structured way to define what should run (tasks and dependencies) and where it should run (organizations and accounts), while the engine manages authentication, role assumption, concurrency, failure handling, and result aggregation.

Anvil is intentionally task-agnostic. Tasks are implemented as simple Python modules with a well-defined contract, enabling teams to build inventory, validation, enforcement, or reporting workflows without coupling business logic to the execution engine. Account execution is parallelized per organization using bounded worker pools, with optional fail-fast semantics.

Usage

  1. When using the uv tool, there are several ways to run and install dependencies. Here are a few examples:
    1. Manual setup (similar to pip-tools):
      1. Create a Python virtual environment: uv venv or python -m venv .venv
      2. Activate the virtual environment: ..venv\Scripts\activate.ps1
      3. Install dependencies: uv pip install --requirements pyproject.toml
  2. uv sync:
    1. Sync the project's dependencies with the environment: uv sync
    2. Activate the virtual environment: .venv\Scripts\activate
  3. uv run:
    1. Run a command in the project environment.: uv run example.py
    2. Note that if you use uv run in a project, i.e. a directory with a pyproject.toml, it will install the current project before running the script.

We have multiple global commands

anvil auth …
anvil graph …
anvil tasks …
anvil run …

Authentication

Authentication checks validate AWS credentials and access without executing any tasks.

anvil auth check --help

Authenticate credentials from an organization file.

anvil auth check --org-file ./yaml/orgs.yaml

Output authentication results as JSON

anvil auth check --org-file orgs.yaml --json

Suppress all output and rely on the exit code only (useful for CI)

anvil auth check --org-file orgs.yaml --quiet

Graph

Display the resolved task dependency graph for an organization configuration.

anvil graph --help

Generate a dependency graph from an organization file.

anvil graph --org-file .\examples\07-optional-task-semantics.yaml

Execution Graph (optional-semantics-org)
----------------------------------------
inventory
└──     reporting
    └──         cleanup

Output graph results as JSON

anvil graph --org-file .\examples\07-optional-task-semantics.yaml --json

{
  "organization": "optional-semantics-org",
  "tasks": [
    {
      "name": "inventory",
      "depends_on": []
    },
    {
      "name": "reporting",
      "depends_on": [
        "inventory"
      ]
    },
    {
      "name": "cleanup",
      "depends_on": [
        "reporting"
      ]
    }
  ]
}

Task Management

List all available stock and user-defined tasks

anvil tasks list

Available tasks:
  - inventory [plugin: my-project]
  - noop [stock]
  - remove_iam_user [stock]
  - cleanup [plugin: my-project]
  ...

Validate all available stock and user-defined tasks:

anvil tasks validate
[ERROR] task validation failed:
  - task 'cleanup' is missing required run() parameters: ['account_alias']
  - task 'inventory' is missing required run() parameters: ['metadata']

Execution

anvil run --help

Execute all configured organizations and accounts

anvil run --org-file ./yaml/orgs.yaml

You can run --include, --exclude, or --dry-run to overide the yaml file if you want to just test something or run on certain accounts

# Include only specific accounts:
anvil run --org-file orgs.yaml --include 111111111111 222222222222

# Exclude specific accounts:
anvil run --org-file orgs.yaml --exclude 333333333333 444444444444

Custom Tasks (Project-Local)

Anvil supports project-local tasks in addition to its stock tasks. This allows you to add custom behavior without forking Anvil.

How task discovery works

Tasks are resolved in the following order:

Anvil discovers tasks from two sources:

  • Stock tasks - tasks shipped with Anvil (anvil.tasks)

  • Plugin tasks - tasks registered via the anvil.tasks entry-point group

Directories named tasks/ are conventional only and are not automatically scanned.

Create a project-local tasks directory

Create a directory anywhere in your project:

my-project/
├─ tasks/
│  ├─ inventory.py
│  ├─ cleanup.py
│  └─ tagging.py

Each task module must define a callable run() function.

Register tasks in your project’s pyproject.toml using entry points

[project.entry-points."anvil.tasks"]
project = "tasks"

Note: Your project might need to be installed pip install --editable . You should see some path output via uv run python -c "import anvil; print(anvil.__file__)"

Implement the Task Contract

Each task module must define a callable run function. This is the minimum interface required for Anvil to discover and execute a task.

def run(
    *,
    account_id: str,
    account_alias: str,
    session,
    dry_run: bool,
    metadata: dict,
):
    """
    Execute the task for a single AWS account.
    """

Arguments

  • account_id - AWS account ID currently being processed.
  • account_alias - Friendly name of the account.
  • session - A boto3 Session already scoped to the target account.
  • dry_run - Indicates whether the task should make changes.
  • metadata - Organization metadata defined in the configuration file.

The return value is optional. Any returned data may be included in execution results.


Optional Helpers (Advanced Usage)

While only the run() function is required, tasks can optionally use Anvil-provided utilities to produce structured results or record actions.

For example, tasks may import helpers such as:

from anvil.task_definition import ActionRecorder

This helper allow tasks to:

  • record planned or executed actions
  • produce structured output for reporting
  • integrate with Anvil’s execution summaries

You can view examples of this here ActionRecorder

Using these utilities is not required, but recommended for tasks that modify infrastructure or need richer audit output.

Reference tasks in YAML

Once configured, custom tasks behave exactly like stock tasks:

tasks:
  - name: inventory
  - name: cleanup
    depends_on: [inventory]

About

A multi-organization AWS task execution framework that orchestrates dependency-aware operations across accounts with concurrency, fail-fast controls, and plugin-based task extensibility.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages