tfctl 0.2.0 → 1.0.0.rc1
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 +4 -4
- data/CHANGELOG.adoc +10 -0
- data/README.adoc +26 -5
- data/bin/tfctl +8 -0
- data/docs/configuration.adoc +16 -4
- data/examples/control_tower/conf/example.yaml +31 -23
- data/lib/tfctl.rb +4 -3
- data/lib/tfctl/error.rb +9 -0
- data/lib/tfctl/schema.rb +80 -0
- data/lib/tfctl/version.rb +1 -1
- data/tfctl.gemspec +1 -0
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7851ad9d647739e471f8d430dace8564f16f403f3a7e7ffb98e21384db641f53
|
4
|
+
data.tar.gz: d52713addc4006e2e67f5c3fce56941697d4a9edfb6bea08953c76eeb673c2f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10f6a9bce18e905d787783a6f61a3f93724b4809d1c7f2c7e7c1edc155ba2c14b17a97096140f790c9b7e250dc4fd24f7af5c86fefea8139d25e1b15dddc65d3
|
7
|
+
data.tar.gz: 9e81ab6abbc15df5917ea6b23a1b167a2cd745e057d462ce0e28db47b27c6f12477c62293893c59f1977c5c406df31dbb797818fc27978b5bd9255a12bd86370
|
data/CHANGELOG.adoc
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
= Changelog
|
2
2
|
|
3
|
+
== 1.0.0-rc1 (unreleased)
|
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
|
+
|
3
13
|
== 0.2.0
|
4
14
|
|
5
15
|
* feat: configurable Terraform and AWS provider version requirements
|
data/README.adoc
CHANGED
@@ -1,19 +1,40 @@
|
|
1
|
-
:
|
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::[]
|
2
20
|
|
3
21
|
= tfctl
|
4
22
|
|
5
23
|
image:https://travis-ci.org/scalefactory/tfctl.svg?branch=master["Build Status", link="https://travis-ci.org/scalefactory/tfctl"]
|
6
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::[]
|
7
28
|
|
8
29
|
== Overview
|
9
30
|
|
10
31
|
Tfctl is a small Terraform wrapper for working with multi-account AWS
|
11
32
|
infrastructures where new accounts may be created dynamically and on-demand.
|
12
33
|
|
13
|
-
|
14
|
-
Terraform resources to accounts based on the organization hierarchy.
|
15
|
-
can be assigned globally, based on organization unit or individual
|
16
|
-
It supports nested OU hierarchies.
|
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.
|
17
38
|
|
18
39
|
Tfctl was originally developed to integrate Terraform with
|
19
40
|
https://aws.amazon.com/solutions/aws-landing-zone/[AWS Landing Zone] and
|
data/bin/tfctl
CHANGED
@@ -150,6 +150,7 @@ begin
|
|
150
150
|
log.info 'Working out AWS account topology'
|
151
151
|
|
152
152
|
yaml_config = YAML.safe_load(File.read(options[:config_file]))
|
153
|
+
Tfctl::Schema.validate(yaml_config)
|
153
154
|
yaml_config.symbolize_names!
|
154
155
|
|
155
156
|
org_units = yaml_config[:organization_units].keys
|
@@ -212,4 +213,11 @@ begin
|
|
212
213
|
rescue Tfctl::Error => e
|
213
214
|
log.error(e)
|
214
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
|
215
223
|
end
|
data/docs/configuration.adoc
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
== Configuration
|
2
2
|
|
3
3
|
Tfctl retrieves initial account configuration from AWS Organizations and merges
|
4
|
-
it with
|
4
|
+
it with configuration specified in a yaml file.
|
5
5
|
|
6
6
|
The configuration is merged in the following order:
|
7
7
|
|
@@ -15,13 +15,16 @@ Parameters further down the hierarchy take precedence. For example:
|
|
15
15
|
[source, yaml]
|
16
16
|
----
|
17
17
|
organization_root:
|
18
|
-
|
18
|
+
data:
|
19
|
+
example_param: 'will be overriden further down'
|
19
20
|
|
20
21
|
organization_units:
|
21
22
|
team:
|
22
|
-
|
23
|
+
data:
|
24
|
+
example_param: 'will win in team ou'
|
23
25
|
team/live:
|
24
|
-
|
26
|
+
data:
|
27
|
+
example_param: 'will win in team/live ou'
|
25
28
|
----
|
26
29
|
|
27
30
|
One exception to this rule is the `profiles` parameter. Profiles are additive:
|
@@ -45,6 +48,15 @@ TIP: You can display the fully merged configuration by running `tfctl -c
|
|
45
48
|
conf/CONFIG_FILE.yaml -s`. It's safe to run as it doesn't make any changes to
|
46
49
|
AWS resources. It's a good way to test your configuration.
|
47
50
|
|
51
|
+
=== Defining arbitrary data
|
52
|
+
|
53
|
+
You can define arbitrary data under the `data:` parameter, both in the root of
|
54
|
+
the config and in the organization sections. It will be available in Terraform
|
55
|
+
profiles to use by your modules. You can use this to define things like VPC
|
56
|
+
subnet ranges, s3 bucket names and so on. `data:` in organization sections
|
57
|
+
will be merged with accounts following the usual merge order as described
|
58
|
+
above.
|
59
|
+
|
48
60
|
=== Handling secrets
|
49
61
|
|
50
62
|
No secrets should be committed into Terraform or tfctl configuration. Use AWS
|
@@ -12,8 +12,6 @@
|
|
12
12
|
# Terraform configuration
|
13
13
|
#
|
14
14
|
|
15
|
-
# State management
|
16
|
-
|
17
15
|
tf_state_bucket: 'CHANGEME'
|
18
16
|
tf_state_dynamodb_table: 'terraform-lock'
|
19
17
|
tf_state_region: 'eu-west-1'
|
@@ -26,47 +24,57 @@ aws_provider_version: '>= 2.14'
|
|
26
24
|
tfctl_role_arn: 'arn:aws:iam::PRIMARY_ACCOUNT_ID:role/TfctlRole'
|
27
25
|
|
28
26
|
#
|
29
|
-
#
|
27
|
+
# Data
|
28
|
+
#
|
29
|
+
# Here you can add arbitrary data which will be accessible from Terraform
|
30
|
+
# profiles. Data can also be defined per account in the organization sections
|
31
|
+
# below.
|
30
32
|
#
|
33
|
+
# data:
|
34
|
+
# my_parameter: some_value
|
31
35
|
|
36
|
+
#
|
37
|
+
# Organization configuration
|
38
|
+
#
|
39
|
+
# Assign resources and data to accounts based on the organization structure.
|
40
|
+
#
|
32
41
|
# IMPORTANT: Removing a Terraform profile here will remove all of it's
|
33
|
-
# associated resources
|
34
|
-
|
35
|
-
# Name of the primary account.
|
36
|
-
primary_account: CHANGEME
|
42
|
+
# associated resources during next apply!
|
37
43
|
|
38
|
-
# Configuration to apply
|
44
|
+
# Configuration to apply to all accounts
|
39
45
|
organization_root:
|
40
|
-
|
41
46
|
# Role assumed by Terraform for execution in each account
|
42
47
|
tf_execution_role: 'AWSControlTowerExecution'
|
43
48
|
region: 'eu-west-1'
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
data:
|
50
|
+
# Bucket name used by example profile it will be prefixed with the target
|
51
|
+
# account number for uniqueness across accounts.
|
52
|
+
example_bucket_name: 'tfctl-example-bucket'
|
47
53
|
# Assign example-profile to all accounts in managed OUs
|
48
54
|
profiles:
|
49
55
|
- example-profile
|
50
56
|
|
51
57
|
# Configuration to apply to accounts in Organization Units
|
52
|
-
#
|
58
|
+
# OU's not listed here will be ignored.
|
53
59
|
organization_units:
|
54
|
-
# Uncomment if you want to include Core OU accounts
|
55
|
-
# Core: {}
|
60
|
+
# Core: {} # Uncomment if you want to include Core OU accounts
|
56
61
|
live: {}
|
57
62
|
test: {}
|
58
63
|
mgmt:
|
59
|
-
|
60
|
-
|
64
|
+
data:
|
65
|
+
# Override the example bucket name in mgmt OU accounts
|
66
|
+
example_bucket_name: 'tfctl-ou-override-example'
|
61
67
|
|
62
68
|
# Configuration to apply to individual accounts
|
63
69
|
account_overrides:
|
64
70
|
test-example1:
|
65
|
-
|
66
|
-
|
71
|
+
data:
|
72
|
+
# Override the bucket name in a specific account
|
73
|
+
example_bucket_name: 'tfctl-account-override-example'
|
67
74
|
|
68
75
|
|
69
|
-
# Exclude individual accounts from Terraform runs
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
76
|
+
# Exclude individual accounts from Terraform runs
|
77
|
+
#
|
78
|
+
# exclude_accounts:
|
79
|
+
# - Audit
|
80
|
+
# - 'Log archive'
|
data/lib/tfctl.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'tfctl/aws_org.rb'
|
3
4
|
require_relative 'tfctl/config.rb'
|
4
|
-
require_relative 'tfctl/generator.rb'
|
5
|
-
require_relative 'tfctl/executor.rb'
|
6
5
|
require_relative 'tfctl/error.rb'
|
6
|
+
require_relative 'tfctl/executor.rb'
|
7
|
+
require_relative 'tfctl/generator.rb'
|
7
8
|
require_relative 'tfctl/logger.rb'
|
8
|
-
require_relative 'tfctl/
|
9
|
+
require_relative 'tfctl/schema.rb'
|
9
10
|
require_relative 'tfctl/version.rb'
|
data/lib/tfctl/error.rb
CHANGED
data/lib/tfctl/schema.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json_schemer'
|
4
|
+
require_relative 'error.rb'
|
5
|
+
|
6
|
+
# Config validator using JSON schema
|
7
|
+
|
8
|
+
module Tfctl
|
9
|
+
module Schema
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def validate(data)
|
13
|
+
schemer = JSONSchemer.schema(main_schema)
|
14
|
+
issues = []
|
15
|
+
schemer.validate(data).each do |issue|
|
16
|
+
issues << {
|
17
|
+
details: issue['details'],
|
18
|
+
data_pointer: issue['data_pointer'],
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
return if issues.empty?
|
23
|
+
|
24
|
+
raise Tfctl::ValidationError.new('Config validation failed', issues)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def main_schema
|
30
|
+
iam_arn_pattern = 'arn:aws:iam:[a-z\-0-9]*:[0-9]{12}:[a-zA-Z\/+@=.,]*'
|
31
|
+
|
32
|
+
# rubocop:disable Layout/AlignHash
|
33
|
+
{
|
34
|
+
'type' => 'object',
|
35
|
+
'properties' => {
|
36
|
+
'tf_state_bucket' => { 'type' => 'string' },
|
37
|
+
'tf_state_role_arn' => {
|
38
|
+
'type' => 'string',
|
39
|
+
'pattern' => iam_arn_pattern,
|
40
|
+
},
|
41
|
+
'tf_state_dynamodb_table' => { 'type' => 'string' },
|
42
|
+
'tf_state_region' => { 'type' => 'string' },
|
43
|
+
'tf_required_version' => { 'type' => 'string' },
|
44
|
+
'aws_provider_version' => { 'type' => 'string' },
|
45
|
+
'tfctl_role_arn' => {
|
46
|
+
'type' => 'string',
|
47
|
+
'pattern' => iam_arn_pattern,
|
48
|
+
},
|
49
|
+
'data' => { 'type' => 'object' },
|
50
|
+
'exclude_accounts' => { 'type' => 'array' },
|
51
|
+
'organization_root' => org_schema,
|
52
|
+
'organization_units' => org_schema,
|
53
|
+
'account_overrides' => org_schema,
|
54
|
+
},
|
55
|
+
'required' => %w[
|
56
|
+
tf_state_bucket
|
57
|
+
tf_state_role_arn
|
58
|
+
tf_state_dynamodb_table
|
59
|
+
tf_state_region
|
60
|
+
tfctl_role_arn
|
61
|
+
],
|
62
|
+
'additionalProperties' => false,
|
63
|
+
}
|
64
|
+
# rubocop:enable Layout/AlignHash
|
65
|
+
end
|
66
|
+
|
67
|
+
def org_schema
|
68
|
+
{
|
69
|
+
'type' => 'object',
|
70
|
+
'properties' => {
|
71
|
+
'profiles' => { 'type'=> 'array' },
|
72
|
+
'data' => { 'type'=> 'object' },
|
73
|
+
'tf_execution_role' => { 'type'=> 'string' },
|
74
|
+
'region' => { 'type'=> 'string' },
|
75
|
+
},
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/tfctl/version.rb
CHANGED
data/tfctl.gemspec
CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
# Think when adding new dependencies. Is it really necessary?
|
27
27
|
# "The things you own end up owning you" etc.
|
28
28
|
spec.add_dependency 'aws-sdk-organizations', '~> 1.13'
|
29
|
+
spec.add_dependency 'json_schemer', '~> 0.2'
|
29
30
|
spec.add_dependency 'parallel', '~> 1.17'
|
30
31
|
spec.add_dependency 'terminal-table', '~> 1.8'
|
31
32
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tfctl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Wasilczuk
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json_schemer
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: parallel
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -135,6 +149,7 @@ files:
|
|
135
149
|
- lib/tfctl/executor.rb
|
136
150
|
- lib/tfctl/generator.rb
|
137
151
|
- lib/tfctl/logger.rb
|
152
|
+
- lib/tfctl/schema.rb
|
138
153
|
- lib/tfctl/version.rb
|
139
154
|
- tfctl.gemspec
|
140
155
|
homepage: https://github.com/scalefactory/tfctl
|
@@ -152,9 +167,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
167
|
version: '0'
|
153
168
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
169
|
requirements:
|
155
|
-
- - "
|
170
|
+
- - ">"
|
156
171
|
- !ruby/object:Gem::Version
|
157
|
-
version:
|
172
|
+
version: 1.3.1
|
158
173
|
requirements: []
|
159
174
|
rubyforge_project:
|
160
175
|
rubygems_version: 2.7.7
|