AWS CDK Codepipeline post step lambda invoke action being incorrectly added parallel to a pre step lambda invoke action

0

Hello,

I'm using the AWS CDK to define our infrastructure. We use 3 accounts: Security account, Dev and Prod account. My Security account holds common resources and the Code Pipeline that deploys the infra to Dev and Prod accounts. I created a Lambda function that i need to run before and after the Dev stage (which deploys the resources to the Dev account) and also for the Prod stage, which does the same but for the Prod account.

This function basically makes a http call to a Google Cloud function to block and unblock some IoT Devices, based on the parameters i pass to it as part of the UserParameters object inside the event object it receives after being invoked. To avoid issues when updating the underlying infra, I was planning on blocking them as a 'Pre' step for both my stages and unblocking them as a 'Post' step for both my stages. There is also a Manual approval step as a 'Post' step between the Dev and Prod stages.

The problem

The problem i'm having is that even though i put the call to the lambda action that blocks the devices as a 'Post' step for both my prod and dev stages, it is always added right next to the call that blocks the devices, so it is being treated as if it where a 'Pre' step. The manual approval step previously mentioned is correctly placed as a 'Post' step. Attached is a screen capture where you can see these two lambda invoke action steps right next to each other, the first correctly treated as a 'Pre' step but not the second one, that was defined as a 'Post' step.

Source code where I define my pipeline:

import { AwsInfrastructureStage } from "./stage";
import { getEnv } from "../config/env-config";
import * as cdk from "aws-cdk-lib";
import {
  CodePipeline,
  CodePipelineSource,
  ManualApprovalStep,
  ShellStep,
} from "aws-cdk-lib/pipelines";
import { Construct } from "constructs";
import { DeploymentStack } from './stacks/deployment-satck';
import { LambdaInvokeStep } from "./lambdaInvokeStep";

export class AwsInfrastructureStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const deploymentStack = new DeploymentStack(this, 'DeploymentStack', { env: props?.env });

    const pipeline = new CodePipeline(this, 'Pipeline', {
      pipelineName: 'AwsInfrastructurePipeline',
      synth: new ShellStep('Synth', {
        input: CodePipelineSource.gitHub('NicolasGorga/aws-infrastructure', 'develop'),
        commands: ['npm ci', 'npm run build', 'npx cdk synth']
      }),
      crossAccountKeys: true,
    });

    let envConfig = getEnv("dev");

    const blockIotDevicesDevStep = new LambdaInvokeStep(`${envConfig.env}BlockIotDevicesStep`, {
      actionName: `${envConfig.env}BlockIotDevices`,
      lambda: deploymentStack.blockDevicesLambda,
      userParameters: {
        blockIotDevicesCloudFunction: envConfig.blockIotDevicesCloudFunction,
        block: true,
        tokenSecretName: "MAINTENANCE_ACTION_TOKEN",
        stageEnv: envConfig.env,
      },
    });

    const unblockIotDevicesDevStep = new LambdaInvokeStep(`${envConfig.env}UnblockIotDevicesStep`, {
      actionName: `${envConfig.env}UnblockIotDevices`,
      lambda: deploymentStack.blockDevicesLambda,
      userParameters: {
        blockIotDevicesCloudFunction: envConfig.blockIotDevicesCloudFunction,
        block: false,
        tokenSecretName: "MAINTENANCE_ACTION_TOKEN",
        stageEnv: envConfig.env,
      },
    });

    const manualApprovalStep = new ManualApprovalStep("Approval before prod deployment");

    const devStage = pipeline.addStage(
      new AwsInfrastructureStage(this, envConfig.env, {
        env: {
          account: envConfig.accountId,
          region: envConfig.region,
        },
        envConfig
      }),
      {
        pre: [blockIotDevicesDevStep],
        post: [unblockIotDevicesDevStep, manualApprovalStep]
      }
    );

    envConfig = getEnv('prod');

    const blockIotDevicesProdStep = new LambdaInvokeStep(`${envConfig.env}BlockIotDevicesStep`, {
      actionName: `${envConfig.env}BlockIotDevices`,
      lambda: deploymentStack.blockDevicesLambda,
      userParameters: {
        blockIotDevicesCloudFunction: envConfig.blockIotDevicesCloudFunction,
        block: true,
        tokenSecretName: "MAINTENANCE_ACTION_TOKEN",
        stageEnv: envConfig.env,
      },
    });

    const unblockIotDevicesProdStep = new LambdaInvokeStep(`${envConfig.env}UnblockIotDevicesStep`, {
      actionName: `${envConfig.env}UnblockIotDevices`,
      lambda: deploymentStack.blockDevicesLambda,
      userParameters: {
        blockIotDevicesCloudFunction: envConfig.blockIotDevicesCloudFunction,
        block: false,
        tokenSecretName: "MAINTENANCE_ACTION_TOKEN",
        stageEnv: envConfig.env,
      },
    });

    pipeline.addStage(
      new AwsInfrastructureStage(this, envConfig.env, {
        env: {
          account: envConfig.accountId,
          region: envConfig.region,
        },
        envConfig
      }), {
        pre: [blockIotDevicesProdStep],
        post: [unblockIotDevicesProdStep],
      }
    );
  }
}

Source Code where I define the custom LambdaInvokeStep:

import * as pipelines from "aws-cdk-lib/pipelines";
import { IStage } from "aws-cdk-lib/aws-codepipeline";
import { LambdaInvokeAction, LambdaInvokeActionProps } from "aws-cdk-lib/aws-codepipeline-actions";

export class LambdaInvokeStep extends pipelines.Step implements pipelines.ICodePipelineActionFactory {
    constructor(
        id: string,
        private readonly invokeActionProps: LambdaInvokeActionProps,
    ) {
        super(id);
    }
    
    produceAction(stage: IStage, options: pipelines.ProduceActionOptions): pipelines.CodePipelineActionFactoryResult {
        stage.addAction(new LambdaInvokeAction(this.invokeActionProps));
        return { runOrdersConsumed: 1 };
    }
}

I have tried everything but the LambdaInvokeStep that is supposed to be a 'Post' step gets always added as a 'Pre' step to be executed in parallel with the LambdaInvokeStep that is supposed to be the only 'Pre' step.

Thanks in advance, really looking forward for any input you guys have!! Enter image description here

No Answers

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions