tfctl 0.1.0 → 1.1.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
@@ -26,6 +26,10 @@ module Tfctl
26
26
  @config[key]
27
27
  end
28
28
 
29
+ def fetch(key, default)
30
+ @config.fetch(key, default)
31
+ end
32
+
29
33
  def each(&block)
30
34
  @config.each(&block)
31
35
  end
@@ -44,7 +48,7 @@ module Tfctl
44
48
  @config.to_json
45
49
  end
46
50
 
47
- # Filters accounts by account property
51
+ # Filters accounts by an account property
48
52
  def find_accounts(property_name, property_value)
49
53
  output =[]
50
54
  @config[:accounts].each do |account|
@@ -84,7 +88,6 @@ module Tfctl
84
88
 
85
89
  # Retrieves AWS Organizations data and merges it with data from yaml config.
86
90
  def load_config(config_name, yaml_config, aws_org_config)
87
-
88
91
  # AWS Organizations data
89
92
  config = aws_org_config
90
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
@@ -12,11 +12,18 @@ module Tfctl
12
12
  # Execute terraform command
13
13
  def run(account_name:, config_name:, log:, cmd: nil, argv: [], unbuffered: true)
14
14
 
15
+ # Use bin/terraform from a project dir if available
16
+ # Otherwise rely on PATH.
15
17
  if cmd.nil?
16
- # use project terraform binary if available
17
18
  cmd = File.exist?("#{PROJECT_ROOT}/bin/terraform") ? "#{PROJECT_ROOT}/bin/terraform" : 'terraform'
18
19
  end
19
20
 
21
+ # Fail if there are no arguments for terraform and show terraform -help
22
+ if argv.empty?
23
+ help = `#{cmd} -help`.lines.to_a[1..-1].join
24
+ raise Tfctl::Error, "Missing terraform command.\n #{help}"
25
+ end
26
+
20
27
  path = "#{PROJECT_ROOT}/.tfctl/#{config_name}/#{account_name}"
21
28
  cwd = FileUtils.pwd
22
29
  plan_file = "#{path}/tfplan"
@@ -14,17 +14,10 @@ module Tfctl
14
14
  end
15
15
  end
16
16
 
17
- def make(
18
- account_id:,
19
- account_name:,
20
- execution_role:,
21
- profiles:,
22
- config:,
23
- region: 'eu-west-1',
24
- tf_version: '>= 0.12.0',
25
- aws_provider_version: '~> 2.14',
26
- target_dir: "#{PROJECT_ROOT}/.tfctl/#{config[:config_name]}/#{account_name}"
27
- )
17
+ def make(account:, config:)
18
+ target_dir = "#{PROJECT_ROOT}/.tfctl/#{config[:config_name]}/#{account[:name]}"
19
+ tf_version = config.fetch(:tf_required_version, '>= 0.12.0')
20
+ aws_provider_version = config.fetch(:aws_provider_version, '>= 2.14')
28
21
 
29
22
  FileUtils.mkdir_p target_dir
30
23
 
