serverspec-extra-types is a set of addition resource types and matchers for ServerSpec, providing types for technologies such as docker swarm, consul, and rabbitmq.
All checks run on the target, which can be useful when executing via a bastion host
Add this line to your application's Gemfile:
gem 'serverspec-extra-types'
And then execute:
$ bundle
Or install it yourself as:
$ gem install serverspec-extra-types
Add the following to your spec_helper.rb
require 'serverspec-extra-types'
curl | consul_node | consul_node_list | consul_service | consul_service_list | docker_config | docker_container | docker_network | docker_node | docker_secret | docker_service | jenkins_credential |jenkins_job | jenkins_plugin | nfs_export | rabbitmq_node_list | rabbitmq_user_permission | rabbitmq_vhost_list | rabbitmq_vhost_policy | sudo_user
Please note: This type requires curl to be installed on the target host
The curl resource allow for check against remote url. Addition parameters are avaliable to allow for insecure certifactes and follow redirects
Example:
describe curl("https://example.org") do
it { should respond_with_OK }
end
#Without certificate verification
describe curl("https://example.org", insecure: true) do
it { should respond_with_OK }
end
#Following redirects
describe curl("https://example.org", follow_redirects: true) do
it { should respond_with_OK }
end
For a full list of HTTP matchers see HTTP Matchers
Please note: This type requires curl to be installed on the target host
describe consul_node('consul') do
it { should have_datacenter 'dc1'}
end
describe consul_node('consul') do
it { should have_datacenter 'dc1'}
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
describe consul_node_list() do
it { should have_node 'consul' }
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
describe consul_service('consul') do
it { should have_id('7ba9a647-0adb-8c92-b0c9-5b011b3530a8') }
end
describe consul_service('consul') do
it { should have_node('consul') }
end
describe consul_service('consul') do
it { should have_address('127.0.0.1') }
end
describe consul_service('consul') do
it { should have_datacenter('dc1') }
end
describe consul_service('consul') do
it { should have_tagged_addresses({"lan"=>"127.0.0.1", "wan"=>"127.0.0.1"}) }
end
describe consul_service('consul') do
it { should have_node_meta({"consul-network-segment"=>""}) }
end
describe consul_service('consul') do
it { should have_service_kind('') }
end
describe consul_service('consul') do
it { should have_service_id('consul') }
end
describe consul_service('consul') do
it { should have_service_name('consul') }
end
describe consul_service('consul') do
it { should have_service_tags([]) }
end
describe consul_service('consul') do
it { should have_service_address('') }
end
describe consul_service('consul') do
it { should have_service_weights({"Passing"=>1, "Warning"=>1}) }
end
describe consul_service('consul') do
it { should have_service_meta({}) }
end
describe consul_service('consul') do
it { should have_service_port(8300) }
end
describe consul_service('consul') do
it { should have_service_enable_tag_override(false) }
end
describe consul_service('consul') do
it { should have_service_proxy_destination('') }
end
describe consul_service('consul') do
it { should have_service_proxy({}) }
end
describe consul_service('consul') do
it { should have_service_connect({}) }
end
describe consul_service('consul') do
it { should have_create_index(9) }
end
describe consul_service('consul') do
it { should have_modify_index(9) }
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
describe consul_service_list() do
it { should have_service 'consul' }
end
Supports the same additional parameters as the curl matcher
Check if config exists
describe docker_config('test.conf') do
it { should exist }
end
Check if config has a specific name
describe docker_config('test.conf') do
it { should have_name 'test.conf' }
end
Check if config data matches a string
describe docker_config('test.conf') do
it { should have_data 'Some config data' }
end
Check if config base64 encoded data matches a string
describe docker_config('test.conf') do
it { should have_data64 'U29tZSBjb25maWcgZGF0YQ=='}
end
Check if config has a specific label
describe docker_config('test.conf') do
it { should have_label('some.label.key') }
# Check for value
it { should have_label('some.label.key').with_value('value') }
end
Extension of the serverspec docker container type, and provides the following matchers:
this type now supports selecting the container via a filter or by name
Check if a container exists by name
describe docker_container('focused_currie') do
it { should exist }
end
# Using a filter (useful for containers launch via docker swarm)
describe docker_container('name=focused_currie') do
it { should exist }
end
Check if a container exists by filter
# Look for a container publishing port 80
describe docker_container('publish=80') do
it { should exist }
end
Check if a container is running (from serverspec)
describe docker_container('focused_curie') do
it { should be_running }
end
Check if container is running a specfic image
describe docker_container('focused_currie') do
it { should have_image('jenkins/jenkins:lts') }
end
Check container hostname
describe docker_container('focused_currie') do
it { should have_hostname('container1') }
end
Check container domain name
describe docker_container('focused_currie') do
it { should have_domainname('leek.com') }
end
Check if container is running as the specified user
describe docker_container('focused_currie') do
it { should run_as_user('jenkins') }
end
Check if host port is mapped to container port
describe docker_container('focused_currie') do
it { should map_port('80','8080') }
end
Check if host port is mapped to container port using a specific protocol
describe docker_container('focused_currie') do
it { should map_port('80','8080').using_protocol('tcp') }
end
Check the for a volume (from serverspec)
describe docker_container('focused_currie') do
it { it { should have_volume('/tmp','/data') }}
end
Check the for mounted volume
describe docker_container('focused_currie') do
it { should have_mount('/var/run/docker.sock', '/var/run/docker.sock') }
end
Check the containers restart policy
describe docker_container('focused_currie') do
it { should have_restart_policy('always') }
end
Check the containers restart limit
describe docker_container('focused_currie') do
it { should have_restart_limit(1) }
end
Check for additional /etc/hosts entries
describe docker_container('focused_currie') do
it { should have_host('8.8.8.8 dns') }
end
Check if the container has an specific environment variable
describe docker_container('focused_currie') do
it { should have_environment_variable('CONSUL_VERSION') }
end
# check its value
describe docker_container('focused_currie') do
it { should have_environment_variable('CONSUL_VERSION').with_value('1.2.0') }
end
Check if the container runs in privileged mode
describe docker_container('focused_currie') do
it { should be_privileged }
end
Check if the container publishes all exposed ports
describe docker_container('focused_currie') do
it { should publishes_all_ports }
end
Check if docker node exists
describe docker_node('somehost') do
it { should exist }
end
Check if docker node is a manager node
describe docker_node(`hostname -f`.chomp) do
it { should be_a_manager }
end
Check if docker node exists
describe docker_node('somehost') do
it { should be_a_worker }
end
Check engine version
describe docker_node('somehost') do
it { should have_engine_version '18.09.1' }
end
Check if node is active
describe docker_node('somehost') do
it { should be_active }
end
Check if node is draining
describe docker_node('somehost') do
it { should be_draining }
end
Check if node is paused
describe docker_node('somehost') do
it { should be_paused }
end
Check if network exists
describe docker_network('test_network') do
it { should exist }
end
Check if network is attachable
describe docker_network('test_network') do
it { should be_attachable }
end
Check if network is swarm scoped
describe docker_network('test_network') do
it { should be_swarm_scoped }
end
Check if network uses a specific driver
describe docker_network('test_network') do
it { should have_driver('overlay') }
end
Check if network is an overlay network
describe docker_network('test_network') do
it { should be_overlay }
end
Check if network is internal
describe docker_network('test_network') do
it { should_not be_internal }
end
Check if network is an ingress network
describe docker_network('test_network') do
it { should_not be_ingress }
end
Check if network is IPv6 enabled
describe docker_network('test_network') do
it { should_not be_IPv6_enabled }
end
Check if secret exists
describe docker_secret('secret.key') do
it { should exist }
end
Check if secret has a specific name
describe docker_secret('secret.key') do
it { should have_name 'secret.key' }
end
Check if secret has a specific label
describe docker_secret('secret.key') do
it { should have_label('some.label.key') }
# Check for value
it { should have_label('some.label.key').with_value('value') }
end
Check if a service exists by name
describe docker_service('my_service') do
it { should exist }
end
Check if service is running a specfic image
describe docker_service('my-awesome-service') do
it { should have_image('jenkins/jenkins:lts') }
end
Check if service is running as the specified user
describe docker_service('my-awesome-service') do
it { should run_as_user('jenkins') }
end
Check if host port is mapped to service port
describe docker_service('my-awesome-service') do
it { should map_port('80','8080') }
end
Check if host port is mapped to service port using a specific protocol
describe docker_service('my-awesome-service') do
it { should map_port('80','8080').using_protocol('tcp') }
end
Check the for a volume (from serverspec)
describe docker_service('my-awesome-service') do
it { it { should have_volume('/tmp','/data') }}
end
Check the for mounted volume
describe docker_service('my-awesome-service') do
it { should have_mount('/var/run/docker.sock', '/var/run/docker.sock') }
end
Check the services restart policy
describe docker_service('my-awesome-service') do
it { should have_restart_policy('any') }
end
Check the services restart limit
describe docker_service('my-awesome-service') do
it { should have_restart_limit(1) }
end
Check for additional /etc/hosts entries
describe docker_service('my-awesome-service') do
it { should have_host('8.8.8.8 dns') }
end
Check if the service has an specific environment variable
describe docker_service('my-awesome-service') do
it { should have_environment_variable('CONSUL_VERSION') }
end
# check its value
describe docker_service('my-awesome-service') do
it { should have_environment_variable('CONSUL_VERSION').with_value('1.2.0') }
end
Check if the service has an specific label
describe docker_service('my-awesome-service') do
it { should be_labeled('CONSUL_VERSION') }
end
# check its value
describe docker_service('my-awesome-service') do
it { should be_labeled('CONSUL_VERSION').with_value('1.2.0') }
end
This matcher supports both the UK and US spelling of labelled and also has have_label as an alias but this may conflict with dockerspec's have_lable matcher if using along side
Check the service has a particular config
describe docker_service('my-awesome-service') do
# Check that it uses a config
it { should have_config('nginx.conf') }
# Check that it places the config in a particular location
it { should have_config('nginx.conf', '/some/target/path') }
end
Check the service has a particular secret
describe docker_service('my-awesome-service') do
# Check that it uses a secret
it { should have_secret('secret.crt') }
# Check that it places the secret in a particular location
it { should have_secret('secret.crt', '/some/target/path') }
end
Check the service is running in global mode
describe docker_service('my-awesome-service') do
it { should be_global }
end
Check the service is running in replicated mode
describe docker_service('my-awesome-service') do
it { should be_replicated }
end
Check the number of replicas the service has
describe docker_service('my-awesome-service') do
it { should have_replica_count 2 }
end
Check the number of replicas the service has
describe docker_service('my-awesome-service') do
it { should have_placement_constraint('node.role == manager') }
end
Please note: This type requires curl to be installed on the target host
Verifies that the credential exists
describe jenkins_credential('someCredential') do
it { should exist }
end
Checks if the credential description matches the specified text
describe jenkins_credential('someCredential') do
it { should have_description 'Username and Password Credential' }
end
Checks if the display name matches the specified text
describe jenkins_credential('someCredential') do
it { should have_display_name 'test/****** (Username and Password Credential)'}
end
Checks if credential is a username and password
describe jenkins_credential('someCredential') do
it { should be_username_with_password }
end
Checks if credential is a secret string
describe jenkins_credential('someCredential') do
it { should be_secret_text }
end
Checks if credential is an ssh private key
describe jenkins_credential('someCredential') do
it { should be_ssh_private_key }
end
Checks if credential is an aws access key/ secret tken pair
describe jenkins_credential('someCredential') do
it { should be_aws_credential }
end
Checks if credential is a gitlab api token
describe jenkins_credential('someCredential') do
it { should be_gitlab_api_token }
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
Verifies that the job exists
describe jenkins_job('someJob') do
it { should exist }
end
Verifies that the job has the specified name
describe jenkins_job('someJob') do
it { should have_name 'someJob' }
end
Verifies that the job has the specified display name
describe jenkins_job('someJob') do
it { should have_display_name 'Some Job' }
end
Verifies that the job has the specified full display name
describe jenkins_job('someJob') do
it { should have_full_display_name 'someJob' }
end
#Jobs in folders use a '»' symbol inbetween the foldername and job name
describe jenkins_job('someFolder/someJob') do
it { should have_full_display_name 'someFolder » someJob' }
end
Verifies that the job has the specified display name
describe jenkins_job('someJob') do
it { should have_description 'Some Job' }
end
Verifies that the job is a free style project
describe jenkins_job('freestyle') do
it { should be_freestyle }
it { should be_freestyle_project }
end
Verifies that the job is a maven project
describe jenkins_job('maven') do
it { should be_maven }
it { should be_maven_project }
end
Verifies that the job is a pipeline project
describe jenkins_job('pipeleine') do
it { should be_pipeline }
it { should be_pipeline_project }
end
Verifies that the job is a multibranch project
describe jenkins_job('multibranch') do
it { should be_multibranch }
it { should be_multibranch_project }
end
Verifies that the job is a folder
describe jenkins_job('folder') do
it { should be_multibranch }
it { should be_multibranch_project }
end
Checks if the job has a particular type
describe jenkins_job('pipeline') do
it { should have_job_type('org.jenkinsci.plugins.workflow.job.WorkflowJob') }
it { should have_project_type('org.jenkinsci.plugins.workflow.job.WorkflowJob') }
end
Checks if the folder conatins the specified job
describe jenkins_job('folder') do
it { should have_job 'job' }
end
Checks if the folder conatins the specified number of jobs
describe jenkins_job('folder') do
it { should have_job_count 1 }
end
Checks if the folder conatains no jobs
describe jenkins_job('folder') do
it { should have_empty_job_list }
end
Jobs inside folder can be acessed vi folderName/jobName (ie the job urls without any '/jobs') for example
describe jenkins_job('folder/job') do
it { should exist }
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
Checks if the plugin exists and is installed
describe jenkins_plugin('ssh-slaves') do
it { should exist }
end
Check is plugin has a particular version
describe jenkins_plugin('ssh-slaves') do
it { should have_version '1.29.4' }
end
Also supports be_installed from serverspec (by matcher chain not supported)
describe jenkins_plugin('ssh-slaves') do
it { should be_installed }
# Supports with_version
it { should be_installed.with_version '1.29.4' }
end
Supports the same additional parameters as the curl matcher
Verifies that the specified export exists
describe nfs_export('/var/nfsroot') do
it { should exist }
it { should have_host('192.168.1.0/16') }
it { should have_host('192.168.1.0/16').with_option('root_squash') }
it { should have_host('192.168.1.0/16').with_options('rw,root_squash') }
it { should have_host('192.168.1.0/16').with_options(%q[rw root_squash subtree_check]) }
end
Verifies that the specified export has permission for the specified host/cidr range
Supports a with_option matcher to verify if the export has a specifed export option Supports a with_options matcher to verify options in list format
describe nfs_export('/var/nfsroot') do
it { should have_host('192.168.1.0/16') }
# Check for a specified option
it { should have_host('192.168.1.0/16').with_option('root_squash') }
# Check for a list of options as a comma delimited string (order is not important)
it { should have_host('192.168.1.0/16').with_options('rw,root_squash') }
# Check for a list of options as an array
it { should have_host('192.168.1.0/16').with_options(%q[rw root_squash subtree_check]) }
end
Please note: This type requires curl to be installed on the target host
Verifies the number of nodes in the node list
describe rabbitmq_node_list do
it { should have_count(1) }
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
Verifies that the user can read from the specifed queue on the specified vhost
describe rabbitmq_user_permission('MyUser') do
it { should read_from_queue('MyVhost', 'Q1') }
end
Verifies that the user can read from the specifed queue on the specified vhost
describe rabbitmq_user_permission('MyUser') do
it { should write_to_queue('MyVhost', 'Q1') }
end
Verifies that the user can configure the specified queue on the specified vhost
describe rabbitmq_user_permission('MyUser') do
it { should configure_queue('MyVhost', 'Q1') }
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
Verifies that the vhost_list contains the specified vhost
describe rabbitmq_vhost_list do
it { should have_vhost('MyVhost') }
end
Supports the same additional parameters as the curl matcher
Please note: This type requires curl to be installed on the target host
Verifies that the specified policy exists on a given vhost
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should exist }
end
Verifies the high availability mode
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should have_ha_mode 'exactly' }
end
Verifies the number of high availability nodes
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should have_ha_nodes 2 }
end
Verifies the high availability syncronisation mode
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should have_ha_sync_mode 'automatic' }
end
Verifies that the policy is applies to type
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should apply_to }
end
Verifies the policy applies to onlyexchanges
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should mirror_exchanges }
end
Verifies the policy applies to both queues and exchanges
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should mirror_queues }
end
Verifies the policy applies to both queues and exchanges
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should mirror_all }
end
Supports the same additional parameters as the curl matcher
Check the sudo permissions of a given user
Checks if the user exists and is in the sudoers file
describe sudo_user('someuser') do
it { should exist }
end
Ensures the user has no sudo permssions
describe sudo_user('someuser') do
it { should have_sudo_disabled }
end
Ensures the user can run a command
describe sudo_user('someuser') do
it { should be_allowed_to_run_command('/usr/bin/cat /var/log/messages') }
it { should be_allowed_to_run_command('/usr/bin/cat /var/log/messages').as('user6') }
it { should be_allowed_to_run_command('/usr/bin/cat /var/log/messages').as('user6').without_password }
it { should be_allowed_to_run_command('/usr/bin/cat /var/log/secure').without_password }
it { should be_allowed_to_run_command('/usr/bin/cat /tmp/logs').as_anybody }
it { should be_allowed_to_run_command('/usr/bin/cat /tmp/logs').as_anybody.without_password }
end
Ensures the user can run a anything
describe sudo_user('someuser') do
it { should be_allowed_run_anything }
# Without password
it { should be_allowed_run_anything.without_a_password }
#As a particular user
it { should be_allowed_run_anything.as('someotheruser') }
#As a particular user with out a password
it { should be_allowed_run_anything.as('someotheruser').without_a_password }
#As any user
it { should be_allowed_run_anything.as_anybody }
#As Any user without a password
it { should be_allowed_run_anything.as_anybody.without_password }
end
Provides a type and matchers for checking UNIX plugable authenticaton modules (PAM)
Checks that the pamfile exists in the given directory (default = /etc/pam.d)
describe unix_pam('su') do
it { should exist }
end
Checks that the pamfile has a 'auth' configuration item using the given module
describe unix_pam('su') do
it { should have_auth 'pam_rootok.so'}
end
This match also support the following matcher chains:
describe unix_pam('su') do
## Control Flag Chain matchers
# Check if module is a required module
it { should have_auth('pam_rootok.so').required }
# Check if module is a requisite module
it { should have_auth('pam_rootok.so').requisite }
# Check if module is a sufficient module
it { should have_auth('pam_rootok.so').sufficient }
# Check if module is a optional module
it { should have_auth('pam_rootok.so').optional }
#Check for a particular control flag (with_control and with_flag are provided as aliases)
it { should have_auth('pam_unix.so').with_control_flag('[success=1 default=ignore]') }
## Argument chain matchers
#Single arg
it { should have_auth('pam_unix.so').with_arg('nullok_secure') }
it { should have_auth('pam_unix.so').with_argument('nullok_secure') }
#Multiple args
it { should have_auth('pam_wheel.so').with_args(['deny', 'group=nosu']) }
it { should have_auth('pam_wheel.so').with_arguments(['deny', 'group=nosu']) }
end
Checks that the pamfile has a 'session' configuration item using the given module
describe unix_pam('su') do
it { should have_session 'pam_env.so'}
end
This matcher supports all the chains of the have_auth matcher (see above)
Checks that the pamfile has a 'account' configuration item using the given module
describe unix_pam('common-account') do
it { should have_account 'pam_deny.so'}
end
This matcher supports all the chains of the have_auth matcher (see above)
Checks that the pamfile has a 'account' configuration item using the given module
describe unix_pam('common-password') do
it { should have_password 'pam_deny.so'}
end
This matcher supports all the chains of the have_auth matcher (see above)
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/andrewwardrobe/serverspec-extra-types. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Serverspec::Extra::Types project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.