Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Storage] Async iterator for listing blobs and containers - storage-blob #3136

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9524d20
Async iterator for container - listBlobs
HarshaNalluru May 22, 2019
eecb12a
remove iter in the generator
HarshaNalluru May 22, 2019
28e8773
Add marker in the generator
HarshaNalluru May 22, 2019
dc03069
yield -> yield *
HarshaNalluru May 22, 2019
bb41616
Add sample using `.next().value`
HarshaNalluru May 23, 2019
da1afa5
containerClient - *listContainers
HarshaNalluru May 23, 2019
34e3772
udpdate - remove resumable from comment
HarshaNalluru May 23, 2019
eb3b1ca
Addressed comments
HarshaNalluru May 23, 2019
72cd066
Add description for options attribute
HarshaNalluru May 23, 2019
7d7cb2f
remove process.env
HarshaNalluru May 23, 2019
7922584
un-specify the type Models.xxx
HarshaNalluru May 24, 2019
d1cf6d0
remove !options
HarshaNalluru May 24, 2019
c8128b5
rectify indentation
HarshaNalluru May 24, 2019
49c1da6
Merge branch 'AsyncIteratorsStorageBlob' of https://github.com/Harsha…
HarshaNalluru May 24, 2019
c272c6a
listBlobs -> listBlobsFlat
HarshaNalluru May 28, 2019
5b07b44
Add test for ListContainers
HarshaNalluru May 28, 2019
cdd3d2e
remove .only
HarshaNalluru May 28, 2019
43d30c0
Add test for listBlobsFlat
HarshaNalluru May 28, 2019
bc932b3
resolve merge conflicts
HarshaNalluru May 29, 2019
3fb743a
Merge remote-tracking branch 'upstream/feature/storage' into AsyncIte…
HarshaNalluru May 30, 2019
842de33
Add test case for listBlobsFlat - for loop syntax
HarshaNalluru May 30, 2019
d7b7f71
Add test case for listContainers - for loop syntax
HarshaNalluru May 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions sdk/storage/storage-blob/samples/typescript/iterators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Setup: Enter your storage account name and shared key in main()
*/

import {
ContainerClient,
BlobServiceClient,
StorageClient,
SharedKeyCredential,
BlobClient,
BlockBlobClient
} from "../../src"; // Change to "@azure/storage-blob" in your package

async function main() {
// Enter your storage account name and shared key
const account = "";
const accountKey = "";

// Use SharedKeyCredential with storage account and account key
const sharedKeyCredential = new SharedKeyCredential(account, accountKey);

// Use sharedKeyCredential, tokenCredential or anonymousCredential to create a pipeline
const pipeline = StorageClient.newPipeline(sharedKeyCredential);

// List containers
const blobServiceClient = new BlobServiceClient(
// When using AnonymousCredential, following url should include a valid SAS or support public access
`https://${account}.blob.core.windows.net`,
pipeline
);

// List Containers
let iter1 = await blobServiceClient.listContainers();
let i = 1;
for await (const container of iter1) {
console.log(`Container ${i++}: ${container.name}`);
}

// List containers - generator syntax
let iter2 = await blobServiceClient.listContainers();
i = 1;
let containerItem = await iter2.next();
do {
console.log(`Container ${i++}: ${containerItem.value.name}`);
containerItem = await iter2.next();
} while (containerItem.value);

// Create a container
const containerName = `newcontainer${new Date().getTime()}`;
const containerClient = ContainerClient.fromBlobServiceClient(blobServiceClient, containerName);

const createContainerResponse = await containerClient.create();
console.log(`Created container ${containerName} successfully`, createContainerResponse.requestId);

for (let index = 0; index < 4; index++) {
// Create a blob
let content = "hello";
let blobName = "newblob" + new Date().getTime();
let blobClient = BlobClient.fromContainerClient(containerClient, blobName);
let blockBlobClient = BlockBlobClient.fromBlobClient(blobClient);
let uploadBlobResponse = await blockBlobClient.upload(content, content.length);
console.log(`Uploaded block blob ${blobName} successfully`, uploadBlobResponse.requestId);
}

// List blobs
iter1 = await containerClient.listBlobsFlat();
i = 1;
for await (const blob of iter1) {
console.log(`Blob ${i++}: ${blob.name}`);
}

// List blobs - generator syntax
iter2 = await containerClient.listBlobsFlat();
i = 1;
let blobItem = await iter2.next();
do {
console.log(`Blob ${i++}: ${blobItem.value.name}`);
blobItem = await iter2.next();
} while (blobItem.value);
}

