From e2fc7240672b5c26394b2fe420531d62fe74189b Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:45:56 -0400 Subject: [PATCH] revert: "feat(core): `Schedule` class" (#27284) Reverts aws/aws-cdk#27105 This fixes #27259 by reverting the offending commit. --- allowed-breaking-changes.txt | 35 ++ .../cdk-backup.assets.json | 6 +- .../cdk-backup.template.json | 3 - .../test/integ.backup.js.snapshot/cdk.out | 2 +- .../test/integ.backup.js.snapshot/integ.json | 2 +- .../integ.backup.js.snapshot/manifest.json | 5 +- .../test/integ.backup.js.snapshot/tree.json | 93 +++-- .../aws-cdk-dynamodb.assets.json | 6 +- .../aws-cdk-dynamodb.template.json | 18 +- .../integ.autoscaling.lit.js.snapshot/cdk.out | 2 +- .../integ.json | 2 +- .../manifest.json | 17 +- .../tree.json | 70 ++-- .../aws-lambda-autoscaling.assets.json | 4 +- .../aws-lambda-autoscaling.template.json | 6 +- .../manifest.json | 12 +- .../tree.json | 2 - .../lib/schedule-expression.ts | 62 +++- .../aws-scheduler-alpha/lib/schedule.ts | 1 - .../aws-scheduler-alpha/lib/target.ts | 2 +- .../aws-cdk-scheduler-schedule.assets.json | 19 ++ .../aws-cdk-scheduler-schedule.template.json | 155 +++++++++ .../integ.schedule.js.snapshot/manifest.json | 137 ++++++++ .../test/integ.schedule.js.snapshot/tree.json | 321 ++++++++++++++++++ .../test/schedule-expression.test.ts | 25 +- .../aws-synthetics-alpha/lib/schedule.ts | 45 ++- .../aws-applicationautoscaling/README.md | 6 +- .../lib/scalable-target.ts | 1 - .../lib/schedule.ts | 159 ++++++++- .../test/schedule.test.ts | 15 +- .../aws-cdk-lib/aws-autoscaling/README.md | 2 +- .../aws-autoscaling/lib/schedule.ts | 95 +++++- .../aws-autoscaling/lib/scheduled-action.ts | 8 +- .../test/scheduled-action.test.ts | 36 -- packages/aws-cdk-lib/aws-backup/README.md | 18 +- packages/aws-cdk-lib/aws-backup/lib/index.ts | 1 - packages/aws-cdk-lib/aws-backup/lib/plan.ts | 3 +- packages/aws-cdk-lib/aws-backup/lib/rule.ts | 25 +- .../aws-cdk-lib/aws-backup/lib/schedule.ts | 19 -- .../aws-cdk-lib/aws-backup/test/plan.test.ts | 16 +- .../aws-dynamodb/test/dynamodb.test.ts | 2 +- .../test/ec2/scheduled-ecs-task.test.ts | 2 +- .../fargate/scheduled-fargate-task.test.ts | 2 +- .../test/fargate/fargate-service.test.ts | 2 +- .../aws-cdk-lib/aws-events/lib/schedule.ts | 86 ++++- .../aws-cdk-lib/aws-events/test/rule.test.ts | 2 +- .../aws-cdk-lib/aws-lambda/test/alias.test.ts | 2 +- .../core/lib/helpers-internal/index.ts | 2 +- packages/aws-cdk-lib/core/lib/index.ts | 1 - packages/aws-cdk-lib/core/lib/schedule.ts | 205 ----------- .../aws-cdk-lib/core/test/schedule.test.ts | 84 ----- .../rosetta/aws_backup/default.ts-fixture | 1 + 52 files changed, 1225 insertions(+), 622 deletions(-) create mode 100644 packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json create mode 100644 packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json create mode 100644 packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json delete mode 100644 packages/aws-cdk-lib/aws-backup/lib/schedule.ts delete mode 100644 packages/aws-cdk-lib/core/lib/schedule.ts delete mode 100644 packages/aws-cdk-lib/core/test/schedule.test.ts diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index dba2f0f6dc042..fa46211a9dbef 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -191,3 +191,38 @@ removed:aws-cdk-lib.aws_ec2.CfnNetworkInterfaceProps.enablePrimaryIpv6 # Changed type from GraphqlApi to IGraphqlApi to support import from a different stack, should not matter. weakened:aws-cdk-lib.aws_appsync.SourceApi + +# Due to https://github.com/aws/aws-cdk/issues/27259, none of these could have worked +# When reimplemented, we can remove this entry entirely +removed:aws-cdk-lib.Schedule +removed:aws-cdk-lib.aws_backup.Schedule +removed:aws-cdk-lib.CronOptions +base-types:aws-cdk-lib.aws_applicationautoscaling.Schedule +removed:aws-cdk-lib.aws_applicationautoscaling.Schedule.protectedAt +removed:aws-cdk-lib.aws_applicationautoscaling.Schedule.protectedCron +removed:aws-cdk-lib.aws_applicationautoscaling.Schedule.protectedExpression +removed:aws-cdk-lib.aws_applicationautoscaling.Schedule.protectedRate +removed-argument:aws-cdk-lib.aws_applicationautoscaling.Schedule.at +incompatible-argument:aws-cdk-lib.aws_applicationautoscaling.Schedule.cron +removed-argument:aws-cdk-lib.aws_applicationautoscaling.Schedule.expression +removed:aws-cdk-lib.aws_applicationautoscaling.Schedule.timeZone +base-types:aws-cdk-lib.aws_autoscaling.Schedule +removed:aws-cdk-lib.aws_autoscaling.Schedule.protectedAt +removed:aws-cdk-lib.aws_autoscaling.Schedule.protectedCron +removed:aws-cdk-lib.aws_autoscaling.Schedule.protectedExpression +removed:aws-cdk-lib.aws_autoscaling.Schedule.protectedRate +incompatible-argument:aws-cdk-lib.aws_autoscaling.Schedule.cron +removed-argument:aws-cdk-lib.aws_autoscaling.Schedule.expression +removed:aws-cdk-lib.aws_autoscaling.Schedule.timeZone +base-types:aws-cdk-lib.aws_events.Schedule +removed:aws-cdk-lib.aws_events.Schedule.protectedAt +removed:aws-cdk-lib.aws_events.Schedule.protectedCron +removed:aws-cdk-lib.aws_events.Schedule.protectedExpression +removed:aws-cdk-lib.aws_events.Schedule.protectedRate +removed:aws-cdk-lib.aws_events.Schedule.timeZone +base-types:aws-cdk-lib.aws_applicationautoscaling.CronOptions +removed:aws-cdk-lib.aws_applicationautoscaling.CronOptions.timeZone +base-types:aws-cdk-lib.aws_autoscaling.CronOptions +removed:aws-cdk-lib.aws_autoscaling.CronOptions.timeZone +removed:aws-cdk-lib.aws_autoscaling.CronOptions.year +removed:aws-cdk-lib.aws_backup.BackupPlanRuleProps.schedule diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json index 967f653a106aa..19ddf51ac30c3 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.assets.json @@ -1,7 +1,7 @@ { - "version": "34.0.0", + "version": "32.0.0", "files": { - "7617f116cb014747232d2ebeb42fb3e22242c4b0987526daf7a033aba236d976": { + "0c52c355c71ac95690274d7987110017ff9cd1a1bc79fa4206fda2f55d6b62d5": { "source": { "path": "cdk-backup.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "7617f116cb014747232d2ebeb42fb3e22242c4b0987526daf7a033aba236d976.json", + "objectKey": "0c52c355c71ac95690274d7987110017ff9cd1a1bc79fa4206fda2f55d6b62d5.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json index e1d8a605c90e0..2722b98da789d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk-backup.template.json @@ -83,7 +83,6 @@ }, "RuleName": "Daily", "ScheduleExpression": "cron(0 5 * * ? *)", - "ScheduleExpressionTimezone": "Etc/UTC", "TargetBackupVault": { "Fn::GetAtt": [ "Vault23237E5B", @@ -97,7 +96,6 @@ }, "RuleName": "Weekly", "ScheduleExpression": "cron(0 5 ? * SAT *)", - "ScheduleExpressionTimezone": "Etc/UTC", "TargetBackupVault": { "Fn::GetAtt": [ "Vault23237E5B", @@ -112,7 +110,6 @@ }, "RuleName": "Monthly5Year", "ScheduleExpression": "cron(0 5 1 * ? *)", - "ScheduleExpressionTimezone": "Etc/UTC", "TargetBackupVault": { "Fn::GetAtt": [ "Vault23237E5B", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out index 2313ab5436501..f0b901e7c06e5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"34.0.0"} \ No newline at end of file +{"version":"32.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json index b044eefcaffbb..266124ac58c12 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "34.0.0", + "version": "33.0.0", "testCases": { "integ.backup": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json index 85402b8c54933..aa4d73d1faac8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "34.0.0", + "version": "33.0.0", "artifacts": { "cdk-backup.assets": { "type": "cdk:asset-manifest", @@ -14,11 +14,10 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "cdk-backup.template.json", - "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7617f116cb014747232d2ebeb42fb3e22242c4b0987526daf7a033aba236d976.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0c52c355c71ac95690274d7987110017ff9cd1a1bc79fa4206fda2f55d6b62d5.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json index 9120c3a2895c6..3728b496ca8cf 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-backup/test/integ.backup.js.snapshot/tree.json @@ -37,22 +37,22 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "ScalingRole": { "id": "ScalingRole", "path": "cdk-backup/Table/ScalingRole", "constructInfo": { - "fqn": "aws-cdk-lib.Resource", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_dynamodb.Table", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "FileSystem": { @@ -63,8 +63,8 @@ "aws:cdk:cloudformation:props": {} }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_efs.CfnFileSystem", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "Vault": { @@ -84,14 +84,14 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.CfnBackupVault", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.BackupVault", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "SecondaryVault": { @@ -111,22 +111,22 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.CfnBackupVault", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.BackupVault", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "Env": { "id": "Env", "path": "cdk-backup/Env", "constructInfo": { - "fqn": "aws-cdk-lib.CfnParameter", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "ThirdVault": { @@ -156,14 +156,14 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.CfnBackupVault", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.BackupVault", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "Plan": { @@ -185,7 +185,6 @@ }, "ruleName": "Daily", "scheduleExpression": "cron(0 5 * * ? *)", - "scheduleExpressionTimezone": "Etc/UTC", "targetBackupVault": { "Fn::GetAtt": [ "Vault23237E5B", @@ -199,7 +198,6 @@ }, "ruleName": "Weekly", "scheduleExpression": "cron(0 5 ? * SAT *)", - "scheduleExpressionTimezone": "Etc/UTC", "targetBackupVault": { "Fn::GetAtt": [ "Vault23237E5B", @@ -214,7 +212,6 @@ }, "ruleName": "Monthly5Year", "scheduleExpression": "cron(0 5 1 * ? *)", - "scheduleExpressionTimezone": "Etc/UTC", "targetBackupVault": { "Fn::GetAtt": [ "Vault23237E5B", @@ -253,8 +250,8 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.CfnBackupPlan", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "Selection": { @@ -269,8 +266,8 @@ "id": "ImportRole", "path": "cdk-backup/Plan/Selection/Role/ImportRole", "constructInfo": { - "fqn": "aws-cdk-lib.Resource", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "Resource": { @@ -308,14 +305,14 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_iam.CfnRole", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_iam.Role", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "Resource": { @@ -397,42 +394,42 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.CfnBackupSelection", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.BackupSelection", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_backup.BackupPlan", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "cdk-backup/BootstrapVersion", "constructInfo": { - "fqn": "aws-cdk-lib.CfnParameter", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "CheckBootstrapVersion": { "id": "CheckBootstrapVersion", "path": "cdk-backup/CheckBootstrapVersion", "constructInfo": { - "fqn": "aws-cdk-lib.CfnRule", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.Stack", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } }, "Tree": { @@ -440,13 +437,13 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.2.70" + "version": "10.2.69" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.App", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.2.69" } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.assets.json index 30904fa397fe7..354247134c202 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.assets.json @@ -1,7 +1,7 @@ { - "version": "34.0.0", + "version": "20.0.0", "files": { - "865151155140d0ba599d3522f659db77ada497ea1a763cf11c3fb58d017a3082": { + "619c64c0f19a7d78d759641bfba47f06ebbd7ffb42aae71ec695a6777a534d01": { "source": { "path": "aws-cdk-dynamodb.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "865151155140d0ba599d3522f659db77ada497ea1a763cf11c3fb58d017a3082.json", + "objectKey": "619c64c0f19a7d78d759641bfba47f06ebbd7ffb42aae71ec695a6777a534d01.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.template.json index bd730b9100e98..72046e5747d8d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/aws-cdk-dynamodb.template.json @@ -3,16 +3,16 @@ "TableCD117FA1": { "Type": "AWS::DynamoDB::Table", "Properties": { - "AttributeDefinitions": [ + "KeySchema": [ { "AttributeName": "hashKey", - "AttributeType": "S" + "KeyType": "HASH" } ], - "KeySchema": [ + "AttributeDefinitions": [ { "AttributeName": "hashKey", - "KeyType": "HASH" + "AttributeType": "S" } ], "ProvisionedThroughput": { @@ -56,25 +56,23 @@ ] }, "ScalableDimension": "dynamodb:table:ReadCapacityUnits", + "ServiceNamespace": "dynamodb", "ScheduledActions": [ { "ScalableTargetAction": { "MinCapacity": 20 }, "Schedule": "cron(0 8 * * ? *)", - "ScheduledActionName": "ScaleUpInTheMorning", - "Timezone": "Etc/UTC" + "ScheduledActionName": "ScaleUpInTheMorning" }, { "ScalableTargetAction": { "MaxCapacity": 20 }, "Schedule": "cron(0 20 * * ? *)", - "ScheduledActionName": "ScaleDownAtNight", - "Timezone": "Etc/UTC" + "ScheduledActionName": "ScaleDownAtNight" } - ], - "ServiceNamespace": "dynamodb" + ] } }, "TableReadScalingTargetTracking67DF0596": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/cdk.out index 2313ab5436501..588d7b269d34f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"34.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/integ.json index 81c9e6f902331..8db5a6b10f544 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "34.0.0", + "version": "20.0.0", "testCases": { "integ.autoscaling.lit": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/manifest.json index d4155ced6dcad..ceac2d72f9911 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/manifest.json @@ -1,6 +1,12 @@ { - "version": "34.0.0", + "version": "20.0.0", "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, "aws-cdk-dynamodb.assets": { "type": "cdk:asset-manifest", "properties": { @@ -14,11 +20,10 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "aws-cdk-dynamodb.template.json", - "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/865151155140d0ba599d3522f659db77ada497ea1a763cf11c3fb58d017a3082.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/619c64c0f19a7d78d759641bfba47f06ebbd7ffb42aae71ec695a6777a534d01.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -66,12 +71,6 @@ ] }, "displayName": "aws-cdk-dynamodb" - }, - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/tree.json index 07eb252a889ae..a7da46d30ce88 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-dynamodb/test/integ.autoscaling.lit.js.snapshot/tree.json @@ -4,6 +4,14 @@ "id": "App", "path": "", "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.85" + } + }, "aws-cdk-dynamodb": { "id": "aws-cdk-dynamodb", "path": "aws-cdk-dynamodb", @@ -18,16 +26,16 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::DynamoDB::Table", "aws:cdk:cloudformation:props": { - "attributeDefinitions": [ + "keySchema": [ { "attributeName": "hashKey", - "attributeType": "S" + "keyType": "HASH" } ], - "keySchema": [ + "attributeDefinitions": [ { "attributeName": "hashKey", - "keyType": "HASH" + "attributeType": "S" } ], "provisionedThroughput": { @@ -37,7 +45,7 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", + "fqn": "@aws-cdk/aws-dynamodb.CfnTable", "version": "0.0.0" } }, @@ -45,8 +53,8 @@ "id": "ScalingRole", "path": "aws-cdk-dynamodb/Table/ScalingRole", "constructInfo": { - "fqn": "aws-cdk-lib.Resource", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.85" } }, "ReadScaling": { @@ -93,11 +101,11 @@ ] }, "scalableDimension": "dynamodb:table:ReadCapacityUnits", + "serviceNamespace": "dynamodb", "scheduledActions": [ { "scheduledActionName": "ScaleUpInTheMorning", "schedule": "cron(0 8 * * ? *)", - "timezone": "Etc/UTC", "scalableTargetAction": { "minCapacity": 20 } @@ -105,17 +113,15 @@ { "scheduledActionName": "ScaleDownAtNight", "schedule": "cron(0 20 * * ? *)", - "timezone": "Etc/UTC", "scalableTargetAction": { "maxCapacity": 20 } } - ], - "serviceNamespace": "dynamodb" + ] } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_applicationautoscaling.CfnScalableTarget", + "fqn": "@aws-cdk/aws-applicationautoscaling.CfnScalableTarget", "version": "0.0.0" } }, @@ -143,68 +149,44 @@ } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_applicationautoscaling.CfnScalingPolicy", + "fqn": "@aws-cdk/aws-applicationautoscaling.CfnScalingPolicy", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_applicationautoscaling.TargetTrackingScalingPolicy", + "fqn": "@aws-cdk/aws-applicationautoscaling.TargetTrackingScalingPolicy", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_applicationautoscaling.ScalableTarget", + "fqn": "@aws-cdk/aws-applicationautoscaling.ScalableTarget", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_applicationautoscaling.BaseScalableAttribute", + "fqn": "@aws-cdk/aws-applicationautoscaling.BaseScalableAttribute", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.aws_dynamodb.Table", - "version": "0.0.0" - } - }, - "BootstrapVersion": { - "id": "BootstrapVersion", - "path": "aws-cdk-dynamodb/BootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnParameter", - "version": "0.0.0" - } - }, - "CheckBootstrapVersion": { - "id": "CheckBootstrapVersion", - "path": "aws-cdk-dynamodb/CheckBootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnRule", + "fqn": "@aws-cdk/aws-dynamodb.Table", "version": "0.0.0" } } }, - "constructInfo": { - "fqn": "aws-cdk-lib.Stack", - "version": "0.0.0" - } - }, - "Tree": { - "id": "Tree", - "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.2.70" + "version": "10.1.85" } } }, "constructInfo": { - "fqn": "aws-cdk-lib.App", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.85" } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.assets.json index adda4ff7aab5e..784bf713b5a18 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.assets.json @@ -1,7 +1,7 @@ { "version": "34.0.0", "files": { - "54ec09132268008f855bb0ad4863af3ceda54b0640d90c386503d8faf54eeae8": { + "4e491c14be2b36af5be729715edfad23d2db6e3050125cdd632fce51b0900fb5": { "source": { "path": "aws-lambda-autoscaling.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "54ec09132268008f855bb0ad4863af3ceda54b0640d90c386503d8faf54eeae8.json", + "objectKey": "4e491c14be2b36af5be729715edfad23d2db6e3050125cdd632fce51b0900fb5.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.template.json index 9da5f1d9d06f2..6ac23d677363c 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/aws-lambda-autoscaling.template.json @@ -124,16 +124,14 @@ "MinCapacity": 20 }, "Schedule": "cron(0 8 * * ? *)", - "ScheduledActionName": "ScaleUpInTheMorning", - "Timezone": "Etc/UTC" + "ScheduledActionName": "ScaleUpInTheMorning" }, { "ScalableTargetAction": { "MaxCapacity": 20 }, "Schedule": "cron(0 20 * * ? *)", - "ScheduledActionName": "ScaleDownAtNight", - "Timezone": "Etc/UTC" + "ScheduledActionName": "ScaleDownAtNight" } ], "ServiceNamespace": "lambda" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/manifest.json index 4f657607872f7..20b77f5b33063 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/manifest.json @@ -14,11 +14,10 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "aws-lambda-autoscaling.template.json", - "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/54ec09132268008f855bb0ad4863af3ceda54b0640d90c386503d8faf54eeae8.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4e491c14be2b36af5be729715edfad23d2db6e3050125cdd632fce51b0900fb5.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -87,6 +86,15 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } + ], + "MyLambdaCurrentVersionE7A382CC9ef7d0f7e3b3b55a2ac4da5225352f4d": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLambdaCurrentVersionE7A382CC9ef7d0f7e3b3b55a2ac4da5225352f4d", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-lambda-autoscaling" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/tree.json index 7822036ae32e5..cec884fba04b0 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-lambda/test/integ.autoscaling.lit.js.snapshot/tree.json @@ -219,7 +219,6 @@ { "scheduledActionName": "ScaleUpInTheMorning", "schedule": "cron(0 8 * * ? *)", - "timezone": "Etc/UTC", "scalableTargetAction": { "minCapacity": 20 } @@ -227,7 +226,6 @@ { "scheduledActionName": "ScaleDownAtNight", "schedule": "cron(0 20 * * ? *)", - "timezone": "Etc/UTC", "scalableTargetAction": { "maxCapacity": 20 } diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts index 9fa2c5165a6e9..477eee7b20659 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts @@ -1,4 +1,5 @@ -import { Duration, TimeZone, Schedule, CronOptions } from 'aws-cdk-lib/core'; +import * as events from 'aws-cdk-lib/aws-events'; +import { Duration, TimeZone } from 'aws-cdk-lib/core'; /** * ScheduleExpression for EventBridge Schedule @@ -8,7 +9,7 @@ import { Duration, TimeZone, Schedule, CronOptions } from 'aws-cdk-lib/core'; * * @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html */ -export abstract class ScheduleExpression extends Schedule { +export abstract class ScheduleExpression { /** * Construct a one-time schedule from a date. * @@ -16,7 +17,15 @@ export abstract class ScheduleExpression extends Schedule { * @param timeZone The time zone to use for interpreting the date. Default: - UTC */ public static at(date: Date, timeZone?: TimeZone): ScheduleExpression { - return super.protectedAt(date, timeZone); + try { + const literal = date.toISOString().split('.')[0]; + return new LiteralScheduleExpression(`at(${literal})`, timeZone ?? TimeZone.ETC_UTC); + } catch (e) { + if (e instanceof RangeError) { + throw new Error('Invalid date'); + } + throw e; + } } /** @@ -25,7 +34,7 @@ export abstract class ScheduleExpression extends Schedule { * @param timeZone The time zone to use for interpreting the expression. Default: - UTC */ public static expression(expression: string, timeZone?: TimeZone): ScheduleExpression { - return super.protectedExpression(expression, timeZone); + return new LiteralScheduleExpression(expression, timeZone ?? TimeZone.ETC_UTC); } /** @@ -34,14 +43,53 @@ export abstract class ScheduleExpression extends Schedule { * Rates may be defined with any unit of time, but when converted into minutes, the duration must be a positive whole number of minutes. */ public static rate(duration: Duration): ScheduleExpression { - return super.protectedRate(duration); + const schedule = events.Schedule.rate(duration); + return new LiteralScheduleExpression(schedule.expressionString); } /** * Create a recurring schedule from a set of cron fields and time zone. */ - public static cron(options: CronOptions): ScheduleExpression { - return super.protectedCron(options, 'aws-scheduler'); + public static cron(options: CronOptionsWithTimezone): ScheduleExpression { + const { timeZone, ...cronOptions } = options; + const schedule = events.Schedule.cron(cronOptions); + return new LiteralScheduleExpression(schedule.expressionString, timeZone); } + + /** + * Retrieve the expression for this schedule + */ + public abstract readonly expressionString: string; + + /** + * Retrieve the expression for this schedule + */ + public abstract readonly timeZone?: TimeZone; + + protected constructor() {} } +/** + * Options to configure a cron expression + * + * All fields are strings so you can use complex expressions. Absence of + * a field implies '*' or '?', whichever one is appropriate. + * + * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html#cron-expressions + */ +export interface CronOptionsWithTimezone extends events.CronOptions { + /** + * The timezone to run the schedule in + * + * @default - TimeZone.ETC_UTC + */ + readonly timeZone?: TimeZone; +} + +const DEFAULT_TIMEZONE = TimeZone.ETC_UTC; + +class LiteralScheduleExpression extends ScheduleExpression { + constructor(public readonly expressionString: string, public readonly timeZone: TimeZone = DEFAULT_TIMEZONE) { + super(); + } +} diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts index e349ebb31e2ac..343acc398324f 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts @@ -87,7 +87,6 @@ export class Schedule extends Resource implements ISchedule { this.group = props.group; const targetConfig = props.target.bind(this); - props.schedule._bind(this); const resource = new CfnSchedule(this, 'Resource', { name: this.physicalName, diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/target.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/target.ts index 3850f095ea04e..e15b635f7ae08 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/target.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/target.ts @@ -30,7 +30,7 @@ export interface ScheduleTargetConfig { readonly role: iam.IRole; /** - * What input to pass to the target + * What input to pass to the tatget */ readonly input?: ScheduleTargetInput; diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json new file mode 100644 index 0000000000000..2a26ac17ddebd --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json @@ -0,0 +1,19 @@ +{ + "version": "34.0.0", + "files": { + "8e518d2f0d7b4fcf41623a37d5a30fa0a49f5185d9301205cad6e72cc59b84a8": { + "source": { + "path": "aws-cdk-scheduler-schedule.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "8e518d2f0d7b4fcf41623a37d5a30fa0a49f5185d9301205cad6e72cc59b84a8.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json new file mode 100644 index 0000000000000..59e00cc6402db --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json @@ -0,0 +1,155 @@ +{ + "Resources": { + "FunctionServiceRole675BB04A": { + "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" + ] + ] + } + ] + } + }, + "Function76856677": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "foo" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionServiceRole675BB04A", + "Arn" + ] + }, + "Runtime": "nodejs18.x" + }, + "DependsOn": [ + "FunctionServiceRole675BB04A" + ] + }, + "Role1ABCC5F0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "scheduler.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "DefaultSchedule597B0B2C": { + "Type": "AWS::Scheduler::Schedule", + "Properties": { + "FlexibleTimeWindow": { + "Mode": "OFF" + }, + "ScheduleExpression": "rate(12 hours)", + "ScheduleExpressionTimezone": "Etc/UTC", + "State": "ENABLED", + "Target": { + "Arn": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } + } + } + }, + "DisabledScheduleA1DF7F0F": { + "Type": "AWS::Scheduler::Schedule", + "Properties": { + "FlexibleTimeWindow": { + "Mode": "OFF" + }, + "ScheduleExpression": "rate(12 hours)", + "ScheduleExpressionTimezone": "Etc/UTC", + "State": "DISABLED", + "Target": { + "Arn": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json new file mode 100644 index 0000000000000..0bbde393e24c3 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json @@ -0,0 +1,137 @@ +{ + "version": "34.0.0", + "artifacts": { + "aws-cdk-scheduler-schedule.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-scheduler-schedule.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-scheduler-schedule": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-scheduler-schedule.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8e518d2f0d7b4fcf41623a37d5a30fa0a49f5185d9301205cad6e72cc59b84a8.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-scheduler-schedule.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-scheduler-schedule.assets" + ], + "metadata": { + "/aws-cdk-scheduler-schedule/Function/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "FunctionServiceRole675BB04A" + } + ], + "/aws-cdk-scheduler-schedule/Function/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Function76856677" + } + ], + "/aws-cdk-scheduler-schedule/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Role1ABCC5F0" + } + ], + "/aws-cdk-scheduler-schedule/DefaultSchedule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DefaultSchedule597B0B2C" + } + ], + "/aws-cdk-scheduler-schedule/DisabledSchedule/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DisabledScheduleA1DF7F0F" + } + ], + "/aws-cdk-scheduler-schedule/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-scheduler-schedule/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-scheduler-schedule" + }, + "integtestscheduleDefaultTestDeployAssert24CB3896.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integtestscheduleDefaultTestDeployAssert24CB3896.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integtestscheduleDefaultTestDeployAssert24CB3896": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integtestscheduleDefaultTestDeployAssert24CB3896.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integtestscheduleDefaultTestDeployAssert24CB3896.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integtestscheduleDefaultTestDeployAssert24CB3896.assets" + ], + "metadata": { + "/integtest-schedule/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integtest-schedule/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integtest-schedule/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json new file mode 100644 index 0000000000000..db604f9e73761 --- /dev/null +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json @@ -0,0 +1,321 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-cdk-scheduler-schedule": { + "id": "aws-cdk-scheduler-schedule", + "path": "aws-cdk-scheduler-schedule", + "children": { + "Function": { + "id": "Function", + "path": "aws-cdk-scheduler-schedule/Function", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "aws-cdk-scheduler-schedule/Function/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "aws-cdk-scheduler-schedule/Function/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-scheduler-schedule/Function/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "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" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-scheduler-schedule/Function/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "zipFile": "foo" + }, + "handler": "index.handler", + "role": { + "Fn::GetAtt": [ + "FunctionServiceRole675BB04A", + "Arn" + ] + }, + "runtime": "nodejs18.x" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_lambda.Function", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "aws-cdk-scheduler-schedule/Role", + "children": { + "ImportRole": { + "id": "ImportRole", + "path": "aws-cdk-scheduler-schedule/Role/ImportRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-cdk-scheduler-schedule/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "scheduler.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, + "DefaultSchedule": { + "id": "DefaultSchedule", + "path": "aws-cdk-scheduler-schedule/DefaultSchedule", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-scheduler-schedule/DefaultSchedule/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Scheduler::Schedule", + "aws:cdk:cloudformation:props": { + "flexibleTimeWindow": { + "mode": "OFF" + }, + "scheduleExpression": "rate(12 hours)", + "scheduleExpressionTimezone": "Etc/UTC", + "state": "ENABLED", + "target": { + "arn": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "roleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_scheduler.CfnSchedule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-scheduler-alpha.Schedule", + "version": "0.0.0" + } + }, + "DisabledSchedule": { + "id": "DisabledSchedule", + "path": "aws-cdk-scheduler-schedule/DisabledSchedule", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-scheduler-schedule/DisabledSchedule/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Scheduler::Schedule", + "aws:cdk:cloudformation:props": { + "flexibleTimeWindow": { + "mode": "OFF" + }, + "scheduleExpression": "rate(12 hours)", + "scheduleExpressionTimezone": "Etc/UTC", + "state": "DISABLED", + "target": { + "arn": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "roleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_scheduler.CfnSchedule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-scheduler-alpha.Schedule", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-cdk-scheduler-schedule/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-cdk-scheduler-schedule/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "integtest-schedule": { + "id": "integtest-schedule", + "path": "integtest-schedule", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integtest-schedule/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "integtest-schedule/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "integtest-schedule/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integtest-schedule/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integtest-schedule/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression.test.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression.test.ts index 6f71558c1a704..d12ca608820d3 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression.test.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression.test.ts @@ -28,23 +28,20 @@ describe('schedule expression', () => { }); test('cron expressions saves timezone', () => { - // GIVEN - const cron = ScheduleExpression.cron({ - minute: '0', - hour: '10', - timeZone: TimeZone.EUROPE_LONDON, - }); - cron._bind(new Stack()); - - // THEN - expect(cron.timeZone).toEqual(TimeZone.EUROPE_LONDON); + expect(TimeZone.EUROPE_LONDON).toEqual(ScheduleExpression.cron( + { + minute: '0', + hour: '10', + timeZone: TimeZone.EUROPE_LONDON, + }).timeZone); }); test('cron expressions timezone is UTC if not specified', () => { - expect(ScheduleExpression.cron({ - minute: '0', - hour: '10', - }).timeZone).toEqual(TimeZone.ETC_UTC); + expect(TimeZone.ETC_UTC).toEqual(ScheduleExpression.cron( + { + minute: '0', + hour: '10', + }).timeZone); }); test('rate cannot be 0', () => { diff --git a/packages/@aws-cdk/aws-synthetics-alpha/lib/schedule.ts b/packages/@aws-cdk/aws-synthetics-alpha/lib/schedule.ts index 1d2aa9b88ea67..7be04de89fab3 100644 --- a/packages/@aws-cdk/aws-synthetics-alpha/lib/schedule.ts +++ b/packages/@aws-cdk/aws-synthetics-alpha/lib/schedule.ts @@ -1,10 +1,9 @@ -import { Duration, Schedule as CoreSchedule } from 'aws-cdk-lib/core'; -import { Construct } from 'constructs'; +import { Duration } from 'aws-cdk-lib/core'; /** * Schedule for canary runs */ -export class Schedule extends CoreSchedule { +export class Schedule { /** * The canary will be executed once. @@ -37,27 +36,39 @@ export class Schedule extends CoreSchedule { if (minutes === 0) { return Schedule.once(); } - return super.protectedRate(interval); + if (minutes === 1) { + return new Schedule('rate(1 minute)'); + } + return new Schedule(`rate(${minutes} minutes)`); } /** * Create a schedule from a set of cron fields */ public static cron(options: CronOptions): Schedule { - return super.protectedCron({ - ...options, - year: '*', // '*' is the only allowed value in the year field - }, 'aws-synthetics'); - } + if (options.weekDay !== undefined && options.day !== undefined) { + throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one'); + } + + const minute = fallback(options.minute, '*'); + const hour = fallback(options.hour, '*'); + const month = fallback(options.month, '*'); + + // Weekday defaults to '?' if not supplied. If it is supplied, day must become '?' + const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*'); + const weekDay = fallback(options.weekDay, '?'); - private constructor(public readonly expressionString: string) { - super(); + // '*' is only allowed in the year field + const year = '*'; + + return new Schedule(`cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`); } - /** - * @internal - */ - public _bind(_scope: Construct) {} + private constructor( + /** + * The Schedule expression + */ + public readonly expressionString: string) {} } /** @@ -104,3 +115,7 @@ export interface CronOptions { */ readonly weekDay?: string; } + +function fallback(x: string | undefined, def: string): string { + return x ?? def; +} diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/README.md b/packages/aws-cdk-lib/aws-applicationautoscaling/README.md index c729bd27b704e..6bc05ee9882f2 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/README.md +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/README.md @@ -191,7 +191,7 @@ capacity.scaleOnSchedule('PrescaleInTheMorning', { capacity.scaleOnSchedule('AllowDownscalingAtNight', { schedule: appscaling.Schedule.cron({ hour: '20', minute: '0' }), - minCapacity: 1, + minCapacity: 1 }); ``` @@ -220,12 +220,12 @@ const target = new appscaling.ScalableTarget(this, 'ScalableTarget', { minCapacity: 10, resourceId: `function:${handler.functionName}:${fnVer.version}`, scalableDimension: 'lambda:function:ProvisionedConcurrency', -}); +}) target.scaleToTrackMetric('PceTracking', { targetValue: 0.9, predefinedMetric: appscaling.PredefinedMetric.LAMBDA_PROVISIONED_CONCURRENCY_UTILIZATION, -}); +}) ``` ### ElastiCache Redis shards scaling with target value diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts index 4f4dee7696fc8..0e825bce03d19 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts @@ -155,7 +155,6 @@ export class ScalableTarget extends Resource implements IScalableTarget { this.actions.push({ scheduledActionName: id, schedule: action.schedule.expressionString, - timezone: action.schedule.timeZone?.timezoneName, startTime: action.startTime, endTime: action.endTime, scalableTargetAction: { diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts index 3954b0a81efaf..82c73291993ed 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts @@ -1,38 +1,87 @@ -import { Duration, TimeZone, CronOptions as CoreCronOptions, Schedule as CoreSchedule } from '../../core'; +import { Construct } from 'constructs'; +import { Annotations, Duration } from '../../core'; /** * Schedule for scheduled scaling actions */ -export abstract class Schedule extends CoreSchedule { +export abstract class Schedule { /** * Construct a schedule from a literal schedule expression * * @param expression The expression to use. Must be in a format that Application AutoScaling will recognize */ - public static expression(expression: string, timeZone?: TimeZone): Schedule { - return super.protectedExpression(expression, timeZone); + public static expression(expression: string): Schedule { + return new LiteralSchedule(expression); } /** - * Construct a schedule from an interval and a time unit. Must be a whole number of seconds. + * Construct a schedule from an interval and a time unit */ public static rate(duration: Duration): Schedule { - return super.protectedRate(duration); + if (duration.isUnresolved()) { + const validDurationUnit = ['minute', 'minutes', 'hour', 'hours', 'day', 'days']; + if (!validDurationUnit.includes(duration.unitLabel())) { + throw new Error("Allowed units for scheduling are: 'minute', 'minutes', 'hour', 'hours', 'day' or 'days'"); + } + return new LiteralSchedule(`rate(${duration.formatTokenToNumber()})`); + } + if (duration.toSeconds() === 0) { + throw new Error('Duration cannot be 0'); + } + + let rate = maybeRate(duration.toDays({ integral: false }), 'day'); + if (rate === undefined) { rate = maybeRate(duration.toHours({ integral: false }), 'hour'); } + if (rate === undefined) { rate = makeRate(duration.toMinutes({ integral: true }), 'minute'); } + return new LiteralSchedule(rate); } /** * Construct a Schedule from a moment in time */ - public static at(moment: Date, timeZone?: TimeZone): Schedule { - return super.protectedAt(moment, timeZone); + public static at(moment: Date): Schedule { + return new LiteralSchedule(`at(${formatISO(moment)})`); } /** * Create a schedule from a set of cron fields */ - public static cron(options: CoreCronOptions): Schedule { - return super.protectedCron(options, 'aws-applicationautoscaling'); + public static cron(options: CronOptions): Schedule { + if (options.weekDay !== undefined && options.day !== undefined) { + throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one'); + } + + const minute = fallback(options.minute, '*'); + const hour = fallback(options.hour, '*'); + const month = fallback(options.month, '*'); + const year = fallback(options.year, '*'); + + // Weekday defaults to '?' if not supplied. If it is supplied, day must become '?' + const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*'); + const weekDay = fallback(options.weekDay, '?'); + + return new class extends Schedule { + public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`; + public _bind(scope: Construct) { + if (!options.minute) { + Annotations.of(scope).addWarningV2('@aws-cdk/aws-applicationautoscaling:defaultRunEveryMinute', 'cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.'); + } + return new LiteralSchedule(this.expressionString); + } + }; } + + /** + * Retrieve the expression for this schedule + */ + public abstract readonly expressionString: string; + + protected constructor() {} + + /** + * + * @internal + */ + public abstract _bind(scope: Construct): void; } /** @@ -42,6 +91,92 @@ export abstract class Schedule extends CoreSchedule { * a field implies '*' or '?', whichever one is appropriate. * * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions - * @deprecated use core.CronOptions instead */ -export interface CronOptions extends CoreCronOptions {} +export interface CronOptions { + /** + * The minute to run this rule at + * + * @default - Every minute + */ + readonly minute?: string; + + /** + * The hour to run this rule at + * + * @default - Every hour + */ + readonly hour?: string; + + /** + * The day of the month to run this rule at + * + * @default - Every day of the month + */ + readonly day?: string; + + /** + * The month to run this rule at + * + * @default - Every month + */ + readonly month?: string; + + /** + * The year to run this rule at + * + * @default - Every year + */ + readonly year?: string; + + /** + * The day of the week to run this rule at + * + * @default - Any day of the week + */ + readonly weekDay?: string; +} + +class LiteralSchedule extends Schedule { + constructor(public readonly expressionString: string) { + super(); + } + + public _bind() {} +} + +function fallback(x: T | undefined, def: T): T { + return x === undefined ? def : x; +} + +function formatISO(date?: Date) { + if (!date) { return undefined; } + + return date.getUTCFullYear() + + '-' + pad(date.getUTCMonth() + 1) + + '-' + pad(date.getUTCDate()) + + 'T' + pad(date.getUTCHours()) + + ':' + pad(date.getUTCMinutes()) + + ':' + pad(date.getUTCSeconds()); + + function pad(num: number) { + if (num < 10) { + return '0' + num; + } + return num; + } +} + +/** + * Return the rate if the rate is whole number + */ +function maybeRate(interval: number, singular: string) { + if (interval === 0 || !Number.isInteger(interval)) { return undefined; } + return makeRate(interval, singular); +} + +/** + * Return 'rate(${interval} ${singular}(s))` for the interval + */ +function makeRate(interval: number, singular: string) { + return interval === 1 ? `rate(1 ${singular})` : `rate(${interval} ${singular}s)`; +} diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/test/schedule.test.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/test/schedule.test.ts index 59e597522f007..86d4d06c8cae6 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/test/schedule.test.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/test/schedule.test.ts @@ -1,17 +1,15 @@ -import { Duration, Stack, Lazy, TimeZone } from '../../core'; +import { Duration, Stack, Lazy } from '../../core'; import * as appscaling from '../lib'; describe('cron', () => { test('test utc cron, hour only', () => { expect(appscaling.Schedule.cron({ hour: '18', minute: '0' }).expressionString).toEqual('cron(0 18 * * ? *)'); + }); test('test utc cron, hour and minute', () => { expect(appscaling.Schedule.cron({ hour: '18', minute: '24' }).expressionString).toEqual('cron(24 18 * * ? *)'); - }); - test('test europe/london cron', () => { - expect(appscaling.Schedule.cron({ hour: '18', minute: '0', timeZone: TimeZone.EUROPE_LONDON }).timeZone?.timezoneName).toEqual('Europe/London'); }); }); @@ -19,24 +17,28 @@ describe('rate', () => { test('rate must be whole number of minutes', () => { expect(() => { appscaling.Schedule.rate(Duration.minutes(0.13456)); - }).toThrow(/0.13456 must be a whole number of minutes/); + }).toThrow(/'0.13456 minutes' cannot be converted into a whole number of seconds/); + }); test('rate can be in seconds', () => { const duration = appscaling.Schedule.rate(Duration.seconds(120)); expect('rate(2 minutes)').toEqual(duration.expressionString); + }); test('rate must not be in seconds when specified as a token', () => { expect(() => { appscaling.Schedule.rate(Duration.seconds(Lazy.number({ produce: () => 5 }))); }).toThrow(/Allowed units for scheduling/); + }); test('rate cannot be 0', () => { expect(() => { appscaling.Schedule.rate(Duration.days(0)); }).toThrow(/Duration cannot be 0/); + }); test('rate can be token', () => { @@ -44,17 +46,20 @@ describe('rate', () => { const lazyDuration = Duration.minutes(Lazy.number({ produce: () => 5 })); const rate = appscaling.Schedule.rate(lazyDuration); expect('rate(5 minutes)').toEqual(stack.resolve(rate).expressionString); + }); test('rate can be in allowed type hours', () => { expect('rate(1 hour)').toEqual(appscaling.Schedule.rate(Duration.hours(1)) .expressionString); + }); }); describe('expression', () => { test('test using a literal schedule expression', () => { expect(appscaling.Schedule.expression('cron(0 18 * * ? *)').expressionString).toEqual('cron(0 18 * * ? *)'); + }); }); diff --git a/packages/aws-cdk-lib/aws-autoscaling/README.md b/packages/aws-cdk-lib/aws-autoscaling/README.md index 4dacd47f1baa2..4fbbafe1a6bc6 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/README.md +++ b/packages/aws-cdk-lib/aws-autoscaling/README.md @@ -284,7 +284,7 @@ autoScalingGroup.scaleOnSchedule('PrescaleInTheMorning', { autoScalingGroup.scaleOnSchedule('AllowDownscalingAtNight', { schedule: autoscaling.Schedule.cron({ hour: '20', minute: '0' }), - minCapacity: 1, + minCapacity: 1 }); ``` diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts index 79cc02545b5d9..7f532e65ac62a 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts @@ -1,44 +1,57 @@ import { Construct } from 'constructs'; -import { Annotations, CronOptions as CoreCronOptions, Schedule as CoreSchedule, TimeZone } from '../../core'; +import { Annotations } from '../../core'; /** * Schedule for scheduled scaling actions */ -export abstract class Schedule extends CoreSchedule { +export abstract class Schedule { /** * Construct a schedule from a literal schedule expression * * @param expression The expression to use. Must be in a format that AutoScaling will recognize * @see http://crontab.org/ */ - public static expression(expression: string, timeZone?: TimeZone): Schedule { - return super.protectedExpression(expression, timeZone); + public static expression(expression: string): Schedule { + return new LiteralSchedule(expression); } /** * Create a schedule from a set of cron fields */ - public static cron(options: CoreCronOptions): Schedule { - const cron = super.protectedCron({ - weekDay: '*', // to override core.Schedule's default - day: '*', // to override core.Schedule's default - ...options, - }); - const cronSplit = cron.expressionString.slice(5).split(' '); // remove "cron(" from start - cronSplit.pop(); // remove year, since autoscaling does not accept it - const autoscalingCron = cronSplit.join(' '); + public static cron(options: CronOptions): Schedule { + if (options.weekDay !== undefined && options.day !== undefined) { + throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one'); + } + + const minute = fallback(options.minute, '*'); + const hour = fallback(options.hour, '*'); + const month = fallback(options.month, '*'); + const day = fallback(options.day, '*'); + const weekDay = fallback(options.weekDay, '*'); return new class extends Schedule { - public readonly expressionString = autoscalingCron; - public readonly timeZone = options.timeZone; + public readonly expressionString: string = `${minute} ${hour} ${day} ${month} ${weekDay}`; public _bind(scope: Construct) { if (!options.minute) { Annotations.of(scope).addWarningV2('@aws-cdk/aws-autoscaling:scheduleDefaultRunsEveryMinute', 'cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.'); } - return Schedule.expression(this.expressionString, this.timeZone); + return new LiteralSchedule(this.expressionString); } }; } + + /** + * Retrieve the expression for this schedule + */ + public abstract readonly expressionString: string; + + protected constructor() {} + + /** + * + * @internal + */ + public abstract _bind(scope: Construct): void; } /** @@ -48,6 +61,52 @@ export abstract class Schedule extends CoreSchedule { * a field implies '*' or '?', whichever one is appropriate. * * @see http://crontab.org/ - * @deprecated use core.CronOptions */ -export interface CronOptions extends CoreCronOptions {} +export interface CronOptions { + /** + * The minute to run this rule at + * + * @default - Every minute + */ + readonly minute?: string; + + /** + * The hour to run this rule at + * + * @default - Every hour + */ + readonly hour?: string; + + /** + * The day of the month to run this rule at + * + * @default - Every day of the month + */ + readonly day?: string; + + /** + * The month to run this rule at + * + * @default - Every month + */ + readonly month?: string; + + /** + * The day of the week to run this rule at + * + * @default - Any day of the week + */ + readonly weekDay?: string; +} + +class LiteralSchedule extends Schedule { + constructor(public readonly expressionString: string) { + super(); + } + + public _bind(): void {} +} + +function fallback(x: T | undefined, def: T): T { + return x === undefined ? def : x; +} diff --git a/packages/aws-cdk-lib/aws-autoscaling/lib/scheduled-action.ts b/packages/aws-cdk-lib/aws-autoscaling/lib/scheduled-action.ts index 468b4027dcd20..84b2d55f63b1d 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/lib/scheduled-action.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/lib/scheduled-action.ts @@ -16,11 +16,9 @@ export interface BasicScheduledActionProps { * For more information, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. * * @default - UTC - * @deprecated use time zones as part of the schedule prop * */ readonly timeZone?: string; - /** * When to perform this action. * @@ -106,10 +104,6 @@ export class ScheduledAction extends Resource { throw new Error('At least one of minCapacity, maxCapacity, or desiredCapacity is required'); } - if (props.timeZone && props.schedule.timeZone) { - throw new Error('Cannot set `timeZone` property and `schedule` property with time zone defined. Please remove the deprecated `timeZone` property.'); - } - // add a warning on synth when minute is not defined in a cron schedule props.schedule._bind(this); @@ -121,7 +115,7 @@ export class ScheduledAction extends Resource { maxSize: props.maxCapacity, desiredCapacity: props.desiredCapacity, recurrence: props.schedule.expressionString, - timeZone: props.schedule.timeZone?.timezoneName ?? props.timeZone, + timeZone: props.timeZone, }); this.scheduledActionName = resource.attrScheduledActionName; diff --git a/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts b/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts index 33fb769a47d15..943939c09febb 100644 --- a/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts +++ b/packages/aws-cdk-lib/aws-autoscaling/test/scheduled-action.test.ts @@ -62,42 +62,6 @@ describeDeprecated('scheduled action', () => { }); }); - test('set timezone as part of schedule', () => { - // GIVEN - const stack = new cdk.Stack(); - const asg = makeAutoScalingGroup(stack); - - // WHEN - asg.scaleOnSchedule('ScaleOutAtMiddaySeoul', { - schedule: autoscaling.Schedule.expression('0 12 * * *', cdk.TimeZone.ASIA_SEOUL), - minCapacity: 12, - }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::AutoScaling::ScheduledAction', { - MinSize: 12, - Recurrence: '0 12 * * *', - TimeZone: 'Asia/Seoul', - }); - }); - - test('throws when timezone and scheduled timezone set together', () => { - // GIVEN - const stack = new cdk.Stack(); - const asg = makeAutoScalingGroup(stack); - - // THEN - expect(() => asg.scaleOnSchedule('ScaleOutAtMiddaySeoul', { - schedule: autoscaling.Schedule.cron({ - hour: '12', - minute: '0', - timeZone: cdk.TimeZone.ASIA_SEOUL, - }), - minCapacity: 12, - timeZone: 'Asia/Seoul', - })).toThrowError(/Please remove the deprecated `timeZone` property./); - }); - test('autoscaling group has recommended updatepolicy for scheduled actions', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/aws-cdk-lib/aws-backup/README.md b/packages/aws-cdk-lib/aws-backup/README.md index fe2abaae5608d..27b2fcc3667f1 100644 --- a/packages/aws-cdk-lib/aws-backup/README.md +++ b/packages/aws-cdk-lib/aws-backup/README.md @@ -8,10 +8,10 @@ configure backup policies and monitor backup activity for your AWS resources in ## Backup plan and selection In AWS Backup, a *backup plan* is a policy expression that defines when and how you want to back up -your AWS resources, such as Amazon DynamoDB tables or Amazon Elastic File System (Amazon EFS) file -systems. You can assign resources to backup plans, and AWS Backup automatically backs up and retains -backups for those resources according to the backup plan. You can create multiple backup plans if you -have workloads with different backup requirements. + your AWS resources, such as Amazon DynamoDB tables or Amazon Elastic File System (Amazon EFS) file + systems. You can assign resources to backup plans, and AWS Backup automatically backs up and retains + backups for those resources according to the backup plan. You can create multiple backup plans if you + have workloads with different backup requirements. This module provides ready-made backup plans (similar to the console experience): @@ -52,8 +52,8 @@ plan.addSelection('Selection', { backup.BackupResource.fromRdsServerlessCluster(myServerlessCluster), // An Aurora Serverless cluster backup.BackupResource.fromTag('stage', 'prod'), // All resources that are tagged stage=prod in the region/account backup.BackupResource.fromConstruct(myCoolConstruct), // All backupable resources in `myCoolConstruct` - ], -}); + ] +}) ``` If not specified, a new IAM role with a managed policy for backup will be @@ -66,7 +66,7 @@ declare const plan: backup.BackupPlan; plan.addRule(new backup.BackupPlanRule({ completionWindow: Duration.hours(2), startWindow: Duration.hours(1), - schedule: backup.Schedule.cron({ // Only cron expressions are supported + scheduleExpression: events.Schedule.cron({ // Only cron expressions are supported day: '15', hour: '3', minute: '30', @@ -100,7 +100,7 @@ plan.addRule(new backup.BackupPlanRule({ destinationBackupVault: secondaryVault, moveToColdStorageAfter: Duration.days(30), deleteAfter: Duration.days(120), - }], + }] })); ``` @@ -193,7 +193,7 @@ const vault = new backup.BackupVault(this, 'Vault', { }), ], }), -}); +}) ``` Alternativately statements can be added to the vault policy using `addToAccessPolicy()`. diff --git a/packages/aws-cdk-lib/aws-backup/lib/index.ts b/packages/aws-cdk-lib/aws-backup/lib/index.ts index 2e6094022a9e1..7b34df14d75df 100644 --- a/packages/aws-cdk-lib/aws-backup/lib/index.ts +++ b/packages/aws-cdk-lib/aws-backup/lib/index.ts @@ -3,7 +3,6 @@ export * from './plan'; export * from './rule'; export * from './selection'; export * from './resource'; -export * from './schedule'; // AWS::Backup CloudFormation Resources: export * from './backup.generated'; diff --git a/packages/aws-cdk-lib/aws-backup/lib/plan.ts b/packages/aws-cdk-lib/aws-backup/lib/plan.ts index 96789462488c5..a7ce167ac92ea 100644 --- a/packages/aws-cdk-lib/aws-backup/lib/plan.ts +++ b/packages/aws-cdk-lib/aws-backup/lib/plan.ts @@ -187,8 +187,7 @@ export class BackupPlan extends Resource implements IBackupPlan { moveToColdStorageAfterDays: rule.props.moveToColdStorageAfter?.toDays(), }, ruleName: rule.props.ruleName ?? `${this.node.id}Rule${this.rules.length}`, - scheduleExpression: rule.props.schedule?.expressionString ?? rule.props.scheduleExpression?.expressionString, - scheduleExpressionTimezone: rule.props.schedule?.timeZone?.timezoneName, + scheduleExpression: rule.props.scheduleExpression?.expressionString, startWindowMinutes: rule.props.startWindow?.toMinutes(), enableContinuousBackup: rule.props.enableContinuousBackup, targetBackupVault: vault.backupVaultName, diff --git a/packages/aws-cdk-lib/aws-backup/lib/rule.ts b/packages/aws-cdk-lib/aws-backup/lib/rule.ts index 30591c29735c3..566f1edbde1ac 100644 --- a/packages/aws-cdk-lib/aws-backup/lib/rule.ts +++ b/packages/aws-cdk-lib/aws-backup/lib/rule.ts @@ -1,4 +1,3 @@ -import { Schedule } from './schedule'; import { IBackupVault } from './vault'; import * as events from '../../aws-events'; import { Duration, Token } from '../../core'; @@ -42,17 +41,9 @@ export interface BackupPlanRuleProps { * A CRON expression specifying when AWS Backup initiates a backup job. * * @default - no schedule - * @deprecated use schedule prop instead */ readonly scheduleExpression?: events.Schedule; - /** - * A CRON expression specifying when AWS Backup initiates a backup job. - * - * @default - no schedule - */ - readonly schedule?: Schedule; - /** * The duration after a backup is scheduled before a job is canceled if it doesn't start successfully. * @@ -131,7 +122,7 @@ export class BackupPlanRule { return new BackupPlanRule({ backupVault, ruleName: 'Daily', - schedule: Schedule.cron({ + scheduleExpression: events.Schedule.cron({ hour: '5', minute: '0', }), @@ -146,7 +137,7 @@ export class BackupPlanRule { return new BackupPlanRule({ backupVault, ruleName: 'Weekly', - schedule: Schedule.cron({ + scheduleExpression: events.Schedule.cron({ hour: '5', minute: '0', weekDay: 'SAT', @@ -162,7 +153,7 @@ export class BackupPlanRule { return new BackupPlanRule({ backupVault, ruleName: 'Monthly1Year', - schedule: Schedule.cron({ + scheduleExpression: events.Schedule.cron({ day: '1', hour: '5', minute: '0', @@ -179,7 +170,7 @@ export class BackupPlanRule { return new BackupPlanRule({ backupVault, ruleName: 'Monthly5Year', - schedule: Schedule.cron({ + scheduleExpression: events.Schedule.cron({ day: '1', hour: '5', minute: '0', @@ -196,7 +187,7 @@ export class BackupPlanRule { return new BackupPlanRule({ backupVault, ruleName: 'Monthly7Year', - schedule: Schedule.cron({ + scheduleExpression: events.Schedule.cron({ day: '1', hour: '5', minute: '0', @@ -209,7 +200,7 @@ export class BackupPlanRule { /** * Properties of BackupPlanRule */ - public readonly props: BackupPlanRuleProps; + public readonly props: BackupPlanRuleProps /** @param props Rule properties */ constructor(props: BackupPlanRuleProps) { @@ -222,10 +213,6 @@ export class BackupPlanRule { throw new Error('`scheduleExpression` must be of type `cron`'); } - if (props.schedule && props.scheduleExpression) { - throw new Error('Cannot specify `schedule` and `scheduleExpression` together. Please use `schedule` only.'); - } - const deleteAfter = (props.enableContinuousBackup && !props.deleteAfter) ? Duration.days(35) : props.deleteAfter; if (props.enableContinuousBackup && props.moveToColdStorageAfter) { diff --git a/packages/aws-cdk-lib/aws-backup/lib/schedule.ts b/packages/aws-cdk-lib/aws-backup/lib/schedule.ts deleted file mode 100644 index 25c38df03b52e..0000000000000 --- a/packages/aws-cdk-lib/aws-backup/lib/schedule.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CronOptions, Schedule as CoreSchedule, TimeZone } from '../../core'; - -export abstract class Schedule extends CoreSchedule { - /** - * Construct a schedule from a literal schedule expression - * - * @param expression The expression to use. Must be in a format that AWS Backup will recognize - */ - public static expression(expression: string, timeZone?: TimeZone): Schedule { - return super.protectedExpression(expression, timeZone); - } - - /** - * Construct a schedule from cron options - */ - public static cron(options: CronOptions): Schedule { - return super.protectedCron(options, 'aws-backup'); - } -} diff --git a/packages/aws-cdk-lib/aws-backup/test/plan.test.ts b/packages/aws-cdk-lib/aws-backup/test/plan.test.ts index 3adb1b5e50c2b..4d9c23700d651 100644 --- a/packages/aws-cdk-lib/aws-backup/test/plan.test.ts +++ b/packages/aws-cdk-lib/aws-backup/test/plan.test.ts @@ -1,8 +1,7 @@ -import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Template } from '../../assertions'; import * as events from '../../aws-events'; import { App, Duration, Stack } from '../../core'; -import { BackupPlan, BackupPlanRule, BackupVault, Schedule } from '../lib'; +import { BackupPlan, BackupPlanRule, BackupVault } from '../lib'; let stack: Stack; beforeEach(() => { @@ -21,7 +20,7 @@ test('create a plan and add rules', () => { new BackupPlanRule({ completionWindow: Duration.hours(2), startWindow: Duration.hours(1), - schedule: Schedule.cron({ + scheduleExpression: events.Schedule.cron({ day: '15', hour: '3', minute: '30', @@ -158,7 +157,7 @@ test('create a plan and add rules - add BackupPlan.AdvancedBackupSettings.Backup new BackupPlanRule({ completionWindow: Duration.hours(2), startWindow: Duration.hours(1), - schedule: Schedule.cron({ + scheduleExpression: events.Schedule.cron({ day: '15', hour: '3', minute: '30', @@ -381,19 +380,12 @@ test('throws when deleteAfter is not greater than moveToColdStorageAfter', () => })).toThrow(/`deleteAfter` must be greater than `moveToColdStorageAfter`/); }); -testDeprecated('throws when scheduleExpression is not of type cron', () => { +test('throws when scheduleExpression is not of type cron', () => { expect(() => new BackupPlanRule({ scheduleExpression: events.Schedule.rate(Duration.hours(5)), })).toThrow(/`scheduleExpression` must be of type `cron`/); }); -testDeprecated('throws when schedule and scheduleExpression are both set', () => { - expect(() => new BackupPlanRule({ - schedule: Schedule.cron({ day: '* ' }), - scheduleExpression: events.Schedule.cron({ day: '?' }), - })).toThrow(/Please use `schedule` only./); -}); - test('synth fails when plan has no rules', () => { // GIVEN const app = new App(); diff --git a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts index a92ccb0473df4..edab404c22f61 100644 --- a/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts @@ -1548,7 +1548,7 @@ test('scheduled scaling shows warning when minute is not defined in cron', () => }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/MyTable/ReadScaling/Target', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-applicationautoscaling:scheduleDefaultRunsEveryMinute]"); + Annotations.fromStack(stack).hasWarning('/Default/MyTable/ReadScaling/Target', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-applicationautoscaling:defaultRunEveryMinute]"); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts index 2d9704e419923..764d99d4216c0 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/scheduled-ecs-task.test.ts @@ -364,7 +364,7 @@ test('Scheduled Ec2 Task shows warning when minute is not defined in cron', () = }); // THEN - Annotations.fromStack(stack).hasWarning('/Default', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-events:scheduleDefaultRunsEveryMinute]"); + Annotations.fromStack(stack).hasWarning('/Default', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-events:scheduleWillRunEveryMinute]"); }); test('Scheduled Ec2 Task shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts index 8e425b9230542..8b318fd8b1167 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/scheduled-fargate-task.test.ts @@ -451,7 +451,7 @@ test('Scheduled Fargate Task shows warning when minute is not defined in cron', }); // THEN - Annotations.fromStack(stack).hasWarning('/Default', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-events:scheduleDefaultRunsEveryMinute]"); + Annotations.fromStack(stack).hasWarning('/Default', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-events:scheduleWillRunEveryMinute]"); }); test('Scheduled Fargate Task shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts index d85edf4e940b3..aec9f09bff889 100644 --- a/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs/test/fargate/fargate-service.test.ts @@ -2493,7 +2493,7 @@ describe('fargate service', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Service/TaskCount/Target', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-applicationautoscaling:scheduleDefaultRunsEveryMinute]"); + Annotations.fromStack(stack).hasWarning('/Default/Service/TaskCount/Target', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-applicationautoscaling:defaultRunEveryMinute]"); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-events/lib/schedule.ts b/packages/aws-cdk-lib/aws-events/lib/schedule.ts index d4d6549e8b611..94b3ecbf7f1e0 100644 --- a/packages/aws-cdk-lib/aws-events/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-events/lib/schedule.ts @@ -1,4 +1,5 @@ -import { Duration, Schedule as CoreSchedule } from '../../core'; +import { Construct } from 'constructs'; +import { Annotations, Duration } from '../../core'; /** * Schedule for scheduled event rules @@ -7,14 +8,14 @@ import { Duration, Schedule as CoreSchedule } from '../../core'; * * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html */ -export abstract class Schedule extends CoreSchedule { +export abstract class Schedule { /** * Construct a schedule from a literal schedule expression * * @param expression The expression to use. Must be in a format that EventBridge will recognize */ public static expression(expression: string): Schedule { - return super.protectedExpression(expression); + return new LiteralSchedule(expression); } /** @@ -23,15 +24,63 @@ export abstract class Schedule extends CoreSchedule { * Rates may be defined with any unit of time, but when converted into minutes, the duration must be a positive whole number of minutes. */ public static rate(duration: Duration): Schedule { - return super.protectedRate(duration); + if (duration.isUnresolved()) { + const validDurationUnit = ['minute', 'minutes', 'hour', 'hours', 'day', 'days']; + if (validDurationUnit.indexOf(duration.unitLabel()) === -1) { + throw new Error("Allowed units for scheduling are: 'minute', 'minutes', 'hour', 'hours', 'day', 'days'"); + } + return new LiteralSchedule(`rate(${duration.formatTokenToNumber()})`); + } + if (duration.toMinutes() === 0) { + throw new Error('Duration cannot be 0'); + } + + let rate = maybeRate(duration.toDays({ integral: false }), 'day'); + if (rate === undefined) { rate = maybeRate(duration.toHours({ integral: false }), 'hour'); } + if (rate === undefined) { rate = makeRate(duration.toMinutes({ integral: true }), 'minute'); } + return new LiteralSchedule(rate); } /** * Create a schedule from a set of cron fields */ public static cron(options: CronOptions): Schedule { - return super.protectedCron(options, 'aws-events'); + if (options.weekDay !== undefined && options.day !== undefined) { + throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one'); + } + + const minute = fallback(options.minute, '*'); + const hour = fallback(options.hour, '*'); + const month = fallback(options.month, '*'); + const year = fallback(options.year, '*'); + + // Weekday defaults to '?' if not supplied. If it is supplied, day must become '?' + const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*'); + const weekDay = fallback(options.weekDay, '?'); + + return new class extends Schedule { + public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`; + public _bind(scope: Construct) { + if (!options.minute) { + Annotations.of(scope).addWarningV2('@aws-cdk/aws-events:scheduleWillRunEveryMinute', 'cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.'); + } + return new LiteralSchedule(this.expressionString); + } + }; } + + /** + * Retrieve the expression for this schedule + */ + public abstract readonly expressionString: string; + + protected constructor() {} + + /** + * + * @internal + */ + public abstract _bind(scope: Construct): void; } /** @@ -85,3 +134,30 @@ export interface CronOptions { */ readonly weekDay?: string; } + +class LiteralSchedule extends Schedule { + constructor(public readonly expressionString: string) { + super(); + } + + public _bind() {} +} + +function fallback(x: T | undefined, def: T): T { + return x ?? def; +} + +/** + * Return the rate if the rate is whole number + */ +function maybeRate(interval: number, singular: string) { + if (interval === 0 || !Number.isInteger(interval)) { return undefined; } + return makeRate(interval, singular); +} + +/** + * Return 'rate(${interval} ${singular}(s))` for the interval + */ +function makeRate(interval: number, singular: string) { + return interval === 1 ? `rate(1 ${singular})` : `rate(${interval} ${singular}s)`; +} diff --git a/packages/aws-cdk-lib/aws-events/test/rule.test.ts b/packages/aws-cdk-lib/aws-events/test/rule.test.ts index f31f7dd5b7ea8..6cca0b11ba804 100644 --- a/packages/aws-cdk-lib/aws-events/test/rule.test.ts +++ b/packages/aws-cdk-lib/aws-events/test/rule.test.ts @@ -38,7 +38,7 @@ describe('rule', () => { }), }); - Annotations.fromStack(stack).hasWarning('/Default/MyRule', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-events:scheduleDefaultRunsEveryMinute]"); + Annotations.fromStack(stack).hasWarning('/Default/MyRule', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-events:scheduleWillRunEveryMinute]"); }); test('rule does not display warning when minute is set to * in cron', () => { diff --git a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts index a5081ea79593a..34ee5d33c52bd 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts @@ -602,7 +602,7 @@ describe('alias', () => { }); // THEN - Annotations.fromStack(stack).hasWarning('/Default/Alias/AliasScaling/Target', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-applicationautoscaling:scheduleDefaultRunsEveryMinute]"); + Annotations.fromStack(stack).hasWarning('/Default/Alias/AliasScaling/Target', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/aws-applicationautoscaling:defaultRunEveryMinute]"); }); test('scheduled scaling shows no warning when minute is * in cron', () => { diff --git a/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts b/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts index 50003af38ab8f..03a39234e8d52 100644 --- a/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts +++ b/packages/aws-cdk-lib/core/lib/helpers-internal/index.ts @@ -3,4 +3,4 @@ export * from './cfn-parse'; export { md5hash } from '../private/md5'; export * from './customize-roles'; export * from './string-specializer'; -export { constructInfoFromConstruct, constructInfoFromStack } from '../private/runtime-info'; +export { constructInfoFromConstruct, constructInfoFromStack } from '../private/runtime-info'; \ No newline at end of file diff --git a/packages/aws-cdk-lib/core/lib/index.ts b/packages/aws-cdk-lib/core/lib/index.ts index b84f63ce14592..b35e89c0e59e4 100644 --- a/packages/aws-cdk-lib/core/lib/index.ts +++ b/packages/aws-cdk-lib/core/lib/index.ts @@ -71,4 +71,3 @@ export * from './validation'; export * from './private/intrinsic'; export * from './names'; export * from './time-zone'; -export * from './schedule'; \ No newline at end of file diff --git a/packages/aws-cdk-lib/core/lib/schedule.ts b/packages/aws-cdk-lib/core/lib/schedule.ts deleted file mode 100644 index 4c30eb3a86a96..0000000000000 --- a/packages/aws-cdk-lib/core/lib/schedule.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { Construct } from 'constructs'; -import { Annotations } from './annotations'; -import { Duration } from './duration'; -import { TimeZone } from './time-zone'; - -/** - * A Core Schedule. This construct is not meant to be used as is or exposed to consumers in other modules. - * It is meant to be extended by other modules that require some sort of schedule implementation. All - * methods in `core.Schedule` are protected, so that construct authors can decide which APIs to expose. - * - * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html - */ -export abstract class Schedule { - /** - * Construct a one-time schedule from a date. - * - * @param date The date and time to use. The millisecond part will be ignored. - * @param timeZone The time zone to use for interpreting the date. Default: - UTC - */ - protected static protectedAt(date: Date, timeZone?: TimeZone): Schedule { - try { - const literal = date.toISOString().split('.')[0]; - return new LiteralSchedule(`at(${literal})`, timeZone ?? DEFAULT_TIMEZONE); - } catch (e) { - if (e instanceof RangeError) { - throw new Error('Invalid date'); - } - throw e; - } - } - - /** - * Construct a schedule from a literal schedule expression - * - * @param expression The expression to use. Must be in a format that EventBridge will recognize - * @param timeZone The time zone, if applicable. This is only valid for 'at' and 'cron' expressions - */ - protected static protectedExpression(expression: string, timeZone?: TimeZone): Schedule { - return new LiteralSchedule(expression, timeZone ?? DEFAULT_TIMEZONE); - } - - /** - * Construct a schedule from an interval and a time unit - * - * Rates may be defined with any unit of time, but when converted into minutes, the duration must be a positive whole number of minutes. - */ - protected static protectedRate(duration: Duration): Schedule { - if (duration.isUnresolved()) { - const validDurationUnit = ['minute', 'minutes', 'hour', 'hours', 'day', 'days']; - if (validDurationUnit.indexOf(duration.unitLabel()) === -1) { - throw new Error("Allowed units for scheduling are: 'minute', 'minutes', 'hour', 'hours', 'day', 'days'"); - } - return new LiteralSchedule(`rate(${duration.formatTokenToNumber()})`); - } - if (duration.toMinutes() === 0) { - throw new Error('Duration cannot be 0'); - } - - let rate = maybeRate(duration.toDays({ integral: false }), 'day'); - if (rate === undefined) { rate = maybeRate(duration.toHours({ integral: false }), 'hour'); } - if (rate === undefined) { rate = makeRate(duration.toMinutes({ integral: true }), 'minute'); } - return new LiteralSchedule(rate); - } - - /** - * Create a schedule from a set of cron fields. - * - * @param module the module calling protectedCron, if you want module-specific warnings (i.e. aws-applicationautoscaling) - */ - protected static protectedCron(options: CronOptions, module?: string): Schedule { - if (options.weekDay !== undefined && options.day !== undefined && - !(options.weekDay === '*' && options.day === '*') // special case for aws-autoscaling - ) { - throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one'); - } - - const minute = fallback(options.minute, '*'); - const hour = fallback(options.hour, '*'); - const month = fallback(options.month, '*'); - const year = fallback(options.year, '*'); - - // Weekday defaults to '?' if not supplied. If it is supplied, day must become '?' - const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*'); - const weekDay = fallback(options.weekDay, '?'); - - return new class extends Schedule { - public readonly expressionString = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`; - public readonly timeZone = options.timeZone ?? DEFAULT_TIMEZONE; - public _bind(scope: Construct) { - if (!options.minute) { - Annotations.of(scope).addWarningV2(`@aws-cdk/${module ?? 'core'}:scheduleDefaultRunsEveryMinute`, 'cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.'); - } - return new LiteralSchedule(this.expressionString, this.timeZone); - } - }; - } - - /** - * Retrieve the expression for this schedule. - */ - public abstract readonly expressionString: string; - - /** - * The timezone of the expression, if applicable. - */ - public readonly timeZone?: TimeZone = undefined; - - protected constructor() {} - - /** - * @internal - */ - public abstract _bind(scope: Construct): void; -} - -/** - * Options to configure a cron expression - * - * All fields are strings so you can use complex expressions. Absence of - * a field implies '*' or '?', whichever one is appropriate. - * - * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html#cron-expressions - */ -export interface CronOptions { - /** - * The minute to run this rule at - * - * @default - Every minute - */ - readonly minute?: string; - - /** - * The hour to run this rule at - * - * @default - Every hour - */ - readonly hour?: string; - - /** - * The day of the month to run this rule at - * - * @default - Every day of the month - */ - readonly day?: string; - - /** - * The month to run this rule at - * - * @default - Every month - */ - readonly month?: string; - - /** - * The year to run this rule at - * - * @default - Every year - */ - readonly year?: string; - - /** - * The day of the week to run this rule at - * - * @default - Any day of the week - */ - readonly weekDay?: string; - - /** - * Retrieve the expression for this schedule - * - * @default TimeZone.ETC_UTC - */ - readonly timeZone?: TimeZone; -} - -const DEFAULT_TIMEZONE = TimeZone.ETC_UTC; - -class LiteralSchedule extends Schedule { - constructor( - public readonly expressionString: string, - public readonly timeZone?: TimeZone, - ) { - super(); - } - - public _bind() {} -} - -function fallback(x: T | undefined, def: T): T { - return x ?? def; -} - -/** - * Return the rate if the rate is whole number - */ -function maybeRate(interval: number, singular: string) { - if (interval === 0 || !Number.isInteger(interval)) { return undefined; } - return makeRate(interval, singular); -} - -/** - * Return 'rate(${interval} ${singular}(s))` for the interval - */ -function makeRate(interval: number, singular: string) { - return interval === 1 ? `rate(1 ${singular})` : `rate(${interval} ${singular}s)`; -} diff --git a/packages/aws-cdk-lib/core/test/schedule.test.ts b/packages/aws-cdk-lib/core/test/schedule.test.ts deleted file mode 100644 index 34723010a9592..0000000000000 --- a/packages/aws-cdk-lib/core/test/schedule.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Annotations } from '../../assertions'; -import { Duration } from '../lib/duration'; -import { CronOptions, Schedule } from '../lib/schedule'; -import { Stack } from '../lib/stack'; -import { TimeZone } from '../lib/time-zone'; - -/** - * Basic example of extending core.Schedule for testing purposes. - */ -abstract class TestSchedule extends Schedule { - public static at(date: Date, timeZone?: TimeZone): Schedule { - return Schedule.protectedAt(date, timeZone); - } - - public static rate(duration: Duration): Schedule { - return Schedule.protectedRate(duration); - } - - public static expression(expression: string, timeZone?: TimeZone): Schedule { - return Schedule.protectedExpression(expression, timeZone); - } - - public static cron(options: CronOptions): Schedule { - return Schedule.protectedCron(options); - } -} - -describe('schedules', () => { - test('at - with default timezone', () => { - const schedule = TestSchedule.at(new Date(Date.UTC(1969, 10, 20, 0, 0, 0))); - expectSchedule(schedule, 'at(1969-11-20T00:00:00)', TimeZone.ETC_UTC); - }); - - test('at - with timezone', () => { - const schedule = TestSchedule.at(new Date(Date.UTC(1969, 10, 20, 0, 0, 0)), TimeZone.ASIA_TOKYO ); - expectSchedule(schedule, 'at(1969-11-20T00:00:00)', TimeZone.ASIA_TOKYO); - }); - - test('rate', () => { - const schedule = TestSchedule.rate(Duration.days(1)); - expectSchedule(schedule, 'rate(1 day)', undefined); - }); - - test('expression - with default timezone', () => { - const schedule = TestSchedule.expression('at(1969-11-20T00:00:00)'); - expectSchedule(schedule, 'at(1969-11-20T00:00:00)', TimeZone.ETC_UTC); - }); - - test('expression - with timezone', () => { - const schedule = TestSchedule.expression('at(1969-11-20T00:00:00)', TimeZone.ASIA_SEOUL); - expectSchedule(schedule, 'at(1969-11-20T00:00:00)', TimeZone.ASIA_SEOUL); - }); - - test('cron - with default timezone', () => { - const schedule = TestSchedule.cron({ - minute: '0/10', - weekDay: 'MON-FRI', - }); - expectSchedule(schedule, 'cron(0/10 * ? * MON-FRI *)', TimeZone.ETC_UTC); - }); - - test('cron - with timezone', () => { - const schedule = TestSchedule.cron({ - minute: '0/10', - weekDay: 'MON-FRI', - timeZone: TimeZone.ANTARCTICA_TROLL, - }); - expectSchedule(schedule, 'cron(0/10 * ? * MON-FRI *)', TimeZone.ANTARCTICA_TROLL); - }); - - test('cron warning when minute not supplied', () => { - const stack = new Stack(); - const schedule = TestSchedule.cron({ - weekDay: 'MON-FRI', - }); - schedule._bind(stack); - Annotations.fromStack(stack).hasWarning('/Default', "cron: If you don't pass 'minute', by default the event runs every minute. Pass 'minute: '*'' if that's what you intend, or 'minute: 0' to run once per hour instead. [ack: @aws-cdk/core:scheduleDefaultRunsEveryMinute]"); - }); -}); - -function expectSchedule(schedule: Schedule, expectedExpression: string, expectedTimeZone?: TimeZone) { - expect(schedule.expressionString).toEqual(expectedExpression); - expect(schedule.timeZone).toEqual(expectedTimeZone); -} diff --git a/packages/aws-cdk-lib/rosetta/aws_backup/default.ts-fixture b/packages/aws-cdk-lib/rosetta/aws_backup/default.ts-fixture index 012bd4f3fdbbd..997a397e1ad70 100644 --- a/packages/aws-cdk-lib/rosetta/aws_backup/default.ts-fixture +++ b/packages/aws-cdk-lib/rosetta/aws_backup/default.ts-fixture @@ -4,6 +4,7 @@ import { Construct } from 'constructs'; import * as backup from 'aws-cdk-lib/aws-backup'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as events from 'aws-cdk-lib/aws-events'; import * as kms from 'aws-cdk-lib/aws-kms'; import * as sns from 'aws-cdk-lib/aws-sns'; import * as rds from 'aws-cdk-lib/aws-rds';