diff --git a/packages/@aws-cdk/cli-plugin-contract/.eslintrc.js b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.js new file mode 100644 index 0000000000000..b284f20df26e9 --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/.eslintrc.js @@ -0,0 +1,8 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; + +baseConfig.rules['import/no-extraneous-dependencies'] = ['error', { devDependencies: true, peerDependencies: true } ]; +baseConfig.rules['import/order'] = 'off'; +baseConfig.rules['@aws-cdk/invalid-cfn-imports'] = 'off'; + +module.exports = baseConfig; diff --git a/packages/@aws-cdk/cli-plugin-contract/.gitignore b/packages/@aws-cdk/cli-plugin-contract/.gitignore new file mode 100644 index 0000000000000..573a12d965554 --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/.gitignore @@ -0,0 +1,29 @@ +*.js +*.js.map +*.d.ts +*.gz +!lib/init-templates/**/javascript/**/* +node_modules +dist +.jsii +tsconfig.json + +# Generated by generate.sh +build-info.json + +.LAST_BUILD +.nyc_output +coverage +nyc.config.js +.LAST_PACKAGE +*.snk + +assets.json +npm-shrinkwrap.json +!.eslintrc.js +!jest.config.js + +junit.xml + +lib/**/*.wasm +lib/**/*.yaml diff --git a/packages/@aws-cdk/cli-plugin-contract/.npmignore b/packages/@aws-cdk/cli-plugin-contract/.npmignore new file mode 100644 index 0000000000000..5890f9a58f970 --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/.npmignore @@ -0,0 +1,23 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +*.snk + +*.tsbuildinfo + +tsconfig.json +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ +**/*.snapshot diff --git a/packages/@aws-cdk/cli-plugin-contract/LICENSE b/packages/@aws-cdk/cli-plugin-contract/LICENSE new file mode 100644 index 0000000000000..dcf28b52a83af --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/cli-plugin-contract/NOTICE b/packages/@aws-cdk/cli-plugin-contract/NOTICE new file mode 100644 index 0000000000000..9d28b2500bc86 --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/NOTICE @@ -0,0 +1,16 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Third party attributions of this package can be found in the THIRD_PARTY_LICENSES file diff --git a/packages/@aws-cdk/cli-plugin-contract/README.md b/packages/@aws-cdk/cli-plugin-contract/README.md new file mode 100644 index 0000000000000..743925b99fac0 --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/README.md @@ -0,0 +1,59 @@ +# AWS CDK CLI Library + + + +--- + +![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) + +--- + + + +## Overview + +As any piece of software that interacts with an AWS account, the CDK CLI needs +AWS credentials for authentication and authorization. When it comes to choose +which sources to get credentials from, it has +the [same behavior as the AWS CLI][cli-auth]. But this basic behavior may result +in some failure scenarios: + +- The initial set of credentials to work with cannot be obtained. +- The account to which the initial credentials belong to cannot be obtained. +- The account associated to the credentials is different from the account on + which the CLI is trying to operate on. + +Since these failures may happen for valid use case reasons, the CDK CLI offers +an alternative mechanism for users to provide AWS credentials: credential +provider plugins. + +This package defines the types and the contract between the CLI and the plugins, +which plugin authors are expected to adhere to. + +The entrypoint is communicated to the CLI via the `--plugin` command line +argument. The value of this argument should be a JavaScript file that, when +`require`'d, will return an instance of the `Plugin` interface. + +Once the CLI gets an instance of a plugin, it first initializes plugin by +calling the `Plugin.init()` method, if one is defined. The CLI uses this method +to pass an instance of `CredentialProviderSourceRepository` to the plugin. The +plugin, in turn, can use the repository to register one or more instances of +`CredentialProviderSource`, which is where the actual logic for providing +credentials is located. + +If, in the authentication process, the CLI decides to use plugins, it will try +each credential provider source in the order in which they were registered. For +each source, the first thing the CLI will check is whether the source is ready +to interact at all, by calling the `isAvailable()` +method. If it is available, the next check is whether it can provide credentials +for the specific account the CLI is targeting at that moment. This is the +`canProvideCredentials()` method. + +If both checks pass, the CLI asks the source for credentials by calling +`getProvider()`. In addition to the account ID, this method also receives the +`Mode` of operation, which can be `ForReading` or `ForWriting`. This information +may be useful to tailor the credentials for the use case. For example, if the +CLI needs the credentials only for reading, the plugin may return credentials +with more restricted permissions. + +[cli-auth]: (https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-authentication.html) diff --git a/packages/@aws-cdk/cli-plugin-contract/jest.config.js b/packages/@aws-cdk/cli-plugin-contract/jest.config.js new file mode 100644 index 0000000000000..d052cbb29f05d --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/jest.config.js @@ -0,0 +1,10 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + ...baseConfig.coverageThreshold.global, + branches: 60, + }, + }, +}; diff --git a/packages/@aws-cdk/cli-plugin-contract/lib/index.ts b/packages/@aws-cdk/cli-plugin-contract/lib/index.ts new file mode 100644 index 0000000000000..c13924b117f12 --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/lib/index.ts @@ -0,0 +1,115 @@ +/** + * The basic contract for plug-ins to adhere to:: + * + * import { CustomCredentialProviderSource, CredentialProviderSourceRepository, Plugin } from '@aws-cdk/cli-plugin-contract'; + * + * export default class FooCDKPlugIn implements Plugin { + * public readonly version = '1'; + * + * public init(host: CredentialProviderSourceRepository) { + * host.registerCredentialProviderSource(new CustomCredentialProviderSource()); + * } + * } + * + */ +export interface Plugin { + /** + * The version of the plug-in interface used by the plug-in. This will be used by + * the plug-in host to handle version changes. + */ + version: '1'; + + /** + * When defined, this function is invoked right after the plug-in has been loaded, + * so that the plug-in is able to initialize itself. It may call methods of the + * `CredentialProviderSourceRepository` instance it receives to register new + * `CredentialProviderSource` instances. + */ + init?: (host: CredentialProviderSourceRepository) => void; +} + +export interface AwsCredentials { + /** + * AWS access key ID + */ + readonly accessKeyId: string; + + /** + * AWS secret access key + */ + readonly secretAccessKey: string; + + /** + * A security or session token to use with these credentials. Usually + * present for temporary credentials. + */ + readonly sessionToken?: string; + + /** + * AWS credential scope for this set of credentials. + */ + readonly credentialScope?: string; + + /** + * AWS accountId. + */ + readonly accountId?: string; + + /** + * A Date when the identity or credential will no longer be accepted. + */ + readonly expiration?: Date; + + /** + * Refreshes the current credentials. + */ + getPromise?: () => Promise; + + /** + * Returns a Promise of AwsCredentials. + */ + resolvePromise?: () => Promise; +} + +export enum Mode { + ForReading, + ForWriting, +} + +/** + */ +export interface CredentialProviderSource { + name: string; + + /** + * Whether the credential provider is even online + * + * Guaranteed to be called before any of the other functions are called. + */ + isAvailable(): Promise; + + /** + * Whether the credential provider can provide credentials for the given account. + */ + canProvideCredentials(accountId: string): Promise; + + /** + * Construct a credential provider for the given account and the given access mode + * + * Guaranteed to be called only if canProvideCredentails() returned true at some point. + */ + getProvider(accountId: string, mode: Mode): Promise; +} + +/** + * A list of credential provider sources + */ +export interface CredentialProviderSourceRepository { + + /** + * Registers a credential provider source. If, in the authentication process, + * the CLI decides to try credentials from the plugins, it will go through the + * sources registered in this way, in the same order as they were registered. + */ + registerCredentialProviderSource(source: CredentialProviderSource): void; +} diff --git a/packages/@aws-cdk/cli-plugin-contract/package.json b/packages/@aws-cdk/cli-plugin-contract/package.json new file mode 100644 index 0000000000000..860ea3a2597a8 --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/package.json @@ -0,0 +1,57 @@ +{ + "name": "@aws-cdk/cli-plugin-contract", + "description": "Contract between the CLI and authentication plugins, for the exchange of AWS credentials", + "version": "0.0.0", + "types": "lib/index.d.ts", + "main": "lib/index.js", + "scripts": { + "build": "cdk-build", + "lint": "cdk-lint", + "pkglint": "pkglint -f", + "watch": "cdk-watch", + "build+test": "yarn build && yarn test", + "build+extract": "yarn build", + "build+test+package": "yarn build+test && yarn package", + "build+test+extract": "yarn build+test", + "package": "cdk-package", + "test": "cdk-test" + }, + "ubergen": { + "exclude": true + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/pkglint": "0.0.0", + "ts-node": "^10.9.2" + }, + "repository": { + "url": "https://github.com/aws/aws-cdk.git", + "type": "git", + "directory": "packages/@aws-cdk/cli-plugin-contract" + }, + "keywords": [ + "aws", + "cdk" + ], + "homepage": "https://github.com/aws/aws-cdk", + "separate-module": true, + "engines": { + "node": ">= 14.15.0" + }, + "stability": "stable", + "maturity": "stable", + "publishConfig": { + "tag": "latest" + }, + "awscdkio": { + "announce": false + }, + "dependencies": {}, + "peerDependencies": {} +} diff --git a/packages/@aws-cdk/cli-plugin-contract/tsconfig.json b/packages/@aws-cdk/cli-plugin-contract/tsconfig.json new file mode 100644 index 0000000000000..4ff0e9d59a3ad --- /dev/null +++ b/packages/@aws-cdk/cli-plugin-contract/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target":"ES2020", + "module": "commonjs", + "lib": ["es2020", "dom"], + "declaration": true, + "composite": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "strictPropertyInitialization":false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true + }, + "include": ["**/*.ts" ], + "exclude": ["node_modules"] +} 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 06900ef3807cb..824055d1703cb 100644 --- a/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts +++ b/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts @@ -1,6 +1,7 @@ +import { AwsCredentials, CredentialProviderSource, Mode } from '@aws-cdk/cli-plugin-contract'; import type { AwsCredentialIdentity } from '@smithy/types'; import { debug, warning } from '../../logging'; -import { CredentialProviderSource, Mode, PluginHost } from '../plugin'; +import { PluginHost } from '../plugin'; /** * Cache for credential providers. @@ -63,15 +64,15 @@ export class CredentialPlugins { // Backwards compatibility: if the plugin returns a ProviderChain, resolve that chain. // Otherwise it must have returned credentials. - const credentials = (providerOrCreds as any).resolvePromise - ? await (providerOrCreds as any).resolvePromise() - : providerOrCreds; + const credentials = providerOrCreds.resolvePromise + ? await providerOrCreds.resolvePromise() + : providerOrCreds as AwsCredentials; // Another layer of backwards compatibility: in SDK v2, the credentials object // is both a container and a provider. So we need to force the refresh using getPromise. // In SDK v3, these two responsibilities are separate, and the getPromise doesn't exist. - if ((credentials as any).getPromise) { - await (credentials as any).getPromise(); + if (credentials.getPromise) { + await credentials.getPromise(); } return { credentials, pluginName: source.name }; diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 0aded40cfce1f..32008ee20b93a 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -1,4 +1,5 @@ import * as os from 'os'; +import { Mode } from '@aws-cdk/cli-plugin-contract'; import { ContextLookupRoleOptions } from '@aws-cdk/cloud-assembly-schema'; import { Environment, EnvironmentUtils, UNKNOWN_ACCOUNT, UNKNOWN_REGION } from '@aws-cdk/cx-api'; import { AssumeRoleCommandInput } from '@aws-sdk/client-sts'; @@ -11,7 +12,6 @@ import { CredentialPlugins } from './credential-plugins'; import { SDK } from './sdk'; import { debug, warning } from '../../logging'; import { traceMethods } from '../../util/tracing'; -import { Mode } from '../plugin'; export type AssumeRoleAdditionalOptions = Partial>; diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts index 779a056e3e666..59a7e83a62da0 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -1,5 +1,6 @@ import { info } from 'console'; import * as path from 'path'; +import { Mode } from '@aws-cdk/cli-plugin-contract'; import * as cxapi from '@aws-cdk/cx-api'; import type { BootstrapEnvironmentOptions, BootstrappingParameters } from './bootstrap-props'; import { BootstrapStack, bootstrapVersionFromTemplate } from './deploy-bootstrap'; @@ -9,7 +10,6 @@ import { loadStructuredFile, serializeStructure } from '../../serialize'; import { rootDir } from '../../util/directories'; import type { SDK, SdkProvider } from '../aws-auth'; import type { SuccessfulDeployStackResult } from '../deploy-stack'; -import { Mode } from '../plugin'; export type BootstrapSource = { source: 'legacy' } | { source: 'default' } | { source: 'custom'; templateFile: string }; diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index 00ef3be686798..680748aabc9c5 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -1,5 +1,6 @@ import * as os from 'os'; import * as path from 'path'; +import { Mode } from '@aws-cdk/cli-plugin-contract'; import { ArtifactType } from '@aws-cdk/cloud-assembly-schema'; import { CloudAssemblyBuilder, Environment, EnvironmentUtils } from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; @@ -14,7 +15,6 @@ import * as logging from '../../logging'; import type { SDK, SdkProvider } from '../aws-auth'; import { assertIsSuccessfulDeployStackResult, deployStack, SuccessfulDeployStackResult } from '../deploy-stack'; import { NoBootstrapStackEnvironmentResources } from '../environment-resources'; -import { Mode } from '../plugin'; import { DEFAULT_TOOLKIT_STACK_NAME, ToolkitInfo } from '../toolkit-info'; /** diff --git a/packages/aws-cdk/lib/api/environment-access.ts b/packages/aws-cdk/lib/api/environment-access.ts index 1985801c2151e..5074216d2e729 100644 --- a/packages/aws-cdk/lib/api/environment-access.ts +++ b/packages/aws-cdk/lib/api/environment-access.ts @@ -1,9 +1,9 @@ +import { Mode } from '@aws-cdk/cli-plugin-contract'; import * as cxapi from '@aws-cdk/cx-api'; import { SDK } from './aws-auth'; import { warning } from '../logging'; import { CredentialsOptions, SdkForEnvironment, SdkProvider } from './aws-auth/sdk-provider'; import { EnvironmentResources, EnvironmentResourcesRegistry } from './environment-resources'; -import { Mode } from './plugin'; import { replaceEnvPlaceholders, StringWithoutPlaceholders } from './util/placeholders'; /** 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 3bab51095d7f7..7416c417197a9 100644 --- a/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts +++ b/packages/aws-cdk/lib/api/garbage-collection/garbage-collector.ts @@ -1,3 +1,4 @@ +import { Mode } from '@aws-cdk/cli-plugin-contract'; import * as cxapi from '@aws-cdk/cx-api'; import { ImageIdentifier } from '@aws-sdk/client-ecr'; import { Tag } from '@aws-sdk/client-s3'; @@ -8,7 +9,6 @@ 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 { Mode } from '../plugin'; // Must use a require() otherwise esbuild complains // eslint-disable-next-line @typescript-eslint/no-require-imports diff --git a/packages/aws-cdk/lib/api/hotswap-deployments.ts b/packages/aws-cdk/lib/api/hotswap-deployments.ts index 3bfb3cd04c07c..6d058e3c718e1 100644 --- a/packages/aws-cdk/lib/api/hotswap-deployments.ts +++ b/packages/aws-cdk/lib/api/hotswap-deployments.ts @@ -1,3 +1,4 @@ +import { Mode } from '@aws-cdk/cli-plugin-contract'; import * as cfn_diff from '@aws-cdk/cloudformation-diff'; import * as cxapi from '@aws-cdk/cx-api'; import { WaiterResult } from '@smithy/util-waiter'; @@ -27,7 +28,6 @@ import { } from './hotswap/s3-bucket-deployments'; import { isHotswappableStateMachineChange } from './hotswap/stepfunctions-state-machines'; import { NestedStackTemplates, loadCurrentTemplateWithNestedStacks } from './nested-stack-helpers'; -import { Mode } from './plugin'; import { CloudFormationStack } from './util/cloudformation'; // Must use a require() otherwise esbuild complains about calling a namespace diff --git a/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts b/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts index 760f63ad0f4c1..e1d5ca1f9c9f4 100644 --- a/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts +++ b/packages/aws-cdk/lib/api/logs/find-cloudwatch-logs.ts @@ -1,10 +1,10 @@ +import { Mode } from '@aws-cdk/cli-plugin-contract'; import type { CloudFormationStackArtifact, Environment } from '@aws-cdk/cx-api'; import type { StackResourceSummary } from '@aws-sdk/client-cloudformation'; import { debug } from '../../logging'; import type { SDK, SdkProvider } from '../aws-auth'; import { EnvironmentAccess } from '../environment-access'; import { EvaluateCloudFormationTemplate, LazyListStackResources } from '../evaluate-cloudformation-template'; -import { Mode } from '../plugin'; import { DEFAULT_TOOLKIT_STACK_NAME } from '../toolkit-info'; // resource types that have associated CloudWatch Log Groups that should _not_ be monitored diff --git a/packages/aws-cdk/lib/api/plugin/credential-provider-source.ts b/packages/aws-cdk/lib/api/plugin/credential-provider-source.ts deleted file mode 100644 index 78e781ae6e259..0000000000000 --- a/packages/aws-cdk/lib/api/plugin/credential-provider-source.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { AwsCredentialIdentity } from '@smithy/types'; -export enum Mode { - ForReading, - ForWriting, -} - -/** - */ -export interface CredentialProviderSource { - name: string; - - /** - * Whether the credential provider is even online - * - * Guaranteed to be called before any of the other functions are called. - */ - isAvailable(): Promise; - - /** - * Whether the credential provider can provide credentials for the given account. - */ - canProvideCredentials(accountId: string): Promise; - - /** - * Construct a credential provider for the given account and the given access mode - * - * Guaranteed to be called only if canProvideCredentails() returned true at some point. - */ - getProvider(accountId: string, mode: Mode): Promise; -} diff --git a/packages/aws-cdk/lib/api/plugin/index.ts b/packages/aws-cdk/lib/api/plugin/index.ts index 8bf5be8809f41..223d3a34cae6d 100644 --- a/packages/aws-cdk/lib/api/plugin/index.ts +++ b/packages/aws-cdk/lib/api/plugin/index.ts @@ -1,3 +1,2 @@ export * from './plugin'; -export * from './context-provider-plugin'; -export * from './credential-provider-source'; \ No newline at end of file +export * from './context-provider-plugin'; \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/plugin/plugin.ts b/packages/aws-cdk/lib/api/plugin/plugin.ts index 16df596e4cccb..437337125c8f0 100644 --- a/packages/aws-cdk/lib/api/plugin/plugin.ts +++ b/packages/aws-cdk/lib/api/plugin/plugin.ts @@ -1,8 +1,8 @@ import { inspect } from 'util'; +import { CredentialProviderSource, CredentialProviderSourceRepository, Plugin } from '@aws-cdk/cli-plugin-contract'; import * as chalk from 'chalk'; import { type ContextProviderPlugin, isContextProviderPlugin } from './context-provider-plugin'; -import type { CredentialProviderSource } from './credential-provider-source'; import { error } from '../../logging'; export let TESTING = false; @@ -11,42 +11,11 @@ export function markTesting() { TESTING = true; } -/** - * The basic contract for plug-ins to adhere to:: - * - * import { Plugin, PluginHost } from 'aws-cdk'; - * import { CustomCredentialProviderSource } from './custom-credential-provider-source'; - * - * export default class FooCDKPlugIn implements PluginHost { - * public readonly version = '1'; - * - * public init(host: PluginHost) { - * host.registerCredentialProviderSource(new CustomCredentialProviderSource()); - * } - * } - * - */ -export interface Plugin { - /** - * The version of the plug-in interface used by the plug-in. This will be used by - * the plug-in host to handle version changes. - */ - version: '1'; - - /** - * When defined, this function is invoked right after the plug-in has been loaded, - * so that the plug-in is able to initialize itself. It may call methods of the - * ``PluginHost`` instance it receives to register new ``CredentialProviderSource`` - * instances. - */ - init?: (host: PluginHost) => void; -} - /** * A utility to manage plug-ins. * */ -export class PluginHost { +export class PluginHost implements CredentialProviderSourceRepository { public static instance = new PluginHost(); /** diff --git a/packages/aws-cdk/lib/api/util/placeholders.ts b/packages/aws-cdk/lib/api/util/placeholders.ts index 2d38d05db531d..76d9d7c9ed6bc 100644 --- a/packages/aws-cdk/lib/api/util/placeholders.ts +++ b/packages/aws-cdk/lib/api/util/placeholders.ts @@ -1,7 +1,7 @@ +import { Mode } from '@aws-cdk/cli-plugin-contract'; import { type Environment, EnvironmentPlaceholders } from '@aws-cdk/cx-api'; import { Branded } from '../../util/type-brands'; import type { SdkProvider } from '../aws-auth/sdk-provider'; -import { Mode } from '../plugin/credential-provider-source'; /** * Replace the {ACCOUNT} and {REGION} placeholders in all strings found in a complex object. diff --git a/packages/aws-cdk/lib/commands/migrate.ts b/packages/aws-cdk/lib/commands/migrate.ts index 15a9115efbdc0..0c135df06c4d6 100644 --- a/packages/aws-cdk/lib/commands/migrate.ts +++ b/packages/aws-cdk/lib/commands/migrate.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import * as fs from 'fs'; import * as path from 'path'; +import { Mode } from '@aws-cdk/cli-plugin-contract'; import { Environment, UNKNOWN_ACCOUNT, UNKNOWN_REGION } from '@aws-cdk/cx-api'; import type { DescribeGeneratedTemplateCommandOutput, @@ -20,7 +21,6 @@ import * as chalk from 'chalk'; import { cliInit } from '../../lib/init'; import { print } from '../../lib/logging'; import type { ICloudFormationClient, SdkProvider } from '../api/aws-auth'; -import { Mode } from '../api/plugin'; import { CloudFormationStack } from '../api/util/cloudformation'; import { zipDirectory } from '../util/archive'; const camelCase = require('camelcase'); diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index fdc432d9d26c6..2022b5c44dc6f 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -1,3 +1,4 @@ +import { Mode } from '@aws-cdk/cli-plugin-contract'; import { type Environment, UNKNOWN_ACCOUNT, UNKNOWN_REGION } from '@aws-cdk/cx-api'; import { type Account, @@ -14,7 +15,6 @@ import { } from 'cdk-assets'; import type { SDK } from '../api'; import type { SdkProvider } from '../api/aws-auth/sdk-provider'; -import { Mode } from '../api/plugin'; import { debug, error, print } from '../logging'; export interface PublishAssetsOptions { diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index f91e2a9882c9e..8fdeba3ab3f8a 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -69,6 +69,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cli-plugin-contract": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@aws-cdk/yargs-gen": "0.0.0", "@octokit/rest": "^18.12.0", @@ -186,5 +187,8 @@ "maturity": "stable", "publishConfig": { "tag": "latest" + }, + "peerDependencies": { + "@aws-cdk/cli-plugin-contract": "0.0.0" } } diff --git a/packages/aws-cdk/test/api/credential-plugins.test.ts b/packages/aws-cdk/test/api/credential-plugins.test.ts new file mode 100644 index 0000000000000..e59c542aa3ec3 --- /dev/null +++ b/packages/aws-cdk/test/api/credential-plugins.test.ts @@ -0,0 +1,40 @@ +import { AwsCredentials, Mode } from '@aws-cdk/cli-plugin-contract'; +import { CredentialPlugins } from '../../lib/api/aws-auth/credential-plugins'; +import { PluginHost } from '../../lib/api/plugin'; + +test('returns credential from plugin', async () => { + // GIVEN + const creds = { + accessKeyId: 'aaa', + secretAccessKey: 'bbb', + getPromise: () => Promise.resolve(), + }; + const host = PluginHost.instance; + + host.registerCredentialProviderSource({ + name: 'Fake', + + canProvideCredentials(_accountId: string): Promise { + return Promise.resolve(true); + }, + + isAvailable(): Promise { + return Promise.resolve(true); + }, + + getProvider(_accountId: string, _mode: Mode): Promise { + return Promise.resolve(creds); + }, + }); + + const plugins = new CredentialPlugins(); + + // WHEN + const pluginCredentials = await plugins.fetchCredentialsFor('aaa', Mode.ForReading); + + // THEN + expect(pluginCredentials).toEqual({ + credentials: creds, + pluginName: 'Fake', + }); +}); \ No newline at end of file diff --git a/packages/aws-cdk/test/api/plugin/plugin-host.test.ts b/packages/aws-cdk/test/api/plugin/plugin-host.test.ts index c2f96b6afa459..c05db9c001301 100644 --- a/packages/aws-cdk/test/api/plugin/plugin-host.test.ts +++ b/packages/aws-cdk/test/api/plugin/plugin-host.test.ts @@ -1,6 +1,5 @@ -import { ContextProviderPlugin } from '../../../lib/api/plugin/context-provider-plugin'; -import { CredentialProviderSource } from '../../../lib/api/plugin/credential-provider-source'; -import { PluginHost, markTesting } from '../../../lib/api/plugin/plugin'; +import { CredentialProviderSource } from '@aws-cdk/cli-plugin-contract'; +import { ContextProviderPlugin, PluginHost, markTesting } from '../../../lib/api/plugin'; markTesting(); diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index f5b276c17431d..5bc66aff072f0 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -1,5 +1,6 @@ import * as os from 'os'; import { bockfs } from '@aws-cdk/cdk-build-tools'; +import { Mode } from '@aws-cdk/cli-plugin-contract'; import * as cxapi from '@aws-cdk/cx-api'; import { AssumeRoleCommand, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; import * as promptly from 'promptly'; @@ -8,7 +9,7 @@ import { FakeSts, RegisterRoleOptions, RegisterUserOptions } from './fake-sts'; import { ConfigurationOptions, SDK, SdkProvider } from '../../lib/api/aws-auth'; import { AwsCliCompatible } from '../../lib/api/aws-auth/awscli-compatible'; import { defaultCliUserAgent } from '../../lib/api/aws-auth/user-agent'; -import { Mode, PluginHost } from '../../lib/api/plugin'; +import { PluginHost } from '../../lib/api/plugin'; import * as logging from '../../lib/logging'; import { withMocked } from '../util'; import { mockSTSClient, restoreSdkMocksToDefault } from '../util/mock-sdk'; diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index bf23245377dd7..aeccd7929413c 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -60,6 +60,7 @@ jest.setTimeout(30_000); import 'aws-sdk-client-mock'; import * as os from 'os'; import * as path from 'path'; +import { Mode } from '@aws-cdk/cli-plugin-contract'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Manifest } from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; @@ -86,7 +87,6 @@ import { RollbackStackResult, } from '../lib/api/deployments'; import { HotswapMode } from '../lib/api/hotswap/common'; -import { Mode } from '../lib/api/plugin'; import { Template } from '../lib/api/util/cloudformation'; import { CdkToolkit, markTesting, Tag } from '../lib/cdk-toolkit'; import { RequireApproval } from '../lib/diff'; diff --git a/tools/@aws-cdk/pkglint/lib/rules.ts b/tools/@aws-cdk/pkglint/lib/rules.ts index b6a105ad7089e..f28774c4b8353 100644 --- a/tools/@aws-cdk/pkglint/lib/rules.ts +++ b/tools/@aws-cdk/pkglint/lib/rules.ts @@ -1663,6 +1663,7 @@ export class UbergenPackageVisibility extends ValidationRule { // The ONLY (non-alpha) packages that should be published for v2. // These include dependencies of the CDK CLI (aws-cdk). private readonly v2PublicPackages = [ + '@aws-cdk/cli-plugin-contract', '@aws-cdk/cloudformation-diff', '@aws-cdk/cx-api', '@aws-cdk/region-info',