Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chatbot): log retention support and metrics utility methods #10137

Merged
merged 11 commits into from
Sep 4, 2020
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-chatbot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,19 @@ slackChannel.addToPrincipalPolicy(new iam.PolicyStatement({
resources: ['arn:aws:s3:::abc/xyz/123.txt'],
}));
```

### Log Group

Slack channel configuration automatically create a log group with the name `/aws/chatbot/<configuration-name>` in `us-east-1` upon first execution with
log data set to never expire.

The `logRetention` property can be used to set a different expiration period. A log group will be created if not already exists.
If the log group already exists, it's expiration will be configured to the value specified in this construct (never expire, by default).

By default, CDK uses the AWS SDK retry options when interacting with the log group. The `logRetentionRetryOptions` property
allows you to customize the maximum number of retries and base backoff duration.

*Note* that, if `logRetention` is set, a [CloudFormation custom
resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html) is added
to the stack that pre-creates the log group as part of the stack deployment, if it already doesn't exist, and sets the
correct log retention period (never expire, by default).
75 changes: 75 additions & 0 deletions packages/@aws-cdk/aws-chatbot/lib/slack-channel-configuration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import * as logs from '@aws-cdk/aws-logs';
import * as sns from '@aws-cdk/aws-sns';
import * as cdk from '@aws-cdk/core';
import { CfnSlackChannelConfiguration } from './chatbot.generated';
Expand Down Expand Up @@ -52,6 +54,31 @@ export interface SlackChannelConfigurationProps {
* @default LoggingLevel.NONE
*/
readonly loggingLevel?: LoggingLevel;

/**
* The number of days log events are kept in CloudWatch Logs. When updating
* this property, unsetting it doesn't remove the log retention policy. To
* remove the retention policy, set the value to `INFINITE`.
*
* @default logs.RetentionDays.INFINITE
*/
readonly logRetention?: logs.RetentionDays;

/**
* The IAM role for the Lambda function associated with the custom resource
* that sets the retention policy.
*
* @default - A new role is created.
*/
readonly logRetentionRole?: iam.IRole;

/**
* When log retention is specified, a custom resource attempts to create the CloudWatch log group.
* These options control the retry policy when interacting with CloudWatch APIs.
*
* @default - Default AWS SDK retry options.
*/
readonly logRetentionRetryOptions?: logs.LogRetentionRetryOptions;
}

/**
Expand Down Expand Up @@ -104,6 +131,11 @@ export interface ISlackChannelConfiguration extends cdk.IResource, iam.IGrantabl
* Adds a statement to the IAM role.
*/
addToRolePolicy(statement: iam.PolicyStatement): void;

/**
* Return the given named metric for this SlackChannelConfiguration
*/
metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric;
}

/**
Expand All @@ -129,6 +161,23 @@ abstract class SlackChannelConfigurationBase extends cdk.Resource implements ISl

this.role.addToPrincipalPolicy(statement);
}

/**
* Return the given named metric for this SlackChannelConfiguration
*/
public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric {
// AWS Chatbot publishes metrics to us-east-1 regardless of stack region
// https://docs.aws.amazon.com/chatbot/latest/adminguide/monitoring-cloudwatch.html
return new cloudwatch.Metric({
namespace: 'AWS/Chatbot',
region: 'us-east-1',
dimensions: {
ConfigurationName: this.slackChannelConfigurationName,
},
metricName,
...props,
});
}
}

/**
Expand Down Expand Up @@ -180,6 +229,20 @@ export class SlackChannelConfiguration extends SlackChannelConfigurationBase {
return new Import(scope, id);
}

/**
* Return the given named metric for All SlackChannelConfigurations
*/
public static metricAll(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric {
// AWS Chatbot publishes metrics to us-east-1 regardless of stack region
// https://docs.aws.amazon.com/chatbot/latest/adminguide/monitoring-cloudwatch.html
return new cloudwatch.Metric({
namespace: 'AWS/Chatbot',
region: 'us-east-1',
metricName,
...props,
});
}

readonly slackChannelConfigurationArn: string;

readonly slackChannelConfigurationName: string;
Expand Down Expand Up @@ -208,6 +271,18 @@ export class SlackChannelConfiguration extends SlackChannelConfigurationBase {
loggingLevel: props.loggingLevel?.toString(),
});

// Log retention
// AWS Chatbot publishes logs to us-east-1 regardless of stack region https://docs.aws.amazon.com/chatbot/latest/adminguide/cloudwatch-logs.html
if (props.logRetention) {
new logs.LogRetention(this, 'LogRetention', {
logGroupName: `/aws/chatbot/${props.slackChannelConfigurationName}`,
retention: props.logRetention,
role: props.logRetentionRole,
logGroupRegion: 'us-east-1',
logRetentionRetryOptions: props.logRetentionRetryOptions,
});
}

this.slackChannelConfigurationArn = configuration.ref;
this.slackChannelConfigurationName = props.slackChannelConfigurationName;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-chatbot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,17 @@
"pkglint": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-sns": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.0.4"
},
"peerDependencies": {
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-sns": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.0.4"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
{
"Resources": {
"MySlackChannelConfigurationRole1D3F23AE": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "chatbot.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"MySlackChannelConfigurationRoleDefaultPolicyE4C1FA62": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "s3:GetObject",
"Effect": "Allow",
"Resource": "arn:aws:s3:::abc/xyz/123.txt"
}
],
"Version": "2012-10-17"
},
"PolicyName": "MySlackChannelConfigurationRoleDefaultPolicyE4C1FA62",
"Roles": [
{
"Ref": "MySlackChannelConfigurationRole1D3F23AE"
}
]
}
},
"MySlackChannelA8E0B56C": {
"Type": "AWS::Chatbot::SlackChannelConfiguration",
"Properties": {
"ConfigurationName": "test-channel",
"IamRoleArn": {
"Fn::GetAtt": [
"MySlackChannelConfigurationRole1D3F23AE",
"Arn"
]
},
"SlackChannelId": "C0187JABUE9",
"SlackWorkspaceId": "T49239U4W",
"LoggingLevel": "NONE"
}
},
"MySlackChannelLogRetention84AA443F": {
"Type": "Custom::LogRetention",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A",
"Arn"
]
},
"LogGroupName": "/aws/chatbot/test-channel",
"RetentionInDays": 30,
"LogGroupRegion": "us-east-1"
}
},
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
}
},
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"logs:PutRetentionPolicy",
"logs:DeleteRetentionPolicy"
],
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB",
"Roles": [
{
"Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB"
}
]
}
},
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3Bucket48EF98C9"
},
"S3Key": {
"Fn::Join": [
"",
[
{
"Fn::Select": [
0,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3VersionKeyF33C73AF"
}
]
}
]
},
{
"Fn::Select": [
1,
{
"Fn::Split": [
"||",
{
"Ref": "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3VersionKeyF33C73AF"
}
]
}
]
}
]
]
}
},
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB",
"Arn"
]
},
"Runtime": "nodejs10.x"
},
"DependsOn": [
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB",
"LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB"
]
}
},
"Parameters": {
"AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3Bucket48EF98C9": {
"Type": "String",
"Description": "S3 bucket for asset \"74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437b\""
},
"AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3VersionKeyF33C73AF": {
"Type": "String",
"Description": "S3 key for asset version \"74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437b\""
},
"AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bArtifactHash976CF1BD": {
"Type": "String",
"Description": "Artifact hash for asset \"74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437b\""
}
}
}
33 changes: 33 additions & 0 deletions packages/@aws-cdk/aws-chatbot/test/integ.chatbot-logretention.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as iam from '@aws-cdk/aws-iam';
import * as logs from '@aws-cdk/aws-logs';
import * as cdk from '@aws-cdk/core';
import * as chatbot from '../lib';

class ChatbotLogRetentionInteg extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const slackChannel = new chatbot.SlackChannelConfiguration(this, 'MySlackChannel', {
slackChannelConfigurationName: 'test-channel',
slackWorkspaceId: 'T49239U4W', // modify to your slack workspace id
slackChannelId: 'C0187JABUE9', // modify to your slack channel id
loggingLevel: chatbot.LoggingLevel.NONE,
logRetention: logs.RetentionDays.ONE_MONTH,
});

slackChannel.addToRolePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:GetObject',
],
resources: ['arn:aws:s3:::abc/xyz/123.txt'],
}));
}
}

const app = new cdk.App();

new ChatbotLogRetentionInteg(app, 'ChatbotLogRetentionInteg');

app.synth();

Loading