From eeded35e9243253afab63894d3223565f55f4aaf Mon Sep 17 00:00:00 2001 From: Florian Eitel Date: Tue, 2 Apr 2019 18:29:42 +0200 Subject: [PATCH 1/3] feat(s3): add MetricsConfiguration Property to S3 Bucket --- packages/@aws-cdk/aws-s3/lib/bucket.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index df3eaacf40ba6..6a6694170ccd2 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -639,6 +639,13 @@ export interface BucketProps { * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html */ readonly blockPublicAccess?: BlockPublicAccess; + + /** + * The metrics configuration of this bucket. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-metricsconfiguration.html + */ + readonly metricsConfigurations?: Array | cdk.Token; } /** @@ -735,7 +742,8 @@ export class Bucket extends BucketBase { versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined, lifecycleConfiguration: new Token(() => this.parseLifecycleConfiguration()), websiteConfiguration: this.renderWebsiteConfiguration(props), - publicAccessBlockConfiguration: props.blockPublicAccess + publicAccessBlockConfiguration: props.blockPublicAccess, + metricsConfigurations: props.metricsConfigurations }); applyRemovalPolicy(resource, props.removalPolicy !== undefined ? props.removalPolicy : RemovalPolicy.Orphan); From 479fcb6d7f9306fa68d753616a1290348a3580c0 Mon Sep 17 00:00:00 2001 From: Florian Eitel Date: Thu, 11 Apr 2019 11:21:30 +0200 Subject: [PATCH 2/3] feat(s3): add metric Property to S3 Bucket for configuring bucket metrics You can either specify the metrics as properties: new Bucket(stack, 'Bucket', { metrics: [{ id: "test", tagFilters: {tagname1: "tagvalue1", tagname2: "tagvalue2"} }] }); Or use the `addMetric` function: const bucket = new Bucket(stack, 'Bucket'); bucket.addMetric({ id: "test" }); --- packages/@aws-cdk/aws-s3/lib/bucket.ts | 56 +++++++-- packages/@aws-cdk/aws-s3/test/test.metrics.ts | 112 ++++++++++++++++++ 2 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 packages/@aws-cdk/aws-s3/test/test.metrics.ts diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 6a6694170ccd2..2fc43b5ae03de 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -564,6 +564,12 @@ export class BlockPublicAccess { } } +export interface BucketMetrics { + readonly id: string; + readonly prefix?: string; + readonly tagFilters?: {[tag: string]: any}; +} + export interface BucketProps { /** * The kind of server-side encryption to apply to this bucket. @@ -645,7 +651,7 @@ export interface BucketProps { * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-metricsconfiguration.html */ - readonly metricsConfigurations?: Array | cdk.Token; + readonly metrics?: BucketMetrics[]; } /** @@ -727,6 +733,7 @@ export class Bucket extends BucketBase { private readonly lifecycleRules: LifecycleRule[] = []; private readonly versioned?: boolean; private readonly notifications: BucketNotifications; + private readonly metrics: BucketMetrics[] = []; constructor(scope: Construct, id: string, props: BucketProps = {}) { super(scope, id); @@ -743,7 +750,7 @@ export class Bucket extends BucketBase { lifecycleConfiguration: new Token(() => this.parseLifecycleConfiguration()), websiteConfiguration: this.renderWebsiteConfiguration(props), publicAccessBlockConfiguration: props.blockPublicAccess, - metricsConfigurations: props.metricsConfigurations + metricsConfigurations: new Token(() => this.parseMetricConfiguration()) }); applyRemovalPolicy(resource, props.removalPolicy !== undefined ? props.removalPolicy : RemovalPolicy.Orphan); @@ -760,6 +767,9 @@ export class Bucket extends BucketBase { this.disallowPublicAccess = props.blockPublicAccess && props.blockPublicAccess.blockPublicPolicy; + // Add all bucket metric configurations rules + (props.metrics || []).forEach(this.addMetric.bind(this)); + // Add all lifecycle rules (props.lifecycleRules || []).forEach(this.addLifecycleRule.bind(this)); @@ -799,6 +809,10 @@ export class Bucket extends BucketBase { this.lifecycleRules.push(rule); } + public addMetric(metric: BucketMetrics) { + this.metrics.push(metric); + } + /** * Adds a bucket notification event destination. * @param event The event to trigger the notification @@ -950,6 +964,8 @@ export class Bucket extends BucketBase { return undefined; } + const self = this; + return { rules: this.lifecycleRules.map(parseLifecycleRule) }; function parseLifecycleRule(rule: LifecycleRule): CfnBucket.RuleProperty { @@ -966,24 +982,42 @@ export class Bucket extends BucketBase { prefix: rule.prefix, status: enabled ? 'Enabled' : 'Disabled', transitions: rule.transitions, - tagFilters: parseTagFilters(rule.tagFilters) + tagFilters: self.parseTagFilters(rule.tagFilters) }; return x; } + } - function parseTagFilters(tagFilters?: {[tag: string]: any}) { - if (!tagFilters || tagFilters.length === 0) { - return undefined; - } + private parseMetricConfiguration(): CfnBucket.MetricsConfigurationProperty[] | undefined { + if (!this.metrics || this.metrics.length === 0) { + return undefined; + } + + const self = this; + + return this.metrics.map(parseMetric); - return Object.keys(tagFilters).map(tag => ({ - key: tag, - value: tagFilters[tag] - })); + function parseMetric(metric: BucketMetrics): CfnBucket.MetricsConfigurationProperty { + return { + id: metric.id, + prefix: metric.prefix, + tagFilters: self.parseTagFilters(metric.tagFilters) + }; } } + private parseTagFilters(tagFilters?: {[tag: string]: any}) { + if (!tagFilters || tagFilters.length === 0) { + return undefined; + } + + return Object.keys(tagFilters).map(tag => ({ + key: tag, + value: tagFilters[tag] + })); + } + private renderWebsiteConfiguration(props: BucketProps): CfnBucket.WebsiteConfigurationProperty | undefined { if (!props.websiteErrorDocument && !props.websiteIndexDocument) { return undefined; diff --git a/packages/@aws-cdk/aws-s3/test/test.metrics.ts b/packages/@aws-cdk/aws-s3/test/test.metrics.ts new file mode 100644 index 0000000000000..1a44c8b3d1096 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/test/test.metrics.ts @@ -0,0 +1,112 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +import { Bucket } from '../lib'; + +export = { + 'Can use addMetrics() to add a metric configuration'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const bucket = new Bucket(stack, 'Bucket'); + bucket.addMetric({ + id: "test" + }); + + // THEN + expect(stack).to(haveResource('AWS::S3::Bucket', { + MetricsConfigurations: [{ + Id: "test" + }] + })); + + test.done(); + }, + + 'Bucket with metrics on prefix'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new Bucket(stack, 'Bucket', { + metrics: [{ + id: "test", + prefix: "prefix" + }] + }); + + // THEN + expect(stack).to(haveResource('AWS::S3::Bucket', { + MetricsConfigurations: [{ + Id: "test", + Prefix: "prefix" + }] + })); + + test.done(); + }, + + 'Bucket with metrics on tag filter'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new Bucket(stack, 'Bucket', { + metrics: [{ + id: "test", + tagFilters: {tagname1: "tagvalue1", tagname2: "tagvalue2"} + }] + }); + + // THEN + expect(stack).to(haveResource('AWS::S3::Bucket', { + MetricsConfigurations: [{ + Id: "test", + TagFilters: [ + { Key: "tagname1", Value: "tagvalue1" }, + { Key: "tagname2", Value: "tagvalue2" }, + ] + }] + })); + + test.done(); + }, + + 'Bucket with multiple metric configurations'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new Bucket(stack, 'Bucket', { + metrics: [ + { + id: "test", + tagFilters: {tagname1: "tagvalue1", tagname2: "tagvalue2"} + + }, + { + id: "test2", + prefix: "prefix" + }, + ] + }); + + // THEN + expect(stack).to(haveResource('AWS::S3::Bucket', { + MetricsConfigurations: [{ + Id: "test", + TagFilters: [ + { Key: "tagname1", Value: "tagvalue1" }, + { Key: "tagname2", Value: "tagvalue2" }, + ] + }, + { + Id: "test2", + Prefix: "prefix" + }] + })); + + test.done(); + }, +}; From 349c966bd6eaf77321dc96168c7cf1850f130aad Mon Sep 17 00:00:00 2001 From: Florian Eitel Date: Wed, 8 May 2019 09:12:55 +0200 Subject: [PATCH 3/3] Add documentation for BucketMetrics --- packages/@aws-cdk/aws-s3/lib/bucket.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 2fc43b5ae03de..0778925ce0327 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -564,9 +564,22 @@ export class BlockPublicAccess { } } +/** + * Specifies a metrics configuration for the CloudWatch request metrics from an Amazon S3 bucket. + */ export interface BucketMetrics { + /** + * The ID used to identify the metrics configuration. + */ readonly id: string; + /** + * The prefix that an object must have to be included in the metrics results. + */ readonly prefix?: string; + /** + * Specifies a list of tag filters to use as a metrics configuration filter. + * The metrics configuration includes only objects that meet the filter's criteria. + */ readonly tagFilters?: {[tag: string]: any}; } @@ -809,6 +822,11 @@ export class Bucket extends BucketBase { this.lifecycleRules.push(rule); } + /** + * Adds a metrics configuration for the CloudWatch request metrics from the bucket. + * + * @param metric The metric configuration to add + */ public addMetric(metric: BucketMetrics) { this.metrics.push(metric); }