// An async method returns a Promise object, which is compatible with then().catch() coding style.
main()
.then(() => {
console.log("Successfully executed the sample.");
})
.catch((err) => {
console.log(err.message);
});
47 changes: 47 additions & 0 deletions sdk/storage/storage-blob/src/BlobServiceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,53 @@ export class BlobServiceClient extends StorageClient {
});
}

/**
* Iterates over containers under the specified account.
*
* @param {ServiceListContainersSegmentOptions} [options={}] Options to list containers(optional)
* @returns {AsyncIterableIterator<Models.ContainerItem>}
* @memberof BlobServiceClient
*
* @example
* for await (const container of blobServiceClient.listContainers()) {
Copy link
Member

Choose a reason for hiding this comment

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

Just wondering what will happen if Aborter aborted iteration

Copy link
Member Author

Choose a reason for hiding this comment

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

Example -

  // List containers - generator syntax
  let iter2 = await blobServiceClient.listContainers({ abortSignal: Aborter.timeout(X) });
  i = 1;
  let containerItem = await iter2.next();
  do {
    console.log(`Container ${i++}: ${containerItem.value.name}`);
    containerItem = await iter2.next();
  } while (containerItem.value);

[ About 6500 containers are present in the account ]

  • When X = 50000 (large number), all the 6500 containers got listed(and their names are printed in the terminal)
  • When X = 2000, only 5000 containers got listed and a message got printed saying The request was aborted.
    image
    [ X = 10000 yielded the same result ]
  • When X < 1000, none of the containers got listed, The request was aborted is printed in the terminal.

* console.log(`Container: ${container.name}`);
* }
*
* @example
* let iter1 = blobServiceClient.listContainers();
* let i = 1;
* for await (const container of iter1) {
* console.log(`${i}: ${container.name}`);
* i++;
* }
*
* @example
* let iter2 = await blobServiceClient.listContainers();
* i = 1;
* let containerItem = await iter2.next();
* do {
* console.log(`Container ${i++}: ${containerItem.value.name}`);
* containerItem = await iter2.next();
* } while (containerItem.value);
*
*/
public async *listContainers(
jeremymeng marked this conversation as resolved.
Show resolved Hide resolved
options: ServiceListContainersSegmentOptions = {}
): AsyncIterableIterator<Models.ContainerItem> {
let marker = undefined;
const blobServiceClient = this;
const aborter = !options.abortSignal ? Aborter.none : options.abortSignal;
let listContainersResponse;
do {
listContainersResponse = await blobServiceClient.listContainersSegment(marker, {
...options,
abortSignal: aborter
});
marker = listContainersResponse.nextMarker;
yield* listContainersResponse.containerItems;
} while (marker);
}

/**
* Returns a list of the containers under the specified account.
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/list-containers2
Expand Down
47 changes: 47 additions & 0 deletions sdk/storage/storage-blob/src/ContainerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,53 @@ export class ContainerClient extends StorageClient {
});
}

/**
* Iterates over blobs under the specified container.
*
* @param {ContainerListBlobsSegmentOptions} [options={}] Options to list blobs(optional)
* @returns {AsyncIterableIterator<Models.BlobItem>}
* @memberof ContainerClient
*
* @example
* for await (const blob of containerClient.listBlobs()) {
* console.log(`Container: ${blob.name}`);
* }
*
* @example
* let iter1 = containerClient.listBlobs();
* let i = 1;
* for await (const blob of iter1) {
* console.log(`${i}: ${blob.name}`);
* i++;
* }
*
* @example
* let iter2 = await containerClient.listBlobs();
* i = 1;
* let blobItem = await iter2.next();
* do {
* console.log(`Blob ${i++}: ${blobItem.value.name}`);
* blobItem = await iter2.next();
* } while (blobItem.value);
*
*/
public async *listBlobsFlat(
options: ContainerListBlobsSegmentOptions = {}
): AsyncIterableIterator<Models.BlobItem> {
let marker = undefined;
const containerClient = this;
const aborter = !options.abortSignal ? Aborter.none : options.abortSignal;
let listBlobsResponse;
do {
listBlobsResponse = await containerClient.listBlobFlatSegment(marker, {
...options,
abortSignal: aborter
});
marker = listBlobsResponse.nextMarker;
yield* listBlobsResponse.segment.blobItems;
} while (marker);
}

