packages/@aws-cdk/toolkit-lib/lib/api/deployments/asset-publishing.ts (156 lines of code) (raw):

import { type Environment, UNKNOWN_ACCOUNT, UNKNOWN_REGION } from '@aws-cdk/cx-api'; import type { ClientOptions, EventType, Account, AssetManifest, IAws, IECRClient, IPublishProgress, IPublishProgressListener, IS3Client, ISecretsManagerClient, } from 'cdk-assets'; import { AssetPublishing, } from 'cdk-assets'; import type { SDK, SdkProvider } from '../aws-auth/private'; import type { IoMessageMaker, IoHelper } from '../io/private'; import { IO } from '../io/private'; import { Mode } from '../plugin'; import { ToolkitError } from '../toolkit-error'; interface PublishAssetsOptions { /** * Whether to build/publish assets in parallel * * @default true To remain backward compatible. */ readonly parallel?: boolean; /** * Whether cdk-assets is allowed to do cross account publishing. */ readonly allowCrossAccount: boolean; } /** * Use cdk-assets to publish all assets in the given manifest. * * @deprecated used in legacy deployments only, should be migrated at some point */ export async function publishAssets( manifest: AssetManifest, sdk: SdkProvider, targetEnv: Environment, options: PublishAssetsOptions, ioHelper: IoHelper, ) { // This shouldn't really happen (it's a programming error), but we don't have // the types here to guide us. Do an runtime validation to be super super sure. if ( targetEnv.account === undefined || targetEnv.account === UNKNOWN_ACCOUNT || targetEnv.region === undefined || targetEnv.account === UNKNOWN_REGION ) { throw new ToolkitError(`Asset publishing requires resolved account and region, got ${JSON.stringify(targetEnv)}`); } const publisher = new AssetPublishing(manifest, { aws: new PublishingAws(sdk, targetEnv), progressListener: new PublishingProgressListener(ioHelper), throwOnError: false, publishInParallel: options.parallel ?? true, buildAssets: true, publishAssets: true, quiet: false, }); await publisher.publish({ allowCrossAccount: options.allowCrossAccount }); if (publisher.hasFailures) { throw new ToolkitError('Failed to publish one or more assets. See the error messages above for more information.'); } } export class PublishingAws implements IAws { private sdkCache: Map<String, SDK> = new Map(); constructor( /** * The base SDK to work with */ private readonly aws: SdkProvider, /** * Environment where the stack we're deploying is going */ private readonly targetEnv: Environment, ) { } public async discoverPartition(): Promise<string> { return (await this.aws.baseCredentialsPartition(this.targetEnv, Mode.ForWriting)) ?? 'aws'; } public async discoverDefaultRegion(): Promise<string> { return this.targetEnv.region; } public async discoverCurrentAccount(): Promise<Account> { const account = await this.aws.defaultAccount(); return ( account ?? { accountId: '<unknown account>', partition: 'aws', } ); } public async discoverTargetAccount(options: ClientOptions): Promise<Account> { return (await this.sdk(options)).currentAccount(); } public async s3Client(options: ClientOptions): Promise<IS3Client> { return (await this.sdk(options)).s3(); } public async ecrClient(options: ClientOptions): Promise<IECRClient> { return (await this.sdk(options)).ecr(); } public async secretsManagerClient(options: ClientOptions): Promise<ISecretsManagerClient> { return (await this.sdk(options)).secretsManager(); } /** * Get an SDK appropriate for the given client options */ private async sdk(options: ClientOptions): Promise<SDK> { const env = { ...this.targetEnv, region: options.region ?? this.targetEnv.region, // Default: same region as the stack }; const cacheKeyMap: any = { env, // region, name, account assumeRuleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, quiet: options.quiet, }; if (options.assumeRoleAdditionalOptions) { cacheKeyMap.assumeRoleAdditionalOptions = options.assumeRoleAdditionalOptions; } const cacheKey = JSON.stringify(cacheKeyMap); const maybeSdk = this.sdkCache.get(cacheKey); if (maybeSdk) { return maybeSdk; } const sdk = ( await this.aws.forEnvironment( env, Mode.ForWriting, { assumeRoleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, assumeRoleAdditionalOptions: options.assumeRoleAdditionalOptions, }, options.quiet, ) ).sdk; this.sdkCache.set(cacheKey, sdk); return sdk; } } const EVENT_TO_MSG_MAKER: Record<EventType, IoMessageMaker<any> | false> = { build: IO.DEFAULT_TOOLKIT_DEBUG, cached: IO.DEFAULT_TOOLKIT_DEBUG, check: IO.DEFAULT_TOOLKIT_DEBUG, debug: IO.DEFAULT_TOOLKIT_DEBUG, fail: IO.DEFAULT_TOOLKIT_ERROR, found: IO.DEFAULT_TOOLKIT_DEBUG, start: IO.DEFAULT_TOOLKIT_INFO, success: IO.DEFAULT_TOOLKIT_INFO, upload: IO.DEFAULT_TOOLKIT_DEBUG, shell_open: IO.DEFAULT_TOOLKIT_DEBUG, shell_stderr: false, shell_stdout: false, shell_close: false, }; export abstract class BasePublishProgressListener implements IPublishProgressListener { protected readonly ioHelper: IoHelper; constructor(ioHelper: IoHelper) { this.ioHelper = ioHelper; } protected abstract getMessage(type: EventType, event: IPublishProgress): string; public onPublishEvent(type: EventType, event: IPublishProgress): void { const maker = EVENT_TO_MSG_MAKER[type]; if (maker) { void this.ioHelper.notify(maker.msg(this.getMessage(type, event))); } } } class PublishingProgressListener extends BasePublishProgressListener { protected getMessage(type: EventType, event: IPublishProgress): string { return `[${event.percentComplete}%] ${type}: ${event.message}`; } }