From 043ab0b3c1c9fa3b8f0f0ff46c082c5638376425 Mon Sep 17 00:00:00 2001 From: Filipp Fediakov Date: Sun, 2 Jul 2023 18:55:29 +0000 Subject: [PATCH 1/7] Implement Schedule Group --- .../@aws-cdk/aws-scheduler-alpha/lib/group.ts | 363 ++++++++++++++++++ .../@aws-cdk/aws-scheduler-alpha/lib/index.ts | 3 +- .../aws-scheduler-alpha/lib/schedule.ts | 7 +- .../aws-scheduler-alpha/test/group.test.ts | 265 +++++++++++++ 4 files changed, 636 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts create mode 100644 packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts new file mode 100644 index 0000000000000..323e5258d4656 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts @@ -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, + }); + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/index.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/index.ts index c2ff54e61f61b..f6b79a57257ae 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/index.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/index.ts @@ -1,3 +1,4 @@ export * from './schedule-expression'; export * from './input'; -export * from './schedule'; \ No newline at end of file +export * from './schedule'; +export * from './group'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts index 23bcd9406c0d2..d5c80d5f6c0da 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts @@ -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; } diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts new file mode 100644 index 0000000000000..7172bafa06da1 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts @@ -0,0 +1,265 @@ +import { Duration, Stack } from 'aws-cdk-lib'; +import { Match, Template } from 'aws-cdk-lib/assertions'; +import * as cw from 'aws-cdk-lib/aws-cloudwatch'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import { CfnScheduleGroup } from 'aws-cdk-lib/aws-scheduler'; +import { Schedule } from '../lib'; +import { Group, GroupProps } from '../lib/group'; + +describe('Schedule Group', () => { + let stack: Stack; + + beforeEach(() => { + stack = new Stack(); + }); + + test('creates a group with default properties', () => { + const props: GroupProps = {}; + const group = new Group(stack, 'TestGroup', props); + + expect(group).toBeInstanceOf(Group); + expect(group.groupName).toBeDefined(); + expect(group.groupArn).toBeDefined(); + + const resource = group.node.findChild('Resource') as CfnScheduleGroup; + expect(resource).toBeInstanceOf(CfnScheduleGroup); + expect(resource.name).toEqual(group.groupName); + }); + + test('creates a group with specified name', () => { + const props: GroupProps = { + groupName: 'MyGroup', + }; + const group = new Group(stack, 'TestGroup', props); + const resource = group.node.findChild('Resource') as CfnScheduleGroup; + expect(resource).toBeInstanceOf(CfnScheduleGroup); + expect(resource.name).toEqual(group.groupName); + + Template.fromStack(stack).hasResource('AWS::Scheduler::ScheduleGroup', { + Properties: { + Name: `${props.groupName}`, + }, + }); + }); + + test('creates a group from ARN', () => { + const groupArn = 'arn:aws:scheduler:region:account-id:schedule-group/group-name'; + const group = Group.fromGroupArn(stack, 'TestGroup', groupArn); + + expect(group.groupArn).toBeDefined(); + expect(group.groupName).toEqual('group-name'); + + const groups = Template.fromStack(stack).findResources('AWS::Scheduler::ScheduleGroup'); + expect(groups).toEqual({}); + }); + + test('creates a group from name', () => { + const groupName = 'MyGroup'; + const group = Group.fromGroupName(stack, 'TestGroup', groupName); + + expect(group.groupArn).toBeDefined(); + expect(group.groupName).toEqual(groupName); + + const groups = Template.fromStack(stack).findResources('AWS::Scheduler::ScheduleGroup'); + expect(groups).toEqual({}); + }); + + test('creates a group from default group', () => { + const group = Group.fromDefaultGroup(stack, 'DefaultGroup'); + + expect(group.groupArn).toBeDefined(); + expect(group.groupName).toEqual('default'); + + const groups = Template.fromStack(stack).findResources('AWS::Scheduler::ScheduleGroup'); + expect(groups).toEqual({}); + }); + + test('adds schedules to the group', () => { + const props: GroupProps = { + groupName: 'MyGroup', + }; + const group = new Group(stack, 'TestGroup', props); + + const schedule1 = new Schedule(stack, 'Schedule1'); + const schedule2 = new Schedule(stack, 'Schedule2'); + + group.addSchedules(schedule1, schedule2); + + expect(schedule1.group).toEqual(group); + expect(schedule2.group).toEqual(group); + }); + + test('grantReadSchedules', () => { + // GIVEN + const props: GroupProps = { + groupName: 'MyGroup', + }; + const group = new Group(stack, 'TestGroup', props); + + const user = new iam.User(stack, 'User'); + + // WHEN + group.grantReadSchedules(user); + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'scheduler:GetSchedule', + 'scheduler:ListSchedules', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':scheduler:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':schedule/MyGroup/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('grantWriteSchedules', () => { + // GIVEN + const props: GroupProps = { + groupName: 'MyGroup', + }; + const group = new Group(stack, 'TestGroup', props); + + const user = new iam.User(stack, 'User'); + + // WHEN + group.grantWriteSchedules(user); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'scheduler:CreateSchedule', + 'scheduler:UpdateSchedule', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':scheduler:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':schedule/MyGroup/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('grantDeleteSchedules', () => { + // GIVEN + const props: GroupProps = { + groupName: 'MyGroup', + }; + const group = new Group(stack, 'TestGroup', props); + + const user = new iam.User(stack, 'User'); + + // WHEN + group.grantDeleteSchedules(user); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 'scheduler:DeleteSchedule', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':scheduler:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':schedule/MyGroup/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + + test('Target Error Metrics', () => { + // GIVEN + const props: GroupProps = { + groupName: 'MyGroup', + }; + const group = new Group(stack, 'TestGroup', props); + + // WHEN + const metricTargetErrors = group.metricTargetErrors({ + period: Duration.minutes(1), + }); + + new cw.Alarm(stack, 'GroupTargetErrorAlarm', { + metric: metricTargetErrors, + evaluationPeriods: 1, + threshold: 1, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::CloudWatch::Alarm', { + Dimensions: Match.arrayWith([ + Match.objectLike({ + Name: 'ScheduleGroup', + Value: 'MyGroup', + }), + ]), + MetricName: 'TargetErrorCount', + Namespace: 'AWS/Scheduler', + }); + }); +}); \ No newline at end of file From c97bb5203424896340362a367f662503630e81dd Mon Sep 17 00:00:00 2001 From: Filipp Fediakov Date: Sun, 2 Jul 2023 19:19:38 +0000 Subject: [PATCH 2/7] Use schedule from private sub-package --- .../@aws-cdk/aws-scheduler-alpha/lib/group.ts | 2 +- .../lib/private/schedule.ts | 15 ++++++++- .../aws-scheduler-alpha/lib/schedule.ts | 11 ++----- .../rosetta/default.ts-fixture | 2 +- .../aws-scheduler-alpha/test/group.test.ts | 31 +++++++++++++++++-- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts index 323e5258d4656..d7d2e352141db 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts @@ -3,7 +3,7 @@ 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'; +import { Schedule } from './private'; export interface GroupProps { /** diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts index 0e2b33742d18f..9773d5e5f052e 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts @@ -1,6 +1,7 @@ -import { Resource } from 'aws-cdk-lib'; +import { Lazy, Resource } from 'aws-cdk-lib'; import { CfnSchedule } from 'aws-cdk-lib/aws-scheduler'; import { Construct } from 'constructs'; +import { IGroup } from '../group'; import { ISchedule } from '../schedule'; import { ScheduleExpression } from '../schedule-expression'; @@ -36,19 +37,31 @@ export interface ScheduleProps { * @default - no value */ readonly description?: string; + + /** + * The schedule's group. + * + * @deafult - By default a schedule will be associated with the `default` group. + */ + readonly group?: IGroup; } /** * An EventBridge Schedule */ export class Schedule extends Resource implements ISchedule { + group?: IGroup; + constructor(scope: Construct, id: string, props: ScheduleProps) { super(scope, id); + this.group = props.group; + new CfnSchedule(this, 'Resource', { flexibleTimeWindow: { mode: 'OFF' }, scheduleExpression: props.schedule.expressionString, scheduleExpressionTimezone: props.schedule.timeZone?.timezoneName, + groupName: Lazy.string({ produce: () => this.group?.groupName }), target: { ...props.target.bind(this), }, diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts index d5c80d5f6c0da..6f51b262353cf 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts @@ -1,13 +1,6 @@ -import { IResource, Resource } from 'aws-cdk-lib'; -import { IGroup } from './group'; +import { IResource } from 'aws-cdk-lib'; /** * Interface representing a created or an imported `Schedule`. */ -export interface ISchedule extends IResource { - group?: IGroup; -} - -export class Schedule extends Resource implements ISchedule { - group?: IGroup; -} +export interface ISchedule extends IResource {} diff --git a/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture index 776fd224ec9b1..661565e353ad3 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture @@ -7,7 +7,7 @@ import * as kms from 'aws-cdk-lib/aws-kms'; import * as sqs from 'aws-cdk-lib/aws-sqs'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import { App, Stack, TimeZone, Duration } from 'aws-cdk-lib'; -import { ScheduleExpression, ScheduleTargetInput, ContextAttribute } from '@aws-cdk/aws-scheduler-alpha'; +import { ScheduleExpression, ScheduleTargetInput, ContextAttribute, Group } from '@aws-cdk/aws-scheduler-alpha'; class Fixture extends cdk.Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts index 7172bafa06da1..10ec3fbfb3efa 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts @@ -2,15 +2,22 @@ import { Duration, Stack } from 'aws-cdk-lib'; import { Match, Template } from 'aws-cdk-lib/assertions'; import * as cw from 'aws-cdk-lib/aws-cloudwatch'; import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; import { CfnScheduleGroup } from 'aws-cdk-lib/aws-scheduler'; -import { Schedule } from '../lib'; +import { ScheduleExpression, ScheduleTargetInput } from '../lib'; import { Group, GroupProps } from '../lib/group'; +import { Schedule, targets } from '../lib/private'; describe('Schedule Group', () => { let stack: Stack; + let func: lambda.IFunction; + let role: iam.IRole; + const expr = ScheduleExpression.at(new Date(Date.UTC(1969, 10, 20, 0, 0, 0))); beforeEach(() => { stack = new Stack(); + role = iam.Role.fromRoleArn(stack, 'Role', 'arn:aws:iam::123456789012:role/johndoe'); + func = lambda.Function.fromFunctionArn(stack, 'Function', 'arn:aws:lambda:us-east-1:123456789012:function/somefunc'); }); test('creates a group with default properties', () => { @@ -80,13 +87,31 @@ describe('Schedule Group', () => { }; const group = new Group(stack, 'TestGroup', props); - const schedule1 = new Schedule(stack, 'Schedule1'); - const schedule2 = new Schedule(stack, 'Schedule2'); + const schedule1 = new Schedule(stack, 'MyScheduleDummy1', { + schedule: expr, + target: new targets.LambdaInvoke({ + role, + input: ScheduleTargetInput.fromText('test'), + }, func), + }); + const schedule2 = new Schedule(stack, 'MyScheduleDummy2', { + schedule: expr, + target: new targets.LambdaInvoke({ + role, + input: ScheduleTargetInput.fromText('test'), + }, func), + }); group.addSchedules(schedule1, schedule2); expect(schedule1.group).toEqual(group); expect(schedule2.group).toEqual(group); + + Template.fromStack(stack).hasResource('AWS::Scheduler::Schedule', { + Properties: { + GroupName: `${props.groupName}`, + }, + }); }); test('grantReadSchedules', () => { From 6462b60b9d1527d8196313551437aa40af4d9e2a Mon Sep 17 00:00:00 2001 From: Filipp Fediakov Date: Sun, 2 Jul 2023 20:20:08 +0100 Subject: [PATCH 3/7] Update README.md --- .../@aws-cdk/aws-scheduler-alpha/README.md | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/README.md b/packages/@aws-cdk/aws-scheduler-alpha/README.md index 7cc01e45a962a..ca313f9e10a12 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/README.md +++ b/packages/@aws-cdk/aws-scheduler-alpha/README.md @@ -44,6 +44,7 @@ TODO: Schedule is not yet fully implemented. See section in [L2 Event Bridge Sch Only an L2 class is created that wraps the L1 class and handles the following properties: - schedule +- schedule group - target (only LambdaInvoke is supported for now) - flexibleTimeWindow will be set to `{ mode: 'OFF' }` @@ -97,7 +98,44 @@ const oneTimeSchedule = new Schedule(this, 'Schedule', { ### Grouping Schedules -TODO: Group is not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) +Your AWS account comes with a default scheduler group, which you can access in CDK with: + + ```text + const defaultGroup = Group.fromDefaultGroup(this, "DefaultGroup"); + ``` + + When creating a new schedule, you can also add the schedule to a custom scheduling group managed by you: + + ```text + const group = new Group(this, "Group", { + groupName: "MyGroup", + }); + + const target = new targets.LambdaInvoke(props.func, { + input: ScheduleTargetInput.fromObject({ + "payload": "useful", + }), + }); + + const schedule1 = new Schedule(this, 'Schedule1', { + scheduleExpression: ScheduleExpression.rate(Duration.minutes(10)), + target, + }); + + const schedule2 = new Schedule(this, 'Schedule2', { + scheduleExpression: ScheduleExpression.rate(Duration.minutes(5)), + target, + }); + + group.addSchedules(schedule1, schedule2); + + // You can also assign groups in a schedule constructor: + const schedule3 = new Schedule(this, 'Schedule2', { + scheduleExpression: ScheduleExpression.rate(Duration.minutes(5)), + group, + target, + }); + ``` ## Scheduler Targets From 68ccc82891cb4b2802d498c4cba09005bee28cb2 Mon Sep 17 00:00:00 2001 From: Filipp Fediakov Date: Thu, 6 Jul 2023 07:13:32 +0000 Subject: [PATCH 4/7] Fix build by depending on ISchedule instead of Schedule Plus this commit fixes access modifiers for methods in group.ts --- .../@aws-cdk/aws-scheduler-alpha/lib/group.ts | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts index d7d2e352141db..743852323ec99 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts @@ -4,6 +4,7 @@ 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 './private'; +import { ISchedule } from './schedule'; export interface GroupProps { /** @@ -38,7 +39,11 @@ export interface IGroup extends IResource { */ readonly groupArn: string; - addSchedules(...schedules: Schedule[]): void; + /** + * Add schedules to this schedule group. + * @param schedules + */ + addSchedules(...schedules: ISchedule[]): void; /** * Return the given named metric for this group schedules @@ -129,18 +134,22 @@ abstract class GroupBase extends Resource implements IGroup { * * @attribute */ - public abstract readonly groupName: string; + abstract readonly groupName: string; /** * The arn of the schedule group * * @attribute */ - public abstract readonly groupArn: string; + abstract readonly groupArn: string; - addSchedules(...schedules: Schedule[]): void { + addSchedules(...schedules: ISchedule[]): void { schedules.forEach(schedule => { - schedule.group = this; + // Temporary using ISchedule as parameter for this method, + // and checking for Schedule type, until class Schedule is moved from private to public + if (schedule instanceof Schedule) { + schedule.group = this; + } }); } @@ -149,7 +158,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ namespace: 'AWS/Scheduler', metricName, @@ -166,7 +175,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - public metricThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + metricThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('InvocationThrottleCount', props); } @@ -175,7 +184,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - public metricAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + metricAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('InvocationAttemptCount', props); } @@ -184,7 +193,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - public metricTargetErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + metricTargetErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('TargetErrorCount', props); } @@ -193,7 +202,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - public metricTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + metricTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('TargetErrorThrottledCount', props); } @@ -202,7 +211,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - public metricDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + metricDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('InvocationDroppedCount', props); } @@ -249,7 +258,7 @@ abstract class GroupBase extends Resource implements IGroup { }); } - arnForScheduleInGroup(scheduleName: string): string { + private arnForScheduleInGroup(scheduleName: string): string { return Arn.format({ region: this.env.region, account: this.env.account, @@ -305,12 +314,12 @@ export class Group extends GroupBase { * @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 { + 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; + groupName = groupName; + groupArn = groupArn; } return new Import(scope, id); } @@ -321,7 +330,7 @@ export class Group extends GroupBase { * @param scope construct scope * @param id construct id */ - public static fromDefaultGroup(scope: Construct, id: string): IGroup { + static fromDefaultGroup(scope: Construct, id: string): IGroup { return Group.fromGroupName(scope, id, 'default'); } @@ -341,8 +350,8 @@ export class Group extends GroupBase { return Group.fromGroupArn(scope, id, groupArn); } - public readonly groupName: string; - public readonly groupArn: string; + readonly groupName: string; + readonly groupArn: string; constructor(scope: Construct, id: string, props: GroupProps) { super(scope, id, { From 6150b102bc8666250cb00505c2200519d33265bc Mon Sep 17 00:00:00 2001 From: Filipp Fediakov Date: Thu, 13 Jul 2023 20:11:17 +0000 Subject: [PATCH 5/7] Update with pr comments - Remove addSchedules method - Update readme - Apply removal policy --- .../@aws-cdk/aws-scheduler-alpha/README.md | 87 ++++++++++--------- .../@aws-cdk/aws-scheduler-alpha/lib/group.ts | 60 +++++-------- .../lib/private/schedule.ts | 4 +- .../aws-scheduler-alpha/test/group.test.ts | 17 +++- 4 files changed, 86 insertions(+), 82 deletions(-) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/README.md b/packages/@aws-cdk/aws-scheduler-alpha/README.md index ca313f9e10a12..666089fd25595 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/README.md +++ b/packages/@aws-cdk/aws-scheduler-alpha/README.md @@ -98,44 +98,31 @@ const oneTimeSchedule = new Schedule(this, 'Schedule', { ### Grouping Schedules -Your AWS account comes with a default scheduler group, which you can access in CDK with: - - ```text - const defaultGroup = Group.fromDefaultGroup(this, "DefaultGroup"); - ``` - - When creating a new schedule, you can also add the schedule to a custom scheduling group managed by you: - - ```text - const group = new Group(this, "Group", { - groupName: "MyGroup", - }); - - const target = new targets.LambdaInvoke(props.func, { - input: ScheduleTargetInput.fromObject({ - "payload": "useful", - }), - }); - - const schedule1 = new Schedule(this, 'Schedule1', { - scheduleExpression: ScheduleExpression.rate(Duration.minutes(10)), - target, - }); - - const schedule2 = new Schedule(this, 'Schedule2', { - scheduleExpression: ScheduleExpression.rate(Duration.minutes(5)), - target, - }); - - group.addSchedules(schedule1, schedule2); - - // You can also assign groups in a schedule constructor: - const schedule3 = new Schedule(this, 'Schedule2', { - scheduleExpression: ScheduleExpression.rate(Duration.minutes(5)), - group, - target, - }); - ``` +Your AWS account comes with a default scheduler group. You can access default group in CDK with: + +```text +const defaultGroup = Group.fromDefaultGroup(this, "DefaultGroup"); +``` + +If not specified a schedule is added to the default group. However, uou can also add the schedule to a custom scheduling group managed by you: + +```text +const group = new Group(this, "Group", { + groupName: "MyGroup", +}); + +const target = new targets.LambdaInvoke(props.func, { + input: ScheduleTargetInput.fromObject({ + "payload": "useful", + }), +}); + +new Schedule(this, 'Schedule', { + scheduleExpression: ScheduleExpression.rate(Duration.minutes(10)), + target, + group +}); +``` ## Scheduler Targets @@ -202,4 +189,26 @@ TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https: ### Metrics for a Group -TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https://github.com/aws/aws-cdk-rfcs/blob/master/text/0474-event-bridge-scheduler-l2.md) +To view metrics for a specific group you can use methods on class `Group`: + +```text +const group = new Group(this, "Group", { + groupName: "MyGroup" +}); + +new Alarm(this, 'MyGroupErrorAlarm', { + metric: group.metricAllErrors(), + threshold: 0 +}); + +// Or use default group +const defaultGroup = Group.fromDefaultGroup(this, "DefaultGroup"); +new Alarm(this, 'DefaultGroupErrorAlarm', { + metric: defaultGroup.metricAllErrors(), + threshold: 0 +}); +``` + +See full list of metrics and their description at +[Monitoring Using CloudWatch Metrics](https://docs.aws.amazon.com/scheduler/latest/UserGuide/monitoring-cloudwatch.html) +in the *AWS Event Bridge Scheduler User Guide*. diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts index 743852323ec99..536f53b4022dc 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/group.ts @@ -3,8 +3,6 @@ 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 './private'; -import { ISchedule } from './schedule'; export interface GroupProps { /** @@ -39,12 +37,6 @@ export interface IGroup extends IResource { */ readonly groupArn: string; - /** - * Add schedules to this schedule group. - * @param schedules - */ - addSchedules(...schedules: ISchedule[]): void; - /** * Return the given named metric for this group schedules * @@ -134,31 +126,21 @@ abstract class GroupBase extends Resource implements IGroup { * * @attribute */ - abstract readonly groupName: string; + public abstract readonly groupName: string; /** * The arn of the schedule group * * @attribute */ - abstract readonly groupArn: string; - - addSchedules(...schedules: ISchedule[]): void { - schedules.forEach(schedule => { - // Temporary using ISchedule as parameter for this method, - // and checking for Schedule type, until class Schedule is moved from private to public - if (schedule instanceof Schedule) { - schedule.group = this; - } - }); - } + public abstract readonly groupArn: string; /** * Return the given named metric for this group schedules * * @default - sum over 5 minutes */ - metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ namespace: 'AWS/Scheduler', metricName, @@ -175,7 +157,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - metricThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metricThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('InvocationThrottleCount', props); } @@ -184,7 +166,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - metricAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metricAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('InvocationAttemptCount', props); } @@ -193,7 +175,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - metricTargetErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metricTargetErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('TargetErrorCount', props); } @@ -202,7 +184,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - metricTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metricTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('TargetErrorThrottledCount', props); } @@ -211,7 +193,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - metricDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metricDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric('InvocationDroppedCount', props); } @@ -229,7 +211,7 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - metricFailedToBeSentToDLQ(errorCode?: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public metricFailedToBeSentToDLQ(errorCode?: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { if (errorCode) { return this.metric(`InvocationsFailedToBeSentToDeadLetterCount_${errorCode}`, props); } @@ -242,14 +224,14 @@ abstract class GroupBase extends Resource implements IGroup { * * @default - sum over 5 minutes */ - metricSentToDLQTrunacted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + public 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 { + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { return iam.Grant.addToPrincipal({ grantee, actions, @@ -272,7 +254,7 @@ abstract class GroupBase extends Resource implements IGroup { /** * Grant list and get schedule permissions for schedules in this group to the given principal */ - grantReadSchedules(identity: iam.IGrantable) { + public grantReadSchedules(identity: iam.IGrantable) { return iam.Grant.addToPrincipal({ grantee: identity, actions: ['scheduler:GetSchedule', 'scheduler:ListSchedules'], @@ -284,7 +266,7 @@ abstract class GroupBase extends Resource implements IGroup { /** * Grant create and update schedule permissions for schedules in this group to the given principal */ - grantWriteSchedules(identity: iam.IGrantable): iam.Grant { + public grantWriteSchedules(identity: iam.IGrantable): iam.Grant { return iam.Grant.addToPrincipal({ grantee: identity, actions: ['scheduler:CreateSchedule', 'scheduler:UpdateSchedule'], @@ -296,7 +278,7 @@ abstract class GroupBase extends Resource implements IGroup { /** * Grant delete schedule permission for schedules in this group to the given principal */ - grantDeleteSchedules(identity: iam.IGrantable): iam.Grant { + public grantDeleteSchedules(identity: iam.IGrantable): iam.Grant { return iam.Grant.addToPrincipal({ grantee: identity, actions: ['scheduler:DeleteSchedule'], @@ -314,7 +296,7 @@ export class Group extends GroupBase { * @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`) */ - static fromGroupArn(scope: Construct, id: string, groupArn: string): IGroup { + 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 { @@ -330,7 +312,7 @@ export class Group extends GroupBase { * @param scope construct scope * @param id construct id */ - static fromDefaultGroup(scope: Construct, id: string): IGroup { + public static fromDefaultGroup(scope: Construct, id: string): IGroup { return Group.fromGroupName(scope, id, 'default'); } @@ -341,7 +323,7 @@ export class Group extends GroupBase { * @param id construct id * @param groupName the name of the existing group to import */ - static fromGroupName(scope: Construct, id: string, groupName: string): IGroup { + public static fromGroupName(scope: Construct, id: string, groupName: string): IGroup { const groupArn = Stack.of(scope).formatArn({ service: 'scheduler', resource: 'schedule-group', @@ -350,10 +332,10 @@ export class Group extends GroupBase { return Group.fromGroupArn(scope, id, groupArn); } - readonly groupName: string; - readonly groupArn: string; + public readonly groupName: string; + public readonly groupArn: string; - constructor(scope: Construct, id: string, props: GroupProps) { + public constructor(scope: Construct, id: string, props: GroupProps) { super(scope, id, { physicalName: props.groupName ?? PhysicalName.GENERATE_IF_NEEDED, }); @@ -362,6 +344,8 @@ export class Group extends GroupBase { name: this.physicalName, }); + group.applyRemovalPolicy(props.removalPolicy); + this.groupArn = this.getResourceArnAttribute(group.attrArn, { service: 'scheduler', resource: 'schedule-group', diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts index 9773d5e5f052e..d117196a488fc 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts @@ -1,4 +1,4 @@ -import { Lazy, Resource } from 'aws-cdk-lib'; +import { Resource } from 'aws-cdk-lib'; import { CfnSchedule } from 'aws-cdk-lib/aws-scheduler'; import { Construct } from 'constructs'; import { IGroup } from '../group'; @@ -61,7 +61,7 @@ export class Schedule extends Resource implements ISchedule { flexibleTimeWindow: { mode: 'OFF' }, scheduleExpression: props.schedule.expressionString, scheduleExpressionTimezone: props.schedule.timeZone?.timezoneName, - groupName: Lazy.string({ produce: () => this.group?.groupName }), + groupName: this.group?.groupName, target: { ...props.target.bind(this), }, diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts index 10ec3fbfb3efa..28fa388a9c313 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/group.test.ts @@ -1,4 +1,4 @@ -import { Duration, Stack } from 'aws-cdk-lib'; +import { Duration, RemovalPolicy, Stack } from 'aws-cdk-lib'; import { Match, Template } from 'aws-cdk-lib/assertions'; import * as cw from 'aws-cdk-lib/aws-cloudwatch'; import * as iam from 'aws-cdk-lib/aws-iam'; @@ -33,6 +33,17 @@ describe('Schedule Group', () => { expect(resource.name).toEqual(group.groupName); }); + test('creates a group with removal policy', () => { + const props: GroupProps = { + removalPolicy: RemovalPolicy.RETAIN, + }; + new Group(stack, 'TestGroup', props); + + Template.fromStack(stack).hasResource('AWS::Scheduler::ScheduleGroup', { + DeletionPolicy: 'Retain', + }); + }); + test('creates a group with specified name', () => { const props: GroupProps = { groupName: 'MyGroup', @@ -89,6 +100,7 @@ describe('Schedule Group', () => { const schedule1 = new Schedule(stack, 'MyScheduleDummy1', { schedule: expr, + group: group, target: new targets.LambdaInvoke({ role, input: ScheduleTargetInput.fromText('test'), @@ -96,14 +108,13 @@ describe('Schedule Group', () => { }); const schedule2 = new Schedule(stack, 'MyScheduleDummy2', { schedule: expr, + group: group, target: new targets.LambdaInvoke({ role, input: ScheduleTargetInput.fromText('test'), }, func), }); - group.addSchedules(schedule1, schedule2); - expect(schedule1.group).toEqual(group); expect(schedule2.group).toEqual(group); From e083f0c9831a063969cac90cba414d77ec3b4ef9 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:39:08 -0400 Subject: [PATCH 6/7] Apply suggestions from code review --- packages/@aws-cdk/aws-scheduler-alpha/README.md | 10 +++++----- .../aws-scheduler-alpha/lib/private/schedule.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/README.md b/packages/@aws-cdk/aws-scheduler-alpha/README.md index 666089fd25595..45552ff3d906a 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/README.md +++ b/packages/@aws-cdk/aws-scheduler-alpha/README.md @@ -104,7 +104,7 @@ Your AWS account comes with a default scheduler group. You can access default gr const defaultGroup = Group.fromDefaultGroup(this, "DefaultGroup"); ``` -If not specified a schedule is added to the default group. However, uou can also add the schedule to a custom scheduling group managed by you: +If not specified a schedule is added to the default group. However, you can also add the schedule to a custom scheduling group managed by you: ```text const group = new Group(this, "Group", { @@ -120,7 +120,7 @@ const target = new targets.LambdaInvoke(props.func, { new Schedule(this, 'Schedule', { scheduleExpression: ScheduleExpression.rate(Duration.minutes(10)), target, - group + group, }); ``` @@ -193,19 +193,19 @@ To view metrics for a specific group you can use methods on class `Group`: ```text const group = new Group(this, "Group", { - groupName: "MyGroup" + groupName: "MyGroup", }); new Alarm(this, 'MyGroupErrorAlarm', { metric: group.metricAllErrors(), - threshold: 0 + threshold: 0, }); // Or use default group const defaultGroup = Group.fromDefaultGroup(this, "DefaultGroup"); new Alarm(this, 'DefaultGroupErrorAlarm', { metric: defaultGroup.metricAllErrors(), - threshold: 0 + threshold: 0, }); ``` diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts index d117196a488fc..3d8bf5f9e6672 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/private/schedule.ts @@ -50,7 +50,7 @@ export interface ScheduleProps { * An EventBridge Schedule */ export class Schedule extends Resource implements ISchedule { - group?: IGroup; + public readonly group?: IGroup; constructor(scope: Construct, id: string, props: ScheduleProps) { super(scope, id); From 76d2c2595007f32dc9ae82b1a4727704341a31e5 Mon Sep 17 00:00:00 2001 From: Filipp Fediakov Date: Fri, 21 Jul 2023 21:57:47 +0000 Subject: [PATCH 7/7] Update readme examples to TS --- packages/@aws-cdk/aws-scheduler-alpha/README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-scheduler-alpha/README.md b/packages/@aws-cdk/aws-scheduler-alpha/README.md index 45552ff3d906a..0ca6cddd19a7f 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/README.md +++ b/packages/@aws-cdk/aws-scheduler-alpha/README.md @@ -191,21 +191,23 @@ TODO: Not yet implemented. See section in [L2 Event Bridge Scheduler RFC](https: To view metrics for a specific group you can use methods on class `Group`: -```text +```ts const group = new Group(this, "Group", { groupName: "MyGroup", }); -new Alarm(this, 'MyGroupErrorAlarm', { - metric: group.metricAllErrors(), - threshold: 0, +new cloudwatch.Alarm(this, 'MyGroupErrorAlarm', { + metric: group.metricTargetErrors(), + evaluationPeriods: 1, + threshold: 0 }); // Or use default group const defaultGroup = Group.fromDefaultGroup(this, "DefaultGroup"); -new Alarm(this, 'DefaultGroupErrorAlarm', { - metric: defaultGroup.metricAllErrors(), - threshold: 0, +new cloudwatch.Alarm(this, 'DefaultGroupErrorAlarm', { + metric: defaultGroup.metricTargetErrors(), + evaluationPeriods: 1, + threshold: 0 }); ```