-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(apprunner): add AutoScalingConfiguration for AppRunner Service (#…
…30358) ### Issue # (if applicable) Closes #30353 . ### Reason for this change At the moment, L2 Construct does not support a custom auto scaling configuration for the AppRunner Service. ### Description of changes * Add `AutoScalingConfiguration` Class * Add `autoScalingConfiguration` property to the `Service` Class ### Description of how you validated changes Add unit tests and integ tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
Showing
15 changed files
with
1,020 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
202 changes: 202 additions & 0 deletions
202
packages/@aws-cdk/aws-apprunner-alpha/lib/auto-scaling-configuration.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import * as cdk from 'aws-cdk-lib/core'; | ||
import { Construct } from 'constructs'; | ||
import { CfnAutoScalingConfiguration } from 'aws-cdk-lib/aws-apprunner'; | ||
|
||
/** | ||
* Properties of the App Runner Auto Scaling Configuration. | ||
*/ | ||
export interface AutoScalingConfigurationProps { | ||
/** | ||
* The name for the Auto Scaling Configuration. | ||
* | ||
* @default - a name generated by CloudFormation | ||
*/ | ||
readonly autoScalingConfigurationName?: string; | ||
|
||
/** | ||
* The maximum number of concurrent requests that an instance processes. | ||
* If the number of concurrent requests exceeds this limit, App Runner scales the service up. | ||
* | ||
* Must be between 1 and 200. | ||
* | ||
* @default 100 | ||
*/ | ||
readonly maxConcurrency?: number; | ||
|
||
/** | ||
* The maximum number of instances that a service scales up to. | ||
* At most maxSize instances actively serve traffic for your service. | ||
* | ||
* Must be between 1 and 25. | ||
* | ||
* @default 25 | ||
*/ | ||
readonly maxSize?: number; | ||
|
||
/** | ||
* The minimum number of instances that App Runner provisions for a service. | ||
* The service always has at least minSize provisioned instances. | ||
* | ||
* | ||
* Must be between 1 and 25. | ||
* | ||
* @default 1 | ||
*/ | ||
readonly minSize?: number; | ||
} | ||
|
||
/** | ||
* Attributes for the App Runner Auto Scaling Configuration. | ||
*/ | ||
export interface AutoScalingConfigurationAttributes { | ||
/** | ||
* The name of the Auto Scaling Configuration. | ||
*/ | ||
readonly autoScalingConfigurationName: string; | ||
|
||
/** | ||
* The revision of the Auto Scaling Configuration. | ||
*/ | ||
readonly autoScalingConfigurationRevision: number; | ||
} | ||
|
||
/** | ||
* Represents the App Runner Auto Scaling Configuration. | ||
*/ | ||
export interface IAutoScalingConfiguration extends cdk.IResource { | ||
/** | ||
* The ARN of the Auto Scaling Configuration. | ||
* @attribute | ||
*/ | ||
readonly autoScalingConfigurationArn: string; | ||
|
||
/** | ||
* The Name of the Auto Scaling Configuration. | ||
* @attribute | ||
*/ | ||
readonly autoScalingConfigurationName: string; | ||
|
||
/** | ||
* The revision of the Auto Scaling Configuration. | ||
* @attribute | ||
*/ | ||
readonly autoScalingConfigurationRevision: number; | ||
} | ||
|
||
/** | ||
* The App Runner Auto Scaling Configuration. | ||
* | ||
* @resource AWS::AppRunner::AutoScalingConfiguration | ||
*/ | ||
export class AutoScalingConfiguration extends cdk.Resource implements IAutoScalingConfiguration { | ||
/** | ||
* Imports an App Runner Auto Scaling Configuration from attributes | ||
*/ | ||
public static fromAutoScalingConfigurationAttributes(scope: Construct, id: string, | ||
attrs: AutoScalingConfigurationAttributes): IAutoScalingConfiguration { | ||
const autoScalingConfigurationName = attrs.autoScalingConfigurationName; | ||
const autoScalingConfigurationRevision = attrs.autoScalingConfigurationRevision; | ||
|
||
class Import extends cdk.Resource implements IAutoScalingConfiguration { | ||
public readonly autoScalingConfigurationName = autoScalingConfigurationName; | ||
public readonly autoScalingConfigurationRevision = autoScalingConfigurationRevision; | ||
public readonly autoScalingConfigurationArn = cdk.Stack.of(this).formatArn({ | ||
resource: 'autoscalingconfiguration', | ||
service: 'apprunner', | ||
resourceName: `${attrs.autoScalingConfigurationName}/${attrs.autoScalingConfigurationRevision}`, | ||
}); | ||
} | ||
|
||
return new Import(scope, id); | ||
} | ||
|
||
/** | ||
* Imports an App Runner Auto Scaling Configuration from its ARN | ||
*/ | ||
public static fromArn(scope: Construct, id: string, autoScalingConfigurationArn: string): IAutoScalingConfiguration { | ||
const resourceParts = cdk.Fn.split('/', autoScalingConfigurationArn); | ||
|
||
if (!resourceParts || resourceParts.length < 3) { | ||
throw new Error(`Unexpected ARN format: ${autoScalingConfigurationArn}`); | ||
} | ||
|
||
const autoScalingConfigurationName = cdk.Fn.select(0, resourceParts); | ||
const autoScalingConfigurationRevision = Number(cdk.Fn.select(1, resourceParts)); | ||
|
||
class Import extends cdk.Resource implements IAutoScalingConfiguration { | ||
public readonly autoScalingConfigurationName = autoScalingConfigurationName; | ||
public readonly autoScalingConfigurationRevision = autoScalingConfigurationRevision; | ||
public readonly autoScalingConfigurationArn = autoScalingConfigurationArn; | ||
} | ||
|
||
return new Import(scope, id); | ||
} | ||
|
||
/** | ||
* The ARN of the Auto Scaling Configuration. | ||
* @attribute | ||
*/ | ||
readonly autoScalingConfigurationArn: string; | ||
|
||
/** | ||
* The name of the Auto Scaling Configuration. | ||
* @attribute | ||
*/ | ||
readonly autoScalingConfigurationName: string; | ||
|
||
/** | ||
* The revision of the Auto Scaling Configuration. | ||
* @attribute | ||
*/ | ||
readonly autoScalingConfigurationRevision: number; | ||
|
||
public constructor(scope: Construct, id: string, props: AutoScalingConfigurationProps = {}) { | ||
super(scope, id, { | ||
physicalName: props.autoScalingConfigurationName, | ||
}); | ||
|
||
this.validateAutoScalingConfiguration(props); | ||
|
||
const resource = new CfnAutoScalingConfiguration(this, 'Resource', { | ||
autoScalingConfigurationName: props.autoScalingConfigurationName, | ||
maxConcurrency: props.maxConcurrency, | ||
maxSize: props.maxSize, | ||
minSize: props.minSize, | ||
}); | ||
|
||
this.autoScalingConfigurationArn = resource.attrAutoScalingConfigurationArn; | ||
this.autoScalingConfigurationRevision = resource.attrAutoScalingConfigurationRevision; | ||
this.autoScalingConfigurationName = resource.ref; | ||
} | ||
|
||
private validateAutoScalingConfiguration(props: AutoScalingConfigurationProps) { | ||
if ( | ||
props.autoScalingConfigurationName !== undefined && | ||
!cdk.Token.isUnresolved(props.autoScalingConfigurationName) && | ||
!/^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$/.test(props.autoScalingConfigurationName) | ||
) { | ||
throw new Error(`autoScalingConfigurationName must match the ^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$ pattern, got ${props.autoScalingConfigurationName}`); | ||
} | ||
|
||
const isMinSizeDefined = typeof props.minSize === 'number'; | ||
const isMaxSizeDefined = typeof props.maxSize === 'number'; | ||
const isMaxConcurrencyDefined = typeof props.maxConcurrency === 'number'; | ||
|
||
if (isMinSizeDefined && (props.minSize < 1 || props.minSize > 25)) { | ||
throw new Error(`minSize must be between 1 and 25, got ${props.minSize}`); | ||
} | ||
|
||
if (isMaxSizeDefined && (props.maxSize < 1 || props.maxSize > 25)) { | ||
throw new Error(`maxSize must be between 1 and 25, got ${props.maxSize}`); | ||
} | ||
|
||
if (isMinSizeDefined && isMaxSizeDefined && !(props.minSize < props.maxSize)) { | ||
throw new Error('maxSize must be greater than minSize'); | ||
} | ||
|
||
if (isMaxConcurrencyDefined && (props.maxConcurrency < 1 || props.maxConcurrency > 200)) { | ||
throw new Error(`maxConcurrency must be between 1 and 200, got ${props.maxConcurrency}`); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
// AWS::AppRunner CloudFormation Resources: | ||
export * from './auto-scaling-configuration'; | ||
export * from './service'; | ||
export * from './vpc-connector'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
packages/@aws-cdk/aws-apprunner-alpha/test/auto-scaling-configuration.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { Match, Template } from 'aws-cdk-lib/assertions'; | ||
import * as cdk from 'aws-cdk-lib'; | ||
import { AutoScalingConfiguration } from '../lib'; | ||
|
||
let stack: cdk.Stack; | ||
beforeEach(() => { | ||
stack = new cdk.Stack(); | ||
}); | ||
|
||
test.each([ | ||
['MyAutoScalingConfiguration'], | ||
['my-autoscaling-configuration_1'], | ||
])('create an Auto scaling Configuration with all properties (name: %s)', (autoScalingConfigurationName: string) => { | ||
// WHEN | ||
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { | ||
autoScalingConfigurationName, | ||
maxConcurrency: 150, | ||
maxSize: 20, | ||
minSize: 5, | ||
}); | ||
|
||
// THEN | ||
Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::AutoScalingConfiguration', { | ||
AutoScalingConfigurationName: autoScalingConfigurationName, | ||
MaxConcurrency: 150, | ||
MaxSize: 20, | ||
MinSize: 5, | ||
}); | ||
}); | ||
|
||
test('create an Auto scaling Configuration without all properties', () => { | ||
// WHEN | ||
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration'); | ||
|
||
// THEN | ||
Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::AutoScalingConfiguration', { | ||
AutoScalingConfigurationName: Match.absent(), | ||
MaxConcurrency: Match.absent(), | ||
MaxSize: Match.absent(), | ||
MinSize: Match.absent(), | ||
}); | ||
}); | ||
|
||
test.each([-1, 0, 26])('invalid minSize', (minSize: number) => { | ||
expect(() => { | ||
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { | ||
minSize, | ||
}); | ||
}).toThrow(`minSize must be between 1 and 25, got ${minSize}`); | ||
}); | ||
|
||
test.each([0, 26])('invalid maxSize', (maxSize: number) => { | ||
expect(() => { | ||
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { | ||
maxSize, | ||
}); | ||
}).toThrow(`maxSize must be between 1 and 25, got ${maxSize}`); | ||
}); | ||
|
||
test('minSize greater than maxSize', () => { | ||
expect(() => { | ||
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { | ||
minSize: 5, | ||
maxSize: 3, | ||
}); | ||
}).toThrow('maxSize must be greater than minSize'); | ||
}); | ||
|
||
test.each([0, 201])('invalid maxConcurrency', (maxConcurrency: number) => { | ||
expect(() => { | ||
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { | ||
maxConcurrency, | ||
}); | ||
}).toThrow(`maxConcurrency must be between 1 and 200, got ${maxConcurrency}`); | ||
}); | ||
|
||
test.each([ | ||
['tes'], | ||
['test-autoscaling-configuration-name-over-limitation'], | ||
['-test'], | ||
['test-?'], | ||
])('invalid autoScalingConfigurationName (name: %s)', (autoScalingConfigurationName: string) => { | ||
expect(() => { | ||
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { | ||
autoScalingConfigurationName, | ||
}); | ||
}).toThrow(`autoScalingConfigurationName must match the ^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$ pattern, got ${autoScalingConfigurationName}`); | ||
}); | ||
|
||
test('create an Auto scaling Configuration with tags', () => { | ||
// WHEN | ||
const autoScalingConfiguration = new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { | ||
autoScalingConfigurationName: 'my-autoscaling-config', | ||
maxConcurrency: 150, | ||
maxSize: 20, | ||
minSize: 5, | ||
}); | ||
|
||
cdk.Tags.of(autoScalingConfiguration).add('Environment', 'production'); | ||
|
||
// THEN | ||
Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::AutoScalingConfiguration', { | ||
Tags: [ | ||
{ | ||
Key: 'Environment', | ||
Value: 'production', | ||
}, | ||
], | ||
}); | ||
}); |
Oops, something went wrong.