Key Benefits

  • OS and CM agnostic
  • InSpec runtime is faster than ServerSpec
  • DRY – Componentization of test suites using shared InSpec Profiles
  • Removes downloading of Gem files on every test execution
  • Quick to Change from ServerSpec to InSpec
  • CLI binary available to run tests from CI/CD
  • Allows usage of community test suites
  • Allows usage of InSpec Profiles from a Chef Compliance server
  • Allows compliance reporting to Chef Automate Visibility server

ServerSpec has been a great automated integration test framework for years, but it has always had some limitations. Such as, when running with Test Kitchen it downloads several gems every time tests are run and the inability to easily have shared test suites that could be used by multiple cookbooks or delivery solutions. Chef took on the challenge of extending ServerSpec to solve many of these inadequacies. Chef created a project named InSpec. It supports the same fundamental syntax of ServerSpec and adds extended capabilities.
InSpec does not require downloading gems during testing. ChefDK comes with all you need which the minimum is the inspec Rubygem. It also has the Test Kitchen extension gem named kitchen-inspec. Just this along makes for faster testing. Also, it helps for environments that may not have internet access.
InSpec also brings shared capabilities by way of what they’ve called InSpec Profiles. InSpec Profiles can be simple or as complex as you would like to make. With Profiles, you can also pass arguments. For example, you can have a shared InSpec Profile hosted in its own Git repo that tests for the version of Chef Client installed. You can set a default Chef Client version to check for, but allow an argument to override the default. So, you can pass in an argument string to the test profile with the version you expect and want it to check for. I have a simple example of that here.
When using InSpec with Test Kitchen it is possible to call multiple InSpec Profiles remote and/or local. For example, it’s possible to have said a set of standard security/compliance profiles, other cookbooks that are wrapped, baseline profile (bootstrapping) and then have a local profile that checks the specific cookbook configurations.
Here’s an example Kitchen config from bonusbits_mediawiki_nginx cookbook. This is at root level of a test suite.
[code language=”text”] verifier:
inspec_tests:
– name: bootstrap
git: https://github.com/bonusbits/inspec_bootstrap.git
– name: bonusbits_base
git: https://github.com/bonusbits/inspec_bonusbits_base.git
– path: test/inspec/profiles/bonusbits_web/
attributes:
chef_version: ‘12.19.36’
[/code] It’s fairly easy and quick to migrate ServerSpec tests to InSpec tests. In all actuality, you could migrate some local ServerSpec tests to InSpec in a matter of minutes.
To demonstrate how quickly we can convert ServerSpec to InSpec I have created a short series of videos that walk through this process. Companions to the videos are Github reference branches for each part and wiki articles that I have linked below:

ServerSpec to InSpec – Part 1
Creating a ServerSpec Tested Chef Cookbook

[youtube https://www.youtube.com/watch?v=fn_GV9Ejnqc] Github Branch – Part 1
Wiki Article – Part 1

ServerSpec to InSpec – Part 2
Converting Local ServerSpec to Local InSpec Tests

[youtube https://www.youtube.com/watch?v=jLJu2fi2z4] Github Branch – Part 2
Wiki Article – Part 2

ServerSpec to InSpec – Part 3
Converting Local InSpec to Shared Remote Tests

[youtube https://www.youtube.com/watch?v=S0RvMnQpjX] Github Branch – Part 3
Github Example Shared InSpec Profile
Wiki Article

Complete Walkthrough Video Playlist

[youtube https://www.youtube.com/playlist?list=PLy2eDDzDOIEpf6obkRNB_Eikx32b68f8I]

Spoiler!

Ok, so if you don’t have time to watch videos or read the wiki articles. Here’s the basic conversion for local ServerSpec to local InSpec tests. This is from Part 2. Part 3 goes into the best part about InSpec which is remote/shared tests.

Before

test
??? integration
 ??? default
 ?   ??? serverspec
 ?      ??? nginx_spec.rb
 ?      ??? phpfpm_spec.rb
 ??? helpers
    ??? serverspec
       ??? spec_helper.rb
test/integration/default/serverspec/nginx_spec.rb
[code language=”ruby”] require ‘spec_helper’
describe ‘Nginx’ do
it ‘nginx installed’ do
expect(package(‘nginx’)).to be_installed
end
it ‘nginx service’ do
expect(service(‘nginx’)).to be_enabled
expect(service(‘nginx’)).to be_running
end
end
[/code]
test/integration/default/serverspec/phpfpm_spec.rb
[code language=”ruby”] require ‘spec_helper’
describe ‘Php FPM’ do
it ‘php-fpm installed’ do
expect(package(‘php70-fpm’)).to be_installed
end
it ‘php-fpm service’ do
expect(service(‘php-fpm-7.0’)).to be_enabled
expect(service(‘php-fpm-7.0’)).to be_running
end
it ‘nginx owns /var/log/php-fpm’ do
expect(file(‘/var/log/php-fpm’)).to be_owned_by(‘nginx’)
expect(file(‘/var/log/php-fpm/7.0’)).to be_owned_by(‘nginx’)
end
it ‘nginx owns /var/lib/php/7.0’ do
expect(file(‘/var/lib/php/7.0’)).to be_grouped_into(‘nginx’)
end
end
[/code]
test/integration/helpers/serverspec/spec_helper.rb
[code language=”ruby”] # Encoding: utf-8
require ‘serverspec’
if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM).nil?
set :backend, :exec
set :path, ‘/sbin:/usr/local/sbin:/bin:/usr/bin:$PATH’
else
set :backend, :cmd
set :os, family: ‘windows’
end
[/code]

After

test
??? integration
    ??? default
        ??? inspec
            ??? nginx_spec.rb
            ??? phpfpm_spec.rb
test/integration/default/serverspec/nginx_spec.rb
[code language=”ruby”] describe ‘Nginx’ do
it ‘nginx installed’ do
expect(package(‘nginx’)).to be_installed
end
it ‘nginx service’ do
expect(service(‘nginx’)).to be_enabled
expect(service(‘nginx’)).to be_running
end
end
[/code]
test/integration/default/serverspec/phpfpm_spec.rb
[code language=”ruby”] describe ‘Php FPM’ do
it ‘php-fpm installed’ do
expect(package(‘php70-fpm’)).to be_installed
end
it ‘php-fpm service’ do
expect(service(‘php-fpm-7.0’)).to be_enabled
expect(service(‘php-fpm-7.0’)).to be_running
end
it ‘nginx owns /var/log/php-fpm’ do
expect(file(‘/var/log/php-fpm’)).to be_owned_by(‘nginx’)
expect(file(‘/var/log/php-fpm/7.0’)).to be_owned_by(‘nginx’)
end
it ‘nginx owns /var/lib/php/7.0’ do
expect(file(‘/var/lib/php/7.0’)).to be_grouped_into(‘nginx’)
end
end
[/code]
.kitchen.yml
[code language=”text”] verifier:
name: inspec
[/code]

Resources

Stelligent Amazon Pollycast
Voiced by Amazon Polly