Skip to content

Commit

Permalink
feat: Add a construct to ease S3 bucket migration
Browse files Browse the repository at this point in the history
A small construct that can be used to prevent orphaning of a bucket when migrating from YAML to GuCDK.

As S3 bucket names have a global namespace, it is best practice to set the `DeletionPolicy` attribute of a CloudFormed bucket.

Use this construct to retain the logicalId of the bucket resource when migrating from YAML to GuCDK to prevent orphaning and GuCDK attempting to recreate an existing bucket.

For example, lets have this YAML template:

```yaml
ConfigBucket:
  Type: AWS::S3::Bucket
  DeletionPolicy: Retain
  Properties:
    BucketName: my-config-bucket
```

If we were to use the standard Bucket construct when migrating this stack to GuCDK, the resulting template would look like:

```yaml
ConfigBucketABCD1234:
  Type: AWS::S3::Bucket
  DeletionPolicy: Retain
  Properties:
    BucketName: my-config-bucket
```

The logicalId has changed and `ConfigBucket` would be deleted.

As the `DeletionPolicy` of `ConfigBucket` is set to `Retain`, the bucket will be orphaned, not deleted.

This will cause issues for `ConfigBucketABCD1234` as it will try to create a bucket of the same name ("my-config-bucket") and fail as that name is already in use.

Use `GuMigratingS3Bucket` to retain the logicalId:

```typescript
new GuMigratingS3Bucket(stack, "ConfigBucket", {
   bucketName: "my-app-config",
   existingLogicalId: { logicalId: "ConfigBucket", reason: "Prevent orphaning of an S3 bucket" },
 });
```

This will yield:

```yaml
ConfigBucket:
  Type: AWS::S3::Bucket
  DeletionPolicy: Retain
  Properties:
    BucketName: my-config-bucket
```

See:
  - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html
  • Loading branch information
akash1810 committed Jun 24, 2021
1 parent c3157aa commit 5d3d094
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/constructs/s3/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./migrating-bucket";
26 changes: 26 additions & 0 deletions src/constructs/s3/migrating-bucket.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import "../../utils/test/jest";
import { simpleGuStackForTesting } from "../../utils/test";
import { GuMigratingS3Bucket } from "./migrating-bucket";

describe("The GuMigratingS3Bucket construct", () => {
it("should auto-generate the logicalId by default", () => {
const stack = simpleGuStackForTesting();

new GuMigratingS3Bucket(stack, "MyPreExistingBucket", {
bucketName: "super-important-stuff",
});

expect(stack).toHaveResourceOfTypeAndLogicalId("AWS::S3::Bucket", /^MyPreExistingBucket.+$/);
});

it("overrides the logicalId when existingLogicalId is set in a migrating stack", () => {
const stack = simpleGuStackForTesting({ migratedFromCloudFormation: true });

new GuMigratingS3Bucket(stack, "MyPreExistingBucket", {
bucketName: "super-important-stuff",
existingLogicalId: { logicalId: "MyPreExistingBucket", reason: "Prevent orphaning of an S3 bucket" },
});

expect(stack).toHaveResourceOfTypeAndLogicalId("AWS::S3::Bucket", "MyPreExistingBucket");
});
});
69 changes: 69 additions & 0 deletions src/constructs/s3/migrating-bucket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { BucketProps } from "@aws-cdk/aws-s3";
import { Bucket } from "@aws-cdk/aws-s3";
import { GuMigratableConstruct } from "../../utils/mixin";
import type { GuStack } from "../core";
import type { GuMigratingResource } from "../core/migrating";

export interface GuMigratingS3BucketProps extends BucketProps, GuMigratingResource {}

/**
* A small construct that can be used to prevent orphaning of a bucket when migrating from YAML to GuCDK.
*
* As S3 bucket names have a global namespace, it is best practice to set the `DeletionPolicy` attribute of a CloudFormed bucket.
*
* Use this construct to retain the logicalId of the bucket resource when migrating from YAML to GuCDK to prevent orphaning
* and GuCDK attempting to recreate an existing bucket.
*
* For example, lets have this YAML template:
*
* ```yaml
* ConfigBucket:
* Type: AWS::S3::Bucket
* DeletionPolicy: Retain
* Properties:
* BucketName: my-config-bucket
* ```
*
* If we were to use the standard `Bucket` construct when migrating this stack to GuCDK, the resulting template would look like:
*
* ```yaml
* ConfigBucketABCD1234:
* Type: AWS::S3::Bucket
* DeletionPolicy: Retain
* Properties:
* BucketName: my-config-bucket
* ```
*
* The logicalId has changed and `ConfigBucket` would be deleted.
*
* As the `DeletionPolicy` of `ConfigBucket` is set to `Retain`, the bucket will be orphaned, not deleted.
* This will cause issues for `ConfigBucketABCD1234` as it will try to create a bucket of the same name ("my-config-bucket")
* and fail as that name is already in use.
*
* Use `GuMigratingS3Bucket` to retain the logicalId:
*
* ```typescript
* new GuMigratingS3Bucket(stack, "ConfigBucket", {
* bucketName: "my-app-config",
* existingLogicalId: { logicalId: "ConfigBucket", reason: "Prevent orphaning of an S3 bucket" },
* });
* ```
*
* This will yield:
*
* ```yaml
* ConfigBucket:
* Type: AWS::S3::Bucket
* DeletionPolicy: Retain
* Properties:
* BucketName: my-config-bucket
* ```
*
* @see GuMigratableConstruct
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html
*/
export class GuMigratingS3Bucket extends GuMigratableConstruct(Bucket) {
constructor(scope: GuStack, id: string, props: GuMigratingS3BucketProps) {
super(scope, id, props);
}
}

0 comments on commit 5d3d094

Please sign in to comment.