Metadata-Version: 2.1
Name: cdk-stacksets
Version: 0.0.3
Summary: cdk-stacksets
Home-page: https://github.com/cdklabs/cdk-stacksets.git
Author: Amazon Web Services<aws-cdk-dev@amazon.com>
License: Apache-2.0
Project-URL: Source, https://github.com/cdklabs/cdk-stacksets.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.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Typing :: Typed
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved
Requires-Python: ~=3.7
Description-Content-Type: text/markdown
License-File: LICENSE

# CDK StackSets Construct Library

<!--BEGIN STABILITY BANNER-->---


![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge)

> The APIs of higher level constructs in this module are experimental and under active development.
> They are subject to non-backward compatible changes or removal in any future version. These are
> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be
> announced in the release notes. This means that while you may use them, you may need to update
> your source code when upgrading to a newer version of this package.

---
<!--END STABILITY BANNER-->

This construct library allows you to define AWS CloudFormation StackSets.

```python
stack = Stack()
stack_set_stack = StackSetStack(stack, "MyStackSet")

StackSet(stack, "StackSet",
    target=StackSetTarget.from_accounts(
        regions=["us-east-1"],
        accounts=["11111111111"],
        parameter_overrides={
            "SomeParam": "overrideValue"
        }
    ),
    template=StackSetTemplate.from_stack_set_stack(stack_set_stack)
)
```

## Installing

### TypeScript/JavaScript

```bash
npm install cdk-stacksets
```

### Python

```bash
pip install cdk-stacksets
```

### Java

```xml
// add this to your pom.xml
<dependency>
    <groupId>io.github.cdklabs</groupId>
    <artifactId>cdk-stacksets</artifactId>
    <version>0.0.0</version> // replace with version
</dependency>
```

### .NET

```bash
dotnet add package CdklabsCdkStacksets --version X.X.X
```

### Go

```bash
go get cdk-stacksets-go
```

## Creating a StackSet Stack

StackSets allow you to deploy a single CloudFormation template across multiple AWS accounts and regions.
Typically when creating a CDK Stack that will be deployed across multiple environments, the CDK will
synthesize separate Stack templates for each environment (account/region combination). Because of the
way that StackSets work, StackSet Stacks behave differently. For Stacks that will be deployed via StackSets
a single Stack is defined and synthesized. Any environmental differences must be encoded using Parameters.

A special class was created to handle the uniqueness of the StackSet Stack.
You declare a `StackSetStack` the same way that you declare a normal `Stack`, but there
are a couple of differences. `StackSetStack`s have a couple of special requirements/limitations when
compared to Stacks.

*Requirements*

* Must be created in the scope of a `Stack`
* Must be environment agnostic

*Limitations*

* Do not support assets

Once you create a `StackSetStack` you can create resources within the stack.

```python
stack = Stack()
stack_set_stack = StackSetStack(stack, "StackSet")

iam.Role(stack_set_stack, "MyRole",
    assumed_by=iam.ServicePrincipal("myservice.amazonaws.com")
)
```

Or

```python
class MyStackSet(StackSetStack):
    def __init__(self, scope, id):
        super().__init__(scope, id)

        iam.Role(self, "MyRole",
            assumed_by=iam.ServicePrincipal("myservice.amazonaws.com")
        )
```

## Creating a StackSet

AWS CloudFormation StackSets enable you to create, update, or delete stacks across multiple accounts and AWS Regions
with a single operation. Using an administrator account, you define and manage an AWS CloudFormation template, and use
the template as the basis for provisioning stacks into selected target accounts across specific AWS Regions.

There are two methods for defining *where* the StackSet should be deployed. You can either define individual accounts, or
you can define AWS Organizations organizational units.

### Deploying to individual accounts

Deploying to individual accounts requires you to specify the account ids. If you want to later deploy to additional accounts,
or remove the stackset from accounts, this has to be done by adding/removing the account id from the list.

```python
stack = Stack()
stack_set_stack = StackSetStack(stack, "MyStackSet")

StackSet(stack, "StackSet",
    target=StackSetTarget.from_accounts(
        regions=["us-east-1"],
        accounts=["11111111111"]
    ),
    template=StackSetTemplate.from_stack_set_stack(stack_set_stack)
)
```

### Deploying to organizational units

AWS Organizations is an AWS service that enables you to centrally manage and govern multiple accounts.
AWS Organizations allows you to define organizational units (OUs) which are logical groupings of AWS accounts.
OUs enable you to organize your accounts into a hierarchy and make it easier for you to apply management controls.
For a deep dive on OU best practices you can read the [Best Practices for Organizational Units with AWS Organizations](https://aws.amazon.com/blogs/mt/best-practices-for-organizational-units-with-aws-organizations/) blog post.

You can either specify the organization itself, or individual OUs. By default the StackSet will be deployed
to all AWS accounts that are part of the OU. If the OU is nested it will also deploy to all accounts
that are part of any nested OUs.

For example, given the following org hierarchy

```mermaid
graph TD
  root-->ou-1;
  root-->ou-2;
  ou-1-->ou-3;
  ou-1-->ou-4;
  ou-3-->account-1;
  ou-3-->account-2;
  ou-4-->account-4;
  ou-2-->account-3;
  ou-2-->account-5;
```

You could deploy to all AWS accounts under OUs `ou-1`, `ou-3`, `ou-4` by specifying the following:

```python
stack = Stack()
stack_set_stack = StackSetStack(stack, "MyStackSet")

StackSet(stack, "StackSet",
    target=StackSetTarget.from_organizational_units(
        regions=["us-east-1"],
        organizational_units=["ou-1"]
    ),
    template=StackSetTemplate.from_stack_set_stack(stack_set_stack)
)
```

This would deploy the StackSet to `account-1`, `account-2`, `account-4`.

If there are specific AWS accounts that are part of the specified OU hierarchy that you would like
to exclude, this can be done by specifying `excludeAccounts`.

```python
stack = Stack()
stack_set_stack = StackSetStack(stack, "MyStackSet")

StackSet(stack, "StackSet",
    target=StackSetTarget.from_organizational_units(
        regions=["us-east-1"],
        organizational_units=["ou-1"],
        exclude_accounts=["account-2"]
    ),
    template=StackSetTemplate.from_stack_set_stack(stack_set_stack)
)
```

This would deploy only to `account-1` & `account-4`, and would exclude `account-2`.

Sometimes you might have individual accounts that you would like to deploy the StackSet to, but
you do not want to include the entire OU. To do that you can specify `additionalAccounts`.

```python
stack = Stack()
stack_set_stack = StackSetStack(stack, "MyStackSet")

StackSet(stack, "StackSet",
    target=StackSetTarget.from_organizational_units(
        regions=["us-east-1"],
        organizational_units=["ou-1"],
        additional_accounts=["account-5"]
    ),
    template=StackSetTemplate.from_stack_set_stack(stack_set_stack)
)
```

This would deploy the StackSet to `account-1`, `account-2`, `account-4` & `account-5`.

### StackSet permissions

There are two modes for managing StackSet permissions (i.e. *where* StackSets can deploy & *what* resources they can create).
A StackSet can either be `Service Managed` or `Self Managed`.

You can control this through the `deploymentType` parameter.

#### Service Managed

When a StackSet is service managed, the permissions are managed by AWS Organizations. This allows the StackSet to deploy the Stack to *any*
account within the organization. In addition, the StackSet will be able to create *any* type of resource.

```python
stack = Stack()
stack_set_stack = StackSetStack(stack, "MyStackSet")

StackSet(stack, "StackSet",
    target=StackSetTarget.from_organizational_units(
        regions=["us-east-1"],
        organizational_units=["ou-1"]
    ),
    deployment_type=DeploymentType.service_managed(),
    template=StackSetTemplate.from_stack_set_stack(stack_set_stack)
)
```

When you specify `serviceManaged` deployment type, automatic deployments are enabled by default.
Automatic deployments allow the StackSet to be automatically deployed to or deleted from
AWS accounts when they are added or removed from the specified organizational units.

## Deploying StackSets using CDK Pipelines

You can also deploy StackSets using [CDK Pipelines](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines-readme.html)

Below is an example of a Pipeline that deploys from a central account. It also
defines separate stages for each "environment" so that you can first test out
the stackset in pre-prod environments.

This would be an automated way of deploying the bootstrap stack described in
[this blog
post](https://aws.amazon.com/blogs/mt/bootstrapping-multiple-aws-accounts-for-aws-cdk-using-cloudformation-stacksets/).

```python
# app: App


class BootstrapStage(Stage):
    def __init__(self, scope, id, *, initialBootstrapTarget, stacksetName=None, env=None, outdir=None, stageName=None):
        super().__init__(scope, id, initialBootstrapTarget=initialBootstrapTarget, stacksetName=stacksetName, env=env, outdir=outdir, stageName=stageName)

        stack = Stack(self, "BootstrapStackSet")

        bootstrap = Bootstrap(stack, "CDKToolkit")

        stack_set = StackSet(stack, "StackSet",
            template=StackSetTemplate.from_stack_set_stack(bootstrap),
            target=initial_bootstrap_target,
            capabilities=[Capability.NAMED_IAM],
            managed_execution=True,
            stack_set_name=stackset_name,
            deployment_type=DeploymentType.service_managed(
                delegated_admin=True,
                auto_deploy_enabled=True,
                auto_deploy_retain_stacks=False
            ),
            operation_preferences=OperationPreferences(
                region_concurrency_type=RegionConcurrencyType.PARALLEL,
                max_concurrent_percentage=100,
                failure_tolerance_percentage=99
            )
        )

pipeline = pipelines.CodePipeline(self, "BootstrapPipeline",
    synth=pipelines.ShellStep("Synth",
        commands=["yarn install --frozen-lockfile", "npx cdk synth"
        ],
        input=pipelines.CodePipelineSource.connection("myorg/myrepo", "main",
            connection_arn="arn:aws:codestar-connections:us-east-2:111111111111:connection/ca65d487-ca6e-41cc-aab2-645db37fdb2b"
        )
    ),
    self_mutation=True
)

regions = ["us-east-1", "us-east-2", "us-west-2", "eu-west-2", "eu-west-1", "ap-south-1", "ap-southeast-1"
]

pipeline.add_stage(
    BootstrapStage(app, "DevBootstrap",
        env=Environment(
            region="us-east-1",
            account="111111111111"
        ),
        stackset_name="CDKToolkit-dev",
        initial_bootstrap_target=StackSetTarget.from_organizational_units(
            regions=regions,
            organizational_units=["ou-hrza-ar333427"]
        )
    ))

pipeline.add_stage(
    BootstrapStage(app, "ProdBootstrap",
        env=Environment(
            region="us-east-1",
            account="111111111111"
        ),
        stackset_name="CDKToolkit-prd",
        initial_bootstrap_target=StackSetTarget.from_organizational_units(
            regions=regions,
            organizational_units=["ou-hrza-bb999427", "ou-hraa-ar111127"]
        )
    ))
```


