tfctl 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e1075714676ca848752725f2d98c38240e5a9ee2bc901b582ffba6edd46fc0f
4
+ data.tar.gz: b48bc64d5a9aaae352538ed88b648b20e2b2c68c59bd0c1b3b5c8757e3c421b9
5
+ SHA512:
6
+ metadata.gz: b87ecf1c66f1528bf98134d631aef3c8264326bbd0e399eb769860ad853d24ebc59339ba6df16be41b6d728c4fd63df035a210a1ca9d09fd17960de2b4d66fa1
7
+ data.tar.gz: 657f44ce06c1ac667525a80009f73e88fbf9754f32f5703faad8fbfe1b6355fd6796690c8b0583af549fef8d75de318d1dc98f56aa80644b78ff2a068e30bfa7
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ .DS_Store
2
+ *.swp
3
+ .tfctl
4
+ pkg/
5
+ *.gem
6
+ vendor/
7
+ .bundle
8
+ bin/bundle
9
+ bin/htmldiff
10
+ bin/ldiff
11
+ bin/rspec
12
+ spec/reports
13
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ rvm:
2
+ - 2.3
3
+ - 2.6
4
+ sudo: false
5
+ script: make test
6
+ deploy:
7
+ provider: rubygems
8
+ api_key:
9
+ secure: FKAONS7x6koN7oiEULr4ViwjlDBzbE0bCgqhXRP4DfTtlRyEymeqgrfSIqkVH7unjd0muIGrMBnFuaQVSx7648RS1Ss0QAJo32SVnzoYl1P03cijqNmbbaf1jRdA3IGmh0gV5vsXrmlHiP8gfAuC9PqQ550OzxzWUvEI8vXgSTibmKd/PoQinv5g/dq0gBFjlhSMt/k3Z9WlMmkEsAro/r/Ie2M7mItHPT65f0ga5q5SeujPQQ3Sd/l3mznh37bmnw5RZpFDYdA7jL2p0Y58XJPBU8soa3ZC5GeHyxCYVoGh6EDGAFb83ERRT6rQ7ywkOufTv1o497P7a/prSbvT6fzc+DcugXPEaglT+dUXMe36OoF907Xva4vq3xIHV2N/yrxbDM85hmMk22wEU+9wpDDzFNQnfsXNbaHG9F7gLgy0eoTRrSuJf6cPDlE8pwvn7b8cjieeqWc//ZNhSYnHYZGER4LFINWVxs68Eofmmqp2IESTcUpJ8oB4bV+bzzyobJMRobOXu2hvgCrTdr6r/PnckpAfZE/l4nVQa14f1FU//8bU3DwvNun6TX1Ujp+XNiRDUlvP2KnkBU4s5rsIkL3lCHW7r6GipSk6SOvGMTz5eySMsoWvZQBdAzk/OxcIteeWH9pdo1Hbu5x2/bwyuTRCQ9E79CKWDKlIQwCgUY0=
10
+ gem: tfctl
11
+ on:
12
+ tags: true
13
+ repo: scalefactory/tfctl
14
+ notifications:
15
+ slack:
16
+ rooms:
17
+ secure: HrdsUaTCvwtq5rLyAf7Uwo76p9JhxnoFvoZU/AtxTCL6lwMm5pqpfDAdbBH8C2BYMWRyiS8IfSrvuLbGUr317qJgVCmnhD7JIgdA0Aih4GkR0yK+EVao7qdOClO2CGJFJGqHveBBpXy94FEwgFwQSvnuSZdY8gNdXafvpgrPvcbWHIF9Bq7StfYelWZX6+FL7xczqVhdetXcHJEj6Q/thkbvyn0hJAVegjDq4Pw3MyR66Mv5HTj+jYbGrJOR1poNW8vv4iQjK0TIMvCCeTv4Ddrjitn1kOK1BPYqoHEkaRwXdS7+hevv+UfV074awTp3UOIS5VP3ve/KX21xcl9DWMG7Gde9tuyap9WKhN3XE5686qrfn4M/L7PmVnzeYimUKLQfdqjDXa7UI3orGIOq8nKCsCUFvFUQZ/DxeEssJzBNNRbeGywwVtXk7/8L2HHiQJ3t/29vMLEq6EwskYK3Uao2aIVWpLSkPwOuwVxrNWXrwojrrKo+oo8QvW6xkov5UW3VkapyzRyDN2oIpNkFeLYfuyTO4MBsnLzCBqJYEeku2l0Hp9WBq2Q1a+FRgqQlcRbLPsYwK5AdkRKcWuXP8FMZoqXX6F9B73q5Dbub2et4YN4zhvroLZN6MmlneM6faJh9aS4yV2ZlU1XbOtbqP0nkHrJ+o8DeM/wgTB1GB90=
data/CHANGELOG.adoc ADDED
@@ -0,0 +1,9 @@
1
+ = Changelog
2
+
3
+ == 0.0.2
4
+
5
+ * BUGFIX: Fixed an exception when `exclude_accounts` is not set.
6
+
7
+ == 0.0.1
8
+
9
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019 Essentia Analytics Ltd
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,26 @@
1
+ .PHONY: clean install test
2
+
3
+ vendor:
4
+ $(info => Installing Ruby dependencies)
5
+ @bundle install --path vendor --with developement --binstubs=vendor/bin
6
+
7
+ test: vendor
8
+ $(info => Running spec tests)
9
+ @vendor/bin/rspec
10
+
11
+ pkg:
12
+ $(info => Building gem package in pkg/)
13
+ @mkdir pkg/
14
+ @gem build tfctl.gemspec
15
+ @mv *.gem pkg/
16
+
17
+ install: pkg
18
+ gem install pkg/*.gem
19
+
20
+ clean:
21
+ $(info => Cleaning)
22
+ @rm -rf pkg/
23
+ @rm -rf vendor/
24
+ @rm -rf .bundle
25
+ @rm -f Gemfile.lock
26
+ @rm -rf spec/reports/
data/README.adoc ADDED
@@ -0,0 +1,145 @@
1
+ :toc:
2
+
3
+ = tfctl
4
+
5
+ == Overview
6
+
7
+ Tfctl is a small Terraform wrapper for working with multi-account AWS
8
+ infrastructures where new accounts may be created dynamically and on-demand.
9
+
10
+ Discovers accounts by reading the AWS Organizations API and can assign
11
+ Terraform resources to accounts based on the organization hierarchy. Resources
12
+ can be assigned globally, based on organization unit or individual accounts.
13
+ It supports nested OU hierarchies.
14
+
15
+ Tfctl was originally developed to integrate Terraform with
16
+ https://aws.amazon.com/solutions/aws-landing-zone/[AWS Landing Zone] and
17
+ https://aws.amazon.com/controltower/[Control Tower] but should work with most
18
+ other ways of managing accounts in AWS Organizations.
19
+
20
+ == Features
21
+
22
+ * Discovers AWS accounts automatically.
23
+ * Automatically generates Terraform account configuration.
24
+ * Parallel execution across multiple accounts.
25
+ * Hierarchical configuration based on AWS Organization units structure.
26
+ * Supports per account configuration overrides for handling exceptions.
27
+ * Supports nested organization units.
28
+ * Terraform state tracking in S3 and locking in DynamoDB.
29
+ * Account targeting by OU path regular expressions.
30
+ * Automatic role assumption in target accounts.
31
+ * Works with CI/CD pipelines.
32
+
33
+ == Requirements
34
+
35
+ * Terraform >= 0.12
36
+ * Ruby >= 2.3
37
+ * Accounts managed in AWS Organizations (by Landing Zone, Control Tower, some
38
+ other means)
39
+
40
+ == Installation
41
+
42
+ Clone this repository and run:
43
+
44
+ ----
45
+ make install
46
+ ----
47
+
48
+ This will build a ruby gem and install it.
49
+
50
+ When using bundler, add this to your `Gemfile`:
51
+
52
+ ----
53
+ gem 'tfctl', git: 'https://github.com/scalefactory/tfctl'
54
+ ----
55
+
56
+ == Docs
57
+
58
+ * https://github.com/scalefactory/tfctl/tree/master/docs/control_tower.adoc[Control Tower quick start guide]
59
+ * https://github.com/scalefactory/tfctl/tree/master/docs/project_layout.adoc[Project layout]
60
+ * https://github.com/scalefactory/tfctl/tree/master/docs/configuration.adoc[Configuration]
61
+ * https://github.com/scalefactory/tfctl/tree/master/docs/iam_permissions.adoc[IAM permissions]
62
+ * https://github.com/scalefactory/tfctl/tree/master/docs/creating_a_profile.adoc[Creating a profile]
63
+
64
+ == Running tfctl
65
+
66
+ tfctl should be run from the root of the project directory. It will generate
67
+ Terraform configuration in `.tfctl/`.
68
+
69
+ Anatomy of a tfctl command:
70
+
71
+ ----
72
+ tfctl -c CONFIG_FILE TARGET_OPTIONS -- TERRAFORM_COMMAND
73
+ ----
74
+
75
+ * `-c` specifies which tfctl config file to use (usually in `conf/`)
76
+ * `TARGET_OPTIONS` specifies which accounts to target. This could be an individual
77
+ account, a group of accounts in an organizational unit or all accounts.
78
+ * `TERRAFORM_COMMAND` will be passed to `terraform` along with any
79
+ options. See https://www.terraform.io/docs/commands/index.html[Terraform
80
+ commands] for details.
81
+
82
+ NOTE: You must have your AWS credentials configured before running tfctl or run
83
+ it using an AWS credentials helper such as
84
+ https://github.com/99designs/aws-vault[aws-vault].
85
+
86
+ === Example commands
87
+
88
+ Show help:
89
+
90
+ ----
91
+ tfctl -h
92
+ ----
93
+
94
+ Show merged configuration:
95
+
96
+ ----
97
+ tfctl -c conf/example.yaml -s
98
+ ----
99
+
100
+ Run Terraform init accross all accounts:
101
+
102
+ ----
103
+ tfctl -c conf/example.yaml --all -- init
104
+ ----
105
+
106
+ Run plan in `test` OU accounts:
107
+
108
+ ----
109
+ tfctl -c conf/example.yaml -o test -- plan
110
+ ----
111
+
112
+ Run plan in `live` accounts assuming that `live` is a child OU in multiple
113
+ organization units:
114
+
115
+ ----
116
+ tfctl -c conf/example.yaml -o '.*/live' -- plan
117
+ ----
118
+
119
+ Run plan in an individual account:
120
+
121
+ ----
122
+ tfctl -c conf/example.yaml -a example-account - plan
123
+ ----
124
+
125
+ Run apply in all accounts:
126
+
127
+ ----
128
+ tfctl -c conf/example.yaml --all -- apply
129
+ ----
130
+
131
+ Run destroy in `test` OU accounts:
132
+
133
+ ----
134
+ tfctl -c conf/example.yaml -o test -- destroy -auto-approve
135
+ ----
136
+
137
+ Don't buffer the output:
138
+
139
+ ----
140
+ tfctl -c conf/example.yaml -a example-account -u -- plan
141
+ ----
142
+
143
+ This will show output in real time. Usually output is buffered and displayed
144
+ after Terraform command finishes to make it more readable when running across
145
+ multiple accounts in parallel.
data/bin/tfctl ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ if File.directory?(File.dirname(__FILE__) + '/../vendor')
5
+ require 'bundler/setup'
6
+ end
7
+ require 'optparse'
8
+ require 'fileutils'
9
+ require 'parallel'
10
+ require_relative '../lib/tfctl'
11
+
12
+ PROJECT_ROOT = Dir.pwd
13
+
14
+ #
15
+ # Process CLI arguments
16
+ #
17
+
18
+ options={
19
+ :account => nil,
20
+ :ou => nil,
21
+ :all => nil,
22
+ :show_config => false,
23
+ :config_file => nil,
24
+ :unbuffered => false,
25
+ :debug => false,
26
+ :use_cache => false
27
+ }
28
+
29
+ optparse = OptionParser.new do |opts|
30
+ opts.on('-a', '--account=name', "Target a specific AWS account") do |o|
31
+ options[:account] = o
32
+ end
33
+ opts.on('-o', '--ou=organization_unit', "Target accounts in an Organization Unit (uses regex matching)") do |o|
34
+ options[:ou] = o
35
+ end
36
+ opts.on('--all', "Target all accounts") do
37
+ options[:all] = true
38
+ end
39
+ opts.on('-c', '--config-file=config', "Path to config file") do |o|
40
+ options[:config_file] = o
41
+ end
42
+ opts.on('-s', '--show-config', "Display configuration") do
43
+ options[:show_config] = true
44
+ end
45
+ opts.on('-x', '--use-cache', "Use cached AWS organization data") do
46
+ options[:use_cache] = true
47
+ end
48
+ opts.on('-u', '--unbuffered', "Disable buffering of Terraform output") do
49
+ options[:unbuffered] = true
50
+ end
51
+ opts.on('-d', '--debug', "Turn on debug messages") do
52
+ options[:debug] = true
53
+ end
54
+ opts.on('-v', '--version', "Show version") do
55
+ puts Tfctl::VERSION
56
+ exit
57
+ end
58
+ end
59
+
60
+
61
+ begin
62
+ optparse.parse!
63
+
64
+ # Validate CLI arguments
65
+
66
+ if options[:config_file].nil?
67
+ raise OptionParser::MissingArgument, '--config-file'
68
+ end
69
+
70
+ unless File.exists? options[:config_file]
71
+ raise OptionParser::InvalidOption,
72
+ "Config file not found in: #{options[:config_file]}"
73
+ end
74
+
75
+ unless File.exists? options[:config_file]
76
+ raise OptionParser::InvalidOption, "Config file #{options[:config_file]} not found."
77
+ end
78
+
79
+ # Validate targets
80
+ targetting_opts = [:account, :ou, :all]
81
+ targets_set = []
82
+ options.each do |k,v|
83
+ if targetting_opts.include?(k)
84
+ targets_set << k.to_s unless v == nil
85
+ end
86
+ end
87
+ if targets_set.length > 1
88
+ raise OptionParser::InvalidOption,
89
+ "Too many target options set: #{targets_set.join(', ')}. Only one can be specified."
90
+ end
91
+ if targets_set.empty? and options[:show_config] == false
92
+ raise OptionParser::InvalidOption, "Please specify target"
93
+ end
94
+
95
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
96
+ $stderr.puts $!.to_s
97
+ $stderr.puts optparse
98
+ exit 2
99
+ end
100
+
101
+
102
+
103
+ # Generates configuration and runs Terraform commands for a target account.
104
+ def run_account(config, account, options, tf_argv, log)
105
+
106
+ # Skip excluded accounts
107
+ if account[:excluded] == true
108
+ log.info "#{account[:name]}: excluded, skipping"
109
+ return
110
+ end
111
+
112
+ # Generate Terraform run directory with configured providers, backend and
113
+ # profiles for the target account. This is where Terraform will be
114
+ # executed from.
115
+ log.info "#{account[:name]}: Generating Terraform run directory"
116
+ Tfctl::Generator.make(
117
+ config: config,
118
+ terraform_io_org: config[:terraform_io_org],
119
+ account_id: account[:id],
120
+ account_name: account[:name],
121
+ profiles: account[:profiles],
122
+ execution_role: account[:tf_execution_role]
123
+ )
124
+
125
+ log.info "#{account[:name]}: Executing Terraform #{tf_argv[0]}"
126
+ Tfctl::Executor.run(
127
+ account_name: account[:name],
128
+ config_name: config[:config_name],
129
+ unbuffered: options[:unbuffered],
130
+ log: log,
131
+ argv: tf_argv
132
+ )
133
+ end
134
+
135
+
136
+ #
137
+ # Main
138
+ #
139
+
140
+ begin
141
+
142
+ # Set up logging
143
+ options[:debug] ? log_level = Logger::DEBUG : log_level = Logger::INFO
144
+ log = Tfctl::Logger.new(log_level)
145
+
146
+ log.info 'tfctl running'
147
+
148
+ config_name = File.basename(options[:config_file]).chomp('.yaml')
149
+ log.info "Using config: #{config_name}"
150
+
151
+ log.info 'Working out AWS account topology'
152
+
153
+ yaml_config = YAML.load(File.read(options[:config_file]))
154
+ yaml_config.symbolize_names!
155
+
156
+ org_units = yaml_config[:organization_units].keys
157
+ aws_org_accounts = Tfctl::AwsOrg.new(yaml_config[:tfctl_role_arn]).accounts(org_units)
158
+
159
+ log.info 'Merging configuration'
160
+
161
+ config = Tfctl::Config.new(
162
+ config_name: config_name,
163
+ yaml_config: yaml_config,
164
+ aws_org_config: aws_org_accounts,
165
+ use_cache: options[:use_cache])
166
+
167
+
168
+ if options[:show_config]
169
+ puts config.to_yaml
170
+ exit 0
171
+ end
172
+
173
+ # Run targets
174
+
175
+ if options[:account]
176
+ account = config.find_accounts(:name, options[:account]).first
177
+ run_account(config, account, options, ARGV, log)
178
+
179
+ elsif options[:ou]
180
+ accounts = config.find_accounts_regex(:ou_path, options[:ou])
181
+ Parallel.each(accounts, in_processes: 8) do |ac|
182
+ run_account(config, ac, options, ARGV, log)
183
+ end
184
+
185
+ elsif options[:all]
186
+ accounts = config[:accounts]
187
+ Parallel.each(accounts, in_processes: 8) do |ac|
188
+ run_account(config, ac, options, ARGV, log)
189
+ end
190
+ end
191
+
192
+
193
+ log.info 'Done'
194
+
195
+ rescue Tfctl::Error => e
196
+ log.error(e)
197
+ exit 1
198
+ end
@@ -0,0 +1,53 @@
1
+ == Configuration
2
+
3
+ Tfctl retrieves initial account configuration from AWS Organizations and merges
4
+ it with organization config specified in the yaml file.
5
+
6
+ The configuration is merged in the following order:
7
+
8
+ * AWS Organizations data is fetched and stored in an `accounts` array.
9
+ * `organization_root` settings are merged with all accounts.
10
+ * `organization_units` settings are merged with accounts matching the OU.
11
+ * `account_overrides` are merged with individual accounts matching the account name.
12
+
13
+ Parameters further down the hierarchy take precedence. For example:
14
+
15
+ [source, yaml]
16
+ ----
17
+ organization_root:
18
+ example_param: 'will be overriden further down'
19
+
20
+ organization_units:
21
+ team:
22
+ example_param: 'will win in team ou'
23
+ team/live:
24
+ example_param: 'will win in team/live ou'
25
+ ----
26
+
27
+ One exception to this rule is the `profiles` parameter. Profiles are additive:
28
+
29
+ [source, yaml]
30
+ ----
31
+ organization_root:
32
+ profiles:
33
+ - profile-one
34
+ - profile-two
35
+
36
+ organization_units:
37
+ team:
38
+ profiles:
39
+ - profile-three
40
+ ----
41
+
42
+ This will result in all three profiles deployed to accounts in `team` OU.
43
+
44
+ TIP: You can display the fully merged configuration by running `tfctl -c
45
+ conf/CONFIG_FILE.yaml -s`. It's safe to run as it doesn't make any changes to
46
+ AWS resources. It's a good way to test your configuration.
47
+
48
+ === Handling secrets
49
+
50
+ No secrets should be committed into Terraform or tfctl configuration. Use AWS
51
+ Secrets Manager instead and retrieve in Terraform profiles using
52
+ https://www.terraform.io/docs/providers/aws/d/secretsmanager_secret.html[secrets
53
+ manager data source]
@@ -0,0 +1,191 @@
1
+ :toc:
2
+
3
+ == Control Tower integration guide
4
+
5
+ This guide will help you integrate Terraform with AWS Control Tower using the
6
+ tfctl wrapper. This involves setting up resources for remote state tracking,
7
+ necessary IAM roles and a tfctl project.
8
+
9
+ === Overview
10
+
11
+ For state tracking we're going to create a dedicated `shared-services` account
12
+ under a `mgmt` organization unit. We'll use S3 for state storage and DynamoDB
13
+ for locking. `TerraformState` IAM role will be created for cross account
14
+ access to state resources from the primary account.
15
+
16
+ In the primary account we'll create a `TfctlOrgAccess` role. It gives tfctl
17
+ read only access to AWS Organizations which is used to discover accounts and
18
+ the organization unit structure.
19
+
20
+ We'll use CloudFormation stacks and stack-sets to bootstrap these resources.
21
+
22
+ For executing Terraform in spoke accounts we'll use the
23
+ `AWSControlTowerExecution` role which is automatically created by Control Tower
24
+ account factory and can be assumed from the primary account.
25
+
26
+ We're going to create a `live` and `test` organization units in Control Tower
27
+ and provision a couple of accounts for testing.
28
+
29
+ === Prerequisites
30
+
31
+ Before starting you'll need:
32
+
33
+ * Control Tower set up in your primary account.
34
+ * A user with `AdministratorAccess` privileges in primary account.
35
+ * AWS CLI tools installed on your machine.
36
+ * Terraform 0.12 or higher.
37
+
38
+ === Configure Control Tower
39
+
40
+ Create the following organization units in Control Tower:
41
+
42
+ * `mgmt`
43
+ * `live`
44
+ * `test`
45
+
46
+ Then provision accounts:
47
+
48
+ * In `mgmt` OU create an account called `mgmt-shared-services`
49
+ * In `live` create `live-example1`
50
+ * In `test` create `test-example1`
51
+
52
+ NOTE: Control Tower accounts need to be provisioned one at a time. It takes
53
+ approximately 20 mins to provision one.
54
+
55
+ === Install tfctl
56
+
57
+ ----
58
+ git clone git@github.com:scalefactory/tfctl.git
59
+ cd tfctl/ && sudo make install
60
+ ----
61
+
62
+ === Set up AWS resources
63
+
64
+ It's assumed you have configured AWS CLI access to the primary account.
65
+
66
+ We'll use CloudFormation templates in `examples/bootstrap/`.
67
+
68
+ First export configuration using environment variables making sure to change to
69
+ values to suit your set up:
70
+
71
+ ----
72
+ export PRIMARY_ACCOUNT_ID=11111111
73
+ export SHARED_SERVICES_ACCOUNT_ID=22222222
74
+ export STATE_BUCKET_NAME='example-terraform-state'
75
+ ----
76
+
77
+ Create the remote state resources stack set:
78
+
79
+ ----
80
+ cd examples/bootstrap/
81
+
82
+ aws cloudformation create-stack-set \
83
+ --stack-set-name TerraformState \
84
+ --template-body file://terraform-state.template \
85
+ --description "Resources for managing Terraform state" \
86
+ --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM \
87
+ --execution-role-name AWSControlTowerExecution \
88
+ --administration-role-arn arn:aws:iam::${PRIMARY_ACCOUNT_ID}:role/service-role/AWSControlTowerStackSetRole \
89
+ --parameters ParameterKey=PrimaryAccountId,ParameterValue=${PRIMARY_ACCOUNT_ID} \
90
+ ParameterKey=TerraformStateBucket,ParameterValue=${STATE_BUCKET_NAME}
91
+ ----
92
+
93
+ Create a stack set instance in you shared services account:
94
+
95
+ ----
96
+ aws cloudformation create-stack-instances \
97
+ --stack-set-name TerraformState \
98
+ --accounts ${SHARED_SERVICES_ACCOUNT_ID} \
99
+ --regions eu-west-1
100
+ ----
101
+
102
+ Check status:
103
+
104
+ ----
105
+ aws cloudformation describe-stack-instance \
106
+ --stack-set-name TerraformState \
107
+ --stack-instance-account ${SHARED_SERVICES_ACCOUNT_ID} \
108
+ --stack-instance-region eu-west-1
109
+ ----
110
+
111
+ NOTE: Initial status will be `OUTDATED`, it should change to `CURRENT` once deployed.
112
+
113
+ Deploy `TfctlOrgAccess` IAM role stack:
114
+
115
+ ----
116
+ aws cloudformation create-stack \
117
+ --stack-name TfctlOrgAccess \
118
+ --template-body file://tfctl-org-access.template \
119
+ --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM \
120
+ --parameters ParameterKey=PrimaryAccountId,ParameterValue=${PRIMARY_ACCOUNT_ID}
121
+ ----
122
+
123
+ Check status:
124
+
125
+ ----
126
+ aws cloudformation describe-stacks --stack-name TfctlOrgAccess
127
+ ----
128
+
129
+ NOTE: Successful status should read: `CREATE_COMPLETE`.
130
+
131
+ === Configure tfctl
132
+
133
+ Copy the example project directory `examples/control_tower` somewhere convenient
134
+ and edit `conf/example.yaml`.
135
+
136
+ You need to modify the following parameters:
137
+
138
+ * `tf_state_bucket` - set to `$STATE_BUCKET_NAME`
139
+ * `tf_state_role_arn` - set shared services account ID
140
+ * `tfctl_role_arn` - set primary account ID
141
+ * `primary_account` - set the primary account name. You can find it in AWS Organizations.
142
+
143
+ TIP: You should keep your project directory under version control.
144
+
145
+ === Deploy example tfctl profile
146
+
147
+ The example profile will create an S3 bucket in accounts under `test`, `live`
148
+ and `mgmt` OUs.
149
+
150
+ NOTE: Run tfctl commands from the root of you project directory.
151
+
152
+ First dump the configuration to verify everything works:
153
+
154
+ ----
155
+ tfctl -c conf/example.yaml -s
156
+ ----
157
+
158
+ This will not make any changes but will print out a yaml containing the final,
159
+ merged configuration data. It should contain a list of discovered accounts and
160
+ their configuration.
161
+
162
+ Initialise terraform for all discovered accounts:
163
+
164
+ ----
165
+ tfctl -c conf/example.yaml --all -- init
166
+ ----
167
+
168
+ Tfctl will run Terraform against all accounts in parallel.
169
+
170
+ Run plan:
171
+
172
+ ----
173
+ tfctl -c conf/example.yaml --all -- plan
174
+ ----
175
+
176
+ and apply:
177
+
178
+ ----
179
+ tfctl -c conf/example.yaml --all -- apply
180
+ ----
181
+
182
+ To destroy created resources run:
183
+
184
+ ----
185
+ tfctl -c conf/example.yaml --all -- destroy -auto-approve
186
+ ----
187
+
188
+ That's it! You can now execute terraform across your Control Tower estate.
189
+
190
+ TIP: Your project directory should be under version control excluding the
191
+ `.tfctl` directory which is automatically generated.