Skip to content

AIR CLI Integration: implement the air cancel command#5812

Open
riddhibhagwat-db wants to merge 5 commits into
air-integration-m1-2from
air-integration-m3-1
Open

AIR CLI Integration: implement the air cancel command#5812
riddhibhagwat-db wants to merge 5 commits into
air-integration-m1-2from
air-integration-m3-1

Conversation

@riddhibhagwat-db

Copy link
Copy Markdown

Summary

Ports the Python air CLI's handle_cancel to the Go air cancel command (stacked on air-integration-m1-2).

  • Cancel one or more runs by ID, or all of the current user's active runs with --all.
  • --all resolves the current user, lists their active runs via the Jobs-API run fetcher, shows a preview table, and prompts for confirmation unless -y is set (fetches up to maxListScan so every active run is cancelled).
  • Cancellation goes through the typed SDK w.Jobs.CancelRun.
  • Not-found is detected via errors.Is(apierr.ErrResourceDoesNotExist) and, for the cancel endpoint's 400 INVALID_PARAMETER_VALUE shape, a typed errors.As check — restoring the Python CLI's friendly "Run X not found" guidance.
  • Text and JSON (air envelope) output; exits non-zero on any failure.

Tests

  • Unit (cancel_test.go): arg validation, by-ID success/not-found (both error shapes), partial-failure JSON exit code, --all (no runs / confirm / abort / read error), current-user + list errors, and the preview table.
  • Acceptance (acceptance/experimental/air/cancel): by-ID text+JSON, multiple IDs, and --all -y.
  • Drops the now-implemented cancel case from the stub unit test and the unimplemented acceptance test.

This pull request and its description were written by Isaac.

Port the Python `handle_cancel` flow to the Go `air cancel` command:
fill in PreRunE and RunE on the existing milestone-0 stub.

- Cancel one or more runs by ID, or all of the current user's active runs
  with --all.
- --all resolves the current user, lists their active runs via the
  ListTrainingWorkflows RPC (reusing listAirRuns), shows a preview table,
  and prompts for confirmation unless -y is set.
- Cancellation goes through the typed SDK w.Jobs.CancelRun.
- Per-run failures are collected and reported; not-found is detected with
  errors.Is(apierr.ErrResourceDoesNotExist).
- Text and JSON (air envelope) output, exiting non-zero on any failure.

Tests (testserver route, unit, and acceptance) follow in phase 2.

Co-authored-by: Isaac
- Unit tests (cancel_test.go): arg validation, by-ID success/not-found,
  partial-failure JSON exit code, --all (no runs / confirm / abort), and
  the preview table.
- Acceptance test (acceptance/experimental/air/cancel): by-ID text+JSON,
  multiple IDs, and --all -y, against stubbed CancelRun and
  ListTrainingWorkflows responses.
- Drop the now-implemented cancel case from the stubs unit test and the
  unimplemented acceptance test.

Co-authored-by: Isaac
- Add unit tests for the remaining branches: generic (non-not-found)
  failure message, --all empty in JSON mode, --all current-user error,
  --all list error, and a confirm-prompt read error.
- Fix two error strings that were capitalized ("Failed to ..." ->
  "failed to ..."), per Go error-string convention.

cancel.go is now at ~93% line coverage; the only uncovered blocks are the
auth PreRunE wrapper (mirrors the get command, depends on real
MustWorkspaceClient) and a defensive renderEnvelope-error return.

Co-authored-by: Isaac
The Jobs cancel endpoint (POST /api/2.2/jobs/runs/cancel) returns
400 INVALID_PARAMETER_VALUE "Run <id> does not exist" for an unknown run.
The SDK only remaps that to apierr.ErrResourceDoesNotExist for the
runs/get path, so errors.Is(err, ErrResourceDoesNotExist) was false on
cancel and the tailored not-found message never fired (it fell through to
the generic "Failed to cancel run" branch).

Add runNotFound(err) which also matches the raw 400 INVALID_PARAMETER_VALUE
APIError via errors.AsType, restoring the Python CLI's friendly not-found
guidance. Covered by a new unit test.

Co-authored-by: Isaac
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/experimental/air/ - needs approval

6 files changed
Eligible: @apeforest, @bfontain, @lu-wang-dl, @panchalhp-db, @vinchenzo-db, @maggiewang-db, @ben-hansen-db, @pardis-beikzadeh-db

/experimental/air/ - needs approval

Files: experimental/air/cmd/cancel.go, experimental/air/cmd/cancel_test.go, experimental/air/cmd/stubs_test.go
Eligible: @apeforest, @bfontain, @lu-wang-dl, @panchalhp-db, @vinchenzo-db, @maggiewang-db, @ben-hansen-db, @pardis-beikzadeh-db

Any maintainer (@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db) can approve all areas.
See OWNERS for ownership rules.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: ecf2d77

Run: 28620386913

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🔄​ aws linux 2 10 13 259 1017 9:08
💚​ aws windows 10 13 263 1015 5:30
💚​ aws-ucws linux 10 13 357 931 5:55
💚​ aws-ucws windows 10 13 359 929 6:33
💚​ azure linux 4 15 264 1015 4:54
💚​ azure windows 4 15 266 1013 5:28
💚​ azure-ucws linux 4 15 362 927 7:28
💚​ azure-ucws windows 4 15 364 925 6:45
💚​ gcp linux 4 15 260 1018 5:37
💚​ gcp windows 4 15 262 1016 6:12
25 interesting tests: 13 SKIP, 10 RECOVERED, 2 flaky
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/replace_existing 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_projects/update_display_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestSecretsPutSecretBytesValue 🔄​f ✅​p 🙈​s 🙈​s ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p
🔄​ TestSecretsPutSecretStringValue 🔄​f ✅​p 🙈​s 🙈​s ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 22 slowest tests (at least 2 minutes):
duration env testname
5:02 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
4:20 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
4:12 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
4:05 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:30 aws windows TestSecretsPutSecretStringValue
3:25 aws linux TestSecretsPutSecretStringValue
3:25 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:23 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:22 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:11 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:08 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:02 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:47 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:47 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:47 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:46 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:40 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:40 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:38 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:38 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:34 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:31 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct

The list command exposes AIR runs via the Jobs-API run fetcher
(newRunFetcher/next), not the old ListTrainingWorkflows RPC. Point
`cancel --all` at it (fetching up to maxListScan active runs so every one
is cancelled, not just the first page), and use the Jobs-API fakes
(runsServer/runsListBody/airJobRun, jobs/runs/list) in the tests.

Co-authored-by: Isaac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants