【问题标题】:How to pass a LambdaStack down to a PipelineStage with AWS-CDK如何使用 AWS-CDK 将 LambdaStack 向下传递到 PipelineStage
【发布时间】:2021-12-29 21:05:13
【问题描述】:

在使用 AWS-CDK 时,我如何将堆栈向下传递到管道阶段?

我目前正在尝试创建一个可以将堆栈作为输入的管道。

我已经关注了 aws-cdk 研讨会,并且有一个可以自我更新的管道,并且可以部署一个预打包的 lambda,但我正在尝试创建一个管道构造库,以便我的团队可以创建一个新的实例管道并传入稍后创建的堆栈,该堆栈已添加相关角色和事件规则。

我当前的代码如下:

pipelines-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),
      );
    }
  }
}

pipeline-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));
  }
}

建议的解决方案如下:

pipelines-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),
      );

pipeline-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-deployment-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();

【问题讨论】:

  • 是的,一切都已编译,实际上是测试套件抛出错误,但实现几乎相同
  • @fedonev 我已经更新了这个问题,详细介绍了导致该错误的当前实现
  • 当前实现失败,因为栈不是在stage的范围内创建的。您需要在舞台中创建堆栈。我不确定您要达到什么目的 - 如果您解释得更好,我相信我们可以提出解决方案。
  • @gshpychka 添加了有关最终目标的更多详细信息

标签: typescript amazon-web-services aws-cdk aws-codepipeline


【解决方案1】:

我对提议的解决方案的理解:实施者应该将LambdaStack 传递给共享的PipelineStackPipelineStage 是一个实现细节 管道。

lambda-deployment-stack.ts 中,实现者将带有签名(scope: cdk.Stage) =&gt; 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
  }
}

【讨论】:

  • 我想我得到了大致的要点,最初的 makeLambdaStack 函数在哪里?在pipeline-stack.ts 文件中?如果是这样的话,导出让我想到了PipelineStack 的堆栈声明之外
  • 解决了,这很好解决了,谢谢!
  • 很高兴为您提供帮助!我编辑了答案以使lambda-deployment-stack.ts 的角色更清晰。
猜你喜欢
  • 2021-08-04
  • 1970-01-01
  • 2021-03-15
  • 2022-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-26
  • 2023-02-26
相关资源
最近更新 更多