Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Multiple Stacks #636

Merged
merged 32 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
259077b
Synthesize multiple stacks
skorfmann Apr 14, 2021
90fc8a1
Merge branch 'main' into multiple-stacks
skorfmann Apr 15, 2021
968faf2
Deploy / destroy / diff multiple stacks
skorfmann Apr 16, 2021
4d97db6
Merge branch 'main' into multiple-stacks
skorfmann Apr 16, 2021
ea1e4bf
Merge branch 'main' into multiple-stacks
skorfmann Apr 16, 2021
7b209dd
Adapt to jsii
skorfmann Apr 16, 2021
7103eb2
Extract manifest in dedicated file
skorfmann Apr 16, 2021
25948e0
Dynamic version
skorfmann Apr 16, 2021
f51ff9e
Drop orhpaned directories
skorfmann Apr 19, 2021
1efa4eb
Adjust existing integration tests to changes
skorfmann Apr 19, 2021
469bd96
Remove obsolete snapshot
skorfmann Apr 19, 2021
ef65315
Trying to prevent random 422 errors in TF Cloud
skorfmann Apr 19, 2021
a18898c
Integegration test for multiple stacks
skorfmann Apr 20, 2021
c5105b3
List stacks command
skorfmann Apr 20, 2021
75eaaf0
Perhaps it needs even more time?
skorfmann Apr 20, 2021
6322a7e
Add docs for multiple stacks
skorfmann Apr 21, 2021
67acd34
Update docs
skorfmann Apr 21, 2021
037c96d
Integration test for cdktf list
skorfmann Apr 21, 2021
4023a45
Test manifest
skorfmann Apr 21, 2021
3159db8
Explicit multiple stacks example
skorfmann Apr 21, 2021
ca8d63e
Add Python integration test
skorfmann Apr 21, 2021
5447f63
Typo
skorfmann Apr 21, 2021
daefa91
A more uplifting error message
skorfmann Apr 21, 2021
03685e3
Provide context for ugly workaround
skorfmann Apr 21, 2021
a4ce96a
Adjust help texts for cli templates
skorfmann Apr 21, 2021
b160bbc
Simplify test
skorfmann Apr 21, 2021
a2af2ec
This doesnt work - Will be fixed with https://github.com/hashicorp/te…
skorfmann Apr 21, 2021
3561a4d
Merge branch 'main' into multiple-stacks
skorfmann Apr 21, 2021
66348ba
Merge branch 'main' into multiple-stacks
skorfmann Apr 21, 2021
f44287f
Explicit timeout
skorfmann Apr 21, 2021
e2f16ce
Merge branch 'main' into multiple-stacks
skorfmann Apr 21, 2021
3432f69
Typo
skorfmann Apr 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ tsconfig.json
**/coverage
**/dist
**/.terraform
**/*.tfstate
**/*.tfstate.backup
.vscode
bootstrap.json
terraform-cdk.github-issues
Expand Down
150 changes: 150 additions & 0 deletions docs/working-with-cdk-for-terraform/app-stacks-concept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# App / Stacks Concept

In CDK for Terraform we have the concept of an Application which consists of one or more Stacks.

## Constructs

