kitchen-terraform 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a27ab58eeb77a3f295cac3a661cb62fa5ef3cd0
4
- data.tar.gz: 6ac7e39c7633a958f5d8cc11de98efbe937bdaa3
3
+ metadata.gz: 125efdc0ccfadc12dbc06c1f2d19f94337b7e514
4
+ data.tar.gz: f63a2992bfcdb73ecf9a60b0d68752b6d1dcc543
5
5
  SHA512:
6
- metadata.gz: e1fa7f9220fde72842c57dd646b0fa0a04dd758fd98c6a21540a53053cde99d50aad13ce0fa9a128eaaf1f1579f1ac3d575b87c0d36d672f9c92623f58a6f25a
7
- data.tar.gz: b8c77cd97d2735d5f8427cd405402a08aa34234859e90faaa0ef1bdcd77f46d3c19f11352e68b6729fbb586f930cb6501020dfd9d2bb323e85321846c729b3b7
6
+ metadata.gz: 680b4c300b78faeb2992aa52b7d915dfa5ec573c77f2fa57e6dbc8d3c44f80ef831d17ee30a377ec4c870246936971e7c6192753c3a472b3bfdf5f6d3c3bf0b0
7
+ data.tar.gz: ade3ff2a56a9ba6febb281de8257006462680d16540c156804128c8b842b669d9cbf772f057f5bd8cb37334f743aede63ed45f292ed937150cfd7b69dda45ce4
@@ -1 +1,3 @@
1
- ���Y���eqW/�ޏvg |l�&C��2����ٜ`��Ut��U;)�)��]�s�8j"q��Zc�=˴<��70���"l����[J)�� x�������H���3~�"-S�%w������+�/�<�7����nAu� `Ԅ�_7<�ǂ��a~ȏ78�j}��3��;D��}I��[DB¹4�ܥ���n"|�P)U�u[g��2 ��:2�1�8j�;�d<Ȳ7�="�$/^X?�o���K
1
+ @�K��R�-�Q��G4�w[6 Lo���1�Ίiy����;>�U�>��x�p|M������5����F�)���,l��ѵ,+��3��ޖ�Ml�"�56
2
+ �_,��*��߇�N����rɌ�pX���G�
3
+ @���L�72��َ��8�j�������V[�{Uoc0�G��h)�����Z�ZK�O��d��r���öل�+�
data.tar.gz.sig CHANGED
@@ -1,3 +1,3 @@
1
- ��(5U������ԯ1�c⠇�����������lP�ž+[
2
- �%�%�lT��"V�ɝ��-#�(��<r)}�|��XCW��o��t���'����&���&$�E%�`�<yH��]�w��
3
- 8@��&��b}o�ptzI�i�HIU�)�x��ȍ9�69�jka�}X�� M������6Nx���}���;�׏��ncV�ϸ�(D[[��e3$�C�;�{�e��C+��(�a�X �
1
+ <z� nZCZ������-0�~�>a*﹈�e8o� v櫘M���h�1�=DԬ��'��X��ø�z�Iu��ZҪ�廂1K{Cˉ�y0�����M@9�W�P:�Z��8s��+6�w�4���{E�u�YrX75ؓ
2
+ I����I����N,��']�=��
3
+ ��& l4��u���?3���:�#��z��J��^�Ӄ�w�& @���9�?Ȏ���z'PLAdR �c8?��<�}�-jpvs8
data/README.md CHANGED
@@ -36,7 +36,8 @@ source 'https://rubygems.org'
36
36
  gem 'kitchen-terraform', '~> 0.1'
37
37
  ```
38
38
 
39
- Before running `bundle`, the author's public key must be added as a trusted certificate:
39
+ Before running `bundle`, the author's public key must be added as a
40
+ trusted certificate:
40
41
 
41
42
  ```sh
42
43
  gem cert --add <(curl --location --silent \
@@ -95,9 +96,7 @@ provisioner.
95
96
 
96
97
  There are no configuration options for the driver.
97
98
 
98
- ##### Example
99
-
100
- *.kitchen.yml*
99
+ ##### Example .kitchen.yml
101
100
 
102
101
  ```yaml
103
102
  ---
@@ -120,6 +119,41 @@ Terraform state based on the provided Terraform configuration.
120
119
 
121
120
  #### Configuration
122
121
 
122
+ ##### apply_timeout
123
+
124
+ The number of seconds to wait for the Terraform `apply` command to be
125
+ successful before raising an error.
126
+
127
+ ###### Example .kitchen.yml
128
+
129
+ ```yaml
130
+ ---
131
+ provisioner:
132
+ name: terraform
133
+ apply_timeout: 1000
134
+ ```
135
+
136
+ ###### Default
137
+
138
+ The default `apply_timeout` is 600 seconds.
139
+
140
+ ##### color
141
+
142
+ Enable or disable colored output from the Terraform command.
143
+
144
+ ###### Example .kitchen.yml
145
+
146
+ ```yaml
147
+ ---
148
+ provisioner:
149
+ name: terraform
150
+ color: false
151
+ ```
152
+
153
+ ###### Default
154
+
155
+ The default value for `color` is true.
156
+
123
157
  ##### directory
124
158
 
125
159
  The pathname of the directory containing the Terraform configuration
@@ -128,9 +162,7 @@ Terraform commands.
128
162
 
129
163
  [directory specified]: https://www.terraform.io/docs/configuration/load.html
130
164
 
131
- ###### Example
132
-
133
- *.kitchen.yml*
165
+ ###### Example .kitchen.yml
134
166
 
135
167
  ```yaml
136
168
  ---
@@ -150,9 +182,7 @@ for the configuration.
150
182
 
151
183
  [Terraform variable files]: https://www.terraform.io/docs/configuration/variables.html#variable-files
152
184
 
153
- ###### Examples
154
-
155
- *.kitchen.yml*
185
+ ###### Example .kitchen.yml
156
186
 
157
187
  ```yaml
158
188
  ---
@@ -173,26 +203,27 @@ The default `variable_files` collection is empty.
173
203
 
174
204
  ##### variables
175
205
 
176
- A collection of [Terraform variables] to be set in the configuration;
177
- the syntax matches that of [assigning variables] with command-line
178
- flags.
206
+ A mapping of [Terraform variables] to be set in the configuration.
179
207
 
180
208
  [Terraform variables]: https://www.terraform.io/docs/configuration/variables.html
181
209
 
182
- [assigning variables]: https://www.terraform.io/intro/getting-started/variables.html#assigning-variables
183
-
184
- ###### Examples
185
-
186
- *.kitchen.yml*
210
+ ###### Example .kitchen.yml
187
211
 
188
212
  ```yaml
189
213
  ---
214
+ provisioner:
215
+ name: terraform
216
+ variables:
217
+ foo: bar
218
+ # deprecated
219
+ ---
190
220
  provisioner:
191
221
  name: terraform
192
222
  variables:
193
223
  - foo=bar
194
224
  - biz=baz
195
225
  ---
226
+ # deprecated
196
227
  provisioner:
197
228
  name: terraform
198
229
  variables: foo=bar
@@ -250,9 +281,7 @@ Each group consists of:
250
281
 
251
282
  - the username to use when connecting to the group's hosts
252
283
 
253
- ###### Example
254
-
255
- *.kitchen.yml*
284
+ ###### Example .kitchen.yml
256
285
 
257
286
  ```yaml
258
287
  ---
@@ -15,15 +15,14 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require 'kitchen'
18
- require 'terraform/client_holder'
19
- require 'terraform/invalid_version'
18
+ require 'terraform/configurable'
20
19
  require 'terraform/version'
21
20
 
22
21
  module Kitchen
23
22
  module Driver
24
23
  # Terraform state lifecycle activities manager
25
24
  class Terraform < Base
26
- include ::Terraform::ClientHolder
25
+ include ::Terraform::Configurable
27
26
 
28
27
  kitchen_driver_api_version 2
29
28
 
@@ -32,25 +31,14 @@ module Kitchen
32
31
  no_parallel_for
33
32
 
34
33
  def create(_state = nil)
35
- client.fetch_version do |output|
36
- raise ::Terraform::InvalidVersion, supported_version, caller unless
37
- output.match supported_version
38
- end
39
- rescue => error
40
- raise Kitchen::ActionFailed, error.message, error.backtrace
34
+ provisioner.validate_version
41
35
  end
42
36
 
43
37
  def destroy(_state = nil)
44
- client.validate_configuration_files
45
- client.download_modules
46
- client.plan_destructive_execution
47
- client.apply_execution_plan
48
- rescue => error
49
- raise Kitchen::ActionFailed, error.message, error.backtrace
50
- end
51
-
52
- def supported_version
53
- 'v0.6'
38
+ provisioner.validate_configuration_files
39
+ provisioner.download_modules
40
+ provisioner.plan_destructive_execution
41
+ provisioner.apply_execution_plan
54
42
  end
55
43
  end
56
44
  end
@@ -15,43 +15,166 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require 'kitchen'
18
- require 'pathname'
19
- require 'terraform/client_holder'
18
+ require 'terraform/apply_command'
19
+ require 'terraform/configurable'
20
+ require 'terraform/get_command'
21
+ require 'terraform/group'
22
+ require 'terraform/output_command'
23
+ require 'terraform/plan_command'
24
+ require 'terraform/validate_command'
20
25
  require 'terraform/version'
26
+ require 'terraform/version_command'
21
27
 
22
28
  module Kitchen
23
29
  module Provisioner
24
30
  # Terraform configuration applier
25
31
  class Terraform < Base
26
- include ::Terraform::ClientHolder
32
+ SUPPORTED_VERSION = /v0.6/
33
+
34
+ include ::Terraform::Configurable
27
35
 
28
36
  kitchen_provisioner_api_version 2
29
37
 
30
38
  plugin_version ::Terraform::VERSION
31
39
 
40
+ required_config :apply_timeout do |_, value, provisioner|
41
+ provisioner.coerce_apply_timeout value: value
42
+ end
43
+
44
+ default_config :apply_timeout, 600
45
+
46
+ default_config :color, true
47
+
48
+ required_config :color do |_, value, provisioner|
49
+ provisioner.coerce_color value: value
50
+ end
51
+
52
+ default_config(:directory) { |provisioner| provisioner[:kitchen_root] }
53
+
54
+ expand_path_for :directory
55
+
56
+ default_config :plan do |provisioner|
57
+ provisioner.instance_pathname filename: 'terraform.tfplan'
58
+ end
59
+
60
+ expand_path_for :plan
61
+
62
+ default_config :state do |provisioner|
63
+ provisioner.instance_pathname filename: 'terraform.tfstate'
64
+ end
65
+
66
+ expand_path_for :state
67
+
68
+ required_config :variable_files do |_, value, provisioner|
69
+ provisioner.coerce_variable_files value: value
70
+ end
71
+
72
+ default_config :variable_files, []
73
+
74
+ expand_path_for :variable_files
75
+
76
+ required_config :variables do |_, value, provisioner|
77
+ provisioner.coerce_variables value: value
78
+ end
79
+
80
+ default_config :variables, {}
81
+
82
+ def apply_execution_plan
83
+ ::Terraform::ApplyCommand.execute \
84
+ logger: logger, state: config[:state], target: config[:plan],
85
+ color: config[:color], timeout: config[:apply_timeout]
86
+ end
87
+
32
88
  def call(_state = nil)
33
- client.validate_configuration_files
34
- client.download_modules
35
- client.plan_execution
36
- client.apply_execution_plan
37
- rescue => error
38
- raise Kitchen::ActionFailed, error.message, error.backtrace
89
+ validate_configuration_files
90
+ download_modules
91
+ plan_constructive_execution
92
+ apply_execution_plan
93
+ end
94
+
95
+ def coerce_apply_timeout(value:)
96
+ config[:apply_timeout] = Integer value
97
+ rescue ArgumentError, TypeError
98
+ config_error attribute: 'apply_timeout', expected: 'an integer'
99
+ end
100
+
101
+ def coerce_color(value:)
102
+ raise TypeError unless [TrueClass, FalseClass].include?(value.class)
103
+ config[:color] = value
104
+ rescue TypeError
105
+ config_error attribute: 'color', expected: 'a boolean'
39
106
  end
40
107
 
41
- def directory
42
- config.fetch(:directory) { kitchen_root }
108
+ def coerce_variable_files(value:)
109
+ config[:variable_files] = Array value
43
110
  end
44
111
 
45
- def kitchen_root
46
- Pathname.new config.fetch :kitchen_root
112
+ def coerce_variables(value:)
113
+ config[:variables] =
114
+ if value.is_a?(Array) || value.is_a?(String)
115
+ deprecated_variables_format value: value
116
+ else
117
+ Hash value
118
+ end
119
+ rescue ArgumentError, TypeError
120
+ config_error attribute: 'variables',
121
+ expected: 'a mapping of Terraform variable assignments'
47
122
  end
48
123
 
49
- def variable_files
50
- config.fetch(:variable_files) { [] }
124
+ def download_modules
125
+ ::Terraform::GetCommand.execute logger: logger,
126
+ target: config[:directory]
51
127
  end
52
128
 
53
- def variables
54
- config.fetch(:variables) { [] }
129
+ def each_list_output(name:, &block)
130
+ output(name: name).split(',').each(&block)
131
+ end
132
+
133
+ def instance_pathname(filename:)
134
+ File.join config[:kitchen_root], '.kitchen', 'kitchen-terraform',
135
+ instance.name, filename
136
+ end
137
+
138
+ def output(name:)
139
+ ::Terraform::OutputCommand
140
+ .execute logger: logger, state: config[:state], target: name, &:chomp
141
+ end
142
+
143
+ def plan_constructive_execution
144
+ ::Terraform::PlanCommand
145
+ .execute destroy: false, logger: logger, out: config[:plan],
146
+ state: config[:state], target: config[:directory],
147
+ variables: config[:variables], color: config[:color],
148
+ variable_files: config[:variable_files]
149
+ end
150
+
151
+ def plan_destructive_execution
152
+ ::Terraform::PlanCommand
153
+ .execute destroy: true, logger: logger, out: config[:plan],
154
+ state: config[:state], target: config[:directory],
155
+ variables: config[:variables], color: config[:color],
156
+ variable_files: config[:variable_files]
157
+ end
158
+
159
+ def validate_configuration_files
160
+ ::Terraform::ValidateCommand.execute logger: logger,
161
+ target: config[:directory]
162
+ end
163
+
164
+ def validate_version
165
+ ::Terraform::VersionCommand.execute logger: logger do |output|
166
+ raise UserError,
167
+ "Terraform version must match #{SUPPORTED_VERSION}" unless
168
+ SUPPORTED_VERSION.match output
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ def deprecated_variables_format(value:)
175
+ config_deprecated attribute: 'variables',
176
+ expected: 'a mapping rather than a list or string'
177
+ Hash[Array(value).map { |string| string.split '=' }]
55
178
  end
56
179
  end
57
180
  end
@@ -16,8 +16,7 @@
16
16
 
17
17
  require 'kitchen'
18
18
  require 'kitchen/verifier/inspec'
19
- require 'terraform/client_holder'
20
- require 'terraform/inspec_runner'
19
+ require 'terraform/configurable'
21
20
  require 'terraform/version'
22
21
 
23
22
  module Kitchen
@@ -25,84 +24,40 @@ module Kitchen
25
24
  # Runs tests post-converge to confirm that instances in the Terraform state
26
25
  # are in an expected state
27
26
  class Terraform < Inspec
28
- include ::Terraform::ClientHolder
27
+ include ::Terraform::Configurable
29
28
 
30
29
  kitchen_verifier_api_version 2
31
30
 
32
31
  plugin_version ::Terraform::VERSION
33
32
 
34
- def attributes(group:)
35
- group.fetch(:attributes) { {} }
33
+ required_config :groups do |_, value, verifier|
34
+ verifier.coerce_groups value: value
36
35
  end
37
36
 
38
- def call(state)
39
- groups.each do |group|
40
- client.extract_list_output name: group.fetch(:hostnames) do |output|
41
- verify group: group, hostnames: output, state: state
42
- end
43
- end
44
- rescue => error
45
- raise ActionFailed, error.message, error.backtrace
46
- end
47
-
48
- def controls(group:)
49
- group.fetch(:controls) { [] }
50
- end
51
-
52
- def evaluate(exit_code:)
53
- raise "Inspec Runner returns #{exit_code}" unless exit_code.zero?
54
- end
55
-
56
- def groups
57
- config.fetch(:groups) { [] }
58
- end
37
+ default_config :groups, []
59
38
 
60
- def initialize_runner(group:, hostname:, state:)
61
- ::Terraform::InspecRunner
62
- .new runner_options_for_terraform group: group, hostname: hostname,
63
- state: state do |runner|
64
- resolve_attributes group: group do |name, value|
65
- runner.define_attribute name: name, value: value
66
- end
67
- runner.add targets: collect_tests
68
- yield runner
39
+ def call(state)
40
+ config[:groups].each do |group|
41
+ group.verify_each_host options: runner_options(transport, state)
69
42
  end
70
43
  end
71
44
 
72
- def port(group:)
73
- # FIXME: apply the principle of least knowledge
74
- group.fetch(:port) { instance.transport.send(:config).fetch :port }
75
- end
76
-
77
- def resolve_attributes(group:)
78
- attributes(group: group).each_pair do |method_name, variable_name|
79
- client.extract_output name: variable_name do |output|
80
- yield method_name, output
81
- end
45
+ def coerce_groups(value:)
46
+ config[:groups] = Array(value).map do |raw_group|
47
+ ::Terraform::Group.new value: raw_group, verifier: self
82
48
  end
49
+ rescue UserError
50
+ config_error attribute: 'groups',
51
+ expected: 'a collection of group mappings'
83
52
  end
84
53
 
85
- def runner_options_for_terraform(group:, hostname:, state:)
86
- runner_options(instance.transport, state)
87
- .merge controls: controls(group: group), host: hostname,
88
- port: port(group: group), user: username(group: group)
89
- end
90
-
91
- def username(group:)
92
- # FIXME: apply the principle of least knowledge
93
- group.fetch :username do
94
- instance.transport.send(:config).fetch :username
95
- end
54
+ def evaluate(exit_code:)
55
+ raise InstanceFailure, "Inspec Runner returns #{exit_code}" unless
56
+ exit_code.zero?
96
57
  end
97
58
 
98
- def verify(group:, hostnames:, state:)
99
- hostnames.each do |hostname|
100
- info "Verifying group: #{group.fetch :name}; hostname #{hostname}\n"
101
- initialize_runner group: group, hostname: hostname,
102
- state: state do |runner|
103
- runner.verify_run verifier: self
104
- end
105
- end
59
+ def populate(runner:)
60
+ collect_tests.each { |test| runner.add target: test }
106
61
  end
107
62
  end
108
63
  end
@@ -15,18 +15,28 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require_relative 'command'
18
+ require_relative 'color_switch'
18
19
 
19
20
  module Terraform
20
21
  # Command to apply an execution plan
21
- class ApplyCommand
22
- include Command
22
+ class ApplyCommand < Command
23
+ include ColorSwitch
24
+
25
+ def name
26
+ 'apply'
27
+ end
28
+
29
+ def options
30
+ "-input=false -state=#{state}#{color_switch}"
31
+ end
23
32
 
24
33
  private
25
34
 
26
- def initialize_attributes(state:, plan:)
27
- self.name = 'apply'
28
- self.options = { input: false, state: state }
29
- self.target = plan
35
+ attr_accessor :color, :state
36
+
37
+ def initialize_attributes(color:, state:)
38
+ self.color = color
39
+ self.state = state
30
40
  end
31
41
  end
32
42
  end
@@ -14,10 +14,11 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- require_relative 'error'
18
-
19
17
  module Terraform
20
- # Error of an output not found
21
- class OutputNotFound < Error
18
+ # Shared color switche for Terraform
19
+ module ColorSwitch
20
+ def color_switch
21
+ color ? '' : ' -no-color'
22
+ end
22
23
  end
23
24
  end
@@ -14,45 +14,54 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ require 'kitchen'
17
18
  require 'mixlib/shellout'
18
- require 'pathname'
19
- require_relative 'command_options'
20
- require_relative 'error'
21
19
 
22
20
  module Terraform
23
- # Common logic for Mixlib::ShellOut Terraform commands
24
- module Command
25
- attr_reader :name, :options, :target
21
+ # Interface to the Terraform command line client
22
+ class Command
23
+ def self.execute(**keyword_arguments, &block)
24
+ new(**keyword_arguments).execute(&block)
25
+ end
26
26
 
27
27
  def execute
28
- # TODO: use the live output stream
29
28
  shell_out.run_command
30
29
  shell_out.error!
31
30
  yield shell_out.stdout if block_given?
32
- rescue => error
33
- handle error: error
34
- raise Error, error.message, error.backtrace
31
+ rescue Errno::EACCES, Errno::ENOENT => error
32
+ command_error error: error, type: Kitchen::InstanceFailure
33
+ rescue Mixlib::ShellOut::CommandTimeout,
34
+ Mixlib::ShellOut::ShellCommandFailed => error
35
+ command_error error: error, type: Kitchen::TransientFailure
35
36
  end
36
37
 
37
- def handle(**_)
38
+ def name
39
+ ''
38
40
  end
39
41
 
40
- def to_s
41
- CommandOptions.new options do |command_options|
42
- return "terraform #{name} #{command_options} #{target}"
43
- end
42
+ def options
43
+ '--help'
44
44
  end
45
45
 
46
46
  private
47
47
 
48
48
  attr_accessor :shell_out
49
49
 
50
- attr_writer :name, :options, :target
50
+ def command_error(error:, type:)
51
+ raise type, %(`#{shell_out.command}` failed: "#{error}")
52
+ end
51
53
 
52
- def initialize(**keyword_arguments)
54
+ def initialize(
55
+ logger:, target: '', timeout: Mixlib::ShellOut::DEFAULT_READ_TIMEOUT,
56
+ **keyword_arguments
57
+ )
53
58
  initialize_attributes(**keyword_arguments)
54
- self.shell_out = Mixlib::ShellOut.new to_s, returns: 0
55
- yield self if block_given?
59
+ self.shell_out = Mixlib::ShellOut
60
+ .new "terraform #{name} #{options} #{target}",
61
+ live_stream: logger, returns: 0, timeout: timeout
62
+ end
63
+
64
+ def initialize_attributes(**_keyword_arguments)
56
65
  end
57
66
  end
58
67
  end
@@ -14,32 +14,32 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- module Terraform
18
- # Manages options for Terraform commands
19
- class CommandOptions
20
- def to_s
21
- key_flags.each_with_object String.new('') do |(flag, values), string|
22
- values.each { |value| string.concat "#{flag}=#{value} " }
23
- end.chomp ' '
24
- end
17
+ require 'forwardable'
18
+ require 'kitchen'
25
19
 
26
- private
20
+ module Terraform
21
+ # Common logic for classes that include Kitchen::Configurable
22
+ module Configurable
23
+ extend Forwardable
27
24
 
28
- attr_accessor :options
25
+ def_delegators :instance, :provisioner, :transport
29
26
 
30
- def key_flags
31
- options
32
- .map { |key, value| [key.to_s.tr('_', '-').prepend('-'), value] }.to_h
27
+ def config_deprecated(attribute:, expected:)
28
+ logger.warn 'DEPRECATION NOTICE'
29
+ logger.warn formatted attribute: attribute,
30
+ message: "should be #{expected}"
33
31
  end
34
32
 
35
- def initialize(**options)
36
- self.options = options
37
- normalize_values
38
- yield self if block_given?
33
+ def config_error(attribute:, expected:)
34
+ raise Kitchen::UserError, formatted(
35
+ attribute: attribute, message: "must be interpretable as #{expected}"
36
+ )
39
37
  end
40
38
 
41
- def normalize_values
42
- options.each_pair { |key, value| options.store key, Array(value) }
39
+ private
40
+
41
+ def formatted(attribute:, message:)
42
+ "#{self.class}#{instance.to_str}#config[:#{attribute}] #{message}"
43
43
  end
44
44
  end
45
45
  end
@@ -18,15 +18,13 @@ require_relative 'command'
18
18
 
19
19
  module Terraform
20
20
  # Command to get modules
21
- class GetCommand
22
- include Command
23
-
24
- private
21
+ class GetCommand < Command
22
+ def name
23
+ 'get'
24
+ end
25
25
 
26
- def initialize_attributes(dir:)
27
- self.name = 'get'
28
- self.options = { update: true }
29
- self.target = dir
26
+ def options
27
+ '-update=true'
30
28
  end
31
29
  end
32
30
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2016 New Context Services, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'delegate'
18
+
19
+ module Terraform
20
+ # Group to be verified
21
+ class Group < DelegateClass Hash
22
+ def populate(runner:)
23
+ dig(:attributes).each_pair do |key, output_name|
24
+ runner.set_attribute key: key,
25
+ value: provisioner.output(name: output_name)
26
+ end
27
+ end
28
+
29
+ def verify_each_host(options:)
30
+ require 'terraform/inspec_runner'
31
+ provisioner.each_list_output name: dig(:hostnames) do |hostname|
32
+ store :host, hostname
33
+ verifier.info "Verifying group: #{dig :name}; current host #{hostname}"
34
+ InspecRunner.run_and_verify group: self, options: options.merge(self),
35
+ verifier: verifier
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_accessor :provisioner, :transport, :verifier
42
+
43
+ def coerce_attributes
44
+ store :attributes, Hash(dig(:attributes))
45
+ rescue ArgumentError, TypeError
46
+ verifier.config_error attribute: "groups][#{self}][:attributes",
47
+ expected: 'a mapping of Inspec attribute names ' \
48
+ 'to Terraform output variable names'
49
+ end
50
+
51
+ def coerce_controls
52
+ store :controls, Array(dig(:controls))
53
+ end
54
+
55
+ def coerce_hostnames
56
+ store :hostnames, String(dig(:hostnames))
57
+ end
58
+
59
+ def coerce_name
60
+ store :name, String(dig(:name))
61
+ end
62
+
63
+ def coerce_parameters
64
+ coerce_attributes
65
+ coerce_controls
66
+ coerce_hostnames
67
+ coerce_name
68
+ coerce_port
69
+ coerce_username
70
+ end
71
+
72
+ def coerce_port
73
+ store :port, Integer(dig(:port) || transport[:port])
74
+ rescue ArgumentError, TypeError
75
+ verifier.config_error attribute: "groups][#{self}][:port",
76
+ expected: 'an integer'
77
+ end
78
+
79
+ def coerce_username
80
+ store :user, String(dig(:username) || transport[:username])
81
+ end
82
+
83
+ def initialize(value:, verifier:)
84
+ super Hash value
85
+ self.provisioner = verifier.provisioner
86
+ self.transport = verifier.transport
87
+ self.verifier = verifier
88
+ coerce_parameters
89
+ rescue ArgumentError, TypeError
90
+ verifier.config_error attribute: "groups][#{self}",
91
+ expected: 'a group mapping'
92
+ end
93
+ end
94
+ end
@@ -22,23 +22,27 @@ module Terraform
22
22
  class InspecRunner < Inspec::Runner
23
23
  attr_reader :conf
24
24
 
25
- def add(targets:)
26
- targets.each { |target| add_target target, conf }
25
+ def self.run_and_verify(group:, options:, verifier:)
26
+ new(options).tap do |runner|
27
+ group.populate runner: runner
28
+ verifier.populate runner: runner
29
+ verifier.evaluate exit_code: runner.run
30
+ end
27
31
  end
28
32
 
29
- def define_attribute(name:, value:)
30
- conf.fetch('attributes').store name.to_s, value
33
+ def add(target:)
34
+ add_target target, conf
31
35
  end
32
36
 
33
- def verify_run(verifier:)
34
- verifier.evaluate exit_code: run
37
+ def set_attribute(key:, value:)
38
+ conf[:attributes].store key.to_s, value
35
39
  end
36
40
 
37
41
  private
38
42
 
39
43
  def initialize(conf = {})
44
+ conf.store :attributes, conf.fetch(:attributes, {}).clone
40
45
  super
41
- yield self if block_given?
42
46
  end
43
47
  end
44
48
  end
@@ -15,24 +15,24 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require_relative 'command'
18
- require_relative 'output_not_found'
19
18
 
20
19
  module Terraform
21
20
  # Command to extract values of output variables
22
- class OutputCommand
23
- include Command
21
+ class OutputCommand < Command
22
+ def name
23
+ 'output'
24
+ end
24
25
 
25
- def handle(error:)
26
- raise OutputNotFound, error.message, error.backtrace if
27
- error.message =~ /no(?:thing to)? output/
26
+ def options
27
+ "-state=#{state}"
28
28
  end
29
29
 
30
30
  private
31
31
 
32
- def initialize_attributes(state:, name:)
33
- self.name = 'output'
34
- self.options = { state: state }
35
- self.target = name
32
+ attr_accessor :state
33
+
34
+ def initialize_attributes(state:)
35
+ self.state = state
36
36
  end
37
37
  end
38
38
  end
@@ -15,21 +15,48 @@
15
15
  # limitations under the License.
16
16
 
17
17
  require_relative 'command'
18
+ require_relative 'color_switch'
18
19
 
19
20
  module Terraform
20
21
  # Command to plan an execution
21
- class PlanCommand
22
- include Command
22
+ class PlanCommand < Command
23
+ include ColorSwitch
24
+
25
+ def name
26
+ 'plan'
27
+ end
28
+
29
+ def options
30
+ "-destroy=#{destroy} -input=false -out=#{out} -state=#{state}" \
31
+ "#{color_switch}" \
32
+ "#{processed_variables}#{processed_variable_files}"
33
+ end
23
34
 
24
35
  private
25
36
 
26
- def initialize_attributes(destroy:, out:, state:, var:, var_file:, dir:)
27
- self.name = 'plan'
28
- self.options = {
29
- destroy: destroy, input: false, out: out, state: state, var: var,
30
- var_file: var_file
31
- }
32
- self.target = dir
37
+ attr_accessor :color, :destroy, :out, :state, :variables, :variable_files
38
+
39
+ def initialize_attributes(
40
+ color:, destroy:, out:, state:, variables:, variable_files:
41
+ )
42
+ self.color = color
43
+ self.destroy = destroy
44
+ self.out = out
45
+ self.state = state
46
+ self.variables = variables
47
+ self.variable_files = variable_files
48
+ end
49
+
50
+ def processed_variable_files
51
+ variable_files.each_with_object String.new do |pathname, string|
52
+ string.concat " -var-file=#{pathname}"
53
+ end
54
+ end
55
+
56
+ def processed_variables
57
+ variables.each_with_object String.new do |(key, value), string|
58
+ string.concat " -var='#{key}=#{value}'"
59
+ end
33
60
  end
34
61
  end
35
62
  end
@@ -18,15 +18,9 @@ require_relative 'command'
18
18
 
19
19
  module Terraform
20
20
  # Command to valdidate configuration files
21
- class ValidateCommand
22
- include Command
23
-
24
- private
25
-
26
- def initialize_attributes(dir:)
27
- self.name = 'validate'
28
- self.options = {}
29
- self.target = dir
21
+ class ValidateCommand < Command
22
+ def name
23
+ 'validate'
30
24
  end
31
25
  end
32
26
  end
@@ -15,5 +15,5 @@
15
15
  # limitations under the License.
16
16
 
17
17
  module Terraform
18
- VERSION = '0.1.2'
18
+ VERSION = '0.2.0'
19
19
  end
@@ -18,15 +18,9 @@ require_relative 'command'
18
18
 
19
19
  module Terraform
20
20
  # Command to obtain the version
21
- class VersionCommand
22
- include Command
23
-
24
- private
25
-
26
- def initialize_attributes(**_)
27
- self.name = 'version'
28
- self.options = {}
29
- self.target = ''
21
+ class VersionCommand < Command
22
+ def name
23
+ 'version'
30
24
  end
31
25
  end
32
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-terraform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Lane
@@ -32,7 +32,7 @@ cert_chain:
32
32
  KucGZX3OeACVUSpmCOCQyma6sDHYWQZM/IgWi7tbLtG5b2GslkauPm4S3wadHi6W
33
33
  LOU=
34
34
  -----END CERTIFICATE-----
35
- date: 2016-08-04 00:00:00.000000000 Z
35
+ date: 2016-09-12 00:00:00.000000000 Z
36
36
  dependencies:
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: bundler-audit
@@ -194,6 +194,20 @@ dependencies:
194
194
  - - ">="
195
195
  - !ruby/object:Gem::Version
196
196
  version: 0.11.2
197
+ - !ruby/object:Gem::Dependency
198
+ name: inspec
199
+ requirement: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - "~>"
202
+ - !ruby/object:Gem::Version
203
+ version: 0.33.2
204
+ type: :runtime
205
+ prerelease: false
206
+ version_requirements: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - "~>"
209
+ - !ruby/object:Gem::Version
210
+ version: 0.33.2
197
211
  - !ruby/object:Gem::Dependency
198
212
  name: kitchen-inspec
199
213
  requirement: !ruby/object:Gem::Requirement
@@ -255,7 +269,7 @@ dependencies:
255
269
  - !ruby/object:Gem::Version
256
270
  version: 1.10.0
257
271
  description:
258
- email: aaron.lane@newcontext.com
272
+ email: kitchen-terraform@newcontext.com
259
273
  executables: []
260
274
  extensions: []
261
275
  extra_rdoc_files: []
@@ -266,16 +280,13 @@ files:
266
280
  - lib/kitchen/provisioner/terraform.rb
267
281
  - lib/kitchen/verifier/terraform.rb
268
282
  - lib/terraform/apply_command.rb
269
- - lib/terraform/client.rb
270
- - lib/terraform/client_holder.rb
283
+ - lib/terraform/color_switch.rb
271
284
  - lib/terraform/command.rb
272
- - lib/terraform/command_options.rb
273
- - lib/terraform/error.rb
285
+ - lib/terraform/configurable.rb
274
286
  - lib/terraform/get_command.rb
287
+ - lib/terraform/group.rb
275
288
  - lib/terraform/inspec_runner.rb
276
- - lib/terraform/invalid_version.rb
277
289
  - lib/terraform/output_command.rb
278
- - lib/terraform/output_not_found.rb
279
290
  - lib/terraform/plan_command.rb
280
291
  - lib/terraform/validate_command.rb
281
292
  - lib/terraform/version.rb
metadata.gz.sig CHANGED
Binary file
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2016 New Context Services, Inc.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require_relative 'apply_command'
18
- require_relative 'get_command'
19
- require_relative 'output_command'
20
- require_relative 'plan_command'
21
- require_relative 'validate_command'
22
- require_relative 'version_command'
23
-
24
- module Terraform
25
- # Runs Mixlib Terraform Command instances
26
- class Client
27
- extend Forwardable
28
-
29
- def_delegators :provisioner, :directory, :info, :kitchen_root,
30
- :variable_files, :variables
31
-
32
- def apply_execution_plan
33
- run command_class: ApplyCommand, state: state_pathname,
34
- plan: plan_pathname
35
- end
36
-
37
- def download_modules
38
- run command_class: GetCommand, dir: directory
39
- end
40
-
41
- def extract_list_output(name:)
42
- extract_output(name: name) { |output| yield output.split ',' }
43
- end
44
-
45
- def extract_output(name:)
46
- run(
47
- command_class: OutputCommand, state: state_pathname, name: name
48
- ) { |output| yield output.chomp }
49
- end
50
-
51
- def fetch_version
52
- run(command_class: VersionCommand) { |output| yield output }
53
- end
54
-
55
- def instance_directory
56
- kitchen_root.join '.kitchen', 'kitchen-terraform', instance_name
57
- end
58
-
59
- def plan_destructive_execution
60
- run command_class: PlanCommand, destroy: true, out: plan_pathname,
61
- state: state_pathname, var: variables, var_file: variable_files,
62
- dir: directory
63
- end
64
-
65
- def plan_execution
66
- run command_class: PlanCommand, destroy: false, out: plan_pathname,
67
- state: state_pathname, var: variables, var_file: variable_files,
68
- dir: directory
69
- end
70
-
71
- def plan_pathname
72
- instance_directory.join 'terraform.tfplan'
73
- end
74
-
75
- def run(command_class:, **parameters)
76
- command_class.new(**parameters) do |command|
77
- info command
78
- command.execute do |output|
79
- info output
80
- yield output if block_given?
81
- end
82
- end
83
- end
84
-
85
- def state_pathname
86
- instance_directory.join 'terraform.tfstate'
87
- end
88
-
89
- def validate_configuration_files
90
- run command_class: ValidateCommand, dir: directory
91
- end
92
-
93
- private
94
-
95
- attr_accessor :instance_name, :provisioner
96
-
97
- def initialize(instance:)
98
- self.instance_name = instance.name
99
- self.provisioner = instance.provisioner
100
- end
101
- end
102
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2016 New Context Services, Inc.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require_relative 'client'
18
-
19
- module Terraform
20
- # Logic to provide a lazily initialized Client instance
21
- module ClientHolder
22
- def client
23
- @client ||= Client.new instance: instance
24
- end
25
- end
26
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2016 New Context Services, Inc.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- module Terraform
18
- class Error < StandardError
19
- end
20
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2016 New Context Services, Inc.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require_relative 'error'
18
-
19
- module Terraform
20
- # Error of an invalid Terraform version
21
- class InvalidVersion < Error
22
- def message
23
- "Terraform version must match #{supported_version}"
24
- end
25
-
26
- private
27
-
28
- attr_accessor :supported_version
29
-
30
- def initialize(supported_version)
31
- self.supported_version = supported_version
32
- end
33
- end
34
- end