diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 0557401360c93..2bc80fc6b3fd5 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { FunctionBase, FunctionImportProps, IFunction } from './lambda-ref'; +import { FunctionBase, FunctionImportProps, IFunction } from './function-base'; import { Version } from './lambda-version'; import { CfnAlias } from './lambda.generated'; import { Permission } from './permission'; diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index c619efdfad891..5c75e956d3daf 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -1,5 +1,5 @@ import cdk = require('@aws-cdk/cdk'); -import { IFunction } from './lambda-ref'; +import { IFunction } from './function-base'; import { CfnEventSourceMapping } from './lambda.generated'; export interface EventSourceMappingProps { diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source.ts b/packages/@aws-cdk/aws-lambda/lib/event-source.ts index 10b0937a6cfca..1eec70b08c809 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source.ts @@ -1,4 +1,4 @@ -import { FunctionBase } from './lambda-ref'; +import { FunctionBase } from './function-base'; /** * An abstract class which represents an AWS Lambda event source. diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts similarity index 87% rename from packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts rename to packages/@aws-cdk/aws-lambda/lib/function-base.ts index 8c241d5428af6..9e8c28b14042c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -71,13 +71,6 @@ export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs */ metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric; - /** - * Metric for the Errors executing this Lambda - * - * @default sum over 5 minutes - */ - metricErrors(props?: cloudwatch.MetricCustomization): cloudwatch.Metric; - /** * Metric for the Duration of this Lambda * @@ -275,54 +268,6 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { } } - /** - * Return the given named metric for this Lambda - */ - public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return new cloudwatch.Metric({ - namespace: 'AWS/Lambda', - metricName, - dimensions: { FunctionName: this.functionName }, - ...props - }); - } - - /** - * Metric for the Errors executing this Lambda - * - * @default sum over 5 minutes - */ - public metricErrors(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('Errors', { statistic: 'sum', ...props }); - } - - /** - * Metric for the Duration of this Lambda - * - * @default average over 5 minutes - */ - public metricDuration(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('Duration', props); - } - - /** - * Metric for the number of invocations of this Lambda - * - * @default sum over 5 minutes - */ - public metricInvocations(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('Invocations', { statistic: 'sum', ...props }); - } - - /** - * Metric for the number of throttled invocations of this Lambda - * - * @default sum over 5 minutes - */ - public metricThrottles(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('Throttles', { statistic: 'sum', ...props }); - } - public logSubscriptionDestination(sourceLogGroup: logs.ILogGroup): logs.LogSubscriptionDestination { const arn = sourceLogGroup.logGroupArn; @@ -419,4 +364,4 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { throw new Error(`Invalid principal type for Lambda permission statement: ${JSON.stringify(this.node.resolve(principal))}. ` + 'Supported: AccountPrincipal, ServicePrincipal'); } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts similarity index 99% rename from packages/@aws-cdk/aws-lambda/lib/lambda.ts rename to packages/@aws-cdk/aws-lambda/lib/function.ts index cc7d9b1b4c878..9d5188a4d726a 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -4,7 +4,7 @@ import iam = require('@aws-cdk/aws-iam'); import sqs = require('@aws-cdk/aws-sqs'); import cdk = require('@aws-cdk/cdk'); import { Code } from './code'; -import { FunctionBase, FunctionImportProps, IFunction } from './lambda-ref'; +import { FunctionBase, FunctionImportProps, IFunction } from './function-base'; import { Version } from './lambda-version'; import { CfnFunction } from './lambda.generated'; import { ILayerVersion } from './layers'; diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index dd5d5fd46e668..c4cb9c7a7cdc0 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -1,6 +1,6 @@ export * from './alias'; -export * from './lambda-ref'; -export * from './lambda'; +export * from './function-base'; +export * from './function'; export * from './layers'; export * from './permission'; export * from './pipeline-action'; @@ -13,3 +13,5 @@ export * from './event-source-mapping'; // AWS::Lambda CloudFormation Resources: export * from './lambda.generated'; + +import './lambda-augmentations.generated'; diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index 015513bc92317..8470d9d0e3451 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -1,5 +1,5 @@ import { Construct } from '@aws-cdk/cdk'; -import { IFunction } from './lambda-ref'; +import { IFunction } from './function-base'; import { CfnVersion } from './lambda.generated'; /** diff --git a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts index 562d7238e38e4..060e463d74fad 100644 --- a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts @@ -1,7 +1,7 @@ import codepipeline = require('@aws-cdk/aws-codepipeline-api'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { IFunction } from './lambda-ref'; +import { IFunction } from './function-base'; /** * Common properties for creating a {@link PipelineInvokeAction} - diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index d5daeecca091b..7f8aac34cfb32 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -1,7 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { Function as LambdaFunction, FunctionProps } from './lambda'; -import { FunctionBase, FunctionImportProps, IFunction } from './lambda-ref'; +import { Function as LambdaFunction, FunctionProps } from './function'; +import { FunctionBase, FunctionImportProps, IFunction } from './function-base'; import { Permission } from './permission'; /** diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index 370453b926a1b..06dde470520de 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1058,6 +1058,27 @@ export = { test.done(); }, + 'Can use metricErrors on a lambda Function'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.inline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, + }); + + // THEN + test.deepEqual(stack.node.resolve(fn.metricErrors()), { + dimensions: { FunctionName: { Ref: 'Function76856677' }}, + namespace: 'AWS/Lambda', + metricName: 'Errors', + periodSec: 300, + statistic: 'Sum', + }); + + test.done(); + }, + 'addEventSource calls bind'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-sns/lib/index.ts b/packages/@aws-cdk/aws-sns/lib/index.ts index 2b1cd1f7ce1c6..46e1f3418aff5 100644 --- a/packages/@aws-cdk/aws-sns/lib/index.ts +++ b/packages/@aws-cdk/aws-sns/lib/index.ts @@ -1,7 +1,9 @@ export * from './policy'; export * from './topic'; -export * from './topic-ref'; +export * from './topic-base'; export * from './subscription'; // AWS::SNS CloudFormation Resources: export * from './sns.generated'; + +import './sns-augmentations.generated'; diff --git a/packages/@aws-cdk/aws-sns/lib/policy.ts b/packages/@aws-cdk/aws-sns/lib/policy.ts index 605e3fe36a0e5..81587863d40bb 100644 --- a/packages/@aws-cdk/aws-sns/lib/policy.ts +++ b/packages/@aws-cdk/aws-sns/lib/policy.ts @@ -1,7 +1,7 @@ import { PolicyDocument } from '@aws-cdk/aws-iam'; import { Construct } from '@aws-cdk/cdk'; import { CfnTopicPolicy } from './sns.generated'; -import { ITopic } from './topic-ref'; +import { ITopic } from './topic-base'; export interface TopicPolicyProps { /** diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index f2896ea9e4297..fa4025af23714 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -1,6 +1,6 @@ import { Construct } from '@aws-cdk/cdk'; import { CfnSubscription } from './sns.generated'; -import { ITopic } from './topic-ref'; +import { ITopic } from './topic-base'; /** * Properties for creating a new subscription diff --git a/packages/@aws-cdk/aws-sns/lib/topic-ref.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts similarity index 80% rename from packages/@aws-cdk/aws-sns/lib/topic-ref.ts rename to packages/@aws-cdk/aws-sns/lib/topic-base.ts index 4a562651af442..b3f189571f29b 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-ref.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -82,39 +82,6 @@ export interface ITopic extends * Grant topic publishing permissions to the given identity */ grantPublish(identity?: iam.IPrincipal): void; - - /** - * Construct a Metric object for the current topic for the given metric - */ - metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric; - - /** - * Metric for the size of messages published through this topic - * - * @default average over 5 minutes - */ - metricPublishSize(props?: cloudwatch.MetricCustomization): cloudwatch.Metric; - - /** - * Metric for the number of messages published through this topic - * - * @default sum over 5 minutes - */ - metricNumberOfMessagesPublished(props?: cloudwatch.MetricCustomization): cloudwatch.Metric; - - /** - * Metric for the number of messages that failed to publish through this topic - * - * @default sum over 5 minutes - */ - metricNumberOfMessagesFailed(props?: cloudwatch.MetricCustomization): cloudwatch.Metric; - - /** - * Metric for the number of messages that were successfully delivered through this topic - * - * @default sum over 5 minutes - */ - metricNumberOfMessagesDelivered(props?: cloudwatch.MetricCustomization): cloudwatch.Metric; } /** @@ -329,54 +296,6 @@ export abstract class TopicBase extends cdk.Construct implements ITopic { return this.topicArn; } - /** - * Construct a Metric object for the current topic for the given metric - */ - public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return new cloudwatch.Metric({ - namespace: 'AWS/SNS', - dimensions: { TopicName: this.topicName }, - metricName, - ...props - }); - } - - /** - * Metric for the size of messages published through this topic - * - * @default average over 5 minutes - */ - public metricPublishSize(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('PublishSize', props); - } - - /** - * Metric for the number of messages published through this topic - * - * @default sum over 5 minutes - */ - public metricNumberOfMessagesPublished(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('NumberOfMessagesPublished', { statistic: 'sum', ...props }); - } - - /** - * Metric for the number of messages that failed to publish through this topic - * - * @default sum over 5 minutes - */ - public metricNumberOfMessagesFailed(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('NumberOfMessagesFailed', { statistic: 'sum', ...props }); - } - - /** - * Metric for the number of messages that were successfully delivered through this topic - * - * @default sum over 5 minutes - */ - public metricNumberOfMessagesDelivered(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('NumberOfMessagesDelivered', { statistic: 'sum', ...props }); - } - /** * Implements the IBucketNotificationDestination interface, allowing topics to be used * as bucket notification destinations. diff --git a/packages/@aws-cdk/aws-sns/lib/topic.ts b/packages/@aws-cdk/aws-sns/lib/topic.ts index cb3de71abda79..489b2d304a4e2 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic.ts @@ -1,6 +1,6 @@ import { Construct, Output } from '@aws-cdk/cdk'; import { CfnTopic } from './sns.generated'; -import { ITopic, TopicBase, TopicImportProps } from './topic-ref'; +import { ITopic, TopicBase, TopicImportProps } from './topic-base'; /** * Properties for a new SNS topic diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index 7d9b21872bd2a..ae9a5921296fe 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -785,6 +785,31 @@ export = { } }); + test.done(); + }, + + 'test metrics'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // THEN + test.deepEqual(stack.node.resolve(topic.metricNumberOfMessagesPublished()), { + dimensions: {TopicName: { 'Fn::GetAtt': [ 'TopicBFC7AF6E', 'TopicName' ] }}, + namespace: 'AWS/SNS', + metricName: 'NumberOfMessagesPublished', + periodSec: 300, + statistic: 'Sum' + }); + + test.deepEqual(stack.node.resolve(topic.metricPublishSize()), { + dimensions: {TopicName: { 'Fn::GetAtt': [ 'TopicBFC7AF6E', 'TopicName' ] }}, + namespace: 'AWS/SNS', + metricName: 'PublishSize', + periodSec: 300, + statistic: 'Average' + }); + test.done(); } }; diff --git a/packages/@aws-cdk/aws-sqs/lib/index.ts b/packages/@aws-cdk/aws-sqs/lib/index.ts index aeb73f0030e1c..310312d5298a9 100644 --- a/packages/@aws-cdk/aws-sqs/lib/index.ts +++ b/packages/@aws-cdk/aws-sqs/lib/index.ts @@ -1,6 +1,8 @@ export * from './policy'; export * from './queue'; -export * from './queue-ref'; +export * from './queue-base'; // AWS::SQS CloudFormation Resources: export * from './sqs.generated'; + +import './sqs-augmentations.generated'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sqs/lib/policy.ts b/packages/@aws-cdk/aws-sqs/lib/policy.ts index 9813cb60bc5e6..26526764d8b86 100644 --- a/packages/@aws-cdk/aws-sqs/lib/policy.ts +++ b/packages/@aws-cdk/aws-sqs/lib/policy.ts @@ -1,6 +1,6 @@ import { PolicyDocument } from '@aws-cdk/aws-iam'; import { Construct } from '@aws-cdk/cdk'; -import { IQueue } from './queue-ref'; +import { IQueue } from './queue-base'; import { CfnQueuePolicy } from './sqs.generated'; export interface QueuePolicyProps { diff --git a/packages/@aws-cdk/aws-sqs/lib/queue-ref.ts b/packages/@aws-cdk/aws-sqs/lib/queue-base.ts similarity index 98% rename from packages/@aws-cdk/aws-sqs/lib/queue-ref.ts rename to packages/@aws-cdk/aws-sqs/lib/queue-base.ts index 69391f1257850..1c35629933694 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue-ref.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue-base.ts @@ -16,6 +16,11 @@ export interface IQueue extends cdk.IConstruct, s3n.IBucketNotificationDestinati */ readonly queueUrl: string; + /** + * The name of this queue + */ + readonly queueName: string; + /** * If this queue is server-side encrypted, this is the KMS encryption key. */ @@ -105,6 +110,11 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { */ public abstract readonly queueUrl: string; + /** + * The name of this queue + */ + public abstract readonly queueName: string; + /** * If this queue is server-side encrypted, this is the KMS encryption key. */ diff --git a/packages/@aws-cdk/aws-sqs/lib/queue.ts b/packages/@aws-cdk/aws-sqs/lib/queue.ts index e935fbb57df1c..162acbadfbe24 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue.ts @@ -1,6 +1,6 @@ import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); -import { IQueue, QueueBase, QueueImportProps } from './queue-ref'; +import { IQueue, QueueBase, QueueImportProps } from './queue-base'; import { CfnQueue } from './sqs.generated'; import { validateProps } from './validate-props'; @@ -341,6 +341,7 @@ interface EncryptionProps { class ImportedQueue extends QueueBase { public readonly queueArn: string; public readonly queueUrl: string; + public readonly queueName: string; public readonly encryptionMasterKey?: kms.IEncryptionKey; protected readonly autoCreatePolicy = false; @@ -349,6 +350,7 @@ class ImportedQueue extends QueueBase { super(scope, id); this.queueArn = props.queueArn; this.queueUrl = props.queueUrl; + this.queueName = cdk.Stack.find(this).parseArn(props.queueArn).resource; if (props.keyArn) { this.encryptionMasterKey = kms.EncryptionKey.import(this, 'Key', { diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 548fe7fc7c44b..c722b3006f207 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -64,6 +64,7 @@ }, "dependencies": { "@aws-cdk/aws-autoscaling-api": "^0.23.0", + "@aws-cdk/aws-cloudwatch": "^0.23.0", "@aws-cdk/aws-iam": "^0.23.0", "@aws-cdk/aws-kms": "^0.23.0", "@aws-cdk/aws-s3-notifications": "^0.23.0", @@ -72,6 +73,7 @@ "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { "@aws-cdk/aws-autoscaling-api": "^0.23.0", + "@aws-cdk/aws-cloudwatch": "^0.23.0", "@aws-cdk/aws-iam": "^0.23.0", "@aws-cdk/aws-kms": "^0.23.0", "@aws-cdk/aws-s3-notifications": "^0.23.0", diff --git a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts index fb2bfcc28883a..f78a8403f8132 100644 --- a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts @@ -158,7 +158,7 @@ export = { 'grants also work on imported queues'(test: Test) { const stack = new Stack(); const queue = Queue.import(stack, 'Import', { - queueArn: 'imported-queue-arn', + queueArn: 'arn:aws:sqs:us-east-1:123456789012:queue1', queueUrl: 'https://queue-url' }); @@ -176,7 +176,7 @@ export = { "sqs:GetQueueUrl" ], "Effect": "Allow", - "Resource": "imported-queue-arn" + "Resource": "arn:aws:sqs:us-east-1:123456789012:queue1" } ], "Version": "2012-10-17" @@ -484,6 +484,31 @@ export = { test.done(); } + }, + + 'test metrics'(test: Test) { + // GIVEN + const stack = new Stack(); + const topic = new Queue(stack, 'Queue'); + + // THEN + test.deepEqual(stack.node.resolve(topic.metricNumberOfMessagesSent()), { + dimensions: {QueueName: { 'Fn::GetAtt': [ 'Queue4A7E3555', 'QueueName' ] }}, + namespace: 'AWS/SQS', + metricName: 'NumberOfMessagesSent', + periodSec: 300, + statistic: 'Sum' + }); + + test.deepEqual(stack.node.resolve(topic.metricSentMessageSize()), { + dimensions: {QueueName: { 'Fn::GetAtt': [ 'Queue4A7E3555', 'QueueName' ] }}, + namespace: 'AWS/SQS', + metricName: 'SentMessageSize', + periodSec: 300, + statistic: 'Average' + }); + + test.done(); } }; diff --git a/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_Lambda_Function.json b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_Lambda_Function.json new file mode 100644 index 0000000000000..3f803ad58b8a4 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_Lambda_Function.json @@ -0,0 +1,27 @@ +{ + "metrics": { + "namespace": "AWS/Lambda", + "dimensions": { "FunctionName": "this.functionName" }, + "metrics": [ + { + "name": "Throttles", + "documentation": "How often this Lambda is throttled", + "type": "count" + }, + { + "name": "Invocations", + "documentation": "How often this Lambda is invoked", + "type": "count" + }, + { + "name": "Errors", + "documentation": "How many invocations of this Lambda fail", + "type": "count" + }, + { + "name": "Duration", + "documentation": "How long execution of this Lambda takes" + } + ] + } +} diff --git a/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_SNS_Topic.json b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_SNS_Topic.json new file mode 100644 index 0000000000000..4f537bccfb19d --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_SNS_Topic.json @@ -0,0 +1,52 @@ +{ + "metrics": { + "namespace": "AWS/SNS", + "dimensions": { "TopicName": "this.topicName" }, + "metrics": [ + { + "name": "PublishSize", + "documentation": "Metric for the size of messages published through this topic" + }, + { + "name": "NumberOfMessagesPublished", + "documentation": "The number of messages published to your Amazon SNS topics.", + "type": "count" + }, + { + "name": "NumberOfNotificationsDelivered", + "documentation": "The number of messages successfully delivered from your Amazon SNS topics to subscribing endpoints.", + "type": "count" + }, + { + "name": "NumberOfNotificationsFailed", + "documentation": "The number of messages that Amazon SNS failed to deliver.", + "type": "count" + }, + { + "name": "NumberOfNotificationsFilteredOut", + "documentation": "The number of messages that were rejected by subscription filter policies.", + "type": "count" + }, + { + "name": "NumberOfNotificationsFilteredOut-NoMessageAttributes", + "documentation": "The number of messages that were rejected by subscription filter policies because the messages have no attributes.", + "type": "count" + }, + { + "name": "NumberOfNotificationsFilteredOut-InvalidAttributes", + "documentation": "The number of messages that were rejected by subscription filter policies because the messages' attributes are invalid", + "type": "count" + }, + { + "name": "SMSMonthToDateSpentUSD", + "documentation": "The charges you have accrued since the start of the current calendar month for sending SMS messages.", + "type": "gauge" + }, + { + "name": "SMSSuccessRate", + "documentation": "The rate of successful SMS message deliveries.", + "type": "count" + } + ] + } +} diff --git a/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_SQS_Queue.json b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_SQS_Queue.json new file mode 100644 index 0000000000000..8d3765c1275e0 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/augmentations/AWS_SQS_Queue.json @@ -0,0 +1,53 @@ +{ + "metrics": { + "namespace": "AWS/SQS", + "dimensions": { "QueueName": "this.queueName" }, + "metrics": [ + { + "name": "ApproximateAgeOfOldestMessage", + "documentation": "The approximate age of the oldest non-deleted message in the queue.", + "type": "gauge" + }, + { + "name": "ApproximateNumberOfMessagesDelayed", + "documentation": "The number of messages in the queue that are delayed and not available for reading immediately.", + "type": "gauge" + }, + { + "name": "ApproximateNumberOfMessagesNotVisible", + "documentation": "The number of messages that are in flight.", + "type": "gauge" + }, + { + "name": "ApproximateNumberOfMessagesVisible", + "documentation": "The number of messages available for retrieval from the queue.", + "type": "gauge" + }, + { + "name": "NumberOfEmptyReceives", + "documentation": "The number of ReceiveMessage API calls that did not return a message.", + "type": "count" + }, + { + "name": "NumberOfMessagesDeleted", + "documentation": "The number of messages deleted from the queue.", + "type": "count" + }, + { + "name": "NumberOfMessagesReceived", + "documentation": "The number of messages returned by calls to the ReceiveMessage action.", + "type": "count" + }, + { + "name": "NumberOfMessagesSent", + "documentation": "The number of messages added to a queue.", + "type": "count" + }, + { + "name": "SentMessageSize", + "documentation": "The size of messages added to a queue." + } + ] + } +} + diff --git a/packages/@aws-cdk/cfnspec/lib/index.ts b/packages/@aws-cdk/cfnspec/lib/index.ts index 7037cb29c7594..ae17a9926a6f3 100644 --- a/packages/@aws-cdk/cfnspec/lib/index.ts +++ b/packages/@aws-cdk/cfnspec/lib/index.ts @@ -23,6 +23,18 @@ export function resourceSpecification(typeName: string): schema.ResourceType { return ret; } +/** + * Get the resource augmentations for a given type + */ +export function resourceAugmentation(typeName: string): schema.ResourceAugmentation { + const fileName = typeName.replace(/::/g, '_'); + try { + return require(`./augmentations/${fileName}.json`); + } catch (e) { + return {}; + } +} + /** * Return the property specification for the given resource's property */ diff --git a/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts b/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts new file mode 100644 index 0000000000000..a47a16a96e0fd --- /dev/null +++ b/packages/@aws-cdk/cfnspec/lib/schema/augmentation.ts @@ -0,0 +1,60 @@ +/** + * Augmentations for a CloudFormation resource type + */ +export interface ResourceAugmentation { + /** + * Metric augmentations for this resource type + */ + metrics?: ResourceMetricAugmentations; +} + +export interface ResourceMetricAugmentations { + namespace: string; + dimensions: {[key: string]: string}; + metrics: ResourceMetric[]; +} + +export interface ResourceMetric { + /** + * Uppercase-first metric name + */ + name: string; + + /** + * Documentation line + */ + documentation: string; + + /** + * Whether this is an even count (1 gets emitted every time something occurs) + * + * @default MetricType.Attrib + */ + type?: MetricType; +} + +export enum MetricType { + /** + * This metric measures an attribute of events + * + * It could be time, or request size, or similar. The default + * aggregate for this type of event is "Avg". + */ + Attrib = 'attrib', + + /** + * This metric is counting events. + * + * This means the metric "1" is emitted every time an event occurs. + * Only "Sum" is a meaningful aggregate of this type of metric. + */ + Count = 'count', + + /** + * This metric is emitting a size. + * + * The metric is not event-based, but measures some global ever-changing + * property. The most useful aggregate of this type of metric is "Max". + */ + Gauge = 'gauge' +} \ No newline at end of file diff --git a/packages/@aws-cdk/cfnspec/lib/schema/index.ts b/packages/@aws-cdk/cfnspec/lib/schema/index.ts index a9e954617ff75..250e5cc1edd96 100644 --- a/packages/@aws-cdk/cfnspec/lib/schema/index.ts +++ b/packages/@aws-cdk/cfnspec/lib/schema/index.ts @@ -2,3 +2,4 @@ export * from './base-types'; export * from './property'; export * from './resource-type'; export * from './specification'; +export * from './augmentation'; diff --git a/packages/@aws-cdk/cfnspec/test/test.augmentation.ts b/packages/@aws-cdk/cfnspec/test/test.augmentation.ts new file mode 100644 index 0000000000000..e79d5aac0d822 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/test/test.augmentation.ts @@ -0,0 +1,29 @@ +import fs = require('fs'); +import { Test } from 'nodeunit'; +import path = require('path'); +import cfnspec = require('../lib'); +import { MetricType } from '../lib/schema'; + +function resourceAugmentationTest(resource: string) { + return (test: Test) => { + const model = cfnspec.resourceAugmentation(resource); + + if (model.metrics) { + test.ok(typeof(model.metrics.namespace) === 'string', `namespace is invalid: ${model.metrics.namespace}`); + test.ok(typeof(model.metrics.dimensions) === 'object', `dimensions is invalid: ${model.metrics.dimensions}`); + for (const metric of model.metrics.metrics) { + test.ok(typeof metric.name === 'string', `name is invalid: ${metric.name}`); + test.ok(typeof metric.documentation === 'string', `documentation is invalid: ${metric.documentation}`); + test.ok(metric.type === undefined || [MetricType.Attrib, MetricType.Count, MetricType.Gauge].includes(metric.type), + `Metric Type is invalid: ${metric.type}`); + } + } + test.done(); + }; +} + +const files = fs.readdirSync(path.resolve(__dirname, '../lib/augmentations')); +for (const file of files) { + const resource = file.replace(/\.json$/, '').replace(/_/g, '::'); + exports[`Validate augmentation schema for ${resource}`] = resourceAugmentationTest(resource); +} diff --git a/tools/cfn2ts/lib/augmentation-generator.ts b/tools/cfn2ts/lib/augmentation-generator.ts new file mode 100644 index 0000000000000..24e82f24b69e7 --- /dev/null +++ b/tools/cfn2ts/lib/augmentation-generator.ts @@ -0,0 +1,134 @@ +import cfnSpec = require('@aws-cdk/cfnspec'); +import { schema } from '@aws-cdk/cfnspec'; +import { CodeMaker } from 'codemaker'; +import genspec = require('./genspec'); +import { SpecName } from './spec-utils'; + +export class AugmentationGenerator { + private readonly code = new CodeMaker(); + private readonly outputFile: string; + + constructor(moduleName: string, private readonly spec: schema.Specification) { + this.outputFile = `${moduleName}-augmentations.generated.ts`; + this.code.openFile(this.outputFile); + + this.code.line(`// Copyright 2012-${new Date().getFullYear()} Amazon.com, Inc. or its affiliates. All Rights Reserved.`); + this.code.line(); + this.code.line('// tslint:disable:max-line-length | This is generated code - line lengths are difficult to control'); + } + + public emitCode() { + for (const resourceTypeName of Object.keys(this.spec.ResourceTypes).sort()) { + const aug = cfnSpec.resourceAugmentation(resourceTypeName); + + if (aug.metrics) { + this.code.line('import cloudwatch = require("@aws-cdk/aws-cloudwatch");'); + + this.emitMetricAugmentations(resourceTypeName, aug.metrics); + } + } + } + + /** + * Saves the generated file. + */ + public async save(dir: string) { + this.code.closeFile(this.outputFile); + return await this.code.save(dir); + } + + private emitMetricAugmentations(resourceTypeName: string, metrics: schema.ResourceMetricAugmentations) { + const cfnName = SpecName.parse(resourceTypeName); + const resourceName = genspec.CodeName.forCfnResource(cfnName); + const l2ClassName = resourceName.className.replace(/^Cfn/, ''); + + const baseClassName = l2ClassName + 'Base'; + const interfaceName = 'I' + l2ClassName; + const baseClassModule = `./${l2ClassName.toLowerCase()}-base`; + + this.code.line(`import { ${baseClassName} } from "${baseClassModule}";`); + + this.code.openBlock(`declare module "${baseClassModule}"`); + + // Add to the interface + this.code.openBlock(`interface ${interfaceName}`); + this.emitMetricFunctionDeclaration(cfnName); + for (const m of metrics.metrics) { + this.emitSpecificMetricFunctionDeclaration(m); + } + this.code.closeBlock(); + + // Add declaration to the base class (implementation added below) + this.code.openBlock(`interface ${baseClassName}`); + this.emitMetricFunctionDeclaration(cfnName); + for (const m of metrics.metrics) { + this.emitSpecificMetricFunctionDeclaration(m); + } + this.code.closeBlock(); + + this.code.closeBlock(); + + // Emit the monkey patches for all methods + this.emitMetricFunction(baseClassName, metrics); + for (const m of metrics.metrics) { + this.emitSpecificMetricFunction(baseClassName, m); + } + } + + private emitMetricFunctionDeclaration(resource: SpecName) { + this.code.line(`/**`); + this.code.line(` * Return the given named metric for this ${resource.resourceName}`); + this.code.line(` */`); + this.code.line(`metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric;`); + } + + private emitMetricFunction(className: string, metrics: schema.ResourceMetricAugmentations) { + this.code.line(`${className}.prototype.metric = function(metricName: string, props?: cloudwatch.MetricCustomization) {`); + this.code.line(` return new cloudwatch.Metric({`); + this.code.line(` namespace: '${metrics.namespace}',`); + this.code.line(` metricName,`); + + const dimStrings = new Array(); + for (const [key, field] of Object.entries(metrics.dimensions)) { + dimStrings.push(`${key}: ${field}`); + } + + this.code.line(` dimensions: { ${dimStrings.join(', ') } },`); + this.code.line(` ...props`); + this.code.line(` });`); + this.code.line('};'); + } + + private emitSpecificMetricFunctionDeclaration(metric: schema.ResourceMetric) { + this.code.line(`/**`); + this.code.line(` * ${metric.documentation}`); + this.code.line(` *`); + this.code.line(` * ${metricStatistic(metric)} over 5 minutes`); + this.code.line(` */`); + this.code.line(`metric${metricFunctionName(metric)}(props?: cloudwatch.MetricCustomization): cloudwatch.Metric;`); + } + + private emitSpecificMetricFunction(className: string, metric: schema.ResourceMetric) { + this.code.line(`${className}.prototype.metric${metricFunctionName(metric)} = function(props?: cloudwatch.MetricCustomization) {`); + this.code.line(` return this.metric('${metric.name}', { statistic: '${metricStatistic(metric)}', ...props });`); + this.code.line('};'); + } +} + +function metricFunctionName(metric: schema.ResourceMetric) { + return metric.name.replace(/[^a-zA-Z0-9]/g, ''); +} + +function metricStatistic(metric: schema.ResourceMetric) { + switch (metric.type) { + case schema.MetricType.Attrib: + case undefined: + return 'Average'; + + case schema.MetricType.Count: + return 'Sum'; + + case schema.MetricType.Gauge: + return 'Maximum'; + } +} \ No newline at end of file diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index 17be51bc8ddce..72c2878a75840 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -33,7 +33,7 @@ export default class CodeGenerator { fingerprint: spec.Fingerprint }; - this.code.line('// Copyright 2012-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.'); + this.code.line(`// Copyright 2012-${new Date().getFullYear()} Amazon.com, Inc. or its affiliates. All Rights Reserved.`); this.code.line('// Generated from the AWS CloudFormation Resource Specification'); this.code.line('// See: docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html'); this.code.line(`// @cfn2ts:meta@ ${JSON.stringify(meta)}`); diff --git a/tools/cfn2ts/lib/index.ts b/tools/cfn2ts/lib/index.ts index 3d38b29efc662..299c7537d8e44 100644 --- a/tools/cfn2ts/lib/index.ts +++ b/tools/cfn2ts/lib/index.ts @@ -1,5 +1,6 @@ import cfnSpec = require('@aws-cdk/cfnspec'); import fs = require('fs-extra'); +import { AugmentationGenerator } from './augmentation-generator'; import CodeGenerator from './codegen'; import { packageName } from './genspec'; @@ -14,6 +15,9 @@ export default async function(scope: string, outPath: string) { const generator = new CodeGenerator(name, spec); generator.emitCode(); - await generator.save(outPath); + + const augs = new AugmentationGenerator(name, spec); + augs.emitCode(); + await augs.save(outPath); }