diff --git a/packages/aws-cdk-lib/aws-appconfig/lib/configuration.ts b/packages/aws-cdk-lib/aws-appconfig/lib/configuration.ts index a974aae0c4203..c0b91fcd51cc4 100644 --- a/packages/aws-cdk-lib/aws-appconfig/lib/configuration.ts +++ b/packages/aws-cdk-lib/aws-appconfig/lib/configuration.ts @@ -2,12 +2,11 @@ import * as fs from 'fs'; import * as path from 'path'; import { Construct, IConstruct } from 'constructs'; -import { CfnConfigurationProfile, CfnDeployment, CfnHostedConfigurationVersion } from './appconfig.generated'; +import { CfnConfigurationProfile, CfnHostedConfigurationVersion } from './appconfig.generated'; import { IApplication } from './application'; import { DeploymentStrategy, IDeploymentStrategy, RolloutStrategy } from './deployment-strategy'; import { IEnvironment } from './environment'; import { ActionPoint, IEventDestination, ExtensionOptions, IExtension, IExtensible, ExtensibleBase } from './extension'; -import { getHash } from './private/hash'; import * as cp from '../../aws-codepipeline'; import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; @@ -319,15 +318,7 @@ abstract class ConfigurationBase extends Construct implements IConfiguration, IE if ((this.deployTo && !this.deployTo.includes(environment))) { return; } - new CfnDeployment(this, `Deployment${getHash(environment.name!)}`, { - applicationId: this.application.applicationId, - configurationProfileId: this.configurationProfileId, - deploymentStrategyId: this.deploymentStrategy!.deploymentStrategyId, - environmentId: environment.environmentId, - configurationVersion: this.versionNumber!, - description: this.description, - kmsKeyIdentifier: this.deploymentKey?.keyArn, - }); + environment.addDeployment(this); }); } } diff --git a/packages/aws-cdk-lib/aws-appconfig/lib/environment.ts b/packages/aws-cdk-lib/aws-appconfig/lib/environment.ts index 437f267c1b366..57b22be765cf0 100644 --- a/packages/aws-cdk-lib/aws-appconfig/lib/environment.ts +++ b/packages/aws-cdk-lib/aws-appconfig/lib/environment.ts @@ -1,6 +1,7 @@ import { Construct } from 'constructs'; -import { CfnEnvironment } from './appconfig.generated'; +import { CfnDeployment, CfnEnvironment } from './appconfig.generated'; import { IApplication } from './application'; +import { IConfiguration } from './configuration'; import { ActionPoint, IEventDestination, ExtensionOptions, IExtension, IExtensible, ExtensibleBase } from './extension'; import { getHash } from './private/hash'; import * as cloudwatch from '../../aws-cloudwatch'; @@ -47,7 +48,31 @@ abstract class EnvironmentBase extends Resource implements IEnvironment, IExtens public abstract applicationId: string; public abstract environmentId: string; public abstract environmentArn: string; + public abstract name?: string | undefined; protected extensible!: ExtensibleBase; + protected deploymentQueue: Array = []; + + public addDeployment(configuration: IConfiguration): void { + const queueSize = this.deploymentQueue.push( + new CfnDeployment(configuration, `Deployment${getHash(this.name!)}`, { + applicationId: configuration.application.applicationId, + configurationProfileId: configuration.configurationProfileId, + deploymentStrategyId: configuration.deploymentStrategy!.deploymentStrategyId, + environmentId: this.environmentId, + configurationVersion: configuration.versionNumber!, + description: configuration.description, + kmsKeyIdentifier: configuration.deploymentKey?.keyArn, + }), + ); + + if (queueSize > 1) { + this.deploymentQueue[queueSize - 1].addDependency(this.deploymentQueue[queueSize - 2]); + } + } + + public addDeployments(...configurations: IConfiguration[]): void { + configurations.forEach((config) => this.addDeployment(config)); + } public on(actionPoint: ActionPoint, eventDestination: IEventDestination, options?: ExtensionOptions) { this.extensible.on(actionPoint, eventDestination, options); @@ -154,6 +179,7 @@ export class Environment extends EnvironmentBase { public readonly applicationId = applicationId; public readonly environmentId = environmentId; public readonly environmentArn = environmentArn; + public readonly name?: string | undefined; } return new Import(scope, id, { @@ -413,6 +439,23 @@ export interface IEnvironment extends IResource { */ readonly environmentArn: string; + /** + * Creates a deployment of the supplied configuration to this environment. + * Note that you can only deploy one configuration at a time to an environment. + * However, you can deploy one configuration each to different environments at the same time. + * If more than one deployment is requested for this environment, they will occur in the same order they were provided. + * + * @param configuration The configuration that will be deployed to this environment. + */ + addDeployment(configuration: IConfiguration): void; + + /** + * Creates a deployment for each of the supplied configurations to this environment. + * + * @param configurations The configurations that will be deployed to this environment. + */ + addDeployments(...configurations: Array): void; + /** * Adds an extension defined by the action point and event destination and also * creates an extension association to the environment. diff --git a/packages/aws-cdk-lib/aws-appconfig/test/environment.test.ts b/packages/aws-cdk-lib/aws-appconfig/test/environment.test.ts index dd2e5e278482f..2cbe0fa159c6d 100644 --- a/packages/aws-cdk-lib/aws-appconfig/test/environment.test.ts +++ b/packages/aws-cdk-lib/aws-appconfig/test/environment.test.ts @@ -2,7 +2,7 @@ import { Template } from '../../assertions'; import { Alarm, CompositeAlarm, Metric } from '../../aws-cloudwatch'; import * as iam from '../../aws-iam'; import * as cdk from '../../core'; -import { Application, Environment, Monitor } from '../lib'; +import { Application, ConfigurationContent, Environment, HostedConfiguration, Monitor } from '../lib'; describe('environment', () => { test('default environment', () => { @@ -54,6 +54,216 @@ describe('environment', () => { }); }); + test('environment with single deployment', () => { + const stack = new cdk.Stack(); + const application = new Application(stack, 'MyAppConfig'); + const env = new Environment(stack, 'MyEnvironment', { + application, + }); + + const firstConfig = new HostedConfiguration(stack, 'FirstConfig', { + application, + content: ConfigurationContent.fromInlineText('This is my content 1'), + }); + env.addDeployment(firstConfig); + + const actual = Template.fromStack(stack); + + actual.hasResourceProperties('AWS::AppConfig::Environment', { + Name: 'MyEnvironment', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + }); + + actual.hasResourceProperties('AWS::AppConfig::ConfigurationProfile', { + Name: 'FirstConfig', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + LocationUri: 'hosted', + }); + actual.hasResourceProperties('AWS::AppConfig::HostedConfigurationVersion', { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + ConfigurationProfileId: { + Ref: 'FirstConfigConfigurationProfileDEF37C63', + }, + Content: 'This is my content 1', + ContentType: 'text/plain', + }); + actual.hasResource('AWS::AppConfig::Deployment', { + Properties: { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + EnvironmentId: { + Ref: 'MyEnvironment465E4DEA', + }, + ConfigurationVersion: { + Ref: 'FirstConfigC35E996C', + }, + ConfigurationProfileId: { + Ref: 'FirstConfigConfigurationProfileDEF37C63', + }, + DeploymentStrategyId: { + Ref: 'FirstConfigDeploymentStrategy863BBA9A', + }, + }, + }); + + actual.resourceCountIs('AWS::AppConfig::Deployment', 1); + }); + + test('environment with multiple deployments', () => { + const stack = new cdk.Stack(); + const application = new Application(stack, 'MyAppConfig'); + const env = new Environment(stack, 'MyEnvironment', { + application, + }); + + const firstConfig = new HostedConfiguration(stack, 'FirstConfig', { + application, + content: ConfigurationContent.fromInlineText('This is my content 1'), + }); + const secondConfig = new HostedConfiguration(stack, 'SecondConfig', { + application, + content: ConfigurationContent.fromInlineText('This is my content 2'), + }); + const thirdConfig = new HostedConfiguration(stack, 'ThirdConfig', { + application, + content: ConfigurationContent.fromInlineText('This is my content 3'), + }); + + env.addDeployments(firstConfig, secondConfig); + env.addDeployment(thirdConfig); + + const actual = Template.fromStack(stack); + + actual.hasResourceProperties('AWS::AppConfig::Environment', { + Name: 'MyEnvironment', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + }); + + actual.hasResourceProperties('AWS::AppConfig::ConfigurationProfile', { + Name: 'FirstConfig', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + LocationUri: 'hosted', + }); + actual.hasResourceProperties('AWS::AppConfig::HostedConfigurationVersion', { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + ConfigurationProfileId: { + Ref: 'FirstConfigConfigurationProfileDEF37C63', + }, + Content: 'This is my content 1', + ContentType: 'text/plain', + }); + actual.hasResource('AWS::AppConfig::Deployment', { + Properties: { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + EnvironmentId: { + Ref: 'MyEnvironment465E4DEA', + }, + ConfigurationVersion: { + Ref: 'FirstConfigC35E996C', + }, + ConfigurationProfileId: { + Ref: 'FirstConfigConfigurationProfileDEF37C63', + }, + DeploymentStrategyId: { + Ref: 'FirstConfigDeploymentStrategy863BBA9A', + }, + }, + }); + + actual.hasResourceProperties('AWS::AppConfig::ConfigurationProfile', { + Name: 'SecondConfig', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + LocationUri: 'hosted', + }); + actual.hasResourceProperties('AWS::AppConfig::HostedConfigurationVersion', { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + ConfigurationProfileId: { + Ref: 'SecondConfigConfigurationProfileE64FE7B4', + }, + Content: 'This is my content 2', + ContentType: 'text/plain', + }); + actual.hasResource('AWS::AppConfig::Deployment', { + Properties: { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + EnvironmentId: { + Ref: 'MyEnvironment465E4DEA', + }, + ConfigurationVersion: { + Ref: 'SecondConfig22E40AAE', + }, + ConfigurationProfileId: { + Ref: 'SecondConfigConfigurationProfileE64FE7B4', + }, + DeploymentStrategyId: { + Ref: 'SecondConfigDeploymentStrategy9929738B', + }, + }, + DependsOn: ['FirstConfigDeployment52928BE68587B'], + }); + + actual.hasResourceProperties('AWS::AppConfig::ConfigurationProfile', { + Name: 'ThirdConfig', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + LocationUri: 'hosted', + }); + actual.hasResourceProperties('AWS::AppConfig::HostedConfigurationVersion', { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + ConfigurationProfileId: { + Ref: 'ThirdConfigConfigurationProfile4945C970', + }, + Content: 'This is my content 3', + ContentType: 'text/plain', + }); + actual.hasResource('AWS::AppConfig::Deployment', { + Properties: { + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + EnvironmentId: { + Ref: 'MyEnvironment465E4DEA', + }, + ConfigurationVersion: { + Ref: 'ThirdConfig498595D6', + }, + ConfigurationProfileId: { + Ref: 'ThirdConfigConfigurationProfile4945C970', + }, + DeploymentStrategyId: { + Ref: 'ThirdConfigDeploymentStrategy246FBD1A', + }, + }, + DependsOn: ['SecondConfigDeployment5292843F35B55'], + }); + + actual.resourceCountIs('AWS::AppConfig::Deployment', 3); + }); + test('environment with monitors with alarm and alarmRole', () => { const stack = new cdk.Stack(); const app = new Application(stack, 'MyAppConfig');