atc

Airflow Traffic Control — a terminal control tower for Apache Airflow 3 environments that live behind awkward network paths.

PyPI version CI status MIT license GitHub stars

Try it in 60 seconds GitHub 简体中文

  dev-k8s │ staging │ prod ⊘ │ mwaa-sit                        atc · airflow 3
 ────────────────────────────────────────────────────────────────────────────
  ◉ dev-k8s    k8s ▸ demo-cluster ▸ airflow-demo ▸ :54312    ● 78ms    v3.1.0
  ★ watching 5     ✗ failing: vendor_feed_import
 ────────────────────────────────────────────────────────────────────────────
  DAG                          SCHED        LAST 20 RUNS         LAST       Ø
  ingest_orders_to_warehouse   @hourly      ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ ✓ 12m ago  8m42s
  sync_customers_to_crm        0 2 * * *    ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▒ ◐ running  9m15s
  vendor_feed_import           @hourly      ▇▇▇▇▇▇▇▇▇▇▇▇▚▇▇▇▇▇▇▚ ✗ 9m ago   7m36s
  rebuild_search_index         @daily       ▇▇▇▇·▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ ✓ 4h ago   22m04s
  export_finance_extract       0 6 * * 1-5  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ ✓ 31m ago  1m08s
 ────────────────────────────────────────────────────────────────────────────
  tab next env   1-9 jump   a add   f unstar   ⏎ runs   t trigger   q quit

The problem

Production Airflow rarely sits on a public URL. It runs inside a private EKS cluster, or on AWS MWAA with the webserver access mode set to private. Checking whether last night's DAG failed then means a manual kubectl port-forward, a bastion host, a VPN, or an AWS console round-trip — every single time.

atc collapses that into one terminal session. It owns the tunnel, mints and renews the token, and keeps the connection path — which environment, reached by which mechanism, at what latency — on screen at all times, so you never trigger a DAG against the wrong environment.

Quickstart

No AWS account, no cluster, no config file, no network:

uvx airflow-atc --fake

That launches the full TUI against a deterministic fake environment. Tabs, the watch list, run history, the empty-log finding and the trigger confirmation all behave exactly as they do against a real Airflow. It is also what the entire test suite runs on.

Install it properly

# as a tool on your PATH
uv tool install airflow-atc

# or with pip
pip install airflow-atc

atc configure     # discovery wizard
atc               # go

Requirements: Python 3.11+. For k8s profiles you need kubectl on your PATH and a working context. For mwaa profiles you need AWS credentials resolvable through the normal chain (profile, environment, SSO, instance role).

What you get

🛰️ The connection path, always on screen

k8s ▸ cluster ▸ namespace ▸ :port ● 78ms v3.1.0 sits above everything. You can never be unsure which environment you are pointed at.

🔌 A tunnel that heals itself

One kubectl port-forward for the whole session. If it dies, atc notices, rebuilds it, and drops the token bound to the dead socket.

🔑 MWAA auth that falls back

Tries mwaa:InvokeRestApi; on a permissions gap falls back to CreateWebLoginToken. Automatic, and pinnable per profile.

🗂️ Tabs, not reconnects

One tab per environment with its own connection, watch list and cursor. Tabs connect lazily — starting atc never dials everything you configured.

⭐ O(favourites), not O(DAGs)

The main screen is a starred watch list. A real environment has hundreds of DAGs; rendering all of them costs one call each and tells you nothing.

🕵️ An empty log is a finding

When Airflow returns nothing for a failed task, atc says so — and on Kubernetes reads the log off the scheduler's PVC instead of showing a blank pane.

🛑 Read-only, marked before you touch it

readonly = true puts ⊘ on the tab and refuses the trigger key outright. The one write path sits behind a confirmation modal.

🧪 Tests that need no credentials

Everything the UI consumes goes through an 8-method provider protocol, so the whole suite — headless UI tests included — runs with no AWS, cluster or network.

Configure

Run atc configure. It discovers your kubectl contexts and namespaces, your AWS profiles and your MWAA environments, and writes ~/.config/atc/config.toml. Running atc with no configuration at all launches the same wizard, so there is no "file not found" dead end.

Or write the config by hand
default = "dev-k8s"

[profiles.dev-k8s]
type      = "k8s"
context   = "YOUR-KUBE-CONTEXT"
namespace = "YOUR-AIRFLOW-NAMESPACE"
service   = "airflow-api-server-cluster-ip-service"
port      = 8080
secret    = "airflow-secret"

[profiles.mwaa-prd]
type        = "mwaa"
environment = "YOUR-MWAA-ENV-NAME"
region      = "ap-southeast-2"
aws_profile = "YOUR-AWS-PROFILE"
strategy    = "auto"        # auto | invoke_rest_api | web_login_token
readonly    = true          # ⊘ on the tab; `t` is refused outright

No secrets go in this file. Kubernetes admin passwords are read from a cluster secret at runtime; AWS credentials come from the standard chain.

How it is built

Everything the UI consumes goes through one 8-method AirflowProvider protocol. That single seam is why --fake exists, why the headless UI tests need no credentials, and why supporting a new Airflow deployment shape means writing one file rather than touching the TUI.

tui/app.py  ·  tui/render.py        Textual screens + a pure renderer
      │
      ▼  AirflowProvider (8 methods)
core/provider.py                    RestApiProvider · Airflow 3 · /api/v2
      │
      ├── providers/k8s.py          self-healing port-forward, JWT, PVC log fallback
      ├── providers/mwaa.py         InvokeRestApi → CreateWebLoginToken fallback
      └── providers/fake.py         --fake, and the whole test suite

The dependency direction is strictly one-way: tui → core → providers. core/models.py imports nothing from the project, and tui/render.py imports only rich and the models — which is what makes the formatting layer unit-testable on its own.

FAQ

Does atc support Airflow 2?

No. atc is Airflow 3 only and speaks /api/v2. An Airflow 2 environment is refused by name rather than failing obscurely. There are no Airflow 2 fallbacks by design.

Can atc break my production environment?

There is exactly one call that mutates a remote Airflow: trigger(). It is gated by a confirmation modal and refused outright on profiles marked readonly = true. Everything else is read-only.

Does it replace the Airflow web UI?

No, and it does not try to. There is no graph view, no Gantt chart, no DAG authoring. atc is for the operating loop: what is failing, what did the log say, run it again.

Where do my credentials go?

Nowhere. Kubernetes admin passwords are read from a cluster secret at runtime, AWS credentials come from the standard chain, and MWAA web sessions are cached in memory for 12 hours. config.toml holds no secrets.

Is it safe to run against production?

Mark the profile readonly = true. The tab then carries a ⊘ marker before you are looking at it, and the trigger key is refused rather than confirmed.

ResourceWhat is there
GitHub repositorySource, issues, releases
PyPI · airflow-atcReleased wheels and sdists
README 简体中文Full Chinese documentation
llms.txtStructured summary for LLMs and AI agents
CONTRIBUTING.mdLayer boundaries and the FakeProvider testing rule
CHANGELOG.mdWhat changed, per release