Skip to content

Add AzureBlobPersistenceProvider (RiftCache.Providers.Azure) - #13

Merged
vectorloom merged 2 commits into
mainfrom
feat/azure-blob-persistence-provider
Aug 15, 2026
Merged

vectorloom merged 2 commits into
mainfrom
feat/azure-blob-persistence-provider

Conversation

@vectorloom

Copy link
Copy Markdown
Owner

Second of the two Azure reference providers -- last remaining Phase 1 item after this is the Bicep deployment.

Unlike Key Vault, Azure Blob Storage has an official local emulator (Azurite), and CONTRIBUTING.md explicitly requires provider tests to run against a local emulator in CI, not just unit tests. So this one gets both: fast unit tests against a hand-faked BlobContainerClient (same subclassing approach as FakeSecretClient, no mocking framework), and real integration tests against Azurite, now wired into .github/workflows/ci.yml as a service container.

Design:

  • Blob content = the cached bytes directly, inspectable, no envelope format. AbsoluteExpiration/SlidingExpiration live in blob metadata instead (round-trip ISO 8601 / seconds). LastAccessed isn't persisted -- InMemoryCacheStore re-Touch()es on promotion from persistence, so a stored value would never be read.
  • Blob name is the cache key, Uri.EscapeDataString-encoded -- reusing the exact encoding CacheEndpoints.cs and RiftCacheClient.cs already use for the same keys.
  • Doesn't create the container, same stance as AzureKeyVaultSecretProvider not creating the vault -- provisioning is the Bicep work's job.
  • AddAzureBlobPersistenceProvider(containerUri, credential?) mirrors the Key Vault extension's shape exactly: Managed-Identity-first via DefaultAzureCredential.

Verified locally end to end, not just assumed: ran Azurite via Podman, hit a real connectivity snag first (port bound IPv6-loopback-only by the Podman machine's forwarder -- explicit -p 127.0.0.1:10000:10000 fixed it), then confirmed all three integration tests pass against the live emulator before wiring CI. GitHub Actions runners are Linux, so that IPv4/IPv6 quirk is Windows/Podman-specific and won't recur there.

Also reflected over the installed 12.29.1 assembly (same lesson from the Key Vault work: don't assume Azure SDK overload/dispatch behavior) to confirm BlobClient.UploadAsync/DownloadContentAsync/DeleteIfExistsAsync each resolve to a single unambiguous virtual overload -- no repeat of the GetSecretAsync surprise.

Also fixed a stale doc-comment reference to ARCHITECTURE_NOTES.md (renamed to ARCHITECTURE.md several PRs ago) noticed while re-reading IPersistenceProvider.cs, and flipped Blob Storage from "not built yet" to available in docs/providers/azure.md and ROADMAP.md.

vectorloom and others added 2 commits August 14, 2026 21:41
Second of the two Azure reference providers -- last remaining Phase 1
item after this is the Bicep deployment.

Unlike Key Vault, Azure Blob Storage has an official local emulator
(Azurite), and CONTRIBUTING.md explicitly requires provider tests to
run against a local emulator in CI, not just unit tests. So this one
gets both: fast unit tests against a hand-faked BlobContainerClient
(same subclassing approach as FakeSecretClient, no mocking framework),
and real integration tests against Azurite, now wired into
.github/workflows/ci.yml as a service container.

Design:
- Blob content = the cached bytes directly, inspectable, no envelope
  format. AbsoluteExpiration/SlidingExpiration live in blob metadata
  instead (round-trip ISO 8601 / seconds). LastAccessed isn't
  persisted -- InMemoryCacheStore re-Touch()es on promotion from
  persistence, so a stored value would never be read.
- Blob name is the cache key, Uri.EscapeDataString-encoded -- reusing
  the exact encoding CacheEndpoints.cs and RiftCacheClient.cs already
  use for the same keys.
- Doesn't create the container, same stance as AzureKeyVaultSecretProvider
  not creating the vault -- provisioning is the Bicep work's job.
- AddAzureBlobPersistenceProvider(containerUri, credential?) mirrors
  the Key Vault extension's shape exactly: Managed-Identity-first via
  DefaultAzureCredential.

Verified locally end to end, not just assumed: ran Azurite via Podman,
hit a real connectivity snag first (port bound IPv6-loopback-only by
the Podman machine's forwarder -- explicit `-p 127.0.0.1:10000:10000`
fixed it), then confirmed all three integration tests pass against the
live emulator before wiring CI. GitHub Actions runners are Linux, so
that IPv4/IPv6 quirk is Windows/Podman-specific and won't recur there.

Also reflected over the installed 12.29.1 assembly (same lesson from
the Key Vault work: don't assume Azure SDK overload/dispatch behavior)
to confirm BlobClient.UploadAsync/DownloadContentAsync/DeleteIfExistsAsync
each resolve to a single unambiguous virtual overload -- no repeat of
the GetSecretAsync surprise.

Also fixed a stale doc-comment reference to ARCHITECTURE_NOTES.md
(renamed to ARCHITECTURE.md several PRs ago) noticed while re-reading
IPersistenceProvider.cs, and flipped Blob Storage from "not built yet"
to available in docs/providers/azure.md and ROADMAP.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PR checks failed: "The API version 2026-06-06 is not supported by
Azurite." The SDK's default service version floats to whatever's
newest (12.29.1's default is V2026_06_06); Azurite lags behind and
rejects anything it doesn't recognize unless --skipApiVersionCheck is
passed. My local Podman testing happened to include that flag, masking
this exact failure -- GitHub Actions' `services:` block has no
command-override field to pass it to a service container, so it must
be fixed on the client side instead.

Reproduced locally first (Azurite without --skipApiVersionCheck) to
confirm the real cause before touching anything, then pinned both the
integration tests and the production AddAzureBlobPersistenceProvider
extension to BlobClientOptions.ServiceVersion.V2024_08_04. Applied to
production too, not just tests: real Azure Storage supports old API
versions for years so there's no functional downside, and it means
AddAzureBlobPersistenceProvider works against a local Azurite instance
out of the box for anyone testing before deploying to real Azure --
not just this repo's own test suite.

Verified against a strict (no-skip-flag) local Azurite before pushing:
all 3 integration tests now pass under the same condition CI hits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vectorloom
vectorloom merged commit 5be4e66 into main Aug 15, 2026
2 checks passed
@vectorloom
vectorloom deleted the feat/azure-blob-persistence-provider branch August 16, 2026 15:00
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