diff --git a/docs/docs/04-standard-library/compatibility/compatibility.json b/docs/docs/04-standard-library/compatibility/compatibility.json index 8d9db443026..8d541222ba8 100644 --- a/docs/docs/04-standard-library/compatibility/compatibility.json +++ b/docs/docs/04-standard-library/compatibility/compatibility.json @@ -129,7 +129,7 @@ "copy": { "sim": { "implemented": true }, "tf-aws": { "implemented": true }, - "tf-azure": { "implemented": false, "issue": 4397 }, + "tf-azure": { "implemented": true }, "tf-gcp": { "implemented": false, "issue": 4398 }, "awscdk": { "implemented": true } }, diff --git a/examples/tests/sdk_tests/bucket/copy.test.w b/examples/tests/sdk_tests/bucket/copy.test.w index 96a1207cbf3..40138aa0ecd 100644 --- a/examples/tests/sdk_tests/bucket/copy.test.w +++ b/examples/tests/sdk_tests/bucket/copy.test.w @@ -15,7 +15,7 @@ test "copy()" { assert(error); }; let UNEXISTING_KEY = "no-such-file.txt"; - let OBJECT_DOES_NOT_EXIST_ERROR = "Unable to copy. Source object does not exist (srcKey=${UNEXISTING_KEY})."; + let OBJECT_DOES_NOT_EXIST_ERROR = "Source object does not exist (srcKey=${UNEXISTING_KEY})."; let KEY1 = "file1.main.w"; let VALUE1 = "bring cloud;"; diff --git a/libs/wingsdk/src/shared-aws/bucket.inflight.ts b/libs/wingsdk/src/shared-aws/bucket.inflight.ts index 82b1104dbcb..264ee2ab267 100644 --- a/libs/wingsdk/src/shared-aws/bucket.inflight.ts +++ b/libs/wingsdk/src/shared-aws/bucket.inflight.ts @@ -278,9 +278,7 @@ export class BucketClient implements IBucketClient { await this.s3Client.send(command); } catch (error) { if (error instanceof NotFound) { - throw new Error( - `Unable to copy. Source object does not exist (srcKey=${srcKey}).` - ); + throw new Error(`Source object does not exist (srcKey=${srcKey}).`); } throw error; } diff --git a/libs/wingsdk/src/target-sim/bucket.inflight.ts b/libs/wingsdk/src/target-sim/bucket.inflight.ts index 4000c3d0fcc..50250ceb5fa 100644 --- a/libs/wingsdk/src/target-sim/bucket.inflight.ts +++ b/libs/wingsdk/src/target-sim/bucket.inflight.ts @@ -256,9 +256,7 @@ export class Bucket implements IBucketClient, ISimulatorResourceInstance { message: `Copy (srcKey=${srcKey} to dstKey=${dstKey}).`, activity: async () => { if (!this.objectKeys.has(srcKey)) { - throw new Error( - `Unable to copy. Source object does not exist (srcKey=${srcKey}).` - ); + throw new Error(`Source object does not exist (srcKey=${srcKey}).`); } const dstValue = await this.get(srcKey); diff --git a/libs/wingsdk/src/target-tf-azure/bucket.inflight.ts b/libs/wingsdk/src/target-tf-azure/bucket.inflight.ts index e7fbec4e945..cb66f0535c4 100644 --- a/libs/wingsdk/src/target-tf-azure/bucket.inflight.ts +++ b/libs/wingsdk/src/target-tf-azure/bucket.inflight.ts @@ -264,16 +264,21 @@ export class BucketClient implements IBucketClient { } /** - * Copy object within the container + * Copy object within the bucket * * @param srcKey The key of the source object you wish to copy. * @param dstKey The key of the destination object after copying. * @throws if `srcKey` object doesn't exist. */ public async copy(srcKey: string, dstKey: string): Promise { - return Promise.reject( - `copy is not implemented: (srcKey=${srcKey}, dstKey=${dstKey})` - ); + const srcBlobUrl = this.containerClient.getBlobClient(srcKey).url; + const dstBlobClient = this.containerClient.getBlockBlobClient(dstKey); + + try { + await dstBlobClient.syncCopyFromURL(srcBlobUrl); + } catch (error) { + throw new Error(`Source object does not exist (srcKey=${srcKey}).`); + } } /** diff --git a/libs/wingsdk/src/target-tf-azure/bucket.ts b/libs/wingsdk/src/target-tf-azure/bucket.ts index c5cf6f7aa57..f4018e3526b 100644 --- a/libs/wingsdk/src/target-tf-azure/bucket.ts +++ b/libs/wingsdk/src/target-tf-azure/bucket.ts @@ -113,6 +113,7 @@ export class Bucket extends cloud.Bucket { cloud.BucketInflightMethods.TRY_GET_JSON, cloud.BucketInflightMethods.TRY_DELETE, cloud.BucketInflightMethods.METADATA, + cloud.BucketInflightMethods.COPY, ]; } diff --git a/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts b/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts index 10db4ad4999..ea638072694 100644 --- a/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts +++ b/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts @@ -727,6 +727,6 @@ test("copy a non-existent object within the bucket", async () => { // THEN await expect(() => client.copy(SRC_KEY, DST_KEY)).rejects.toThrowError( - `Unable to copy. Source object does not exist (srcKey=${SRC_KEY}).` + `Source object does not exist (srcKey=${SRC_KEY}).` ); }); diff --git a/libs/wingsdk/test/target-sim/bucket.test.ts b/libs/wingsdk/test/target-sim/bucket.test.ts index 3e5a4b4aeae..fd45096ffd2 100644 --- a/libs/wingsdk/test/target-sim/bucket.test.ts +++ b/libs/wingsdk/test/target-sim/bucket.test.ts @@ -769,7 +769,7 @@ test("copy non-existent object within the bucket", async () => { // THEN await expect(() => client.copy(SRC_KEY, DST_KEY)).rejects.toThrowError( - /Unable to copy. Source object does not exist/ + /Source object does not exist/ ); await s.stop(); }); diff --git a/libs/wingsdk/test/target-tf-azure/bucket.inflight.test.ts b/libs/wingsdk/test/target-tf-azure/bucket.inflight.test.ts index 518f8b8809c..1d9e612b4a9 100644 --- a/libs/wingsdk/test/target-tf-azure/bucket.inflight.test.ts +++ b/libs/wingsdk/test/target-tf-azure/bucket.inflight.test.ts @@ -2,6 +2,7 @@ import { Readable } from "stream"; import { PagedAsyncIterableIterator, PageSettings } from "@azure/core-paging"; import { BlobClient, + BlobCopyFromURLResponse, BlobDeleteResponse, BlobDownloadResponseParsed, BlobExistsOptions, @@ -9,6 +10,7 @@ import { BlobGetPropertiesResponse, BlobItem, BlobServiceClient, + BlobSyncCopyFromURLOptions, BlockBlobClient, BlockBlobUploadResponse, ContainerClient, @@ -514,6 +516,52 @@ test("fetch metadata of an unexisting object from the bucket", async () => { ); }); +test("copy objects within the bucket", async () => { + // GIVEN + const BUCKET_NAME = "BUCKET_NAME"; + const STORAGE_NAME = "STORAGE_NAME"; + const SRC_KEY = "SRC/KEY"; + const DST_KEY = "DST/KEY"; + + // WHEN + const client = new BucketClient( + BUCKET_NAME, + STORAGE_NAME, + false, + mockBlobServiceClient + ); + TEST_PATH = "happy"; + + const response1 = await client.copy(SRC_KEY, SRC_KEY); + const response2 = await client.copy(SRC_KEY, DST_KEY); + + // THEN + expect(response1).toEqual(undefined); + expect(response2).toEqual(undefined); +}); + +test("copy a non-existent object within the bucket", async () => { + // GIVEN + const BUCKET_NAME = "BUCKET_NAME"; + const STORAGE_NAME = "STORAGE_NAME"; + const SRC_KEY = "SRC/KEY"; + const DST_KEY = "DST/KEY"; + + // WHEN + const client = new BucketClient( + BUCKET_NAME, + STORAGE_NAME, + false, + mockBlobServiceClient + ); + TEST_PATH = "sad"; + + // THEN + await expect(() => client.copy(SRC_KEY, DST_KEY)).rejects.toThrowError( + `Source object does not exist (srcKey=${SRC_KEY}).` + ); +}); + // Mock Clients class MockBlobClient extends BlobClient { public download(): Promise { @@ -592,6 +640,16 @@ class MockBlockBlobClient extends BlockBlobClient { public delete(): Promise { return Promise.resolve({} as any); } + + public syncCopyFromURL( + copySource: string, + options?: BlobSyncCopyFromURLOptions + ): Promise { + if (TEST_PATH === "happy") { + return Promise.resolve({} as BlobCopyFromURLResponse); + } + return Promise.reject("some fake error"); + } } class MockContainerClient extends ContainerClient {