tfctl 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.travis.yml +17 -0
- data/CHANGELOG.adoc +9 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/Makefile +26 -0
- data/README.adoc +145 -0
- data/bin/tfctl +198 -0
- data/docs/configuration.adoc +53 -0
- data/docs/control_tower.adoc +191 -0
- data/docs/creating_a_profile.adoc +169 -0
- data/docs/iam_permissions.adoc +18 -0
- data/docs/project_layout.adoc +43 -0
- data/examples/bootstrap/terraform-exec-role.template +24 -0
- data/examples/bootstrap/terraform-state.template +81 -0
- data/examples/bootstrap/tfctl-org-access.template +42 -0
- data/examples/control_tower/conf/example.yaml +71 -0
- data/examples/control_tower/modules/s3-bucket/main.tf +4 -0
- data/examples/control_tower/modules/s3-bucket/variables.tf +4 -0
- data/examples/control_tower/profiles/example-profile/data.tf +1 -0
- data/examples/control_tower/profiles/example-profile/main.tf +4 -0
- data/examples/control_tower/profiles/example-profile/variables.tf +12 -0
- data/lib/hash.rb +27 -0
- data/lib/tfctl/aws_org.rb +121 -0
- data/lib/tfctl/config.rb +177 -0
- data/lib/tfctl/error.rb +6 -0
- data/lib/tfctl/executor.rb +104 -0
- data/lib/tfctl/generator.rb +96 -0
- data/lib/tfctl/logger.rb +38 -0
- data/lib/tfctl/version.rb +5 -0
- data/lib/tfctl.rb +9 -0
- data/tfctl.gemspec +29 -0
- metadata +119 -0
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
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
data/Gemfile
ADDED
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.
|