Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Commit

Permalink
Load Terraform outputs in kitchen verify (#297)
Browse files Browse the repository at this point in the history
* Refactor spec for `terraform output`

* Extract outputs logic from apply logic

* Retrieve Terraform outputs in verify action

* Update version to 4.0.6

* Update Change Log

* Update bundles
  • Loading branch information
aaron-lane authored Dec 3, 2018
1 parent 1a601aa commit 1f3625d
Show file tree
Hide file tree
Showing 14 changed files with 243 additions and 286 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased][unreleased]

## [4.0.6] - 2018-12-02

### Fixed

- `terraform output` is moved from `kitchen converge` to
`kitchen verify` to ensure Terraform state outputs are up to date for
use as InSpec attributes regardless of the result of
`kitchen converge`.

## [4.0.5] - 2018-12-01

### Fixed
Expand Down Expand Up @@ -548,7 +557,8 @@ Gandalf the Free-As-In-Beer

- Initial release

[unreleased]: https://github.com/newcontext/kitchen-terraform/compare/v4.0.5...HEAD
[unreleased]: https://github.com/newcontext/kitchen-terraform/compare/v4.0.6...HEAD
[4.0.6]: https://github.com/newcontext/kitchen-terraform/compare/v4.0.5...v4.0.6
[4.0.5]: https://github.com/newcontext/kitchen-terraform/compare/v4.0.4...v4.0.5
[4.0.4]: https://github.com/newcontext/kitchen-terraform/compare/v4.0.3...v4.0.4
[4.0.3]: https://github.com/newcontext/kitchen-terraform/compare/v4.0.2...v4.0.3
Expand Down
2 changes: 1 addition & 1 deletion Gemfile-Ruby-2_3.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
kitchen-terraform (4.0.5)
kitchen-terraform (4.0.6)
dry-types (~> 0.9)
dry-validation (~> 0.10)
inspec (~> 2.2, != 2.3.5, != 2.3.4, != 2.2.112, != 2.2.102, != 2.2.101)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile-Ruby-2_4.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
kitchen-terraform (4.0.5)
kitchen-terraform (4.0.6)
dry-types (~> 0.9)
dry-validation (~> 0.10)
inspec (~> 2.2, != 2.3.5, != 2.3.4, != 2.2.112, != 2.2.102, != 2.2.101)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
kitchen-terraform (4.0.5)
kitchen-terraform (4.0.6)
dry-types (~> 0.9)
dry-validation (~> 0.10)
inspec (~> 2.2, != 2.3.5, != 2.3.4, != 2.2.112, != 2.2.102, != 2.2.101)
Expand Down
24 changes: 14 additions & 10 deletions lib/kitchen/driver/terraform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,24 +212,15 @@ class ::Kitchen::Driver::Terraform < ::Kitchen::Driver::Base
include ::Kitchen::Terraform::Configurable

# Applies changes to the state by selecting the test workspace, updating the dependency modules, validating the root
# module, applying the state changes, and retrieving the state output.
# module, and applying the state changes.
#
# @raise [::Kitchen::Terraform::Error] if one of the steps fails.
# @return [void]
# @yieldparam output [::String] the state output.
def apply(&block)
run_workspace_select_instance
apply_run_get
apply_run_validate
apply_run_apply
::Kitchen::Terraform::Command::Output.run(
options: {
cwd: config_root_module_directory,
live_stream: logger,
timeout: config_command_timeout,
},
&block
)
end

# Creates a Test Kitchen instance by initializing the working directory and creating a test workspace.
Expand Down Expand Up @@ -266,6 +257,19 @@ def destroy(_state)
)
end

# Retrieves the Terraform state outputs.
#
# @raise [::Kitchen::Terraform::Error] if the retrieval fails.
# @return [void]
# @yieldparam output [::String] the state output.
def retrieve_outputs(&block)
::Kitchen::Terraform::Command::Output.run(
options: {
cwd: config_root_module_directory, live_stream: logger, timeout: config_command_timeout,
}, &block
)
end

# Verifies that the Terraform version available to the driver is supported.
#
# @raise [::Kitchen::UserError] if the version is not supported.
Expand Down
18 changes: 3 additions & 15 deletions lib/kitchen/provisioner/terraform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ module ::Kitchen::Provisioner
# [-var-file=<variable_files.first>...] \
# <directory>
#
# ===== Retrieving the Terraform Output
#
# terraform output -json
#
# === Configuration Attributes
#
# The provisioner has no configuration attributes, but the +provisioner+ mapping must be declared with the plugin name
Expand All @@ -90,18 +86,10 @@ class ::Kitchen::Provisioner::Terraform < ::Kitchen::Provisioner::Base

# Converges a Test Kitchen instance.
#
# @param state [::Hash] the mutable instance and provisioner state.
# @param _state [::Hash] the mutable instance and provisioner state.
# @raise [::Kitchen::ActionFailed] if the result of the action is a failure.
def call(state)
instance
.driver
.apply do |output:|
state
.store(
:kitchen_terraform_outputs,
output
)
end
def call(_state)
instance.driver.apply
rescue ::Kitchen::Terraform::Error => error
raise(
::Kitchen::ActionFailed,
Expand Down
5 changes: 3 additions & 2 deletions lib/kitchen/terraform/command/output.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# limitations under the License.

require "json"
require "kitchen"
require "kitchen/terraform/command"
require "kitchen/terraform/error"
require "kitchen/terraform/shell_out"
Expand Down Expand Up @@ -57,7 +58,7 @@ def handle_json_parser(error:)
# @api private
def handle_kitchen_terraform(error:)
/no\\ outputs\\ defined/.match ::Regexp.escape error.to_s or raise error
yield output: {}
yield outputs: {}
end

# @api private
Expand All @@ -67,7 +68,7 @@ def run_shell_out(options:)
command: "output -json",
options: options,
) do |standard_output:|
yield output: ::JSON.parse(standard_output)
yield outputs: ::JSON.parse(standard_output)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/kitchen/terraform/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def temporarily_override(version:)

