Metadata-Version: 2.1
Name: cdk-remote-stack
Version: 0.1.212
Summary: Get outputs and parameters from remote CDK stacks
Home-page: https://github.com/pahud/cdk-remote-stack.git
Author: Pahud Hsieh<pahudnet@gmail.com>
License: Apache-2.0
Project-URL: Source, https://github.com/pahud/cdk-remote-stack.git
Platform: UNKNOWN
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: JavaScript
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Typing :: Typed
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aws-cdk.aws-iam (<2.0.0,>=1.77.0)
Requires-Dist: aws-cdk.aws-lambda (<2.0.0,>=1.77.0)
Requires-Dist: aws-cdk.aws-logs (<2.0.0,>=1.77.0)
Requires-Dist: aws-cdk.aws-ssm (<2.0.0,>=1.77.0)
Requires-Dist: aws-cdk.core (<2.0.0,>=1.77.0)
Requires-Dist: aws-cdk.custom-resources (<2.0.0,>=1.77.0)
Requires-Dist: constructs (<4.0.0,>=3.2.27)
Requires-Dist: jsii (<2.0.0,>=1.44.2)
Requires-Dist: publication (>=0.0.3)

[![awscdk-jsii-template](https://img.shields.io/badge/built%20with-awscdk--jsii--template-blue)](https://github.com/pahud/awscdk-jsii-template)
[![npm version](https://badge.fury.io/js/cdk-remote-stack.svg)](https://badge.fury.io/js/cdk-remote-stack)
[![PyPI version](https://badge.fury.io/py/cdk-remote-stack.svg)](https://badge.fury.io/py/cdk-remote-stack)
[![Build](https://github.com/pahud/cdk-remote-stack/actions/workflows/build.yml/badge.svg)](https://github.com/pahud/cdk-remote-stack/actions/workflows/build.yml)

# cdk-remote-stack

Get outputs from cross-region AWS CloudFormation stacks

# Why

Setting up cross-regional cross-stack references requires using multiple constructs from the AWS CDK construct library and is not straightforward.

`cdk-remote-stack` aims to simplify the cross-regional cross-stack references to help you easily build cross-regional multi-stack AWS CDK applications.

This construct library provides two main constructs:

* **RemoteOutputs** - cross regional stack outputs reference.
* **RemoteParameters** - cross regional/account SSM parameters reference.

# RemoteOutputs

`RemoteOutputs` is ideal for one stack referencing the outputs from another across different AWS regions.

Let's say we have two cross-regional stacks in the same AWS CDK application:

1. **stackJP** - stack in Japan (`JP`) to create a SNS topic
2. **stackUS** - stack in United States (`US`) to get the outputs from `stackJP` and print out the SNS `TopicName` from `stackJP` outputs.

```python
# Example automatically generated from non-compiling source. May contain errors.
from cdk_remote_stack import RemoteOutputs
import aws_cdk.core as cdk

app = cdk.App()

env_jP = {
    "region": "ap-northeast-1",
    "account": process.env.CDK_DEFAULT_ACCOUNT
}

env_uS = {
    "region": "us-west-2",
    "account": process.env.CDK_DEFAULT_ACCOUNT
}

# first stack in JP
stack_jP = cdk.Stack(app, "demo-stack-jp", env=env_jP)

cdk.CfnOutput(stack_jP, "TopicName", value="foo")

# second stack in US
stack_uS = cdk.Stack(app, "demo-stack-us", env=env_uS)

# ensure the dependency
stack_uS.add_dependency(stack_jP)

# get the stackJP stack outputs from stackUS
outputs = RemoteOutputs(stack_uS, "Outputs", stack=stack_jP)

remote_output_value = outputs.get("TopicName")

# the value should be exactly the same with the output value of `TopicName`
cdk.CfnOutput(stack_uS, "RemoteTopicName", value=remote_output_value)
```

At this moment, `RemoteOutputs` only supports cross-regional reference in a single AWS account.

## Always get the latest stack output

By default, the `RemoteOutputs` construct will always try to get the latest output from the source stack. You may opt out by setting `alwaysUpdate` to `false` to turn this feature off.

For example:

```python
# Example automatically generated from non-compiling source. May contain errors.
outputs = RemoteOutputs(stack_uS, "Outputs",
    stack=stack_jP,
    always_update=False
)
```

# RemoteParameters

[AWS Systems Manager (AWS SSM) Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) is great to store and persist parameters and allow stacks from other regons/accounts to reference. Let's dive into the two major scenarios below:

## #1 - Stacks from single account and different regions

In this sample, we create two stacks from JP (`ap-northeast-1`) and US (`us-west-2`). The JP stack will produce and update parameters in its parameter store, while the US stack will consume the parameters across differnt regions with the `RemoteParameters` construct.

![](images/remote-param-1.svg)

```python
# Example automatically generated from non-compiling source. May contain errors.
env_jP = {"region": "ap-northeast-1", "account": "111111111111"}
env_uS = {"region": "us-west-2", "account": "111111111111"}

# first stack in JP
producer_stack_name = "demo-stack-jp"
stack_jP = cdk.Stack(app, producer_stack_name, env=env_jP)
parameter_path = f"/{envJP.account}/{envJP.region}/{producerStackName}"

ssm.StringParameter(stack_jP, "foo1",
    parameter_name=f"{parameterPath}/foo1",
    string_value="bar1"
)
ssm.StringParameter(stack_jP, "foo2",
    parameter_name=f"{parameterPath}/foo2",
    string_value="bar2"
)
ssm.StringParameter(stack_jP, "foo3",
    parameter_name=f"{parameterPath}/foo3",
    string_value="bar3"
)

# second stack in US
stack_uS = cdk.Stack(app, "demo-stack-us", env=env_uS)

# ensure the dependency
stack_uS.add_dependency(stack_jP)

# get remote parameters by path from AWS SSM parameter store
parameters = RemoteParameters(stack_uS, "Parameters",
    path=parameter_path,
    region=stack_jP.region
)

foo1 = parameters.get(f"{parameterPath}/foo1")
foo2 = parameters.get(f"{parameterPath}/foo2")
foo3 = parameters.get(f"{parameterPath}/foo3")

cdk.CfnOutput(stack_uS, "foo1Output", value=foo1)
cdk.CfnOutput(stack_uS, "foo2Output", value=foo2)
cdk.CfnOutput(stack_uS, "foo3Output", value=foo3)
```

## #2 - Stacks from differnt accounts and different regions

Similar to the use case above, but now we deploy stacks in separate accounts and regions.  We will need to pass an AWS Identity and Access Management (AWS IAM) `role` to the `RemoteParameters` construct to get all the parameters from the remote environment.

![](images/remote-param-2.svg)

```python
# Example automatically generated from non-compiling source. May contain errors.
env_jP = {"region": "ap-northeast-1", "account": "111111111111"}
env_uS = {"region": "us-west-2", "account": "222222222222"}

# first stack in JP
producer_stack_name = "demo-stack-jp"
stack_jP = cdk.Stack(app, producer_stack_name, env=env_jP)
parameter_path = f"/{envJP.account}/{envJP.region}/{producerStackName}"

ssm.StringParameter(stack_jP, "foo1",
    parameter_name=f"{parameterPath}/foo1",
    string_value="bar1"
)
ssm.StringParameter(stack_jP, "foo2",
    parameter_name=f"{parameterPath}/foo2",
    string_value="bar2"
)
ssm.StringParameter(stack_jP, "foo3",
    parameter_name=f"{parameterPath}/foo3",
    string_value="bar3"
)

# allow US account to assume this read only role to get parameters
cdk_read_only_role = iam.Role(stack_jP, "readOnlyRole",
    assumed_by=iam.AccountPrincipal(env_uS.account),
    role_name=PhysicalName.GENERATE_IF_NEEDED,
    managed_policies=[iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMReadOnlyAccess")]
)

# second stack in US
stack_uS = cdk.Stack(app, "demo-stack-us", env=env_uS)

# ensure the dependency
stack_uS.add_dependency(stack_jP)

# get remote parameters by path from AWS SSM parameter store
parameters = RemoteParameters(stack_uS, "Parameters",
    path=parameter_path,
    region=stack_jP.region,
    # assume this role for cross-account parameters
    role=iam.Role.from_role_arn(stack_uS, "readOnlyRole", cdk_read_only_role.role_arn)
)

foo1 = parameters.get(f"{parameterPath}/foo1")
foo2 = parameters.get(f"{parameterPath}/foo2")
foo3 = parameters.get(f"{parameterPath}/foo3")

cdk.CfnOutput(stack_uS, "foo1Output", value=foo1)
cdk.CfnOutput(stack_uS, "foo2Output", value=foo2)
cdk.CfnOutput(stack_uS, "foo3Output", value=foo3)
```

## #3 - Dedicated account for a centralized parameter store

The parameters are stored in a centralized account/region and previously provisioned as a source-of-truth configuration store. All other stacks from different accounts/regions are consuming the parameters from the central configuration store.

This scenario is pretty much like #2. The difference is that there's a dedicated account for centralized configuration store being shared with all other accounts.

![](images/remote-param-3.svg)

You will need create `RemoteParameters` for all the consuming stacks like:

```python
# Example automatically generated from non-compiling source. May contain errors.
# for StackUS
RemoteParameters(stack_uS, "Parameters",
    path=parameter_path,
    region="eu-central-1",
    # assume this role for cross-account parameters
    role=iam.Role.from_role_arn(stack_uS, "readOnlyRole", shared_read_only_role_arn)
)

# for StackJP
RemoteParameters(stack_jP, "Parameters",
    path=parameter_path,
    region="eu-central-1",
    # assume this role for cross-account parameters
    role=iam.Role.from_role_arn(stack_jP, "readOnlyRole", shared_read_only_role_arn)
)
```

## Tools for multi-account deployment

You will need to install and bootstrap your target accounts with AWS CDK 1.108.0 or later, so you can deploy stacks from different accounts. It [adds support](https://github.com/aws/aws-cdk/pull/14874) for cross-account lookups. Alternatively, install [cdk-assume-role-credential-plugin](https://github.com/aws-samples/cdk-assume-role-credential-plugin). Read this [blog post](https://aws.amazon.com/tw/blogs/devops/cdk-credential-plugin/) to setup this plugin.

## Limitations

1. At this moment, the `RemoteParameters` construct only supports the `String` data type from parameter store.
2. Maximum number of parameters is `100`. Will make it configurable in the future if required.

# Contributing

See [CONTRIBUTING](CONTRIBUTING.md) for more information.

# License

This code is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file.


