tfctl 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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.