From 45195b6f2e5162eaa795d3a412d89dd09680aa8b Mon Sep 17 00:00:00 2001 From: Roger Chi Date: Fri, 3 Mar 2023 14:14:53 -0500 Subject: [PATCH] fix(apprunner-alpha): env vars and secrets can't solely be added via .add*() methods (#24346) This fixes the logic for rendering environment variables and environment secrets for the `apprunner-alpha` module. Previously, `.addEnvironmentVariable()` and `.addSecret()` were being ignored if there were not already "seed" values in the input props. Closes #24345. ---- *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 | 62 ++-- ...efaultTestDeployAssert07867A67.assets.json | 19 ++ ...aultTestDeployAssert07867A67.template.json | 36 +++ .../cdk.out | 1 + ...prunner-later-secrets-env-vars.assets.json | 19 ++ ...unner-later-secrets-env-vars.template.json | 157 ++++++++++ .../integ.json | 12 + .../manifest.json | 135 ++++++++ .../tree.json | 288 ++++++++++++++++++ .../integ.service-later-secrets-env-vars.ts | 36 +++ .../aws-apprunner/test/service.test.ts | 137 +++++++++ 11 files changed, 865 insertions(+), 37 deletions(-) create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.assets.json create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.template.json create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.assets.json create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.template.json create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.ts diff --git a/packages/@aws-cdk/aws-apprunner/lib/service.ts b/packages/@aws-cdk/aws-apprunner/lib/service.ts index 4ab5553ad0442..7cabd377c2574 100644 --- a/packages/@aws-cdk/aws-apprunner/lib/service.ts +++ b/packages/@aws-cdk/aws-apprunner/lib/service.ts @@ -4,6 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as ssm from '@aws-cdk/aws-ssm'; import * as cdk from '@aws-cdk/core'; +import { Lazy } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnService } from './apprunner.generated'; import { IVpcConnector } from './vpc-connector'; @@ -924,16 +925,6 @@ export class Service extends cdk.Resource { */ readonly environment: { [key: string]: string } = {}; - /** - * Environment variables for this service. - */ - private environmentVariables: { [key: string]: string } = {}; - - /** - * Environment secrets for this service. - */ - private environmentSecrets: { [key: string]: Secret; } = {}; - /** * Environment secrets for this service. */ @@ -981,17 +972,22 @@ export class Service extends cdk.Resource { this.source = source; this.props = props; - this.environmentVariables = this.getEnvironmentVariables(); - this.environmentSecrets = this.getEnvironmentSecrets(); + this.instanceRole = this.props.instanceRole; + + const environmentVariables = this.getEnvironmentVariables(); + const environmentSecrets = this.getEnvironmentSecrets(); + + for (const [key, value] of Object.entries(environmentVariables)) { + this.addEnvironmentVariable(key, value); + } + for (const [key, value] of Object.entries(environmentSecrets)) { + this.addSecret(key, value); + } // generate an IAM role only when ImageRepositoryType is ECR and props.accessRole is undefined this.accessRole = (this.source.imageRepository?.imageRepositoryType == ImageRepositoryType.ECR) ? this.props.accessRole ?? this.generateDefaultRole() : undefined; - // generalte an IAM role only when environmentSecrets has values and props.instanceRole is undefined - this.instanceRole = (Object.keys(this.environmentSecrets).length > 0 && !this.props.instanceRole) ? - this.createInstanceRole() : this.props.instanceRole; - if (this.source.codeRepository?.codeConfiguration.configurationSource == ConfigurationSourceType.REPOSITORY && this.source.codeRepository?.codeConfiguration.configurationValues) { throw new Error('configurationValues cannot be provided if the ConfigurationSource is Repository'); @@ -1001,7 +997,7 @@ export class Service extends cdk.Resource { instanceConfiguration: { cpu: this.props.cpu?.unit, memory: this.props.memory?.unit, - instanceRoleArn: this.instanceRole?.roleArn, + instanceRoleArn: Lazy.string({ produce: () => this.instanceRole?.roleArn }), }, sourceConfiguration: { authenticationConfiguration: this.renderAuthenticationConfiguration(), @@ -1036,6 +1032,9 @@ export class Service extends cdk.Resource { * This method adds an environment variable to the App Runner service. */ public addEnvironmentVariable(name: string, value: string) { + if (name.startsWith('AWSAPPRUNNER')) { + throw new Error(`Environment variable key ${name} with a prefix of AWSAPPRUNNER is not allowed`); + } this.variables.push({ name: name, value: value }); } @@ -1043,6 +1042,9 @@ export class Service extends cdk.Resource { * This method adds a secret as environment variable to the App Runner service. */ public addSecret(name: string, secret: Secret) { + if (name.startsWith('AWSAPPRUNNER')) { + throw new Error(`Environment secret key ${name} with a prefix of AWSAPPRUNNER is not allowed`); + } if (!this.instanceRole) { this.instanceRole = this.createInstanceRole(); } @@ -1130,20 +1132,14 @@ export class Service extends cdk.Resource { port: props.port, buildCommand: props.buildCommand, runtime: props.runtime.name, - runtimeEnvironmentVariables: this.renderEnvironmentVariables(), - runtimeEnvironmentSecrets: this.renderEnvironmentSecrets(), + runtimeEnvironmentVariables: Lazy.any({ produce: () => this.renderEnvironmentVariables() }), + runtimeEnvironmentSecrets: Lazy.any({ produce: () => this.renderEnvironmentSecrets() }), startCommand: props.startCommand, }; } private renderEnvironmentVariables(): EnvironmentVariable[] | undefined { - if (Object.keys(this.environmentVariables).length > 0) { - for (const [key, value] of Object.entries(this.environmentVariables)) { - if (key.startsWith('AWSAPPRUNNER')) { - throw new Error(`Environment variable key ${key} with a prefix of AWSAPPRUNNER is not allowed`); - } - this.variables.push({ name: key, value: value }); - } + if (this.variables.length > 0) { return this.variables; } else { return undefined; @@ -1151,15 +1147,7 @@ export class Service extends cdk.Resource { } private renderEnvironmentSecrets(): EnvironmentSecret[] | undefined { - if (Object.keys(this.environmentSecrets).length > 0 && this.instanceRole) { - for (const [key, value] of Object.entries(this.environmentSecrets)) { - if (key.startsWith('AWSAPPRUNNER')) { - throw new Error(`Environment secret key ${key} with a prefix of AWSAPPRUNNER is not allowed`); - } - - value.grantRead(this.instanceRole); - this.secrets.push({ name: key, value: value.arn }); - } + if (this.secrets.length > 0 && this.instanceRole) { return this.secrets; } else { return undefined; @@ -1171,8 +1159,8 @@ export class Service extends cdk.Resource { imageConfiguration: { port: repo.imageConfiguration?.port?.toString(), startCommand: repo.imageConfiguration?.startCommand, - runtimeEnvironmentVariables: this.renderEnvironmentVariables(), - runtimeEnvironmentSecrets: this.renderEnvironmentSecrets(), + runtimeEnvironmentVariables: Lazy.any({ produce: () => this.renderEnvironmentVariables() }), + runtimeEnvironmentSecrets: Lazy.any({ produce: () => this.renderEnvironmentSecrets() }), }, }); } diff --git a/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.assets.json b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.assets.json new file mode 100644 index 0000000000000..12219ed3e9ba6 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.assets.json @@ -0,0 +1,19 @@ +{ + "version": "30.1.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.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/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.template.json b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.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/test/integ.service-later-secrets-env-vars.js.snapshot/cdk.out b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/cdk.out new file mode 100644 index 0000000000000..b72fef144f05c --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"30.1.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.assets.json b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.assets.json new file mode 100644 index 0000000000000..94ab415cc5959 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.assets.json @@ -0,0 +1,19 @@ +{ + "version": "30.1.0", + "files": { + "7cbdc4561bb7693ec11f95d96ee8a14d99732d386c66f27cb36e08a108d4ef30": { + "source": { + "path": "integ-apprunner-later-secrets-env-vars.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "7cbdc4561bb7693ec11f95d96ee8a14d99732d386c66f27cb36e08a108d4ef30.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/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.template.json b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.template.json new file mode 100644 index 0000000000000..b9d9c032d5622 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ-apprunner-later-secrets-env-vars.template.json @@ -0,0 +1,157 @@ +{ + "Resources": { + "LaterSecretF6C54C5B": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "SecretString": "{\"password\":\"mySecretPassword\",\"apikey\":\"mySecretApiKey\"}" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "Service9DECC815E": { + "Type": "AWS::AppRunner::Service", + "Properties": { + "SourceConfiguration": { + "AuthenticationConfiguration": {}, + "ImageRepository": { + "ImageConfiguration": { + "Port": "8000", + "RuntimeEnvironmentSecrets": [ + { + "Name": "LATER_SECRET", + "Value": { + "Fn::Join": [ + "", + [ + { + "Ref": "LaterSecretF6C54C5B" + }, + ":apikey::" + ] + ] + } + } + ], + "RuntimeEnvironmentVariables": [ + { + "Name": "LATER_ENVVAR", + "Value": "testNewEnvVar" + } + ] + }, + "ImageIdentifier": "public.ecr.aws/aws-containers/hello-app-runner:latest", + "ImageRepositoryType": "ECR_PUBLIC" + } + }, + "InstanceConfiguration": { + "InstanceRoleArn": { + "Fn::GetAtt": [ + "Service9InstanceRole8BD2CEE0", + "Arn" + ] + } + }, + "NetworkConfiguration": { + "EgressConfiguration": { + "EgressType": "DEFAULT" + } + } + } + }, + "Service9InstanceRole8BD2CEE0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "tasks.apprunner.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "Service9InstanceRoleDefaultPolicy85BF9E64": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "LaterSecretF6C54C5B" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Service9InstanceRoleDefaultPolicy85BF9E64", + "Roles": [ + { + "Ref": "Service9InstanceRole8BD2CEE0" + } + ] + } + } + }, + "Outputs": { + "URL9": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Fn::GetAtt": [ + "Service9DECC815E", + "ServiceUrl" + ] + } + ] + ] + } + } + }, + "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/test/integ.service-later-secrets-env-vars.js.snapshot/integ.json b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ.json new file mode 100644 index 0000000000000..6bb968cb25c63 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "30.1.0", + "testCases": { + "AppRunnerLaterSecretsEnvVars/DefaultTest": { + "stacks": [ + "integ-apprunner-later-secrets-env-vars" + ], + "assertionStack": "AppRunnerLaterSecretsEnvVars/DefaultTest/DeployAssert", + "assertionStackName": "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/manifest.json b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/manifest.json new file mode 100644 index 0000000000000..26532fdc82343 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/manifest.json @@ -0,0 +1,135 @@ +{ + "version": "30.1.0", + "artifacts": { + "integ-apprunner-later-secrets-env-vars.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-apprunner-later-secrets-env-vars.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-apprunner-later-secrets-env-vars": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-apprunner-later-secrets-env-vars.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}/7cbdc4561bb7693ec11f95d96ee8a14d99732d386c66f27cb36e08a108d4ef30.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-apprunner-later-secrets-env-vars.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-later-secrets-env-vars.assets" + ], + "metadata": { + "/integ-apprunner-later-secrets-env-vars/LaterSecret/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LaterSecretF6C54C5B" + } + ], + "/integ-apprunner-later-secrets-env-vars/Service9/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Service9DECC815E" + } + ], + "/integ-apprunner-later-secrets-env-vars/Service9/InstanceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Service9InstanceRole8BD2CEE0" + } + ], + "/integ-apprunner-later-secrets-env-vars/Service9/InstanceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Service9InstanceRoleDefaultPolicy85BF9E64" + } + ], + "/integ-apprunner-later-secrets-env-vars/URL9": [ + { + "type": "aws:cdk:logicalId", + "data": "URL9" + } + ], + "/integ-apprunner-later-secrets-env-vars/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-apprunner-later-secrets-env-vars/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-apprunner-later-secrets-env-vars" + }, + "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.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": [ + "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.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": [ + "AppRunnerLaterSecretsEnvVarsDefaultTestDeployAssert07867A67.assets" + ], + "metadata": { + "/AppRunnerLaterSecretsEnvVars/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/AppRunnerLaterSecretsEnvVars/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "AppRunnerLaterSecretsEnvVars/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/tree.json b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/tree.json new file mode 100644 index 0000000000000..56bafbed7e5e2 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.js.snapshot/tree.json @@ -0,0 +1,288 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "integ-apprunner-later-secrets-env-vars": { + "id": "integ-apprunner-later-secrets-env-vars", + "path": "integ-apprunner-later-secrets-env-vars", + "children": { + "LaterSecret": { + "id": "LaterSecret", + "path": "integ-apprunner-later-secrets-env-vars/LaterSecret", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-apprunner-later-secrets-env-vars/LaterSecret/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SecretsManager::Secret", + "aws:cdk:cloudformation:props": { + "secretString": "{\"password\":\"mySecretPassword\",\"apikey\":\"mySecretApiKey\"}" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-secretsmanager.CfnSecret", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-secretsmanager.Secret", + "version": "0.0.0" + } + }, + "Service9": { + "id": "Service9", + "path": "integ-apprunner-later-secrets-env-vars/Service9", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-apprunner-later-secrets-env-vars/Service9/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::AppRunner::Service", + "aws:cdk:cloudformation:props": { + "sourceConfiguration": { + "authenticationConfiguration": {}, + "imageRepository": { + "imageConfiguration": { + "port": "8000", + "runtimeEnvironmentVariables": [ + { + "name": "LATER_ENVVAR", + "value": "testNewEnvVar" + } + ], + "runtimeEnvironmentSecrets": [ + { + "name": "LATER_SECRET", + "value": { + "Fn::Join": [ + "", + [ + { + "Ref": "LaterSecretF6C54C5B" + }, + ":apikey::" + ] + ] + } + } + ] + }, + "imageIdentifier": "public.ecr.aws/aws-containers/hello-app-runner:latest", + "imageRepositoryType": "ECR_PUBLIC" + } + }, + "instanceConfiguration": { + "instanceRoleArn": { + "Fn::GetAtt": [ + "Service9InstanceRole8BD2CEE0", + "Arn" + ] + } + }, + "networkConfiguration": { + "egressConfiguration": { + "egressType": "DEFAULT" + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apprunner.CfnService", + "version": "0.0.0" + } + }, + "InstanceRole": { + "id": "InstanceRole", + "path": "integ-apprunner-later-secrets-env-vars/Service9/InstanceRole", + "children": { + "ImportInstanceRole": { + "id": "ImportInstanceRole", + "path": "integ-apprunner-later-secrets-env-vars/Service9/InstanceRole/ImportInstanceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-apprunner-later-secrets-env-vars/Service9/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": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "integ-apprunner-later-secrets-env-vars/Service9/InstanceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-apprunner-later-secrets-env-vars/Service9/InstanceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": { + "Ref": "LaterSecretF6C54C5B" + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "Service9InstanceRoleDefaultPolicy85BF9E64", + "roles": [ + { + "Ref": "Service9InstanceRole8BD2CEE0" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apprunner.Service", + "version": "0.0.0" + } + }, + "URL9": { + "id": "URL9", + "path": "integ-apprunner-later-secrets-env-vars/URL9", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-apprunner-later-secrets-env-vars/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-apprunner-later-secrets-env-vars/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "AppRunnerLaterSecretsEnvVars": { + "id": "AppRunnerLaterSecretsEnvVars", + "path": "AppRunnerLaterSecretsEnvVars", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "AppRunnerLaterSecretsEnvVars/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "AppRunnerLaterSecretsEnvVars/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.252" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "AppRunnerLaterSecretsEnvVars/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "AppRunnerLaterSecretsEnvVars/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "AppRunnerLaterSecretsEnvVars/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.252" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.ts b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.ts new file mode 100644 index 0000000000000..163ea13282811 --- /dev/null +++ b/packages/@aws-cdk/aws-apprunner/test/integ.service-later-secrets-env-vars.ts @@ -0,0 +1,36 @@ +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; +import * as cdk from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import * as apprunner from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'integ-apprunner-later-secrets-env-vars'); + +// Scenario 9: Create the service from ECR public with secrets and environment vars added later +const laterSecret = new secretsmanager.Secret(stack, 'LaterSecret', { + secretObjectValue: { + password: cdk.SecretValue.unsafePlainText('mySecretPassword'), + apikey: cdk.SecretValue.unsafePlainText('mySecretApiKey'), + }, +}); + +const service9 = new apprunner.Service(stack, 'Service9', { + source: apprunner.Source.fromEcrPublic({ + imageConfiguration: { + port: 8000, + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), +}); + +service9.addSecret('LATER_SECRET', apprunner.Secret.fromSecretsManager(laterSecret, 'apikey')); +service9.addEnvironmentVariable('LATER_ENVVAR', 'testNewEnvVar'); + +new cdk.CfnOutput(stack, 'URL9', { value: `https://${service9.serviceUrl}` }); + +new integ.IntegTest(app, 'AppRunnerLaterSecretsEnvVars', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apprunner/test/service.test.ts b/packages/@aws-cdk/aws-apprunner/test/service.test.ts index a5e4604278194..914fdd81f1745 100644 --- a/packages/@aws-cdk/aws-apprunner/test/service.test.ts +++ b/packages/@aws-cdk/aws-apprunner/test/service.test.ts @@ -391,6 +391,109 @@ test('custom environment secrets and start commands are allowed for imageConfigu }); }); +test('custom environment variables can be added with .addEnvironmentVariable() without first defining them in props', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + const service = new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageConfiguration: { + startCommand: '/root/start-command.sh', + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + }); + + // WHEN + service.addEnvironmentVariable('TEST_ENVIRONMENT_VARIABLE', 'test environment variable value'); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::Service', { + SourceConfiguration: { + AuthenticationConfiguration: {}, + ImageRepository: { + ImageConfiguration: { + RuntimeEnvironmentVariables: [ + { + Name: 'TEST_ENVIRONMENT_VARIABLE', + Value: 'test environment variable value', + }, + ], + StartCommand: '/root/start-command.sh', + }, + ImageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + ImageRepositoryType: 'ECR_PUBLIC', + }, + }, + NetworkConfiguration: { + EgressConfiguration: { + EgressType: 'DEFAULT', + }, + }, + }); +}); + +test('custom environment secrets can be added with .addSecret() without first defining them in props', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const service = new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageConfiguration: { + startCommand: '/root/start-command.sh', + }, + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + }); + + // WHEN + service.addSecret('LATER_SECRET', apprunner.Secret.fromSecretsManager(secret, 'field')); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::AppRunner::Service', { + SourceConfiguration: { + AuthenticationConfiguration: {}, + ImageRepository: { + ImageConfiguration: { + RuntimeEnvironmentSecrets: [ + { + Name: 'LATER_SECRET', + Value: { + 'Fn::Join': [ + '', + [ + { + Ref: 'SecretA720EF05', + }, + ':field::', + ], + ], + }, + }, + ], + StartCommand: '/root/start-command.sh', + }, + ImageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + ImageRepositoryType: 'ECR_PUBLIC', + }, + }, + NetworkConfiguration: { + EgressConfiguration: { + EgressType: 'DEFAULT', + }, + }, + InstanceConfiguration: { + InstanceRoleArn: { + 'Fn::GetAtt': [ + 'DemoServiceInstanceRoleFCED1725', + 'Arn', + ], + }, + }, + }); +}); + test('create a service from existing ECR repository(image repository type: ECR)', () => { // GIVEN const app = new cdk.App(); @@ -871,6 +974,22 @@ test('environment variable with a prefix of AWSAPPRUNNER should throw an error', }).toThrow('Environment variable key AWSAPPRUNNER_FOO with a prefix of AWSAPPRUNNER is not allowed'); }); +test('environment variable with a prefix of AWSAPPRUNNER added later 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(() => { + const service = new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + }); + service.addEnvironmentVariable('AWSAPPRUNNER_FOO', 'BAR'); + }).toThrow('Environment variable key AWSAPPRUNNER_FOO with a prefix of AWSAPPRUNNER is not allowed'); +}); + test('environment secrets with a prefix of AWSAPPRUNNER should throw an error', () => { // GIVEN const app = new cdk.App(); @@ -893,6 +1012,24 @@ test('environment secrets with a prefix of AWSAPPRUNNER should throw an error', }).toThrow('Environment secret key AWSAPPRUNNER_FOO with a prefix of AWSAPPRUNNER is not allowed'); }); +test('environment secrets with a prefix of AWSAPPRUNNER added later should throw an error', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'demo-stack'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + // we should have the service + expect(() => { + const service = new apprunner.Service(stack, 'DemoService', { + source: apprunner.Source.fromEcrPublic({ + imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest', + }), + }); + service.addSecret('AWSAPPRUNNER_FOO', apprunner.Secret.fromSecretsManager(secret)); + }).toThrow('Environment secret key AWSAPPRUNNER_FOO with a prefix of AWSAPPRUNNER is not allowed'); +}); + test('specifying a vpcConnector should assign the service to it and set the egressType to VPC', () => { // GIVEN const app = new cdk.App();