@@ -34,7 +27,7 @@ module Tfctl
34
27
  'backend' => {
35
28
  's3' => {
36
29
  'bucket' => config[:tf_state_bucket],
37
- 'key' => "#{account_name}/tfstate",
30
+ 'key' => "#{account[:name]}/tfstate",
38
31
  'region' => config[:tf_state_region],
39
32
  'role_arn' => config[:tf_state_role_arn],
40
33
  'dynamodb_table' => config[:tf_state_dynamodb_table],
@@ -49,9 +42,9 @@ module Tfctl
49
42
  'provider' => {
50
43
  'aws' => {
51
44
  'version' => aws_provider_version,
52
- 'region' => region,
45
+ 'region' => account[:region],
53
46
  'assume_role' => {
54
- 'role_arn' => "arn:aws:iam::#{account_id}:role/#{execution_role}",
47
+ 'role_arn' => "arn:aws:iam::#{account[:id]}:role/#{account[:tf_execution_role]}",
55
48
  },
56
49
  },
57
50
  },
@@ -74,7 +67,7 @@ module Tfctl
74
67
 
75
68
  FileUtils.rm Dir.glob("#{target_dir}/profile_*.tf.json")
76
69
 
77
- profiles.each do |profile|
70
+ account[:profiles].each do |profile|
78
71
  profile_block = {
79
72
  'module' => {
80
73
  profile => {
@@ -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.1.0'
4
+ VERSION = '1.1.1'
5
5
  end
@@ -25,10 +25,12 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  # Think when adding new dependencies. Is it really necessary?
27
27
  # "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'
28
+ spec.add_dependency 'aws-sdk-organizations', '~> 1.40'
29
+ spec.add_dependency 'json_schemer', '~> 0.2'
30
+ spec.add_dependency 'parallel', '~> 1.19'
30
31
  spec.add_dependency 'terminal-table', '~> 1.8'
31
32
 
32
- spec.add_development_dependency 'rspec', '~> 3.8'
33
- spec.add_development_dependency 'rubocop', '~> 0.76'
33
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
34
+ spec.add_development_dependency 'rspec', '~> 3.9'
35
+ spec.add_development_dependency 'rubocop', '~> 0.84'
34
36
  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.1.0
4
+ version: 1.1.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-09 00:00:00.000000000 Z
11
+ date: 2020-05-28 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
@@ -52,34 +66,48 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '1.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.7'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rspec
57
85
  requirement: !ruby/object:Gem::Requirement
58
86
  requirements:
59
87
  - - "~>"
60
88
  - !ruby/object:Gem::Version
61
- version: '3.8'
89
+ version: '3.9'
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
- version: '3.8'
96
+ version: '3.9'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: rubocop
71
99
  requirement: !ruby/object:Gem::Requirement
72
100
  requirements:
73
101
  - - "~>"
74
102
  - !ruby/object:Gem::Version
75
- version: '0.76'
103
+ version: '0.84'
76
104
  type: :development
77
105
  prerelease: false
78
106
  version_requirements: !ruby/object:Gem::Requirement
79
107
  requirements:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
- version: '0.76'
110
+ version: '0.84'
83
111
  description:
84
112
  email:
85
113
  - akw@scalefactory.com
@@ -94,6 +122,7 @@ files:
94
122
  - ".travis.yml"
95
123
  - CHANGELOG.adoc
96
124
  - Gemfile
125
+ - Guardfile
97
126
  - LICENSE
98
127
  - Makefile
99
128
  - README.adoc
@@ -106,12 +135,12 @@ files:
106
135
  - examples/bootstrap/terraform-exec-role.template
107
136
  - examples/bootstrap/terraform-state.template
108
137
  - examples/bootstrap/tfctl-org-access.template
109
- - examples/control_tower/conf/example.yaml
110
138
  - examples/control_tower/modules/s3-bucket/main.tf
111
139
  - examples/control_tower/modules/s3-bucket/variables.tf
112
140
  - examples/control_tower/profiles/example-profile/data.tf
113
141
  - examples/control_tower/profiles/example-profile/main.tf
114
142
  - examples/control_tower/profiles/example-profile/variables.tf
143
+ - examples/control_tower/tfctl.yaml
115
144
  - lib/hash.rb
116
145
  - lib/tfctl.rb
117
146
  - lib/tfctl/aws_org.rb
@@ -120,6 +149,7 @@ files:
120
149
  - lib/tfctl/executor.rb
121
150
  - lib/tfctl/generator.rb
122
151
  - lib/tfctl/logger.rb
152
+ - lib/tfctl/schema.rb
123
153
  - lib/tfctl/version.rb
124
154
  - tfctl.gemspec
125
155
  homepage: https://github.com/scalefactory/tfctl