From d911c5878c59498a2d0e14ff536e0f8f9f503bfe Mon Sep 17 00:00:00 2001 From: Pahud Hsieh Date: Wed, 1 Dec 2021 00:39:59 +0800 Subject: [PATCH] fix(apprunner): startCommand and environment are ignored in imageConfiguration (#16939) This PR addresses the following issues 1. custom environment variables and start commands should be allowed for `imageConfiguration` 2. buildCommand, environment and startCommand should be allowed for `codeConfigurationValues` Fixes: #16812 - [x] add tests - [x] implementation ---- *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/lib/service.ts | 46 ++++- .../aws-apprunner/test/service.test.ts | 167 ++++++++++++++++++ 2 files changed, 204 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-apprunner/lib/service.ts b/packages/@aws-cdk/aws-apprunner/lib/service.ts index 32e5151da9cbd..68d2fb0e65702 100644 --- a/packages/@aws-cdk/aws-apprunner/lib/service.ts +++ b/packages/@aws-cdk/aws-apprunner/lib/service.ts @@ -118,6 +118,13 @@ export class Runtime { private constructor(public readonly name: string) { } } +/** + * The environment variable for the service. + */ +interface EnvironmentVariable { + readonly name: string; + readonly value: string; +} /** * Result of binding `Source` into a `Service`. @@ -707,6 +714,10 @@ export class Service extends cdk.Resource { private readonly props: ServiceProps; private accessRole?: iam.IRole; private source: SourceConfig; + /** + * Environment variables for this service + */ + private environment?: { [key: string]: string } = {}; /** * The ARN of the Service. @@ -798,22 +809,39 @@ export class Service extends cdk.Resource { } private renderCodeConfigurationValues(props: CodeConfigurationValues): any { + this.environment = props.environment; return { - ...props, + port: props.port, + buildCommand: props.buildCommand, runtime: props.runtime.name, + runtimeEnvironmentVariables: this.renderEnvironmentVariables(), + startCommand: props.startCommand, }; } private renderImageRepository(): any { const repo = this.source.imageRepository!; - if (repo.imageConfiguration?.port) { - // convert port from type number to string - return Object.assign(repo, { - imageConfiguration: { - port: repo.imageConfiguration.port.toString(), - }, - }); + this.environment = repo.imageConfiguration?.environment; + return Object.assign(repo, { + imageConfiguration: { + port: repo.imageConfiguration?.port?.toString(), + startCommand: repo.imageConfiguration?.startCommand, + runtimeEnvironmentVariables: this.renderEnvironmentVariables(), + }, + }); + } + + private renderEnvironmentVariables(): EnvironmentVariable[] | undefined { + if (this.environment) { + let env: EnvironmentVariable[] = []; + for (const [key, value] of Object.entries(this.environment)) { + if (key.startsWith('AWSAPPRUNNER')) { + throw new Error(`Environment variable key ${key} with a prefix of AWSAPPRUNNER is not allowed`); + } + env.push({ name: key, value: value }); + } + return env; } else { - return repo; + return undefined; } } diff --git a/packages/@aws-cdk/aws-apprunner/test/service.test.ts b/packages/@aws-cdk/aws-apprunner/test/service.test.ts index a36cc97950119..412ff09cd14e9 100644 --- a/packages/@aws-cdk/aws-apprunner/test/service.test.ts +++ b/packages/@aws-cdk/aws-apprunner/test/service.test.ts @@ -1,4 +1,5 @@ import * as path from 'path'; +import '@aws-cdk/assert-internal/jest'; import { Template } from '@aws-cdk/assertions'; import * as ecr from '@aws-cdk/aws-ecr'; import * as ecr_assets from '@aws-cdk/aws-ecr-assets'; @@ -32,6 +33,92 @@ test('create a service with ECR Public(image repository type: ECR_PUBLIC)', () = }); }); +test('custom environment variables and start commands are allowed for imageConfiguration with defined port', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + // WHEN + new Service(stack, 'DemoService', { + source: Source.fromEcrPublic({ + imageConfiguration: { + port: 8000, + environment: { + foo: 'fooval', + bar: 'barval', + }, + startCommand: '/root/start-command.sh', + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + }); + // we should have the service + Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::Service', { + SourceConfiguration: { + AuthenticationConfiguration: {}, + ImageRepository: { + ImageConfiguration: { + Port: '8000', + RuntimeEnvironmentVariables: [ + { + Name: 'foo', + Value: 'fooval', + }, + { + Name: 'bar', + Value: 'barval', + }, + ], + StartCommand: '/root/start-command.sh', + }, + ImageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + ImageRepositoryType: 'ECR_PUBLIC', + }, + }, + }); +}); + +test('custom environment variables and start commands are allowed for imageConfiguration with port undefined', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + // WHEN + new Service(stack, 'DemoService', { + source: Source.fromEcrPublic({ + imageConfiguration: { + environment: { + foo: 'fooval', + bar: 'barval', + }, + startCommand: '/root/start-command.sh', + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + }); + // we should have the service + Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::Service', { + SourceConfiguration: { + AuthenticationConfiguration: {}, + ImageRepository: { + ImageConfiguration: { + RuntimeEnvironmentVariables: [ + { + Name: 'foo', + Value: 'fooval', + }, + { + Name: 'bar', + Value: 'barval', + }, + ], + StartCommand: '/root/start-command.sh', + }, + ImageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + ImageRepositoryType: 'ECR_PUBLIC', + }, + }, + }); +}); + test('create a service from existing ECR repository(image repository type: ECR)', () => { // GIVEN const app = new cdk.App(); @@ -249,6 +336,66 @@ test('create a service with github repository - undefined branch name is allowed }); }); +test('create a service with github repository - buildCommand, environment and startCommand are allowed', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + // WHEN + new Service(stack, 'DemoService', { + source: Source.fromGitHub({ + repositoryUrl: 'https://github.com/aws-containers/hello-app-runner', + configurationSource: ConfigurationSourceType.API, + codeConfigurationValues: { + runtime: Runtime.PYTHON_3, + port: '8000', + buildCommand: '/root/build.sh', + environment: { + foo: 'fooval', + bar: 'barval', + }, + startCommand: '/root/start.sh', + }, + connection: GitHubConnection.fromConnectionArn('MOCK'), + }), + }); + + // THEN + // we should have the service with the branch value as 'main' + Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::Service', { + SourceConfiguration: { + AuthenticationConfiguration: { + ConnectionArn: 'MOCK', + }, + CodeRepository: { + CodeConfiguration: { + CodeConfigurationValues: { + Port: '8000', + Runtime: 'PYTHON_3', + BuildCommand: '/root/build.sh', + RuntimeEnvironmentVariables: [ + { + Name: 'foo', + Value: 'fooval', + }, + { + Name: 'bar', + Value: 'barval', + }, + ], + StartCommand: '/root/start.sh', + }, + ConfigurationSource: 'API', + }, + RepositoryUrl: 'https://github.com/aws-containers/hello-app-runner', + SourceCodeVersion: { + Type: 'BRANCH', + Value: 'main', + }, + }, + }, + }); +}); + test('import from service name', () => { // GIVEN @@ -417,3 +564,23 @@ test('custom cpu and memory units are allowed', () => { }, }); }); + +test('environment variable with a prefix of AWSAPPRUNNER should throw an error', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + // WHEN + // we should have the service + expect(() => { + new Service(stack, 'DemoService', { + source: Source.fromEcrPublic({ + imageConfiguration: { + environment: { + AWSAPPRUNNER_FOO: 'bar', + }, + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + }); + }).toThrow('Environment variable key AWSAPPRUNNER_FOO with a prefix of AWSAPPRUNNER is not allowed'); +});