From ee1ab4b31ee462d95406cc3ffe407bacd5e69942 Mon Sep 17 00:00:00 2001 From: Shai Ber Date: Fri, 1 Nov 2024 10:28:22 +0200 Subject: [PATCH] feat(sdk): add support for the forceDestroy property on the bucket (#7213) The forceDestroy property allows to delete a bucket that contains objects ## Checklist - [X] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [X] Description explains motivation and solution - [X] Tests added (always) - [X] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --------- Signed-off-by: monada-bot[bot] Co-authored-by: monada-bot[bot] --- docs/api/04-standard-library/cloud/bucket.md | 14 + .../@winglang/platform-awscdk/src/bucket.ts | 7 +- .../@winglang/platform-awscdk/src/website.ts | 2 +- .../test/__snapshots__/bucket.test.ts.snap | 336 ++++++++++++++++++ .../platform-awscdk/test/bucket.test.ts | 11 + packages/@winglang/sdk/src/cloud/bucket.ts | 6 + .../@winglang/sdk/src/target-sim/bucket.ts | 3 + .../sdk/src/target-sim/schema-resources.ts | 2 + .../@winglang/sdk/src/target-tf-aws/bucket.ts | 7 +- .../sdk/src/target-tf-aws/website.ts | 2 +- .../@winglang/sdk/src/target-tf-gcp/bucket.ts | 2 +- .../__snapshots__/connections.test.ts.snap | 1 + .../__snapshots__/bucket.test.ts.snap | 4 + .../__snapshots__/file-counter.test.ts.snap | 1 + .../sdk/test/target-sim/bucket.test.ts | 1 + .../__snapshots__/bucket.test.ts.snap | 126 +++++++ .../sdk/test/target-tf-aws/bucket.test.ts | 14 + .../__snapshots__/bucket.test.ts.snap | 144 +++++++- .../__snapshots__/counter.test.ts.snap | 5 - .../__snapshots__/function.test.ts.snap | 4 - .../__snapshots__/schedule.test.ts.snap | 1 - .../sdk/test/target-tf-gcp/bucket.test.ts | 11 + .../incomplete_inflight_namespace.snap | 2 +- .../completions/namespace_middle_dot.snap | 2 +- .../partial_type_reference_annotation.snap | 2 +- .../variable_type_annotation_namespace.snap | 2 +- .../snapshots/signature/constructor_arg.snap | 2 +- 27 files changed, 686 insertions(+), 28 deletions(-) diff --git a/docs/api/04-standard-library/cloud/bucket.md b/docs/api/04-standard-library/cloud/bucket.md index 39720d2d74b..07409f1d411 100644 --- a/docs/api/04-standard-library/cloud/bucket.md +++ b/docs/api/04-standard-library/cloud/bucket.md @@ -1080,6 +1080,7 @@ let BucketProps = cloud.BucketProps{ ... }; | --- | --- | --- | | cors | bool | Whether to add default cors configuration. | | corsOptions | BucketCorsOptions | Custom cors configuration for the bucket. | +| forceDestroy | bool | Whether to allow the bucket to be deleted even if it is not empty. | | public | bool | Whether the bucket's objects should be publicly accessible. | --- @@ -1130,6 +1131,19 @@ with the following options: --- +##### `forceDestroy`Optional + +```wing +forceDestroy: bool; +``` + +- *Type:* bool +- *Default:* false + +Whether to allow the bucket to be deleted even if it is not empty. + +--- + ##### `public`Optional ```wing diff --git a/packages/@winglang/platform-awscdk/src/bucket.ts b/packages/@winglang/platform-awscdk/src/bucket.ts index 0c04077feeb..e226043b68e 100644 --- a/packages/@winglang/platform-awscdk/src/bucket.ts +++ b/packages/@winglang/platform-awscdk/src/bucket.ts @@ -41,14 +41,16 @@ export class Bucket extends cloud.Bucket implements IAwsBucket { private readonly bucket: S3Bucket; private readonly public: boolean; + private readonly forceDestroy: boolean; private bucketDeployment?: BucketDeployment; constructor(scope: Construct, id: string, props: cloud.BucketProps = {}) { super(scope, id, props); this.public = props.public ?? false; + this.forceDestroy = props.forceDestroy ?? false; - this.bucket = createEncryptedBucket(this, this.public); + this.bucket = createEncryptedBucket(this, this.public, this.forceDestroy); if (props.cors ?? true) { this.addCorsRule( @@ -254,6 +256,7 @@ export class Bucket extends cloud.Bucket implements IAwsBucket { export function createEncryptedBucket( scope: Construct, isPublic: boolean, + forceDestroy: boolean, name: string = "Default" ): S3Bucket { const isTestEnvironment = App.of(scope).isTestEnvironment; @@ -270,6 +273,6 @@ export function createEncryptedBucket( : BlockPublicAccess.BLOCK_ALL, publicReadAccess: isPublic ? true : false, removalPolicy: RemovalPolicy.DESTROY, - autoDeleteObjects: isTestEnvironment ? true : false, + autoDeleteObjects: isTestEnvironment || forceDestroy ? true : false, }); } diff --git a/packages/@winglang/platform-awscdk/src/website.ts b/packages/@winglang/platform-awscdk/src/website.ts index 6bf632c2404..d8b3fc07ed0 100644 --- a/packages/@winglang/platform-awscdk/src/website.ts +++ b/packages/@winglang/platform-awscdk/src/website.ts @@ -24,7 +24,7 @@ export class Website extends cloud.Website implements IAwsWebsite { constructor(scope: Construct, id: string, props: cloud.WebsiteProps) { super(scope, id, props); - this.bucket = createEncryptedBucket(this, false, "WebsiteBucket"); + this.bucket = createEncryptedBucket(this, false, false, "WebsiteBucket"); new BucketDeployment(this, "BucketWebsiteConfiguration", { destinationBucket: this.bucket, diff --git a/packages/@winglang/platform-awscdk/test/__snapshots__/bucket.test.ts.snap b/packages/@winglang/platform-awscdk/test/__snapshots__/bucket.test.ts.snap index 5c69c2a0798..02f84045ba0 100644 --- a/packages/@winglang/platform-awscdk/test/__snapshots__/bucket.test.ts.snap +++ b/packages/@winglang/platform-awscdk/test/__snapshots__/bucket.test.ts.snap @@ -1,5 +1,341 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`bucket is force destroy 1`] = ` +{ + "Mappings": { + "LatestNodeRuntimeMap": { + "af-south-1": { + "value": "nodejs20.x", + }, + "ap-east-1": { + "value": "nodejs20.x", + }, + "ap-northeast-1": { + "value": "nodejs20.x", + }, + "ap-northeast-2": { + "value": "nodejs20.x", + }, + "ap-northeast-3": { + "value": "nodejs20.x", + }, + "ap-south-1": { + "value": "nodejs20.x", + }, + "ap-south-2": { + "value": "nodejs20.x", + }, + "ap-southeast-1": { + "value": "nodejs20.x", + }, + "ap-southeast-2": { + "value": "nodejs20.x", + }, + "ap-southeast-3": { + "value": "nodejs20.x", + }, + "ap-southeast-4": { + "value": "nodejs20.x", + }, + "ca-central-1": { + "value": "nodejs20.x", + }, + "cn-north-1": { + "value": "nodejs18.x", + }, + "cn-northwest-1": { + "value": "nodejs18.x", + }, + "eu-central-1": { + "value": "nodejs20.x", + }, + "eu-central-2": { + "value": "nodejs20.x", + }, + "eu-north-1": { + "value": "nodejs20.x", + }, + "eu-south-1": { + "value": "nodejs20.x", + }, + "eu-south-2": { + "value": "nodejs20.x", + }, + "eu-west-1": { + "value": "nodejs20.x", + }, + "eu-west-2": { + "value": "nodejs20.x", + }, + "eu-west-3": { + "value": "nodejs20.x", + }, + "il-central-1": { + "value": "nodejs20.x", + }, + "me-central-1": { + "value": "nodejs20.x", + }, + "me-south-1": { + "value": "nodejs20.x", + }, + "sa-east-1": { + "value": "nodejs20.x", + }, + "us-east-1": { + "value": "nodejs20.x", + }, + "us-east-2": { + "value": "nodejs20.x", + }, + "us-gov-east-1": { + "value": "nodejs18.x", + }, + "us-gov-west-1": { + "value": "nodejs18.x", + }, + "us-iso-east-1": { + "value": "nodejs18.x", + }, + "us-iso-west-1": { + "value": "nodejs18.x", + }, + "us-isob-east-1": { + "value": "nodejs18.x", + }, + "us-west-1": { + "value": "nodejs20.x", + }, + "us-west-2": { + "value": "nodejs20.x", + }, + }, + }, + "Parameters": { + "BootstrapVersion": { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + ], + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", + }, + "S3Key": "", + }, + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "mybucketD601CBAA", + }, + " S3 bucket.", + ], + ], + }, + "Handler": "index.handler", + "MemorySize": 128, + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn", + ], + }, + "Runtime": { + "Fn::FindInMap": [ + "LatestNodeRuntimeMap", + { + "Ref": "AWS::Region", + }, + "value", + ], + }, + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "mybucketAutoDeleteObjectsCustomResource73B2A268": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "mybucketPolicy41B6C0F4", + ], + "Properties": { + "BucketName": { + "Ref": "mybucketD601CBAA", + }, + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn", + ], + }, + }, + "Type": "Custom::S3AutoDeleteObjects", + "UpdateReplacePolicy": "Delete", + }, + "mybucketD601CBAA": { + "DeletionPolicy": "Delete", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "CorsConfiguration": { + "CorsRules": [ + { + "AllowedHeaders": [ + "*", + ], + "AllowedMethods": [ + "GET", + "POST", + "PUT", + "DELETE", + "HEAD", + ], + "AllowedOrigins": [ + "*", + ], + "ExposedHeaders": [], + "MaxAge": 0, + }, + ], + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true", + }, + ], + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + }, + "mybucketPolicy41B6C0F4": { + "Properties": { + "Bucket": { + "Ref": "mybucketD601CBAA", + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:PutBucketPolicy", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn", + ], + }, + }, + "Resource": [ + { + "Fn::GetAtt": [ + "mybucketD601CBAA", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "mybucketD601CBAA", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5", + ], + { + "Ref": "BootstrapVersion", + }, + ], + }, + ], + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", + }, + ], + }, + }, +} +`; + exports[`bucket is public 1`] = ` { "Parameters": { diff --git a/packages/@winglang/platform-awscdk/test/bucket.test.ts b/packages/@winglang/platform-awscdk/test/bucket.test.ts index 64e60ca1778..54c3f2def8f 100644 --- a/packages/@winglang/platform-awscdk/test/bucket.test.ts +++ b/packages/@winglang/platform-awscdk/test/bucket.test.ts @@ -42,6 +42,17 @@ test("bucket is public", () => { expect(awscdkSanitize(template)).toMatchSnapshot(); }); +test("bucket is force destroy", () => { + // GIVEN + const app = new AwsCdkApp(); + new cloud.Bucket(app, "my_bucket", { forceDestroy: true }); + const output = app.synth(); + + // THEN + const template = Template.fromJSON(JSON.parse(output)); + expect(awscdkSanitize(template)).toMatchSnapshot(); +}); + test("bucket with two preflight objects", () => { // GIVEN const app = new AwsCdkApp(); diff --git a/packages/@winglang/sdk/src/cloud/bucket.ts b/packages/@winglang/sdk/src/cloud/bucket.ts index 7907ac817a4..3399791333c 100644 --- a/packages/@winglang/sdk/src/cloud/bucket.ts +++ b/packages/@winglang/sdk/src/cloud/bucket.ts @@ -61,6 +61,12 @@ export interface BucketProps { */ readonly public?: boolean; + /** + * Whether to allow the bucket to be deleted even if it is not empty. + * @default false + */ + readonly forceDestroy?: boolean; + /** * Whether to add default cors configuration. * diff --git a/packages/@winglang/sdk/src/target-sim/bucket.ts b/packages/@winglang/sdk/src/target-sim/bucket.ts index 1e8a859f96a..6b44b70fc49 100644 --- a/packages/@winglang/sdk/src/target-sim/bucket.ts +++ b/packages/@winglang/sdk/src/target-sim/bucket.ts @@ -25,6 +25,7 @@ export class Bucket extends cloud.Bucket implements ISimulatorResource { } private readonly public: boolean; + private readonly forceDestroy: boolean; private readonly initialObjects: Record = {}; private readonly policy: Policy; @@ -32,6 +33,7 @@ export class Bucket extends cloud.Bucket implements ISimulatorResource { super(scope, id, props); this.public = props.public ?? false; + this.forceDestroy = props.forceDestroy ?? false; this.policy = new Policy(this, "Policy", { principal: this }); } @@ -129,6 +131,7 @@ export class Bucket extends cloud.Bucket implements ISimulatorResource { public toSimulator(): ToSimulatorOutput { const props: BucketSchema = { public: this.public, + forceDestroy: this.forceDestroy, initialObjects: this.initialObjects, topics: this.convertTopicsToHandles(), }; diff --git a/packages/@winglang/sdk/src/target-sim/schema-resources.ts b/packages/@winglang/sdk/src/target-sim/schema-resources.ts index 338485dab9b..d9198ad8ee1 100644 --- a/packages/@winglang/sdk/src/target-sim/schema-resources.ts +++ b/packages/@winglang/sdk/src/target-sim/schema-resources.ts @@ -134,6 +134,8 @@ export interface TopicSubscriber extends EventSubscription { export interface BucketSchema { /** Whether the bucket should be publicly accessible. */ readonly public: boolean; + /** Whether to allow the bucket to be deleted even if it is not empty. */ + readonly forceDestroy: boolean; /** The initial objects uploaded to the bucket. */ readonly initialObjects: Record; /** Event notification topics- the record has BucketEventType as a key and a topic handle as a value */ diff --git a/packages/@winglang/sdk/src/target-tf-aws/bucket.ts b/packages/@winglang/sdk/src/target-tf-aws/bucket.ts index b3b5ce03559..cf7d9d3b7c7 100644 --- a/packages/@winglang/sdk/src/target-tf-aws/bucket.ts +++ b/packages/@winglang/sdk/src/target-tf-aws/bucket.ts @@ -65,6 +65,7 @@ export class Bucket extends cloud.Bucket implements IAwsBucket { private readonly bucket: S3Bucket; private readonly public: boolean; + private readonly forceDestroy: boolean; private readonly notificationTopics: S3BucketNotificationTopic[] = []; private readonly notificationDependencies: ITerraformDependable[] = []; private readonly corsRules: cloud.BucketCorsOptions[] = []; @@ -74,8 +75,9 @@ export class Bucket extends cloud.Bucket implements IAwsBucket { super(scope, id, props); this.public = props.public ?? false; + this.forceDestroy = props.forceDestroy ?? false; - this.bucket = createEncryptedBucket(this, this.public); + this.bucket = createEncryptedBucket(this, this.public, this.forceDestroy); if (props.cors ?? true) { this.addCorsRule( @@ -220,6 +222,7 @@ export class Bucket extends cloud.Bucket implements IAwsBucket { export function createEncryptedBucket( scope: Construct, isPublic: boolean, + forceDestroy: boolean, name: string = "Default" ): S3Bucket { const bucketPrefix = ResourceNames.generateName(scope, BUCKET_PREFIX_OPTS); @@ -242,7 +245,7 @@ export function createEncryptedBucket( const bucket = new S3Bucket(scope, name, { bucketPrefix, - forceDestroy: !!isTestEnvironment, + forceDestroy: !!isTestEnvironment || forceDestroy, }); if (isPublic) { diff --git a/packages/@winglang/sdk/src/target-tf-aws/website.ts b/packages/@winglang/sdk/src/target-tf-aws/website.ts index bb8d7258288..e1b7ed07c22 100644 --- a/packages/@winglang/sdk/src/target-tf-aws/website.ts +++ b/packages/@winglang/sdk/src/target-tf-aws/website.ts @@ -34,7 +34,7 @@ export class Website extends cloud.Website implements aws.IAwsWebsite { constructor(scope: Construct, id: string, props: aws.AwsWebsiteProps) { super(scope, id, props); - this.bucket = createEncryptedBucket(this, false, "WebsiteBucket"); + this.bucket = createEncryptedBucket(this, false, false, "WebsiteBucket"); new S3BucketWebsiteConfiguration(this, "BucketWebsiteConfiguration", { bucket: this.bucket.bucket, diff --git a/packages/@winglang/sdk/src/target-tf-gcp/bucket.ts b/packages/@winglang/sdk/src/target-tf-gcp/bucket.ts index 015db2cff15..33d3b8fcc59 100644 --- a/packages/@winglang/sdk/src/target-tf-gcp/bucket.ts +++ b/packages/@winglang/sdk/src/target-tf-gcp/bucket.ts @@ -91,7 +91,7 @@ export class Bucket extends cloud.Bucket { // recommended by GCP: https://cloud.google.com/storage/docs/uniform-bucket-level-access#should-you-use uniformBucketLevelAccess: true, publicAccessPrevention: props.public ? "inherited" : "enforced", - forceDestroy: !!isTestEnvironment, + forceDestroy: !!isTestEnvironment || props.forceDestroy, dependsOn: [iamServiceAccountCredentialsApi], }); diff --git a/packages/@winglang/sdk/test/core/__snapshots__/connections.test.ts.snap b/packages/@winglang/sdk/test/core/__snapshots__/connections.test.ts.snap index ba1c202649e..d2880fd5675 100644 --- a/packages/@winglang/sdk/test/core/__snapshots__/connections.test.ts.snap +++ b/packages/@winglang/sdk/test/core/__snapshots__/connections.test.ts.snap @@ -35,6 +35,7 @@ exports.handler = async function(event) { "addr": "c8220a82a4ad9c25a4f236946f350d2ac081dd7bbc", "path": "root/my_bucket", "props": { + "forceDestroy": false, "initialObjects": {}, "public": false, "topics": {}, diff --git a/packages/@winglang/sdk/test/target-sim/__snapshots__/bucket.test.ts.snap b/packages/@winglang/sdk/test/target-sim/__snapshots__/bucket.test.ts.snap index 8a05d5d51cb..f1d647fc6e2 100644 --- a/packages/@winglang/sdk/test/target-sim/__snapshots__/bucket.test.ts.snap +++ b/packages/@winglang/sdk/test/target-sim/__snapshots__/bucket.test.ts.snap @@ -89,6 +89,7 @@ exports[`can add file in preflight 2`] = ` "addr": "c8220a82a4ad9c25a4f236946f350d2ac081dd7bbc", "path": "root/my_bucket", "props": { + "forceDestroy": false, "initialObjects": { "test.txt": "test1", }, @@ -254,6 +255,7 @@ exports[`can add object in preflight 2`] = ` "addr": "c8220a82a4ad9c25a4f236946f350d2ac081dd7bbc", "path": "root/my_bucket", "props": { + "forceDestroy": false, "initialObjects": { "greeting.txt": "Hello world!", }, @@ -404,6 +406,7 @@ exports[`create a bucket 1`] = ` "addr": "c8220a82a4ad9c25a4f236946f350d2ac081dd7bbc", "path": "root/my_bucket", "props": { + "forceDestroy": false, "initialObjects": {}, "public": false, "topics": {}, @@ -564,6 +567,7 @@ exports[`get invalid object throws an error 2`] = ` "addr": "c8220a82a4ad9c25a4f236946f350d2ac081dd7bbc", "path": "root/my_bucket", "props": { + "forceDestroy": false, "initialObjects": {}, "public": false, "topics": {}, diff --git a/packages/@winglang/sdk/test/target-sim/__snapshots__/file-counter.test.ts.snap b/packages/@winglang/sdk/test/target-sim/__snapshots__/file-counter.test.ts.snap index 8c79e3c0884..6be3198898d 100644 --- a/packages/@winglang/sdk/test/target-sim/__snapshots__/file-counter.test.ts.snap +++ b/packages/@winglang/sdk/test/target-sim/__snapshots__/file-counter.test.ts.snap @@ -264,6 +264,7 @@ async rename(...args) { return this.backend.rename(...args); } "addr": "c80e3625bbefd53d8c61fbb978711d410b5f2a41c8", "path": "root/HelloWorld/Bucket", "props": { + "forceDestroy": false, "initialObjects": {}, "public": false, "topics": {}, diff --git a/packages/@winglang/sdk/test/target-sim/bucket.test.ts b/packages/@winglang/sdk/test/target-sim/bucket.test.ts index da26a80c682..a6698b25b3a 100644 --- a/packages/@winglang/sdk/test/target-sim/bucket.test.ts +++ b/packages/@winglang/sdk/test/target-sim/bucket.test.ts @@ -27,6 +27,7 @@ test("create a bucket", async () => { policy: [], props: { public: false, + forceDestroy: false, initialObjects: {}, topics: {}, }, diff --git a/packages/@winglang/sdk/test/target-tf-aws/__snapshots__/bucket.test.ts.snap b/packages/@winglang/sdk/test/target-tf-aws/__snapshots__/bucket.test.ts.snap index 48ebf869bb9..ff79e66f6fe 100644 --- a/packages/@winglang/sdk/test/target-tf-aws/__snapshots__/bucket.test.ts.snap +++ b/packages/@winglang/sdk/test/target-tf-aws/__snapshots__/bucket.test.ts.snap @@ -1,5 +1,131 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`bucket is force destroy 1`] = ` +{ + "resource": { + "aws_s3_bucket": { + "my_bucket": { + "bucket_prefix": "my-bucket-c8045fcc-", + "force_destroy": true, + }, + }, + "aws_s3_bucket_cors_configuration": { + "my_bucket_CorsConfiguration-c53a401a_67598037": { + "bucket": "\${aws_s3_bucket.my_bucket.id}", + "cors_rule": [ + { + "allowed_headers": [ + "*", + ], + "allowed_methods": [ + "GET", + "POST", + "PUT", + "DELETE", + "HEAD", + ], + "allowed_origins": [ + "*", + ], + "expose_headers": [], + "max_age_seconds": 0, + }, + ], + }, + }, + }, +} +`; + +exports[`bucket is force destroy 2`] = ` +{ + "tree": { + "children": { + "root": { + "children": { + "Default": { + "children": { + "aws": { + "constructInfo": { + "fqn": "cdktf.TerraformProvider", + "version": "0.20.7", + }, + "display": {}, + "id": "aws", + "path": "root/Default/aws", + }, + "my_bucket": { + "children": { + "CorsConfiguration-c53a401a": { + "constructInfo": { + "fqn": "cdktf.TerraformResource", + "version": "0.20.7", + }, + "display": {}, + "id": "CorsConfiguration-c53a401a", + "path": "root/Default/my_bucket/CorsConfiguration-c53a401a", + }, + "Default": { + "constructInfo": { + "fqn": "cdktf.TerraformResource", + "version": "0.20.7", + }, + "display": {}, + "id": "Default", + "path": "root/Default/my_bucket/Default", + }, + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": { + "description": "A cloud object store", + "title": "Bucket", + }, + "id": "my_bucket", + "path": "root/Default/my_bucket", + }, + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": {}, + "id": "Default", + "path": "root/Default", + }, + "backend": { + "constructInfo": { + "fqn": "cdktf.LocalBackend", + "version": "0.20.7", + }, + "display": {}, + "id": "backend", + "path": "root/backend", + }, + }, + "constructInfo": { + "fqn": "cdktf.TerraformStack", + "version": "0.20.7", + }, + "display": {}, + "id": "root", + "path": "root", + }, + }, + "constructInfo": { + "fqn": "cdktf.App", + "version": "0.20.7", + }, + "display": {}, + "id": "App", + "path": "", + }, + "version": "tree-0.1", +} +`; + exports[`bucket is public 1`] = ` { "resource": { diff --git a/packages/@winglang/sdk/test/target-tf-aws/bucket.test.ts b/packages/@winglang/sdk/test/target-tf-aws/bucket.test.ts index e175f01ef04..c04489b8a82 100644 --- a/packages/@winglang/sdk/test/target-tf-aws/bucket.test.ts +++ b/packages/@winglang/sdk/test/target-tf-aws/bucket.test.ts @@ -56,6 +56,20 @@ test("bucket is public", () => { expect(treeJsonOf(app.outdir)).toMatchSnapshot(); }); +test("bucket is force destroy", () => { + // GIVEN + const app = new AwsApp(); + new Bucket(app, "my_bucket", { forceDestroy: true }); + const output = app.synth(); + + // THEN + expect( + JSON.parse(output).resource.aws_s3_bucket.my_bucket.force_destroy + ).toBe(true); + expect(tfSanitize(output)).toMatchSnapshot(); + expect(treeJsonOf(app.outdir)).toMatchSnapshot(); +}); + test("bucket with cors disabled", () => { // GIVEN const app = new AwsApp(); diff --git a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/bucket.test.ts.snap b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/bucket.test.ts.snap index b1ea25775c2..79362d1060f 100644 --- a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/bucket.test.ts.snap +++ b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/bucket.test.ts.snap @@ -1,5 +1,143 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`bucket is force destroy 1`] = ` +{ + "resource": { + "google_project_service": { + "my_bucket_IamServiceAccountCredentialsApi_0618BD85": { + "disable_dependent_services": false, + "disable_on_destroy": false, + "service": "iamcredentials.googleapis.com", + }, + }, + "google_storage_bucket": { + "my_bucket": { + "depends_on": [ + "google_project_service.my_bucket_IamServiceAccountCredentialsApi_0618BD85", + ], + "force_destroy": true, + "location": "us-central1", + "name": "my_bucket-\${random_id.my_bucket_Id_50F73A6B.hex}", + "public_access_prevention": "enforced", + "uniform_bucket_level_access": true, + }, + }, + "random_id": { + "my_bucket_Id_50F73A6B": { + "byte_length": 4, + }, + }, + }, +} +`; + +exports[`bucket is force destroy 2`] = ` +{ + "tree": { + "children": { + "root": { + "children": { + "Default": { + "children": { + "google": { + "constructInfo": { + "fqn": "cdktf.TerraformProvider", + "version": "0.20.7", + }, + "display": {}, + "id": "google", + "path": "root/Default/google", + }, + "my_bucket": { + "children": { + "Default": { + "constructInfo": { + "fqn": "cdktf.TerraformResource", + "version": "0.20.7", + }, + "display": {}, + "id": "Default", + "path": "root/Default/my_bucket/Default", + }, + "IamServiceAccountCredentialsApi": { + "constructInfo": { + "fqn": "cdktf.TerraformResource", + "version": "0.20.7", + }, + "display": {}, + "id": "IamServiceAccountCredentialsApi", + "path": "root/Default/my_bucket/IamServiceAccountCredentialsApi", + }, + "Id": { + "constructInfo": { + "fqn": "cdktf.TerraformResource", + "version": "0.20.7", + }, + "display": {}, + "id": "Id", + "path": "root/Default/my_bucket/Id", + }, + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": { + "description": "A cloud object store", + "title": "Bucket", + }, + "id": "my_bucket", + "path": "root/Default/my_bucket", + }, + "random": { + "constructInfo": { + "fqn": "cdktf.TerraformProvider", + "version": "0.20.7", + }, + "display": {}, + "id": "random", + "path": "root/Default/random", + }, + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0", + }, + "display": {}, + "id": "Default", + "path": "root/Default", + }, + "backend": { + "constructInfo": { + "fqn": "cdktf.LocalBackend", + "version": "0.20.7", + }, + "display": {}, + "id": "backend", + "path": "root/backend", + }, + }, + "constructInfo": { + "fqn": "cdktf.TerraformStack", + "version": "0.20.7", + }, + "display": {}, + "id": "root", + "path": "root", + }, + }, + "constructInfo": { + "fqn": "cdktf.App", + "version": "0.20.7", + }, + "display": {}, + "id": "App", + "path": "", + }, + "version": "tree-0.1", +} +`; + exports[`bucket is public 1`] = ` { "resource": { @@ -15,7 +153,6 @@ exports[`bucket is public 1`] = ` "depends_on": [ "google_project_service.my_bucket_IamServiceAccountCredentialsApi_0618BD85", ], - "force_destroy": false, "location": "us-central1", "name": "my_bucket-\${random_id.my_bucket_Id_50F73A6B.hex}", "public_access_prevention": "inherited", @@ -169,7 +306,6 @@ exports[`bucket with two preflight files 1`] = ` "depends_on": [ "google_project_service.my_bucket_IamServiceAccountCredentialsApi_0618BD85", ], - "force_destroy": false, "location": "us-central1", "name": "my_bucket-\${random_id.my_bucket_Id_50F73A6B.hex}", "public_access_prevention": "enforced", @@ -337,7 +473,6 @@ exports[`bucket with two preflight objects 1`] = ` "depends_on": [ "google_project_service.my_bucket_IamServiceAccountCredentialsApi_0618BD85", ], - "force_destroy": false, "location": "us-central1", "name": "my_bucket-\${random_id.my_bucket_Id_50F73A6B.hex}", "public_access_prevention": "enforced", @@ -505,7 +640,6 @@ exports[`create a bucket 1`] = ` "depends_on": [ "google_project_service.my_bucket_IamServiceAccountCredentialsApi_0618BD85", ], - "force_destroy": false, "location": "us-central1", "name": "my_bucket-\${random_id.my_bucket_Id_50F73A6B.hex}", "public_access_prevention": "enforced", @@ -648,7 +782,6 @@ exports[`two buckets 1`] = ` "depends_on": [ "google_project_service.my_bucket1_IamServiceAccountCredentialsApi_5EA21318", ], - "force_destroy": false, "location": "us-central1", "name": "my_bucket1-\${random_id.my_bucket1_Id_D79FE240.hex}", "public_access_prevention": "enforced", @@ -658,7 +791,6 @@ exports[`two buckets 1`] = ` "depends_on": [ "google_project_service.my_bucket2_IamServiceAccountCredentialsApi_4C3E6B90", ], - "force_destroy": false, "location": "us-central1", "name": "my_bucket2-\${random_id.my_bucket2_Id_0AB96F49.hex}", "public_access_prevention": "enforced", diff --git a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/counter.test.ts.snap b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/counter.test.ts.snap index 24b83b09fe2..ded3515ba37 100644 --- a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/counter.test.ts.snap +++ b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/counter.test.ts.snap @@ -319,7 +319,6 @@ exports[`dec() IAM permissions 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", @@ -660,7 +659,6 @@ exports[`function with a counter binding 2`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", @@ -948,7 +946,6 @@ exports[`inc() IAM permissions 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", @@ -1236,7 +1233,6 @@ exports[`peek() IAM permissions 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", @@ -1650,7 +1646,6 @@ exports[`set() IAM permissions 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", diff --git a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/function.test.ts.snap b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/function.test.ts.snap index f428aae2a73..7c45189cb2e 100644 --- a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/function.test.ts.snap +++ b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/function.test.ts.snap @@ -39,7 +39,6 @@ exports[`basic function 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", @@ -261,7 +260,6 @@ exports[`basic function with environment variables 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", @@ -482,7 +480,6 @@ exports[`basic function with memory size specified 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", @@ -703,7 +700,6 @@ exports[`basic function with timeout explicitly set 1`] = ` "depends_on": [ "google_project_service.Function_FunctionBucket_IamServiceAccountCredentialsApi_EC496DEC", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Function_FunctionBucket_Id_216676D0.hex}", "public_access_prevention": "enforced", diff --git a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/schedule.test.ts.snap b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/schedule.test.ts.snap index 5a2c749f0b8..6e80b8da207 100644 --- a/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/schedule.test.ts.snap +++ b/packages/@winglang/sdk/test/target-tf-gcp/__snapshots__/schedule.test.ts.snap @@ -68,7 +68,6 @@ exports[`create a schedule 1`] = ` "depends_on": [ "google_project_service.Schedule_OnTick0_FunctionBucket_IamServiceAccountCredentialsApi_DF643D92", ], - "force_destroy": false, "location": "us-central1", "name": "functionbucket-\${random_id.Schedule_OnTick0_FunctionBucket_Id_79EDFDFF.hex}", "public_access_prevention": "enforced", diff --git a/packages/@winglang/sdk/test/target-tf-gcp/bucket.test.ts b/packages/@winglang/sdk/test/target-tf-gcp/bucket.test.ts index a53edccacaf..34053303168 100644 --- a/packages/@winglang/sdk/test/target-tf-gcp/bucket.test.ts +++ b/packages/@winglang/sdk/test/target-tf-gcp/bucket.test.ts @@ -41,6 +41,17 @@ test("bucket is public", () => { expect(treeJsonOf(app.outdir)).toMatchSnapshot(); }); +test("bucket is force destroy", () => { + // GIVEN + const app = new GcpApp(); + new Bucket(app, "my_bucket", { forceDestroy: true }); + const output = app.synth(); + + // THEN + expect(tfSanitize(output)).toMatchSnapshot(); + expect(treeJsonOf(app.outdir)).toMatchSnapshot(); +}); + test("two buckets", () => { // GIVEN const app = new GcpApp(); diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap index a4db5a431fe..095ea679438 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap @@ -215,7 +215,7 @@ source: packages/@winglang/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." + value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n forceDestroy?: bool;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." sortText: hh|BucketProps - label: BucketPutOptions kind: 22 diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap index a4db5a431fe..095ea679438 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap @@ -215,7 +215,7 @@ source: packages/@winglang/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." + value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n forceDestroy?: bool;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." sortText: hh|BucketProps - label: BucketPutOptions kind: 22 diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap index a4db5a431fe..095ea679438 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap @@ -215,7 +215,7 @@ source: packages/@winglang/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." + value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n forceDestroy?: bool;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." sortText: hh|BucketProps - label: BucketPutOptions kind: 22 diff --git a/packages/@winglang/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap b/packages/@winglang/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap index a4db5a431fe..095ea679438 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap @@ -215,7 +215,7 @@ source: packages/@winglang/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." + value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n forceDestroy?: bool;\n public?: bool;\n}\n```\n---\nOptions for `Bucket`." sortText: hh|BucketProps - label: BucketPutOptions kind: 22 diff --git a/packages/@winglang/wingc/src/lsp/snapshots/signature/constructor_arg.snap b/packages/@winglang/wingc/src/lsp/snapshots/signature/constructor_arg.snap index 4aaec270b7f..81f9e2d470c 100644 --- a/packages/@winglang/wingc/src/lsp/snapshots/signature/constructor_arg.snap +++ b/packages/@winglang/wingc/src/lsp/snapshots/signature/constructor_arg.snap @@ -10,6 +10,6 @@ signatures: - label: "...props" documentation: kind: markdown - value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n public?: bool;\n}\n```\n" + value: "```wing\nstruct BucketProps {\n cors?: bool;\n corsOptions?: BucketCorsOptions;\n forceDestroy?: bool;\n public?: bool;\n}\n```\n" activeParameter: 0