From e01faffcf7228fd1a7632ff32617c77547bd8c7b Mon Sep 17 00:00:00 2001 From: amine-mf <121807940+amine-mf@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:00:15 +0200 Subject: [PATCH] feat(cli-lib-alpha): support hotswap deployments (#26786) Closes #26785. Exemption Request: The `cli-lib-alpha` README does not currently specify the exhaustive list of arguments, which looks like a choice, thus I did not add the new argument. Currently, no integration tests were implemented for this lib, only unit tests which I did update/add. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/cli-lib-alpha/lib/cli.ts | 14 +++++- .../cli-lib-alpha/lib/commands/deploy.ts | 26 ++++++++++ .../cli-lib-alpha/test/commands.test.ts | 49 +++++++++++++++++-- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts b/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts index cde003aa42eed..d17a8afa87f29 100644 --- a/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts +++ b/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts @@ -2,7 +2,7 @@ import { exec as runCli } from 'aws-cdk/lib'; // eslint-disable-next-line import/no-extraneous-dependencies import { createAssembly, prepareContext, prepareDefaultEnvironment } from 'aws-cdk/lib/api/cxapp/exec'; -import { SharedOptions, DeployOptions, DestroyOptions, BootstrapOptions, SynthOptions, ListOptions, StackActivityProgress } from './commands'; +import { SharedOptions, DeployOptions, DestroyOptions, BootstrapOptions, SynthOptions, ListOptions, StackActivityProgress, HotswapMode } from './commands'; /** * AWS CDK CLI operations @@ -211,6 +211,7 @@ export class AwsCdkCli implements IAwsCdkCli { ...renderBooleanArg('asset-parallelism', options.assetParallelism), ...renderBooleanArg('asset-prebuild', options.assetPrebuild), ...renderNumberArg('concurrency', options.concurrency), + ...renderHotswapArg(options.hotswap), ...options.reuseAssets ? renderArrayArg('--reuse-assets', options.reuseAssets) : [], ...options.notificationArns ? renderArrayArg('--notification-arns', options.notificationArns) : [], ...options.parameters ? renderMapArrayArg('--parameters', options.parameters) : [], @@ -267,6 +268,17 @@ export class AwsCdkCli implements IAwsCdkCli { } } +function renderHotswapArg(hotswapMode: HotswapMode | undefined): string[] { + switch (hotswapMode) { + case HotswapMode.FALL_BACK: + return ['--hotswap-fallback']; + case HotswapMode.HOTSWAP_ONLY: + return ['--hotswap']; + default: + return []; + } +} + function renderMapArrayArg(flag: string, parameters: { [name: string]: string | undefined }): string[] { const params: string[] = []; for (const [key, value] of Object.entries(parameters)) { diff --git a/packages/@aws-cdk/cli-lib-alpha/lib/commands/deploy.ts b/packages/@aws-cdk/cli-lib-alpha/lib/commands/deploy.ts index 6af92236aa813..4af8da8a92d1f 100644 --- a/packages/@aws-cdk/cli-lib-alpha/lib/commands/deploy.ts +++ b/packages/@aws-cdk/cli-lib-alpha/lib/commands/deploy.ts @@ -1,5 +1,22 @@ import { SharedOptions, RequireApproval } from './common'; +export enum HotswapMode { + /** + * Will fall back to CloudFormation when a non-hotswappable change is detected + */ + FALL_BACK = 'fall-back', + + /** + * Will not fall back to CloudFormation when a non-hotswappable change is detected + */ + HOTSWAP_ONLY = 'hotswap-only', + + /** + * Will not attempt to hotswap anything and instead go straight to CloudFormation + */ + FULL_DEPLOYMENT = 'full-deployment', +} + /** * Options to use with cdk deploy */ @@ -68,6 +85,15 @@ export interface DeployOptions extends SharedOptions { */ readonly execute?: boolean; + /* + * Whether to perform a 'hotswap' deployment. + * A 'hotswap' deployment will attempt to short-circuit CloudFormation + * and update the affected resources like Lambda functions directly. Do not use this in production environments + * + * @default - `HotswapMode.FULL_DEPLOYMENT` for regular deployments, `HotswapMode.HOTSWAP_ONLY` for 'watch' deployments + */ + readonly hotswap?: HotswapMode; + /** * Additional parameters for CloudFormation at deploy time * @default {} diff --git a/packages/@aws-cdk/cli-lib-alpha/test/commands.test.ts b/packages/@aws-cdk/cli-lib-alpha/test/commands.test.ts index bc2474dc9a24e..710bc278b2ff4 100644 --- a/packages/@aws-cdk/cli-lib-alpha/test/commands.test.ts +++ b/packages/@aws-cdk/cli-lib-alpha/test/commands.test.ts @@ -1,7 +1,7 @@ import * as core from 'aws-cdk-lib/core'; import * as cli from 'aws-cdk/lib'; import { AwsCdkCli } from '../lib'; -import { RequireApproval, StackActivityProgress } from '../lib/commands'; +import { HotswapMode, RequireApproval, StackActivityProgress } from '../lib/commands'; jest.mock('aws-cdk/lib'); jest.mocked(cli.exec).mockResolvedValue(0); @@ -23,7 +23,7 @@ const cdk = AwsCdkCli.fromCloudAssemblyDirectoryProducer({ describe('deploy', () => { test('default deploy', async () => { // WHEN - await await cdk.deploy(); + await cdk.deploy(); // THEN expect(jest.mocked(cli.exec)).toHaveBeenCalledWith( @@ -34,7 +34,7 @@ describe('deploy', () => { test('deploy with all arguments', async () => { // WHEN - await await cdk.deploy({ + await cdk.deploy({ stacks: ['Stack1'], ci: false, json: true, @@ -45,6 +45,7 @@ describe('deploy', () => { trace: false, strict: false, execute: true, + hotswap: HotswapMode.HOTSWAP_ONLY, lookups: false, notices: true, profile: 'my-profile', @@ -98,6 +99,7 @@ describe('deploy', () => { '--no-strict', '--no-trace', '--no-lookups', + '--hotswap', '--no-ignore-errors', '--json', '--verbose', @@ -118,9 +120,46 @@ describe('deploy', () => { ); }); + test('can parse hotswap-fallback argument', async () => { + // WHEN + await cdk.deploy({ + stacks: ['Stack1'], + hotswap: HotswapMode.FALL_BACK, + }); + + // THEN + expect(jest.mocked(cli.exec)).toHaveBeenCalledWith( + [ + 'deploy', + '--hotswap-fallback', + '--progress', 'events', + 'Stack1', + ], + expect.anything(), + ); + }); + + test('skip hotswap full-deployment argument', async () => { + // WHEN + await cdk.deploy({ + stacks: ['Stack1'], + hotswap: HotswapMode.FULL_DEPLOYMENT, + }); + + // THEN + expect(jest.mocked(cli.exec)).toHaveBeenCalledWith( + [ + 'deploy', + '--progress', 'events', + 'Stack1', + ], + expect.anything(), + ); + }); + test('can parse boolean arguments', async () => { // WHEN - await await cdk.deploy({ + await cdk.deploy({ stacks: ['Stack1'], json: true, color: false, @@ -141,7 +180,7 @@ describe('deploy', () => { test('can parse parameters', async() => { // WHEN - await await cdk.deploy({ + await cdk.deploy({ stacks: ['Stack1'], parameters: { 'myparam': 'test',