Repository: awslabs/aws-ddk (ts)

Issue:
CICD Multi Account or Multi Region Deployment Fails 
### Describe the bug

When trying to deploy multi env, multi account or multi region cicd pipelines using CICDPipelineStack, the cdk synth fails at pipeline.synth() step with the error _"RuntimeError: Error: Pipeline stack which uses cross-environment actions must have an explicitly set region"_. 

### How to Reproduce

```
pipeline = CICDPipelineStack(
    app,
    id=pipeline_name,
    environment_id="cicd",
    pipeline_name=pipeline_name,
    env=cdk.Environment(
        account=cicd_params.get_config_attribute("account"),
        region=cicd_params.get_config_attribute("region"),
    ),
)
pipeline.add_source_action(repository_name=repository_name)
pipeline.add_synth_action()
pipeline.build_pipeline() # type:ignore
pipeline.add_stage(
    stage_id="dev-storage",
    stage=DataStorageStage(
    app,
    env=cdk.Environment(
        account=storage_params.get_config_attribute("account"),
        region=storage_params.get_config_attribute("region"),
    ),
    environment_id="stor",
    mode=mode,
    compute_params=compute_params,
    
),
)
pipeline.add_stage(
    stage_id="dev-compute",
    stage=DataComputeStage(
    app,
    environment_id="comp",
    mode=mode,
    storage_params=storage_params,
    env=cdk.Environment(
        account=compute_params.get_config_attribute("account"),
        region=compute_params.get_config_attribute("region"),
    ),
),
)

pipeline.synth()
#######################

app.synth()

```

If you use the cross account cross region pipeline example with the above code changes and then run cdk synth you can go to cdk.out and check the assembly-comp and assembly stor manifest.json will take in the correct region and account from ddk.json, likewise if you check the manifest.json for the pipeline, you will see that even though we pass environment it will be unknown, i think ddk ts version doesnt pass env to the top most inherited stack, this issue was not there in 0.6.2 where we passed env (line 154 in super init) https://github.com/awslabs/aws-ddk/blob/main/core/aws_ddk_core/cicd/pipeline.py

### Expected behavior

It should synthesize correctly with supplied env (account and region)

### Your project

_No response_

### Screenshots

_No response_

### Environment

```
Provide your `pip list` output, particularly the version of the AWS DDK library you used. Providing this information may significantly improve resolution times.
```


### OS

Mac

### Python version

3.9

### AWS DDK version

1.0.0b

### Additional context

_No response_

Pull request:
fix: cicd environment support
Fix to close #277 

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


Patch:
diff --git a/API.md b/API.md
index abe3f9d..c3dbf90 100644
--- a/API.md
+++ b/API.md
@@ -5462,41 +5462,162 @@ const baseStackProps: BaseStackProps = { ... }
 
 | **Name** | **Type** | **Description** |
 | --- | --- | --- |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.analyticsReporting">analyticsReporting</a></code> | <code>boolean</code> | Include runtime versioning information in this Stack. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.crossRegionReferences">crossRegionReferences</a></code> | <code>boolean</code> | Enable this flag to allow native cross region stack references. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.description">description</a></code> | <code>string</code> | A description of the stack. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.env">env</a></code> | <code>aws-cdk-lib.Environment</code> | The AWS environment (account/region) where this stack will be deployed. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.permissionsBoundary">permissionsBoundary</a></code> | <code>aws-cdk-lib.PermissionsBoundary</code> | Options for applying a permissions boundary to all IAM Roles and Users created within this Stage. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.stackName">stackName</a></code> | <code>string</code> | Name to deploy the stack with. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.synthesizer">synthesizer</a></code> | <code>aws-cdk-lib.IStackSynthesizer</code> | Synthesis method to use while deploying this stack. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.tags">tags</a></code> | <code>{[ key: string ]: string}</code> | Stack tags that will be applied to all the taggable resources and the stack itself. |
+| <code><a href="#aws-ddk-core.BaseStackProps.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | Whether to enable termination protection for this stack. |
 | <code><a href="#aws-ddk-core.BaseStackProps.property.config">config</a></code> | <code>string \| object</code> | *No description.* |
 | <code><a href="#aws-ddk-core.BaseStackProps.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.BaseStackProps.property.permissionsBoundaryArn">permissionsBoundaryArn</a></code> | <code>string</code> | *No description.* |
-| <code><a href="#aws-ddk-core.BaseStackProps.property.synthesizer">synthesizer</a></code> | <code>aws-cdk-lib.IStackSynthesizer</code> | *No description.* |
-| <code><a href="#aws-ddk-core.BaseStackProps.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | *No description.* |
 
 ---
 
-##### `config`<sup>Optional</sup> <a name="config" id="aws-ddk-core.BaseStackProps.property.config"></a>
+##### `analyticsReporting`<sup>Optional</sup> <a name="analyticsReporting" id="aws-ddk-core.BaseStackProps.property.analyticsReporting"></a>
 
 ```typescript
-public readonly config: string | object;
+public readonly analyticsReporting: boolean;
 ```
 
-- *Type:* string | object
+- *Type:* boolean
+- *Default:* `analyticsReporting` setting of containing `App`, or value of 'aws:cdk:version-reporting' context key
+
+Include runtime versioning information in this Stack.
 
 ---
 
-##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.BaseStackProps.property.environmentId"></a>
+##### `crossRegionReferences`<sup>Optional</sup> <a name="crossRegionReferences" id="aws-ddk-core.BaseStackProps.property.crossRegionReferences"></a>
 
 ```typescript
-public readonly environmentId: string;
+public readonly crossRegionReferences: boolean;
+```
+
+- *Type:* boolean
+- *Default:* false
+
+Enable this flag to allow native cross region stack references.
+
+Enabling this will create a CloudFormation custom resource
+in both the producing stack and consuming stack in order to perform the export/import
+
+This feature is currently experimental
+
+---
+
+##### `description`<sup>Optional</sup> <a name="description" id="aws-ddk-core.BaseStackProps.property.description"></a>
+
+```typescript
+public readonly description: string;
 ```
 
 - *Type:* string
+- *Default:* No description.
+
+A description of the stack.
 
 ---
 
-##### `permissionsBoundaryArn`<sup>Optional</sup> <a name="permissionsBoundaryArn" id="aws-ddk-core.BaseStackProps.property.permissionsBoundaryArn"></a>
+##### `env`<sup>Optional</sup> <a name="env" id="aws-ddk-core.BaseStackProps.property.env"></a>
 
 ```typescript
-public readonly permissionsBoundaryArn: string;
+public readonly env: Environment;
+```
+
+- *Type:* aws-cdk-lib.Environment
+- *Default:* The environment of the containing `Stage` if available, otherwise create the stack will be environment-agnostic.
+
+The AWS environment (account/region) where this stack will be deployed.
+
+Set the `region`/`account` fields of `env` to either a concrete value to
+select the indicated environment (recommended for production stacks), or to
+the values of environment variables
+`CDK_DEFAULT_REGION`/`CDK_DEFAULT_ACCOUNT` to let the target environment
+depend on the AWS credentials/configuration that the CDK CLI is executed
+under (recommended for development stacks).
+
+If the `Stack` is instantiated inside a `Stage`, any undefined
+`region`/`account` fields from `env` will default to the same field on the
+encompassing `Stage`, if configured there.
+
+If either `region` or `account` are not set nor inherited from `Stage`, the
+Stack will be considered "*environment-agnostic*"". Environment-agnostic
+stacks can be deployed to any environment but may not be able to take
+advantage of all features of the CDK. For example, they will not be able to
+use environmental context lookups such as `ec2.Vpc.fromLookup` and will not
+automatically translate Service Principals to the right format based on the
+environment's AWS partition, and other such enhancements.
+
+---
+
+*Example*
+
+```typescript
+// Use a concrete account and region to deploy this stack to:
+// `.account` and `.region` will simply return these values.
+new Stack(app, 'Stack1', {
+  env: {
+    account: '123456789012',
+    region: 'us-east-1'
+  },
+});
+
+// Use the CLI's current credentials to determine the target environment:
+// `.account` and `.region` will reflect the account+region the CLI
+// is configured to use (based on the user CLI credentials)
+new Stack(app, 'Stack2', {
+  env: {
+    account: process.env.CDK_DEFAULT_ACCOUNT,
+    region: process.env.CDK_DEFAULT_REGION
+  },
+});
+
+// Define multiple stacks stage associated with an environment
+const myStage = new Stage(app, 'MyStage', {
+  env: {
+    account: '123456789012',
+    region: 'us-east-1'
+  }
+});
+
+// both of these stacks will use the stage's account/region:
+// `.account` and `.region` will resolve to the concrete values as above
+new MyStack(myStage, 'Stack1');
+new YourStack(myStage, 'Stack2');
+
+// Define an environment-agnostic stack:
+// `.account` and `.region` will resolve to `{ "Ref": "AWS::AccountId" }` and `{ "Ref": "AWS::Region" }` respectively.
+// which will only resolve to actual values by CloudFormation during deployment.
+new MyStack(app, 'Stack1');
+```
+
+
+##### `permissionsBoundary`<sup>Optional</sup> <a name="permissionsBoundary" id="aws-ddk-core.BaseStackProps.property.permissionsBoundary"></a>
+
+```typescript
+public readonly permissionsBoundary: PermissionsBoundary;
+```
+
+- *Type:* aws-cdk-lib.PermissionsBoundary
+- *Default:* no permissions boundary is applied
+
+Options for applying a permissions boundary to all IAM Roles and Users created within this Stage.
+
+---
+
+##### `stackName`<sup>Optional</sup> <a name="stackName" id="aws-ddk-core.BaseStackProps.property.stackName"></a>
+
+```typescript
+public readonly stackName: string;
 ```
 
 - *Type:* string
+- *Default:* Derived from construct path.
+
+Name to deploy the stack with.
 
 ---
 
@@ -5507,6 +5628,32 @@ public readonly synthesizer: IStackSynthesizer;
 ```
 
 - *Type:* aws-cdk-lib.IStackSynthesizer
+- *Default:* The synthesizer specified on `App`, or `DefaultStackSynthesizer` otherwise.
+
+Synthesis method to use while deploying this stack.
+
+The Stack Synthesizer controls aspects of synthesis and deployment,
+like how assets are referenced and what IAM roles to use. For more
+information, see the README of the main CDK package.
+
+If not specified, the `defaultStackSynthesizer` from `App` will be used.
+If that is not specified, `DefaultStackSynthesizer` is used if
+`@aws-cdk/core:newStyleStackSynthesis` is set to `true` or the CDK major
+version is v2. In CDK v1 `LegacyStackSynthesizer` is the default if no
+other synthesizer is specified.
+
+---
+
+##### `tags`<sup>Optional</sup> <a name="tags" id="aws-ddk-core.BaseStackProps.property.tags"></a>
+
+```typescript
+public readonly tags: {[ key: string ]: string};
+```
+
+- *Type:* {[ key: string ]: string}
+- *Default:* {}
+
+Stack tags that will be applied to all the taggable resources and the stack itself.
 
 ---
 
@@ -5517,6 +5664,39 @@ public readonly terminationProtection: boolean;
 ```
 
 - *Type:* boolean
+- *Default:* false
+
+Whether to enable termination protection for this stack.
+
+---
+
+##### `config`<sup>Optional</sup> <a name="config" id="aws-ddk-core.BaseStackProps.property.config"></a>
+
+```typescript
+public readonly config: string | object;
+```
+
+- *Type:* string | object
+
+---
+
+##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.BaseStackProps.property.environmentId"></a>
+
+```typescript
+public readonly environmentId: string;
+```
+
+- *Type:* string
+
+---
+
+##### `permissionsBoundaryArn`<sup>Optional</sup> <a name="permissionsBoundaryArn" id="aws-ddk-core.BaseStackProps.property.permissionsBoundaryArn"></a>
+
+```typescript
+public readonly permissionsBoundaryArn: string;
+```
+
+- *Type:* string
 
 ---
 
@@ -5543,9 +5723,9 @@ const cICDPipelineStackProps: CICDPipelineStackProps = { ... }
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.synthesizer">synthesizer</a></code> | <code>aws-cdk-lib.IStackSynthesizer</code> | Synthesis method to use while deploying this stack. |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.tags">tags</a></code> | <code>{[ key: string ]: string}</code> | Stack tags that will be applied to all the taggable resources and the stack itself. |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.terminationProtection">terminationProtection</a></code> | <code>boolean</code> | Whether to enable termination protection for this stack. |
-| <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.config">config</a></code> | <code>object</code> | *No description.* |
-| <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.configPath">configPath</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.config">config</a></code> | <code>string \| object</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.environmentId">environmentId</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.permissionsBoundaryArn">permissionsBoundaryArn</a></code> | <code>string</code> | *No description.* |
 | <code><a href="#aws-ddk-core.CICDPipelineStackProps.property.pipelineName">pipelineName</a></code> | <code>string</code> | *No description.* |
 
 ---
@@ -5746,27 +5926,27 @@ Whether to enable termination protection for this stack.
 ##### `config`<sup>Optional</sup> <a name="config" id="aws-ddk-core.CICDPipelineStackProps.property.config"></a>
 
 ```typescript
-public readonly config: object;
+public readonly config: string | object;
 ```
 
-- *Type:* object
+- *Type:* string | object
 
 ---
 
-##### `configPath`<sup>Optional</sup> <a name="configPath" id="aws-ddk-core.CICDPipelineStackProps.property.configPath"></a>
+##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.CICDPipelineStackProps.property.environmentId"></a>
 
 ```typescript
-public readonly configPath: string;
+public readonly environmentId: string;
 ```
 
 - *Type:* string
 
 ---
 
-##### `environmentId`<sup>Optional</sup> <a name="environmentId" id="aws-ddk-core.CICDPipelineStackProps.property.environmentId"></a>
+##### `permissionsBoundaryArn`<sup>Optional</sup> <a name="permissionsBoundaryArn" id="aws-ddk-core.CICDPipelineStackProps.property.permissionsBoundaryArn"></a>
 
 ```typescript
-public readonly environmentId: string;
+public readonly permissionsBoundaryArn: string;
 ```
 
 - *Type:* string
diff --git a/src/base/stack.ts b/src/base/stack.ts
index 7eebbd1..5d9461b 100644
--- a/src/base/stack.ts
+++ b/src/base/stack.ts
@@ -3,10 +3,8 @@ import * as iam from "aws-cdk-lib/aws-iam";
 import { Construct } from "constructs";
 import { getStackSynthesizer } from "../config";
 
-export interface BaseStackProps {
-  readonly terminationProtection?: boolean | undefined;
+export interface BaseStackProps extends cdk.StackProps {
   readonly permissionsBoundaryArn?: string;
-  readonly synthesizer?: cdk.IStackSynthesizer;
   readonly environmentId?: string;
   readonly config?: string | object;
 }
@@ -20,7 +18,7 @@ export class BaseStack extends cdk.Stack {
       ? props.synthesizer
       : getStackSynthesizer({ environmentId: environmentId, config: props.config });
 
-    super(scope, id, { synthesizer: synthesizer, terminationProtection: props.terminationProtection });
+    super(scope, id, { synthesizer: synthesizer, ...props });
 
     if (props.permissionsBoundaryArn) {
       iam.PermissionsBoundary.of(scope).apply(
diff --git a/src/cicd/pipelines.ts b/src/cicd/pipelines.ts
index 8f21218..f85f7f7 100644
--- a/src/cicd/pipelines.ts
+++ b/src/cicd/pipelines.ts
@@ -7,7 +7,7 @@ import * as pipelines from "aws-cdk-lib/pipelines";
 import { Construct, IConstruct } from "constructs";
 import { CICDActions } from "./actions";
 import { toTitleCase } from "./utils";
-import { BaseStack } from "../base";
+import { BaseStack, BaseStackProps } from "../base";
 import { Configurator } from "../config";
 
 export interface SourceActionProps {
@@ -58,11 +58,8 @@ export interface AddCustomStageProps {
   readonly steps: pipelines.Step[];
 }
 
-export interface CICDPipelineStackProps extends cdk.StackProps {
-  readonly environmentId?: string;
+export interface CICDPipelineStackProps extends BaseStackProps {
   readonly pipelineName?: string;
-  readonly configPath?: string;
-  readonly config?: object;
 }
 
 export interface AdditionalPipelineProps {
@@ -97,7 +94,7 @@ export class CICDPipelineStack extends BaseStack {
     this.environmentId = props.environmentId;
     this.pipelineName = props.pipelineName;
     this.pipelineId = id;
-    const config = props.configPath ?? props.config ?? "./ddk.json";
+    const config = props.config ?? "./ddk.json";
     this.config = new Configurator(this, config, this.environmentId);
   }
 


Test patch:
diff --git a/integ/integ.cicd-pipeline.test.ts b/integ/integ.cicd-pipeline.test.ts
index 4605645..699ed6b 100644
--- a/integ/integ.cicd-pipeline.test.ts
+++ b/integ/integ.cicd-pipeline.test.ts
@@ -1,9 +1,7 @@
-import path from "path";
 import * as integration from "@aws-cdk/integ-tests-alpha";
 import * as cdk from "aws-cdk-lib";
 import * as lambda from "aws-cdk-lib/aws-lambda";
 import * as s3 from "aws-cdk-lib/aws-s3";
-import * as glue_alpha from "@aws-cdk/aws-glue-alpha";
 import { Construct } from "constructs";
 import { RequireApproval } from "aws-cdk-lib/cloud-assembly-schema";
 
@@ -14,7 +12,7 @@ interface CICDPipelineTestStackProps extends cdk.StackProps {}
 class CICDPipelineTestStack extends cdk.Stack {
   constructor(scope: Construct, id: string, props: CICDPipelineTestStackProps) {
     super(scope, id, props);
-    const devStage = new cdk.Stage(this, "dev", { env: { account: "228197580683" } });
+    const devStage = new cdk.Stage(this, "dev");
     const devStack = new cdk.Stack(devStage, "application-stack");
 
     const bucket = new s3.Bucket(devStack, "Bucket", {removalPolicy: cdk.RemovalPolicy.DESTROY});
@@ -33,7 +31,7 @@ class CICDPipelineTestStack extends cdk.Stack {
 
     pipeline.addStage({ stage: firehoseToS3Stage }).addStage({ stage: sqsToLambdaStage });
 
-    new CICDPipelineStack(this, "dummy-pipeline", { environmentId: "dev", pipelineName: "dummy-pipeline" })
+    new CICDPipelineStack(this, "dummy-pipeline", { environmentId: "cicd", pipelineName: "dummy-pipeline" })
       .addSourceAction({ repositoryName: "dummy-repository" })
       .addSynthAction()
       .buildPipeline()
diff --git a/test/base-stack.test.ts b/test/base-stack.test.ts
index 97d67de..334d3b3 100644
--- a/test/base-stack.test.ts
+++ b/test/base-stack.test.ts
@@ -389,3 +389,13 @@ test("Bring Your Own Stack Synthesizer", () => {
     );
   }
 });
+
+test("Additional Stack Props", () => {
+  const app = new cdk.App();
+
+  new BaseStack(app, "my-stack", {
+    environmentId: "dev",
+    description: "My Description",
+    stackName: "MyStack",
+  });
+});
diff --git a/test/cicd-pipeline-stack.test.ts b/test/cicd-pipeline-stack.test.ts
index beeff99..c72bd5e 100644
--- a/test/cicd-pipeline-stack.test.ts
+++ b/test/cicd-pipeline-stack.test.ts
@@ -614,3 +614,30 @@ test("Test Pipeline with Artifact Upload", () => {
 
   Template.fromStack(stack);
 });
+
+test("CICDPipeline with different environments", () => {
+  const app = new cdk.App();
+
+  const stageA = new cdk.Stage(app, "foo", { env: { account: "000000000000", region: "us-east-1" } });
+  const stackA = new cdk.Stack(stageA, "application-stack");
+  new s3.Bucket(stackA, "BucketA");
+
+  const stageB = new cdk.Stage(app, "bar", { env: { account: "111111111111", region: "us-east-1" } });
+  const stackB = new cdk.Stack(stageB, "application-stack");
+  new s3.Bucket(stackB, "BucketB");
+
+  const stack = new CICDPipelineStack(app, "dummy-pipeline", {
+    env: { region: "us-east-1" },
+    environmentId: "dev",
+    pipelineName: "dummy-pipeline",
+  })
+    .addSourceAction({ repositoryName: "dummy-repository" })
+    .addSynthAction()
+    .buildPipeline()
+    .addStage({ stageId: "a", stage: stageA })
+    .addStage({ stageId: "b", stage: stageB })
+    .synth();
+
+  const template = Template.fromStack(stack);
+  template.resourceCountIs("AWS::CodePipeline::Pipeline", 1);
+});