Skip to content

Migrate Azure Blob adapter from abandoned SDK to azure-oss/storage#402

Merged
sebastianfeldmann merged 3 commits into
sebastianfeldmann:mainfrom
CybotTM:feature/migrate-azure-oss-storage
Jun 20, 2026
Merged

Migrate Azure Blob adapter from abandoned SDK to azure-oss/storage#402
sebastianfeldmann merged 3 commits into
sebastianfeldmann:mainfrom
CybotTM:feature/migrate-azure-oss-storage

Conversation

@CybotTM

@CybotTM CybotTM commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Why

The Azure Blob sync adapter depends on microsoft/azure-storage-blob, which Microsoft has officially abandoned (Packagist abandoned: true, no Composer replacement, capped at 1.5.4; repo archived Nov 2023). The maintained community successor is azure-oss/storage (active — 1.9.0, 2026-03; PHP ^8.1, compatible with phpbu's >=8.1).

What changed

Three classes consumed the old MicrosoftAzure\Storage\Blob\BlobRestProxy god-object; they now use the azure-oss fluent client hierarchy (BlobServiceClientBlobContainerClientBlobClient):

  • Backup\Sync\AzureBlob — builds a BlobContainerClient from the connection string; container check/create uses exists() / create() (replacing listContainers() + createContainer()); upload via getBlobClient()->upload($stream).
  • Backup\Collector\AzureBlob — lists via BlobContainerClient::getBlobs($prefix). The SDK paginates transparently, so the manual ListBlobsOptions + continuation-token loop is removed. The collector now receives the container client directly.
  • Backup\File\AzureBlob — reads name / contentLength / lastModified from the Blob model's readonly properties; deletes via getBlobClient()->delete().

composer.json require-dev + suggest swapped to azure-oss/storage: ^1.9. End-user config (connection_string, container_name, path) is unchanged. The adapters follow the AmazonS3v3 sibling conventions (protected $client, thin protected seams).

Tests

Tests use phpbu's standard PHPUnit mock pattern (createPartialMock / getMockBuilder->onlyMethods), matching AmazonS3v3Test. Because the azure-oss client classes are final, the mocks target the adapter's own protected seams rather than the SDK client, and the values passed to those seams (upload path, list prefix, deleted blob path) are asserted via with() matchers. simulate() is verified to mask the credential-bearing connection string. Full suite green locally (901 tests); PSR-2 (phpcs) and PHPStan level 3 clean on the changed files.

Phar build — built & smoke-tested

build.xml / build/phar-manifest.php updated to bundle azure-oss/storage + its deps (azure-oss/storage-common, azure-oss/identity, caseyamcl/guzzle_retry_middleware, symfony/deprecation-contracts, symfony/polyfill-php80). I built the phar with ant phar and ran an azureblob sync through it in --simulate: it loaded AzureOss\Storage\Blob\BlobServiceClient and emitted connectionString: ********. Note: azure-oss/storage + storage-common ship no LICENSE file, so I omitted their LICENSE copy tasks — the redistributed phar should still carry the MIT notice (worth adding a LICENSE / upstream issue).

Independent review

I ran multi-perspective reviews (correctness, reliability, security, conformity, test coverage) and applied the conformity + coverage findings (the test pattern above, sibling-aligned visibility, SDK-argument assertions). A few suggestions I intentionally did not apply, to stay consistent with the AmazonS3v3 sibling and keep this a faithful migration — flagging them for your call:

  • Wrap createClient() / container existence-check in a try/catch so SDK exceptions surface as Sync\Exception. Declined: AmazonS3v3::sync() is structured identically (client creation + bucket check outside the try); matching the sibling. Failures still propagate loudly.
  • Use createIfNotExists() for the container (removes a check-then-create race). The old code and AmazonS3v3 both use check-then-create; kept the faithful port. azure-oss offers the safer primitive if you'd prefer it.
  • Sanitize SDK exception messages (a SAS-token connection string could surface in a re-wrapped exception on a connect-level error; AccountKey auth is unaffected — key is header-only). Real but cross-cutting — AmazonS3v3 has the same property — so this is an adapter-wide logging-policy decision rather than something to fix only here.
  • Pin php-http/discovery in allow-plugins (it's an install-time composer-plugin pulled transitively via azure-oss/identity; not reached at runtime on the shared-key path). Flagged as a project-wide install-policy choice.

CybotTM added 2 commits June 20, 2026 16:42
Microsoft abandoned the microsoft/azure-storage-* libraries with no
Composer replacement. Migrate the Azure Blob sync adapter, collector and
file classes from MicrosoftAzure\Storage\Blob\BlobRestProxy to the
maintained azure-oss/storage SDK (BlobServiceClient / BlobContainerClient
/ BlobClient).

- Sync\AzureBlob: build a BlobContainerClient from the connection string;
  use exists()/create() instead of listContainers()/createContainer();
  upload via getBlobClient()->upload().
- Collector\AzureBlob: list via BlobContainerClient::getBlobs() (the SDK
  paginates transparently, so the manual continuation-token loop is gone);
  the collector now receives the container client directly.
- File\AzureBlob: read name/size/last-modified from the Blob model's
  readonly properties; delete via getBlobClient()->delete().

The azure-oss client classes are final and cannot be mocked, so the tests
drive the real adapter logic through small seams (createClient, listBlobs,
deleteBlob, ...) and build the offline BlobContainerClient and Blob models
directly. Container existence, the create/skip-create branch, upload,
exception wrapping, blob listing/filtering and deletion are covered.

Also update the phar build (build.xml, build/phar-manifest.php) to bundle
azure-oss/storage and its dependencies instead of microsoft/azure-storage-*.

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
- Narrow the upload seam (uploadBlob) so the real upload() body and
  getUploadPath() composition run under test; assert the computed blob path.
- Assert the listBlobs() prefix and the deleted blob pathname so a wrong
  prefix / wrong-target regression is caught.
- Replace brittle exact debug() call-count expectations with assertions on
  the actual log messages and resulting state.
- Assert simulate() masks the credential-bearing connection string
  (connectionString: ********) and never logs the raw value.

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
@sebastianfeldmann

Copy link
Copy Markdown
Owner

Well done, looks solid!

Replace the in-file Testable* subclass doubles with phpbu's standard
PHPUnit mock pattern (createPartialMock / getMockBuilder->onlyMethods),
matching the sibling AmazonS3v3 tests. The azure-oss client is final, so
the mocks target the adapter's own protected seams rather than the SDK
client; coverage of the orchestration is preserved.

Assert the values passed to the SDK seams via with() matchers instead of
capturing internal state: the upload path, the list prefix and the
deleted blob path are now verified, and the File fixture uses a nested
blob name so pathname and filename are distinguishable.

Also align the adapters with the AmazonS3v3 sibling: $client is now
protected (Sync and File), and File\AzureBlob::deleteBlob() takes the
blob path as an argument.

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
@CybotTM CybotTM marked this pull request as ready for review June 20, 2026 15:56
@sebastianfeldmann sebastianfeldmann merged commit 35541f8 into sebastianfeldmann:main Jun 20, 2026
4 checks passed
CybotTM added a commit to netresearch/phpbu-docker that referenced this pull request Jun 20, 2026
#154)

Removes the abandoned `microsoft/azure-storage-blob` SDK from the
**full** image by adopting phpbu 6.0.32's migration to the maintained
`azure-oss/storage`.

## Background

`microsoft/azure-storage-blob` is abandoned upstream (Microsoft archived
it; Packagist `abandoned: true`, no replacement). It was a direct
require in the full image only because phpbu's Azure Blob sync adapter
was hardwired to it. That's now fixed at the root: phpbu migrated the
adapter to `azure-oss/storage` in
[sebastianfeldmann/phpbu#402](sebastianfeldmann/phpbu#402),
released in [phpbu
6.0.32](https://github.com/sebastianfeldmann/phpbu/releases/tag/6.0.32).

## What changed (`app/full/composer.json` + lock)

- `phpbu/phpbu` `^6.0` → `^6.0.32` (the release whose Azure adapter uses
azure-oss).
- Direct require `microsoft/azure-storage-blob: ^1.4` →
`azure-oss/storage: ^1.9` (phpbu lists azure-oss as
`suggest`/`require-dev`, so the full image still declares it explicitly
to enable the adapter).
- `composer update` removed `microsoft/azure-storage-blob` +
`microsoft/azure-storage-common` entirely; pulled `azure-oss/storage`
1.9.0, `azure-oss/storage-common`, `azure-oss/identity`.

## Verification

- `composer validate` clean; **no abandoned packages remain in the
lock**; `composer audit` reports no advisories.
- phpbu 6.0.32's `azureblob` sync runs in `--simulate` under PHP 8.5:
loads `AzureOss\Storage\Blob\BlobServiceClient` (no "SDK not loaded")
and masks the credential (`connectionString: ********`).
- The Security Scan (Trivy) runs on this PR and rebuilds the full image
— it should now be clean of the abandonment finding.

## Supersedes

This replaces the documentation-only workaround in #153 (which explained
why the abandoned package was kept). With the package removed, that note
is obsolete — closing #153 in favour of this fix.
@sebastianfeldmann

Copy link
Copy Markdown
Owner

Thanks! Great job!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants