UNPKG

@paulbarmstrong/cdk-managed-objects-bucket

Version:

A CDK construct representing a bucket and the objects within it, which can be defined by an Asset or directly in the CDK. It extends the Bucket construct.

201 lines 8.73 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ManagedObjectsBucket = exports.ObjectChangeAction = void 0; const child_process_1 = require("child_process"); const path_1 = __importDefault(require("path")); const fs_1 = require("fs"); const cdk = __importStar(require("aws-cdk-lib")); const s3 = __importStar(require("aws-cdk-lib/aws-s3")); const lambda = __importStar(require("aws-cdk-lib/aws-lambda")); const iam = __importStar(require("aws-cdk-lib/aws-iam")); /** * An action to be performed when changes are made to the objects in the bucket. */ class ObjectChangeAction { /** @hidden */ #classname = "ObjectChangeAction"; /** ObjectChangeAction for performing a CloudFront invalidation after objects * in the bucket have changed. */ static cloudFrontInvalidation(props) { return new CloudFrontInvalidationObjectChangeAction(props); } } exports.ObjectChangeAction = ObjectChangeAction; /** ObjectChangeAction for performing an invalidation on a CloudFront distribution after objects * in the bucket have changed. */ class CloudFrontInvalidationObjectChangeAction extends ObjectChangeAction { distribution; waitForCompletion; constructor(props) { super(); this.distribution = props.distribution; this.waitForCompletion = props.waitForCompletion; } } /** * An S3 Bucket where the objects in the bucket are completely managed by CDK. A * `Custom::ManagedBucketObjects` CFN resource internal to the ManagedObjectsBucket construct * mutates objects in the bucket to align the bucket with the objects defined in the CDK * definition. The objects in the bucket are otherwise read-only. Objects are added by calling * the `addObject` and `addObjectsFromAsset` methods. * * ManagedObjectsBucket extends [Bucket]( * https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html). All props from * Bucket are allowed except: * * 1. `removalPolicy` and `autoDeleteObjects` are not configurable. ManagedObjectsBuckets are * always emptied and destroyed on removal. * * In the event of ManagedObjectsBucket's `Custom::ManagedBucketObjects` custom resource having * a persistent problem, you can unblock your stack by adding the following environment variable * to the ObjectManagerFunction lambda function also inside the stack: * * Key: `SKIP`, Value: `true` */ class ManagedObjectsBucket extends s3.Bucket { /** @hidden */ #inlineBucketObjects; /** @hidden */ #assets; /** @hidden */ #ObjectChangeActions; /** @hidden */ #handlerRole; constructor(scope, id, props) { super(scope, id, { removalPolicy: cdk.RemovalPolicy.DESTROY, ...props }); this.#inlineBucketObjects = []; this.#assets = []; this.#ObjectChangeActions = []; const codePackagePath = path_1.default.join(__dirname, "..", "..", "handler"); if (!(0, fs_1.readdirSync)(codePackagePath).includes("node_modules")) { (0, child_process_1.execSync)("npm install", { cwd: codePackagePath }); } this.#handlerRole = new iam.Role(this, "ObjectManagerRole", { managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")], assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com") }); this.#handlerRole.addToPolicy(new iam.PolicyStatement({ actions: [ "s3:ListBucket", "s3:PutObject", "s3:PutObjectAcl", "s3:DeleteObject" ], resources: [this.bucketArn, `${this.bucketArn}/*`] })); this.addToResourcePolicy(new iam.PolicyStatement({ principals: [new iam.StarPrincipal()], effect: iam.Effect.DENY, actions: ["s3:PutObject", "s3:DeleteObject"], resources: [`${this.bucketArn}/*`], conditions: { StringNotLike: { "aws:userId": `${this.#handlerRole.roleId}:*` } } })); const handler = new lambda.Function(this, "ObjectManagerFunction", { runtime: lambda.Runtime.NODEJS_20_X, role: this.#handlerRole, code: lambda.Code.fromAsset(codePackagePath), handler: "index.handler", timeout: cdk.Duration.seconds(900), ephemeralStorageSize: cdk.Size.mebibytes(10240), logGroup: props.objectManagerLogGroup }); new cdk.CustomResource(this, "Objects", { resourceType: "Custom::ManagedBucketObjects", serviceToken: handler.functionArn, properties: { props: cdk.Lazy.any({ produce: () => ({ bucketUrl: `s3://${this.bucketName}`, assets: this.#assets.map(asset => ({ hash: asset.assetHash, s3BucketName: asset.s3BucketName, s3ObjectKey: asset.s3ObjectKey })), objects: this.#inlineBucketObjects, invalidationActions: this.#ObjectChangeActions .filter(action => action.distribution !== undefined) .map(action => ({ distributionId: action.distribution.distributionId, waitForCompletion: action.waitForCompletion })) }) }) } }); } /** * Add an object to the bucket based on a given key and body. Deploy-time values from the CDK * like resource ARNs can be used here. */ addObject(props) { if (this.#inlineBucketObjects.find(x => x.key === props.key)) { throw new Error(`Cannot add object with duplicate key ${props.key} to ${this.node.id}.`); } this.#inlineBucketObjects.push(props); } /** Add objects to the bucket based on an [Asset]( * https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3_assets-readme.html). * For example: * * `bucket.addObjectsFromAsset({ asset: new Asset(this, "MyAsset", { path: "./my-local-files" }) })` */ addObjectsFromAsset(props) { if (this.#assets.find(x => x.assetHash === props.asset.assetHash)) { throw new Error(`Cannot add objects from asset ${props.asset.assetHash} to ${this.node.id} twice.`); } this.#assets.push(props.asset); this.#handlerRole.addToPolicy(new iam.PolicyStatement({ actions: ["s3:GetObject"], resources: [`${props.asset.bucket.bucketArn}/*`] })); } /** Add an action to be performed when objects in the bucket are changed. */ addObjectChangeAction(action) { this.#ObjectChangeActions.push(action); if (action.distribution !== undefined) { this.#handlerRole.addToPolicy(new iam.PolicyStatement({ actions: ["cloudfront:CreateInvalidation", "cloudfront:GetInvalidation"], resources: [getDistributionArn(action.distribution)] })); } } } exports.ManagedObjectsBucket = ManagedObjectsBucket; /** @hidden */ function getDistributionArn(distribution) { return `arn:aws:cloudfront::${distribution.stack.account}:distribution/${distribution.distributionId}`; } //# sourceMappingURL=managed-objects-bucket.js.map