Skip to content

feat: runtime event subscription management (ARM control-plane facade)#293

Open
we4sz wants to merge 3 commits into
pm7y:masterfrom
we4sz:feat/runtime-event-subscription-management
Open

feat: runtime event subscription management (ARM control-plane facade)#293
we4sz wants to merge 3 commits into
pm7y:masterfrom
we4sz:feat/runtime-event-subscription-management

Conversation

@we4sz

@we4sz we4sz commented Jun 14, 2026

Copy link
Copy Markdown

Summary

Adds an optional ARM control-plane (management) API so the real Azure.ResourceManager.EventGrid client can create, get, list and delete topic-scoped WebHook event subscriptions at runtime against the simulator — simply by repointing the client at a new managementPort. Today subscribers can only be configured at boot via appsettings.json; this closes the gap for apps that register/unregister subscriptions programmatically (e.g. self-registering webhooks) and want local/CI parity without touching their business code.

Disabled by default — if managementPort is not set, nothing changes.

Why

Azure Event Grid has two separate APIs: the data plane (the topic endpoint that receives events — already simulated) and the control plane (ARM, management.azure.com, where subscriptions are managed at runtime). The simulator only had the data plane, so code that manages subscriptions via Azure.ResourceManager.EventGrid had to be stubbed out locally. This adds a faithful-enough control-plane facade so that client works unmodified.

How

  • New managementPort setting hosts a dedicated HTTPS listener, isolated from the per-topic data-plane pipeline via an EventGridMiddleware bypass — mirroring how Azure splits management.azure.com from the topic endpoint.
  • EventSubscriptionsManagementController speaks the exact HTTP the SDK emits (the TopicEventSubscriptions operation group, api-version 2025-02-15), with synchronous, LRO-compatible responses so WaitUntil.Completed finalizes immediately without polling.
  • ARM ⇄ HttpSubscriberSettings mapper (WebHook destinations), plus an in-memory, thread-safe copy-on-write subscriber registry seeded from config at boot — so the existing delivery path picks up runtime changes with no change to delivery code.
  • Validation handshake on create: runtime-created WebHook subscriptions run the normal SubscriptionValidationEvent handshake before they receive events. The outbound handshake was extracted from ValidateAllSubscriptionsCommandHandler into a reusable SubscriptionValidationSender, now shared by the boot-time validator and the management API.
  • Follows existing conventions: custom mediator (command + handler), thin controllers, [JsonPropertyName] settings, CSharpier, Constants.SupportedManagementApiVersion.

Scope / fidelity

Intentionally pragmatic — covers exactly what real callers use:

  • Topic scope, WebHook destinations only. Domains/system/namespace/partner topics, non-WebHook destinations via ARM, true async provisioning operations, and AAD token validation are out of scope (the bearer token is accepted but not validated — this is a local dev simulator).
  • Runtime subscriptions are in-memory and reset on restart (seeded from config).
  • The WebHook endpointUrl is echoed back on GET (Azure treats it write-only) so the read-modify-write update pattern works against the simulator.

Config

{
  "managementPort": 60100,
  "topics": [ { "name": "MyTopic", "port": 60101, "key": "TheLocal+DevelopmentKey=" } ]
}
var options = new ArmClientOptions
{
    Environment = new ArmEnvironment(new Uri("https://localhost:60100/"), "https://management.azure.com"),
};
var arm = new ArmClient(credential, subscriptionId, options); // any token; not validated
await arm.GetEventGridTopicResource(new ResourceIdentifier(topicResourceId))
    .GetTopicEventSubscriptions()
    .CreateOrUpdateAsync(WaitUntil.Completed, "my-sub", data);

Testing

Driven by the real Azure.ResourceManager.EventGrid client against an actual running simulator instance:

  • AzureResourceManagerEventGridTest — full create / get / list / delete round-trip (proves wire compatibility + LRO finalization).
  • RuntimeSubscriptionDeliveryTest — end-to-end: create a subscription, publish an event, assert it is delivered to the runtime-created webhook, delete, assert delivery stops.
  • Unit tests for the ARM↔subscriber mapper, the registry (including concurrent mutate-while-enumerate), and managementPort validation.

Full suite: 924 passing, 0 failing. README updated with a "Runtime subscription management" section.

Checklist

  • Tests pass (dotnet test)
  • Build succeeds (dotnet build)
  • Commit messages use conventional format (feat:, fix:, chore:, etc.)
  • Documentation updated (README)

🤖 Generated with Claude Code

…agement

Adds an optional ARM management API so the real Azure.ResourceManager.EventGrid
client can create, get, list and delete topic-scoped WebHook event subscriptions
against the simulator at runtime, simply by repointing the client at a new
managementPort. Previously subscribers could only be configured at boot.

- New `managementPort` setting hosts a dedicated HTTPS listener, isolated from the
  per-topic data-plane pipeline (EventGridMiddleware bypass), mirroring how Azure
  splits management.azure.com from the topic endpoint.
- EventSubscriptionsManagementController speaks the exact HTTP the SDK emits
  (TopicEventSubscriptions, api-version 2025-02-15), with synchronous
  LRO-compatible responses so WaitUntil.Completed finalizes immediately.
- ARM <-> HttpSubscriberSettings mapper (WebHook only); in-memory, thread-safe
  copy-on-write subscriber registry seeded from config; bearer token not validated.
- Runtime-created subscriptions run the normal validation handshake before
  delivery (outbound handshake extracted into SubscriptionValidationSender, reused
  by the boot-time validator).

Verified with integration tests driven by the real Azure.ResourceManager.EventGrid
client: full create/get/list/delete round-trip, plus an end-to-end test proving a
runtime-created subscription receives published events and stops after deletion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@we4sz we4sz requested a review from pm7y as a code owner June 14, 2026 10:45
we4sz and others added 2 commits June 17, 2026 09:51
…ement

The ARM control-plane facade (management-port event-subscription PUT/GET/LIST/DELETE)
previously accepted only WebHook destinations — a storage-queue eventSubscriptions PUT
returned 400 (UnsupportedDestination) — even though the simulator already delivers to
storage queues for statically-configured subscribers (StorageQueueEventDeliveryService
+ RetryDeliveryBackgroundService).

Extend the four runtime operations to also handle StorageQueue destinations: a runtime
storage-queue subscription is upserted into topic.Subscribers.StorageQueue (inheriting
the topic-level storageQueueConnectionString) and then delivered by the existing path.
No webhook validation handshake (that is webhook-only). List/Get/Delete now see both
subscriber kinds. The ARM destination-properties bag carries resourceId/queueName
alongside the webhook endpointUrl. +4 mapper unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant