aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.64k stars 3.91k forks source link

(pipelines): Assets are missing to be packaged since StackSynthesizer being ignored #31070

Open mrlikl opened 2 months ago

mrlikl commented 2 months ago

Describe the bug

When a CDK pipeline with 2 stages targeting the same account and region but with different synthesizers, the assets are packaged only once.

Expected Behavior

The assets be packaged for both the Stages. The stages along with assets are generated correctly only that the asset stage in the pipeline is missing publish for the stage.

Current Behavior

The BuildSpec generate is missing asset publish prd stage:

{
    "version": "0.2",
    "phases": {
        "install": {
            "commands": [
                "npm install -g cdk-assets@2"
            ]
        },
        "build": {
            "commands": [
                "cdk-assets --path assembly-Pipeline-Staging/PipelineStagingA8C81619.assets.json --verbose publish 652f12ad2ae83a553600995736a7129bd3f2c46d13f49048a9f92059e6c66822:012345678901-ap-southeast-2"
            ]
        }
    }
}

Reproduction Steps

Sample code to replicate the issue:

from aws_cdk import (
    Stack,
    Stage,
    pipelines,
    aws_codecommit,
    Environment,
    DefaultStackSynthesizer,
)
from aws_cdk import aws_lambda as _lambda
from constructs import Construct

class DeploymentStage(Stage):
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        account_id: str,
        region: str,
        environment_abbreviation: str,
        **kwargs,
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)
        if environment_abbreviation == "stg" and account_id == "012345678901":
            synth = DefaultStackSynthesizer(
                qualifier="hnb659fdj",
                file_assets_bucket_name="cdk-hnb659fdj-assets-012345678901-ap-southeast-2"
            )
        elif environment_abbreviation == "prd" and account_id == "012345678901":
            synth = DefaultStackSynthesizer(
                qualifier="hnb659fdk",
                file_assets_bucket_name="cdk-hnb659fdk-assets-012345678901-ap-southeast-2"
            )
        stack = TestCdkStack(
            scope=self,
            stack_name=f"{environment_abbreviation}-aie-sagemaker-studio-cdk",
            construct_id=construct_id,
            env=Environment(
                account=f"{account_id}", region=f"{region}"
            ),
            synthesizer=synth,
        )

class Pipeline(Stack):
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        **kwargs,
    ) -> None:
        super().__init__(
            scope, construct_id, **kwargs
        )
        existing_repository = aws_codecommit.Repository.from_repository_name(
            scope=self,
            id="ExistingRepository",
            repository_name='cdkpipeline',
        )
        pipeline = pipelines.CodePipeline(
            scope=self,
            id="Pipeline",
            synth=pipelines.ShellStep(
                id="Synthesis",
                input=pipelines.CodePipelineSource.code_commit(
                    repository=existing_repository,
                    branch='main',
                ),
                commands=[
                    "cdk synth",
                ],
                install_commands=[
                    "npm install -g aws-cdk",
                    "python -m pip install -r requirements.txt",
                    "python -m pip install -r requirements-dev.txt",
                ],
            ),
            cross_account_keys=True,
        )
        staging_stage = DeploymentStage(
            scope=self,
            construct_id="Staging",
            account_id="012345678901",
            environment_abbreviation="stg",
            region="ap-southeast-2"
        )
        pipeline.add_stage(staging_stage)
        production_stage = DeploymentStage(
            scope=self,
            construct_id="Production",
            account_id="012345678901",
            environment_abbreviation="prd",
            region="ap-southeast-2"
        )
        pipeline.add_stage(production_stage)

class TestCdkStack(Stack):
    def get_associated_id(self, table, environment, account_number):
        return table.get(environment, {}).get(account_number)
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        **kwargs,
    ) -> None:
        super().__init__(
            scope=scope,
            id=construct_id,
            **kwargs,
        )
        _lambda.Function(self, "test", runtime=_lambda.Runtime.PYTHON_3_9, handler="index.handler",
                         code=_lambda.Code.from_asset("lambda-handler"))

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.151

Framework Version

No response

Node.js Version

v18.20.4

OS

macos

Language

TypeScript, Python

Language Version

No response

Other information

No response

pahud commented 2 months ago

I didn't manage to deploy your provided code snippet but I will bring this to team's attention.

12:54:16 PM | CREATE_FAILED        | AWS::KMS::Key         | CrossRegionCodePip...ryptionKey70216490
Resource handler returned message: "Policy contains a statement with one or more invalid principals. (Service: Kms, Status Code: 400, Request ID: d999b1df-98d1
-4a21-8b3e-49ea3672e709)" (RequestToken: 7d97c070-6417-d89c-6b77-4fa7e00b516b, HandlerErrorCode: InvalidRequest)
pahud commented 2 months ago

internal tracking: V1478992191

pahud commented 2 months ago

confirmed.

With code snippet like this, the assets stage codebuild would not upload assets to both destinations.

class DeploymentStage(Stage):
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        account_id: str,
        region: str,
        environment_abbreviation: str,
        **kwargs,
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)
        if environment_abbreviation == "stg" and account_id == "123456789012":
            synth = DefaultStackSynthesizer(
                qualifier="dev",
                file_assets_bucket_name="cdk-dev-assets-123456789012-ap-southeast-1"
            )
        elif environment_abbreviation == "prd" and account_id == "123456789012":
            synth = DefaultStackSynthesizer(
                qualifier="hnb659fds",
                file_assets_bucket_name="cdk-hnb659fds-assets-123456789012-ap-southeast-1"
            )
        stack = TestCdkStack(
            scope=self,
            stack_name=f"{environment_abbreviation}-aie-sagemaker-studio-cdk",
            construct_id=construct_id,
            env=Environment(
                account=f"{account_id}", region=f"{region}"
            ),
            synthesizer=synth,
        )

class Pipeline(Stack):
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        **kwargs,
    ) -> None:
        super().__init__(
            scope, construct_id, **kwargs
        )
        # existing_repository = aws_codecommit.Repository.from_repository_name(
        #     scope=self,
        #     id="ExistingRepository",
        #     repository_name='cdkpipeline',
        # )
        bucket = s3.Bucket.from_bucket_name(self, "assetsBucket", "<deducted>")
        pipeline = pipelines.CodePipeline(
            scope=self,
            id="Pipeline",
            synth=pipelines.ShellStep(
                id="Synthesis",
                input=pipelines.CodePipelineSource.s3(
                    bucket,
                    "cdk-sample.zip"
                ),
                commands=[
                    "cdk synth",
                ],
                install_commands=[
                    "npm install -g aws-cdk",
                    "python -m pip install -r requirements.txt",
                    "python -m pip install -r requirements-dev.txt",
                ],
            ),
            # cross_account_keys=True,
        )
        staging_stage = DeploymentStage(
            scope=self,
            construct_id="Staging",
            account_id="903779448426",
            environment_abbreviation="stg",
            region="ap-southeast-1"
        )
        pipeline.add_stage(staging_stage)
        production_stage = DeploymentStage(
            scope=self,
            construct_id="Production",
            account_id="903779448426",
            environment_abbreviation="prd",
            region="ap-southeast-1"
        )
        pipeline.add_stage(production_stage)

class TestCdkStack(Stack):
    def get_associated_id(self, table, environment, account_number):
        return table.get(environment, {}).get(account_number)
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        **kwargs,
    ) -> None:
        super().__init__(
            scope=scope,
            id=construct_id,
            **kwargs,
        )
        _lambda.Function(self, "test", runtime=_lambda.Runtime.PYTHON_3_9, handler="index.handler",
                         code=_lambda.Code.from_asset("lambda-handler"))

buildspec for the assets stage:

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "npm install -g cdk-assets@2"
      ]
    },
    "build": {
      "commands": [
        "cdk-assets --path \"assembly-issue-triage-python-stack-Staging/<deducted>.assets.json\" --verbose publish \"<deducted>:123456789012-ap-southeast-1\""
      ]
    }
  }
}