Skip to content

Add subscribe API support for azure#2118

Merged
edwardrf merged 7 commits into
mainfrom
edw/azure-subscribe
May 29, 2026
Merged

Add subscribe API support for azure#2118
edwardrf merged 7 commits into
mainfrom
edw/azure-subscribe

Conversation

@edwardrf
Copy link
Copy Markdown
Contributor

@edwardrf edwardrf commented May 19, 2026

Description

So defang cli only exits when all services are up correctly.

Linked Issues

#2073

Checklist

  • I have performed a self-review of my code
  • I have added appropriate tests
  • I have updated the Defang CLI docs and/or README to reflect my changes, if necessary

Summary by CodeRabbit

  • New Features

    • Real-time Azure BYOC subscription: live deployment and build events now aggregate container app revision and registry run status, with start-timestamp tracking for CD/deploy executions and new helpers to query revision and run state.
  • Tests

    • Expanded test suite for subscription behavior and state-mapping; replaced one subscribe test with a guard for subscribing without an active deployment.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77b8f63e-152e-4bc9-a09e-26cd00af6e53

📥 Commits

Reviewing files that changed from the base of the PR and between 07d91c7 and 445496e.

📒 Files selected for processing (1)
  • src/pkg/cli/client/byoc/azure/subscribe.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pkg/cli/client/byoc/azure/subscribe.go

📝 Walkthrough

Walkthrough

Adds Azure BYOC Subscribe: cloud helpers to read ACA revisions and ACR runs, ByocAzure cdStart tracking, a Subscribe implementation that multiplexes per-service revision and build pollers into an iterator, and integration/unit tests for mappings and polling.

Changes

Azure Deployment Event Streaming

Layer / File(s) Summary
Azure Container Apps revision and ACR run API helpers
src/pkg/clouds/azure/aca/revisions.go, src/pkg/clouds/azure/acr/runs.go
Adds RevisionState and GetRevisionState() for ACA revisions and implements RunInfo, RunsLister, and ListRunsSince() for paginated ACR run polling, plus IsTerminal()/IsSuccess() helpers.
ByocAzure CD execution start timestamp tracking
src/pkg/cli/client/byoc/azure/byoc.go, src/pkg/cli/client/byoc/azure/byoc_test.go
Adds cdStart time.Time to ByocAzure; CdCommand and deploy capture the timestamp before runCdCommand and persist it after receiving the execution name; removes the previous Subscribe stub; test updated to require active deployment for Subscribe.
Subscribe orchestration with concurrent revision and build pollers
src/pkg/cli/client/byoc/azure/subscribe.go
Implements Subscribe() validation (active CD run, optional ETag, non-empty services), then subscribe() launches per-service pollRevision() and a single pollBuilds() goroutine; maps ACA revision fields to deployment states and ACR run statuses to BUILD_* states, de-duplicates emissions, and closes the event stream after pollers exit.
Subscribe integration and unit tests
src/pkg/cli/client/byoc/azure/subscribe_test.go
Adds fast-poll test harness, fake revision/run clients scripting state sequences, drain() helper with timeout, integration tests for healthy transition, provisioning failure termination, and build lifecycle emission, plus unit tests for mapRevisionState and mapRunStatus.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • DefangLabs/defang#2087: Modifies Azure BYOC CD execution flow in byoc.go (CdCommand/deploy setup), overlapping with this PR's cdStart tracking during CD bootstrapping.

Suggested reviewers

  • jordanstephens
  • lionello

Poem

🐰 I nibble logs at break of day,
Polling revisions as they sway.
Builds and health in gentle hops,
cdStart marks the moment it pops.
Hooray — deployment tweets away! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add subscribe API support for azure' directly and clearly describes the main change: implementing the Subscribe API functionality for Azure BYOC providers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch edw/azure-subscribe

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=warning msg="The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2."
level=warning msg="Suggested new configuration:\nlinters:\n enable:\n - gomodguard_v2\n"
level=warning msg="[linters_context] running gomodguard failed: unable to read module file go.mod: current working directory must have a go.mod file: if you are not using go modules it is suggested to disable this linter"
level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/pkg/cli/client/byoc/azure/subscribe_test.go (1)

251-276: ⚡ Quick win

Collapse mapRevisionState unit tests into a table-driven test.

These three tests are repetitive and should be table-driven for consistency and easier case expansion.

