feat(operator): support 'oabctl delete -f <manifest>', mirroring apply -f#1296
Conversation
…y -f
kubectl has a well-established, documented convention: 'kubectl apply -f'
and 'kubectl delete -f' both accept the same manifest file, so the exact
same file that created a resource can delete it — no need to separately
remember/type the resource type and name. oabctl's apply already followed
this ('apply -f <file>'), but delete required the imperative
'<resource> <name>' form, with no file-based option at all.
Add '-f/--file' to 'oabctl delete', accepting the same manifest file or
directory apply -f does (including OABFleet, which expands to multiple
services - all get deleted in turn, continuing past individual failures
so one broken/already-gone service doesn't block cleanup of the rest).
Mutually exclusive with <resource>/<name> (clap's conflicts_with_all).
--cluster/--namespace are ignored when using -f, since apply itself
never reads cluster from a manifest either (it's hardcoded to "oab");
namespace comes from each manifest's metadata.namespace instead, same
as apply.
Reuses the existing load_manifests/parse_manifest_file parsing (now
pub(crate)) and the existing per-service run() logic - no duplicated
delete logic between the two entry points.
Testing: cargo build/clippy --all-targets -D warnings/test clean on
macmini (33/33 tests pass, no regressions - no new pure logic to unit
test here, this is CLI wiring + reuse of existing tested paths).
Live E2E: ran 'oabctl delete -f /tmp/oab-telegram-ingress/manifest.yaml'
against a real running service - correctly scaled to 0, deleted the
ECS service, tore down ingress wiring (Cloud Map, HTTP API route/
integration), and cleaned up S3 manifest/config artifacts, identical
to the equivalent 'oabctl delete oabservice <name> --cluster oab
--namespace <ns>' invocation. Confirmed the traditional <resource>
<name> form and the new mutual-exclusion validation both still work
correctly.
This comment has been minimized.
This comment has been minimized.
Mirrors the terraform-style 'plan then confirm as it happens' pattern
(not a true in-place terminal checklist redraw, which requires ANSI
cursor control and breaks when output is piped/redirected/viewed in CI
logs - this stays plain sequential println!, safe everywhere):
Plan:
telegram-ingress-demo:
[ ] S3 manifest
[ ] ECS task definition
[ ] ECS service
[ ] Cloud Map namespace + service
[ ] VPC Link
...
Applying telegram-ingress-demo (ECS)...
✓ Cloud Map namespace exists: oab-vpc-1f5b7e78
✓ Cloud Map service exists: oab-prod-telegram-ingress-demo.oab-vpc-1f5b7e78
...
The plan is derived purely from each manifest's own fields (spec.ingress
presence/paths, spec.secrets keys) — it's read-only and informational,
never blocks execution, and doesn't need to predict create-vs-update or
skip-vs-act decisions made at runtime. The existing ✓/⚠ lines already
printed by apply_ecs/ensure_gateway/delete::run as each step actually
completes remain the single source of truth for real detail (resource
IDs, ARNs, URLs) — this only adds the upfront preview on top.
Applies to both and the new (#1296) - the
imperative form has no manifest to derive a
plan from, so it's unaffected.
Testing: cargo build/clippy --all-targets -D warnings/test clean on
macmini (33/33 tests pass, no regressions - purely additive println
output, no logic changed). Live E2E: ran both and
against the real manifest, confirmed the plan prints
correctly before each step, followed by the existing detailed
confirmation lines as each resource actually gets created/removed.
|
LGTM ✅ — Clean implementation of What This PR DoesAdds How It Works
Findings
What's Good (🟢)
Baseline Check
|
Per feedback: the upfront plan is meant to list AWS resources apply will touch, not the best-effort Telegram setWebhook call after them - drop it from print_plan. The actual registration logic and its existing '✓ Telegram webhook registered: ...' confirmation line are untouched.
|
LGTM ✅ — Clean, well-scoped feature addition that mirrors an established kubectl convention. What This PR Does
How It Works
Findings
Baseline Check
What's Good (🟢)
|
Per feedback across several iterations on the upfront plan/checklist feature: removed the [+]/[~]/[-] plan preview and the ✅ per-item confirmation lines entirely from both apply -f and delete -f — the existing ✓-style lines already printed as apply_ecs/ensure_gateway run were sufficient, and the plan/checklist added complexity without enough payoff (an earlier in-place-terminal-redraw attempt was also reverted after testing found it genuinely broken with pyte). Instead, made --wait's stabilization polling actually informative: previously it printed nothing between "Waiting for X to stabilize..." and "X is stable" even on a multi-minute deploy. Now prints each poll's real state as a composite status string, reusing the exact vocabulary ecsctl itself already uses for get/alias ls (github.com/oablab/ecsctl, src/alias.rs) - RUNNING, REPLACING(n->m) (new deployment's tasks still coming up), DRAINING(n+m) (new deployment up, old one still stopping), PENDING(n), PARTIAL(n/m), or the raw ECS service status as a fallback - instead of raw running_count/rollout_state fields. Testing: cargo build/clippy --all-targets -D warnings/test clean on macmini (33/33 tests pass, no regressions). Live E2E: ran apply -f against the real manifest, observed the full real transition sequence over a 160s rollout - DRAINING(0+1) -> REPLACING(0->1) -> DRAINING(1+1) -> DRAINING(1+0) -> RUNNING - matching the actual old task draining while the new one started, then the old task fully stopping before settling. Also documents (unrelated to this diff, discovered along the way): on this machine, macOS 26.5.1 (25F80) kills freshly-scp'd ad-hoc-signed binaries with SIGKILL (Code Signature Invalid) before they can even run - `codesign --force --sign - <binary>` after copying resolves it. Confirmed via crash report analysis (namespace CODESIGNING, dying in dyld's mach_o::UnsafeHeader::isMachO before main()) and cross-checked that the same binary runs fine directly on macmini - not a build or code issue, a local re-signing step this specific machine now needs.
|
LGTM ✅ — Clean, well-structured addition of What This PR DoesAdds How It Works
Findings
What's Good (🟢)
Baseline Check
|
Summary
kubectlhas a well-established convention:kubectl apply -f <file>andkubectl delete -f <file>both accept the exact same manifest, so tearingdown what you deployed doesn't require separately remembering/typing the
resource type and name.
oabctl applyalready followed this pattern(
apply -f <file>), butoabctl deleteonly supported the imperative<resource> <name>form — no file-based option at all, which is whatprompted this PR (came up while walking through manual teardown of a test
deployment).
Change
Adds
-f/--filetooabctl delete, mirroringapply -fexactly:oabctl delete -f /tmp/oab-telegram-ingress/manifest.yaml # equivalent to: oabctl delete oabservice telegram-ingress-demo --cluster oab --namespace prod<resource>/<name>(clap'sconflicts_with_all) —clear error if both are given, or if neither is given.
apply -f), and correctly expandsan
OABFleetmanifest into multiple services, deleting each in turn.--cluster/--namespaceare ignored when using-f— consistent withapply, which never reads a cluster from the manifest either (hardcoded to"oab"); namespace comes from each manifest'smetadata.namespace.gone) instead of aborting the whole batch, but still reports an overall
error at the end listing which ones failed.
load_manifests/parse_manifest_fileparsing (nowpub(crate), previously private toapply.rs) and the existing per-servicedelete::run().Testing done
No new pure logic to unit test here — this is CLI wiring plus reuse of
already-tested parsing/delete paths, matching the pattern of
apply::run/delete::runthemselves (neither has direct unit tests either; only theirpure helper functions do).
Live E2E: ran
oabctl delete -f /tmp/oab-telegram-ingress/manifest.yamlagainst a real running service. Output was identical to the equivalent
oabctl delete oabservice <name> --cluster oab --namespace <ns>invocation— scaled to 0, deleted the ECS service, tore down ingress wiring (Cloud Map
service, HTTP API route/integration), cleaned up S3 manifest/config
artifacts. Confirmed via
aws ecs describe-servicesthat the servicetransitioned to
DRAININGafterward. Also verified: the traditional<resource> <name>form still works unchanged, and both new validationerror paths (missing args,
-fcombined with<resource>/<name>) produceclear error messages.
Also updates
docs/oabctl.md's commands table and the ingress teardown noteto mention the new flag.
Update: upfront resource plan before apply/delete -f
Added a second commit: both
apply -fand this PR's newdelete -fnowprint an upfront
Plan:listing every resource that manifest may touch, asan unchecked
[ ]list, before any AWS calls happen — then each step'sexisting
✓/⚠confirmation line follows with the real detail (resourceID, ARN, URL) as it actually completes:
This is the terraform-style "plan, then confirm as it happens" pattern —
deliberately not a true in-place terminal checklist redraw (which needs ANSI
cursor control and breaks when output is piped, redirected, or viewed in CI
logs). Stays plain sequential
println!, safe everywhere.The plan is derived purely from each manifest's own fields (
spec.ingresspresence/paths,
spec.secretskeys) — read-only, informational, neverblocks execution, and doesn't try to predict create-vs-update or
skip-vs-act decisions made at runtime by the existing logic. The already
existing
✓/⚠lines remain the single source of truth for real detail;this only adds the upfront preview on top.
The imperative
delete oabservice <name>form has no manifest to derive aplan from, so it's unaffected — only the
-fpaths get a plan.Re-verified live: ran both
apply -fanddelete -fagain against thereal manifest with this change — plan prints correctly, followed by the
same detailed confirmations as before.