tfctl 1.0.0
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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.rubocop.yml +79 -0
- data/.travis.yml +18 -0
- data/CHANGELOG.adoc +29 -0
- data/Gemfile +5 -0
- data/Guardfile +7 -0
- data/LICENSE +19 -0
- data/Makefile +36 -0
- data/README.adoc +176 -0
- data/bin/tfctl +223 -0
- data/docs/configuration.adoc +89 -0
- data/docs/control_tower.adoc +211 -0
- data/docs/creating_a_profile.adoc +191 -0
- data/docs/iam_permissions.adoc +38 -0
- data/docs/project_layout.adoc +65 -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 +80 -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 +33 -0
- data/lib/tfctl.rb +10 -0
- data/lib/tfctl/aws_org.rb +112 -0
- data/lib/tfctl/config.rb +182 -0
- data/lib/tfctl/error.rb +15 -0
- data/lib/tfctl/executor.rb +103 -0
- data/lib/tfctl/generator.rb +88 -0
- data/lib/tfctl/logger.rb +52 -0
- data/lib/tfctl/schema.rb +80 -0
- data/lib/tfctl/version.rb +5 -0
- data/tfctl.gemspec +36 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6d47682cf9949db840c18d07b6f06907a9d36e2a75d0a5255b4c57f3603c0dbf
|
4
|
+
data.tar.gz: d5a81e877943fff53e903a104249e1147a827c593430bc91c0b78d5cc467fc4b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 03b7d69b7a7bbf296b0b1ab3aa8794d8b91e51559a1e2ee1d450396615aeba495b63993f84866c6fe5997d34c760da143cb66ca0cabc2a1a5a1266b701676bac
|
7
|
+
data.tar.gz: f9ebdae72fd58473c3a8cc5015fd2d2e32092488218b52efa79e2d625802ccdf4447298e45497d4ec0e03583b0edbd3a31b51e92f76a5981a5f3819f9cee1e4a
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
---
|
2
|
+
AllCops:
|
3
|
+
TargetRubyVersion: 2.3
|
4
|
+
DisplayCopNames: true
|
5
|
+
|
6
|
+
Layout/IndentationWidth:
|
7
|
+
Width: 4
|
8
|
+
|
9
|
+
Layout/IndentHeredoc:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Layout/EmptyLines:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Layout/EmptyLinesAroundMethodBody:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Layout/AlignHash:
|
19
|
+
EnforcedHashRocketStyle:
|
20
|
+
- table
|
21
|
+
EnforcedColonStyle:
|
22
|
+
- table
|
23
|
+
|
24
|
+
Layout/SpaceAroundOperators:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Layout/ExtraSpacing:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Layout/EmptyLinesAroundBlockBody:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Layout/EmptyLinesAroundClassBody:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Metrics/CyclomaticComplexity:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
Metrics/PerceivedComplexity:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Metrics/BlockLength:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Metrics/MethodLength:
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
Metrics/LineLength:
|
49
|
+
Max: 140
|
50
|
+
|
51
|
+
Metrics/AbcSize:
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
Metrics/ParameterLists:
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
Metrics/ClassLength:
|
58
|
+
Enabled: false
|
59
|
+
|
60
|
+
Style/IfUnlessModifier:
|
61
|
+
Enabled: false
|
62
|
+
|
63
|
+
Style/AndOr:
|
64
|
+
Enabled: false
|
65
|
+
|
66
|
+
Style/Documentation:
|
67
|
+
Enabled: false
|
68
|
+
|
69
|
+
Style/TrailingCommaInArguments:
|
70
|
+
EnforcedStyleForMultiline: comma
|
71
|
+
|
72
|
+
Style/TrailingCommaInArrayLiteral:
|
73
|
+
EnforcedStyleForMultiline: comma
|
74
|
+
|
75
|
+
Style/TrailingCommaInHashLiteral:
|
76
|
+
EnforcedStyleForMultiline: comma
|
77
|
+
|
78
|
+
Style/RedundantReturn:
|
79
|
+
Enabled: false
|
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
rvm:
|
2
|
+
- 2.3
|
3
|
+
- 2.6
|
4
|
+
os: linux
|
5
|
+
language: ruby
|
6
|
+
script: make test
|
7
|
+
jobs:
|
8
|
+
include:
|
9
|
+
- stage: Gem release
|
10
|
+
rvm: 2.6
|
11
|
+
deploy:
|
12
|
+
provider: rubygems
|
13
|
+
api_key:
|
14
|
+
secure: FKAONS7x6koN7oiEULr4ViwjlDBzbE0bCgqhXRP4DfTtlRyEymeqgrfSIqkVH7unjd0muIGrMBnFuaQVSx7648RS1Ss0QAJo32SVnzoYl1P03cijqNmbbaf1jRdA3IGmh0gV5vsXrmlHiP8gfAuC9PqQ550OzxzWUvEI8vXgSTibmKd/PoQinv5g/dq0gBFjlhSMt/k3Z9WlMmkEsAro/r/Ie2M7mItHPT65f0ga5q5SeujPQQ3Sd/l3mznh37bmnw5RZpFDYdA7jL2p0Y58XJPBU8soa3ZC5GeHyxCYVoGh6EDGAFb83ERRT6rQ7ywkOufTv1o497P7a/prSbvT6fzc+DcugXPEaglT+dUXMe36OoF907Xva4vq3xIHV2N/yrxbDM85hmMk22wEU+9wpDDzFNQnfsXNbaHG9F7gLgy0eoTRrSuJf6cPDlE8pwvn7b8cjieeqWc//ZNhSYnHYZGER4LFINWVxs68Eofmmqp2IESTcUpJ8oB4bV+bzzyobJMRobOXu2hvgCrTdr6r/PnckpAfZE/l4nVQa14f1FU//8bU3DwvNun6TX1Ujp+XNiRDUlvP2KnkBU4s5rsIkL3lCHW7r6GipSk6SOvGMTz5eySMsoWvZQBdAzk/OxcIteeWH9pdo1Hbu5x2/bwyuTRCQ9E79CKWDKlIQwCgUY0=
|
15
|
+
gem: tfctl
|
16
|
+
on:
|
17
|
+
tags: true
|
18
|
+
repo: scalefactory/tfctl
|
data/CHANGELOG.adoc
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
= Changelog
|
2
|
+
|
3
|
+
== 1.0.0
|
4
|
+
|
5
|
+
* feat(config): JSON schema config validation
|
6
|
+
* feat(config): added 'data' parameter
|
7
|
+
|
8
|
+
BREAKING CHANGE: This release moves user defined data under a separate `data`
|
9
|
+
parameter so it can be easily distinguished from parameters required by tfctl.
|
10
|
+
Configuration file will need to be updated to reflect this to pass validation.
|
11
|
+
|
12
|
+
|
13
|
+
== 0.2.0
|
14
|
+
|
15
|
+
* feat: configurable Terraform and AWS provider version requirements
|
16
|
+
* fix: use provider region from config file
|
17
|
+
* fix: fail when terraform command is missing
|
18
|
+
|
19
|
+
== 0.1.0
|
20
|
+
|
21
|
+
* feat: Added `-l` switch to list discovered accounts.
|
22
|
+
|
23
|
+
== 0.0.2
|
24
|
+
|
25
|
+
* fix: Fixed an exception when `exclude_accounts` is not set.
|
26
|
+
|
27
|
+
== 0.0.1
|
28
|
+
|
29
|
+
* Initial release
|
data/Gemfile
ADDED
data/Guardfile
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,36 @@
|
|
1
|
+
.PHONY: clean install test rubocop spec guard
|
2
|
+
|
3
|
+
vendor:
|
4
|
+
$(info => Installing Ruby dependencies)
|
5
|
+
@bundle install --path vendor --with developement --binstubs=vendor/bin
|
6
|
+
|
7
|
+
test: vendor rubocop spec
|
8
|
+
|
9
|
+
guard: vendor
|
10
|
+
$(info => Starting guard)
|
11
|
+
@bundle exec guard
|
12
|
+
|
13
|
+
rubocop:
|
14
|
+
$(info => Running rubocop)
|
15
|
+
@vendor/bin/rubocop
|
16
|
+
|
17
|
+
spec:
|
18
|
+
$(info => Running spec tests)
|
19
|
+
@vendor/bin/rspec
|
20
|
+
|
21
|
+
pkg:
|
22
|
+
$(info => Building gem package in pkg/)
|
23
|
+
@mkdir pkg/
|
24
|
+
@gem build tfctl.gemspec
|
25
|
+
@mv *.gem pkg/
|
26
|
+
|
27
|
+
install: pkg
|
28
|
+
gem install pkg/*.gem
|
29
|
+
|
30
|
+
clean:
|
31
|
+
$(info => Cleaning)
|
32
|
+
@rm -rf pkg/
|
33
|
+
@rm -rf vendor/
|
34
|
+
@rm -rf .bundle
|
35
|
+
@rm -f Gemfile.lock
|
36
|
+
@rm -rf spec/reports/
|
data/README.adoc
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
// Settings:
|
2
|
+
:idprefix:
|
3
|
+
:idseparator: -
|
4
|
+
ifndef::env-github[:icons: font]
|
5
|
+
ifdef::env-github,env-browser[]
|
6
|
+
:toc: macro
|
7
|
+
:toclevels: 1
|
8
|
+
endif::[]
|
9
|
+
ifdef::env-github[]
|
10
|
+
:branch: master
|
11
|
+
:status:
|
12
|
+
:outfilesuffix: .adoc
|
13
|
+
:!toc-title:
|
14
|
+
:caution-caption: :fire:
|
15
|
+
:important-caption: :exclamation:
|
16
|
+
:note-caption: :paperclip:
|
17
|
+
:tip-caption: :bulb:
|
18
|
+
:warning-caption: :warning:
|
19
|
+
endif::[]
|
20
|
+
|
21
|
+
= tfctl
|
22
|
+
|
23
|
+
image:https://travis-ci.org/scalefactory/tfctl.svg?branch=master["Build Status", link="https://travis-ci.org/scalefactory/tfctl"]
|
24
|
+
image:https://badge.fury.io/rb/tfctl.svg["Gem Version", link="https://badge.fury.io/rb/tfctl"]
|
25
|
+
image:https://img.shields.io/badge/terraform-0.12-blue.svg["Terraform 0.12", link="https://img.shields.io/badge/terraform-0.12-blue"]
|
26
|
+
|
27
|
+
toc::[]
|
28
|
+
|
29
|
+
== Overview
|
30
|
+
|
31
|
+
Tfctl is a small Terraform wrapper for working with multi-account AWS
|
32
|
+
infrastructures where new accounts may be created dynamically and on-demand.
|
33
|
+
|
34
|
+
It discovers accounts by reading the AWS Organizations API and can assign
|
35
|
+
Terraform resources to multiple accounts based on the organization hierarchy.
|
36
|
+
Resources can be assigned globally, based on organization unit or to individual
|
37
|
+
accounts. It supports nested OU hierarchies and helps keep your Terraform DRY.
|
38
|
+
|
39
|
+
Tfctl was originally developed to integrate Terraform with
|
40
|
+
https://aws.amazon.com/solutions/aws-landing-zone/[AWS Landing Zone] and
|
41
|
+
https://aws.amazon.com/controltower/[Control Tower] but should work with most
|
42
|
+
other ways of managing accounts in AWS Organizations.
|
43
|
+
|
44
|
+
== Features
|
45
|
+
|
46
|
+
* Discovers AWS accounts automatically.
|
47
|
+
* Automatically generates Terraform account configuration.
|
48
|
+
* Parallel execution across multiple accounts.
|
49
|
+
* Hierarchical configuration based on AWS Organization units structure.
|
50
|
+
* Supports per account configuration overrides for handling exceptions.
|
51
|
+
* Supports nested organization units.
|
52
|
+
* Terraform state tracking in S3 and locking in DynamoDB.
|
53
|
+
* Account targeting by OU path regular expressions.
|
54
|
+
* Automatic role assumption in target accounts.
|
55
|
+
* Works with CI/CD pipelines.
|
56
|
+
|
57
|
+
== Requirements
|
58
|
+
|
59
|
+
* Terraform >= 0.12
|
60
|
+
* Ruby >= 2.3
|
61
|
+
* Accounts managed in AWS Organizations (by Landing Zone, Control Tower, some
|
62
|
+
other means)
|
63
|
+
|
64
|
+
== Installation
|
65
|
+
|
66
|
+
To install the latest release from RubyGems run:
|
67
|
+
|
68
|
+
----
|
69
|
+
gem install tfctl
|
70
|
+
----
|
71
|
+
|
72
|
+
Alternatively you can build and install from this repo with:
|
73
|
+
|
74
|
+
----
|
75
|
+
make install
|
76
|
+
----
|
77
|
+
|
78
|
+
== Docs
|
79
|
+
|
80
|
+
* https://github.com/scalefactory/tfctl/tree/master/docs/control_tower.adoc[Control Tower quick start guide]
|
81
|
+
* https://github.com/scalefactory/tfctl/tree/master/docs/project_layout.adoc[Project layout]
|
82
|
+
* https://github.com/scalefactory/tfctl/tree/master/docs/configuration.adoc[Configuration]
|
83
|
+
* https://github.com/scalefactory/tfctl/tree/master/docs/iam_permissions.adoc[IAM permissions]
|
84
|
+
* https://github.com/scalefactory/tfctl/tree/master/docs/creating_a_profile.adoc[Creating a profile]
|
85
|
+
|
86
|
+
== Running tfctl
|
87
|
+
|
88
|
+
tfctl should be run from the root of the project directory. It will generate
|
89
|
+
Terraform configuration in `.tfctl/`.
|
90
|
+
|
91
|
+
Anatomy of a tfctl command:
|
92
|
+
|
93
|
+
----
|
94
|
+
tfctl -c CONFIG_FILE TARGET_OPTIONS -- TERRAFORM_COMMAND
|
95
|
+
----
|
96
|
+
|
97
|
+
* `-c` specifies which tfctl config file to use (usually in `conf/`)
|
98
|
+
* `TARGET_OPTIONS` specifies which accounts to target. This could be an individual
|
99
|
+
account, a group of accounts in an organizational unit or all accounts.
|
100
|
+
* `TERRAFORM_COMMAND` will be passed to `terraform` along with any
|
101
|
+
options. See https://www.terraform.io/docs/commands/index.html[Terraform
|
102
|
+
commands] for details.
|
103
|
+
|
104
|
+
NOTE: You must have your AWS credentials configured before running tfctl or run
|
105
|
+
it using an AWS credentials helper such as
|
106
|
+
https://github.com/99designs/aws-vault[aws-vault].
|
107
|
+
|
108
|
+
=== Example commands
|
109
|
+
|
110
|
+
Show help:
|
111
|
+
|
112
|
+
----
|
113
|
+
tfctl -h
|
114
|
+
----
|
115
|
+
|
116
|
+
Show merged configuration:
|
117
|
+
|
118
|
+
----
|
119
|
+
tfctl -c conf/example.yaml -s
|
120
|
+
----
|
121
|
+
|
122
|
+
List all discovered accounts:
|
123
|
+
|
124
|
+
----
|
125
|
+
tfctl -c conf/example.yaml --all -l
|
126
|
+
----
|
127
|
+
|
128
|
+
TIP: This can be narrowed down using targeting options and is a good way to
|
129
|
+
test what accounts match.
|
130
|
+
|
131
|
+
Run Terraform init across all accounts:
|
132
|
+
|
133
|
+
----
|
134
|
+
tfctl -c conf/example.yaml --all -- init
|
135
|
+
----
|
136
|
+
|
137
|
+
Run plan in `test` OU accounts:
|
138
|
+
|
139
|
+
----
|
140
|
+
tfctl -c conf/example.yaml -o test -- plan
|
141
|
+
----
|
142
|
+
|
143
|
+
Run plan in `live` accounts assuming that `live` is a child OU in multiple
|
144
|
+
organization units:
|
145
|
+
|
146
|
+
----
|
147
|
+
tfctl -c conf/example.yaml -o '.*/live' -- plan
|
148
|
+
----
|
149
|
+
|
150
|
+
Run plan in an individual account:
|
151
|
+
|
152
|
+
----
|
153
|
+
tfctl -c conf/example.yaml -a example-account - plan
|
154
|
+
----
|
155
|
+
|
156
|
+
Run apply in all accounts:
|
157
|
+
|
158
|
+
----
|
159
|
+
tfctl -c conf/example.yaml --all -- apply
|
160
|
+
----
|
161
|
+
|
162
|
+
Run destroy in `test` OU accounts:
|
163
|
+
|
164
|
+
----
|
165
|
+
tfctl -c conf/example.yaml -o test -- destroy -auto-approve
|
166
|
+
----
|
167
|
+
|
168
|
+
Don't buffer the output:
|
169
|
+
|
170
|
+
----
|
171
|
+
tfctl -c conf/example.yaml -a example-account -u -- plan
|
172
|
+
----
|
173
|
+
|
174
|
+
This will show output in real time. Usually output is buffered and displayed
|
175
|
+
after Terraform command finishes to make it more readable when running across
|
176
|
+
multiple accounts in parallel.
|
data/bin/tfctl
ADDED
@@ -0,0 +1,223 @@
|
|
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 'English'
|
11
|
+
require 'terminal-table'
|
12
|
+
require_relative '../lib/tfctl'
|
13
|
+
|
14
|
+
PROJECT_ROOT = Dir.pwd
|
15
|
+
|
16
|
+
#
|
17
|
+
# Process CLI arguments
|
18
|
+
#
|
19
|
+
|
20
|
+
options = {
|
21
|
+
account: nil,
|
22
|
+
ou: nil,
|
23
|
+
all: nil,
|
24
|
+
show_config: false,
|
25
|
+
config_file: nil,
|
26
|
+
unbuffered: false,
|
27
|
+
debug: false,
|
28
|
+
use_cache: false,
|
29
|
+
}
|
30
|
+
|
31
|
+
optparse = OptionParser.new do |opts|
|
32
|
+
opts.on('-a', '--account=name', 'Target a specific AWS account') do |o|
|
33
|
+
options[:account] = o
|
34
|
+
end
|
35
|
+
opts.on('-o', '--ou=organization_unit', 'Target accounts in an Organization Unit (uses regex matching)') do |o|
|
36
|
+
options[:ou] = o
|
37
|
+
end
|
38
|
+
opts.on('--all', 'Target all accounts') do
|
39
|
+
options[:all] = true
|
40
|
+
end
|
41
|
+
opts.on('-c', '--config-file=config', 'Path to config file') do |o|
|
42
|
+
options[:config_file] = o
|
43
|
+
end
|
44
|
+
opts.on('-s', '--show-config', 'Display configuration') do
|
45
|
+
options[:show_config] = true
|
46
|
+
end
|
47
|
+
opts.on('-l', '--list-accounts', 'List discovered accounts') do
|
48
|
+
options[:list_accounts] = true
|
49
|
+
end
|
50
|
+
opts.on('-x', '--use-cache', 'Use cached AWS organization data') do
|
51
|
+
options[:use_cache] = true
|
52
|
+
end
|
53
|
+
opts.on('-u', '--unbuffered', 'Disable buffering of Terraform output') do
|
54
|
+
options[:unbuffered] = true
|
55
|
+
end
|
56
|
+
opts.on('-d', '--debug', 'Turn on debug messages') do
|
57
|
+
options[:debug] = true
|
58
|
+
end
|
59
|
+
opts.on('-v', '--version', 'Show version') do
|
60
|
+
puts Tfctl::VERSION
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
begin
|
67
|
+
optparse.parse!
|
68
|
+
|
69
|
+
# Validate CLI arguments
|
70
|
+
|
71
|
+
if options[:config_file].nil?
|
72
|
+
raise OptionParser::MissingArgument, '--config-file'
|
73
|
+
end
|
74
|
+
|
75
|
+
unless File.exist? options[:config_file]
|
76
|
+
raise OptionParser::InvalidOption,
|
77
|
+
"Config file not found in: #{options[:config_file]}"
|
78
|
+
end
|
79
|
+
|
80
|
+
unless File.exist? options[:config_file]
|
81
|
+
raise OptionParser::InvalidOption, "Config file #{options[:config_file]} not found."
|
82
|
+
end
|
83
|
+
|
84
|
+
# Validate targets
|
85
|
+
targetting_opts = %i[account ou all]
|
86
|
+
targets_set = []
|
87
|
+
options.each do |k, v|
|
88
|
+
if targetting_opts.include?(k)
|
89
|
+
targets_set << k.to_s unless v.nil?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
if targets_set.length > 1
|
93
|
+
raise OptionParser::InvalidOption,
|
94
|
+
"Too many target options set: #{targets_set.join(', ')}. Only one can be specified."
|
95
|
+
end
|
96
|
+
if targets_set.empty? and options[:show_config] == false
|
97
|
+
raise OptionParser::InvalidOption, 'Please specify target'
|
98
|
+
end
|
99
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
100
|
+
warn $ERROR_INFO.to_s
|
101
|
+
warn optparse
|
102
|
+
exit 2
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
# Generates configuration and runs Terraform commands for a target account.
|
108
|
+
def run_account(config, account, options, tf_argv, log)
|
109
|
+
|
110
|
+
# Skip excluded accounts
|
111
|
+
if account[:excluded] == true
|
112
|
+
log.info "#{account[:name]}: excluded, skipping"
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
# Generate Terraform run directory with configured providers, backend and
|
117
|
+
# profiles for the target account. This is where Terraform will be
|
118
|
+
# executed from.
|
119
|
+
log.info "#{account[:name]}: Generating Terraform run directory"
|
120
|
+
Tfctl::Generator.make(
|
121
|
+
account: account,
|
122
|
+
config: config,
|
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
|
+
# Set up logging
|
142
|
+
log_level = options[:debug] ? Logger::DEBUG : Logger::INFO
|
143
|
+
log = Tfctl::Logger.new(log_level)
|
144
|
+
|
145
|
+
log.info 'tfctl running'
|
146
|
+
|
147
|
+
config_name = File.basename(options[:config_file]).chomp('.yaml')
|
148
|
+
log.info "Using config: #{config_name}"
|
149
|
+
|
150
|
+
log.info 'Working out AWS account topology'
|
151
|
+
|
152
|
+
yaml_config = YAML.safe_load(File.read(options[:config_file]))
|
153
|
+
Tfctl::Schema.validate(yaml_config)
|
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
|
+
# Find target accounts
|
174
|
+
|
175
|
+
if options[:account]
|
176
|
+
accounts = config.find_accounts(:name, options[:account])
|
177
|
+
elsif options[:ou]
|
178
|
+
accounts = config.find_accounts_regex(:ou_path, options[:ou])
|
179
|
+
elsif options[:all]
|
180
|
+
accounts = config[:accounts]
|
181
|
+
else
|
182
|
+
raise Tfctl::Error, 'Missing target'
|
183
|
+
end
|
184
|
+
|
185
|
+
# List target accounts
|
186
|
+
|
187
|
+
if options[:list_accounts]
|
188
|
+
log.info "Listing accounts\n"
|
189
|
+
table = Terminal::Table.new do |t|
|
190
|
+
t.style = {
|
191
|
+
border_x: '',
|
192
|
+
border_y: '',
|
193
|
+
border_i: '',
|
194
|
+
padding_left: 0,
|
195
|
+
}
|
196
|
+
t << %w[ACCOUNT_ID OU NAME]
|
197
|
+
accounts.each do |account|
|
198
|
+
t << [account[:id], account[:ou_path], account[:name]]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
puts table
|
203
|
+
exit 0
|
204
|
+
end
|
205
|
+
|
206
|
+
# Execute Terraform in target accounts
|
207
|
+
|
208
|
+
Parallel.each(accounts, in_processes: 8) do |ac|
|
209
|
+
run_account(config, ac, options, ARGV, log)
|
210
|
+
end
|
211
|
+
|
212
|
+
log.info 'Done'
|
213
|
+
rescue Tfctl::Error => e
|
214
|
+
log.error(e)
|
215
|
+
exit 1
|
216
|
+
rescue Tfctl::ValidationError => e
|
217
|
+
log.error(e)
|
218
|
+
e.issues.each do |issue|
|
219
|
+
log.error("Parameter: #{issue[:data_pointer]}") unless issue[:data_pointer] == ''
|
220
|
+
log.error(issue[:details]) unless issue[:details].nil?
|
221
|
+
end
|
222
|
+
exit 2
|
223
|
+
end
|