Skip to content

Commit 5e835f3

Browse files
authored
refactor(apprunner): consolidate name validations for App Runner Resources and improve error messages (#32062)
### Issue # (if applicable) N/A ### Reason for this change Consolidate name validations for App Runner Resources and improve error messages. ### Description of changes * Split regex-pattern check and length check to be more user-friendly. * `autoScalingConfigurationName` in `AutoScalingConfiguration` * `observabilityConfigurationName` in `ObservabilityConfigurationName` * `vpcIngressConnectionName` in `VpcIngressConnectionName` * Add name validations. * `serviceName` in `ServiceName`. (Ref: [CFn document](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apprunner-service.html#cfn-apprunner-service-servicename)) * `vpcConnectorName` in `VpcConnectorName`. (Ref: [CFn document](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apprunner-vpcconnector.html#cfn-apprunner-vpcconnector-vpcconnectorname)) ### Description of how you validated changes Modify and add unit 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*
1 parent 49b5afd commit 5e835f3

10 files changed

+262
-31
lines changed

packages/@aws-cdk/aws-apprunner-alpha/lib/auto-scaling-configuration.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class AutoScalingConfiguration extends cdk.Resource implements IAutoScali
117117
const resourceParts = cdk.Fn.split('/', autoScalingConfigurationArn);
118118

119119
if (!resourceParts || resourceParts.length < 3) {
120-
throw new Error(`Unexpected ARN format: ${autoScalingConfigurationArn}`);
120+
throw new Error(`Unexpected ARN format: ${autoScalingConfigurationArn}.`);
121121
}
122122

123123
const autoScalingConfigurationName = cdk.Fn.select(0, resourceParts);
@@ -170,32 +170,39 @@ export class AutoScalingConfiguration extends cdk.Resource implements IAutoScali
170170
}
171171

172172
private validateAutoScalingConfiguration(props: AutoScalingConfigurationProps) {
173-
if (
174-
props.autoScalingConfigurationName !== undefined &&
175-
!cdk.Token.isUnresolved(props.autoScalingConfigurationName) &&
176-
!/^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$/.test(props.autoScalingConfigurationName)
177-
) {
178-
throw new Error(`autoScalingConfigurationName must match the ^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$ pattern, got ${props.autoScalingConfigurationName}`);
173+
if (props.autoScalingConfigurationName !== undefined && !cdk.Token.isUnresolved(props.autoScalingConfigurationName)) {
174+
175+
if (props.autoScalingConfigurationName.length < 4 || props.autoScalingConfigurationName.length > 32) {
176+
throw new Error(
177+
`\`autoScalingConfigurationName\` must be between 4 and 32 characters, got: ${props.autoScalingConfigurationName.length} characters.`,
178+
);
179+
}
180+
181+
if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.autoScalingConfigurationName)) {
182+
throw new Error(
183+
`\`autoScalingConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.autoScalingConfigurationName}.`,
184+
);
185+
}
179186
}
180187

181188
const isMinSizeDefined = typeof props.minSize === 'number';
182189
const isMaxSizeDefined = typeof props.maxSize === 'number';
183190
const isMaxConcurrencyDefined = typeof props.maxConcurrency === 'number';
184191

185192
if (isMinSizeDefined && (props.minSize < 1 || props.minSize > 25)) {
186-
throw new Error(`minSize must be between 1 and 25, got ${props.minSize}`);
193+
throw new Error(`minSize must be between 1 and 25, got ${props.minSize}.`);
187194
}
188195

189196
if (isMaxSizeDefined && (props.maxSize < 1 || props.maxSize > 25)) {
190-
throw new Error(`maxSize must be between 1 and 25, got ${props.maxSize}`);
197+
throw new Error(`maxSize must be between 1 and 25, got ${props.maxSize}.`);
191198
}
192199

193200
if (isMinSizeDefined && isMaxSizeDefined && !(props.minSize < props.maxSize)) {
194-
throw new Error('maxSize must be greater than minSize');
201+
throw new Error('maxSize must be greater than minSize.');
195202
}
196203

