From 4e8c9c4dfdae690d9f6650bbc57bacdb83dec68c Mon Sep 17 00:00:00 2001 From: "k.goto" <24818752+go-to-k@users.noreply.github.com> Date: Wed, 27 Sep 2023 23:28:59 +0900 Subject: [PATCH] feat(apprunner): add HealthCheckConfiguration property in Service (#27029) This PR adds HealthCheckConfiguration property in Service construct. Closes #26972. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-apprunner-alpha/README.md | 22 ++ .../aws-apprunner-alpha/lib/service.ts | 151 ++++++++++++ .../rosetta/default.ts-fixture | 2 +- .../cdk.out | 1 + ...efaultTestDeployAssert4D8483F4.assets.json | 19 ++ ...aultTestDeployAssert4D8483F4.template.json | 36 +++ .../integ-apprunner.assets.json | 19 ++ .../integ-apprunner.template.json | 91 +++++++ .../integ.json | 12 + .../manifest.json | 117 +++++++++ .../tree.json | 198 +++++++++++++++ ...nteg.service-health-check-configuration.ts | 27 +++ .../aws-apprunner-alpha/test/service.test.ts | 229 ++++++++++++++++++ 13 files changed, 923 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.assets.json create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.template.json create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.assets.json create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.template.json create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.ts diff --git a/packages/@aws-cdk/aws-apprunner-alpha/README.md b/packages/@aws-cdk/aws-apprunner-alpha/README.md index 74989c792bfda..2e85bb3730cd5 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/README.md +++ b/packages/@aws-cdk/aws-apprunner-alpha/README.md @@ -215,3 +215,25 @@ const service = new apprunner.Service(stack, 'Service', { service.addSecret('LATER_SECRET', apprunner.Secret.fromSecretsManager(secret, 'field')); ``` + +## HealthCheck + +To configure the health check for the service, use the `healthCheck` attribute. + +You can specify it by static methods `HealthCheck.http` or `HealthCheck.tcp`. + +```ts +new apprunner.Service(this, 'Service', { + source: apprunner.Source.fromEcrPublic({ + imageConfiguration: { port: 8000 }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: Duration.seconds(10), + path: '/', + timeout: Duration.seconds(10), + unhealthyThreshold: 10, + }), +}); +``` diff --git a/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts b/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts index 4312405c6d455..87c508ab8f643 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts @@ -706,6 +706,15 @@ export interface ServiceProps { * @default - no VPC connector, uses the DEFAULT egress type instead */ readonly vpcConnector?: IVpcConnector; + + /** + * Settings for the health check that AWS App Runner performs to monitor the health of a service. + * + * You can specify it by static methods `HealthCheck.http` or `HealthCheck.tcp`. + * + * @default - no health check configuration + */ + readonly healthCheck?: HealthCheck; } /** @@ -848,6 +857,145 @@ export class GitHubConnection { } } +/** + * The health check protocol type + */ +export enum HealthCheckProtocolType { + /** + * HTTP protocol + */ + HTTP = 'HTTP', + + /** + * TCP protocol + */ + TCP = 'TCP', +} + +/** + * Describes the settings for the health check that AWS App Runner performs to monitor the health of a service. + */ +interface HealthCheckCommonOptions { + /** + * The number of consecutive checks that must succeed before App Runner decides that the service is healthy. + * + * @default 1 + */ + readonly healthyThreshold?: number; + + /** + * The time interval, in seconds, between health checks. + * + * @default Duration.seconds(5) + */ + readonly interval?: cdk.Duration; + + /** + * The time, in seconds, to wait for a health check response before deciding it failed. + * + * @default Duration.seconds(2) + */ + readonly timeout?: cdk.Duration; + + /** + * The number of consecutive checks that must fail before App Runner decides that the service is unhealthy. + * + * @default 5 + */ + readonly unhealthyThreshold?: number; +} + +/** + * Properties used to define HTTP Based healthchecks. + */ +export interface HttpHealthCheckOptions extends HealthCheckCommonOptions { + /** + * The URL that health check requests are sent to. + * + * @default / + */ + readonly path?: string; +} + +/** + * Properties used to define TCP Based healthchecks. + */ +export interface TcpHealthCheckOptions extends HealthCheckCommonOptions { } + +/** + * Contains static factory methods for creating health checks for different protocols + */ +export class HealthCheck { + /** + * Construct a HTTP health check + */ + public static http(options: HttpHealthCheckOptions = {}): HealthCheck { + return new HealthCheck( + HealthCheckProtocolType.HTTP, + options.healthyThreshold, + options.interval, + options.timeout, + options.unhealthyThreshold, + options.path, + ); + } + + /** + * Construct a TCP health check + */ + public static tcp(options: TcpHealthCheckOptions = {}): HealthCheck { + return new HealthCheck( + HealthCheckProtocolType.TCP, + options.healthyThreshold, + options.interval, + options.timeout, + options.unhealthyThreshold, + ); + } + + private constructor( + public readonly healthCheckProtocolType: HealthCheckProtocolType, + public readonly healthyThreshold: number = 1, + public readonly interval: cdk.Duration = cdk.Duration.seconds(5), + public readonly timeout: cdk.Duration = cdk.Duration.seconds(2), + public readonly unhealthyThreshold: number = 5, + public readonly path?: string, + ) { + if (this.healthCheckProtocolType === HealthCheckProtocolType.HTTP) { + if (this.path !== undefined && this.path.length === 0) { + throw new Error('path length must be greater than 0'); + } + if (this.path === undefined) { + this.path = '/'; + } + } + + if (this.healthyThreshold < 1 || this.healthyThreshold > 20) { + throw new Error(`healthyThreshold must be between 1 and 20, got ${this.healthyThreshold}`); + } + if (this.unhealthyThreshold < 1 || this.unhealthyThreshold > 20) { + throw new Error(`unhealthyThreshold must be between 1 and 20, got ${this.unhealthyThreshold}`); + } + if (this.interval.toSeconds() < 1 || this.interval.toSeconds() > 20) { + throw new Error(`interval must be between 1 and 20 seconds, got ${this.interval.toSeconds()}`); + } + if (this.timeout.toSeconds() < 1 || this.timeout.toSeconds() > 20) { + throw new Error(`timeout must be between 1 and 20 seconds, got ${this.timeout.toSeconds()}`); + } + } + + public bind(): CfnService.HealthCheckConfigurationProperty { + return { + healthyThreshold: this.healthyThreshold, + interval: this.interval?.toSeconds(), + path: this.path, + protocol: this.healthCheckProtocolType, + timeout: this.timeout?.toSeconds(), + unhealthyThreshold: this.unhealthyThreshold, + }; + } +} + /** * Attributes for the App Runner Service */ @@ -1097,6 +1245,9 @@ export class Service extends cdk.Resource implements iam.IGrantable { vpcConnectorArn: this.props.vpcConnector?.vpcConnectorArn, }, }, + healthCheckConfiguration: this.props.healthCheck ? + this.props.healthCheck.bind() : + undefined, }); // grant required privileges for the role diff --git a/packages/@aws-cdk/aws-apprunner-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-apprunner-alpha/rosetta/default.ts-fixture index 345494f4f19f5..ac2c1342a1bfe 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-apprunner-alpha/rosetta/default.ts-fixture @@ -1,5 +1,5 @@ // Fixture with packages imported, but nothing else -import { Stack, SecretValue } from 'aws-cdk-lib'; +import { Duration, Stack, SecretValue } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as apprunner from '@aws-cdk/aws-apprunner-alpha'; import * as path from 'path'; diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdk.out b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdk.out new file mode 100644 index 0000000000000..2313ab5436501 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"34.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.assets.json b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.assets.json new file mode 100644 index 0000000000000..eff956f039962 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.assets.json @@ -0,0 +1,19 @@ +{ + "version": "34.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.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-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.template.json b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.template.json @@ -0,0 +1,36 @@ +{ + "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-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.assets.json b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.assets.json new file mode 100644 index 0000000000000..7e9e7f013cdf8 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.assets.json @@ -0,0 +1,19 @@ +{ + "version": "34.0.0", + "files": { + "ccc8cf5583d9df7c492bad801474db72b5a57969d2141f2dfd67d96f3b69c9bb": { + "source": { + "path": "integ-apprunner.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "ccc8cf5583d9df7c492bad801474db72b5a57969d2141f2dfd67d96f3b69c9bb.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-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.template.json b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.template.json new file mode 100644 index 0000000000000..1f8681abf6cbd --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ-apprunner.template.json @@ -0,0 +1,91 @@ +{ + "Resources": { + "ServiceInstanceRoleDFA90CEC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "tasks.apprunner.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ServiceDBC79909": { + "Type": "AWS::AppRunner::Service", + "Properties": { + "HealthCheckConfiguration": { + "HealthyThreshold": 5, + "Interval": 10, + "Path": "/", + "Protocol": "HTTP", + "Timeout": 10, + "UnhealthyThreshold": 10 + }, + "InstanceConfiguration": { + "InstanceRoleArn": { + "Fn::GetAtt": [ + "ServiceInstanceRoleDFA90CEC", + "Arn" + ] + } + }, + "NetworkConfiguration": { + "EgressConfiguration": { + "EgressType": "DEFAULT" + } + }, + "SourceConfiguration": { + "AuthenticationConfiguration": {}, + "ImageRepository": { + "ImageConfiguration": { + "Port": "8000" + }, + "ImageIdentifier": "public.ecr.aws/aws-containers/hello-app-runner:latest", + "ImageRepositoryType": "ECR_PUBLIC" + } + } + } + } + }, + "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-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ.json b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ.json new file mode 100644 index 0000000000000..ac8863b334018 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "34.0.0", + "testCases": { + "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest": { + "stacks": [ + "integ-apprunner" + ], + "assertionStack": "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/DeployAssert", + "assertionStackName": "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/manifest.json b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/manifest.json new file mode 100644 index 0000000000000..b94cc1be42345 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/manifest.json @@ -0,0 +1,117 @@ +{ + "version": "34.0.0", + "artifacts": { + "integ-apprunner.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-apprunner.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-apprunner": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-apprunner.template.json", + "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}/ccc8cf5583d9df7c492bad801474db72b5a57969d2141f2dfd67d96f3b69c9bb.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-apprunner.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": [ + "integ-apprunner.assets" + ], + "metadata": { + "/integ-apprunner/Service/InstanceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceInstanceRoleDFA90CEC" + } + ], + "/integ-apprunner/Service/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceDBC79909" + } + ], + "/integ-apprunner/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-apprunner/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-apprunner" + }, + "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.template.json", + "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": [ + "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.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": [ + "cdkintegdashboardandwidgetwithstartandendDefaultTestDeployAssert4D8483F4.assets" + ], + "metadata": { + "/cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/tree.json b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/tree.json new file mode 100644 index 0000000000000..c9315e6865197 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.js.snapshot/tree.json @@ -0,0 +1,198 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "integ-apprunner": { + "id": "integ-apprunner", + "path": "integ-apprunner", + "children": { + "Service": { + "id": "Service", + "path": "integ-apprunner/Service", + "children": { + "InstanceRole": { + "id": "InstanceRole", + "path": "integ-apprunner/Service/InstanceRole", + "children": { + "ImportInstanceRole": { + "id": "ImportInstanceRole", + "path": "integ-apprunner/Service/InstanceRole/ImportInstanceRole", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-apprunner/Service/InstanceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "tasks.apprunner.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-apprunner/Service/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppRunner::Service", + "aws:cdk:cloudformation:props": { + "healthCheckConfiguration": { + "healthyThreshold": 5, + "interval": 10, + "path": "/", + "protocol": "HTTP", + "timeout": 10, + "unhealthyThreshold": 10 + }, + "instanceConfiguration": { + "instanceRoleArn": { + "Fn::GetAtt": [ + "ServiceInstanceRoleDFA90CEC", + "Arn" + ] + } + }, + "networkConfiguration": { + "egressConfiguration": { + "egressType": "DEFAULT" + } + }, + "sourceConfiguration": { + "authenticationConfiguration": {}, + "imageRepository": { + "imageConfiguration": { + "port": "8000" + }, + "imageIdentifier": "public.ecr.aws/aws-containers/hello-app-runner:latest", + "imageRepositoryType": "ECR_PUBLIC" + } + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-apprunner/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-apprunner/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "cdk-integ-dashboard-and-widget-with-start-and-end": { + "id": "cdk-integ-dashboard-and-widget-with-start-and-end", + "path": "cdk-integ-dashboard-and-widget-with-start-and-end", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-integ-dashboard-and-widget-with-start-and-end/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.2.70" + } + } + }, + "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": "constructs.Construct", + "version": "10.2.70" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.ts b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.ts new file mode 100644 index 0000000000000..ef4367a18db27 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/integ.service-health-check-configuration.ts @@ -0,0 +1,27 @@ +import * as cdk from 'aws-cdk-lib'; +import { HealthCheck, Service, Source } from '../lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'integ-apprunner'); + +new Service(stack, 'Service', { + source: Source.fromEcrPublic({ + imageConfiguration: { + port: 8000, + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(10), + path: '/', + timeout: cdk.Duration.seconds(10), + unhealthyThreshold: 10, + }), +}); + +new IntegTest(app, 'cdk-integ-dashboard-and-widget-with-start-and-end', { + testCases: [stack], +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts b/packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts index 8fc2465e4a02b..d73b9cf0d7a16 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts @@ -1350,4 +1350,233 @@ test('addToRolePolicy', () => { { Ref: 'DemoServiceInstanceRoleFCED1725' }, ], }); +}); + +test('Service has healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + // WHEN + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(5), + path: '/', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 5, + }), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::Service', { + HealthCheckConfiguration: { + HealthyThreshold: 5, + Interval: 5, + Path: '/', + Protocol: 'HTTP', + Timeout: 2, + UnhealthyThreshold: 5, + }, + }); +}); + +test('path cannot be empty in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(5), + path: '', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 5, + }), + }); + }).toThrow('path length must be greater than 0'); +}); + +test('healthyThreshold must be greater than or equal to 1 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 0, + interval: cdk.Duration.seconds(5), + path: '/', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 5, + }), + }); + }).toThrow('healthyThreshold must be between 1 and 20, got 0'); +}); + +test('healthyThreshold must be less than or equal to 20 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 21, + interval: cdk.Duration.seconds(5), + path: '/', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 5, + }), + }); + }).toThrow('healthyThreshold must be between 1 and 20, got 21'); +}); + +test('unhealthyThreshold must be greater than or equal to 1 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(5), + path: '/', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 0, + }), + }); + }).toThrow('unhealthyThreshold must be between 1 and 20, got 0'); +}); + +test('unhealthyThreshold must be less than or equal to 20 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(5), + path: '/', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 21, + }), + }); + }).toThrow('unhealthyThreshold must be between 1 and 20, got 21'); +}); + +test('interval must be greater than or equal to 1 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(0), + path: '/', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 5, + }), + }); + }).toThrow('interval must be between 1 and 20 seconds, got 0'); +}); + +test('interval must be less than or equal to 20 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(21), + path: '/', + timeout: cdk.Duration.seconds(2), + unhealthyThreshold: 5, + }), + }); + }).toThrow('interval must be between 1 and 20 seconds, got 21'); +}); + +test('timeout must be greater than or equal to 1 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(5), + path: '/', + timeout: cdk.Duration.seconds(0), + unhealthyThreshold: 5, + }), + }); + }).toThrow('timeout must be between 1 and 20 seconds, got 0'); +}); + +test('timeout must be less than or equal to 20 in healthCheck', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + // WHEN + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + healthCheck: apprunner.HealthCheck.http({ + healthyThreshold: 5, + interval: cdk.Duration.seconds(5), + path: '/', + timeout: cdk.Duration.seconds(21), + unhealthyThreshold: 5, + }), + }); + }).toThrow('timeout must be between 1 and 20 seconds, got 21'); }); \ No newline at end of file