-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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(scheduler): ScheduleGroup #26196
Changes from 1 commit
043ab0b
c97bb52
6462b60
68ccc82
6150b10
e083f0c
76d2c25
ab8367e
510c43d
b792f76
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 |
---|---|---|
@@ -0,0 +1,363 @@ | ||
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; | ||
import * as iam from 'aws-cdk-lib/aws-iam'; | ||
import { CfnScheduleGroup } from 'aws-cdk-lib/aws-scheduler'; | ||
import { Arn, ArnFormat, Aws, IResource, PhysicalName, RemovalPolicy, Resource, Stack } from 'aws-cdk-lib/core'; | ||
import { Construct } from 'constructs'; | ||
import { Schedule } from './schedule'; | ||
|
||
export interface GroupProps { | ||
/** | ||
* The name of the schedule group. | ||
* | ||
* Up to 64 letters (uppercase and lowercase), numbers, hyphens, underscores and dots are allowed. | ||
* | ||
* @default - A unique name will be generated | ||
*/ | ||
readonly groupName?: string; | ||
|
||
/** | ||
* The removal policy for the group. If the group is removed also all schedules are removed. | ||
* | ||
* @default RemovalPolicy.RETAIN | ||
*/ | ||
readonly removalPolicy?: RemovalPolicy; | ||
} | ||
|
||
export interface IGroup extends IResource { | ||
/** | ||
* The name of the schedule group | ||
* | ||
* @attribute | ||
*/ | ||
readonly groupName: string; | ||
|
||
/** | ||
* The arn of the schedule group | ||
* | ||
* @attribute | ||
*/ | ||
readonly groupArn: string; | ||
|
||
addSchedules(...schedules: Schedule[]): void; | ||
|
||
/** | ||
* Return the given named metric for this group schedules | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Metric for the number of invocations that were throttled because it exceeds your service quotas. | ||
* | ||
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/scheduler-quotas.html | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Metric for all invocation attempts. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Emitted when the target returns an exception after EventBridge Scheduler calls the target API. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricTargetErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Metric for invocation failures due to API throttling by the target. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Metric for dropped invocations when EventBridge Scheduler stops attempting to invoke the target after a schedule's retry policy has been exhausted. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Metric for invocations delivered to the DLQ | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricSentToDLQ(props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Metric for failed invocations that also failed to deliver to DLQ. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricFailedToBeSentToDLQ(errorCode?: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Metric for delivery of failed invocations to DLQ when the payload of the event sent to the DLQ exceeds the maximum size allowed by Amazon SQS. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricSentToDLQTrunacted(props?: cloudwatch.MetricOptions): cloudwatch.Metric; | ||
|
||
/** | ||
* Grant the indicated permissions on this group to the given principal | ||
*/ | ||
grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; | ||
/** | ||
* Grant list and get schedule permissions for schedules in this group to the given principal | ||
*/ | ||
grantReadSchedules(identity: iam.IGrantable): iam.Grant; | ||
/** | ||
* Grant create and update schedule permissions for schedules in this group to the given principal | ||
*/ | ||
grantWriteSchedules(identity: iam.IGrantable): iam.Grant; | ||
/** | ||
* Grant delete schedule permission for schedules in this group to the given principal | ||
*/ | ||
grantDeleteSchedules(identity: iam.IGrantable): iam.Grant | ||
} | ||
|
||
abstract class GroupBase extends Resource implements IGroup { | ||
/** | ||
* The name of the schedule group | ||
* | ||
* @attribute | ||
*/ | ||
public abstract readonly groupName: string; | ||
|
||
/** | ||
* The arn of the schedule group | ||
* | ||
* @attribute | ||
*/ | ||
public abstract readonly groupArn: string; | ||
|
||
addSchedules(...schedules: Schedule[]): void { | ||
schedules.forEach(schedule => { | ||
schedule.group = this; | ||
}); | ||
} | ||
|
||
/** | ||
* Return the given named metric for this group schedules | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return new cloudwatch.Metric({ | ||
namespace: 'AWS/Scheduler', | ||
metricName, | ||
dimensionsMap: { ScheduleGroup: this.groupName }, | ||
statistic: 'sum', | ||
...props, | ||
}).attachTo(this); | ||
} | ||
|
||
/** | ||
* Metric for the number of invocations that were throttled because it exceeds your service quotas. | ||
* | ||
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/scheduler-quotas.html | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
public metricThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return this.metric('InvocationThrottleCount', props); | ||
} | ||
|
||
/** | ||
* Metric for all invocation attempts. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
public metricAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return this.metric('InvocationAttemptCount', props); | ||
} | ||
|
||
/** | ||
* Emitted when the target returns an exception after EventBridge Scheduler calls the target API. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
public metricTargetErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return this.metric('TargetErrorCount', props); | ||
} | ||
|
||
/** | ||
* Metric for invocation failures due to API throttling by the target. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
public metricTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return this.metric('TargetErrorThrottledCount', props); | ||
} | ||
|
||
/** | ||
* Metric for dropped invocations when EventBridge Scheduler stops attempting to invoke the target after a schedule's retry policy has been exhausted. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
public metricDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return this.metric('InvocationDroppedCount', props); | ||
} | ||
|
||
/** | ||
* Metric for invocations delivered to the DLQ | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricSentToDLQ(props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return this.metric('InvocationsSentToDeadLetterCount', props); | ||
} | ||
|
||
/** | ||
* Metric for failed invocations that also failed to deliver to DLQ. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricFailedToBeSentToDLQ(errorCode?: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
if (errorCode) { | ||
return this.metric(`InvocationsFailedToBeSentToDeadLetterCount_${errorCode}`, props); | ||
} | ||
|
||
return this.metric('InvocationsFailedToBeSentToDeadLetterCount', props); | ||
} | ||
|
||
/** | ||
* Metric for delivery of failed invocations to DLQ when the payload of the event sent to the DLQ exceeds the maximum size allowed by Amazon SQS. | ||
* | ||
* @default - sum over 5 minutes | ||
*/ | ||
metricSentToDLQTrunacted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { | ||
return this.metric('InvocationsSentToDeadLetterCount_Truncated_MessageSizeExceeded', props); | ||
} | ||
|
||
/** | ||
* Grant the indicated permissions on this group to the given principal | ||
*/ | ||
grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { | ||
return iam.Grant.addToPrincipal({ | ||
grantee, | ||
actions, | ||
resourceArns: [this.groupArn], | ||
scope: this, | ||
}); | ||
} | ||
|
||
arnForScheduleInGroup(scheduleName: string): string { | ||
return Arn.format({ | ||
region: this.env.region, | ||
account: this.env.account, | ||
partition: Aws.PARTITION, | ||
service: 'scheduler', | ||
resource: 'schedule', | ||
resourceName: this.groupName + '/' + scheduleName, | ||
}); | ||
} | ||
filletofish marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Grant list and get schedule permissions for schedules in this group to the given principal | ||
*/ | ||
grantReadSchedules(identity: iam.IGrantable) { | ||
return iam.Grant.addToPrincipal({ | ||
grantee: identity, | ||
actions: ['scheduler:GetSchedule', 'scheduler:ListSchedules'], | ||
resourceArns: [this.arnForScheduleInGroup('*')], | ||
scope: this, | ||
}); | ||
} | ||
|
||
/** | ||
* Grant create and update schedule permissions for schedules in this group to the given principal | ||
*/ | ||
grantWriteSchedules(identity: iam.IGrantable): iam.Grant { | ||
return iam.Grant.addToPrincipal({ | ||
grantee: identity, | ||
actions: ['scheduler:CreateSchedule', 'scheduler:UpdateSchedule'], | ||
resourceArns: [this.arnForScheduleInGroup('*')], | ||
scope: this, | ||
}); | ||
} | ||
|
||
/** | ||
* Grant delete schedule permission for schedules in this group to the given principal | ||
*/ | ||
grantDeleteSchedules(identity: iam.IGrantable): iam.Grant { | ||
return iam.Grant.addToPrincipal({ | ||
grantee: identity, | ||
actions: ['scheduler:DeleteSchedule'], | ||
resourceArns: [this.arnForScheduleInGroup('*')], | ||
scope: this, | ||
}); | ||
} | ||
} | ||
|
||
export class Group extends GroupBase { | ||
/** | ||
* Import an external group by ARN. | ||
* | ||
* @param scope construct scope | ||
* @param id construct id | ||
* @param groupArn the ARN of the group to import (e.g. `arn:aws:scheduler:region:account-id:schedule-group/group-name`) | ||
*/ | ||
public static fromGroupArn(scope: Construct, id: string, groupArn: string): IGroup { | ||
const arnComponents = Stack.of(scope).splitArn(groupArn, ArnFormat.SLASH_RESOURCE_NAME); | ||
const groupName = arnComponents.resourceName!; | ||
class Import extends GroupBase { | ||
public groupName = groupName; | ||
public groupArn = groupArn; | ||
} | ||
return new Import(scope, id); | ||
} | ||
|
||
/** | ||
* Import a default schedule group. | ||
* | ||
* @param scope construct scope | ||
* @param id construct id | ||
*/ | ||
public static fromDefaultGroup(scope: Construct, id: string): IGroup { | ||
return Group.fromGroupName(scope, id, 'default'); | ||
} | ||
|
||
/** | ||
* Import an existing group with a given name. | ||
* | ||
* @param scope construct scope | ||
* @param id construct id | ||
* @param groupName the name of the existing group to import | ||
*/ | ||
static fromGroupName(scope: Construct, id: string, groupName: string): IGroup { | ||
const groupArn = Stack.of(scope).formatArn({ | ||
service: 'scheduler', | ||
resource: 'schedule-group', | ||
resourceName: groupName, | ||
}); | ||
return Group.fromGroupArn(scope, id, groupArn); | ||
} | ||
|
||
public readonly groupName: string; | ||
public readonly groupArn: string; | ||
|
||
constructor(scope: Construct, id: string, props: GroupProps) { | ||
super(scope, id, { | ||
physicalName: props.groupName ?? PhysicalName.GENERATE_IF_NEEDED, | ||
}); | ||
|
||
const group = new CfnScheduleGroup(this, 'Resource', { | ||
name: this.physicalName, | ||
}); | ||
|
||
this.groupArn = this.getResourceArnAttribute(group.attrArn, { | ||
service: 'scheduler', | ||
resource: 'schedule-group', | ||
resourceName: this.physicalName, | ||
}); | ||
this.groupName = this.physicalName; | ||
} | ||
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 think a call to applyRemovalPolicy is missing here? This means the removalPolicy from the props will not be emitted to CloudFormation. (+ that needs a test) 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. Thanks! I have actually missed that.
Are we sure that we want the default to be |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './schedule-expression'; | ||
export * from './input'; | ||
export * from './schedule'; | ||
export * from './schedule'; | ||
export * from './group'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,13 @@ | ||
import { IResource } from 'aws-cdk-lib'; | ||
import { IResource, Resource } from 'aws-cdk-lib'; | ||
import { IGroup } from './group'; | ||
|
||
/** | ||
* Interface representing a created or an imported `Schedule`. | ||
*/ | ||
export interface ISchedule extends IResource { | ||
group?: IGroup; | ||
} | ||
|
||
export class Schedule extends Resource implements ISchedule { | ||
group?: IGroup; | ||
} |
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.
Compared to RFC I have updated the method names for granting permissions. From
grantRead
tograntReadSchedules
- to be more precise that the method grants permissions to manage schedules in the group, not the permissions for the group itself.There are IAM permissions for the schedules and for the groups.