197204
if (isMaxConcurrencyDefined && (props.maxConcurrency < 1 || props.maxConcurrency > 200)) {
198-
throw new Error(`maxConcurrency must be between 1 and 200, got ${props.maxConcurrency}`);
205+
throw new Error(`maxConcurrency must be between 1 and 200, got ${props.maxConcurrency}.`);
199206
}
200207
}
201208

packages/@aws-cdk/aws-apprunner-alpha/lib/observability-configuration.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class ObservabilityConfiguration extends cdk.Resource implements IObserva
103103
const resourceParts = cdk.Fn.split('/', observabilityConfigurationArn);
104104

105105
if (!resourceParts || resourceParts.length < 3) {
106-
throw new Error(`Unexpected ARN format: ${observabilityConfigurationArn}`);
106+
throw new Error(`Unexpected ARN format: ${observabilityConfigurationArn}.`);
107107
}
108108

109109
const observabilityConfigurationName = cdk.Fn.select(0, resourceParts);
@@ -141,12 +141,19 @@ export class ObservabilityConfiguration extends cdk.Resource implements IObserva
141141
physicalName: props.observabilityConfigurationName,
142142
});
143143

144-
if (
145-
props.observabilityConfigurationName !== undefined &&
146-
!cdk.Token.isUnresolved(props.observabilityConfigurationName) &&
147-
!/^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$/.test(props.observabilityConfigurationName)
148-
) {
149-
throw new Error(`observabilityConfigurationName must match the \`^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$\` pattern, got ${props.observabilityConfigurationName}`);
144+
if (props.observabilityConfigurationName !== undefined && !cdk.Token.isUnresolved(props.observabilityConfigurationName)) {
145+
146+
if (props.observabilityConfigurationName.length < 4 || props.observabilityConfigurationName.length > 32) {
147+
throw new Error(
148+
`\`observabilityConfigurationName\` must be between 4 and 32 characters, got: ${props.observabilityConfigurationName.length} characters.`,
149+
);
150+
}
151+
152+
if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.observabilityConfigurationName)) {
153+
throw new Error(
154+
`\`observabilityConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.observabilityConfigurationName}.`,
155+
);
156+
}
150157
}
151158

152159
const resource = new CfnObservabilityConfiguration(this, 'Resource', {

packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts

+15
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,21 @@ export class Service extends cdk.Resource implements iam.IGrantable {
12931293
throw new Error('configurationValues cannot be provided if the ConfigurationSource is Repository');
12941294
}
12951295

1296+
if (props.serviceName !== undefined && !cdk.Token.isUnresolved(props.serviceName)) {
1297+
1298+
if (props.serviceName.length < 4 || props.serviceName.length > 40) {
1299+
throw new Error(
1300+
`\`serviceName\` must be between 4 and 40 characters, got: ${props.serviceName.length} characters.`,
1301+
);
1302+
}
1303+
1304+
if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.serviceName)) {
1305+
throw new Error(
1306+
`\`serviceName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.serviceName}.`,
1307+
);
1308+
}
1309+
}
1310+
12961311
const resource = new CfnService(this, 'Resource', {
12971312
serviceName: this.props.serviceName,
12981313
instanceConfiguration: {

packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-connector.ts

+15
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ export class VpcConnector extends cdk.Resource implements IVpcConnector {
136136
physicalName: props.vpcConnectorName,
137137
});
138138

139+
if (props.vpcConnectorName !== undefined && !cdk.Token.isUnresolved(props.vpcConnectorName)) {
140+
141+
if (props.vpcConnectorName.length < 4 || props.vpcConnectorName.length > 40) {
142+
throw new Error(
143+
`\`vpcConnectorName\` must be between 4 and 40 characters, got: ${props.vpcConnectorName.length} characters.`,
144+
);
145+
}
146+
147+
if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.vpcConnectorName)) {
148+
throw new Error(
149+
`\`vpcConnectorName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.vpcConnectorName}.`,
150+
);
151+
}
152+
}
153+
139154
const securityGroups = props.securityGroups?.length ?
140155
props.securityGroups
141156
: [new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc })];

packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-ingress-connection.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,19 @@ export class VpcIngressConnection extends cdk.Resource implements IVpcIngressCon
143143
physicalName: props.vpcIngressConnectionName,
144144
});
145145

