From d48d77aa1adbddf27c94e279ee756bdc2c2d18cf Mon Sep 17 00:00:00 2001 From: Sumu Pitchayan <35242245+sumupitchayan@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:18:06 -0500 Subject: [PATCH 1/9] chore(cli): use typed errors `ToolkitError` and `AuthenticationError` in CLI (#32548) Closes #32347 This PR creates two new error types, `ToolkitError` and `AuthenticationError` and uses them in `aws-cdk`. ### 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* --------- Signed-off-by: Sumu Co-authored-by: Momo Kornher --- .../lib/api/aws-auth/awscli-compatible.ts | 3 +- .../lib/api/aws-auth/credential-plugins.ts | 5 +- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 9 ++-- packages/aws-cdk/lib/api/aws-auth/sdk.ts | 5 +- .../api/bootstrap/bootstrap-environment.ts | 19 ++++---- .../aws-cdk/lib/api/cxapp/cloud-assembly.ts | 15 +++--- .../aws-cdk/lib/api/cxapp/cloud-executable.ts | 5 +- .../aws-cdk/lib/api/cxapp/environments.ts | 5 +- packages/aws-cdk/lib/api/cxapp/exec.ts | 13 ++--- .../garbage-collection/garbage-collector.ts | 13 ++--- .../api/garbage-collection/stack-refresh.ts | 5 +- .../api/hotswap/appsync-mapping-templates.ts | 3 +- packages/aws-cdk/lib/api/hotswap/common.ts | 5 +- .../lib/api/hotswap/lambda-functions.ts | 3 +- packages/aws-cdk/lib/api/plugin/plugin.ts | 9 ++-- packages/aws-cdk/lib/assets.ts | 9 ++-- packages/aws-cdk/lib/build.ts | 3 +- packages/aws-cdk/lib/cdk-toolkit.ts | 43 +++++++++-------- packages/aws-cdk/lib/cli.ts | 17 +++---- packages/aws-cdk/lib/diff.ts | 3 +- packages/aws-cdk/lib/import.ts | 3 +- packages/aws-cdk/lib/init-hooks.ts | 3 +- packages/aws-cdk/lib/init.ts | 11 +++-- packages/aws-cdk/lib/notices.ts | 15 +++--- packages/aws-cdk/lib/os.ts | 3 +- packages/aws-cdk/lib/settings.ts | 21 ++++---- packages/aws-cdk/lib/toolkit/error.ts | 48 +++++++++++++++++++ packages/aws-cdk/lib/version.ts | 3 +- packages/aws-cdk/test/toolkit-error.test.ts | 17 +++++++ 29 files changed, 204 insertions(+), 112 deletions(-) create mode 100644 packages/aws-cdk/lib/toolkit/error.ts create mode 100644 packages/aws-cdk/test/toolkit-error.test.ts diff --git a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts index 31ef2640b5682..fd6f255d0523d 100644 --- a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts +++ b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts @@ -9,6 +9,7 @@ import { makeCachingProvider } from './provider-caching'; import type { SdkHttpOptions } from './sdk-provider'; import { readIfPossible } from './util'; import { debug } from '../../logging'; +import { AuthenticationError } from '../../toolkit/error'; const DEFAULT_CONNECTION_TIMEOUT = 10000; const DEFAULT_TIMEOUT = 300000; @@ -291,7 +292,7 @@ async function tokenCodeFn(serialArn: string): Promise { return token; } catch (err: any) { debug('Failed to get MFA token', err); - const e = new Error(`Error fetching MFA token: ${err.message ?? err}`); + const e = new AuthenticationError(`Error fetching MFA token: ${err.message ?? err}`); e.name = 'SharedIniFileCredentialsProviderFailure'; throw e; } diff --git a/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts b/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts index b2047bd3fbbfb..1c9e214eca067 100644 --- a/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts +++ b/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts @@ -3,6 +3,7 @@ import type { AwsCredentialIdentity, AwsCredentialIdentityProvider } from '@smit import { debug, warning } from '../../logging'; import { CredentialProviderSource, PluginProviderResult, Mode, PluginHost, SDKv2CompatibleCredentials, SDKv3CompatibleCredentialProvider, SDKv3CompatibleCredentials } from '../plugin'; import { credentialsAboutToExpire, makeCachingProvider } from './provider-caching'; +import { AuthenticationError } from '../../toolkit/error'; /** * Cache for credential providers. @@ -124,7 +125,7 @@ async function v3ProviderFromPlugin(producer: () => Promise>; @@ -158,14 +159,14 @@ export class SdkProvider { // At this point, we need at least SOME credentials if (baseCreds.source === 'none') { - throw new Error(fmtObtainCredentialsError(env.account, baseCreds)); + throw new AuthenticationError(fmtObtainCredentialsError(env.account, baseCreds)); } // Simple case is if we don't need to "assumeRole" here. If so, we must now have credentials for the right // account. if (options?.assumeRoleArn === undefined) { if (baseCreds.source === 'incorrectDefault') { - throw new Error(fmtObtainCredentialsError(env.account, baseCreds)); + throw new AuthenticationError(fmtObtainCredentialsError(env.account, baseCreds)); } // Our current credentials must be valid and not expired. Confirm that before we get into doing @@ -240,7 +241,7 @@ export class SdkProvider { const account = env.account !== UNKNOWN_ACCOUNT ? env.account : (await this.defaultAccount())?.accountId; if (!account) { - throw new Error( + throw new AuthenticationError( 'Unable to resolve AWS account to use. It must be either configured when you define your CDK Stack, or through the environment', ); } @@ -377,7 +378,7 @@ export class SdkProvider { } debug(`Assuming role failed: ${err.message}`); - throw new Error( + throw new AuthenticationError( [ 'Could not assume role in target account', ...(sourceDescription ? [`using ${sourceDescription}`] : []), diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index 1b1c771987b7f..722f946623fad 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -319,6 +319,7 @@ import { cachedAsync } from './cached'; import { Account } from './sdk-provider'; import { defaultCliUserAgent } from './user-agent'; import { debug } from '../../logging'; +import { AuthenticationError } from '../../toolkit/error'; import { traceMethods } from '../../util/tracing'; export interface S3ClientOptions { @@ -902,7 +903,7 @@ export class SDK { return upload.done(); } catch (e: any) { - throw new Error(`Upload failed: ${e.message}`); + throw new AuthenticationError(`Upload failed: ${e.message}`); } }, }; @@ -957,7 +958,7 @@ export class SDK { const accountId = result.Account; const partition = result.Arn!.split(':')[1]; if (!accountId) { - throw new Error("STS didn't return an account ID"); + throw new AuthenticationError("STS didn't return an account ID"); } debug('Default account ID:', accountId); diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts index 6c28af3c019b7..bb221c6f52869 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -6,6 +6,7 @@ import { BootstrapStack, bootstrapVersionFromTemplate } from './deploy-bootstrap import { legacyBootstrapTemplate } from './legacy-template'; import { warning } from '../../logging'; import { loadStructuredFile, serializeStructure } from '../../serialize'; +import { ToolkitError } from '../../toolkit/error'; import { rootDir } from '../../util/directories'; import type { SDK, SdkProvider } from '../aws-auth'; import type { SuccessfulDeployStackResult } from '../deploy-stack'; @@ -48,16 +49,16 @@ export class Bootstrapper { const params = options.parameters ?? {}; if (params.trustedAccounts?.length) { - throw new Error('--trust can only be passed for the modern bootstrap experience.'); + throw new ToolkitError('--trust can only be passed for the modern bootstrap experience.'); } if (params.cloudFormationExecutionPolicies?.length) { - throw new Error('--cloudformation-execution-policies can only be passed for the modern bootstrap experience.'); + throw new ToolkitError('--cloudformation-execution-policies can only be passed for the modern bootstrap experience.'); } if (params.createCustomerMasterKey !== undefined) { - throw new Error('--bootstrap-customer-key can only be passed for the modern bootstrap experience.'); + throw new ToolkitError('--bootstrap-customer-key can only be passed for the modern bootstrap experience.'); } if (params.qualifier) { - throw new Error('--qualifier can only be passed for the modern bootstrap experience.'); + throw new ToolkitError('--qualifier can only be passed for the modern bootstrap experience.'); } const current = await BootstrapStack.lookup(sdkProvider, environment, options.toolkitStackName); @@ -88,7 +89,7 @@ export class Bootstrapper { const partition = await current.partition(); if (params.createCustomerMasterKey !== undefined && params.kmsKeyId) { - throw new Error( + throw new ToolkitError( "You cannot pass '--bootstrap-kms-key-id' and '--bootstrap-customer-key' together. Specify one or the other", ); } @@ -131,7 +132,7 @@ export class Bootstrapper { `Using default execution policy of '${implicitPolicy}'. Pass '--cloudformation-execution-policies' to customize.`, ); } else if (cloudFormationExecutionPolicies.length === 0) { - throw new Error( + throw new ToolkitError( `Please pass \'--cloudformation-execution-policies\' when using \'--trust\' to specify deployment permissions. Try a managed policy of the form \'arn:${partition}:iam::aws:policy/\'.`, ); } else { @@ -226,7 +227,7 @@ export class Bootstrapper { ); const policyName = arn.split('/').pop(); if (!policyName) { - throw new Error('Could not retrieve the example permission boundary!'); + throw new ToolkitError('Could not retrieve the example permission boundary!'); } return Promise.resolve(policyName); } @@ -308,7 +309,7 @@ export class Bootstrapper { if (createPolicyResponse.Policy?.Arn) { return createPolicyResponse.Policy.Arn; } else { - throw new Error(`Could not retrieve the example permission boundary ${arn}!`); + throw new ToolkitError(`Could not retrieve the example permission boundary ${arn}!`); } } @@ -319,7 +320,7 @@ export class Bootstrapper { const regexp: RegExp = /[\w+\/=,.@-]+/; const matches = regexp.exec(permissionsBoundary); if (!(matches && matches.length === 1 && matches[0] === permissionsBoundary)) { - throw new Error(`The permissions boundary name ${permissionsBoundary} does not match the IAM conventions.`); + throw new ToolkitError(`The permissions boundary name ${permissionsBoundary} does not match the IAM conventions.`); } } diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts index 96843b706b005..322508881321d 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts @@ -3,6 +3,7 @@ import * as chalk from 'chalk'; import { minimatch } from 'minimatch'; import * as semver from 'semver'; import { error, print, warning } from '../../logging'; +import { ToolkitError } from '../../toolkit/error'; import { flatten } from '../../util'; export enum DefaultSelection { @@ -109,7 +110,7 @@ export class CloudAssembly { if (options.ignoreNoStacks) { return new StackCollection(this, []); } - throw new Error('This app contains no stacks'); + throw new ToolkitError('This app contains no stacks'); } if (allTopLevel) { @@ -129,7 +130,7 @@ export class CloudAssembly { if (topLevelStacks.length > 0) { return this.extendStacks(topLevelStacks, stacks, extend); } else { - throw new Error('No stack found in the main cloud assembly. Use "list" to print manifest'); + throw new ToolkitError('No stack found in the main cloud assembly. Use "list" to print manifest'); } } @@ -161,11 +162,11 @@ export class CloudAssembly { if (topLevelStacks.length === 1) { return new StackCollection(this, topLevelStacks); } else { - throw new Error('Since this app includes more than a single stack, specify which stacks to use (wildcards are supported) or specify `--all`\n' + + throw new ToolkitError('Since this app includes more than a single stack, specify which stacks to use (wildcards are supported) or specify `--all`\n' + `Stacks: ${stacks.map(x => x.hierarchicalId).join(' · ')}`); } default: - throw new Error(`invalid default behavior: ${defaultSelection}`); + throw new ToolkitError(`invalid default behavior: ${defaultSelection}`); } } @@ -221,7 +222,7 @@ export class StackCollection { public get firstStack() { if (this.stackCount < 1) { - throw new Error('StackCollection contains no stack artifacts (trying to access the first one)'); + throw new ToolkitError('StackCollection contains no stack artifacts (trying to access the first one)'); } return this.stackArtifacts[0]; } @@ -270,11 +271,11 @@ export class StackCollection { } if (errors && !options.ignoreErrors) { - throw new Error('Found errors'); + throw new ToolkitError('Found errors'); } if (options.strict && warnings) { - throw new Error('Found warnings (--strict mode)'); + throw new ToolkitError('Found warnings (--strict mode)'); } function printMessage(logFn: (s: string) => void, prefix: string, id: string, entry: cxapi.MetadataEntry) { diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts index fd97007c1e22d..6fc24758d3dce 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts @@ -6,6 +6,7 @@ import { CloudAssembly } from './cloud-assembly'; import * as contextproviders from '../../context-providers'; import { debug, warning } from '../../logging'; import { Configuration } from '../../settings'; +import { ToolkitError } from '../../toolkit/error'; import { SdkProvider } from '../aws-auth'; /** @@ -82,7 +83,7 @@ export class CloudExecutable { const missingKeys = missingContextKeys(assembly.manifest.missing); if (!this.canLookup) { - throw new Error( + throw new ToolkitError( 'Context lookups have been disabled. ' + 'Make sure all necessary context is already in \'cdk.context.json\' by running \'cdk synth\' on a machine with sufficient AWS credentials and committing the result. ' + `Missing context keys: '${Array.from(missingKeys).join(', ')}'`); @@ -214,7 +215,7 @@ function _makeCdkMetadataAvailableCondition() { */ function _fnOr(operands: any[]): any { if (operands.length === 0) { - throw new Error('Cannot build `Fn::Or` with zero operands!'); + throw new ToolkitError('Cannot build `Fn::Or` with zero operands!'); } if (operands.length === 1) { return operands[0]; diff --git a/packages/aws-cdk/lib/api/cxapp/environments.ts b/packages/aws-cdk/lib/api/cxapp/environments.ts index c46281759a881..a1b8c66e6f442 100644 --- a/packages/aws-cdk/lib/api/cxapp/environments.ts +++ b/packages/aws-cdk/lib/api/cxapp/environments.ts @@ -1,6 +1,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import { minimatch } from 'minimatch'; import { StackCollection } from './cloud-assembly'; +import { ToolkitError } from '../../toolkit/error'; import { SdkProvider } from '../aws-auth'; export function looksLikeGlob(environment: string) { @@ -21,7 +22,7 @@ export async function globEnvironmentsFromStacks(stacks: StackCollection, enviro if (environments.length === 0) { const globs = JSON.stringify(environmentGlobs); const envList = availableEnvironments.length > 0 ? availableEnvironments.map(env => env!.name).join(', ') : ''; - throw new Error(`No environments were found when selecting across ${globs} (available: ${envList})`); + throw new ToolkitError(`No environments were found when selecting across ${globs} (available: ${envList})`); } return environments; @@ -36,7 +37,7 @@ export function environmentsFromDescriptors(envSpecs: string[]): cxapi.Environme for (const spec of envSpecs) { const parts = spec.replace(/^aws:\/\//, '').split('/'); if (parts.length !== 2) { - throw new Error(`Expected environment name in format 'aws:///', got: ${spec}`); + throw new ToolkitError(`Expected environment name in format 'aws:///', got: ${spec}`); } ret.push({ diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 6b62d7ae2527f..b02ad38445a07 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -7,6 +7,7 @@ import * as fs from 'fs-extra'; import * as semver from 'semver'; import { debug, warning } from '../../logging'; import { Configuration, PROJECT_CONFIG, USER_DEFAULTS } from '../../settings'; +import { ToolkitError } from '../../toolkit/error'; import { loadTree, some } from '../../tree'; import { splitBySize } from '../../util/objects'; import { versionNumber } from '../../version'; @@ -30,7 +31,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom const app = config.settings.get(['app']); if (!app) { - throw new Error(`--app is required either in command-line, in ${PROJECT_CONFIG} or in ${USER_DEFAULTS}`); + throw new ToolkitError(`--app is required either in command-line, in ${PROJECT_CONFIG} or in ${USER_DEFAULTS}`); } // bypass "synth" if app points to a cloud assembly @@ -47,15 +48,15 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom const outdir = config.settings.get(['output']); if (!outdir) { - throw new Error('unexpected: --output is required'); + throw new ToolkitError('unexpected: --output is required'); } if (typeof outdir !== 'string') { - throw new Error(`--output takes a string, got ${JSON.stringify(outdir)}`); + throw new ToolkitError(`--output takes a string, got ${JSON.stringify(outdir)}`); } try { await fs.mkdirp(outdir); } catch (error: any) { - throw new Error(`Could not create output directory ${outdir} (${error.message})`); + throw new ToolkitError(`Could not create output directory ${outdir} (${error.message})`); } debug('outdir:', outdir); @@ -127,7 +128,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom return ok(); } else { debug('failed command:', commandAndArgs); - return fail(new Error(`Subprocess exited with error ${code}`)); + return fail(new ToolkitError(`Subprocess exited with error ${code}`)); } }); }); @@ -147,7 +148,7 @@ export function createAssembly(appDir: string) { if (error.message.includes(cxschema.VERSION_MISMATCH)) { // this means the CLI version is too old. // we instruct the user to upgrade. - throw new Error(`This CDK CLI is not compatible with the CDK library used by your application. Please upgrade the CLI to the latest version.\n(${error.message})`); + throw new ToolkitError(`This CDK CLI is not compatible with the CDK library used by your application. Please upgrade the CLI to the latest version.\n(${error.message})`); } throw error; } diff --git a/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts b/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts index a73e44e25c16a..19441be95f788 100644 --- a/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts +++ b/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts @@ -8,6 +8,7 @@ import { IECRClient, IS3Client, SDK, SdkProvider } from '../aws-auth'; import { DEFAULT_TOOLKIT_STACK_NAME, ToolkitInfo } from '../toolkit-info'; import { ProgressPrinter } from './progress-printer'; import { ActiveAssetCache, BackgroundStackRefresh, refreshStacks } from './stack-refresh'; +import { ToolkitError } from '../../toolkit/error'; import { Mode } from '../plugin'; // Must use a require() otherwise esbuild complains @@ -91,14 +92,14 @@ export class ObjectAsset { private getTag(tag: string) { if (!this.cached_tags) { - throw new Error('Cannot call getTag before allTags'); + throw new ToolkitError('Cannot call getTag before allTags'); } return this.cached_tags.find((t: any) => t.Key === tag)?.Value; } private hasTag(tag: string) { if (!this.cached_tags) { - throw new Error('Cannot call hasTag before allTags'); + throw new ToolkitError('Cannot call hasTag before allTags'); } return this.cached_tags.some((t: any) => t.Key === tag); } @@ -225,7 +226,7 @@ export class GarbageCollector { await this.garbageCollectEcr(sdk, activeAssets, backgroundStackRefresh); } } catch (err: any) { - throw new Error(err); + throw new ToolkitError(err); } finally { backgroundStackRefresh.stop(); } @@ -298,7 +299,7 @@ export class GarbageCollector { printer.reportScannedAsset(batch.length); } } catch (err: any) { - throw new Error(err); + throw new ToolkitError(err); } finally { printer.stop(); } @@ -374,7 +375,7 @@ export class GarbageCollector { printer.reportScannedAsset(batch.length); } } catch (err: any) { - throw new Error(err); + throw new ToolkitError(err); } finally { printer.stop(); } @@ -727,7 +728,7 @@ export class GarbageCollector { // Anything other than yes/y/delete-all is treated as no if (!response || !['yes', 'y', 'delete-all'].includes(response.toLowerCase())) { - throw new Error('Deletion aborted by user'); + throw new ToolkitError('Deletion aborted by user'); } else if (response.toLowerCase() == 'delete-all') { this.confirm = false; } diff --git a/packages/aws-cdk/lib/api/garbage-collection/stack-refresh.ts b/packages/aws-cdk/lib/api/garbage-collection/stack-refresh.ts index 8fdfb4e3a2562..2a795e23757d5 100644 --- a/packages/aws-cdk/lib/api/garbage-collection/stack-refresh.ts +++ b/packages/aws-cdk/lib/api/garbage-collection/stack-refresh.ts @@ -1,5 +1,6 @@ import { ParameterDeclaration } from '@aws-sdk/client-cloudformation'; import { debug } from '../../logging'; +import { ToolkitError } from '../../toolkit/error'; import { ICloudFormationClient } from '../aws-auth'; export class ActiveAssetCache { @@ -103,7 +104,7 @@ export async function refreshStacks(cfn: ICloudFormationClient, activeAssets: Ac activeAssets.rememberStack(stack); } } catch (err) { - throw new Error(`Error refreshing stacks: ${err}`); + throw new ToolkitError(`Error refreshing stacks: ${err}`); } } @@ -180,7 +181,7 @@ export class BackgroundStackRefresh { // We will wait for the latest refresh to land or reject if it takes too long return Promise.race([ new Promise(resolve => this.queuedPromises.push(resolve)), - new Promise((_, reject) => setTimeout(() => reject(new Error('refreshStacks took too long; the background thread likely threw an error')), ms)), + new Promise((_, reject) => setTimeout(() => reject(new ToolkitError('refreshStacks took too long; the background thread likely threw an error')), ms)), ]); } diff --git a/packages/aws-cdk/lib/api/hotswap/appsync-mapping-templates.ts b/packages/aws-cdk/lib/api/hotswap/appsync-mapping-templates.ts index d5b520d6a13e5..3218bc5fc3157 100644 --- a/packages/aws-cdk/lib/api/hotswap/appsync-mapping-templates.ts +++ b/packages/aws-cdk/lib/api/hotswap/appsync-mapping-templates.ts @@ -9,6 +9,7 @@ import { lowerCaseFirstCharacter, transformObjectKeys, } from './common'; +import { ToolkitError } from '../../toolkit/error'; import type { SDK } from '../aws-auth'; import type { EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template'; @@ -143,7 +144,7 @@ export async function isHotswappableAppSyncChange( schemaCreationResponse = await sdk.appsync().getSchemaCreationStatus(getSchemaCreationStatusRequest); } if (schemaCreationResponse.status === 'FAILED') { - throw new Error(schemaCreationResponse.details); + throw new ToolkitError(schemaCreationResponse.details ?? 'Schema creation has failed.'); } } else { //isApiKey diff --git a/packages/aws-cdk/lib/api/hotswap/common.ts b/packages/aws-cdk/lib/api/hotswap/common.ts index ed176791f28dd..4012c119d5130 100644 --- a/packages/aws-cdk/lib/api/hotswap/common.ts +++ b/packages/aws-cdk/lib/api/hotswap/common.ts @@ -1,4 +1,5 @@ import type { PropertyDifference, Resource } from '@aws-cdk/cloudformation-diff'; +import { ToolkitError } from '../../toolkit/error'; import type { SDK } from '../aws-auth'; export const ICON = '✨'; @@ -121,10 +122,10 @@ export class EcsHotswapProperties { 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'); + throw new ToolkitError('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'); + throw new ToolkitError('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) { diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index e927ad03cac8f..64ec5a53465ae 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -1,6 +1,7 @@ import { Writable } from 'stream'; import { type FunctionConfiguration, type UpdateFunctionConfigurationCommandInput } from '@aws-sdk/client-lambda'; import { type ChangeHotswapResult, classifyChanges, type HotswappableChangeCandidate, PropDiffs } from './common'; +import { ToolkitError } from '../../toolkit/error'; import { flatMap } from '../../util'; import type { ILambdaClient, SDK } from '../aws-auth'; import { CfnEvaluationException, type EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template'; @@ -245,7 +246,7 @@ async function evaluateLambdaFunctionProps( break; default: // we will never get here, but just in case we do throw an error - throw new Error( + throw new ToolkitError( 'while apply()ing, found a property that cannot be hotswapped. Please report this at github.com/aws/aws-cdk/issues/new/choose', ); } diff --git a/packages/aws-cdk/lib/api/plugin/plugin.ts b/packages/aws-cdk/lib/api/plugin/plugin.ts index 16df596e4cccb..270c8343be404 100644 --- a/packages/aws-cdk/lib/api/plugin/plugin.ts +++ b/packages/aws-cdk/lib/api/plugin/plugin.ts @@ -4,6 +4,7 @@ import * as chalk from 'chalk'; import { type ContextProviderPlugin, isContextProviderPlugin } from './context-provider-plugin'; import type { CredentialProviderSource } from './credential-provider-source'; import { error } from '../../logging'; +import { ToolkitError } from '../../toolkit/error'; export let TESTING = false; @@ -59,7 +60,7 @@ export class PluginHost { constructor() { if (!TESTING && PluginHost.instance && PluginHost.instance !== this) { - throw new Error('New instances of PluginHost must not be built. Use PluginHost.instance instead!'); + throw new ToolkitError('New instances of PluginHost must not be built. Use PluginHost.instance instead!'); } } @@ -75,14 +76,14 @@ export class PluginHost { /* eslint-enable */ if (!isPlugin(plugin)) { error(`Module ${chalk.green(moduleSpec)} is not a valid plug-in, or has an unsupported version.`); - throw new Error(`Module ${moduleSpec} does not define a valid plug-in.`); + throw new ToolkitError(`Module ${moduleSpec} does not define a valid plug-in.`); } if (plugin.init) { plugin.init(this); } } catch (e: any) { error(`Unable to load ${chalk.green(moduleSpec)}: ${e.stack}`); - throw new Error(`Unable to load plug-in: ${moduleSpec}: ${e}`); + throw new ToolkitError(`Unable to load plug-in: ${moduleSpec}: ${e}`); } function isPlugin(x: any): x is Plugin { @@ -134,7 +135,7 @@ export class PluginHost { */ public registerContextProviderAlpha(pluginProviderName: string, provider: ContextProviderPlugin) { if (!isContextProviderPlugin(provider)) { - throw new Error(`Object you gave me does not look like a ContextProviderPlugin: ${inspect(provider)}`); + throw new ToolkitError(`Object you gave me does not look like a ContextProviderPlugin: ${inspect(provider)}`); } this.contextProviderPlugins[pluginProviderName] = provider; } diff --git a/packages/aws-cdk/lib/assets.ts b/packages/aws-cdk/lib/assets.ts index 317e8c3f34272..e1bdd5ce4fb3c 100644 --- a/packages/aws-cdk/lib/assets.ts +++ b/packages/aws-cdk/lib/assets.ts @@ -6,6 +6,7 @@ import * as chalk from 'chalk'; import { EnvironmentResources } from './api/environment-resources'; import { ToolkitInfo } from './api/toolkit-info'; import { debug } from './logging'; +import { ToolkitError } from './toolkit/error'; import { AssetManifestBuilder } from './util/asset-manifest-builder'; /** @@ -26,7 +27,7 @@ export async function addMetadataAssetsToManifest(stack: cxapi.CloudFormationSta const toolkitInfo = await envResources.lookupToolkit(); if (!toolkitInfo.found) { // eslint-disable-next-line max-len - throw new Error(`This stack uses assets, so the toolkit stack must be deployed to the environment (Run "${chalk.blue('cdk bootstrap ' + stack.environment!.name)}")`); + throw new ToolkitError(`This stack uses assets, so the toolkit stack must be deployed to the environment (Run "${chalk.blue('cdk bootstrap ' + stack.environment!.name)}")`); } const params: Record = {}; @@ -43,7 +44,7 @@ export async function addMetadataAssetsToManifest(stack: cxapi.CloudFormationSta debug(`Preparing asset ${asset.id}: ${JSON.stringify(asset)}`); if (!stack.assembly) { - throw new Error('Unexpected: stack assembly is required in order to find assets in assembly directory'); + throw new ToolkitError('Unexpected: stack assembly is required in order to find assets in assembly directory'); } Object.assign(params, await prepareAsset(asset, assetManifest, envResources, toolkitInfo)); @@ -66,7 +67,7 @@ async function prepareAsset(asset: cxschema.AssetMetadataEntry, assetManifest: A return prepareDockerImageAsset(asset, assetManifest, envResources); default: // eslint-disable-next-line max-len - throw new Error(`Unsupported packaging type: ${(asset as any).packaging}. You might need to upgrade your aws-cdk toolkit to support this asset type.`); + throw new ToolkitError(`Unsupported packaging type: ${(asset as any).packaging}. You might need to upgrade your aws-cdk toolkit to support this asset type.`); } } @@ -110,7 +111,7 @@ async function prepareDockerImageAsset( // Post-1.21.0, repositoryName will always be specified and it will be a shared repository between // all assets, and asset will have imageTag specified as well. Validate the combination. if (!asset.imageNameParameter && (!asset.repositoryName || !asset.imageTag)) { - throw new Error('Invalid Docker image asset configuration: "repositoryName" and "imageTag" are required when "imageNameParameter" is left out'); + throw new ToolkitError('Invalid Docker image asset configuration: "repositoryName" and "imageTag" are required when "imageNameParameter" is left out'); } const repositoryName = asset.repositoryName ?? 'cdk/' + asset.id.replace(/[:/]/g, '-').toLowerCase(); diff --git a/packages/aws-cdk/lib/build.ts b/packages/aws-cdk/lib/build.ts index 93440f0861bd1..974db182ccdfe 100644 --- a/packages/aws-cdk/lib/build.ts +++ b/packages/aws-cdk/lib/build.ts @@ -1,4 +1,5 @@ import * as cxapi from '@aws-cdk/cx-api'; +import { ToolkitError } from './toolkit/error'; type Options = { buildStackAssets: (stack: cxapi.CloudFormationStackArtifact) => Promise; @@ -18,6 +19,6 @@ export async function buildAllStackAssets(stacks: cxapi.CloudFormationStackArtif } if (buildingErrors.length) { - throw Error(`Building Assets Failed: ${buildingErrors.join(', ')}`); + throw new ToolkitError(`Building Assets Failed: ${buildingErrors.join(', ')}`); } } diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 34c766efdd519..32f6616e5cdb6 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -48,6 +48,7 @@ import { listStacks } from './list-stacks'; import { data, debug, error, highlight, print, success, warning, withCorkedLogging } from './logging'; import { deserializeStructure, serializeStructure } from './serialize'; import { Configuration, PROJECT_CONFIG } from './settings'; +import { ToolkitError } from './toolkit/error'; import { numberFromBool, partition } from './util'; import { validateSnsTopicArn } from './util/validate-notification-arn'; import { Concurrency, WorkGraph } from './util/work-graph'; @@ -161,13 +162,13 @@ export class CdkToolkit { if (options.templatePath !== undefined) { // Compare single stack against fixed template if (stacks.stackCount !== 1) { - throw new Error( + throw new ToolkitError( 'Can only select one stack when comparing to fixed template. Use --exclusively to avoid selecting multiple stacks.', ); } if (!(await fs.pathExists(options.templatePath))) { - throw new Error(`There is no file at ${options.templatePath}`); + throw new ToolkitError(`There is no file at ${options.templatePath}`); } const template = deserializeStructure(await fs.readFile(options.templatePath, { encoding: 'UTF-8' })); @@ -336,7 +337,7 @@ export class CdkToolkit { if (!stack.environment) { // eslint-disable-next-line max-len - throw new Error( + throw new ToolkitError( `Stack ${stack.displayName} does not define an environment, and AWS credentials could not be obtained from standard locations or no region was configured.`, ); } @@ -381,7 +382,7 @@ export class CdkToolkit { for (const notificationArn of notificationArns ?? []) { if (!validateSnsTopicArn(notificationArn)) { - throw new Error(`Notification arn ${notificationArn} is not a valid arn for an SNS topic`); + throw new ToolkitError(`Notification arn ${notificationArn} is not a valid arn for an SNS topic`); } } @@ -402,7 +403,7 @@ export class CdkToolkit { let iteration = 0; while (!deployResult) { if (++iteration > 2) { - throw new Error('This loop should have stabilized in 2 iterations, but didn\'t. If you are seeing this error, please report it at https://github.com/aws/aws-cdk/issues/new/choose'); + throw new ToolkitError('This loop should have stabilized in 2 iterations, but didn\'t. If you are seeing this error, please report it at https://github.com/aws/aws-cdk/issues/new/choose'); } const r = await this.props.deployments.deployStack({ @@ -480,7 +481,7 @@ export class CdkToolkit { } default: - throw new Error(`Unexpected result type from deployStack: ${JSON.stringify(r)}. If you are seeing this error, please report it at https://github.com/aws/aws-cdk/issues/new/choose`); + throw new ToolkitError(`Unexpected result type from deployStack: ${JSON.stringify(r)}. If you are seeing this error, please report it at https://github.com/aws/aws-cdk/issues/new/choose`); } } @@ -509,7 +510,7 @@ export class CdkToolkit { } catch (e: any) { // It has to be exactly this string because an integration test tests for // "bold(stackname) failed: ResourceNotReady: " - throw new Error( + throw new ToolkitError( [`❌ ${chalk.bold(stack.stackName)} failed:`, ...(e.name ? [`${e.name}:`] : []), e.message].join(' '), ); } finally { @@ -603,11 +604,11 @@ export class CdkToolkit { print('\n✨ Rollback time: %ss\n', formatTime(elapsedRollbackTime)); } catch (e: any) { error('\n ❌ %s failed: %s', chalk.bold(stack.displayName), e.message); - throw new Error('Rollback failed (use --force to orphan failing resources)'); + throw new ToolkitError('Rollback failed (use --force to orphan failing resources)'); } } if (!anyRollbackable) { - throw new Error('No stacks were in a state that could be rolled back'); + throw new ToolkitError('No stacks were in a state that could be rolled back'); } } @@ -618,7 +619,7 @@ export class CdkToolkit { const watchSettings: { include?: string | string[]; exclude: string | string[] } | undefined = this.props.configuration.settings.get(['watch']); if (!watchSettings) { - throw new Error( + throw new ToolkitError( "Cannot use the 'watch' command without specifying at least one directory to monitor. " + 'Make sure to add a "watch" key to your cdk.json', ); @@ -716,13 +717,13 @@ export class CdkToolkit { const stacks = await this.selectStacksForDeploy(options.selector, true, true, false); if (stacks.stackCount > 1) { - throw new Error( + throw new ToolkitError( `Stack selection is ambiguous, please choose a specific stack for import [${stacks.stackArtifacts.map((x) => x.id).join(', ')}]`, ); } if (!process.stdout.isTTY && !options.resourceMappingFile) { - throw new Error('--resource-mapping is required when input is not a terminal'); + throw new ToolkitError('--resource-mapping is required when input is not a terminal'); } const stack = stacks.stackArtifacts[0]; @@ -980,12 +981,12 @@ export class CdkToolkit { if (globSpecs.length > 0 && !this.props.cloudExecutable.hasApp) { if (userEnvironmentSpecs.length > 0) { // User did request this glob - throw new Error( + throw new ToolkitError( `'${globSpecs}' is not an environment name. Specify an environment name like 'aws://123456789012/us-east-1', or run in a directory with 'cdk.json' to use wildcards.`, ); } else { // User did not request anything - throw new Error( + throw new ToolkitError( "Specify an environment name like 'aws://123456789012/us-east-1', or run in a directory with 'cdk.json'.", ); } @@ -1053,7 +1054,7 @@ export class CdkToolkit { } else if (scanType == TemplateSourceOptions.STACK) { const template = await readFromStack(options.stackName, this.props.sdkProvider, environment); if (!template) { - throw new Error(`No template found for stack-name: ${options.stackName}`); + throw new ToolkitError(`No template found for stack-name: ${options.stackName}`); } generateTemplateOutput = { migrateJson: { @@ -1063,7 +1064,7 @@ export class CdkToolkit { }; } else { // We shouldn't ever get here, but just in case. - throw new Error(`Invalid source option provided: ${scanType}`); + throw new ToolkitError(`Invalid source option provided: ${scanType}`); } const stack = generateStack(generateTemplateOutput.migrateJson.templateBody, options.stackName, language); success(' ⏳ Generating CDK app for %s...', chalk.blue(options.stackName)); @@ -1177,7 +1178,7 @@ export class CdkToolkit { */ private validateStacksSelected(stacks: StackCollection, stackNames: string[]) { if (stackNames.length != 0 && stacks.stackCount == 0) { - throw new Error(`No stacks match the name(s) ${stackNames}`); + throw new ToolkitError(`No stacks match the name(s) ${stackNames}`); } } @@ -1197,7 +1198,7 @@ export class CdkToolkit { // Could have been a glob so check that we evaluated to exactly one if (stacks.stackCount > 1) { - throw new Error(`This command requires exactly one stack and we matched more than one: ${stacks.stackIds}`); + throw new ToolkitError(`This command requires exactly one stack and we matched more than one: ${stacks.stackIds}`); } return assembly.stackById(stacks.firstStack.id); @@ -1939,15 +1940,15 @@ async function askUserConfirmation( await withCorkedLogging(async () => { // only talk to user if STDIN is a terminal (otherwise, fail) if (!TESTING && !process.stdin.isTTY) { - throw new Error(`${motivation}, but terminal (TTY) is not attached so we are unable to get a confirmation from the user`); + throw new ToolkitError(`${motivation}, but terminal (TTY) is not attached so we are unable to get a confirmation from the user`); } // only talk to user if concurrency is 1 (otherwise, fail) if (concurrency > 1) { - throw new Error(`${motivation}, but concurrency is greater than 1 so we are unable to get a confirmation from the user`); + throw new ToolkitError(`${motivation}, but concurrency is greater than 1 so we are unable to get a confirmation from the user`); } const confirmed = await promptly.confirm(`${chalk.cyan(question)} (y/n)?`); - if (!confirmed) { throw new Error('Aborted by user'); } + if (!confirmed) { throw new ToolkitError('Aborted by user'); } }); } diff --git a/packages/aws-cdk/lib/cli.ts b/packages/aws-cdk/lib/cli.ts index 2af03e05eb728..72344548aff9b 100644 --- a/packages/aws-cdk/lib/cli.ts +++ b/packages/aws-cdk/lib/cli.ts @@ -27,6 +27,7 @@ import { Notices } from '../lib/notices'; import { Command, Configuration, Settings } from '../lib/settings'; import * as version from '../lib/version'; import { SdkToCliLogger } from './api/aws-auth/sdk-logger'; +import { ToolkitError } from './toolkit/error'; /* eslint-disable max-len */ /* eslint-disable @typescript-eslint/no-shadow */ // yargs @@ -142,7 +143,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise Promise; @@ -85,6 +86,6 @@ async function dotnetAddProject(targetDirectory: string, context: HookContext, e try { await shell(['dotnet', 'sln', slnPath, 'add', csprojPath]); } catch (e: any) { - throw new Error(`Could not add project ${pname}.${ext} to solution ${pname}.sln. ${e.message}`); + throw new ToolkitError(`Could not add project ${pname}.${ext} to solution ${pname}.sln. ${e.message}`); } }; diff --git a/packages/aws-cdk/lib/init.ts b/packages/aws-cdk/lib/init.ts index c6801b0229554..d1e61b8173c42 100644 --- a/packages/aws-cdk/lib/init.ts +++ b/packages/aws-cdk/lib/init.ts @@ -4,6 +4,7 @@ import * as chalk from 'chalk'; import * as fs from 'fs-extra'; import { invokeBuiltinHooks } from './init-hooks'; import { error, print, warning } from './logging'; +import { ToolkitError } from './toolkit/error'; import { cdkHomeDir, rootDir } from './util/directories'; import { rangeFromSemver } from './util/version-range'; @@ -40,7 +41,7 @@ export async function cliInit(options: CliInitOptions) { const template = (await availableInitTemplates()).find((t) => t.hasName(type!)); if (!template) { await printAvailableTemplates(options.language); - throw new Error(`Unknown init template: ${type}`); + throw new ToolkitError(`Unknown init template: ${type}`); } if (!options.language && template.languages.length === 1) { const language = template.languages[0]; @@ -50,7 +51,7 @@ export async function cliInit(options: CliInitOptions) { } if (!options.language) { print(`Available languages for ${chalk.green(type)}: ${template.languages.map((l) => chalk.blue(l)).join(', ')}`); - throw new Error('No language was selected'); + throw new ToolkitError('No language was selected'); } await initializeProject( @@ -119,7 +120,7 @@ export class InitTemplate { `The ${chalk.blue(language)} language is not supported for ${chalk.green(this.name)} ` + `(it supports: ${this.languages.map((l) => chalk.blue(l)).join(', ')})`, ); - throw new Error(`Unsupported language: ${language}`); + throw new ToolkitError(`Unsupported language: ${language}`); } const projectInfo: ProjectInfo = { @@ -340,7 +341,7 @@ async function initializeProject( async function assertIsEmptyDirectory(workDir: string) { const files = await fs.readdir(workDir); if (files.filter((f) => !f.startsWith('.')).length !== 0) { - throw new Error('`cdk init` cannot be run in a non-empty directory!'); + throw new ToolkitError('`cdk init` cannot be run in a non-empty directory!'); } } @@ -466,7 +467,7 @@ async function execute(cmd: string, args: string[], { cwd }: { cwd: string }) { return ok(stdout); } else { process.stderr.write(stdout); - return fail(new Error(`${cmd} exited with status ${status}`)); + return fail(new ToolkitError(`${cmd} exited with status ${status}`)); } }); }); diff --git a/packages/aws-cdk/lib/notices.ts b/packages/aws-cdk/lib/notices.ts index 8970485f321af..ca0f1487a97fc 100644 --- a/packages/aws-cdk/lib/notices.ts +++ b/packages/aws-cdk/lib/notices.ts @@ -7,8 +7,9 @@ import * as fs from 'fs-extra'; import * as semver from 'semver'; import { SdkHttpOptions } from './api'; import { AwsCliCompatible } from './api/aws-auth/awscli-compatible'; -import { debug, error, print, warning } from './logging'; +import { debug, print, warning, error } from './logging'; import { Context } from './settings'; +import { ToolkitError } from './toolkit/error'; import { loadTreeFromDir, some } from './tree'; import { flatMap } from './util'; import { cdkCacheDir } from './util/directories'; @@ -399,7 +400,7 @@ export class WebsiteNoticeDataSource implements NoticeDataSource { let timer = setTimeout(() => { if (req) { - req.destroy(new Error('Request timed out')); + req.destroy(new ToolkitError('Request timed out')); } }, timeout); @@ -423,24 +424,24 @@ export class WebsiteNoticeDataSource implements NoticeDataSource { try { const data = JSON.parse(rawData).notices as Notice[]; if (!data) { - throw new Error("'notices' key is missing"); + throw new ToolkitError("'notices' key is missing"); } debug('Notices refreshed'); resolve(data ?? []); } catch (e: any) { - reject(new Error(`Failed to parse notices: ${e.message}`)); + reject(new ToolkitError(`Failed to parse notices: ${e.message}`)); } }); res.on('error', e => { - reject(new Error(`Failed to fetch notices: ${e.message}`)); + reject(new ToolkitError(`Failed to fetch notices: ${e.message}`)); }); } else { - reject(new Error(`Failed to fetch notices. Status code: ${res.statusCode}`)); + reject(new ToolkitError(`Failed to fetch notices. Status code: ${res.statusCode}`)); } }); req.on('error', reject); } catch (e: any) { - reject(new Error(`HTTPS 'get' call threw an error: ${e.message}`)); + reject(new ToolkitError(`HTTPS 'get' call threw an error: ${e.message}`)); } }); } diff --git a/packages/aws-cdk/lib/os.ts b/packages/aws-cdk/lib/os.ts index 6483081eb9182..660a83a6bfbd6 100644 --- a/packages/aws-cdk/lib/os.ts +++ b/packages/aws-cdk/lib/os.ts @@ -1,6 +1,7 @@ import * as child_process from 'child_process'; import * as chalk from 'chalk'; import { debug } from './logging'; +import { ToolkitError } from './toolkit/error'; /** * OS helpers @@ -32,7 +33,7 @@ export async function shell(command: string[]): Promise { if (code === 0) { resolve(Buffer.from(stdout).toString('utf-8')); } else { - reject(new Error(`${commandLine} exited with error code ${code}`)); + reject(new ToolkitError(`${commandLine} exited with error code ${code}`)); } }); }); diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 7e558a391d5ac..58755af786643 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -3,6 +3,7 @@ import * as fs_path from 'path'; import * as fs from 'fs-extra'; import { Tag } from './cdk-toolkit'; import { debug, warning } from './logging'; +import { ToolkitError } from './toolkit/error'; import * as util from './util'; export type SettingsMap = {[key: string]: any}; @@ -94,14 +95,14 @@ export class Configuration { private get projectConfig() { if (!this._projectConfig) { - throw new Error('#load has not been called yet!'); + throw new ToolkitError('#load has not been called yet!'); } return this._projectConfig; } public get projectContext() { if (!this._projectContext) { - throw new Error('#load has not been called yet!'); + throw new ToolkitError('#load has not been called yet!'); } return this._projectContext; } @@ -117,7 +118,7 @@ export class Configuration { const readUserContext = this.props.readUserContext ?? true; if (userConfig.get(['build'])) { - throw new Error('The `build` key cannot be specified in the user config (~/.cdk.json), specify it in the project config (cdk.json) instead'); + throw new ToolkitError('The `build` key cannot be specified in the user config (~/.cdk.json), specify it in the project config (cdk.json) instead'); } const contextSources = [ @@ -355,7 +356,7 @@ export class Settings { if (parts.length === 2) { debug('CLI argument context: %s=%s', parts[0], parts[1]); if (parts[0].match(/^aws:.+/)) { - throw new Error(`User-provided context cannot use keys prefixed with 'aws:', but ${parts[0]} was provided.`); + throw new ToolkitError(`User-provided context cannot use keys prefixed with 'aws:', but ${parts[0]} was provided.`); } context[parts[0]] = parts[1]; } else { @@ -398,7 +399,7 @@ export class Settings { public async load(fileName: string): Promise { if (this.readOnly) { - throw new Error(`Can't load ${fileName}: settings object is readonly`); + throw new ToolkitError(`Can't load ${fileName}: settings object is readonly`); } this.settings = {}; @@ -439,7 +440,7 @@ export class Settings { public clear() { if (this.readOnly) { - throw new Error('Cannot clear(): settings are readonly'); + throw new ToolkitError('Cannot clear(): settings are readonly'); } this.settings = {}; } @@ -454,7 +455,7 @@ export class Settings { public set(path: string[], value: any): Settings { if (this.readOnly) { - throw new Error(`Can't set ${path}: settings object is readonly`); + throw new ToolkitError(`Can't set ${path}: settings object is readonly`); } if (path.length === 0) { // deepSet can't handle this case @@ -473,7 +474,7 @@ export class Settings { if (!this.settings.context) { return; } if (key in this.settings.context) { // eslint-disable-next-line max-len - throw new Error(`The 'context.${key}' key was found in ${fs_path.resolve(fileName)}, but it is no longer supported. Please remove it.`); + throw new ToolkitError(`The 'context.${key}' key was found in ${fs_path.resolve(fileName)}, but it is no longer supported. Please remove it.`); } } @@ -520,11 +521,11 @@ function isTransientValue(value: any) { function expectStringList(x: unknown): string[] | undefined { if (x === undefined) { return undefined; } if (!Array.isArray(x)) { - throw new Error(`Expected array, got '${x}'`); + throw new ToolkitError(`Expected array, got '${x}'`); } const nonStrings = x.filter(e => typeof e !== 'string'); if (nonStrings.length > 0) { - throw new Error(`Expected list of strings, found ${nonStrings}`); + throw new ToolkitError(`Expected list of strings, found ${nonStrings}`); } return x; } diff --git a/packages/aws-cdk/lib/toolkit/error.ts b/packages/aws-cdk/lib/toolkit/error.ts new file mode 100644 index 0000000000000..cc22b935b5648 --- /dev/null +++ b/packages/aws-cdk/lib/toolkit/error.ts @@ -0,0 +1,48 @@ +const TOOLKIT_ERROR_SYMBOL = Symbol.for('@aws-cdk/core.TooklitError'); +const AUTHENTICATION_ERROR_SYMBOL = Symbol.for('@aws-cdk/core.AuthenticationError'); + +/** + * Represents a general toolkit error in the AWS CDK Toolkit. + */ +class ToolkitError extends Error { + /** + * Determines if a given error is an instance of ToolkitError. + */ + public static isToolkitError(x: any): x is ToolkitError { + return x !== null && typeof(x) === 'object' && TOOLKIT_ERROR_SYMBOL in x; + } + + /** + * Determines if a given error is an instance of AuthenticationError. + */ + public static isAuthenticationError(x: any): x is AuthenticationError { + return this.isToolkitError(x) && AUTHENTICATION_ERROR_SYMBOL in x; + } + + /** + * The type of the error, defaults to "toolkit". + */ + public readonly type: string; + + constructor(message: string, type: string = 'toolkit') { + super(message); + Object.setPrototypeOf(this, ToolkitError.prototype); + Object.defineProperty(this, TOOLKIT_ERROR_SYMBOL, { value: true }); + this.name = new.target.name; + this.type = type; + } +} + +/** + * Represents an authentication-specific error in the AWS CDK Toolkit. + */ +class AuthenticationError extends ToolkitError { + constructor(message: string) { + super(message, 'authentication'); + Object.setPrototypeOf(this, AuthenticationError.prototype); + Object.defineProperty(this, AUTHENTICATION_ERROR_SYMBOL, { value: true }); + } +} + +// Export classes for internal usage only +export { ToolkitError, AuthenticationError }; diff --git a/packages/aws-cdk/lib/version.ts b/packages/aws-cdk/lib/version.ts index bdeee8d51b482..a3b7f8383a28b 100644 --- a/packages/aws-cdk/lib/version.ts +++ b/packages/aws-cdk/lib/version.ts @@ -5,6 +5,7 @@ import * as semver from 'semver'; import { cdkCacheDir, rootDir } from './util/directories'; import { getLatestVersionFromNpm } from './util/npm'; import { debug, print } from '../lib/logging'; +import { ToolkitError } from './toolkit/error'; import { formatAsBanner } from '../lib/util/console-formatters'; const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60; @@ -46,7 +47,7 @@ export class VersionCheckTTL { fs.mkdirsSync(path.dirname(this.file)); fs.accessSync(path.dirname(this.file), fs.constants.W_OK); } catch { - throw new Error(`Directory (${path.dirname(this.file)}) is not writable.`); + throw new ToolkitError(`Directory (${path.dirname(this.file)}) is not writable.`); } this.ttlSecs = ttlSecs || ONE_DAY_IN_SECONDS; } diff --git a/packages/aws-cdk/test/toolkit-error.test.ts b/packages/aws-cdk/test/toolkit-error.test.ts new file mode 100644 index 0000000000000..1aef772e186a5 --- /dev/null +++ b/packages/aws-cdk/test/toolkit-error.test.ts @@ -0,0 +1,17 @@ +import { AuthenticationError, ToolkitError } from '../lib/toolkit/error'; + +describe('toolkit error', () => { + let toolkitError = new ToolkitError('Test toolkit error'); + let authError = new AuthenticationError('Test authentication error'); + test('types are correctly assigned', async () => { + expect(toolkitError.type).toBe('toolkit'); + expect(authError.type).toBe('authentication'); + }); + + test('isToolkitError and isAuthenticationError functions work', () => { + expect(ToolkitError.isToolkitError(toolkitError)).toBe(true); + expect(ToolkitError.isToolkitError(authError)).toBe(true); + expect(ToolkitError.isAuthenticationError(toolkitError)).toBe(false); + expect(ToolkitError.isAuthenticationError(authError)).toBe(true); + }); +}); From 61626dc9e5d580878dec665aa2d2ac976103a826 Mon Sep 17 00:00:00 2001 From: Mahdi Azarboon <21277296+azarboon@users.noreply.github.com> Date: Wed, 25 Dec 2024 05:18:40 +0800 Subject: [PATCH 2/9] docs(appsync): add placeholder Query type for improved schema compatibility (#32652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Issue # (if applicable) Closes #. ### Reason for this change My earlier PR (#32633) addressing the same issue was approved but encountered sync problems, leading me to close it and open this fresh PR. I’ve ensured this branch is properly synced, and I hope the issue is resolved. For clarity, the rationale behind this change is as follows: While the current schema is technically valid with a single root Mutation type, most GraphQL clients and tools expect a default Query operation for introspection and standard queries. This PR introduces a minimal placeholder Query type to enhance compatibility and align with widely accepted GraphQL conventions. ### Description of changes ### Describe any new or updated permissions being added ### Description of how you validated changes ### Checklist - [ ] 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* --- packages/aws-cdk-lib/aws-appsync/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/aws-cdk-lib/aws-appsync/README.md b/packages/aws-cdk-lib/aws-appsync/README.md index f0ab40d93b3c8..1f85e8bdedc08 100644 --- a/packages/aws-cdk-lib/aws-appsync/README.md +++ b/packages/aws-cdk-lib/aws-appsync/README.md @@ -240,6 +240,10 @@ input DemoInput { type Mutation { callStepFunction(input: DemoInput!): job } + +type Query { + _placeholder: String +} ``` GraphQL request mapping template `request.vtl`: From 44360cc8bb34c713e99afa564eceafea8ea41545 Mon Sep 17 00:00:00 2001 From: Matsuda Date: Wed, 25 Dec 2024 06:50:11 +0900 Subject: [PATCH 3/9] chore(glue): add missing connection types (#32600) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Issue # (if applicable) N/A ### Reason for this change Missing Glue connection types. Ref: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-glue-connection-connectioninput.html#cfn-glue-connection-connectioninput-connectiontype ``` VIEW_VALIDATION_REDSHIFT - Designates a connection used for view validation by Amazon Redshift. VIEW_VALIDATION_ATHENA - Designates a connection used for view validation by Amazon Athena. ... FACEBOOKADS - Designates a connection to Facebook Ads. GOOGLEADS - Designates a connection to Google Ads. GOOGLESHEETS - Designates a connection to Google Sheets. GOOGLEANALYTICS4 - Designates a connection to Google Analytics 4. HUBSPOT - Designates a connection to HubSpot. INSTAGRAMADS - Designates a connection to Instagram Ads. INTERCOM - Designates a connection to Intercom. JIRACLOUD - Designates a connection to Jira Cloud. MARKETO - Designates a connection to Adobe Marketo Engage. NETSUITEERP - Designates a connection to Oracle NetSuite. SALESFORCE - Designates a connection to Salesforce using OAuth authentication. SALESFORCEMARKETINGCLOUD - Designates a connection to Salesforce Marketing Cloud. SALESFORCEPARDOT - Designates a connection to Salesforce Marketing Cloud Account Engagement (MCAE). SAPODATA - Designates a connection to SAP OData. SERVICENOW - Designates a connection to ServiceNow. SLACK - Designates a connection to Slack. SNAPCHATADS - Designates a connection to Snapchat Ads. STRIPE - Designates a connection to Stripe. ZENDESK - Designates a connection to Zendesk. ZOHOCRM - Designates a connection to Zoho CRM. ``` ### Description of changes Add enums. ### Describe any new or updated permissions being added Nothing. ### Description of how you validated changes Just add enums, so I've not added a test. ### 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* --- .../@aws-cdk/aws-glue-alpha/lib/connection.ts | 114 +++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-glue-alpha/lib/connection.ts b/packages/@aws-cdk/aws-glue-alpha/lib/connection.ts index e999eb58d11e5..61981720abd0a 100644 --- a/packages/@aws-cdk/aws-glue-alpha/lib/connection.ts +++ b/packages/@aws-cdk/aws-glue-alpha/lib/connection.ts @@ -28,6 +28,16 @@ export class ConnectionType { */ public static readonly MONGODB = new ConnectionType('MONGODB'); + /** + * Designates a connection used for view validation by Amazon Redshift. + */ + public static readonly VIEW_VALIDATION_REDSHIFT = new ConnectionType('VIEW_VALIDATION_REDSHIFT'); + + /** + * Designates a connection used for view validation by Amazon Athena. + */ + public static readonly VIEW_VALIDATION_ATHENA = new ConnectionType('VIEW_VALIDATION_ATHENA'); + /** * Designates a network connection to a data source within an Amazon Virtual Private Cloud environment (Amazon VPC). */ @@ -45,6 +55,106 @@ export class ConnectionType { */ public static readonly CUSTOM = new ConnectionType('CUSTOM'); + /** + * Designates a connection to Facebook Ads. + */ + public static readonly FACEBOOKADS = new ConnectionType('FACEBOOKADS'); + + /** + * Designates a connection to Google Ads. + */ + public static readonly GOOGLEADS = new ConnectionType('GOOGLEADS'); + + /** + * Designates a connection to Google Sheets. + */ + public static readonly GOOGLESHEETS = new ConnectionType('GOOGLESHEETS'); + + /** + * Designates a connection to Google Analytics 4. + */ + public static readonly GOOGLEANALYTICS4 = new ConnectionType('GOOGLEANALYTICS4'); + + /** + * Designates a connection to HubSpot. + */ + public static readonly HUBSPOT = new ConnectionType('HUBSPOT'); + + /** + * Designates a connection to Instagram Ads. + */ + public static readonly INSTAGRAMADS = new ConnectionType('INSTAGRAMADS'); + + /** + * Designates a connection to Intercom. + */ + public static readonly INTERCOM = new ConnectionType('INTERCOM'); + + /** + * Designates a connection to Jira Cloud. + */ + public static readonly JIRACLOUD = new ConnectionType('JIRACLOUD'); + + /** + * Designates a connection to Adobe Marketo Engage. + */ + public static readonly MARKETO = new ConnectionType('MARKETO'); + + /** + * Designates a connection to Oracle NetSuite. + */ + public static readonly NETSUITEERP = new ConnectionType('NETSUITEERP'); + + /** + * Designates a connection to Salesforce using OAuth authentication. + */ + public static readonly SALESFORCE = new ConnectionType('SALESFORCE'); + + /** + * Designates a connection to Salesforce Marketing Cloud. + */ + public static readonly SALESFORCEMARKETINGCLOUD = new ConnectionType('SALESFORCEMARKETINGCLOUD'); + + /** + * Designates a connection to Salesforce Marketing Cloud Account Engagement (MCAE). + */ + public static readonly SALESFORCEPARDOT = new ConnectionType('SALESFORCEPARDOT'); + + /** + * Designates a connection to SAP OData. + */ + public static readonly SAPODATA = new ConnectionType('SAPODATA'); + + /** + * Designates a connection to ServiceNow. + */ + public static readonly SERVICENOW = new ConnectionType('SERVICENOW'); + + /** + * Designates a connection to Slack. + */ + public static readonly SLACK = new ConnectionType('SLACK'); + + /** + * Designates a connection to Snapchat Ads. + */ + public static readonly SNAPCHATADS = new ConnectionType('SNAPCHATADS'); + + /** + * Designates a connection to Stripe. + */ + public static readonly STRIPE = new ConnectionType('STRIPE'); + + /** + * Designates a connection to Zendesk. + */ + public static readonly ZENDESK = new ConnectionType('ZENDESK'); + + /** + * Designates a connection to Zoho CRM. + */ + public static readonly ZOHOCRM = new ConnectionType('ZOHOCRM'); + /** * The name of this ConnectionType, as expected by Connection resource. */ @@ -169,7 +279,7 @@ export class Connection extends cdk.Resource implements IConnection { return new Import(scope, id); } - private static buildConnectionArn(scope: constructs.Construct, connectionName: string) : string { + private static buildConnectionArn(scope: constructs.Construct, connectionName: string): string { return cdk.Stack.of(scope).formatArn({ service: 'glue', resource: 'connection', @@ -187,7 +297,7 @@ export class Connection extends cdk.Resource implements IConnection { */ public readonly connectionName: string; - private readonly properties: {[key: string]: string}; + private readonly properties: { [key: string]: string }; constructor(scope: constructs.Construct, id: string, props: ConnectionProps) { super(scope, id, { From b00de76efd5efb0214bf05227bf5c9e515a0c842 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Tue, 24 Dec 2024 23:20:23 +0100 Subject: [PATCH 4/9] chore(cdk-build-tools): allow packing of private packages for local testing (#32644) ### Reason for this change Sometimes there is a need to pack a private package so it can be tested as a package locally. For example when a package is not released during development. Previously it was not possible to pack a private package with our tooling. ### Description of changes Adds a `--private` flag to the `cdk-package` command to allow force packing of private packages. ### Describe any new or updated permissions being added n/a ### Description of how you validated changes Manually used the new flag to pack a private package. ### 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* --- tools/@aws-cdk/cdk-build-tools/bin/cdk-package.ts | 6 ++++-- tools/@aws-cdk/cdk-build-tools/lib/package-info.ts | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/@aws-cdk/cdk-build-tools/bin/cdk-package.ts b/tools/@aws-cdk/cdk-build-tools/bin/cdk-package.ts index bc48546f91787..66ae1264f4265 100644 --- a/tools/@aws-cdk/cdk-build-tools/bin/cdk-package.ts +++ b/tools/@aws-cdk/cdk-build-tools/bin/cdk-package.ts @@ -24,6 +24,7 @@ async function main() { }) .option('pre-only', { type: 'boolean', default: false, desc: 'run pre package steps only' }) .option('post-only', { type: 'boolean', default: false, desc: 'run post package steps only' }) + .option('private', { type: 'boolean', default: false, desc: 'Also package private packages for local usage' }) .argv; if (args['pre-only'] && args['post-only']) { @@ -43,8 +44,9 @@ async function main() { const outdir = 'dist'; // if this is a private module, don't package - if (isPrivate()) { - process.stdout.write('No packaging for private modules.\n'); + const packPrivate = args.private || options.private; + if (isPrivate() && !packPrivate) { + process.stdout.write('No packaging for private modules.\nUse --private to force packing private packages for local testing.\n'); return; } diff --git a/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts b/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts index 9657930be27d7..6687cfb497f67 100644 --- a/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts +++ b/tools/@aws-cdk/cdk-build-tools/lib/package-info.ts @@ -205,6 +205,12 @@ export interface CDKPackageOptions { * Should this package be bundled. (and if so, how) */ bundle?: Omit; + + /** + * Also package private packages for local usage. + * @default false + */ + private?: boolean; } /** From 0731095f5a9e2f58ec113b4ae05894b95df89ea2 Mon Sep 17 00:00:00 2001 From: Clare Liguori Date: Tue, 24 Dec 2024 14:52:08 -0800 Subject: [PATCH 5/9] fix(elasticloadbalancingv2): open, dual-stack-without-public-ipv4 ALB allows IPv6 inbound traffic (#32203) ### Issue # Closes #32197 ### Reason for this change Default generated security group ingress rules for open, dual-stack-without-public-ipv4 ALB does not allow IPv6 traffic. Only a rule for IPv4 ingress traffic is added to the security group rules currently. ### Description of changes Default generated security group ingress rules now have an additional rule that allows IPv6 ingress from anywhere. ### Description of how you validated changes Added a unit test, and updated an existing integration test ### 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* --- ...efaultTestDeployAssertFA6F90DD.assets.json | 2 +- ...-dualstack-without-public-ipv4.assets.json | 6 +-- ...ualstack-without-public-ipv4.template.json | 7 ++++ .../cdk.out | 2 +- .../integ.json | 2 +- .../manifest.json | 4 +- .../tree.json | 11 +++++- .../aws-elasticloadbalancingv2/README.md | 3 +- .../lib/alb/application-listener.ts | 3 +- .../test/alb/listener.test.ts | 37 +++++++++++++++++++ 10 files changed, 65 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/AlbDualstackWithoutPublicIpv4DefaultTestDeployAssertFA6F90DD.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/AlbDualstackWithoutPublicIpv4DefaultTestDeployAssertFA6F90DD.assets.json index 4a0f5e0c5e4b3..3d5b95b9e8666 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/AlbDualstackWithoutPublicIpv4DefaultTestDeployAssertFA6F90DD.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/AlbDualstackWithoutPublicIpv4DefaultTestDeployAssertFA6F90DD.assets.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "38.0.1", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.assets.json index 5091d2e660924..14276309ba9ba 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.assets.json @@ -1,7 +1,7 @@ { - "version": "36.0.0", + "version": "38.0.1", "files": { - "688bf4caeb2845f3dc89826da60063b380e5d0fe7ab50a95f9ffc76451c42a77": { + "0fac4619627ba59020023785c5d86d47abad0759e7add3aa2f150f8cbfcd7a9a": { "source": { "path": "aws-cdk-elbv2-integ-dualstack-without-public-ipv4.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "688bf4caeb2845f3dc89826da60063b380e5d0fe7ab50a95f9ffc76451c42a77.json", + "objectKey": "0fac4619627ba59020023785c5d86d47abad0759e7add3aa2f150f8cbfcd7a9a.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.template.json index 8882537e5df34..d430a9159e6c5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/aws-cdk-elbv2-integ-dualstack-without-public-ipv4.template.json @@ -530,6 +530,13 @@ "FromPort": 80, "IpProtocol": "tcp", "ToPort": 80 + }, + { + "CidrIpv6": "::/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 } ], "VpcId": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/cdk.out index 1f0068d32659a..c6e612584e352 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"36.0.0"} \ No newline at end of file +{"version":"38.0.1"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/integ.json index 4fd4fa6f896d6..975d3543a4156 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "38.0.1", "testCases": { "AlbDualstackWithoutPublicIpv4/DefaultTest": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/manifest.json index 87f61f9552ca1..3c6852a3f2e04 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "38.0.1", "artifacts": { "aws-cdk-elbv2-integ-dualstack-without-public-ipv4.assets": { "type": "cdk:asset-manifest", @@ -18,7 +18,7 @@ "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}/688bf4caeb2845f3dc89826da60063b380e5d0fe7ab50a95f9ffc76451c42a77.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/0fac4619627ba59020023785c5d86d47abad0759e7add3aa2f150f8cbfcd7a9a.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/tree.json index 07d5c271d52c2..f8537fa89d692 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-elasticloadbalancingv2/test/integ.alb.dualstack-without-public-ipv4.js.snapshot/tree.json @@ -821,6 +821,13 @@ "fromPort": 80, "toPort": 80, "description": "Allow from anyone on port 80" + }, + { + "cidrIpv6": "::/0", + "ipProtocol": "tcp", + "fromPort": 80, + "toPort": 80, + "description": "Allow from anyone on port 80" } ], "vpcId": { @@ -1269,7 +1276,7 @@ "path": "AlbDualstackWithoutPublicIpv4/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.3.0" + "version": "10.4.2" } }, "DeployAssert": { @@ -1315,7 +1322,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.3.0" + "version": "10.4.2" } } }, diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md index f55effba63631..6e4ae54ad3534 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/README.md @@ -298,13 +298,14 @@ const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { }); ``` -By setting `DUAL_STACK_WITHOUT_PUBLIC_IPV4`, you can provision load balancers without public IPv4s +By setting `DUAL_STACK_WITHOUT_PUBLIC_IPV4`, you can provision load balancers without public IPv4s: ```ts declare const vpc: ec2.Vpc; const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, + internetFacing: true, ipAddressType: elbv2.IpAddressType.DUAL_STACK_WITHOUT_PUBLIC_IPV4, }); ``` diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 07cfb949f3b83..35ba80472146b 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -303,7 +303,8 @@ export class ApplicationListener extends BaseListener implements IApplicationLis if (props.open !== false) { this.connections.allowDefaultPortFrom(ec2.Peer.anyIpv4(), `Allow from anyone on port ${port}`); - if (this.loadBalancer.ipAddressType === IpAddressType.DUAL_STACK) { + if (this.loadBalancer.ipAddressType === IpAddressType.DUAL_STACK || + this.loadBalancer.ipAddressType === IpAddressType.DUAL_STACK_WITHOUT_PUBLIC_IPV4) { this.connections.allowDefaultPortFrom(ec2.Peer.anyIpv6(), `Allow from anyone on port ${port}`); } } diff --git a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts index 1943a1945ac2a..6f17cfc8a7979 100644 --- a/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts +++ b/packages/aws-cdk-lib/aws-elasticloadbalancingv2/test/alb/listener.test.ts @@ -107,6 +107,43 @@ describe('tests', () => { }); }); + test('Listener default to open - IPv6 (dual stack without public IPV4)', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Stack'); + const loadBalancer = new elbv2.ApplicationLoadBalancer(stack, 'LB', { + vpc, + internetFacing: true, + ipAddressType: elbv2.IpAddressType.DUAL_STACK_WITHOUT_PUBLIC_IPV4, + }); + + // WHEN + loadBalancer.addListener('MyListener', { + port: 80, + defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::EC2::SecurityGroup', { + SecurityGroupIngress: [ + { + Description: 'Allow from anyone on port 80', + CidrIp: '0.0.0.0/0', + FromPort: 80, + IpProtocol: 'tcp', + ToPort: 80, + }, + { + Description: 'Allow from anyone on port 80', + CidrIpv6: '::/0', + FromPort: 80, + IpProtocol: 'tcp', + ToPort: 80, + }, + ], + }); + }); + test('HTTPS listener requires certificate', () => { // GIVEN const stack = new cdk.Stack(); From 0e1854dba05586b286fd9b9163fb80f57eb4f743 Mon Sep 17 00:00:00 2001 From: "Kenta Goto (k.goto)" <24818752+go-to-k@users.noreply.github.com> Date: Thu, 26 Dec 2024 06:22:47 +0900 Subject: [PATCH 6/9] docs(cli): fix typo for `defaultBehavior` doc in `SelectStacksOptions` (#32654) This PR is about very very small typo, but I found... (I was just looking at this module now and was curious about it, so I reported it while I was at it.) --- packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts index 322508881321d..d02e9b12d5008 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts @@ -38,7 +38,7 @@ export interface SelectStacksOptions { extend?: ExtendedStackSelection; /** - * The behavior if if no selectors are provided. + * The behavior if no selectors are provided. */ defaultBehavior: DefaultSelection; From 0ce42a5df9fcced3330bab2ee3e96f5eab372d4e Mon Sep 17 00:00:00 2001 From: Kaizen Conroy <36202692+kaizencc@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:23:41 -0500 Subject: [PATCH 7/9] chore(cli): explicit defaults to yargs definition and cli arguments (#32596) This makes sure we have a default value (even if its `undefined`) for every option in yargs. There is a special case for arrays where the default will be `[]`. I also took the liberty to simplify the eslint exemptions by changing the prettier options. ### 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* --- packages/aws-cdk/lib/cli-arguments.ts | 42 ++-- .../lib/parse-command-line-arguments.ts | 233 +++++++++++------- .../@aws-cdk/cli-args-gen/lib/cli-type-gen.ts | 25 +- tools/@aws-cdk/cli-args-gen/lib/util.ts | 3 + tools/@aws-cdk/cli-args-gen/lib/yargs-gen.ts | 19 +- .../cli-args-gen/test/cli-type-gen.test.ts | 105 ++++++++ .../cli-args-gen/test/yargs-gen.test.ts | 79 +++++- 7 files changed, 380 insertions(+), 126 deletions(-) create mode 100644 tools/@aws-cdk/cli-args-gen/lib/util.ts diff --git a/packages/aws-cdk/lib/cli-arguments.ts b/packages/aws-cdk/lib/cli-arguments.ts index 4d14edc5bf60f..410a5f6876bdb 100644 --- a/packages/aws-cdk/lib/cli-arguments.ts +++ b/packages/aws-cdk/lib/cli-arguments.ts @@ -143,14 +143,14 @@ export interface GlobalOptions { /** * Add contextual string parameter (KEY=VALUE) * - * @default - undefined + * @default - [] */ readonly context?: Array; /** * Name or path of a node package that extend the CDK features. Can be specified multiple times * - * @default - undefined + * @default - [] */ readonly plugin?: Array; @@ -297,7 +297,7 @@ export interface GlobalOptions { /** * Opt in to unstable features. The flag indicates that the scope and API of a feature might still change. Otherwise the feature is generally production ready and fully supported. Can be specified multiple times. * - * @default - undefined + * @default - [] */ readonly unstable?: Array; } @@ -429,7 +429,7 @@ export interface BootstrapOptions { * * aliases: t * - * @default - undefined + * @default - [] */ readonly tags?: Array; @@ -443,21 +443,21 @@ export interface BootstrapOptions { /** * The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated, modern bootstrapping only) * - * @default - undefined + * @default - [] */ readonly trust?: Array; /** * The AWS account IDs that should be trusted to look up values in this environment (may be repeated, modern bootstrapping only) * - * @default - undefined + * @default - [] */ readonly 'trust-for-lookup'?: Array; /** * The Managed Policy ARNs that should be attached to the role performing deployments into this environment (may be repeated, modern bootstrapping only) * - * @default - undefined + * @default - [] */ readonly 'cloudformation-execution-policies'?: Array; @@ -515,28 +515,28 @@ export interface GcOptions { /** * The action (or sub-action) you want to perform. Valid entires are "print", "tag", "delete-tagged", "full". * - * @default - full + * @default - "full" */ readonly action?: string; /** * Specify either ecr, s3, or all * - * @default - all + * @default - "all" */ readonly type?: string; /** * Delete assets that have been marked as isolated for this many days * - * @default - undefined + * @default - 0 */ readonly 'rollback-buffer-days'?: number; /** * Never delete assets younger than this (in days) * - * @default - undefined + * @default - 1 */ readonly 'created-buffer-days'?: number; @@ -573,7 +573,7 @@ export interface DeployOptions { * * aliases: E * - * @default - undefined + * @default - [] */ readonly 'build-exclude'?: Array; @@ -605,7 +605,7 @@ export interface DeployOptions { * * aliases: t * - * @default - undefined + * @default - [] */ readonly tags?: Array; @@ -645,7 +645,7 @@ export interface DeployOptions { /** * Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE) * - * @default - undefined + * @default - {} */ readonly parameters?: Array; @@ -717,7 +717,7 @@ export interface DeployOptions { /** * Maximum number of simultaneous deployments (dependency permitting) to execute. * - * @default - undefined + * @default - 1 */ readonly concurrency?: number; @@ -782,7 +782,7 @@ export interface RollbackOptions { /** * Orphan the given resources, identified by their logical ID (can be specified multiple times) * - * @default - undefined + * @default - [] */ readonly orphan?: Array; } @@ -860,7 +860,7 @@ export interface WatchOptions { * * aliases: E * - * @default - undefined + * @default - [] */ readonly 'build-exclude'?: Array; @@ -934,7 +934,7 @@ export interface WatchOptions { /** * Maximum number of simultaneous deployments (dependency permitting) to execute. * - * @default - undefined + * @default - 1 */ readonly concurrency?: number; } @@ -989,7 +989,7 @@ export interface DiffOptions { /** * Number of context lines to include in arbitrary JSON diff rendering * - * @default - undefined + * @default - 3 */ readonly 'context-lines'?: number; @@ -1129,7 +1129,7 @@ export interface MigrateOptions { * * aliases: l * - * @default - typescript + * @default - "typescript" */ readonly language?: string; @@ -1185,7 +1185,7 @@ export interface MigrateOptions { * tag-key: a string that matches resources with at least one tag with the provided key. i.e. "myTagKey" * tag-value: a string that matches resources with at least one tag with the provided value. i.e. "myTagValue" * - * @default - undefined + * @default - [] */ readonly filter?: Array; diff --git a/packages/aws-cdk/lib/parse-command-line-arguments.ts b/packages/aws-cdk/lib/parse-command-line-arguments.ts index 4cdd4f1023806..37443fc6a713b 100644 --- a/packages/aws-cdk/lib/parse-command-line-arguments.ts +++ b/packages/aws-cdk/lib/parse-command-line-arguments.ts @@ -2,7 +2,7 @@ // GENERATED FROM packages/aws-cdk/lib/config.ts. // Do not edit by hand; all changes will be overwritten at build time from the config file. // ------------------------------------------------------------------------------------------- -/* eslint-disable @stylistic/comma-dangle, @stylistic/comma-spacing, @stylistic/max-len, @stylistic/quotes, @stylistic/quote-props */ +/* eslint-disable max-len */ import { Argv } from 'yargs'; import * as helpers from './util/yargs-helpers'; @@ -12,16 +12,19 @@ export function parseCommandLineArguments(args: Array): any { .env('CDK') .usage('Usage: cdk -a COMMAND') .option('app', { + default: undefined, type: 'string', alias: 'a', desc: 'REQUIRED WHEN RUNNING APP: command-line for executing your app or a cloud assembly directory (e.g. "node bin/my-app.js"). Can also be specified in cdk.json or ~/.cdk.json', requiresArg: true, }) .option('build', { + default: undefined, type: 'string', desc: 'Command-line for a pre-synth build', }) .option('context', { + default: [], type: 'array', alias: 'c', desc: 'Add contextual string parameter (KEY=VALUE)', @@ -29,6 +32,7 @@ export function parseCommandLineArguments(args: Array): any { requiresArg: true, }) .option('plugin', { + default: [], type: 'array', alias: 'p', desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times', @@ -36,259 +40,269 @@ export function parseCommandLineArguments(args: Array): any { requiresArg: true, }) .option('trace', { + default: undefined, type: 'boolean', desc: 'Print trace for stack warnings', }) .option('strict', { + default: undefined, type: 'boolean', desc: 'Do not construct stacks with warnings', }) .option('lookups', { + default: true, type: 'boolean', desc: 'Perform context lookups (synthesis fails if this is disabled and context lookups need to be performed)', - default: true, }) .option('ignore-errors', { - type: 'boolean', default: false, + type: 'boolean', desc: 'Ignores synthesis errors, which will likely produce an invalid output', }) .option('json', { + default: false, type: 'boolean', alias: 'j', desc: 'Use JSON output instead of YAML when templates are printed to STDOUT', - default: false, }) .option('verbose', { + default: false, type: 'boolean', alias: 'v', desc: 'Show debug logs (specify multiple times to increase verbosity)', - default: false, count: true, }) .option('debug', { + default: false, type: 'boolean', desc: 'Debug the CDK app. Log additional information during synthesis, such as creation stack traces of tokens (sets CDK_DEBUG, will slow down synthesis)', - default: false, }) .option('profile', { + default: undefined, type: 'string', desc: 'Use the indicated AWS profile as the default environment', requiresArg: true, }) .option('proxy', { + default: undefined, type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified', requiresArg: true, }) .option('ca-bundle-path', { + default: undefined, type: 'string', desc: 'Path to CA certificate to use when validating HTTPS requests. Will read from AWS_CA_BUNDLE environment variable if not specified', requiresArg: true, }) .option('ec2creds', { + default: undefined, type: 'boolean', alias: 'i', - default: undefined, desc: 'Force trying to fetch EC2 instance credentials. Default: guess EC2 instance status', }) .option('version-reporting', { + default: undefined, type: 'boolean', desc: 'Include the "AWS::CDK::Metadata" resource in synthesized templates (enabled by default)', - default: undefined, }) .option('path-metadata', { + default: undefined, type: 'boolean', desc: 'Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)', - default: undefined, }) .option('asset-metadata', { + default: undefined, type: 'boolean', desc: 'Include "aws:asset:*" CloudFormation metadata for resources that uses assets (enabled by default)', - default: undefined, }) .option('role-arn', { + default: undefined, type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', - default: undefined, requiresArg: true, }) .option('staging', { + default: true, type: 'boolean', desc: 'Copy assets to the output directory (use --no-staging to disable the copy of assets which allows local debugging via the SAM CLI to reference the original source files)', - default: true, }) .option('output', { + default: undefined, type: 'string', alias: 'o', desc: 'Emits the synthesized cloud assembly into a directory (default: cdk.out)', requiresArg: true, }) .option('notices', { + default: undefined, type: 'boolean', desc: 'Show relevant notices', }) .option('no-color', { + default: false, type: 'boolean', desc: 'Removes colors and other style from console output', - default: false, }) .option('ci', { + default: helpers.isCI(), type: 'boolean', desc: 'Force CI detection. If CI=true then logs will be sent to stdout instead of stderr', - default: helpers.isCI(), }) .option('unstable', { + default: [], type: 'array', desc: 'Opt in to unstable features. The flag indicates that the scope and API of a feature might still change. Otherwise the feature is generally production ready and fully supported. Can be specified multiple times.', - default: [], nargs: 1, requiresArg: true, }) .command(['list [STACKS..]', 'ls [STACKS..]'], 'Lists all stacks in the app', (yargs: Argv) => yargs .option('long', { - type: 'boolean', default: false, + type: 'boolean', alias: 'l', desc: 'Display environment information for each stack', }) .option('show-dependencies', { - type: 'boolean', default: false, + type: 'boolean', alias: 'd', desc: 'Display stack dependency information for each stack', - }) + }), ) .command(['synthesize [STACKS..]', 'synth [STACKS..]'], 'Synthesizes and prints the CloudFormation template for this stack', (yargs: Argv) => yargs .option('exclusively', { + default: undefined, type: 'boolean', alias: 'e', desc: "Only synthesize requested stacks, don't include dependencies", }) .option('validation', { + default: true, type: 'boolean', desc: 'After synthesis, validate stacks with the "validateOnSynth" attribute set (can also be controlled with CDK_VALIDATION)', - default: true, }) .option('quiet', { + default: false, type: 'boolean', alias: 'q', desc: 'Do not output CloudFormation Template to stdout', - default: false, - }) + }), ) .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', (yargs: Argv) => yargs .option('bootstrap-bucket-name', { + default: undefined, type: 'string', alias: ['b', 'toolkit-bucket-name'], desc: 'The name of the CDK toolkit bucket; bucket will be created and must not exist', - default: undefined, }) .option('bootstrap-kms-key-id', { + default: undefined, type: 'string', desc: 'AWS KMS master key ID used for the SSE-KMS encryption', - default: undefined, conflicts: 'bootstrap-customer-key', }) .option('example-permissions-boundary', { + default: undefined, type: 'boolean', alias: 'epb', desc: 'Use the example permissions boundary.', - default: undefined, conflicts: 'custom-permissions-boundary', }) .option('custom-permissions-boundary', { + default: undefined, type: 'string', alias: 'cpb', desc: 'Use the permissions boundary specified by name.', - default: undefined, conflicts: 'example-permissions-boundary', }) .option('bootstrap-customer-key', { + default: undefined, type: 'boolean', desc: 'Create a Customer Master Key (CMK) for the bootstrap bucket (you will be charged but can customize permissions, modern bootstrapping only)', - default: undefined, conflicts: 'bootstrap-kms-key-id', }) .option('qualifier', { + default: undefined, type: 'string', desc: 'String which must be unique for each bootstrap stack. You must configure it on your CDK app if you change this from the default.', - default: undefined, }) .option('public-access-block-configuration', { + default: undefined, type: 'boolean', desc: 'Block public access configuration on CDK toolkit bucket (enabled by default) ', - default: undefined, }) .option('tags', { + default: [], type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', - default: [], nargs: 1, requiresArg: true, }) .option('execute', { + default: true, type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', - default: true, }) .option('trust', { + default: [], type: 'array', desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated, modern bootstrapping only)', - default: [], nargs: 1, requiresArg: true, }) .option('trust-for-lookup', { + default: [], type: 'array', desc: 'The AWS account IDs that should be trusted to look up values in this environment (may be repeated, modern bootstrapping only)', - default: [], nargs: 1, requiresArg: true, }) .option('cloudformation-execution-policies', { + default: [], type: 'array', desc: 'The Managed Policy ARNs that should be attached to the role performing deployments into this environment (may be repeated, modern bootstrapping only)', - default: [], nargs: 1, requiresArg: true, }) .option('force', { + default: false, alias: 'f', type: 'boolean', desc: 'Always bootstrap even if it would downgrade template version', - default: false, }) .option('termination-protection', { - type: 'boolean', default: undefined, + type: 'boolean', desc: 'Toggle CloudFormation termination protection on the bootstrap stacks', }) .option('show-template', { + default: false, type: 'boolean', desc: "Instead of actual bootstrapping, print the current CLI's bootstrapping template to stdout for customization", - default: false, }) .option('toolkit-stack-name', { + default: undefined, type: 'string', desc: 'The name of the CDK toolkit stack to create', requiresArg: true, }) .option('template', { + default: undefined, type: 'string', requiresArg: true, desc: 'Use the template from the given file instead of the built-in one (use --show-template to obtain an example)', }) .option('previous-parameters', { - type: 'boolean', default: true, + type: 'boolean', desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)', - }) + }), ) .command( 'gc [ENVIRONMENTS..]', @@ -296,57 +310,60 @@ export function parseCommandLineArguments(args: Array): any { (yargs: Argv) => yargs .option('action', { + default: 'full', type: 'string', desc: 'The action (or sub-action) you want to perform. Valid entires are "print", "tag", "delete-tagged", "full".', - default: 'full', }) .option('type', { + default: 'all', type: 'string', desc: 'Specify either ecr, s3, or all', - default: 'all', }) .option('rollback-buffer-days', { + default: 0, type: 'number', desc: 'Delete assets that have been marked as isolated for this many days', - default: 0, }) .option('created-buffer-days', { + default: 1, type: 'number', desc: 'Never delete assets younger than this (in days)', - default: 1, }) .option('confirm', { + default: true, type: 'boolean', desc: 'Confirm via manual prompt before deletion', - default: true, }) .option('bootstrap-stack-name', { + default: undefined, type: 'string', desc: 'The name of the CDK toolkit stack, if different from the default "CDKToolkit"', requiresArg: true, - }) + }), ) .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', (yargs: Argv) => yargs .option('all', { + default: false, type: 'boolean', desc: 'Deploy all available stacks', - default: false, }) .option('build-exclude', { + default: [], type: 'array', alias: 'E', desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', - default: [], nargs: 1, requiresArg: true, }) .option('exclusively', { + default: undefined, type: 'boolean', alias: 'e', desc: "Only deploy requested stacks, don't include dependencies", }) .option('require-approval', { + default: undefined, type: 'string', choices: ['never', 'any-change', 'broadening'], desc: 'What security-sensitive changes need manual approval', @@ -358,6 +375,7 @@ export function parseCommandLineArguments(args: Array): any { requiresArg: true, }) .option('tags', { + default: [], type: 'array', alias: 't', desc: 'Tags to add to the stack (KEY=VALUE), overrides tags from Cloud Assembly (deprecated)', @@ -365,15 +383,18 @@ export function parseCommandLineArguments(args: Array): any { requiresArg: true, }) .option('execute', { + default: undefined, type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet) (deprecated)', deprecated: true, }) .option('change-set-name', { + default: undefined, type: 'string', desc: 'Name of the CloudFormation change set to create (only if method is not direct)', }) .option('method', { + default: undefined, alias: 'm', type: 'string', choices: ['direct', 'change-set', 'prepare-change-set'], @@ -381,228 +402,254 @@ export function parseCommandLineArguments(args: Array): any { desc: 'How to perform the deployment. Direct is a bit faster but lacks progress information', }) .option('force', { + default: false, alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', - default: false, }) .option('parameters', { + default: {}, type: 'array', desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)', - default: {}, nargs: 1, requiresArg: true, }) .option('outputs-file', { + default: undefined, type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true, }) .option('previous-parameters', { - type: 'boolean', default: true, + type: 'boolean', desc: 'Use previous values for existing parameters (you must specify all parameters on every deployment if this is disabled)', }) .option('toolkit-stack-name', { + default: undefined, type: 'string', desc: 'The name of the existing CDK toolkit stack (only used for app using legacy synthesis)', requiresArg: true, }) .option('progress', { + default: undefined, type: 'string', choices: ['bar', 'events'], desc: 'Display mode for stack activity events', }) .option('rollback', { + default: undefined, type: 'boolean', desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. Note: do **not** disable this flag for deployments with resource replacements, as that will always fail", }) .option('R', { type: 'boolean', hidden: true }) .middleware(helpers.yargsNegativeAlias('R', 'rollback'), true) .option('hotswap', { + default: undefined, type: 'boolean', desc: "Attempts to perform a 'hotswap' deployment, but does not fall back to a full deployment if that is not possible. Instead, changes to any non-hotswappable properties are ignored.Do not use this in production environments", }) .option('hotswap-fallback', { + default: undefined, type: 'boolean', desc: "Attempts to perform a 'hotswap' deployment, which skips CloudFormation and updates the resources directly, and falls back to a full deployment if that is not possible. Do not use this in production environments", }) .option('watch', { + default: undefined, type: 'boolean', desc: 'Continuously observe the project files, and deploy the given stack(s) automatically when changes are detected. Implies --hotswap by default', }) .option('logs', { - type: 'boolean', default: true, + type: 'boolean', desc: "Show CloudWatch log events from all resources in the selected Stacks in the terminal. 'true' by default, use --no-logs to turn off. Only in effect if specified alongside the '--watch' option", }) .option('concurrency', { + default: 1, type: 'number', desc: 'Maximum number of simultaneous deployments (dependency permitting) to execute.', - default: 1, requiresArg: true, }) .option('asset-parallelism', { + default: undefined, type: 'boolean', desc: 'Whether to build/publish assets in parallel', }) .option('asset-prebuild', { + default: true, type: 'boolean', desc: 'Whether to build all assets before deploying the first stack (useful for failing Docker builds)', - default: true, }) .option('ignore-no-stacks', { + default: false, type: 'boolean', desc: 'Whether to deploy if the app contains no stacks', - default: false, - }) + }), ) .command('rollback [STACKS..]', 'Rolls back the stack(s) named STACKS to their last stable state', (yargs: Argv) => yargs .option('all', { - type: 'boolean', default: false, + type: 'boolean', desc: 'Roll back all available stacks', }) .option('toolkit-stack-name', { + default: undefined, type: 'string', desc: 'The name of the CDK toolkit stack the environment is bootstrapped with', requiresArg: true, }) .option('force', { + default: undefined, alias: 'f', type: 'boolean', desc: 'Orphan all resources for which the rollback operation fails.', }) .option('validate-bootstrap-version', { + default: undefined, type: 'boolean', desc: "Whether to validate the bootstrap stack version. Defaults to 'true', disable with --no-validate-bootstrap-version.", }) .option('orphan', { + default: [], type: 'array', desc: 'Orphan the given resources, identified by their logical ID (can be specified multiple times)', - default: [], nargs: 1, requiresArg: true, - }) + }), ) .command('import [STACK]', 'Import existing resource(s) into the given STACK', (yargs: Argv) => yargs .option('execute', { + default: true, type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', - default: true, }) .option('change-set-name', { + default: undefined, type: 'string', desc: 'Name of the CloudFormation change set to create', }) .option('toolkit-stack-name', { + default: undefined, type: 'string', desc: 'The name of the CDK toolkit stack to create', requiresArg: true, }) .option('rollback', { + default: undefined, type: 'boolean', desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. Note: do **not** disable this flag for deployments with resource replacements, as that will always fail", }) .option('force', { + default: undefined, alias: 'f', type: 'boolean', desc: "Do not abort if the template diff includes updates or deletes. This is probably safe but we're not sure, let us know how it goes.", }) .option('record-resource-mapping', { + default: undefined, type: 'string', alias: 'r', requiresArg: true, desc: 'If specified, CDK will generate a mapping of existing physical resources to CDK resources to be imported as. The mapping will be written in the given file path. No actual import operation will be performed', }) .option('resource-mapping', { + default: undefined, type: 'string', alias: 'm', requiresArg: true, desc: 'If specified, CDK will use the given file to map physical resources to CDK resources for import, instead of interactively asking the user. Can be run from scripts', - }) + }), ) .command('watch [STACKS..]', "Shortcut for 'deploy --watch'", (yargs: Argv) => yargs .option('build-exclude', { + default: [], type: 'array', alias: 'E', desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', - default: [], nargs: 1, requiresArg: true, }) .option('exclusively', { + default: undefined, type: 'boolean', alias: 'e', desc: "Only deploy requested stacks, don't include dependencies", }) .option('change-set-name', { + default: undefined, type: 'string', desc: 'Name of the CloudFormation change set to create', }) .option('force', { + default: false, alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', - default: false, }) .option('toolkit-stack-name', { + default: undefined, type: 'string', desc: 'The name of the existing CDK toolkit stack (only used for app using legacy synthesis)', requiresArg: true, }) .option('progress', { + default: undefined, type: 'string', choices: ['bar', 'events'], desc: 'Display mode for stack activity events', }) .option('rollback', { + default: undefined, type: 'boolean', desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. Note: do **not** disable this flag for deployments with resource replacements, as that will always fail", }) .option('R', { type: 'boolean', hidden: true }) .middleware(helpers.yargsNegativeAlias('R', 'rollback'), true) .option('hotswap', { + default: undefined, type: 'boolean', desc: "Attempts to perform a 'hotswap' deployment, but does not fall back to a full deployment if that is not possible. Instead, changes to any non-hotswappable properties are ignored.'true' by default, use --no-hotswap to turn off", }) .option('hotswap-fallback', { + default: undefined, type: 'boolean', desc: "Attempts to perform a 'hotswap' deployment, which skips CloudFormation and updates the resources directly, and falls back to a full deployment if that is not possible.", }) .option('logs', { - type: 'boolean', default: true, + type: 'boolean', desc: "Show CloudWatch log events from all resources in the selected Stacks in the terminal. 'true' by default, use --no-logs to turn off", }) .option('concurrency', { + default: 1, type: 'number', desc: 'Maximum number of simultaneous deployments (dependency permitting) to execute.', - default: 1, requiresArg: true, - }) + }), ) .command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', (yargs: Argv) => yargs .option('all', { - type: 'boolean', default: false, + type: 'boolean', desc: 'Destroy all available stacks', }) .option('exclusively', { + default: undefined, type: 'boolean', alias: 'e', desc: "Only destroy requested stacks, don't include dependees", }) .option('force', { + default: undefined, type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks', - }) + }), ) .command( 'diff [STACKS..]', @@ -610,159 +657,173 @@ export function parseCommandLineArguments(args: Array): any { (yargs: Argv) => yargs .option('exclusively', { + default: undefined, type: 'boolean', alias: 'e', desc: "Only diff requested stacks, don't include dependencies", }) .option('context-lines', { + default: 3, type: 'number', desc: 'Number of context lines to include in arbitrary JSON diff rendering', - default: 3, requiresArg: true, }) .option('template', { + default: undefined, type: 'string', desc: 'The path to the CloudFormation template to compare with', requiresArg: true, }) .option('strict', { + default: false, type: 'boolean', desc: 'Do not filter out AWS::CDK::Metadata resources, mangled non-ASCII characters, or the CheckBootstrapVersionRule', - default: false, }) .option('security-only', { + default: false, type: 'boolean', desc: 'Only diff for broadened security changes', - default: false, }) .option('fail', { + default: undefined, type: 'boolean', desc: 'Fail with exit code 1 in case of diff', }) .option('processed', { + default: false, type: 'boolean', desc: 'Whether to compare against the template with Transforms already processed', - default: false, }) .option('quiet', { + default: false, type: 'boolean', alias: 'q', desc: 'Do not print stack name and default message when there is no diff to stdout', - default: false, }) .option('change-set', { + default: true, type: 'boolean', alias: 'changeset', desc: 'Whether to create a changeset to analyze resource replacements. In this mode, diff will use the deploy role instead of the lookup role.', - default: true, - }) + }), ) .command('metadata [STACK]', 'Returns all metadata associated with this stack') .command(['acknowledge [ID]', 'ack [ID]'], 'Acknowledge a notice so that it does not show up anymore') .command('notices', 'Returns a list of relevant notices', (yargs: Argv) => yargs.option('unacknowledged', { + default: false, type: 'boolean', alias: 'u', - default: false, desc: 'Returns a list of unacknowledged notices', - }) + }), ) .command('init [TEMPLATE]', 'Create a new, empty CDK project from a template.', (yargs: Argv) => yargs .option('language', { + default: undefined, type: 'string', alias: 'l', desc: 'The language to be used for the new project (default can be configured in ~/.cdk.json)', choices: ['csharp', 'fsharp', 'go', 'java', 'javascript', 'python', 'typescript'], }) .option('list', { + default: undefined, type: 'boolean', desc: 'List the available templates', }) .option('generate-only', { - type: 'boolean', default: false, + type: 'boolean', desc: 'If true, only generates project files, without executing additional operations such as setting up a git repo, installing dependencies or compiling the project', - }) + }), ) .command('migrate', 'Migrate existing AWS resources into a CDK app', (yargs: Argv) => yargs .option('stack-name', { + default: undefined, type: 'string', alias: 'n', desc: 'The name assigned to the stack created in the new project. The name of the app will be based off this name as well.', requiresArg: true, }) .option('language', { - type: 'string', default: 'typescript', + type: 'string', alias: 'l', desc: 'The language to be used for the new project', choices: ['typescript', 'go', 'java', 'python', 'csharp'], }) .option('account', { + default: undefined, type: 'string', desc: 'The account to retrieve the CloudFormation stack template from', }) .option('region', { + default: undefined, type: 'string', desc: 'The region to retrieve the CloudFormation stack template from', }) .option('from-path', { + default: undefined, type: 'string', desc: 'The path to the CloudFormation template to migrate. Use this for locally stored templates', }) .option('from-stack', { + default: undefined, type: 'boolean', desc: 'Use this flag to retrieve the template for an existing CloudFormation stack', }) .option('output-path', { + default: undefined, type: 'string', desc: 'The output path for the migrated CDK app', }) .option('from-scan', { + default: undefined, type: 'string', desc: 'Determines if a new scan should be created, or the last successful existing scan should be used \n options are "new" or "most-recent"', }) .option('filter', { + default: [], type: 'array', desc: 'Filters the resource scan based on the provided criteria in the following format: "key1=value1,key2=value2"\n This field can be passed multiple times for OR style filtering: \n filtering options: \n resource-identifier: A key-value pair that identifies the target resource. i.e. {"ClusterName", "myCluster"}\n resource-type-prefix: A string that represents a type-name prefix. i.e. "AWS::DynamoDB::"\n tag-key: a string that matches resources with at least one tag with the provided key. i.e. "myTagKey"\n tag-value: a string that matches resources with at least one tag with the provided value. i.e. "myTagValue"', nargs: 1, requiresArg: true, }) .option('compress', { + default: undefined, type: 'boolean', desc: 'Use this flag to zip the generated CDK app', - }) + }), ) .command('context', 'Manage cached context values', (yargs: Argv) => yargs .option('reset', { + default: undefined, alias: 'e', desc: 'The context key (or its index) to reset', type: 'string', requiresArg: true, - default: undefined, }) .option('force', { + default: false, alias: 'f', desc: 'Ignore missing key error', type: 'boolean', - default: false, }) .option('clear', { + default: false, desc: 'Clear all context', type: 'boolean', - default: false, - }) + }), ) .command(['docs', 'doc'], 'Opens the reference documentation in a browser', (yargs: Argv) => yargs.option('browser', { + default: helpers.browserForPlatform(), alias: 'b', desc: 'the command to use to open the browser, using %u as a placeholder for the path of the file to open', type: 'string', - default: helpers.browserForPlatform(), - }) + }), ) .command('doctor', 'Check your set-up for potential problems') .version(helpers.cliVersion()) @@ -771,7 +832,7 @@ export function parseCommandLineArguments(args: Array): any { .help() .alias('h', 'help') .epilogue( - 'If your app has a single stack, there is no need to specify the stack name\n\nIf one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.' + 'If your app has a single stack, there is no need to specify the stack name\n\nIf one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.', ) .parse(args); } // eslint-disable-next-line @typescript-eslint/no-require-imports diff --git a/tools/@aws-cdk/cli-args-gen/lib/cli-type-gen.ts b/tools/@aws-cdk/cli-args-gen/lib/cli-type-gen.ts index 7c92fd8c65fd4..a250415035cd7 100644 --- a/tools/@aws-cdk/cli-args-gen/lib/cli-type-gen.ts +++ b/tools/@aws-cdk/cli-args-gen/lib/cli-type-gen.ts @@ -1,6 +1,7 @@ import { Module, SelectiveModuleImport, StructType, Type, TypeScriptRenderer } from '@cdklabs/typewriter'; import { EsLintRules } from '@cdklabs/typewriter/lib/eslint-rules'; import * as prettier from 'prettier'; +import { generateDefault } from './util'; import { CliConfig } from './yargs-types'; export async function renderCliType(config: CliConfig): Promise { @@ -44,7 +45,7 @@ export async function renderCliType(config: CliConfig): Promise { name: optionName, type: convertType(option.type), docs: { - default: normalizeDefault(option.default), + default: normalizeDefault(option.default, option.type), summary: option.desc, deprecated: option.deprecated ? String(option.deprecated) : undefined, }, @@ -76,7 +77,8 @@ export async function renderCliType(config: CliConfig): Promise { name: optionName, type: convertType(option.type), docs: { - default: normalizeDefault(option.default), + // Notification Arns is a special property where undefined and [] mean different things + default: optionName === 'notification-arns' ? 'undefined' : normalizeDefault(option.default, option.type), summary: option.desc, deprecated: option.deprecated ? String(option.deprecated) : undefined, remarks: option.alias ? `aliases: ${Array.isArray(option.alias) ? option.alias.join(' ') : option.alias}` : undefined, @@ -129,9 +131,20 @@ function kebabToPascal(str: string): string { .join(''); } -function normalizeDefault(defaultValue: any): string { - if (typeof defaultValue === 'boolean' || typeof defaultValue === 'string') { - return String(defaultValue); +function normalizeDefault(defaultValue: any, type: string): string { + switch (typeof defaultValue) { + case 'boolean': + case 'string': + case 'number': + case 'object': + return JSON.stringify(defaultValue); + + // In these cases we cannot use the given defaultValue, so we then check the type + // of the option to determine the default value + case 'undefined': + case 'function': + default: + const generatedDefault = generateDefault(type); + return generatedDefault ? JSON.stringify(generatedDefault) : 'undefined'; } - return 'undefined'; } diff --git a/tools/@aws-cdk/cli-args-gen/lib/util.ts b/tools/@aws-cdk/cli-args-gen/lib/util.ts new file mode 100644 index 0000000000000..047c2498283c6 --- /dev/null +++ b/tools/@aws-cdk/cli-args-gen/lib/util.ts @@ -0,0 +1,3 @@ +export function generateDefault(type: string) { + return type === 'array' ? [] : undefined; +} diff --git a/tools/@aws-cdk/cli-args-gen/lib/yargs-gen.ts b/tools/@aws-cdk/cli-args-gen/lib/yargs-gen.ts index ff7ab0efc3973..fee7fdaa61e3d 100644 --- a/tools/@aws-cdk/cli-args-gen/lib/yargs-gen.ts +++ b/tools/@aws-cdk/cli-args-gen/lib/yargs-gen.ts @@ -1,5 +1,7 @@ import { $E, Expression, ExternalModule, FreeFunction, IScope, Module, SelectiveModuleImport, Statement, ThingSymbol, Type, TypeScriptRenderer, code, expr } from '@cdklabs/typewriter'; +import { EsLintRules } from '@cdklabs/typewriter/lib/eslint-rules'; import * as prettier from 'prettier'; +import { generateDefault } from './util'; import { CliConfig, CliOption, YargsOption } from './yargs-types'; // to import lodash.clonedeep properly, we would need to set esModuleInterop: true @@ -45,19 +47,14 @@ export async function renderYargs(config: CliConfig, helpers: CliHelpers): Promi parseCommandLineArguments.addBody(makeYargs(config, helpers)); const ts = new TypeScriptRenderer({ - disabledEsLintRules: [ - '@stylistic/comma-dangle', - '@stylistic/comma-spacing', - '@stylistic/max-len', - '@stylistic/quotes', - '@stylistic/quote-props', - ] as any, // Force our string[] into EsLintRules[], it will work out at runtime + disabledEsLintRules: [EsLintRules.MAX_LEN], // the default disabled rules result in 'Definition for rule 'prettier/prettier' was not found' }).render(scope); return prettier.format(ts, { parser: 'typescript', printWidth: 150, singleQuote: true, + trailingComma: 'all', }); } @@ -116,7 +113,13 @@ function makeYargs(config: CliConfig, helpers: CliHelpers): Statement { function makeOptions(prefix: Expression, options: { [optionName: string]: CliOption }, helpers: CliHelpers) { let optionsExpr = prefix; for (const option of Object.keys(options)) { - const theOption: CliOption = options[option]; + const theOption: CliOption = { + // Make the default explicit (overridden if the option includes an actual default) + // 'notification-arns' is a special snowflake that should be defaulted to 'undefined', but https://github.com/yargs/yargs/issues/2443 + // prevents us from doing so. This should be changed if the issue is resolved. + ...(option === 'notification-arns' ? {} : { default: generateDefault(options[option].type) }), + ...options[option], + }; const optionProps: YargsOption = cloneDeep(theOption); const optionArgs: { [key: string]: Expression } = {}; diff --git a/tools/@aws-cdk/cli-args-gen/test/cli-type-gen.test.ts b/tools/@aws-cdk/cli-args-gen/test/cli-type-gen.test.ts index c3719aa4b9149..74d21f47ecb43 100644 --- a/tools/@aws-cdk/cli-args-gen/test/cli-type-gen.test.ts +++ b/tools/@aws-cdk/cli-args-gen/test/cli-type-gen.test.ts @@ -13,6 +13,16 @@ describe('render', () => { desc: 'Enable debug logging', default: false, }, + context: { + default: [], + type: 'array', + alias: 'c', + desc: 'context values', + }, + plugin: { + type: 'array', + desc: 'plugins to load', + }, }, commands: { deploy: { @@ -77,6 +87,20 @@ describe('render', () => { * @default - false */ readonly debug?: boolean; + + /** + * context values + * + * @default - [] + */ + readonly context?: Array; + + /** + * plugins to load + * + * @default - [] + */ + readonly plugin?: Array; } /** @@ -95,4 +119,85 @@ describe('render', () => { " `); }); + + test('special notification-arn option gets undefined default', async () => { + const config: CliConfig = { + commands: { + deploy: { + description: 'Notification Arns', + options: { + ['notification-arns']: { + type: 'array', + desc: 'Deploy all stacks', + }, + ['other-array']: { + type: 'array', + desc: 'Other array', + }, + }, + }, + }, + globalOptions: {}, + }; + + expect(await renderCliType(config)).toMatchInlineSnapshot(` + "// ------------------------------------------------------------------------------------------- + // GENERATED FROM packages/aws-cdk/lib/config.ts. + // Do not edit by hand; all changes will be overwritten at build time from the config file. + // ------------------------------------------------------------------------------------------- + /* eslint-disable max-len */ + import { Command } from './settings'; + + /** + * The structure of the CLI configuration, generated from packages/aws-cdk/lib/config.ts + * + * @struct + */ + export interface CliArguments { + /** + * The CLI command name followed by any properties of the command + */ + readonly _: [Command, ...string[]]; + + /** + * Global options available to all CLI commands + */ + readonly globalOptions?: GlobalOptions; + + /** + * Notification Arns + */ + readonly deploy?: DeployOptions; + } + + /** + * Global options available to all CLI commands + * + * @struct + */ + export interface GlobalOptions {} + + /** + * Notification Arns + * + * @struct + */ + export interface DeployOptions { + /** + * Deploy all stacks + * + * @default - undefined + */ + readonly 'notification-arns'?: Array; + + /** + * Other array + * + * @default - [] + */ + readonly 'other-array'?: Array; + } + " + `); + }); }); diff --git a/tools/@aws-cdk/cli-args-gen/test/yargs-gen.test.ts b/tools/@aws-cdk/cli-args-gen/test/yargs-gen.test.ts index 45820f41aeff1..722d7d7807225 100644 --- a/tools/@aws-cdk/cli-args-gen/test/yargs-gen.test.ts +++ b/tools/@aws-cdk/cli-args-gen/test/yargs-gen.test.ts @@ -28,7 +28,7 @@ describe('render', () => { // GENERATED FROM packages/aws-cdk/lib/config.ts. // Do not edit by hand; all changes will be overwritten at build time from the config file. // ------------------------------------------------------------------------------------------- - /* eslint-disable @stylistic/comma-dangle, @stylistic/comma-spacing, @stylistic/max-len, @stylistic/quotes, @stylistic/quote-props */ + /* eslint-disable max-len */ import { Argv } from 'yargs'; import * as helpers from './util/yargs-helpers'; @@ -38,16 +38,19 @@ describe('render', () => { .env('CDK') .usage('Usage: cdk -a COMMAND') .option('one', { + default: undefined, type: 'string', alias: 'o', desc: 'text for one', requiresArg: true, }) .option('two', { + default: undefined, type: 'number', desc: 'text for two', }) .option('three', { + default: [], type: 'array', alias: 't', desc: 'text for three', @@ -60,7 +63,7 @@ describe('render', () => { .help() .alias('h', 'help') .epilogue( - 'If your app has a single stack, there is no need to specify the stack name\\n\\nIf one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.' + 'If your app has a single stack, there is no need to specify the stack name\\n\\nIf one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.', ) .parse(args); } // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -92,7 +95,7 @@ describe('render', () => { // GENERATED FROM packages/aws-cdk/lib/config.ts. // Do not edit by hand; all changes will be overwritten at build time from the config file. // ------------------------------------------------------------------------------------------- - /* eslint-disable @stylistic/comma-dangle, @stylistic/comma-spacing, @stylistic/max-len, @stylistic/quotes, @stylistic/quote-props */ + /* eslint-disable max-len */ import { Argv } from 'yargs'; import * as helpers from './util/yargs-helpers'; @@ -104,12 +107,13 @@ describe('render', () => { .command('test', 'the action under test', (yargs: Argv) => yargs .option('one', { + default: undefined, type: 'boolean', alias: 'o', desc: 'text for one', }) .option('O', { type: 'boolean', hidden: true }) - .middleware(helpers.yargsNegativeAlias('O', 'one'), true) + .middleware(helpers.yargsNegativeAlias('O', 'one'), true), ) .version(helpers.cliVersion()) .demandCommand(1, '') @@ -117,7 +121,7 @@ describe('render', () => { .help() .alias('h', 'help') .epilogue( - 'If your app has a single stack, there is no need to specify the stack name\\n\\nIf one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.' + 'If your app has a single stack, there is no need to specify the stack name\\n\\nIf one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.', ) .parse(args); } // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -150,4 +154,69 @@ describe('render', () => { 'default: helpers.banana(1, 2, 3)', ); }); + + test('special notification-arn option gets NO default value', async () => { + const config: CliConfig = { + commands: { + deploy: { + description: 'Notification Arns', + options: { + ['notification-arns']: { + type: 'array', + desc: 'Deploy all stacks', + }, + ['other-array']: { + type: 'array', + desc: 'Other array', + }, + }, + }, + }, + globalOptions: {}, + }; + + expect(await renderYargs(config, YARGS_HELPERS)).toMatchInlineSnapshot(` + "// ------------------------------------------------------------------------------------------- + // GENERATED FROM packages/aws-cdk/lib/config.ts. + // Do not edit by hand; all changes will be overwritten at build time from the config file. + // ------------------------------------------------------------------------------------------- + /* eslint-disable max-len */ + import { Argv } from 'yargs'; + import * as helpers from './util/yargs-helpers'; + + // @ts-ignore TS6133 + export function parseCommandLineArguments(args: Array): any { + return yargs + .env('CDK') + .usage('Usage: cdk -a COMMAND') + .command('deploy', 'Notification Arns', (yargs: Argv) => + yargs + .option('notification-arns', { + type: 'array', + desc: 'Deploy all stacks', + nargs: 1, + requiresArg: true, + }) + .option('other-array', { + default: [], + type: 'array', + desc: 'Other array', + nargs: 1, + requiresArg: true, + }), + ) + .version(helpers.cliVersion()) + .demandCommand(1, '') + .recommendCommands() + .help() + .alias('h', 'help') + .epilogue( + 'If your app has a single stack, there is no need to specify the stack name\\n\\nIf one of cdk.json or ~/.cdk.json exists, options specified there will be used as defaults. Settings in cdk.json take precedence.', + ) + .parse(args); + } // eslint-disable-next-line @typescript-eslint/no-require-imports + const yargs = require('yargs'); + " + `); + }); }); From 982be5c0cc862193080b3c74b531b6183e0e81b1 Mon Sep 17 00:00:00 2001 From: yasuaki640 Date: Fri, 27 Dec 2024 09:15:59 +0900 Subject: [PATCH 8/9] chore(cognito): fix typo of user pool feature plans doc (#32660) ### Issue # (if applicable) Closes # N/A ### Reason for this change Due to the discovery of a document that appears to be a typo. ### Description of changes Perhaps what someone wanted to write was `Learn more about`. ### Describe any new or updated permissions being added ### Description of how you validated changes I consider this unnecessary due to document modification. ### 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* --- packages/aws-cdk-lib/aws-cognito/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-cognito/README.md b/packages/aws-cdk-lib/aws-cognito/README.md index 72bd51cb4ae94..5aa67692d292c 100644 --- a/packages/aws-cdk-lib/aws-cognito/README.md +++ b/packages/aws-cdk-lib/aws-cognito/README.md @@ -79,7 +79,7 @@ userPool.grant(role, 'cognito-idp:AdminCreateUser'); ### User pool feature plans Amazon Cognito has feature plans for user pools. Each plan has a set of features and a monthly cost per active user. Each feature plan unlocks access to more features than the one before it. -Lean more aboug [feature plans here](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-sign-in-feature-plans.html). +Learn more about [feature plans here](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-sign-in-feature-plans.html). - *Lite* - a low-cost feature plan for user pools with lower numbers of monthly active users. - *Essentials* - all of the latest user pool authentication features. From 07e6dd3791acb1870233c98355323e3072376085 Mon Sep 17 00:00:00 2001 From: Matsuda Date: Fri, 27 Dec 2024 09:47:40 +0900 Subject: [PATCH 9/9] chore(config): fix typo in IoT TwinMaker Workspace config rule enum (#32655) ### Issue # (if applicable) N/A ### Reason for this change A Enum of IoT TwinMaker Workspace enum is invalid. `AWS::IoTwinMaker::Workspace` ### Description of changes Update enum value. `AWS::IoTTwinMaker::Workspace` ### Describe any new or updated permissions being added Nothing ### Description of how you validated changes Nothing because only enum name was updated ### 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* --- packages/aws-cdk-lib/aws-config/lib/rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-config/lib/rule.ts b/packages/aws-cdk-lib/aws-config/lib/rule.ts index b13e98c6379bf..9cf824a809138 100644 --- a/packages/aws-cdk-lib/aws-config/lib/rule.ts +++ b/packages/aws-cdk-lib/aws-config/lib/rule.ts @@ -2488,7 +2488,7 @@ export class ResourceType { /** AWS IoT mitigation action */ public static readonly IOT_MITIGATION_ACTION = new ResourceType('AWS::IoT::MitigationAction'); /** AWS IoT TwinMaker workspace */ - public static readonly IOT_TWINMAKER_WORKSPACE = new ResourceType('AWS::IoTwinMaker::Workspace'); + public static readonly IOT_TWINMAKER_WORKSPACE = new ResourceType('AWS::IoTTwinMaker::Workspace'); /** AWS IoT TwinMaker entity */ public static readonly IOT_TWINMAKER_ENTITY = new ResourceType('AWS::IoTTwinMaker::Entity'); /** AWS IoT Analytics datastore */