lib/kitchen/verifier/terraform.rb
# frozen_string_literal: true
# Copyright 2016-2021 Copado NCS LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "kitchen"
require "kitchen/terraform/raise/action_failed"
require "kitchen/terraform/raise/client_error"
require "kitchen/terraform/config_attribute/color"
require "kitchen/terraform/config_attribute/fail_fast"
require "kitchen/terraform/config_attribute/systems"
require "kitchen/terraform/configurable"
require "kitchen/terraform/systems_verifier_factory"
require "kitchen/terraform/outputs_manager"
require "kitchen/terraform/variables_manager"
module Kitchen
# This namespace is defined by Kitchen.
#
# @see https://www.rubydoc.info/gems/test-kitchen/Kitchen/Verifier
module Verifier
# The Terraform verifier utilizes {https://community.chef.io/tools/chef-inspec InSpec} to verify the
# behaviour and state of resources in the Terraform state.
#
# === Commands
#
# The following command-line commands are provided by the verifier.
#
# ==== kitchen verify
#
# 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.
#
# === Configuration Attributes
#
# The configuration attributes of the verifier control the behaviour of the InSpec runner. Within the
# {http://kitchen.ci/docs/getting-started/kitchen-yml Test Kitchen configuration file}, these attributes must be
# declared in the +verifier+ mapping along with the plugin name.
#
# verifier:
# name: terraform
# a_configuration_attribute: some value
#
# ==== color
#
# {include:Kitchen::Terraform::ConfigAttribute::Color}
#
# ==== fail_fast
#
# {include:Kitchen::Terraform::ConfigAttribute::FailFast}
#
# ==== systems
#
# {include:Kitchen::Terraform::ConfigAttribute::Systems}
#
# === Ruby Interface
#
# This class implements the interface of Kitchen::Configurable which requires the following Reek suppressions:
# :reek:MissingSafeMethod { exclude: [ finalize_config!, load_needed_dependencies! ] }
#
# @example Describe the verify command
# kitchen help verify
# @example Verify a Test Kitchen instance
# kitchen verify default-ubuntu
# @version 2
class Terraform < ::Kitchen::Verifier::Base
# UNSUPPORTED_BASE_ATTRIBUTES is the list of attributes inherited from
# Kitchen::Verifier::Base which are not supported by Kitchen::Verifier::Terraform.
UNSUPPORTED_BASE_ATTRIBUTES = [
:chef_omnibus_root,
:command_prefix,
:http_proxy,
:https_proxy,
:ftp_proxy,
:root_path,
:sudo,
:sudo_command,
]
defaults.delete_if do |key|
UNSUPPORTED_BASE_ATTRIBUTES.include? key
end
include ::Kitchen::Terraform::ConfigAttribute::Color
include ::Kitchen::Terraform::ConfigAttribute::FailFast
include ::Kitchen::Terraform::ConfigAttribute::Systems
include ::Kitchen::Terraform::Configurable
kitchen_verifier_api_version 2
attr_reader :outputs, :variables
# The verifier enumerates through each host of each system and verifies the associated InSpec controls.
#
# @example
# `kitchen verify suite-name`
# @param state [Hash] the mutable instance and verifier state.
# @raise [Kitchen::ActionFailed] if the result of the action is a failure.
# @return [void]
def call(state)
load_variables state: state
load_outputs state: state
verify_systems
rescue => error
action_failed.call message: error.message
end
# doctor checks the system and configuration for common errors.
#
# @param _state [Hash] the mutable Kitchen instance state.
# @return [Boolean] +true+ if any errors are found; +false+ if no errors are found.
def doctor(_state)
errors = false
methods.each do |method|
next if !method.match? /doctor_config_.*/
error = send method
errors = errors || error
end
errors
end
# #initialize prepares a new instance of the class.
#
# @param config [Hash] the verifier configuration.
# @return [Kitchen::Verifier::Terraform]
def initialize(config = {})
init_config config
self.action_failed = ::Kitchen::Terraform::Raise::ActionFailed.new logger: logger
self.client_error = ::Kitchen::Terraform::Raise::ClientError.new logger: logger
self.outputs = {}
self.variables = {}
end
private
attr_accessor :action_failed, :client_error, :outputs, :variables
def load_variables(state:)
logger.warn "Reading the Terraform input variables from the Kitchen instance state..."
::Kitchen::Terraform::VariablesManager.new.load variables: variables, state: state
logger.warn "Finished reading the Terraform input variables from the Kitchen instance state."
end
# load_needed_dependencies! loads the InSpec libraries required to verify a Terraform state.
#
# @raise [Kitchen::ClientError] if loading the InSpec libraries fails.
# @see https://github.com/test-kitchen/test-kitchen/blob/v1.21.2/lib/kitchen/configurable.rb#L252-L274
def load_needed_dependencies!
require "kitchen/terraform/inspec_runner"
require "kitchen/terraform/system"
::Kitchen::Terraform::InSpecRunner.logger = logger
rescue ::LoadError => load_error
client_error.call message: load_error.message
end
def load_outputs(state:)
logger.warn "Reading the Terraform output variables from the Kitchen instance state..."
::Kitchen::Terraform::OutputsManager.new.load outputs: outputs, state: state
logger.warn "Finished reading the Terraform output variables from the Kitchen instance state."
end
def profile_locations
@profile_locations ||= [::File.join(config.fetch(:test_base_path), instance.suite.name)]
end
def systems
@systems ||= config_systems.map do |system|
::Kitchen::Terraform::System.new(
configuration_attributes: { color: config_color, profile_locations: profile_locations }.merge(system),
logger: logger,
)
end
end
def systems_verifier
@systems_verifier ||= ::Kitchen::Terraform::SystemsVerifierFactory.new(fail_fast: config_fail_fast).build(
systems: systems,
)
end
def verify_systems
logger.warn "Verifying the systems..."
systems_verifier.verify outputs: outputs, variables: variables
logger.warn "Finished verifying the systems."
end
end
end
end