146-
if (
147-
props.vpcIngressConnectionName !== undefined &&
148-
!cdk.Token.isUnresolved(props.vpcIngressConnectionName) &&
149-
!/^[A-Za-z0-9][A-Za-z0-9\-_]{3,39}$/.test(props.vpcIngressConnectionName)
150-
) {
151-
throw new Error(`vpcIngressConnectionName must match the \`^[A-Za-z0-9][A-Za-z0-9\-_]{3,39}\` pattern, got ${props.vpcIngressConnectionName}`);
146+
if (props.vpcIngressConnectionName !== undefined && !cdk.Token.isUnresolved(props.vpcIngressConnectionName)) {
147+
148+
if (props.vpcIngressConnectionName.length < 4 || props.vpcIngressConnectionName.length > 40) {
149+
throw new Error(
150+
`\`vpcIngressConnectionName\` must be between 4 and 40 characters, got: ${props.vpcIngressConnectionName.length} characters.`,
151+
);
152+
}
153+
154+
if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.vpcIngressConnectionName)) {
155+
throw new Error(
156+
`\`vpcIngressConnectionName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.vpcIngressConnectionName}.`,
157+
);
158+
}
152159
}
153160

154161
const resource = new CfnVpcIngressConnection(this, 'Resource', {

packages/@aws-cdk/aws-apprunner-alpha/test/auto-scaling-configuration.test.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -63,28 +63,38 @@ test('minSize greater than maxSize', () => {
6363
minSize: 5,
6464
maxSize: 3,
6565
});
66-
}).toThrow('maxSize must be greater than minSize');
66+
}).toThrow('maxSize must be greater than minSize.');
6767
});
6868

6969
test.each([0, 201])('invalid maxConcurrency', (maxConcurrency: number) => {
7070
expect(() => {
7171
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
7272
maxConcurrency,
7373
});
74-
}).toThrow(`maxConcurrency must be between 1 and 200, got ${maxConcurrency}`);
74+
}).toThrow(`maxConcurrency must be between 1 and 200, got ${maxConcurrency}.`);
7575
});
7676

7777
test.each([
7878
['tes'],
7979
['test-autoscaling-configuration-name-over-limitation'],
80+
])('autoScalingConfigurationName length is invalid(name: %s)', (autoScalingConfigurationName: string) => {
81+
expect(() => {
82+
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
83+
autoScalingConfigurationName,
84+
});
85+
}).toThrow(`\`autoScalingConfigurationName\` must be between 4 and 32 characters, got: ${autoScalingConfigurationName.length} characters.`);
86+
});
87+
88+
test.each([
8089
['-test'],
8190
['test-?'],
82-
])('invalid autoScalingConfigurationName (name: %s)', (autoScalingConfigurationName: string) => {
91+
['test-\\'],
92+
])('autoScalingConfigurationName includes invalid characters(name: %s)', (autoScalingConfigurationName: string) => {
8393
expect(() => {
8494
new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', {
8595
autoScalingConfigurationName,
8696
});
87-
}).toThrow(`autoScalingConfigurationName must match the ^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$ pattern, got ${autoScalingConfigurationName}`);
97+
}).toThrow(`\`autoScalingConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${autoScalingConfigurationName}.`);
8898
});
8999

