Skip to content

Commit 1341799

Browse files
committed
Migrate Azure Blob adapter from abandoned SDK to azure-oss/storage
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>
1 parent 9a03d8e commit 1341799

9 files changed

Lines changed: 376 additions & 348 deletions

File tree

build.xml

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -504,18 +504,48 @@
504504
</fileset>
505505
</copy>
506506

507-
<!-- microsoft -->
508-
<copy file="${basedir}/vendor/microsoft/azure-storage-blob/LICENSE"
509-
tofile="${basedir}/build/phar/lib/microsoft/azure-storage-blob/LICENSE"/>
510-
<copy todir="${basedir}/build/phar/lib/microsoft/azure-storage-blob">
511-
<fileset dir="${basedir}/vendor/microsoft/azure-storage-blob/src">
507+
<!-- azure-oss (Azure Blob Storage SDK) -->
508+
<copy todir="${basedir}/build/phar/lib/azure-oss/storage">
509+
<fileset dir="${basedir}/vendor/azure-oss/storage">
512510
<include name="**/*.php"/>
513511
</fileset>
514512
</copy>
515-
<copy file="${basedir}/vendor/microsoft/azure-storage-common/LICENSE"
516-
tofile="${basedir}/build/phar/lib/microsoft/azure-storage-common/LICENSE"/>
517-
<copy todir="${basedir}/build/phar/lib/microsoft/azure-storage-common">
518-
<fileset dir="${basedir}/vendor/microsoft/azure-storage-common/src">
513+
<copy todir="${basedir}/build/phar/lib/azure-oss/storage-common">
514+
<fileset dir="${basedir}/vendor/azure-oss/storage-common">
515+
<include name="**/*.php"/>
516+
</fileset>
517+
</copy>
518+
<copy file="${basedir}/vendor/azure-oss/identity/LICENSE"
519+
tofile="${basedir}/build/phar/lib/azure-oss/identity/LICENSE"/>
520+
<copy todir="${basedir}/build/phar/lib/azure-oss/identity">
521+
<fileset dir="${basedir}/vendor/azure-oss/identity/src">
522+
<include name="**/*.php"/>
523+
</fileset>
524+
</copy>
525+
526+
<!-- caseyamcl/guzzle_retry_middleware (azure-oss dependency) -->
527+
<copy file="${basedir}/vendor/caseyamcl/guzzle_retry_middleware/LICENSE.md"
528+
tofile="${basedir}/build/phar/lib/caseyamcl/guzzle_retry_middleware/LICENSE.md"/>
529+
<copy todir="${basedir}/build/phar/lib/caseyamcl/guzzle_retry_middleware">
530+
<fileset dir="${basedir}/vendor/caseyamcl/guzzle_retry_middleware/src">
531+
<include name="**/*.php"/>
532+
</fileset>
533+
</copy>
534+
535+
<!-- symfony/deprecation-contracts (azure-oss dependency) -->
536+
<copy file="${basedir}/vendor/symfony/deprecation-contracts/LICENSE"
537+
tofile="${basedir}/build/phar/lib/symfony/deprecation-contracts/LICENSE"/>
538+
<copy todir="${basedir}/build/phar/lib/symfony/deprecation-contracts">
539+
<fileset dir="${basedir}/vendor/symfony/deprecation-contracts">
540+
<include name="**/*.php"/>
541+
</fileset>
542+
</copy>
543+
544+
<!-- symfony/polyfill-php80 (azure-oss dependency) -->
545+
<copy file="${basedir}/vendor/symfony/polyfill-php80/LICENSE"
546+
tofile="${basedir}/build/phar/lib/symfony/polyfill-php80/LICENSE"/>
547+
<copy todir="${basedir}/build/phar/lib/symfony/polyfill-php80">
548+
<fileset dir="${basedir}/vendor/symfony/polyfill-php80">
519549
<include name="**/*.php"/>
520550
</fileset>
521551
</copy>