CDK for Terraform apps are structured as a tree of [constructs](https://github.com/aws/constructs). The classes `App`, `TerraformStack`, `TerraformResource` and `Resource` are all deriving from `Construct` and are therefore represented as a node in the application tree, where the `App` node is the root.

## Application

The app is host of stacks and the root node in the constructs tree. It can be used to provide global configuration to each stack and underlying constructs.

### App Context

One option to provide global configuration is the app `context`, which can be accessed in any construct within the app.

```typescript
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, Instance } from "./.gen/providers/aws";

class MyStack extends TerraformStack {
constructor(scope: Construct, id: string) {
super(scope, id);

new AwsProvider(this, "aws", {
region: "us-east-1",
});

new Instance(this, "Hello", {
ami: "ami-2757f631",
instanceType: "t2.micro",
tags: {
myConfig: this.constructNode.getContext('myConfig')
}
});
}
}

const app = new App({context: {myConfig: 'config'}});
new MyStack(app, "hello-cdktf");
app.synth();
```

## Stack

A stack represents a collection of infrastructure which will be synthesized as a dedicated Terraform configuration. In comparision to the Terraform CLI, a Stack is equal to a dedicated working directory. Stacks are useful to separate the state management within an application.

## A single Stack

The following example will synthesize a single Terraform configuration in the configured output folder. When running `cdktf synth` we'll find the synthesized Terraform configuration in the folder `cdktf.out/stacks/a-single-stack`

```typescript
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, Instance } from "./.gen/providers/aws";

class MyStack extends TerraformStack {
constructor(scope: Construct, id: string) {
super(scope, id);

new AwsProvider(this, "aws", {
region: "us-east-1",
});

new Instance(this, "Hello", {
ami: "ami-2757f631",
instanceType: "t2.micro"
});
}
}

const app = new App();
new MyStack(app, "a-single-stack");
app.synth();
```

## Multiple Stacks

The following example will synthesize multiple Terraform configurations in the configured output folder.

```typescript
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, Instance } from "./.gen/providers/aws";

interface MyStackConfig {
environment: string,
region?: string;
}

class MyStack extends TerraformStack {
constructor(scope: Construct, id: string, config: MyStackConfig) {
super(scope, id);

const { region = 'us-east-1' } = config

new AwsProvider(this, "aws", {
region,
});

new Instance(this, "Hello", {
ami: "ami-2757f631",
instanceType: "t2.micro",
tags: {
environment: config.environment
}
});
}
}

const app = new App();
new MyStack(app, "multiple-stacks-dev", { environment: 'dev' });
new MyStack(app, "multiple-stacks-staging", { environment: 'staging' });
new MyStack(app, "multiple-stacks-production-us", { environment: 'production', region: 'us-east-1' });
new MyStack(app, "multiple-stacks-production-eu", { environment: 'production', region: 'eu-central-1' });
app.synth();
```

After running `cdktf synth` we can see the following synthesized stacks:

```
$ cdktf list

Stack name Path
multiple-stacks-dev cdktf.out/stacks/multiple-stacks-dev
multiple-stacks-staging cdktf.out/stacks/multiple-stacks-staging
multiple-stacks-production-us cdktf.out/stacks/multiple-stacks-production-us
multiple-stacks-production-eu cdktf.out/stacks/multiple-stacks-production-eu
```

### Current Limitations

#### Deployments

At the moment all Terraform operations are limited to a single stack. In order to run `diff`, `deploy` or `destroy`, a target stack has to be specified. A deploy command like `cdktf deploy multiple-stacks-dev` will work and all Terraform operations will run in the folder `cdktf.out/stacks/multiple-stacks-dev`.

Omitting the target stack by running a plain `cdktf deploy` will result in error. This will change in future versions, where support for targeting all or a subset of stacks will be added.

Please track this [issue](https://github.com/hashicorp/terraform-cdk/issues/650) when you're interested in this feature.

#### Cross Stack References

Referencing resources from another stack is not yet supported automatically. It can be achieved manually by using Outputs and the Remote State data source.

Please track this [issue](https://github.com/hashicorp/terraform-cdk/issues/651) when you're interested in this feature.

### Migration from `<= 0.2`

Up until CDK for Terraform version `0.2` only a single stack was supported. For local state handling, a `terraform.tfstate` in the project root folder was used. With version `>= 0.3` the local state file reflects the stack name it belongs to in its file name. When a `terraform.tfstate` file is still present in the project root folder, it has to be renamed to match the schema `terraform.<stack-name>.tfstate` manually.
35 changes: 35 additions & 0 deletions examples/typescript/aws-multiple-stacks/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, Instance } from "./.gen/providers/aws";

interface MyStackConfig {
environment: string,
region?: string;
}

class MyStack extends TerraformStack {
constructor(scope: Construct, id: string, config: MyStackConfig) {
super(scope, id);

const { region = 'us-east-1' } = config

new AwsProvider(this, "aws", {
region,
});

new Instance(this, "Hello", {
ami: "ami-2757f631",
instanceType: "t2.micro",
tags: {
environment: config.environment
}
});
}
}

const app = new App();
new MyStack(app, "multiple-stacks-dev", { environment: 'dev' });
new MyStack(app, "multiple-stacks-staging", { environment: 'staging' });
new MyStack(app, "multiple-stacks-production-us", { environment: 'production', region: 'us-east-1' });
new MyStack(app, "multiple-stacks-production-eu", { environment: 'production', region: 'eu-central-1' });
app.synth();
51 changes: 0 additions & 51 deletions examples/typescript/aws/main.ts

This file was deleted.

6 changes: 4 additions & 2 deletions packages/cdktf-cli/bin/cmds/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { displayVersionMessage } from './version-check'
const config = readConfigSync();

class Command implements yargs.CommandModule {
public readonly command = 'deploy [OPTIONS]';
public readonly command = 'deploy [stack] [OPTIONS]';
public readonly describe = 'Deploy the given stack';

public readonly builder = (args: yargs.Argv) => args
.positional('stack', { desc: 'Deploy stack which matches the given id only. Required when more than one stack is present in the app', type: 'string' })
skorfmann marked this conversation as resolved.
Show resolved Hide resolved
.option('app', { default: config.app, required: true, desc: 'Command to use in order to execute cdktf app', alias: 'a' })
.option('output', { default: config.output, required: true, desc: 'Output directory', alias: 'o' })
.option('auto-approve', { type: 'boolean', default: false, required: false, desc: 'Auto approve' })
Expand All @@ -22,8 +23,9 @@ class Command implements yargs.CommandModule {
const command = argv.app;
const outdir = argv.output;
const autoApprove = argv.autoApprove;
const stack = argv.stack;

await renderInk(React.createElement(Deploy, { targetDir: outdir, synthCommand: command, autoApprove }))
await renderInk(React.createElement(Deploy, { targetDir: outdir, targetStack: stack, synthCommand: command, autoApprove }))
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/cdktf-cli/bin/cmds/destroy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { displayVersionMessage } from './version-check'
const config = readConfigSync();

class Command implements yargs.CommandModule {
public readonly command = 'destroy [OPTIONS]';
public readonly command = 'destroy [stack] [OPTIONS]';
public readonly describe = 'Destroy the given stack';

public readonly builder = (args: yargs.Argv) => args
.positional('stack', { desc: 'Destroy stack which matches the given id only. Required when more than one stack is present in the app', type: 'string' })
skorfmann marked this conversation as resolved.
Show resolved Hide resolved
.option('app', { default: config.app, required: true, desc: 'Command to use in order to execute cdktf app', alias: 'a' })
.option('output', { default: config.output, required: true, desc: 'Output directory', alias: 'o' })
.option('auto-approve', { type: 'boolean', default: false, required: false, desc: 'Auto approve' })
Expand All @@ -22,8 +23,9 @@ class Command implements yargs.CommandModule {
const command = argv.app;
const outdir = argv.output;
const autoApprove = argv.autoApprove;
const stack = argv.stack;

await renderInk(React.createElement(Destroy, { targetDir: outdir, synthCommand: command, autoApprove }))
await renderInk(React.createElement(Destroy, { targetDir: outdir, targetStack: stack, synthCommand: command, autoApprove }))
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/cdktf-cli/bin/cmds/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { displayVersionMessage } from './version-check'
const config = readConfigSync();

class Command implements yargs.CommandModule {
public readonly command = 'diff [OPTIONS]';
public readonly command = 'diff [stack] [OPTIONS]';
public readonly describe = 'Perform a diff (terraform plan) for the given stack';

public readonly builder = (args: yargs.Argv) => args
.positional('stack', { desc: 'Diff stack which matches the given id only. Required when more than one stack is present in the app', type: 'string' })
skorfmann marked this conversation as resolved.
Show resolved Hide resolved
.option('app', { default: config.app, required: true, desc: 'Command to use in order to execute cdktf app', alias: 'a' })
.option('output', { default: config.output, required: true, desc: 'Output directory', alias: 'o' })
.showHelpOnFail(true)
Expand All @@ -20,8 +21,9 @@ class Command implements yargs.CommandModule {
await displayVersionMessage()
const command = argv.app;
const outdir = argv.output;
const stack = argv.stack;

await renderInk(React.createElement(Diff, { targetDir: outdir, synthCommand: command }))
await renderInk(React.createElement(Diff, { targetDir: outdir, targetStack: stack, synthCommand: command }))
}
}

Expand Down
Loading