-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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(lambda): configurable retries for log retention custom resource #8258
Changes from 6 commits
53fcc4e
4856cb3
044a81c
4f3ef1d
1f86f2f
5e9a4f0
7ca18c7
f33ae20
c4823d5
1c6f5d7
6708723
6f4057c
458b074
1c15e29
7de918b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,16 @@ | |
|
||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import * as AWS from 'aws-sdk'; | ||
import { LogRetentionRetryOptions } from '../log-retention'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this will work, the lambda function execution should fail. Did you try deploying a stack that used this? This file gets deployed as a lambda function during stack deployment. The CDK code is not available there and it should not be. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I linked it using a postinstall hook in my local test project. Then it works (I guess since it's just an interface that doesn't add any logic to the deployed lambda function). But this is indeed incorrect, I'll fix it. |
||
|
||
/** | ||
* Creates a log group and doesn't throw if it exists. | ||
* | ||
* @param logGroupName the name of the log group to create | ||
*/ | ||
async function createLogGroupSafe(logGroupName: string) { | ||
async function createLogGroupSafe(logGroupName: string, retryOptions?: LogRetentionRetryOptions) { | ||
try { // Try to create the log group | ||
const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); | ||
const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', ...retryOptions }); | ||
await cloudwatchlogs.createLogGroup({ logGroupName }).promise(); | ||
} catch (e) { | ||
if (e.code !== 'ResourceAlreadyExistsException') { | ||
|
@@ -25,8 +26,8 @@ async function createLogGroupSafe(logGroupName: string) { | |
* @param logGroupName the name of the log group to create | ||
* @param retentionInDays the number of days to retain the log events in the specified log group. | ||
*/ | ||
async function setRetentionPolicy(logGroupName: string, retentionInDays?: number) { | ||
const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); | ||
async function setRetentionPolicy(logGroupName: string, retryOptions?: LogRetentionRetryOptions, retentionInDays?: number) { | ||
const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28', ...retryOptions }); | ||
if (!retentionInDays) { | ||
await cloudwatchlogs.deleteRetentionPolicy({ logGroupName }).promise(); | ||
} else { | ||
|
@@ -41,10 +42,13 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent | |
// The target log group | ||
const logGroupName = event.ResourceProperties.LogGroupName; | ||
|
||
// Parse retry options for creating the target log group | ||
const retryOptions = parseRetryOptions(event.ResourceProperties.LogRetentionRetryOptions); | ||
|
||
if (event.RequestType === 'Create' || event.RequestType === 'Update') { | ||
// Act on the target log group | ||
await createLogGroupSafe(logGroupName); | ||
await setRetentionPolicy(logGroupName, parseInt(event.ResourceProperties.RetentionInDays, 10)); | ||
await createLogGroupSafe(logGroupName, retryOptions); | ||
await setRetentionPolicy(logGroupName, retryOptions, parseInt(event.ResourceProperties.RetentionInDays, 10)); | ||
|
||
if (event.RequestType === 'Create') { | ||
// Set a retention policy of 1 day on the logs of this function. The log | ||
|
@@ -56,8 +60,8 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent | |
// same time. This can sometime result in an OperationAbortedException. To | ||
// avoid this and because this operation is not critical we catch all errors. | ||
try { | ||
await createLogGroupSafe(`/aws/lambda/${context.functionName}`); | ||
await setRetentionPolicy(`/aws/lambda/${context.functionName}`, 1); | ||
await createLogGroupSafe(`/aws/lambda/${context.functionName}`, retryOptions); | ||
await setRetentionPolicy(`/aws/lambda/${context.functionName}`, retryOptions, 1); | ||
} catch (e) { | ||
console.log(e); | ||
} | ||
|
@@ -108,4 +112,19 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent | |
} | ||
}); | ||
} | ||
|
||
function parseRetryOptions(rawOptions: any) { | ||
const retryOptions: { maxRetries?: number, retryOptions?: { base?: number } } = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be nice to take advantage of type checking (instead of using interface SdkRetryOptions {
readonly maxRetries?: number;
retryOptions?: AWS.RetryDelayOptions;
}
|
||
if (rawOptions) { | ||
if (rawOptions.maxRetries) { | ||
retryOptions.maxRetries = parseInt(rawOptions.maxRetries, 10); | ||
} | ||
if (rawOptions.base) { | ||
retryOptions.retryOptions = { | ||
base: parseInt(rawOptions.base, 10), | ||
}; | ||
} | ||
} | ||
return retryOptions; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -26,6 +26,31 @@ export interface LogRetentionProps { | |||||
* @default - A new role is created | ||||||
*/ | ||||||
readonly role?: iam.IRole; | ||||||
|
||||||
/** | ||||||
* Retry options for managing CloudWatch log groups. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this?
Suggested change
|
||||||
* | ||||||
* @default - AWS SDK default retry options | ||||||
*/ | ||||||
readonly logRetentionRetryOptions?: LogRetentionRetryOptions; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Retry options for managing CloudWatch log groups | ||||||
*/ | ||||||
export interface LogRetentionRetryOptions { | ||||||
/** | ||||||
* The maximum amount of retries. | ||||||
* | ||||||
* @default - AWS SDK default | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We usually document the default, even if it comes from a dependency There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm, yeah I can change that, but left it like this on purpose. How will you make sure it stays in sync? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unlikely the SDK's defaults change. Changing so may break existing customers using the CLI whose applications work because of a given set of defaults, so this is likely to be considered a breaking change. It may change on the next major version but we have better control over that from the import statements. |
||||||
*/ | ||||||
readonly maxRetries?: number; | ||||||
nija-at marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/** | ||||||
* The base number of milliseconds to use in the exponential backoff for operation retries. | ||||||
* | ||||||
* @default - AWS SDK default | ||||||
*/ | ||||||
readonly base?: number; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||||||
} | ||||||
|
||||||
/** | ||||||
|
@@ -69,6 +94,7 @@ export class LogRetention extends cdk.Construct { | |||||
properties: { | ||||||
ServiceToken: provider.functionArn, | ||||||
LogGroupName: props.logGroupName, | ||||||
LogRetentionRetryOptions: props.logRetentionRetryOptions, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, will change |
||||||
RetentionInDays: props.retention === logs.RetentionDays.INFINITE ? undefined : props.retention, | ||||||
}, | ||||||
}); | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sufficient to just document what these options are. There can be many reasons to use them.
Something like this -
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, will change that.