build/phar-manifest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
"google/apiclient",
2929
"guzzlehttp/guzzle",
3030
"kunalvarma05/dropbox-php-sdk",
31-
"microsoft/azure-storage-blob",
31+
"azure-oss/storage",
32+
"azure-oss/storage-common",
33+
"azure-oss/identity",
3234
"php-opencloud/openstack",
3335
"phpmailer/phpmailer",
3436
"phpseclib/phpseclib",

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@
6363
"vlucas/phpdotenv": "^4.0",
6464
"google/apiclient":"^2.7",
6565
"php-opencloud/openstack": "^3.0",
66-
"microsoft/azure-storage-blob": "^1.4",
6766
"phpmailer/phpmailer": "^6.0",
68-
"google/cloud-storage": "^1.42"
67+
"google/cloud-storage": "^1.42",
68+
"azure-oss/storage": "^1.9"
6969
},
7070
"suggest": {
7171
"sebastianfeldmann/ftp": "Require ^0.9.2 to sync to an FTP server",
@@ -77,7 +77,7 @@
7777
"php-opencloud/openstack": "Require ^3.0 to sync to OpenStack",
7878
"vlucas/phpdotenv": "Require ^3.0 or ^4.0 or ^5.0 to use the Dotenv adapter",
7979
"google/apiclient":"Require ^2.0 to sync to Google Drive",
80-
"microsoft/azure-storage-blob": "Require ^1.4 to sync to Azure Blob Storage",
80+
"azure-oss/storage": "Require ^1.9 to sync to Azure Blob Storage",
8181
"phpmailer/phpmailer": "Require ^6.0 to receive logs via email",
8282
"google/cloud-storage": "Require ^1.42 to sync to Google Cloud Storage"
8383
},

src/Backup/Collector/AzureBlob.php

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<?php
22
namespace phpbu\App\Backup\Collector;
33

