From 5e835f35ab0d87280e2a92dc9656586ad03dd772 Mon Sep 17 00:00:00 2001 From: Matsuda Date: Wed, 20 Nov 2024 04:32:50 +0900 Subject: [PATCH] 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* --- .../lib/auto-scaling-configuration.ts | 29 ++++--- .../lib/observability-configuration.ts | 21 +++-- .../aws-apprunner-alpha/lib/service.ts | 15 ++++ .../aws-apprunner-alpha/lib/vpc-connector.ts | 15 ++++ .../lib/vpc-ingress-connection.ts | 19 +++-- .../test/auto-scaling-configuration.test.ts | 18 ++++- .../test/obserbability-configuration.test.ts | 15 +++- .../aws-apprunner-alpha/test/service.test.ts | 39 ++++++++++ .../test/vpc-connector.test.ts | 45 ++++++++++- .../test/vpc-ingress-connection.test.ts | 77 +++++++++++++++++++ 10 files changed, 262 insertions(+), 31 deletions(-) diff --git a/packages/@aws-cdk/aws-apprunner-alpha/lib/auto-scaling-configuration.ts b/packages/@aws-cdk/aws-apprunner-alpha/lib/auto-scaling-configuration.ts index aae6e0ef9069a..16ae92f7e144b 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/lib/auto-scaling-configuration.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/lib/auto-scaling-configuration.ts @@ -117,7 +117,7 @@ export class AutoScalingConfiguration extends cdk.Resource implements IAutoScali const resourceParts = cdk.Fn.split('/', autoScalingConfigurationArn); if (!resourceParts || resourceParts.length < 3) { - throw new Error(`Unexpected ARN format: ${autoScalingConfigurationArn}`); + throw new Error(`Unexpected ARN format: ${autoScalingConfigurationArn}.`); } const autoScalingConfigurationName = cdk.Fn.select(0, resourceParts); @@ -170,12 +170,19 @@ export class AutoScalingConfiguration extends cdk.Resource implements IAutoScali } 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}`); + if (props.autoScalingConfigurationName !== undefined && !cdk.Token.isUnresolved(props.autoScalingConfigurationName)) { + + if (props.autoScalingConfigurationName.length < 4 || props.autoScalingConfigurationName.length > 32) { + throw new Error( + `\`autoScalingConfigurationName\` must be between 4 and 32 characters, got: ${props.autoScalingConfigurationName.length} characters.`, + ); + } + + if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.autoScalingConfigurationName)) { + throw new Error( + `\`autoScalingConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.autoScalingConfigurationName}.`, + ); + } } const isMinSizeDefined = typeof props.minSize === 'number'; @@ -183,19 +190,19 @@ export class AutoScalingConfiguration extends cdk.Resource implements IAutoScali 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}`); + 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}`); + 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'); + 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}`); + throw new Error(`maxConcurrency must be between 1 and 200, got ${props.maxConcurrency}.`); } } diff --git a/packages/@aws-cdk/aws-apprunner-alpha/lib/observability-configuration.ts b/packages/@aws-cdk/aws-apprunner-alpha/lib/observability-configuration.ts index 56be971ec5500..4b7fddee0786b 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/lib/observability-configuration.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/lib/observability-configuration.ts @@ -103,7 +103,7 @@ export class ObservabilityConfiguration extends cdk.Resource implements IObserva const resourceParts = cdk.Fn.split('/', observabilityConfigurationArn); if (!resourceParts || resourceParts.length < 3) { - throw new Error(`Unexpected ARN format: ${observabilityConfigurationArn}`); + throw new Error(`Unexpected ARN format: ${observabilityConfigurationArn}.`); } const observabilityConfigurationName = cdk.Fn.select(0, resourceParts); @@ -141,12 +141,19 @@ export class ObservabilityConfiguration extends cdk.Resource implements IObserva physicalName: props.observabilityConfigurationName, }); - if ( - props.observabilityConfigurationName !== undefined && - !cdk.Token.isUnresolved(props.observabilityConfigurationName) && - !/^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$/.test(props.observabilityConfigurationName) - ) { - throw new Error(`observabilityConfigurationName must match the \`^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$\` pattern, got ${props.observabilityConfigurationName}`); + if (props.observabilityConfigurationName !== undefined && !cdk.Token.isUnresolved(props.observabilityConfigurationName)) { + + if (props.observabilityConfigurationName.length < 4 || props.observabilityConfigurationName.length > 32) { + throw new Error( + `\`observabilityConfigurationName\` must be between 4 and 32 characters, got: ${props.observabilityConfigurationName.length} characters.`, + ); + } + + if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.observabilityConfigurationName)) { + throw new Error( + `\`observabilityConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.observabilityConfigurationName}.`, + ); + } } const resource = new CfnObservabilityConfiguration(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts b/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts index 9bc60b6448f82..5817f9dcac6dc 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts @@ -1293,6 +1293,21 @@ export class Service extends cdk.Resource implements iam.IGrantable { throw new Error('configurationValues cannot be provided if the ConfigurationSource is Repository'); } + if (props.serviceName !== undefined && !cdk.Token.isUnresolved(props.serviceName)) { + + if (props.serviceName.length < 4 || props.serviceName.length > 40) { + throw new Error( + `\`serviceName\` must be between 4 and 40 characters, got: ${props.serviceName.length} characters.`, + ); + } + + if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.serviceName)) { + throw new Error( + `\`serviceName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.serviceName}.`, + ); + } + } + const resource = new CfnService(this, 'Resource', { serviceName: this.props.serviceName, instanceConfiguration: { diff --git a/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-connector.ts b/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-connector.ts index f2f3a7f99fd3a..62232c160760b 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-connector.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-connector.ts @@ -136,6 +136,21 @@ export class VpcConnector extends cdk.Resource implements IVpcConnector { physicalName: props.vpcConnectorName, }); + if (props.vpcConnectorName !== undefined && !cdk.Token.isUnresolved(props.vpcConnectorName)) { + + if (props.vpcConnectorName.length < 4 || props.vpcConnectorName.length > 40) { + throw new Error( + `\`vpcConnectorName\` must be between 4 and 40 characters, got: ${props.vpcConnectorName.length} characters.`, + ); + } + + if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.vpcConnectorName)) { + throw new Error( + `\`vpcConnectorName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.vpcConnectorName}.`, + ); + } + } + const securityGroups = props.securityGroups?.length ? props.securityGroups : [new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc })]; diff --git a/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-ingress-connection.ts b/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-ingress-connection.ts index 1ffeda7b7c7ec..8006ed9d3bec7 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-ingress-connection.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/lib/vpc-ingress-connection.ts @@ -143,12 +143,19 @@ export class VpcIngressConnection extends cdk.Resource implements IVpcIngressCon physicalName: props.vpcIngressConnectionName, }); - if ( - props.vpcIngressConnectionName !== undefined && - !cdk.Token.isUnresolved(props.vpcIngressConnectionName) && - !/^[A-Za-z0-9][A-Za-z0-9\-_]{3,39}$/.test(props.vpcIngressConnectionName) - ) { - throw new Error(`vpcIngressConnectionName must match the \`^[A-Za-z0-9][A-Za-z0-9\-_]{3,39}\` pattern, got ${props.vpcIngressConnectionName}`); + if (props.vpcIngressConnectionName !== undefined && !cdk.Token.isUnresolved(props.vpcIngressConnectionName)) { + + if (props.vpcIngressConnectionName.length < 4 || props.vpcIngressConnectionName.length > 40) { + throw new Error( + `\`vpcIngressConnectionName\` must be between 4 and 40 characters, got: ${props.vpcIngressConnectionName.length} characters.`, + ); + } + + if (!/^[A-Za-z0-9][A-Za-z0-9\-_]*$/.test(props.vpcIngressConnectionName)) { + throw new Error( + `\`vpcIngressConnectionName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${props.vpcIngressConnectionName}.`, + ); + } } const resource = new CfnVpcIngressConnection(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/auto-scaling-configuration.test.ts b/packages/@aws-cdk/aws-apprunner-alpha/test/auto-scaling-configuration.test.ts index c563e4a5c7745..e77ab4822612a 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/test/auto-scaling-configuration.test.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/auto-scaling-configuration.test.ts @@ -63,7 +63,7 @@ test('minSize greater than maxSize', () => { minSize: 5, maxSize: 3, }); - }).toThrow('maxSize must be greater than minSize'); + }).toThrow('maxSize must be greater than minSize.'); }); test.each([0, 201])('invalid maxConcurrency', (maxConcurrency: number) => { @@ -71,20 +71,30 @@ test.each([0, 201])('invalid maxConcurrency', (maxConcurrency: number) => { new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { maxConcurrency, }); - }).toThrow(`maxConcurrency must be between 1 and 200, got ${maxConcurrency}`); + }).toThrow(`maxConcurrency must be between 1 and 200, got ${maxConcurrency}.`); }); test.each([ ['tes'], ['test-autoscaling-configuration-name-over-limitation'], +])('autoScalingConfigurationName length is invalid(name: %s)', (autoScalingConfigurationName: string) => { + expect(() => { + new AutoScalingConfiguration(stack, 'AutoScalingConfiguration', { + autoScalingConfigurationName, + }); + }).toThrow(`\`autoScalingConfigurationName\` must be between 4 and 32 characters, got: ${autoScalingConfigurationName.length} characters.`); +}); + +test.each([ ['-test'], ['test-?'], -])('invalid autoScalingConfigurationName (name: %s)', (autoScalingConfigurationName: string) => { + ['test-\\'], +])('autoScalingConfigurationName includes invalid characters(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}`); + }).toThrow(`\`autoScalingConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${autoScalingConfigurationName}.`); }); test('create an Auto scaling Configuration with tags', () => { diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/obserbability-configuration.test.ts b/packages/@aws-cdk/aws-apprunner-alpha/test/obserbability-configuration.test.ts index d450c9a04f976..50f6d5370da8b 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/test/obserbability-configuration.test.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/obserbability-configuration.test.ts @@ -29,15 +29,26 @@ test.each([ test.each([ ['tes'], ['test-observability-configuration-name-over-limitation'], +])('observabilityConfigurationName length is invalid (name: %s)', (observabilityConfigurationName: string) => { + expect(() => { + new ObservabilityConfiguration(stack, 'ObservabilityConfiguration', { + observabilityConfigurationName, + traceConfigurationVendor: TraceConfigurationVendor.AWSXRAY, + }); + }).toThrow(`\`observabilityConfigurationName\` must be between 4 and 32 characters, got: ${observabilityConfigurationName.length} characters.`); +}); + +test.each([ ['-test'], ['test-?'], -])('observabilityConfigurationName over length limitation (name: %s)', (observabilityConfigurationName: string) => { + ['test-\\'], +])('observabilityConfigurationName includes invalid characters (name: %s)', (observabilityConfigurationName: string) => { expect(() => { new ObservabilityConfiguration(stack, 'ObservabilityConfiguration', { observabilityConfigurationName, traceConfigurationVendor: TraceConfigurationVendor.AWSXRAY, }); - }).toThrow(`observabilityConfigurationName must match the \`^[A-Za-z0-9][A-Za-z0-9\-_]{3,31}$\` pattern, got ${observabilityConfigurationName}`); + }).toThrow(`\`observabilityConfigurationName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${observabilityConfigurationName}.`); }); test('create an Auto scaling Configuration with tags', () => { 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 69683e0f1abaf..dfbfb25392bc7 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/service.test.ts @@ -1781,3 +1781,42 @@ test.each([true, false])('isPubliclyAccessible is set %s', (isPubliclyAccessible }, }); }); + +test.each([ + ['tes'], + ['test-service-name-over-limitation-apprunner'], +])('serviceName length is invalid (name: %s)', (serviceName: string) => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageConfiguration: { port: 8000 }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + serviceName, + }); + }).toThrow(`\`serviceName\` must be between 4 and 40 characters, got: ${serviceName.length} characters.`); +}); + +test.each([ + ['-test'], + ['test-?'], + ['test-\\'], +])('serviceName includes invalid characters (name: %s)', (serviceName: string) => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + expect(() => { + new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageConfiguration: { port: 8000 }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + serviceName, + }); + }).toThrow(`\`serviceName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${serviceName}.`); +}); diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-connector.test.ts b/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-connector.test.ts index 4cc432c975d96..8ad60852f37dd 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-connector.test.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-connector.test.ts @@ -148,4 +148,47 @@ test('create a vpcConnector with an empty security group array should create one }, ], }); -}); \ No newline at end of file +}); + +test.each([ + ['tes'], + ['test-vpc-connector-name-over-limitation-apprunner'], +])('vpcConnectorName length is invalid (name: %s)', (vpcConnectorName: string) => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + const vpc = new ec2.Vpc(stack, 'Vpc', { + ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), + }); + + expect(() => { + new VpcConnector(stack, 'VpcConnector', { + vpc, + vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }), + vpcConnectorName, + }); + }).toThrow(`\`vpcConnectorName\` must be between 4 and 40 characters, got: ${vpcConnectorName.length} characters.`); +}); + +test.each([ + ['-test'], + ['test-?'], + ['test-\\'], +])('vpcConnectorName includes invalid characters (name: %s)', (vpcConnectorName: string) => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + const vpc = new ec2.Vpc(stack, 'Vpc', { + ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), + }); + + expect(() => { + new VpcConnector(stack, 'VpcConnector', { + vpc, + vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }), + vpcConnectorName, + }); + }).toThrow(`\`vpcConnectorName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${vpcConnectorName}.`); +}); diff --git a/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-ingress-connection.test.ts b/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-ingress-connection.test.ts index 14e642239faa6..ca646653744c3 100644 --- a/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-ingress-connection.test.ts +++ b/packages/@aws-cdk/aws-apprunner-alpha/test/vpc-ingress-connection.test.ts @@ -107,3 +107,80 @@ test('create a vpcIngressConnection without a name', () => { }, }); }); + +test.each([ + ['tes'], + ['test-vpc-ingress-connection-name-over-limitation'], +])('vpcIngressConnectionName length is invalid (name: %s)', (vpcIngressConnectionName: string) => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + const vpc = new ec2.Vpc(stack, 'MyVpc', { + ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), + }); + + const interfaceVpcEndpoint = new ec2.InterfaceVpcEndpoint(stack, 'MyVpcEndpoint', { + vpc, + service: ec2.InterfaceVpcEndpointAwsService.APP_RUNNER_REQUESTS, + privateDnsEnabled: false, + }); + + const service = new Service(stack, 'Service', { + source: Source.fromEcrPublic({ + imageConfiguration: { + port: 8000, + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + isPubliclyAccessible: false, + }); + + expect(() => { + new VpcIngressConnection(stack, 'VpcIngressConnection', { + vpc, + interfaceVpcEndpoint, + service, + vpcIngressConnectionName, + }); + }).toThrow(`\`vpcIngressConnectionName\` must be between 4 and 40 characters, got: ${vpcIngressConnectionName.length} characters.`); +}); + +test.each([ + ['-test'], + ['test-?'], + ['test-\\'], +])('vpcIngressConnectionName includes invalid characters (name: %s)', (vpcIngressConnectionName: string) => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + + const vpc = new ec2.Vpc(stack, 'MyVpc', { + ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), + }); + + const interfaceVpcEndpoint = new ec2.InterfaceVpcEndpoint(stack, 'MyVpcEndpoint', { + vpc, + service: ec2.InterfaceVpcEndpointAwsService.APP_RUNNER_REQUESTS, + privateDnsEnabled: false, + }); + + const service = new Service(stack, 'Service', { + source: Source.fromEcrPublic({ + imageConfiguration: { + port: 8000, + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + isPubliclyAccessible: false, + }); + + expect(() => { + new VpcIngressConnection(stack, 'VpcIngressConnection', { + vpc, + interfaceVpcEndpoint, + service, + vpcIngressConnectionName, + }); + }).toThrow(`\`vpcIngressConnectionName\` must start with an alphanumeric character and contain only alphanumeric characters, hyphens, or underscores after that, got: ${vpcIngressConnectionName}.`); +});