kitchen-terraform 0.1.2 → 0.2.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 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