As per coding guidelines: **/*_test.go: Use table-driven tests for comprehensive test coverage in unit tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pkg/cli/client/byoc/azure/subscribe_test.go` around lines 251 - 276,
Replace the three repetitive tests (TestMapRevisionState_NotFound,
TestMapRevisionState_Healthy, TestMapRevisionState_FailedProvisioning) with a
single table-driven test that iterates a slice of cases (each with a name, input
*aca.RevisionState, expected defangv1.ServiceState value, and expected terminal
bool), calling mapRevisionState for each case inside t.Run; include the three
cases: nil → UPDATE_QUEUED/terminal=false, &aca.RevisionState{NotFound:true} →
UPDATE_QUEUED/terminal=false, healthyRevision() →
DEPLOYMENT_COMPLETED/terminal=true, and &aca.RevisionState{ProvisioningState:
armappcontainersv3.RevisionProvisioningStateFailed} →
DEPLOYMENT_FAILED/terminal=true, and assert expected state and terminal for each
case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pkg/cli/client/byoc/azure/subscribe_test.go`:
- Line 230: The test currently ignores the error returned by drain when calling
got, _ := drain(t, t.Context(), subscribeInputs{...}); update the call in the
build-state test to capture and assert the error (e.g., err := drain(...)) and
fail the test when err != nil using the test helper (t.Fatal/t.Fatalf or the
test harness’s equivalent). Ensure you reference the drain function and
subscribeInputs invocation so the test fails on any stream/drain errors instead
of silently passing.

In `@src/pkg/cli/client/byoc/azure/subscribe.go`:
- Around line 139-144: The loop in subscribe.go currently treats ev.err
non-terminal by calling yield(nil, ev.err) and then continuing; change the
control flow so that after emitting an error via yield (when ev.err != nil) the
subscription loop breaks/returns immediately instead of continuing, ensuring
pollers stop and the iterator completes; locate the block handling ev.err in the
subscription loop (the section that calls yield(nil, ev.err)) and replace the
continue with a terminal exit (return or break) so errors are treated as
terminal by the Subscribe/iterator logic.

In `@src/pkg/clouds/azure/aca/revisions.go`:
- Around line 24-27: Wrap the raw errors returned from the credential/setup
calls with contextual messages using fmt.Errorf and %w: replace direct returns
of err from the c.NewCreds() call (the cred, err := c.NewCreds() path) and the
other setup return around the 35-38 block with something like fmt.Errorf("setup
<describe operation>: %w", err) so callers see which operation failed; add the
fmt import if missing and ensure the message names the operation (e.g.,
"creating Azure creds" or "initializing subscription").

In `@src/pkg/clouds/azure/acr/runs.go`:
- Around line 38-41: Wrap and annotate raw errors returned from setup/discovery
paths so callers keep context: in the functions/methods ensureClients (where you
call r.NewCreds()), findRegistry, and the ListRunsSince entry path, replace bare
"return err" with fmt.Errorf including a short contextual message and %w to wrap
the original error (e.g., "ensureClients: failed to get creds: %w"). Use
fmt.Errorf consistently for each failing call site referenced (including the
spots around the r.NewCreds() call and the other locations at the review comment
ranges) so the error chain preserves original error values while adding
operation context.
- Around line 109-117: The early-exit check uses a zero-valued create when
run.Properties.CreateTime is nil, causing create.Before(since) to be true and
prematurely stopping pagination; modify the logic in the block handling
run.Properties.CreateTime so the "if !since.IsZero() && create.Before(since)"
check only runs when run.Properties.CreateTime != nil (i.e., move the since
comparison inside the CreateTime non-nil branch or otherwise skip the
early-return when CreateTime is nil) to avoid skipping newer runs.

---

Nitpick comments:
In `@src/pkg/cli/client/byoc/azure/subscribe_test.go`:
- Around line 251-276: Replace the three repetitive tests
(TestMapRevisionState_NotFound, TestMapRevisionState_Healthy,
TestMapRevisionState_FailedProvisioning) with a single table-driven test that
iterates a slice of cases (each with a name, input *aca.RevisionState, expected
defangv1.ServiceState value, and expected terminal bool), calling
mapRevisionState for each case inside t.Run; include the three cases: nil →
UPDATE_QUEUED/terminal=false, &aca.RevisionState{NotFound:true} →
UPDATE_QUEUED/terminal=false, healthyRevision() →
DEPLOYMENT_COMPLETED/terminal=true, and &aca.RevisionState{ProvisioningState:
armappcontainersv3.RevisionProvisioningStateFailed} →
DEPLOYMENT_FAILED/terminal=true, and assert expected state and terminal for each
case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89863da9-0416-4dd9-a371-2c745552b027

📥 Commits

Reviewing files that changed from the base of the PR and between 5c42faa and 209ef0f.

📒 Files selected for processing (6)
  • src/pkg/cli/client/byoc/azure/byoc.go
  • src/pkg/cli/client/byoc/azure/byoc_test.go
  • src/pkg/cli/client/byoc/azure/subscribe.go
  • src/pkg/cli/client/byoc/azure/subscribe_test.go
  • src/pkg/clouds/azure/aca/revisions.go
  • src/pkg/clouds/azure/acr/runs.go

Comment thread src/pkg/cli/client/byoc/azure/subscribe_test.go Outdated
Comment thread src/pkg/cli/client/byoc/azure/subscribe.go Outdated
Comment thread src/pkg/clouds/azure/aca/revisions.go
Comment thread src/pkg/clouds/azure/acr/runs.go
Comment thread src/pkg/clouds/azure/acr/runs.go Outdated
@edwardrf edwardrf linked an issue May 19, 2026 that may be closed by this pull request
Comment thread src/pkg/cli/client/byoc/azure/subscribe.go Outdated
CD success/failure is already detected by WaitForCdTaskExit in
TailAndMonitor, which calls ByocAzure.GetDeploymentStatus on a separate
goroutine and cancels svcStatusCtx on failure. Tracking CD state inside
Subscribe meant we emitted the same failure twice and gated revision-
completed events on a signal the caller already observes independently.

Removes pollCD, cdGate, the pendingReady gating in the aggregator, the
post-CD deadline in pollRevision, and allServicesTerminal. Flattens
subscribeEvent into a plain chan since pollCD was the only emitter of
the err and cdSucceeded fields.

The producer no longer self-terminates; cleanup relies on the consumer
cancelling the parent ctx (TailAndMonitor does this on command exit).
Documented in the Subscribe godoc.

Smoke-tested on real Azure: happy path, CD failure, and revision failure
all behave correctly. The CD-failure path now surfaces solely via
WaitForCdTaskExit → ErrDeploymentFailed{Message: "CD job ...: Failed"}
with Pulumi context preserved.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/pkg/cli/client/byoc/azure/subscribe_test.go (1)

70-89: ⚡ Quick win

Let the test harness assert stream errors explicitly.

drain hard-fails on the first iterator error, so this suite still can't cover the non-NotFound provider failures from GetRevisionState / ListRunsSince. Returning the terminal error would make it possible to add the missing provider-error cases for this new path.

As per coding guidelines, "Add tests for new behavior and important failure modes: not-found vs other errors, duplicate adds, missing returns, blocking behavior, provider mismatches, and optional-file handling".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pkg/cli/client/byoc/azure/subscribe_test.go` around lines 70 - 89, The
test helper drain currently calls subscribe(ctx, in) and t.Fatalf on the first
iterator error, preventing tests from asserting terminal stream errors; change
drain to return ([]*defangv1.SubscribeResponse, error) instead of failing:
iterate over subscribe(ctx, in), collect responses into got, and if the iterator
yields a non-nil err return got, err (or nil if iteration completed normally);
update the drain signature and all test call sites to expect and assert the
returned error so provider failures from GetRevisionState/ListRunsSince can be
explicitly asserted; keep references to subscribe, subscribeInputs, and
defangv1.SubscribeResponse when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pkg/cli/client/byoc/azure/subscribe.go`:
- Around line 99-119: The iterator currently drops all errors because eventCh is
typed as chan *defangv1.SubscribeResponse and pollBuilds/pollRevision only log
failures; change the design so errors are delivered to the caller: either
convert eventCh to carry a union (e.g., a struct with Response
*defangv1.SubscribeResponse and Err error) or add a separate errCh that
pollBuilds and pollRevision can send real errors on; update pollBuilds,
pollRevision, the anonymous wg.Wait goroutine, and the loop that reads from
eventCh to handle received errors (wrap and return them instead of retrying/only
logging), and ensure proper closing of channels and wg usage so errors are
propagated out of the subscribe iterator via yield/return rather than swallowed.

---

Nitpick comments:
In `@src/pkg/cli/client/byoc/azure/subscribe_test.go`:
- Around line 70-89: The test helper drain currently calls subscribe(ctx, in)
and t.Fatalf on the first iterator error, preventing tests from asserting
terminal stream errors; change drain to return ([]*defangv1.SubscribeResponse,
error) instead of failing: iterate over subscribe(ctx, in), collect responses
into got, and if the iterator yields a non-nil err return got, err (or nil if
iteration completed normally); update the drain signature and all test call
sites to expect and assert the returned error so provider failures from
GetRevisionState/ListRunsSince can be explicitly asserted; keep references to
subscribe, subscribeInputs, and defangv1.SubscribeResponse when making these
changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57db65d4-284e-4d09-8df5-9f94670ea950

📥 Commits

Reviewing files that changed from the base of the PR and between 373ec42 and 07d91c7.

📒 Files selected for processing (2)
  • src/pkg/cli/client/byoc/azure/subscribe.go
  • src/pkg/cli/client/byoc/azure/subscribe_test.go

Comment thread src/pkg/cli/client/byoc/azure/subscribe.go Outdated
The 16-slot buffer was a holdover from the pre-refactor multi-channel
design. All sends already select on ctx.Done(), so an unbuffered channel
is correct, and at this volume (5s tick, a few producers) the latency
difference is unobservable.
@edwardrf edwardrf merged commit e496a4a into main May 29, 2026
15 checks passed
@edwardrf edwardrf deleted the edw/azure-subscribe branch May 29, 2026 20:29
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.

Azure: support etag, for monitoring (Subscribe API)

4 participants