如何使用 AWS-CDK 将 LambdaStack 向下传递到 PipelineStage
How to pass a LambdaStack down to a PipelineStage with AWS-CDK
使用 AWS-CDK 时,如何将 Stack 向下传递到管道阶段?
我目前正在尝试创建一个可以将堆栈作为输入的管道。
我参加了 aws-cdk 研讨会并拥有一个可以自我更新并可以部署预打包 lambda 的管道,但我正在尝试创建一个管道构造库,以便我的团队可以创建一个新的管道实例并传入后来创建的堆栈,其中添加了相关角色和事件规则。
我当前的代码如下:
流水线-stack.ts
import {
Stack,
StackProps,
pipelines,
DefaultStackSynthesizer,
SecretValue,
aws_codebuild as codebuild,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { PipelineStage } from './pipeline-stage';
export type PipelineStackProps = StackProps;
export interface Environment {
readonly name: string;
readonly accountNumber: string;
}
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string, props: PipelineStackProps,
environments: Environment[], repoName: string, serviceName: string, lambdaPath: string, policies: Array<string>) {
super(scope, id, props);
new DefaultStackSynthesizer({
deployRoleArn: 'arn:aws:iam::{ACCOUNTID}:role/service-role/ops-codepipeline-role',
});
const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
pipelineName: serviceName,
synth: new pipelines.CodeBuildStep('Synth', {
input: pipelines.CodePipelineSource.gitHub(`company_repo/${repoName}`, 'main', {
authentication: SecretValue.secretsManager('github-oauth-token', { jsonField: 'OAUTH_TOKEN' }),
}),
buildEnvironment: {
environmentVariables: {
NPM_TOKEN: {
value: '/codepipeline/npm_token',
type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE,
},
},
},
commands: [
'echo "@company:registry=https://npm.pkg.github.com/" > .npmrc',
'echo "//npm.pkg.github.com/:_authToken=\${NPM_TOKEN}" >> .npmrc',
'npm ci',
'npm run build',
'npm test',
'npx cdk synth',
'pip install -r lambda/requirements.txt -t lambda',
],
}),
crossAccountKeys: true,
});
for (const environment of environments) {
const environmentName = environment.name;
const envAccountNumber = environment.accountNumber;
pipeline.addStage(
new PipelineStage(this, environmentName, {
env: {
account: envAccountNumber,
region: 'eu-west-2',
},
}, lambdaPath, policies),
);
}
}
}
管道-stage.ts
import { Stage, StageProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { LambdaStack } from './lambda-stack';
export class PipelineStage extends Stage {
constructor(scope: Construct, id: string, props: StageProps, lambdaPath: string, policies: Array<string>) {
super(scope, id, props);
new LambdaStack(this, 'LambdaStack', lambdaPath, policies);
}
}
lambda-stack.ts
import {
Stack,
StackProps,
aws_lambda as lambda,
aws_events as events,
aws_events_targets as targets,
aws_iam as iam,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class LambdaStack extends Stack {
constructor(scope: Construct, id: string, lambdaPath: string, policies: Array<string>, props?: StackProps) {
super(scope, id, props);
const role = new iam.Role(this, 'LambdaCleanupRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});
for (const policy of policies) {
role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName(policy));
}
// The code that defines your stack goes here
const fn = new lambda.Function(this, 'lambda-cleanup', {
runtime: lambda.Runtime.PYTHON_3_8,
handler: 'app.handler',
code: lambda.Code.fromAsset(lambdaPath),
role,
});
const rule = new events.Rule(this, 'Schedule Rule', {
schedule: events.Schedule.expression('rate(1 day)'),
});
rule.addTarget(new targets.LambdaFunction(fn));
}
}
建议的解决方案如下:
流水线-stack.ts
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string, props: PipelineStackProps,
environments: Environment[], repoName: string, serviceName: string, lambdaPath: string, policies: Array<string>, lambdaStack: Stack) {
super(scope, id, props);
// pipeline implementation here
const stage = pipeline.addStage(
new PipelineStage(this, environmentName, {
env: {
account: envAccountNumber,
region: 'eu-west-2',
},
}, lambdaPath, policies, lambdaStack),
);
管道-stage.ts
export class PipelineStage extends Stage {
constructor(scope: Construct, id: string, props: StageProps, lambdaPath: string, policies: Array<string>, lambdaStack: Stack) {
super(scope, id, props);
lambdaStack
}
}
但是这样做会导致 The given Stage construct ('Default/BymilesLambdaPipelineStack/dev') should contain at least one Stack
错误
团队的最终目标是从我们的注册表中导入一个 npm 包并执行类似于以下的操作:
lambda-部署-stack.ts
import { LambdaStack } from './lambda-stack.ts'
import { PipelineStack } from '@company-register/cdk-pipeline-python'
const app = new App();
const policies: string[] = [
'service-role/AWSLambdaBasicExecutionRole',
'AWSLambda_FullAccess',
];
new PipelineStack(app, 'LambdaCleanupPipeline', {
env: {
region: 'eu-west-2',
},
}, [
{ name: 'Test', accountNumber: '11111111' },
{ name: 'Ops', accountNumber: '22222222' }],
'bymiles-lambda-cleanup-cdk',
'bymiles-lambda-cleanup-cdk',
new LambdaStack(stack, 'BymilesLambdaStack', 'test/test_lambda', policies)
);
app.synth();
我对提议的解决方案的理解:实施者应该将 LambdaStack
传递给共享的 PipelineStack
。 PipelineStage
是一个实现细节
管道。
在 lambda-deployment-stack.ts
中,实现者将带有签名 (scope: cdk.Stage) => void
的函数传递给通用 PipelineStack
。
此 dependency injection 模式有两个目的:(1) 它将堆栈构造推迟到阶段范围可用,以及 (2) 它封装了与管道无关的策略和其他细节。
// lambda-deployment-stack.ts
// PipelineStack accepts this function signature as a prop
// defers the lambda stack creation until the stage scope is available
const makeLambdaStack = (scope: cdk.Stage): void => {
new LambdaStack(scope, 'BymilesLambdaStack', 'test/test_lambda', policies);
};
不接受 PipelineStack
的 LambdaStack、策略和路径,PipelineStack
将包装函数作为 stackMaker
道具。
// pipeline-stack.ts
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string, props: PipelineStackProps,
environments: Environment[], repoName: string, serviceName: string,
stackMaker: (scope: cdk.Stage) => void) {
super(scope, id, props);
// etc...
Pipeline
依次将 makeLambdaStack
向下传递到 PipelineStage
,在该处调用函数并实际构建 Lambda。
// pipeline-stage.ts
export class PipelineStage extends Stage {
constructor(
scope: Construct,
id: string,
props: StageProps,
stackMaker: (scope: cdk.Stage) => void) {
super(scope, id, props);
props.stackMaker(this) // <- actually call the maker function, in the sceope of the Pipeline Stage
}
}
使用 AWS-CDK 时,如何将 Stack 向下传递到管道阶段?
我目前正在尝试创建一个可以将堆栈作为输入的管道。
我参加了 aws-cdk 研讨会并拥有一个可以自我更新并可以部署预打包 lambda 的管道,但我正在尝试创建一个管道构造库,以便我的团队可以创建一个新的管道实例并传入后来创建的堆栈,其中添加了相关角色和事件规则。
我当前的代码如下:
流水线-stack.ts
import {
Stack,
StackProps,
pipelines,
DefaultStackSynthesizer,
SecretValue,
aws_codebuild as codebuild,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { PipelineStage } from './pipeline-stage';
export type PipelineStackProps = StackProps;
export interface Environment {
readonly name: string;
readonly accountNumber: string;
}
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string, props: PipelineStackProps,
environments: Environment[], repoName: string, serviceName: string, lambdaPath: string, policies: Array<string>) {
super(scope, id, props);
new DefaultStackSynthesizer({
deployRoleArn: 'arn:aws:iam::{ACCOUNTID}:role/service-role/ops-codepipeline-role',
});
const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
pipelineName: serviceName,
synth: new pipelines.CodeBuildStep('Synth', {
input: pipelines.CodePipelineSource.gitHub(`company_repo/${repoName}`, 'main', {
authentication: SecretValue.secretsManager('github-oauth-token', { jsonField: 'OAUTH_TOKEN' }),
}),
buildEnvironment: {
environmentVariables: {
NPM_TOKEN: {
value: '/codepipeline/npm_token',
type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE,
},
},
},
commands: [
'echo "@company:registry=https://npm.pkg.github.com/" > .npmrc',
'echo "//npm.pkg.github.com/:_authToken=\${NPM_TOKEN}" >> .npmrc',
'npm ci',
'npm run build',
'npm test',
'npx cdk synth',
'pip install -r lambda/requirements.txt -t lambda',
],
}),
crossAccountKeys: true,
});
for (const environment of environments) {
const environmentName = environment.name;
const envAccountNumber = environment.accountNumber;
pipeline.addStage(
new PipelineStage(this, environmentName, {
env: {
account: envAccountNumber,
region: 'eu-west-2',
},
}, lambdaPath, policies),
);
}
}
}
管道-stage.ts
import { Stage, StageProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { LambdaStack } from './lambda-stack';
export class PipelineStage extends Stage {
constructor(scope: Construct, id: string, props: StageProps, lambdaPath: string, policies: Array<string>) {
super(scope, id, props);
new LambdaStack(this, 'LambdaStack', lambdaPath, policies);
}
}
lambda-stack.ts
import {
Stack,
StackProps,
aws_lambda as lambda,
aws_events as events,
aws_events_targets as targets,
aws_iam as iam,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class LambdaStack extends Stack {
constructor(scope: Construct, id: string, lambdaPath: string, policies: Array<string>, props?: StackProps) {
super(scope, id, props);
const role = new iam.Role(this, 'LambdaCleanupRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});
for (const policy of policies) {
role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName(policy));
}
// The code that defines your stack goes here
const fn = new lambda.Function(this, 'lambda-cleanup', {
runtime: lambda.Runtime.PYTHON_3_8,
handler: 'app.handler',
code: lambda.Code.fromAsset(lambdaPath),
role,
});
const rule = new events.Rule(this, 'Schedule Rule', {
schedule: events.Schedule.expression('rate(1 day)'),
});
rule.addTarget(new targets.LambdaFunction(fn));
}
}
建议的解决方案如下:
流水线-stack.ts
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string, props: PipelineStackProps,
environments: Environment[], repoName: string, serviceName: string, lambdaPath: string, policies: Array<string>, lambdaStack: Stack) {
super(scope, id, props);
// pipeline implementation here
const stage = pipeline.addStage(
new PipelineStage(this, environmentName, {
env: {
account: envAccountNumber,
region: 'eu-west-2',
},
}, lambdaPath, policies, lambdaStack),
);
管道-stage.ts
export class PipelineStage extends Stage {
constructor(scope: Construct, id: string, props: StageProps, lambdaPath: string, policies: Array<string>, lambdaStack: Stack) {
super(scope, id, props);
lambdaStack
}
}
但是这样做会导致 The given Stage construct ('Default/BymilesLambdaPipelineStack/dev') should contain at least one Stack
错误
团队的最终目标是从我们的注册表中导入一个 npm 包并执行类似于以下的操作:
lambda-部署-stack.ts
import { LambdaStack } from './lambda-stack.ts'
import { PipelineStack } from '@company-register/cdk-pipeline-python'
const app = new App();
const policies: string[] = [
'service-role/AWSLambdaBasicExecutionRole',
'AWSLambda_FullAccess',
];
new PipelineStack(app, 'LambdaCleanupPipeline', {
env: {
region: 'eu-west-2',
},
}, [
{ name: 'Test', accountNumber: '11111111' },
{ name: 'Ops', accountNumber: '22222222' }],
'bymiles-lambda-cleanup-cdk',
'bymiles-lambda-cleanup-cdk',
new LambdaStack(stack, 'BymilesLambdaStack', 'test/test_lambda', policies)
);
app.synth();
我对提议的解决方案的理解:实施者应该将 LambdaStack
传递给共享的 PipelineStack
。 PipelineStage
是一个实现细节
管道。
在 lambda-deployment-stack.ts
中,实现者将带有签名 (scope: cdk.Stage) => void
的函数传递给通用 PipelineStack
。
此 dependency injection 模式有两个目的:(1) 它将堆栈构造推迟到阶段范围可用,以及 (2) 它封装了与管道无关的策略和其他细节。
// lambda-deployment-stack.ts
// PipelineStack accepts this function signature as a prop
// defers the lambda stack creation until the stage scope is available
const makeLambdaStack = (scope: cdk.Stage): void => {
new LambdaStack(scope, 'BymilesLambdaStack', 'test/test_lambda', policies);
};
不接受 PipelineStack
的 LambdaStack、策略和路径,PipelineStack
将包装函数作为 stackMaker
道具。
// pipeline-stack.ts
export class PipelineStack extends Stack {
constructor(scope: Construct, id: string, props: PipelineStackProps,
environments: Environment[], repoName: string, serviceName: string,
stackMaker: (scope: cdk.Stage) => void) {
super(scope, id, props);
// etc...
Pipeline
依次将 makeLambdaStack
向下传递到 PipelineStage
,在该处调用函数并实际构建 Lambda。
// pipeline-stage.ts
export class PipelineStage extends Stage {
constructor(
scope: Construct,
id: string,
props: StageProps,
stackMaker: (scope: cdk.Stage) => void) {
super(scope, id, props);
props.stackMaker(this) // <- actually call the maker function, in the sceope of the Pipeline Stage
}
}