tfctl 0.2.0 → 1.2.1

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.
@@ -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/aws_org.rb'
9
+ require_relative 'tfctl/schema.rb'
9
10
  require_relative 'tfctl/version.rb'
@@ -71,23 +71,25 @@ module Tfctl
71
71
  @aws_org_client.list_children(
72
72
  child_type: 'ORGANIZATIONAL_UNIT',
73
73
  parent_id: parent_id,
74
- ).children.each do |child|
75
-
76
- begin
77
- ou = @aws_org_client.describe_organizational_unit(
78
- organizational_unit_id: child.id,
79
- ).organizational_unit
80
- rescue Aws::Organizations::Errors::TooManyRequestsException
81
- # FIXME: - use logger
82
- puts 'AWS Organizations: too many requests. Retrying in 5 secs.'
83
- sleep 5
84
- retries += 1
85
- retry if retries < 10
86
- end
74
+ ).each do |resp|
75
+ resp.children.each do |child|
76
+
77
+ begin
78
+ ou = @aws_org_client.describe_organizational_unit(
79
+ organizational_unit_id: child.id,
80
+ ).organizational_unit
81
+ rescue Aws::Organizations::Errors::TooManyRequestsException
82
+ # FIXME: - use logger
83
+ puts 'AWS Organizations: too many requests. Retrying in 5 secs.'
84
+ sleep 5
85
+ retries += 1
86
+ retry if retries < 10
87
+ end
87
88
 
88
- ou_name = parent_name == :root ? ou.name.to_sym : "#{parent_name}/#{ou.name}".to_sym
89
+ ou_name = parent_name == :root ? ou.name.to_sym : "#{parent_name}/#{ou.name}".to_sym
89
90
 
90
- output[ou_name] = ou.id
91
+ output[ou_name] = ou.id
92
+ end
91
93
  end
92
94
  output
93
95
  end
@@ -48,7 +48,7 @@ module Tfctl
48
48
  @config.to_json
49
49
  end
50
50
 
51
- # Filters accounts by account property
51
+ # Filters accounts by an account property
52
52
  def find_accounts(property_name, property_value)
53
53
  output =[]
54
54
  @config[:accounts].each do |account|
@@ -88,7 +88,6 @@ module Tfctl
88
88
 
89
89
  # Retrieves AWS Organizations data and merges it with data from yaml config.
90
90
  def load_config(config_name, yaml_config, aws_org_config)
91
-
92
91
  # AWS Organizations data
93
92
  config = aws_org_config
94
93
  # Merge organization sections from yaml file
@@ -3,4 +3,13 @@
3
3
  module Tfctl
4
4
  class Error < StandardError
5
5
  end
6
+
7
+ class ValidationError < StandardError
8
+ attr_reader :issues
9
+
10
+ def initialize(message, issues = [])
11
+ super(message)
12
+ @issues = issues
13
+ end
14
+ end
6
15
  end
@@ -41,18 +41,22 @@ module Tfctl
41
41
  # Create the command
42
42
  exec = [cmd] + [subcmd] + args
43
43
 
44
- # Set environment variables for Terraform
45
- env = {
46
- 'TF_INPUT' => '0',
44
+ # Copy TF_* variables from process environment, but
45
+ # override those based on the following hash:
46
+ enforced_terraform_environment = {
47
47
  'CHECKPOINT_DISABLE' => '1',
48
+ 'TF_INPUT' => '0',
48
49
  'TF_IN_AUTOMATION' => 'true',
49
- # 'TF_LOG' => 'TRACE'
50
50
  }
51
51
 
52
+ terraform_env = ENV.keep_if do |variable_name|
53
+ variable_name.start_with?('TF_')
54
+ end.merge(enforced_terraform_environment)
55
+
52
56
  log.debug "#{account_name}: Executing: #{exec.shelljoin}"
53
57
 
54
58
  FileUtils.cd path
55
- Open3.popen3(env, exec.shelljoin) do |stdin, stdout, stderr, wait_thr|
59
+ Open3.popen3(terraform_env, exec.shelljoin) do |stdin, stdout, stderr, wait_thr|
56
60
  stdin.close_write
57
61
 
58
62
  # capture stdout and stderr in separate threads to prevent deadlocks
@@ -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/HashAlignment
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/HashAlignment
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tfctl
4
- VERSION = '0.2.0'
4
+ VERSION = '1.2.1'
5
5
  end
@@ -23,13 +23,16 @@ Gem::Specification.new do |spec|
23
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
24
  spec.require_paths = ['lib']
25
25
 
26
+ spec.required_ruby_version = '>= 2.5.0'
27
+
26
28
  # Think when adding new dependencies. Is it really necessary?
27
29
  # "The things you own end up owning you" etc.
28
- spec.add_dependency 'aws-sdk-organizations', '~> 1.13'
29
- spec.add_dependency 'parallel', '~> 1.17'
30
+ spec.add_dependency 'aws-sdk-organizations', '~> 1.40'
31
+ spec.add_dependency 'json_schemer', '~> 0.2'
32
+ spec.add_dependency 'parallel', '~> 1.19'
30
33
  spec.add_dependency 'terminal-table', '~> 1.8'
31
34
 
32
35
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
33
- spec.add_development_dependency 'rspec', '~> 3.8'
34
- spec.add_development_dependency 'rubocop', '~> 0.76'
36
+ spec.add_development_dependency 'rspec', '~> 3.9'
37
+ spec.add_development_dependency 'rubocop', '~> 0.84'
35
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tfctl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Wasilczuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-10 00:00:00.000000000 Z
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-organizations
@@ -16,28 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.13'
19
+ version: '1.40'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.13'
26
+ version: '1.40'
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
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '1.17'
47
+ version: '1.19'
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '1.17'
54
+ version: '1.19'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: terminal-table
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -72,28 +86,28 @@ dependencies:
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: '3.8'
89
+ version: '3.9'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: '3.8'
96
+ version: '3.9'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubocop
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '0.76'
103
+ version: '0.84'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '0.76'
110
+ version: '0.84'
97
111
  description:
98
112
  email:
99
113
  - akw@scalefactory.com
@@ -121,12 +135,12 @@ files:
121
135
  - examples/bootstrap/terraform-exec-role.template
122
136
  - examples/bootstrap/terraform-state.template
123
137
  - examples/bootstrap/tfctl-org-access.template
124
- - examples/control_tower/conf/example.yaml
125
138
  - examples/control_tower/modules/s3-bucket/main.tf
126
139
  - examples/control_tower/modules/s3-bucket/variables.tf
127
140
  - examples/control_tower/profiles/example-profile/data.tf
128
141
  - examples/control_tower/profiles/example-profile/main.tf
129
142
  - examples/control_tower/profiles/example-profile/variables.tf
143
+ - examples/control_tower/tfctl.yaml
130
144
  - lib/hash.rb
131
145
  - lib/tfctl.rb
132
146
  - lib/tfctl/aws_org.rb
@@ -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
@@ -149,15 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
164
  requirements:
150
165
  - - ">="
151
166
  - !ruby/object:Gem::Version
152
- version: '0'
167
+ version: 2.5.0
153
168
  required_rubygems_version: !ruby/object:Gem::Requirement
154
169
  requirements:
155
170
  - - ">="
156
171
  - !ruby/object:Gem::Version
157
172
  version: '0'
158
173
  requirements: []
159
- rubyforge_project:
160
- rubygems_version: 2.7.7
174
+ rubygems_version: 3.0.8
161
175
  signing_key:
162
176
  specification_version: 4
163
177
  summary: Terraform wrapper for managing multi-account AWS infrastructures