在其他 App 项目中重用父 CDK Stack
Reuse a parent CDK Stack in other App project
首先,重要的是我没有像往常一样使用 CDK。相反,我正在以编程方式即时创建资源。所以,基本上,我有一个多租户应用程序,它在机上创建了一个客户根堆栈,在客户生命周期内将包含带有资源的嵌套堆栈。
第一次执行的代码是这样的:
import {App, Stack, Construct, NestedStack} from '@aws-cdk/core';
const main = async() => {
const app = new App();
const rootStack = new (class RootStack extends Stack {
constructor() {
super(app, `Customer-123-RootStack`, {});
}
});
const tenantIamStack = new (class CustomerIamNestedClass extends NestedStack {
constructor() {
super(rootStack, `BasicIAM`, {});
const cognitoFederatedPrincipal = new iam.FederatedPrincipal('cognito-identity.amazonaws.com', {
'StringEquals': {
'cognito-identity.amazonaws.com:aud': process.env.SHARED_IDENTITY_POOL_ID
},
'ForAnyValue:StringLike': {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}, 'sts:AssumeRoleWithWebIdentity');
new iam.Role(this, 'IamRoleTenantUser', {
roleName: `Customer-123-TenantUser`,
assumedBy: cognitoFederatedPrincipal,
});
}
});
}
main().then(() => console.log('done'));
我现在想在其他例程中创建的其他堆栈上重用根堆栈 Customer-123-RootStack
。例如,客户将在我们的平台中创建其他 AWS 资源,例如 AWS EventBridge 规则或 ACM 证书。
如果对另一个嵌套栈执行相同的代码,第一个嵌套栈将被删除。
import {App, Stack, Construct, NestedStack} from '@aws-cdk/core';
const main = async() => {
const app = new App();
const rootStack = new (class RootStack extends Stack {
constructor() {
super(app, `Customer-123-RootStack`, {});
}
});
const tenantAcmStack = new (class CustomerAcmNestedClass extends NestedStack {
constructor() {
super(rootStack, `BasicACM`, {});
//create ACM certificate
}
});
}
main().then(() => console.log('done'));
我已阅读此文档,但无法弄清楚该怎么做:https://docs.aws.amazon.com/cdk/v2/guide/resources.html#resources_importing
我可以使用 SDK 获取堆栈,但不明白如何让它与 CDK 一起工作。
编辑:为了让我的问题更清楚:我需要将父现有堆栈(由其他应用程序创建,在其他时刻,在其他代码库中创建)引用到新的嵌套堆栈。
所以我会做的是:
1。创建根堆栈
创建一个核心根堆栈,其中包含您希望客户堆栈使用的公共资源。您将需要 arn/name 您希望在客户堆栈中访问的资源。一种方法是使用 CfnOutput
写入 cdk.context.json
文件:
// Example of outputting an S3 bucket
const myBucket = new Bucket...
new CfnOutput(this, "MyBucketARN", {
value: String(myBucket.bucketARn)
})
2。创建客户根堆栈
对于每个客户,您将拥有诸如 customerRootStack 之类的东西,它定义了您已经创建的根资源 - 因为您不想 overwrite/recreate 您将希望使用现有的 ARN/name 例如:
// Read the root stack context
import rootData from '../commonApp/cdk.context.json`
s3.Bucket.fromBucketArn(this, 'MyBucket', rootData.nameOfRootStack.MyBucketARN)
// Define custom stuff for each customer
...
请注意,同一文件夹中可以有多个应用程序,但您需要确保唯一的名称等)- 每个客户一个项目和根用户一个项目可能更容易,但这取决于您。
我找到了一个解决方案并创建了这个示例存储库:
我混合了 AWS SDK CloudFormation 模块:
src/index.js:
import * as fs from "fs";
import * as AWS from 'aws-sdk';
import * as cfninc from '@aws-cdk/cloudformation-include';
import * as tmp from 'tmp';
import CdkDeployAbstraction from "./cdk/cdkDeployAbstraction";
import * as dotenv from "dotenv";
import {ReusableRootStackWithNestedStacks} from './cdk/ReusableRootStackWithNestedStacks';
import {OtherStack} from "./cdk/OtherStack";
import {App} from "@aws-cdk/core";
dotenv.config()
AWS.config.region = process.env.AWS_REGION;
AWS.config.credentials = {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
};
const getNestedsStacks = async (stackName: string) => {
const cloudformation = new AWS.CloudFormation();
const resourcesResult = await cloudformation.describeStackResources({
StackName: stackName
}).promise();
const resources = resourcesResult.StackResources;
const filteredResources = resources.filter(value => value.ResourceType === 'AWS::CloudFormation::Stack');
let nestedStacks: { logicalResourceId: string, physicalResourceId: string; stack: AWS.CloudFormation.Stack }[] = [];
for (let i = 0; i < filteredResources.length; i++) {
const resource = filteredResources[i];
const nestedStackResult = await cloudformation.describeStacks({
StackName: resource.PhysicalResourceId
}).promise();
nestedStacks.push({
logicalResourceId: resource.LogicalResourceId,
physicalResourceId: resource.PhysicalResourceId,
stack: nestedStackResult.Stacks.shift()
});
}
return nestedStacks;
}
const getNestedsStackDetails = async (stackName: string) => {
const cloudformation = new AWS.CloudFormation();
const currentNestedStacks = await getNestedsStacks(stackName);
let nestedStacks: { [stackName: string]: cfninc.CfnIncludeProps; } = undefined;
for (let i = 0; i < currentNestedStacks.length; i++) {
const currentNestedStack = currentNestedStacks[i];
const nestedStackResult = await cloudformation.describeStacks({
StackName: currentNestedStack.stack.StackName
}).promise();
const nestedStack = nestedStackResult.Stacks.shift();
const nestedStackTemplate = await cloudformation.getTemplate({
StackName: nestedStack.StackName
}).promise();
const tmpFileName = tmp.tmpNameSync({
postfix: '.yaml'
});
fs.writeFileSync(tmpFileName, nestedStackTemplate.TemplateBody);
if (!nestedStacks) {
nestedStacks = {};
}
const recursiveNestedStacks = await getNestedsStackDetails(nestedStack.StackName);
nestedStacks[currentNestedStack.logicalResourceId] = {
templateFile: tmpFileName,
parameters: nestedStack.Parameters.reduce((previousValue, currentValue) => {
previousValue[currentValue.ParameterKey] = currentValue.ParameterValue;
return previousValue;
}, {}),
loadNestedStacks: recursiveNestedStacks
}
}
return nestedStacks;
}
const getStackDetails = async (stackName: string) => {
const cloudformation = new AWS.CloudFormation();
const stacksResult = await cloudformation.describeStacks({
StackName: stackName,
}).promise();
const stack = stacksResult.Stacks.shift();
const stackTemplate = await cloudformation.getTemplate({
StackName: stack.StackName,
}).promise();
const tmpFileName = tmp.tmpNameSync({
postfix: '.yaml'
});
fs.writeFileSync(tmpFileName, stackTemplate.TemplateBody);
const nestedsStacksDetails = await getNestedsStackDetails(stack.StackName)
return {
mainStack: {
stack,
tmpFileName
},
nestedsStacksDetails
}
}
const main = async (rootStackName: string) => {
const stackName = rootStackName;
const stackDetails = await getStackDetails(stackName);
const app = new App();
const rootStack = new ReusableRootStackWithNestedStacks(app,
stackName,
stackDetails.mainStack.tmpFileName,
stackDetails.nestedsStacksDetails,
stackDetails.mainStack.stack
);
new OtherStack(rootStack, 'OtherStack')
const deploy = new CdkDeployAbstraction({
region: process.env.AWS_REGION
});
const deployResult = await deploy.deployCdkStack(app, rootStack);
console.log(deployResult);
}
main('MyAwesomeRootStackName')
.then(value => console.log('finish!'))
.catch(err => console.log(err))
src/cdk/ReusableRootStackWithNestedStacks.ts
import * as AWS from 'aws-sdk';
import {Construct, Stack, StackProps} from "@aws-cdk/core";
import * as cfninc from '@aws-cdk/cloudformation-include';
export class ReusableRootStackWithNestedStacks extends Stack {
constructor(scope: Construct, id: string, tmpFileName: string, nestedStacks: { [stackName: string]: cfninc.CfnIncludeProps; }, stack: AWS.CloudFormation.Stack, props?: StackProps) {
super(scope, id, props);
const template = new cfninc.CfnInclude(this, 'CfCurrentTemplate', {
templateFile: tmpFileName,
loadNestedStacks: nestedStacks,
parameters: stack.Parameters.reduce((previousValue, currentValue) => {
previousValue[currentValue.ParameterKey] = currentValue.ParameterValue;
return previousValue;
}, {})
});
}
}
src/cdk/OtherStack.ts:
import {Construct, NestedStack, NestedStackProps} from "@aws-cdk/core";
import * as iam from '@aws-cdk/aws-iam';
export class OtherStack extends NestedStack {
constructor(scope: Construct, id: string, props?: NestedStackProps) {
super(scope, id, props);
new iam.ManagedPolicy(this, 'IamManagedPolicy', {
managedPolicyName: 'TesteIAM-ManagedPolicy',
document: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'dynamodb:GetItem',
'dynamodb:Query',
],
resources: ['*']
})
]
}),
})
}
}
src/cdk/cdkDeployAbstraction:
import {Credentials} from "@aws-sdk/types";
import {CloudFormationDeployments} from "aws-cdk/lib/api/cloudformation-deployments";
import * as AWS from "aws-sdk";
import {App, Stack} from '@aws-cdk/core';
import {CloudFormationStackArtifact} from '@aws-cdk/cx-api';
import {DeployStackResult, SdkProvider} from "aws-cdk/lib";
export default class CdkDeployAbstraction {
private readonly credentials: Credentials;
private readonly region: string;
constructor(config: { credentials?: Credentials; region: string }) {
this.credentials = config.credentials;
this.region = config.region;
}
public async deployCdkStack(app: App, stack: Stack, notificationTopicArn?: string): Promise<DeployStackResult> {
const stackArtifact = app.synth().getStackByName(stack.stackName) as unknown as CloudFormationStackArtifact;
const credentialProviderChain = new AWS.CredentialProviderChain();
let credentials;
if (this.credentials) {
credentials = new AWS.Credentials({
accessKeyId: this.credentials.accessKeyId,
secretAccessKey: this.credentials.secretAccessKey,
});
credentialProviderChain.providers.push(credentials);
}
const sdkProvider = new SdkProvider(credentialProviderChain, this.region, {
credentials: credentials,
});
const cloudFormation = new CloudFormationDeployments({sdkProvider});
if (notificationTopicArn) {
return cloudFormation.deployStack({
// @ts-ignore
stack: stackArtifact,
notificationArns: [notificationTopicArn],
quiet: true,
});
}
return cloudFormation.deployStack({
// @ts-ignore
stack: stackArtifact,
quiet: true,
});
}
}
首先,重要的是我没有像往常一样使用 CDK。相反,我正在以编程方式即时创建资源。所以,基本上,我有一个多租户应用程序,它在机上创建了一个客户根堆栈,在客户生命周期内将包含带有资源的嵌套堆栈。
第一次执行的代码是这样的:
import {App, Stack, Construct, NestedStack} from '@aws-cdk/core';
const main = async() => {
const app = new App();
const rootStack = new (class RootStack extends Stack {
constructor() {
super(app, `Customer-123-RootStack`, {});
}
});
const tenantIamStack = new (class CustomerIamNestedClass extends NestedStack {
constructor() {
super(rootStack, `BasicIAM`, {});
const cognitoFederatedPrincipal = new iam.FederatedPrincipal('cognito-identity.amazonaws.com', {
'StringEquals': {
'cognito-identity.amazonaws.com:aud': process.env.SHARED_IDENTITY_POOL_ID
},
'ForAnyValue:StringLike': {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}, 'sts:AssumeRoleWithWebIdentity');
new iam.Role(this, 'IamRoleTenantUser', {
roleName: `Customer-123-TenantUser`,
assumedBy: cognitoFederatedPrincipal,
});
}
});
}
main().then(() => console.log('done'));
我现在想在其他例程中创建的其他堆栈上重用根堆栈 Customer-123-RootStack
。例如,客户将在我们的平台中创建其他 AWS 资源,例如 AWS EventBridge 规则或 ACM 证书。
如果对另一个嵌套栈执行相同的代码,第一个嵌套栈将被删除。
import {App, Stack, Construct, NestedStack} from '@aws-cdk/core';
const main = async() => {
const app = new App();
const rootStack = new (class RootStack extends Stack {
constructor() {
super(app, `Customer-123-RootStack`, {});
}
});
const tenantAcmStack = new (class CustomerAcmNestedClass extends NestedStack {
constructor() {
super(rootStack, `BasicACM`, {});
//create ACM certificate
}
});
}
main().then(() => console.log('done'));
我已阅读此文档,但无法弄清楚该怎么做:https://docs.aws.amazon.com/cdk/v2/guide/resources.html#resources_importing
我可以使用 SDK 获取堆栈,但不明白如何让它与 CDK 一起工作。
编辑:为了让我的问题更清楚:我需要将父现有堆栈(由其他应用程序创建,在其他时刻,在其他代码库中创建)引用到新的嵌套堆栈。
所以我会做的是:
1。创建根堆栈
创建一个核心根堆栈,其中包含您希望客户堆栈使用的公共资源。您将需要 arn/name 您希望在客户堆栈中访问的资源。一种方法是使用 CfnOutput
写入 cdk.context.json
文件:
// Example of outputting an S3 bucket
const myBucket = new Bucket...
new CfnOutput(this, "MyBucketARN", {
value: String(myBucket.bucketARn)
})
2。创建客户根堆栈
对于每个客户,您将拥有诸如 customerRootStack 之类的东西,它定义了您已经创建的根资源 - 因为您不想 overwrite/recreate 您将希望使用现有的 ARN/name 例如:
// Read the root stack context
import rootData from '../commonApp/cdk.context.json`
s3.Bucket.fromBucketArn(this, 'MyBucket', rootData.nameOfRootStack.MyBucketARN)
// Define custom stuff for each customer
...
请注意,同一文件夹中可以有多个应用程序,但您需要确保唯一的名称等)- 每个客户一个项目和根用户一个项目可能更容易,但这取决于您。
我找到了一个解决方案并创建了这个示例存储库:
我混合了 AWS SDK CloudFormation 模块:
src/index.js:
import * as fs from "fs";
import * as AWS from 'aws-sdk';
import * as cfninc from '@aws-cdk/cloudformation-include';
import * as tmp from 'tmp';
import CdkDeployAbstraction from "./cdk/cdkDeployAbstraction";
import * as dotenv from "dotenv";
import {ReusableRootStackWithNestedStacks} from './cdk/ReusableRootStackWithNestedStacks';
import {OtherStack} from "./cdk/OtherStack";
import {App} from "@aws-cdk/core";
dotenv.config()
AWS.config.region = process.env.AWS_REGION;
AWS.config.credentials = {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
};
const getNestedsStacks = async (stackName: string) => {
const cloudformation = new AWS.CloudFormation();
const resourcesResult = await cloudformation.describeStackResources({
StackName: stackName
}).promise();
const resources = resourcesResult.StackResources;
const filteredResources = resources.filter(value => value.ResourceType === 'AWS::CloudFormation::Stack');
let nestedStacks: { logicalResourceId: string, physicalResourceId: string; stack: AWS.CloudFormation.Stack }[] = [];
for (let i = 0; i < filteredResources.length; i++) {
const resource = filteredResources[i];
const nestedStackResult = await cloudformation.describeStacks({
StackName: resource.PhysicalResourceId
}).promise();
nestedStacks.push({
logicalResourceId: resource.LogicalResourceId,
physicalResourceId: resource.PhysicalResourceId,
stack: nestedStackResult.Stacks.shift()
});
}
return nestedStacks;
}
const getNestedsStackDetails = async (stackName: string) => {
const cloudformation = new AWS.CloudFormation();
const currentNestedStacks = await getNestedsStacks(stackName);
let nestedStacks: { [stackName: string]: cfninc.CfnIncludeProps; } = undefined;
for (let i = 0; i < currentNestedStacks.length; i++) {
const currentNestedStack = currentNestedStacks[i];
const nestedStackResult = await cloudformation.describeStacks({
StackName: currentNestedStack.stack.StackName
}).promise();
const nestedStack = nestedStackResult.Stacks.shift();
const nestedStackTemplate = await cloudformation.getTemplate({
StackName: nestedStack.StackName
}).promise();
const tmpFileName = tmp.tmpNameSync({
postfix: '.yaml'
});
fs.writeFileSync(tmpFileName, nestedStackTemplate.TemplateBody);
if (!nestedStacks) {
nestedStacks = {};
}
const recursiveNestedStacks = await getNestedsStackDetails(nestedStack.StackName);
nestedStacks[currentNestedStack.logicalResourceId] = {
templateFile: tmpFileName,
parameters: nestedStack.Parameters.reduce((previousValue, currentValue) => {
previousValue[currentValue.ParameterKey] = currentValue.ParameterValue;
return previousValue;
}, {}),
loadNestedStacks: recursiveNestedStacks
}
}
return nestedStacks;
}
const getStackDetails = async (stackName: string) => {
const cloudformation = new AWS.CloudFormation();
const stacksResult = await cloudformation.describeStacks({
StackName: stackName,
}).promise();
const stack = stacksResult.Stacks.shift();
const stackTemplate = await cloudformation.getTemplate({
StackName: stack.StackName,
}).promise();
const tmpFileName = tmp.tmpNameSync({
postfix: '.yaml'
});
fs.writeFileSync(tmpFileName, stackTemplate.TemplateBody);
const nestedsStacksDetails = await getNestedsStackDetails(stack.StackName)
return {
mainStack: {
stack,
tmpFileName
},
nestedsStacksDetails
}
}
const main = async (rootStackName: string) => {
const stackName = rootStackName;
const stackDetails = await getStackDetails(stackName);
const app = new App();
const rootStack = new ReusableRootStackWithNestedStacks(app,
stackName,
stackDetails.mainStack.tmpFileName,
stackDetails.nestedsStacksDetails,
stackDetails.mainStack.stack
);
new OtherStack(rootStack, 'OtherStack')
const deploy = new CdkDeployAbstraction({
region: process.env.AWS_REGION
});
const deployResult = await deploy.deployCdkStack(app, rootStack);
console.log(deployResult);
}
main('MyAwesomeRootStackName')
.then(value => console.log('finish!'))
.catch(err => console.log(err))
src/cdk/ReusableRootStackWithNestedStacks.ts
import * as AWS from 'aws-sdk';
import {Construct, Stack, StackProps} from "@aws-cdk/core";
import * as cfninc from '@aws-cdk/cloudformation-include';
export class ReusableRootStackWithNestedStacks extends Stack {
constructor(scope: Construct, id: string, tmpFileName: string, nestedStacks: { [stackName: string]: cfninc.CfnIncludeProps; }, stack: AWS.CloudFormation.Stack, props?: StackProps) {
super(scope, id, props);
const template = new cfninc.CfnInclude(this, 'CfCurrentTemplate', {
templateFile: tmpFileName,
loadNestedStacks: nestedStacks,
parameters: stack.Parameters.reduce((previousValue, currentValue) => {
previousValue[currentValue.ParameterKey] = currentValue.ParameterValue;
return previousValue;
}, {})
});
}
}
src/cdk/OtherStack.ts:
import {Construct, NestedStack, NestedStackProps} from "@aws-cdk/core";
import * as iam from '@aws-cdk/aws-iam';
export class OtherStack extends NestedStack {
constructor(scope: Construct, id: string, props?: NestedStackProps) {
super(scope, id, props);
new iam.ManagedPolicy(this, 'IamManagedPolicy', {
managedPolicyName: 'TesteIAM-ManagedPolicy',
document: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'dynamodb:GetItem',
'dynamodb:Query',
],
resources: ['*']
})
]
}),
})
}
}
src/cdk/cdkDeployAbstraction:
import {Credentials} from "@aws-sdk/types";
import {CloudFormationDeployments} from "aws-cdk/lib/api/cloudformation-deployments";
import * as AWS from "aws-sdk";
import {App, Stack} from '@aws-cdk/core';
import {CloudFormationStackArtifact} from '@aws-cdk/cx-api';
import {DeployStackResult, SdkProvider} from "aws-cdk/lib";
export default class CdkDeployAbstraction {
private readonly credentials: Credentials;
private readonly region: string;
constructor(config: { credentials?: Credentials; region: string }) {
this.credentials = config.credentials;
this.region = config.region;
}
public async deployCdkStack(app: App, stack: Stack, notificationTopicArn?: string): Promise<DeployStackResult> {
const stackArtifact = app.synth().getStackByName(stack.stackName) as unknown as CloudFormationStackArtifact;
const credentialProviderChain = new AWS.CredentialProviderChain();
let credentials;
if (this.credentials) {
credentials = new AWS.Credentials({
accessKeyId: this.credentials.accessKeyId,
secretAccessKey: this.credentials.secretAccessKey,
});
credentialProviderChain.providers.push(credentials);
}
const sdkProvider = new SdkProvider(credentialProviderChain, this.region, {
credentials: credentials,
});
const cloudFormation = new CloudFormationDeployments({sdkProvider});
if (notificationTopicArn) {
return cloudFormation.deployStack({
// @ts-ignore
stack: stackArtifact,
notificationArns: [notificationTopicArn],
quiet: true,
});
}
return cloudFormation.deployStack({
// @ts-ignore
stack: stackArtifact,
quiet: true,
});
}
}