90100
test('create an Auto scaling Configuration with tags', () => {

packages/@aws-cdk/aws-apprunner-alpha/test/obserbability-configuration.test.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,26 @@ test.each([
2929
test.each([
3030
['tes'],
3131
['test-observability-configuration-name-over-limitation'],
32+
])('observabilityConfigurationName length is invalid (name: %s)', (observabilityConfigurationName: string) => {
33+
expect(() => {
34+
new ObservabilityConfiguration(stack, 'ObservabilityConfiguration', {
35+
observabilityConfigurationName,
36+
traceConfigurationVendor: TraceConfigurationVendor.AWSXRAY,
37+
});
38+
}).toThrow(`\`observabilityConfigurationName\` must be between 4 and 32 characters, got: ${observabilityConfigurationName.length} characters.`);
39+
});
40+
41+
test.each([
3242
['-test'],
3343
['test-?'],
34-
])('observabilityConfigurationName over length limitation (name: %s)', (observabilityConfigurationName: string) => {
44+
['test-\\'],
45+
])('observabilityConfigurationName includes invalid characters (name: %s)', (observabilityConfigurationName: string) => {
3546
expect(() => {
3647
new ObservabilityConfiguration(stack, 'ObservabilityConfiguration', {
3748
observabilityConfigurationName,
3849
traceConfigurationVendor: TraceConfigurationVendor.AWSXRAY,
3950
});
40-
}).toThrow(`observabilityConfigurationName must match the \`^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$\` pattern, got ${observabilityConfigurationName}`);
51+
}).toThrow(`\`observabilityConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${observabilityConfigurationName}.`);
4152
});
4253

4354
test('create an Auto scaling Configuration with tags', () => {

packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts

+39
Original file line numberDiff line numberDiff line change
@@ -1781,3 +1781,42 @@ test.each([true, false])('isPubliclyAccessible is set %s', (isPubliclyAccessible
17811781
},
17821782
});
17831783
});
1784+
1785+
test.each([
1786+
['tes'],
1787+
['test-service-name-over-limitation-apprunner'],
1788+
])('serviceName length is invalid (name: %s)', (serviceName: string) => {
1789+
// GIVEN
1790+
const app = new cdk.App();
1791+
const stack = new cdk.Stack(app, 'demo-stack');
1792+
1793+
expect(() => {
1794+
new apprunner.Service(stack, 'DemoService', {
1795+
source: apprunner.Source.fromEcrPublic({
1796+
imageConfiguration: { port: 8000 },
1797+
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
1798+
}),
1799+
serviceName,
1800+
});
1801+
}).toThrow(`\`serviceName\` must be between 4 and 40 characters, got: ${serviceName.length} characters.`);
1802+
});
1803+
1804+
test.each([
1805+
['-test'],
1806+
['test-?'],
1807+
['test-\\'],
1808+
])('serviceName includes invalid characters (name: %s)', (serviceName: string) => {
1809+
// GIVEN
1810+
const app = new cdk.App();
1811+
const stack = new cdk.Stack(app, 'demo-stack');
1812+
1813+
expect(() => {
1814+
new apprunner.Service(stack, 'DemoService', {
1815+
source: apprunner.Source.fromEcrPublic({
1816+
imageConfiguration: { port: 8000 },
1817+
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
1818+
}),
1819+
serviceName,
1820+
});
1821+
}).toThrow(`\`serviceName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${serviceName}.`);
1822+
});

packages/@aws-cdk/aws-apprunner-alpha/test/vpc-connector.test.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,47 @@ test('create a vpcConnector with an empty security group array should create one
148148
},
149149
],
150150
});
151-
});
151+
});
152+
153+
test.each([
154+
['tes'],
155+
['test-vpc-connector-name-over-limitation-apprunner'],
156+
])('vpcConnectorName length is invalid (name: %s)', (vpcConnectorName: string) => {
157+
// GIVEN
158+
const app = new cdk.App();
159+
const stack = new cdk.Stack(app, 'demo-stack');
160+
161+
const vpc = new ec2.Vpc(stack, 'Vpc', {
162+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
163+
});
164+
165+
expect(() => {
166+
new VpcConnector(stack, 'VpcConnector', {
167+
vpc,
168+
vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
169+
vpcConnectorName,
170+
});
171+
}).toThrow(`\`vpcConnectorName\` must be between 4 and 40 characters, got: ${vpcConnectorName.length} characters.`);
172+
});
173+
174+
test.each([
175+
['-test'],
176+
['test-?'],
177+
['test-\\'],
178+
])('vpcConnectorName includes invalid characters (name: %s)', (vpcConnectorName: string) => {
179+
// GIVEN
180+
const app = new cdk.App();
181+
const stack = new cdk.Stack(app, 'demo-stack');
182+
183+
const vpc = new ec2.Vpc(stack, 'Vpc', {
184+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
185+
});
186+
187+
expect(() => {
188+
new VpcConnector(stack, 'VpcConnector', {
189+
vpc,
190+
vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
191+
vpcConnectorName,
192+
});
193+
}).toThrow(`\`vpcConnectorName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${vpcConnectorName}.`);
194+
});

0 commit comments

Comments
 (0)