/**
* listBlobFlatSegment returns a single segment of blobs starting from the
* specified Marker. Use an empty Marker to start enumeration from the beginning.
Expand Down
73 changes: 73 additions & 0 deletions sdk/storage/storage-blob/test/blobserviceclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,79 @@ describe("BlobServiceClient", () => {
await containerClient2.delete();
});

it("Verify AsyncIterator(generator .next() syntax) for ListContainers", async () => {
const blobServiceClient = getBSU();

const containerNamePrefix = getUniqueName("container");
const containerName1 = `${containerNamePrefix}x1`;
const containerName2 = `${containerNamePrefix}x2`;
const containerClient1 = blobServiceClient.createContainerClient(containerName1);
const containerClient2 = blobServiceClient.createContainerClient(containerName2);
await containerClient1.create({ metadata: { key: "val" } });
await containerClient2.create({ metadata: { key: "val" } });

const iterator = await blobServiceClient.listContainers({
include: "metadata",
prefix: containerNamePrefix
});

let containerItem = await iterator.next();
assert.ok(containerItem.value.name.startsWith(containerNamePrefix));
assert.ok(containerItem.value.properties.etag.length > 0);
assert.ok(containerItem.value.properties.lastModified);
assert.ok(!containerItem.value.properties.leaseDuration);
assert.ok(!containerItem.value.properties.publicAccess);
assert.deepEqual(containerItem.value.properties.leaseState, "available");
assert.deepEqual(containerItem.value.properties.leaseStatus, "unlocked");
assert.deepEqual(containerItem.value.metadata!.key, "val");

containerItem = await iterator.next();
assert.ok(containerItem.value.name.startsWith(containerNamePrefix));
assert.ok(containerItem.value.properties.etag.length > 0);
assert.ok(containerItem.value.properties.lastModified);
assert.ok(!containerItem.value.properties.leaseDuration);
assert.ok(!containerItem.value.properties.publicAccess);
assert.deepEqual(containerItem.value.properties.leaseState, "available");
assert.deepEqual(containerItem.value.properties.leaseStatus, "unlocked");
assert.deepEqual(containerItem.value.metadata!.key, "val");

await containerClient1.delete();
await containerClient2.delete();
});

it("Verify AsyncIterator(for-loop syntax) for ListContainers", async () => {
const containerClients = [];
const blobServiceClient = getBSU();

const containerNamePrefix = getUniqueName("container");

for (let i = 0; i < 4; i++) {
const containerName = `${containerNamePrefix}x${i}`;
const containerClient = blobServiceClient.createContainerClient(containerName);
await containerClient.create({ metadata: { key: "val" } });
containerClients.push(containerClient);
}

for await (const container of blobServiceClient.listContainers({
include: "metadata",
prefix: containerNamePrefix,
maxresults: 2
})) {
assert.ok(container.name.startsWith(containerNamePrefix));
assert.ok(container.properties.etag.length > 0);
assert.ok(container.properties.lastModified);
assert.ok(!container.properties.leaseDuration);
assert.ok(!container.properties.publicAccess);
assert.deepEqual(container.properties.leaseState, "available");
assert.deepEqual(container.properties.leaseStatus, "unlocked");
assert.deepEqual(container.metadata!.key, "val");
}

for (const client of containerClients) {
await client.delete();
}
});

it("GetProperties", async () => {
const blobServiceClient = getBSU();
const result = await blobServiceClient.getProperties();
Expand Down
Loading