4-
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
5-
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
4+
use AzureOss\Storage\Blob\BlobContainerClient;
65
use phpbu\App\Backup\Collector;
76
use phpbu\App\Backup\File\AzureBlob as BlobFile;
87
use phpbu\App\Backup\Path;
@@ -24,52 +23,50 @@
2423
class AzureBlob extends Remote implements Collector
2524
{
2625
/**
27-
* @var \MicrosoftAzure\Storage\Blob\BlobRestProxy
26+
* @var \AzureOss\Storage\Blob\BlobContainerClient
2827
*/
2928
protected $client;
3029

3130
/**
32-
* Azure Blob Storage Container name
33-
*
34-
* @var string
35-
*/
36-
protected $containerName;
37-
38-
/**
39-
* Amazon S3 constructor.
31+
* AzureBlob constructor.
4032
*
4133
* @param \phpbu\App\Backup\Target $target
4234
* @param \phpbu\App\Backup\Path $path
43-
* @param \MicrosoftAzure\Storage\Blob\BlobRestProxy $client
44-
* @param string $containerName
35+
* @param \AzureOss\Storage\Blob\BlobContainerClient $client
4536
*/
46-
public function __construct(Target $target, Path $path, BlobRestProxy $client, string $containerName)
37+
public function __construct(Target $target, Path $path, BlobContainerClient $client)
4738
{
4839
$this->setUp($target, $path);
4940
$this->client = $client;
50-
$this->containerName = $containerName;
5141
}
5242

5343
/**
5444
* Collect all created backups.
5545
*/
5646
protected function collectBackups()
5747
{
58-
$listBlobsOptions = new ListBlobsOptions();
59-
$listBlobsOptions->setPrefix($this->getPrefix($this->path->getPathThatIsNotChanging()));
60-
$listBlobsOptions->setMaxResults(10);
48+
$prefix = $this->getPrefix($this->path->getPathThatIsNotChanging());
6149

62-
do {
63-
$blobList = $this->client->listBlobs($this->containerName, $listBlobsOptions);
64-
foreach ($blobList->getBlobs() as $blob) {
65-
if ($this->isFileMatch($blob->getName())) {
66-
$file = new BlobFile($this->client, $this->containerName, $blob);
67-
$index = $this->getFileIndex($file);
68-
$this->files[$index] = $file;
69-
}
50+
foreach ($this->listBlobs($prefix) as $blob) {
51+
if ($this->isFileMatch($blob->name)) {
52+
$file = new BlobFile($this->client, $blob);
53+
$index = $this->getFileIndex($file);
54+
$this->files[$index] = $file;
7055
}
71-
$listBlobsOptions->setContinuationToken($blobList->getContinuationToken());
72-
} while ($blobList->getContinuationToken());
56+
}
57+
}
58+
59+
/**
60+
* List all blobs in the container matching the given prefix.
61+
*
62+
* Pagination is handled transparently by the Azure Blob SDK.
63+
*
64+
* @param string $prefix
65+
* @return iterable<\AzureOss\Storage\Blob\Models\Blob>
66+
*/
67+
protected function listBlobs(string $prefix): iterable
68+
{
69+
return $this->client->getBlobs($prefix);
7370
}
7471

7572
/**

src/Backup/File/AzureBlob.php

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?php
22
namespace phpbu\App\Backup\File;
33

4-
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
5-
use MicrosoftAzure\Storage\Blob\Models\Blob;
4+
use AzureOss\Storage\Blob\BlobContainerClient;
5+
use AzureOss\Storage\Blob\Models\Blob;
66
use phpbu\App\Exception;
77

88
/**
@@ -20,33 +20,25 @@
2020
class AzureBlob extends Remote
2121
{
2222
/**
23-
* Azure Blob client.
23+
* Azure Blob container client.
2424
*
25-
* @var BlobRestProxy
25+
* @var BlobContainerClient
2626
*/
2727
private $client;
2828

29-
/**
30-
* @var string
31-
*/
32-
protected $containerName;
33-
3429
/**
3530
* AzureBlob constructor.
3631
*
37-
* @param BlobRestProxy $client
38-
* @param string $containerName
39-
* @param Blob $blob
32+
* @param BlobContainerClient $client
33+
* @param Blob $blob
4034
*/
41-
public function __construct(BlobRestProxy $client, string $containerName, Blob $blob)
35+
public function __construct(BlobContainerClient $client, Blob $blob)
4236
{
43-
$this->client = $client;
44-
$this->containerName = $containerName;
45-
$this->filename = basename($blob->getName());
46-
$this->pathname = $blob->getName();
47-
$props = $blob->getProperties();
48-
$this->size = $props->getContentLength();
49-
$this->lastModified = $props->getLastModified()->getTimestamp();
37+
$this->client = $client;
38+
$this->filename = basename($blob->name);
39+
$this->pathname = $blob->name;
40+
$this->size = $blob->properties->contentLength;
41+
$this->lastModified = $blob->properties->lastModified->getTimestamp();
5042
}
5143

5244
/**
@@ -57,9 +49,17 @@ public function __construct(BlobRestProxy $client, string $containerName, Blob $
5749
public function unlink()
5850
{
5951
try {
60-
$this->client->deleteBlob($this->containerName, $this->pathname);
52+
$this->deleteBlob();
6153
} catch (\Exception $exception) {
6254
throw new Exception($exception->getMessage());
6355
}
6456
}
57+
58+
/**
59+
* Delete the blob from the container.
60+
*/
61+
protected function deleteBlob(): void
62+
{
63+
$this->client->getBlobClient($this->pathname)->delete();
64+
}
6565
}

src/Backup/Sync/AzureBlob.php

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<?php
22
namespace phpbu\App\Backup\Sync;
33

4-
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
4+
use AzureOss\Storage\Blob\BlobContainerClient;
5+
use AzureOss\Storage\Blob\BlobServiceClient;
56
use phpbu\App\Backup\Collector;
67
use phpbu\App\Backup\Path;
78
use phpbu\App\Backup\Target;
@@ -25,9 +26,9 @@ class AzureBlob implements Simulator
2526
use Cleanable;
2627

2728
/**
28-
* Azure Blob client.
29+
* Azure Blob container client.
2930
*
30-
* @var BlobRestProxy;
31+
* @var BlobContainerClient
3132
*/
3233
private $client;
3334

@@ -77,9 +78,9 @@ class AzureBlob implements Simulator
7778
*/
7879
public function setup(array $config)
7980
{
80-
if (!class_exists('\\MicrosoftAzure\\Storage\\Blob\\BlobRestProxy')) {
81-
throw new Exception('Azure Bob Storage SDK not loaded: use composer to install ' .
82-
'"microsoft/azure-storage-blob"');
81+
if (!class_exists('\\AzureOss\\Storage\\Blob\\BlobServiceClient')) {
82+
throw new Exception('Azure Blob Storage SDK not loaded: use composer to install ' .
83+
'"azure-oss/storage"');
8384
}
8485

8586
// check for mandatory options
@@ -122,7 +123,7 @@ public function sync(Target $target, Result $result)
122123
{
123124
$this->client = $this->createClient();
124125

125-
if (!$this->doesContainerExist($this->client, $this->containerName)) {
126+
if (!$this->doesContainerExist($this->client)) {
126127
$result->debug('create blob container');
127128
$this->createContainer($this->client);
128129
}
@@ -139,13 +140,14 @@ public function sync(Target $target, Result $result)
139140
}
140141

141142
/**
142-
* Create the Azure Blob client.
143+
* Create the Azure Blob container client.
143144
*
144-
* @return \MicrosoftAzure\Storage\Blob\BlobRestProxy
145+
* @return \AzureOss\Storage\Blob\BlobContainerClient
145146
*/
146-
protected function createClient() : BlobRestProxy
147+
protected function createClient() : BlobContainerClient
147148
{
148-
return BlobRestProxy::createBlobService($this->connectionString);
149+
return BlobServiceClient::fromConnectionString($this->connectionString)
150+
->getContainerClient($this->containerName);
149151
}
150152

151153
/**
@@ -157,7 +159,7 @@ protected function createClient() : BlobRestProxy
157159
protected function createCollector(Target $target) : Collector
158160
{
159161
$path = new Path($this->pathRaw, $this->time);
160-
return new Collector\AzureBlob($target, $path, $this->client, $this->containerName);
162+
return new Collector\AzureBlob($target, $path, $this->client);
161163
}
162164

163165
/**
@@ -180,47 +182,36 @@ public function simulate(Target $target, Result $result)
180182
/**
181183
* Check if an Azure Blob Storage Container exists
182184
*
183-
* @param \MicrosoftAzure\Storage\Blob\BlobRestProxy $blobRestProxy
184-
* @param string $containerName
185+
* @param \AzureOss\Storage\Blob\BlobContainerClient $client
185186
* @return bool
186187
*/
187-
private function doesContainerExist(BlobRestProxy $blobRestProxy, string $containerName): bool
188+
protected function doesContainerExist(BlobContainerClient $client): bool
188189
{
189-
$containers = $blobRestProxy->listContainers()->getContainers();
190-
foreach ($containers as $container) {
191-
if ($container->getName() === $containerName) {
192-
return true;
193-
}
194-
}
195-
return false;
190+
return $client->exists();
196191
}
197192

198193
/**
199194
* Create an Azure Storage Container
200195
*
201-
* @param \MicrosoftAzure\Storage\Blob\BlobRestProxy $blobRestProxy
196+
* @param \AzureOss\Storage\Blob\BlobContainerClient $client
202197
*/
203-
private function createContainer(BlobRestProxy $blobRestProxy)
198+
protected function createContainer(BlobContainerClient $client)
204199
{
205-
$blobRestProxy->createContainer($this->containerName);
200+
$client->create();
206201
}
207202

208203
/**
209204
* Upload backup to Azure Blob Storage
210205
*
211-
* @param \phpbu\App\Backup\Target $target
212-
* @param \MicrosoftAzure\Storage\Blob\BlobRestProxy $blobRestProxy
206+
* @param \phpbu\App\Backup\Target $target
207+
* @param \AzureOss\Storage\Blob\BlobContainerClient $client
213208
* @throws \phpbu\App\Backup\Sync\Exception
214209
* @throws \phpbu\App\Exception
215210
*/
216-
private function upload(Target $target, BlobRestProxy $blobRestProxy)
211+
protected function upload(Target $target, BlobContainerClient $client)
217212
{
218213
$source = $this->getFileHandle($target->getPathname(), 'r');
219-
$blobRestProxy->createBlockBlob(
220-
$this->containerName,
221-
$this->getUploadPath($target),
222-
$source
223-
);
214+
$client->getBlobClient($this->getUploadPath($target))->upload($source);
224215
}
225216

226217
/**

0 commit comments

Comments
 (0)