# @api private
def value
self.value = ::Gem::Version.new "4.0.5" if not @value
self.value = ::Gem::Version.new "4.0.6" if not @value
@value
end

Expand Down
25 changes: 14 additions & 11 deletions lib/kitchen/verifier/terraform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ module Verifier
#
# ==== kitchen verify
#
# A Test Kitchen instance is verified by iterating through the systems and executing the associated InSpec controls
# against the hosts of each system.
# A Kitchen instance is verified by iterating through the systems and executing the associated InSpec controls
# against the hosts of each system. The outputs of the Terraform state are retrieved and exposed as attributes to
# the InSpec controls.
#
# ===== Retrieving the Terraform Output
#
# terraform output -json
#
# === Configuration Attributes
#
Expand Down Expand Up @@ -74,11 +79,11 @@ class Terraform
#
# @example
# `kitchen verify suite-name`
# @param kitchen_state [::Hash] the mutable instance and verifier state.
# @param _kitchen_state [::Hash] the mutable instance and verifier state.
# @raise [::Kitchen::ActionFailed] if the result of the action is a failure.
# @return [void]
def call(kitchen_state)
load_outputs kitchen_state: kitchen_state
def call(_kitchen_state)
load_outputs
config_systems.each do |system|
verify system: system
end
Expand Down Expand Up @@ -127,12 +132,10 @@ def configure_inspec_miscellaneous_options
)
end

def load_outputs(kitchen_state:)
@outputs = ::Kitchen::Util.stringified_hash Hash kitchen_state.fetch :kitchen_terraform_outputs
rescue ::KeyError => key_error
raise ::Kitchen::Terraform::Error,
"Loading Terraform outputs from the Kitchen state failed; this implies that the " \
"Kitchen-Terraform provisioner has not successfully converged\n#{key_error}"
def load_outputs
instance.driver.retrieve_outputs do |outputs:|
@outputs.replace ::Kitchen::Util.stringified_hash outputs
end
end

def initialize(configuration = {})
Expand Down
148 changes: 72 additions & 76 deletions spec/lib/kitchen/driver/terraform_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,82 +264,6 @@ def shell_out_run_yield(command:, standard_output: "mocked `terraform` success")
"-var-file=\"/Arbitrary Directory/Variable File.tfvars\"",
)
end

context "when `terraform output` results in failure due to no outputs defined" do
before do
shell_out_run_failure(
command: "output -json",
message: "no outputs defined",
)
end

it do
is_expected.to yield_with_args output: {}
end
end

context "when `terraform output` results in failure" do
before do
shell_out_run_failure(
command: "output -json",
message: "mocked `terraform output` failure",
)
end

it do
is_expected.to result_in_failure.with_message "mocked `terraform output` failure"
end
end

context "when `terraform output` results in success" do
before do
shell_out_run_yield(
command: "output -json",
standard_output: terraform_output_value,
)
end

context "when the value of the `terraform output` result is not valid JSON" do
let :terraform_output_value do
"not valid JSON"
end

it do
is_expected.to result_in_failure.with_message /Parsing Terraform output as JSON failed:/
end
end

context "when the value of the `terraform output` result is valid JSON" do
let :terraform_output_value do
::JSON.dump value_as_hash
end

let :value_as_hash do
{
output_name: {
sensitive: false,
type: "list",
value: ["output_value_1"],
},
}
end

it do
is_expected
.to(
yield_with_args(
output: {
"output_name" => {
"sensitive" => false,
"type" => "list",
"value" => ["output_value_1"],
},
},
)
)
end
end
end
end
end
end
Expand Down Expand Up @@ -653,6 +577,78 @@ def shell_out_run_yield(command:, standard_output: "mocked `terraform` success")
end
end

describe "#retrieve_outputs" do
subject do
described_instance
end

before do
described_instance.finalize_config! kitchen_instance
end

context "when the command results in failure due to no outputs defined" do
before do
shell_out_run_failure command: "output -json", message: "no outputs defined"
end

specify "should ignore the failure and yield an empty hash" do
expect do |block|
subject.retrieve_outputs(&block)
end.to yield_with_args outputs: {}
end
end

context "when the command results in failure not due to no outputs defined" do
before do
shell_out_run_failure command: "output -json", message: "mocked `terraform output` failure"
end

specify "should result in failure with the command failure message" do
expect do
subject.retrieve_outputs
end.to result_in_failure.with_message "mocked `terraform output` failure"
end
end

context "when the command results in success" do
before do
shell_out_run_yield command: "output -json", standard_output: terraform_output_value
end

context "when the value of the command result is not valid JSON" do
let :terraform_output_value do
"not valid JSON"
end

specify "should result in failure with a message which indicates the output is not valid JSON" do
expect do
subject.retrieve_outputs
end.to result_in_failure.with_message(/Parsing Terraform output as JSON failed:/)
end
end

context "when the value of the command result is valid JSON" do
let :terraform_output_value do
::JSON.dump value_as_hash
end

let :value_as_hash do
{output_name: {sensitive: false, type: "list", value: ["output_value_1"]}}
end

specify "should yield the hash which results from processing the output as JSON" do
expect do |block|
subject.retrieve_outputs(&block)
end.to yield_with_args(
outputs: {
"output_name" => {"sensitive" => false, "type" => "list", "value" => ["output_value_1"]},
},
)
end
end
end
end

describe "#verify_dependencies" do
subject do
lambda do
Expand Down
Loading

0 comments on commit 1f3625d

Please sign in to comment.