From fee2cf88c58c6c1f25b9e6fad87c8042de464fd9 Mon Sep 17 00:00:00 2001 From: Atanas Pamukchiev Date: Wed, 23 Oct 2024 18:53:01 +0200 Subject: [PATCH] feat(cli): add ability to configure hotswap properties for ECS (#30511) ### Issue #29618 ### Reason for this change We aim to speed up deployment times in our development environment by using the hotswap feature. However, our services have dependencies on each other, and the current hotswap behavior is too disruptive. ### Description of changes We modified the hotswap implementation for ECS services to pass the `minimumHealthyPercent` and `maximumHealthyPercent` configurable parameters. These parameters are exposed to the cli and can be passed as `--hotswap-ecs-minimum-healthy-percent ` and `--hotswap-ecs-maximum-healthy-percent ` The implementation is careful to maintain the existing behaviour. That is, if none of the new flags is used, the current `minimumHealthyPercent = 0` and `maximumHealthyPercent = undefined` values are used. ### Description of how you validated changes We added a unit test validating that the correct values are passed to the task definition. We also executed using the locally built version of cdk validating that the behavior is as expected: the parameters are respected during hotswap deployments, and the existing API is maintained. ### 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* --- .../tests/cli-integ-tests/cli.integtest.ts | 52 +++++++++++ packages/aws-cdk/README.md | 13 +++ packages/aws-cdk/lib/api/deploy-stack.ts | 10 +- packages/aws-cdk/lib/api/deployments.ts | 8 +- .../aws-cdk/lib/api/hotswap-deployments.ts | 31 +++++-- packages/aws-cdk/lib/api/hotswap/common.ts | 46 ++++++++++ .../aws-cdk/lib/api/hotswap/ecs-services.ts | 14 ++- packages/aws-cdk/lib/cdk-toolkit.ts | 11 ++- packages/aws-cdk/lib/settings.ts | 6 ++ .../aws-cdk/test/api/deploy-stack.test.ts | 4 +- .../ecs-services-hotswap-deployments.test.ts | 91 ++++++++++++++++++- .../test/api/hotswap/hotswap-test-setup.ts | 6 +- 12 files changed, 272 insertions(+), 20 deletions(-) diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts index a7365c8a1b993..b1c0d4f62bbad 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts @@ -2301,6 +2301,58 @@ integTest('hotswap deployment supports AppSync APIs with many functions', }), ); +integTest('hotswap ECS deployment respects properties override', withDefaultFixture(async (fixture) => { + // Update the CDK context with the new ECS properties + let ecsMinimumHealthyPercent = 100; + let ecsMaximumHealthyPercent = 200; + let cdkJson = JSON.parse(await fs.readFile(path.join(fixture.integTestDir, 'cdk.json'), 'utf8')); + cdkJson = { + ...cdkJson, + hotswap: { + ecs: { + minimumHealthyPercent: ecsMinimumHealthyPercent, + maximumHealthyPercent: ecsMaximumHealthyPercent, + }, + }, + }; + + await fs.writeFile(path.join(fixture.integTestDir, 'cdk.json'), JSON.stringify(cdkJson)); + + // GIVEN + const stackArn = await fixture.cdkDeploy('ecs-hotswap', { + captureStderr: false, + }); + + // WHEN + await fixture.cdkDeploy('ecs-hotswap', { + options: [ + '--hotswap', + ], + modEnv: { + DYNAMIC_ECS_PROPERTY_VALUE: 'new value', + }, + }); + + const describeStacksResponse = await fixture.aws.cloudFormation.send( + new DescribeStacksCommand({ + StackName: stackArn, + }), + ); + + const clusterName = describeStacksResponse.Stacks?.[0].Outputs?.find(output => output.OutputKey == 'ClusterName')?.OutputValue!; + const serviceName = describeStacksResponse.Stacks?.[0].Outputs?.find(output => output.OutputKey == 'ServiceName')?.OutputValue!; + + // THEN + const describeServicesResponse = await fixture.aws.ecs.send( + new DescribeServicesCommand({ + cluster: clusterName, + services: [serviceName], + }), + ); + expect(describeServicesResponse.services?.[0].deploymentConfiguration?.minimumHealthyPercent).toEqual(ecsMinimumHealthyPercent); + expect(describeServicesResponse.services?.[0].deploymentConfiguration?.maximumPercent).toEqual(ecsMaximumHealthyPercent); +})); + async function listChildren(parent: string, pred: (x: string) => Promise) { const ret = new Array(); for (const child of await fs.readdir(parent, { encoding: 'utf-8' })) { diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index deec121451f77..30818c8cb1402 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -451,6 +451,19 @@ Hotswapping is currently supported for the following changes - VTL mapping template changes for AppSync Resolvers and Functions. - Schema changes for AppSync GraphQL Apis. +You can optionally configure the behavior of your hotswap deployments in `cdk.json`. Currently you can only configure ECS hotswap behavior: + +```json +{ +"hotswap": { + "ecs": { + "minimumHealthyPercent": 100, + "maximumHealthyPercent": 250 + } + } +} +``` + **⚠ Note #1**: This command deliberately introduces drift in CloudFormation stacks in order to speed up deployments. For this reason, only use it for development purposes. **Never use this flag for your production deployments**! diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 4a142d82e4309..16da0447b81f5 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -5,7 +5,7 @@ import * as uuid from 'uuid'; import { ISDK, SdkProvider } from './aws-auth'; import { EnvironmentResources } from './environment-resources'; import { CfnEvaluationException } from './evaluate-cloudformation-template'; -import { HotswapMode, ICON } from './hotswap/common'; +import { HotswapMode, HotswapPropertyOverrides, ICON } from './hotswap/common'; import { tryHotswapDeployment } from './hotswap-deployments'; import { addMetadataAssetsToManifest } from '../assets'; import { Tag } from '../cdk-toolkit'; @@ -173,6 +173,11 @@ export interface DeployStackOptions { */ readonly hotswap?: HotswapMode; + /** + * Extra properties that configure hotswap behavior + */ + readonly hotswapPropertyOverrides?: HotswapPropertyOverrides; + /** * The extra string to append to the User-Agent header when performing AWS SDK calls. * @@ -264,6 +269,7 @@ export async function deployStack(options: DeployStackOptions): Promise Promise; const RESOURCE_DETECTORS: { [key: string]: HotswapDetector } = { @@ -62,7 +65,7 @@ const RESOURCE_DETECTORS: { [key: string]: HotswapDetector } = { export async function tryHotswapDeployment( sdkProvider: SdkProvider, assetParams: { [key: string]: string }, cloudFormationStack: CloudFormationStack, stackArtifact: cxapi.CloudFormationStackArtifact, - hotswapMode: HotswapMode, + hotswapMode: HotswapMode, hotswapPropertyOverrides: HotswapPropertyOverrides, ): Promise { // resolve the environment, so we can substitute things like AWS::Region in CFN expressions const resolvedEnv = await sdkProvider.resolveEnvironment(stackArtifact.environment); @@ -86,7 +89,7 @@ export async function tryHotswapDeployment( const stackChanges = cfn_diff.fullDiff(currentTemplate.deployedRootTemplate, stackArtifact.template); const { hotswappableChanges, nonHotswappableChanges } = await classifyResourceChanges( - stackChanges, evaluateCfnTemplate, sdk, currentTemplate.nestedStacks, + stackChanges, evaluateCfnTemplate, sdk, currentTemplate.nestedStacks, hotswapPropertyOverrides, ); logNonHotswappableChanges(nonHotswappableChanges, hotswapMode); @@ -113,6 +116,7 @@ async function classifyResourceChanges( evaluateCfnTemplate: EvaluateCloudFormationTemplate, sdk: ISDK, nestedStackNames: { [nestedStackName: string]: NestedStackTemplates }, + hotswapPropertyOverrides: HotswapPropertyOverrides, ): Promise { const resourceDifferences = getStackResourceDifferences(stackChanges); @@ -131,7 +135,14 @@ async function classifyResourceChanges( // gather the results of the detector functions for (const [logicalId, change] of Object.entries(resourceDifferences)) { if (change.newValue?.Type === 'AWS::CloudFormation::Stack' && change.oldValue?.Type === 'AWS::CloudFormation::Stack') { - const nestedHotswappableResources = await findNestedHotswappableChanges(logicalId, change, nestedStackNames, evaluateCfnTemplate, sdk); + const nestedHotswappableResources = await findNestedHotswappableChanges( + logicalId, + change, + nestedStackNames, + evaluateCfnTemplate, + sdk, + hotswapPropertyOverrides, + ); hotswappableResources.push(...nestedHotswappableResources.hotswappableChanges); nonHotswappableResources.push(...nestedHotswappableResources.nonHotswappableChanges); @@ -151,7 +162,7 @@ async function classifyResourceChanges( const resourceType: string = hotswappableChangeCandidate.newValue.Type; if (resourceType in RESOURCE_DETECTORS) { // run detector functions lazily to prevent unhandled promise rejections - promises.push(() => RESOURCE_DETECTORS[resourceType](logicalId, hotswappableChangeCandidate, evaluateCfnTemplate)); + promises.push(() => RESOURCE_DETECTORS[resourceType](logicalId, hotswappableChangeCandidate, evaluateCfnTemplate, hotswapPropertyOverrides)); } else { reportNonHotswappableChange(nonHotswappableResources, hotswappableChangeCandidate, undefined, 'This resource type is not supported for hotswap deployments'); } @@ -233,6 +244,7 @@ async function findNestedHotswappableChanges( nestedStackTemplates: { [nestedStackName: string]: NestedStackTemplates }, evaluateCfnTemplate: EvaluateCloudFormationTemplate, sdk: ISDK, + hotswapPropertyOverrides: HotswapPropertyOverrides, ): Promise { const nestedStack = nestedStackTemplates[logicalId]; if (!nestedStack.physicalName) { @@ -256,7 +268,12 @@ async function findNestedHotswappableChanges( nestedStackTemplates[logicalId].deployedTemplate, nestedStackTemplates[logicalId].generatedTemplate, ); - return classifyResourceChanges(nestedDiff, evaluateNestedCfnTemplate, sdk, nestedStackTemplates[logicalId].nestedStackTemplates); + return classifyResourceChanges( + nestedDiff, + evaluateNestedCfnTemplate, + sdk, + nestedStackTemplates[logicalId].nestedStackTemplates, + hotswapPropertyOverrides); } /** Returns 'true' if a pair of changes is for the same resource. */ diff --git a/packages/aws-cdk/lib/api/hotswap/common.ts b/packages/aws-cdk/lib/api/hotswap/common.ts index eb4b787d04e92..a066e7b0e4d5d 100644 --- a/packages/aws-cdk/lib/api/hotswap/common.ts +++ b/packages/aws-cdk/lib/api/hotswap/common.ts @@ -98,6 +98,52 @@ export class HotswappableChangeCandidate { type Exclude = { [key: string]: Exclude | true } +/** + * Represents configuration property overrides for hotswap deployments + */ +export class HotswapPropertyOverrides { + // Each supported resource type will have its own properties. Currently this is ECS + ecsHotswapProperties?: EcsHotswapProperties; + + public constructor (ecsHotswapProperties?: EcsHotswapProperties) { + this.ecsHotswapProperties = ecsHotswapProperties; + } +} + +/** + * Represents configuration properties for ECS hotswap deployments + */ +export class EcsHotswapProperties { + // The lower limit on the number of your service's tasks that must remain in the RUNNING state during a deployment, as a percentage of the desiredCount + readonly minimumHealthyPercent?: number; + // The upper limit on the number of your service's tasks that are allowed in the RUNNING or PENDING state during a deployment, as a percentage of the desiredCount + readonly maximumHealthyPercent?: number; + + public constructor (minimumHealthyPercent?: number, maximumHealthyPercent?: number) { + if (minimumHealthyPercent !== undefined && minimumHealthyPercent < 0 ) { + throw new Error('hotswap-ecs-minimum-healthy-percent can\'t be a negative number'); + } + if (maximumHealthyPercent !== undefined && maximumHealthyPercent < 0 ) { + throw new Error('hotswap-ecs-maximum-healthy-percent can\'t be a negative number'); + } + // In order to preserve the current behaviour, when minimumHealthyPercent is not defined, it will be set to the currently default value of 0 + if (minimumHealthyPercent == undefined) { + this.minimumHealthyPercent = 0; + } else { + this.minimumHealthyPercent = minimumHealthyPercent; + } + this.maximumHealthyPercent = maximumHealthyPercent; + } + + /** + * Check if any hotswap properties are defined + * @returns true if all properties are undefined, false otherwise + */ + public isEmpty(): boolean { + return this.minimumHealthyPercent === 0 && this.maximumHealthyPercent === undefined; + } +} + /** * This function transforms all keys (recursively) in the provided `val` object. * diff --git a/packages/aws-cdk/lib/api/hotswap/ecs-services.ts b/packages/aws-cdk/lib/api/hotswap/ecs-services.ts index c79032eec6e88..f6f626d32f2c9 100644 --- a/packages/aws-cdk/lib/api/hotswap/ecs-services.ts +++ b/packages/aws-cdk/lib/api/hotswap/ecs-services.ts @@ -1,10 +1,13 @@ import * as AWS from 'aws-sdk'; -import { ChangeHotswapResult, classifyChanges, HotswappableChangeCandidate, lowerCaseFirstCharacter, reportNonHotswappableChange, transformObjectKeys } from './common'; +import { ChangeHotswapResult, classifyChanges, HotswappableChangeCandidate, HotswapPropertyOverrides, lowerCaseFirstCharacter, reportNonHotswappableChange, transformObjectKeys } from './common'; import { ISDK } from '../aws-auth'; import { EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template'; export async function isHotswappableEcsServiceChange( - logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate, + logicalId: string, + change: HotswappableChangeCandidate, + evaluateCfnTemplate: EvaluateCloudFormationTemplate, + hotswapPropertyOverrides: HotswapPropertyOverrides, ): Promise { // the only resource change we can evaluate here is an ECS TaskDefinition if (change.newValue.Type !== 'AWS::ECS::TaskDefinition') { @@ -83,6 +86,10 @@ export async function isHotswappableEcsServiceChange( const registerTaskDefResponse = await sdk.ecs().registerTaskDefinition(lowercasedTaskDef).promise(); const taskDefRevArn = registerTaskDefResponse.taskDefinition?.taskDefinitionArn; + let ecsHotswapProperties = hotswapPropertyOverrides.ecsHotswapProperties; + let minimumHealthyPercent = ecsHotswapProperties?.minimumHealthyPercent; + let maximumHealthyPercent = ecsHotswapProperties?.maximumHealthyPercent; + // Step 2 - update the services using that TaskDefinition to point to the new TaskDefinition Revision const servicePerClusterUpdates: { [cluster: string]: Array<{ promise: Promise; ecsService: EcsService }> } = {}; for (const ecsService of ecsServicesReferencingTaskDef) { @@ -105,7 +112,8 @@ export async function isHotswappableEcsServiceChange( cluster: clusterName, forceNewDeployment: true, deploymentConfiguration: { - minimumHealthyPercent: 0, + minimumHealthyPercent: minimumHealthyPercent !== undefined ? minimumHealthyPercent : 0, + maximumPercent: maximumHealthyPercent !== undefined ? maximumHealthyPercent : undefined, }, }).promise(), ecsService: ecsService, diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 64a9a0b4dd20c..57dabce74a5dd 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -13,7 +13,7 @@ import { CloudAssembly, DefaultSelection, ExtendedStackSelection, StackCollectio import { CloudExecutable } from './api/cxapp/cloud-executable'; import { Deployments } from './api/deployments'; import { GarbageCollector } from './api/garbage-collection/garbage-collector'; -import { HotswapMode } from './api/hotswap/common'; +import { HotswapMode, HotswapPropertyOverrides, EcsHotswapProperties } from './api/hotswap/common'; import { findCloudWatchLogGroups } from './api/logs/find-cloudwatch-logs'; import { CloudWatchLogEventMonitor } from './api/logs/logs-monitor'; import { createDiffChangeSet, ResourcesToImport } from './api/util/cloudformation'; @@ -237,6 +237,14 @@ export class CdkToolkit { warning('⚠️ They should only be used for development - never use them for your production Stacks!\n'); } + let hotswapPropertiesFromSettings = this.props.configuration.settings.get(['hotswap']) || {}; + + let hotswapPropertyOverrides = new HotswapPropertyOverrides(); + hotswapPropertyOverrides.ecsHotswapProperties = new EcsHotswapProperties( + hotswapPropertiesFromSettings.ecs?.minimumHealthyPercent, + hotswapPropertiesFromSettings.ecs?.maximumHealthyPercent, + ); + const stacks = stackCollection.stackArtifacts; const stackOutputs: { [key: string]: any } = { }; @@ -347,6 +355,7 @@ export class CdkToolkit { ci: options.ci, rollback: options.rollback, hotswap: options.hotswap, + hotswapPropertyOverrides: hotswapPropertyOverrides, extraUserAgent: options.extraUserAgent, assetParallelism: options.assetParallelism, ignoreNoStacks: options.ignoreNoStacks, diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 74684dc3501d3..37e607f34c4dd 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -292,6 +292,12 @@ export class Settings { assetParallelism: argv['asset-parallelism'], assetPrebuild: argv['asset-prebuild'], ignoreNoStacks: argv['ignore-no-stacks'], + hotswap: { + ecs: { + minimumEcsHealthyPercent: argv.minimumEcsHealthyPercent, + maximumEcsHealthyPercent: argv.maximumEcsHealthyPercent, + }, + }, unstable: argv.unstable, }); } diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index bdb58f8aad99d..7afcea68f4c60 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -151,7 +151,7 @@ test('correctly passes CFN parameters when hotswapping', async () => { }); // THEN - expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { A: 'A-value', B: 'B=value' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK); + expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { A: 'A-value', B: 'B=value' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK, expect.anything()); }); test('correctly passes SSM parameters when hotswapping', async () => { @@ -181,7 +181,7 @@ test('correctly passes SSM parameters when hotswapping', async () => { }); // THEN - expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { SomeParameter: 'SomeValue' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK); + expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { SomeParameter: 'SomeValue' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK, expect.anything()); }); test('call CreateStack when method=direct and the stack doesnt exist yet', async () => { diff --git a/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts index d1ef496819328..182749aaa89d0 100644 --- a/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/ecs-services-hotswap-deployments.test.ts @@ -1,6 +1,7 @@ import * as AWS from 'aws-sdk'; import * as setup from './hotswap-test-setup'; -import { HotswapMode } from '../../../lib/api/hotswap/common'; +import { EcsHotswapProperties, HotswapMode, HotswapPropertyOverrides } from '../../../lib/api/hotswap/common'; +import { Configuration } from '../../../lib/settings'; import { silentTest } from '../../util/silent'; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; @@ -9,7 +10,6 @@ let mockUpdateService: (params: AWS.ECS.UpdateServiceRequest) => AWS.ECS.UpdateS beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockRegisterTaskDef = jest.fn(); mockUpdateService = jest.fn(); hotswapMockSdkProvider.stubEcs({ @@ -637,3 +637,90 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot }); }); }); + +describe.each([ + new Configuration().settings.set(['hotswap'], { ecs: { minimumHealthyPercent: 10 } }), + new Configuration().settings.set(['hotswap'], { ecs: { minimumHealthyPercent: 10, maximumHealthyPercent: 100 } }), +])('hotswap properties', (settings) => { + test('should handle all possible hotswap properties', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image1' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }); + setup.pushStackResourceSummaries( + setup.stackSummaryOf('Service', 'AWS::ECS::Service', + 'arn:aws:ecs:region:account:service/my-cluster/my-service'), + ); + mockRegisterTaskDef.mockReturnValue({ + taskDefinition: { + taskDefinitionArn: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + TaskDef: { + Type: 'AWS::ECS::TaskDefinition', + Properties: { + Family: 'my-task-def', + ContainerDefinitions: [ + { Image: 'image2' }, + ], + }, + }, + Service: { + Type: 'AWS::ECS::Service', + Properties: { + TaskDefinition: { Ref: 'TaskDef' }, + }, + }, + }, + }, + }); + + // WHEN + let ecsHotswapProperties = new EcsHotswapProperties(settings.get(['hotswap']).ecs.minimumHealthyPercent, settings.get(['hotswap']).ecs.maximumHealthyPercent); + const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment( + HotswapMode.HOTSWAP_ONLY, + cdkStackArtifact, + {}, + new HotswapPropertyOverrides(ecsHotswapProperties), + ); + + // THEN + expect(deployStackResult).not.toBeUndefined(); + expect(mockRegisterTaskDef).toBeCalledWith({ + family: 'my-task-def', + containerDefinitions: [ + { image: 'image2' }, + ], + }); + expect(mockUpdateService).toBeCalledWith({ + service: 'arn:aws:ecs:region:account:service/my-cluster/my-service', + cluster: 'my-cluster', + taskDefinition: 'arn:aws:ecs:region:account:task-definition/my-task-def:3', + deploymentConfiguration: { + minimumHealthyPercent: settings.get(['hotswap']).ecs?.minimumHealthyPercent == undefined ? + 0 : settings.get(['hotswap']).ecs?.minimumHealthyPercent, + maximumPercent: settings.get(['hotswap']).ecs?.maximumHealthyPercent, + }, + forceNewDeployment: true, + }); + }); +}); diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index 505f128a0dee2..1288c827f2300 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -4,7 +4,7 @@ import * as codebuild from 'aws-sdk/clients/codebuild'; import * as lambda from 'aws-sdk/clients/lambda'; import * as stepfunctions from 'aws-sdk/clients/stepfunctions'; import { DeployStackResult } from '../../../lib/api'; -import { HotswapMode } from '../../../lib/api/hotswap/common'; +import { HotswapMode, HotswapPropertyOverrides } from '../../../lib/api/hotswap/common'; import * as deployments from '../../../lib/api/hotswap-deployments'; import { CloudFormationStack, Template } from '../../../lib/api/util/cloudformation'; import { testStack, TestStackArtifact } from '../../util'; @@ -179,7 +179,9 @@ export class HotswapMockSdkProvider { hotswapMode: HotswapMode, stackArtifact: cxapi.CloudFormationStackArtifact, assetParams: { [key: string]: string } = {}, + hotswapPropertyOverrides?: HotswapPropertyOverrides, ): Promise { - return deployments.tryHotswapDeployment(this.mockSdkProvider, assetParams, currentCfnStack, stackArtifact, hotswapMode); + let hotswapProps = hotswapPropertyOverrides || new HotswapPropertyOverrides(); + return deployments.tryHotswapDeployment(this.mockSdkProvider, assetParams, currentCfnStack, stackArtifact, hotswapMode, hotswapProps); } }