diff --git a/.github/workflows/delivery.yml b/.github/workflows/delivery.yml index c3cb2ac7d..7a268ef93 100644 --- a/.github/workflows/delivery.yml +++ b/.github/workflows/delivery.yml @@ -119,6 +119,21 @@ jobs: run: | chmod 400 ./test/terraform/$VERSION_MATCHER/backend-ssh/id_ed25519 rake test:kitchen:backend-ssh-ubuntu + - name: Run Kitchen doctor + run: | + status=0 + output="$(kitchen doctor doctor)" + if [[ $output != *"${{ matrix.operating-system }}.driver.client"* ]] + then + echo "kitchen doctor did not detect driver.client error" + status=1 + fi + if [[ $output != *"${{ matrix.operating-system }}.verifier.systems"* ]] + then + echo "kitchen doctor did not detect verifier.systems error" + status=1 + fi + exit $status release: name: "Release" diff --git a/CHANGELOG.md b/CHANGELOG.md index bff052943..af92a8d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to ### Added - Support for Inspec < 6.0 +- Support for `kitchen doctor` command: initially validates driver.client and verifier.systems ### Changed diff --git a/README.md b/README.md index eae9bb4ea..7bf991eb4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # ![Kitchen-Terraform Logo][kitchen-terraform-logo] Kitchen-Terraform > Kitchen-Terraform enables verification of infrastructure systems provisioned with Terraform. @@ -7,17 +6,16 @@ [![Gem downloads version][gem-downloads-version-shield]][kitchen-terraform-gem] [![Gem downloads total][gem-downloads-total-shield]][kitchen-terraform-gem] -[![Kitchen tests workflow][kitchen-tests-workflow-shield]][kitchen-tests-workflow] -[![RSpec tests workflow][rspec-tests-workflow-shield]][rspec-tests-workflow] +[![Delivery][delivery-shield]][delivery-workflow] +[![Pages Build and Deployment][pages-build-deployment-shield]][pages-build-deployment-workflow] [![Code coverage][code-coverage-shield]][code-coverage] [![Maintainability][maintainability-shield]][maintainability] [![Technical debt][technical-debt-shield]][technical-debt] -[![Dependencies][hakiri-shield]][hakiri] [![Gitter chat][gitter-shield]][gitter] -Kitchen-Terraform provides a set of [Kitchen][kitchen] plugins -which enable the use of Kitchen to converge a [Terraform][terraform] +Kitchen-Terraform provides a set of [Test Kitchen][test-kitchen] plugins +which enable the use of Test Kitchen to converge a [Terraform][terraform] configuration and verify the resulting infrastructure systems with [InSpec][inspec] controls. @@ -75,11 +73,13 @@ the semantic versioning of the Ruby gem. > Defining Kitchen-Terraform as a dependency for Bundler in a Gemfile --- + ```ruby source "https://rubygems.org/" do gem "kitchen-terraform", "~> 6.1" end ``` + --- Second, run the following command. @@ -87,9 +87,11 @@ Second, run the following command. > Installing Kitchen-Terraform with Bundler --- + ```sh bundle install ``` + --- The preceding command will create a `Gemfile.lock` comprising a list @@ -107,9 +109,11 @@ example. > Installing Kitchen-Terraform with RubyGems --- + ```sh gem install kitchen-terraform --version 6.1.0 ``` + --- This approach is not recommended as it requires more effort to install @@ -131,11 +135,13 @@ Ed25519-type SSH keys. ## Usage +A familiarity with [Test Kitchen][test-kitchen] workflows and commands is required to use Kitchen-Terraform. + ### Configuration Kitchen-Terraform provides three Test Kitchen plugins which must be configured in a -[Kitchen configuration file][kitchen-configuration-file] in +[Test Kitchen configuration file][kitchen-configuration-file] in order to successfully test Terraform configuration. The [Terraform driver][terraform-driver] manages the state of the @@ -150,6 +156,9 @@ Terraform state. More information can be found in the [Ruby gem documentation][ruby-gem-documentation]. +The `kitchen doctor` command can be used to validate the system and the +configuration file. + ### Caveats Versions of Terraform in the 0.11 series may cause `kitchen test` to @@ -166,7 +175,7 @@ Several tutorials are available on the The integration tests for Kitchen-Terraform can also be viewed as examples of how it works. The -[integration test Kitchen configuration file][int-kitchen-config] +[integration test Test Kitchen configuration file][int-kitchen-config] and the [integration test directory][test-directory] provide several functional examples which exercise various features of Kitchen-Terraform. @@ -233,9 +242,6 @@ Kitchen-Terraform is distributed under the [Apache License][license]. -[appveyor-build-status-shield]: https://ci.appveyor.com/api/projects/status/8d7t014gij5grk5r/branch/master?svg=true -[appveyor-build-status]: https://ci.appveyor.com/project/aaron-lane/kitchen-terraform/branch/master -[bundler-getting-started]: https://bundler.io/#getting-started [bundler-in-depth]: https://bundler.io/gemfile.html [bundler]: https://bundler.io/index.html#getting-started [changelog]: https://github.com/newcontext-oss/kitchen-terraform/blob/master/CHANGELOG.md @@ -243,37 +249,30 @@ Kitchen-Terraform is distributed under the [Apache License][license]. [code-coverage]: https://codeclimate.com/github/newcontext-oss/kitchen-terraform/ [contributing-document]: https://github.com/newcontext-oss/kitchen-terraform/blob/master/CONTRIBUTING.md [contributors]: https://github.com/newcontext-oss/kitchen-terraform/graphs/contributors -[docker]: https://www.docker.com/ -[docker-community-edition]: https://store.docker.com/editions/community/docker-ce-server-ubuntu -[docker-provider]: https://www.terraform.io/docs/providers/docker/index.html +[copado-github]: https://github.com/CopadoSolutions +[copado-linkedin]: https://www.linkedin.com/company/copado-solutions-s.l +[copado-twitter]: https://twitter.com/CopadoSolutions +[copado]: https://copado.com/ +[delivery-shield]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/delivery.yml/badge.svg +[delivery-workflow]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/delivery.yml [gem-downloads-total-shield]: https://img.shields.io/gem/dt/kitchen-terraform.svg [gem-downloads-version-shield]: https://img.shields.io/gem/dtv/kitchen-terraform.svg [gem-version-shield]: https://img.shields.io/gem/v/kitchen-terraform.svg [gitter-shield]: https://img.shields.io/gitter/room/kitchen-terraform/Lobby.svg [gitter]: https://gitter.im/kitchen-terraform/Lobby -[hakiri-shield]: https://hakiri.io/github/newcontext-oss/kitchen-terraform/master.svg -[hakiri]: https://hakiri.io/github/newcontext-oss/kitchen-terraform/ [inspec]: https://www.inspec.io/ [int-kitchen-config]: https://github.com/newcontext-oss/kitchen-terraform/blob/master/kitchen.yml -[issue-271]: https://github.com/newcontext-oss/kitchen-terraform/issues/271 -[kitchen]: http://kitchen.ci/index.html [kitchen-configuration-file]: https://docs.chef.io/config_yml_kitchen.html [kitchen-terraform-gem]: https://rubygems.org/gems/kitchen-terraform [kitchen-terraform-logo]: https://raw.githubusercontent.com/newcontext-oss/kitchen-terraform/master/assets/logo.png [kitchen-terraform-tutorials]: https://newcontext-oss.github.io/kitchen-terraform/tutorials/ -[kitchen-tests-workflow]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/kitchen-tests.yml -[kitchen-tests-workflow-shield]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/kitchen-tests.yml/badge.svg [license]: https://github.com/newcontext-oss/kitchen-terraform/blob/master/LICENSE [maintainability-shield]: https://img.shields.io/codeclimate/maintainability-percentage/newcontext-oss/kitchen-terraform.svg [maintainability]: https://codeclimate.com/github/newcontext-oss/kitchen-terraform/ -[copado-github]: https://github.com/CopadoSolutions -[copado-linkedin]: https://www.linkedin.com/company/copado-solutions-s.l -[copado-twitter]: https://twitter.com/CopadoSolutions -[copado]: https://copado.com/ +[pages-build-deployment-shield]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/pages/pages-build-deployment/badge.svg +[pages-build-deployment-workflow]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/pages/pages-build-deployment [rbenv]: https://github.com/rbenv/rbenv [rbnacl-installation]: https://github.com/crypto-rb/rbnacl/tree/v4.0.2#installation -[rspec-tests-workflow]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/rspec-tests.yml -[rspec-tests-workflow-shield]: https://github.com/newcontext-oss/kitchen-terraform/actions/workflows/rspec-tests.yml/badge.svg [ruby-branches]: https://www.ruby-lang.org/en/downloads/branches/ [ruby-gem-documentation]: http://www.rubydoc.info/github/newcontext-oss/kitchen-terraform/ [ruby-gems-what-is]: http://guides.rubygems.org/ruby-gems-what-is/index.html @@ -289,4 +288,5 @@ Kitchen-Terraform is distributed under the [Apache License][license]. [terraform-verifier]: http://www.rubydoc.info/github/newcontext-oss/kitchen-terraform/Kitchen/Verifier/Terraform [terraform]: https://www.terraform.io/ [test-directory]: https://github.com/newcontext-oss/kitchen-terraform/tree/master/test +[test-kitchen]: http://kitchen.ci/ [tfenv]: https://github.com/kamatama41/tfenv diff --git a/kitchen.yml b/kitchen.yml index 1c1d5f0d3..e095a2dfa 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -85,6 +85,11 @@ suites: shell_command: /bin/sh shell_options: -x user: root + - name: doctor + driver: + client: /nonexistent/pathname + verifier: + systems: [] - name: plug-ins driver: plugin_directory: test/terraform/PlugIns/PlugInDirectory diff --git a/lib/kitchen/driver/terraform.rb b/lib/kitchen/driver/terraform.rb index e56f7b266..3b2ea2586 100644 --- a/lib/kitchen/driver/terraform.rb +++ b/lib/kitchen/driver/terraform.rb @@ -31,6 +31,7 @@ require "kitchen/terraform/configurable" require "kitchen/terraform/driver/create" require "kitchen/terraform/driver/destroy" +require "kitchen/terraform/driver/doctor" require "kitchen/terraform/version_verifier" require "rubygems" require "shellwords" @@ -191,6 +192,20 @@ def destroy(_state) 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) + driver_errors = ::Kitchen::Terraform::Driver::Doctor.new( + instance_name: instance.name, + logger: logger + ).call config: config + verifier_errors = instance.verifier.doctor state + + driver_errors or verifier_errors + end + # #finalize_config! invokes the super implementation and then initializes the strategies. # # @param instance [Kitchen::Instance] an associated instance. diff --git a/lib/kitchen/terraform/driver/doctor.rb b/lib/kitchen/terraform/driver/doctor.rb new file mode 100644 index 000000000..65788c908 --- /dev/null +++ b/lib/kitchen/terraform/driver/doctor.rb @@ -0,0 +1,59 @@ +# 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. + +module Kitchen + module Terraform + module Driver + # Doctor checks for driver configuration errors. + class Doctor + # #call executes the action. + # + # @param config [Hash] the configuration of the driver. + # @return [Boolean] +true+ if any errors are found; +false+ if no errors are found. + def call(config:) + errors = false + client = config.fetch :client + + if !::File.exist? client + errors = true + logger.error "#{instance_name}.driver.client '#{client}' does not exist" + end + if !::File.executable? client + errors = true + logger.error "#{instance_name}.driver.client '#{client}' is not executable" + end + + errors + end + + # #initialize prepares a new instance of the class. + # + # @param instance_name [String] the name of the Kitchen instance. + # @param logger [Kitchen::Logger] a logger for logging messages. + # @option config [String] :client the pathname of the Terraform client. + # @return [Kitchen::Terraform::Driver::Doctor] + def initialize(instance_name:, logger:) + self.instance_name = instance_name + self.logger = logger + end + + private + + attr_accessor :logger, :instance_name + end + end + end +end diff --git a/lib/kitchen/terraform/verifier/doctor.rb b/lib/kitchen/terraform/verifier/doctor.rb new file mode 100644 index 000000000..112afefea --- /dev/null +++ b/lib/kitchen/terraform/verifier/doctor.rb @@ -0,0 +1,55 @@ +# 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. + +module Kitchen + module Terraform + module Verifier + # Doctor checks for verifier configuration errors. + class Doctor + # #call executes the action. + # + # @param config [Hash] the configuration of the verifier. + # @return [Boolean] +true+ if any errors are found; +false+ if no errors are found. + def call(config:) + errors = false + systems = config.fetch :systems + + if systems.empty? + errors = true + logger.error "#{instance_name}.verifier.systems is empty" + end + + errors + end + + # #initialize prepares a new instance of the class. + # + # @param instance_name [String] the name of the Kitchen instance. + # @param logger [Kitchen::Logger] a logger for logging messages. + # @option config [String] :client the pathname of the Terraform client. + # @return [Kitchen::Terraform::Verifier::Doctor] + def initialize(instance_name:, logger:) + self.instance_name = instance_name + self.logger = logger + end + + private + + attr_accessor :logger, :instance_name + end + end + end +end diff --git a/lib/kitchen/verifier/terraform.rb b/lib/kitchen/verifier/terraform.rb index e24d795c0..250075d07 100644 --- a/lib/kitchen/verifier/terraform.rb +++ b/lib/kitchen/verifier/terraform.rb @@ -24,6 +24,7 @@ require "kitchen/terraform/systems_verifier_factory" require "kitchen/terraform/outputs_manager" require "kitchen/terraform/variables_manager" +require "kitchen/terraform/verifier/doctor" module Kitchen # This namespace is defined by Kitchen. @@ -118,9 +119,8 @@ def call(state) # # @param _state [Hash] the mutable Kitchen instance state. # @return [Boolean] +true+ if any errors are found; +false+ if no errors are found. - # @see https://github.com/test-kitchen/test-kitchen/blob/v1.21.2/lib/kitchen/verifier/base.rb#L85-L91 def doctor(_state) - false + ::Kitchen::Terraform::Verifier::Doctor.new(instance_name: instance.name, logger: logger).call config: config end # #initialize prepares a new instance of the class. diff --git a/spec/lib/kitchen/driver/terraform_spec.rb b/spec/lib/kitchen/driver/terraform_spec.rb index 80eaa65a5..85f1da67a 100644 --- a/spec/lib/kitchen/driver/terraform_spec.rb +++ b/spec/lib/kitchen/driver/terraform_spec.rb @@ -183,4 +183,18 @@ end end end + + describe "#doctor" do + let :kitchen_instance_state do + {} + end + + before do + subject.finalize_config! kitchen_instance + end + + specify "should return true" do + expect(subject.doctor(kitchen_instance_state)).to be_truthy + end + end end diff --git a/spec/lib/kitchen/terraform/driver/doctor_spec.rb b/spec/lib/kitchen/terraform/driver/doctor_spec.rb new file mode 100644 index 000000000..360589831 --- /dev/null +++ b/spec/lib/kitchen/terraform/driver/doctor_spec.rb @@ -0,0 +1,62 @@ +# 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/driver/doctor" +require "tempfile" + +::RSpec.describe ::Kitchen::Terraform::Driver::Doctor do + subject do + described_class.new instance_name: "test-instance", logger: logger + end + + let :logger do + ::Kitchen::Logger.new + end + + describe "#call" do + context "when the configured client does not exist" do + let :config do + { + client: "/nonexistent/pathname" + } + end + + specify "should return true" do + expect(subject.call(config: config)).to be_truthy + end + end + + context "when the configured client is not executable" do + let :config do + { + client: ::Tempfile.new("client") + } + end + + specify "should raise a user error" do + expect(subject.call(config: config)).to be_truthy + end + + after do + config.fetch(:client).tap do |client| + client.close + client.unlink + end + end + end + end +end diff --git a/spec/lib/kitchen/terraform/verifier/doctor_spec.rb b/spec/lib/kitchen/terraform/verifier/doctor_spec.rb new file mode 100644 index 000000000..3ef442df5 --- /dev/null +++ b/spec/lib/kitchen/terraform/verifier/doctor_spec.rb @@ -0,0 +1,43 @@ +# 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/verifier/doctor" +require "tempfile" + +::RSpec.describe ::Kitchen::Terraform::Verifier::Doctor do + subject do + described_class.new instance_name: "test-instance", logger: logger + end + + let :logger do + ::Kitchen::Logger.new + end + + describe "#call" do + context "when the configured systems are empty" do + let :config do + { + systems: [] + } + end + + specify "should return true" do + expect(subject.call(config: config)).to be_truthy + end + end + end +end diff --git a/spec/lib/kitchen/verifier/terraform_spec.rb b/spec/lib/kitchen/verifier/terraform_spec.rb index c95f17376..5bfa6cc79 100644 --- a/spec/lib/kitchen/verifier/terraform_spec.rb +++ b/spec/lib/kitchen/verifier/terraform_spec.rb @@ -176,6 +176,10 @@ {} end + before do + subject.finalize_config! kitchen_instance + end + specify "should return false" do expect(subject.doctor(kitchen_instance_state)).to be_falsey end