ServerspecExtraTypes
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
Requirements
- rabbitmq and consul checks requires curl to be installed on the target
Installation
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
Usage
Add the following to your spec_helper.rb
require 'serverspec-extra-types'
Resource 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
curl(url, options = {})
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
consul_node
Please note: This type requires curl to be installed on the target host
have_datacenter
describe consul_node('consul') do
it { should have_datacenter 'dc1'}
end
have_service
describe consul_node('consul') do
it { should have_datacenter 'dc1'}
end
Supports the same additional parameters as the curl matcher
consul_node_list
Please note: This type requires curl to be installed on the target host
have_node
describe consul_node_list() do
it { should have_node 'consul' }
end
Supports the same additional parameters as the curl matcher
consul_service
Please note: This type requires curl to be installed on the target host
have_id
describe consul_service('consul') do
it { should have_id('7ba9a647-0adb-8c92-b0c9-5b011b3530a8') }
end
have_node
describe consul_service('consul') do
it { should have_node('consul') }
end
have_address
describe consul_service('consul') do
it { should have_address('127.0.0.1') }
end
have_datacenter
describe consul_service('consul') do
it { should have_datacenter('dc1') }
end
have_tagged_addresses
describe consul_service('consul') do
it { should have_tagged_addresses({"lan"=>"127.0.0.1", "wan"=>"127.0.0.1"}) }
end
have_node_meta
describe consul_service('consul') do
it { should ({"consul-network-segment"=>""}) }
end
have_service_kind
describe consul_service('consul') do
it { should have_service_kind('') }
end
have_service_id
describe consul_service('consul') do
it { should have_service_id('consul') }
end
have_service_name
describe consul_service('consul') do
it { should have_service_name('consul') }
end
have_service_tags
describe consul_service('consul') do
it { should ([]) }
end
have_service_address
describe consul_service('consul') do
it { should have_service_address('') }
end
have_service_weights
describe consul_service('consul') do
it { should have_service_weights({"Passing"=>1, "Warning"=>1}) }
end
have_service_meta
describe consul_service('consul') do
it { should ({}) }
end
have_service_port
describe consul_service('consul') do
it { should have_service_port(8300) }
end
have_service_enable_tag_override
describe consul_service('consul') do
it { should have_service_enable_tag_override(false) }
end
have_service_proxy_destination
describe consul_service('consul') do
it { should have_service_proxy_destination('') }
end
have_service_proxy
describe consul_service('consul') do
it { should have_service_proxy({}) }
end
have_service_connect
describe consul_service('consul') do
it { should have_service_connect({}) }
end
have_create_index
describe consul_service('consul') do
it { should have_create_index(9) }
end
have_modify_index
describe consul_service('consul') do
it { should have_modify_index(9) }
end
Supports the same additional parameters as the curl matcher
consul_service_list
Please note: This type requires curl to be installed on the target host
have_service
describe consul_service_list() do
it { should have_service 'consul' }
end
Supports the same additional parameters as the curl matcher
docker_config
exists
Check if config exists
describe docker_config('test.conf') do
it { should exist }
end
have_name
Check if config has a specific name
describe docker_config('test.conf') do
it { should have_name 'test.conf' }
end
have_data
Check if config data matches a string
describe docker_config('test.conf') do
it { should have_data 'Some config data' }
end
have_data64
Check if config base64 encoded data matches a string
describe docker_config('test.conf') do
it { should have_data64 'U29tZSBjb25maWcgZGF0YQ=='}
end
be_labeled /be_labelled
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
docker_container
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
exist
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
be_running
Check if a container is running (from serverspec)
describe docker_container('focused_curie') do
it { should be_running }
end
have_image
Check if container is running a specfic image
describe docker_container('focused_currie') do
it { should have_image('jenkins/jenkins:lts') }
end
have_hostname / has_host_name
Check container hostname
describe docker_container('focused_currie') do
it { should have_hostname('container1') }
end
have_domainname / have_domain_name
Check container domain name
describe docker_container('focused_currie') do
it { should have_domainname('leek.com') }
end
have_user / run_as_user
Check if container is running as the specified user
describe docker_container('focused_currie') do
it { should run_as_user('jenkins') }
end
map_port
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
have_volume
Check the for a volume (from serverspec)
describe docker_container('focused_currie') do
it { it { should have_volume('/tmp','/data') }}
end
have_mount
Check the for mounted volume
describe docker_container('focused_currie') do
it { should have_mount('/var/run/docker.sock', '/var/run/docker.sock') }
end
have_restart_policy
Check the containers restart policy
describe docker_container('focused_currie') do
it { should have_restart_policy('always') }
end
have_restart_limit
Check the containers restart limit
describe docker_container('focused_currie') do
it { should have_restart_limit(1) }
end
have_host
Check for additional /etc/hosts entries
describe docker_container('focused_currie') do
it { should have_host('8.8.8.8 dns') }
end
have_environment_variable
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
be_privileged
Check if the container runs in privileged mode
describe docker_container('focused_currie') do
it { should be_privileged }
end
publishes_all_ports
Check if the container publishes all exposed ports
describe docker_container('focused_currie') do
it { should publishes_all_ports }
end
docker_node
exist
Check if docker node exists
describe docker_node('somehost') do
it { should exist }
end
be_manager / be_a_manager
Check if docker node is a manager node
describe docker_node(`hostname -f`.chomp) do
it { should be_a_manager }
end
be_worker / be_a_worker
Check if docker node exists
describe docker_node('somehost') do
it { should be_a_worker }
end
have_engine_version
Check engine version
describe docker_node('somehost') do
it { should have_engine_version '18.09.1' }
end
be_active
Check if node is active
describe docker_node('somehost') do
it { should be_active }
end
be_draining
Check if node is draining
describe docker_node('somehost') do
it { should be_draining }
end
be_paused
Check if node is paused
describe docker_node('somehost') do
it { should be_paused }
end
docker_network
exist
Check if network exists
describe docker_network('test_network') do
it { should exist }
end
be_attachable
Check if network is attachable
describe docker_network('test_network') do
it { should be_attachable }
end
be_swarm_scoped
Check if network is swarm scoped
describe docker_network('test_network') do
it { should be_swarm_scoped }
end
have_driver
Check if network uses a specific driver
describe docker_network('test_network') do
it { should have_driver('overlay') }
end
be_overlay
Check if network is an overlay network
describe docker_network('test_network') do
it { should }
end
be_internal
Check if network is internal
describe docker_network('test_network') do
it { should_not be_internal }
end
be_ingress
Check if network is an ingress network
describe docker_network('test_network') do
it { should_not be_ingress }
end
be_IPv6_enabled
Check if network is IPv6 enabled
describe docker_network('test_network') do
it { should_not be_IPv6_enabled }
end
docker_secret
exists
Check if secret exists
describe docker_secret('secret.key') do
it { should exist }
end
have_name
Check if secret has a specific name
describe docker_secret('secret.key') do
it { should have_name 'secret.key' }
end
be_labeled /be_labelled
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
docker_service
exist
Check if a service exists by name
describe docker_service('my_service') do
it { should exist }
end
have_image
Check if service is running a specfic image
describe docker_service('my-awesome-service') do
it { should have_image('jenkins/jenkins:lts') }
end
have_user / run_as_user
Check if service is running as the specified user
describe docker_service('my-awesome-service') do
it { should run_as_user('jenkins') }
end
map_port
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
have_volume
Check the for a volume (from serverspec)
describe docker_service('my-awesome-service') do
it { it { should have_volume('/tmp','/data') }}
end
have_mount
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
have_restart_policy
Check the services restart policy
describe docker_service('my-awesome-service') do
it { should have_restart_policy('any') }
end
have_restart_limit
Check the services restart limit
describe docker_service('my-awesome-service') do
it { should have_restart_limit(1) }
end
have_host
Check for additional /etc/hosts entries
describe docker_service('my-awesome-service') do
it { should have_host('8.8.8.8 dns') }
end
have_environment_variable
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
be_labeled/be_labelled
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
have_config
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
have_secret
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
be_global
Check the service is running in global mode
describe docker_service('my-awesome-service') do
it { should be_global }
end
be_replicated
Check the service is running in replicated mode
describe docker_service('my-awesome-service') do
it { should be_replicated }
end
have_replica_count
Check the number of replicas the service has
describe docker_service('my-awesome-service') do
it { should have_replica_count 2 }
end
have_placement_constraint
Check the number of replicas the service has
describe docker_service('my-awesome-service') do
it { should have_placement_constraint('node.role == manager') }
end
jenkins_credential(credential_id)
Please note: This type requires curl to be installed on the target host
exists
Verifies that the credential exists
describe jenkins_credential('someCredential') do
it { should exist }
end
have_description(description)
Checks if the credential description matches the specified text
describe jenkins_credential('someCredential') do
it { should have_description 'Username and Password Credential' }
end
have_display_name(text)
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
be_username_with_password
Checks if credential is a username and password
describe jenkins_credential('someCredential') do
it { should be_username_with_password }
end
be_secret_text
Checks if credential is a secret string
describe jenkins_credential('someCredential') do
it { should be_secret_text }
end
be_ssh_private_key
Checks if credential is an ssh private key
describe jenkins_credential('someCredential') do
it { should be_ssh_private_key }
end
be_aws_credential
Checks if credential is an aws access key/ secret tken pair
describe jenkins_credential('someCredential') do
it { should be_aws_credential }
end
be_gitlab_api_token
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
jenkins_job(jobname)
Please note: This type requires curl to be installed on the target host
exists
Verifies that the job exists
describe jenkins_job('someJob') do
it { should exist }
end
have_name(name)
Verifies that the job has the specified name
describe jenkins_job('someJob') do
it { should have_name 'someJob' }
end
have_display_name(displayName)
Verifies that the job has the specified display name
describe jenkins_job('someJob') do
it { should have_display_name 'Some Job' }
end
have_full_display_name(displayName)
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
have_description(description)
Verifies that the job has the specified display name
describe jenkins_job('someJob') do
it { should have_description 'Some Job' }
end
be_freestyle / be_freestyle_project
Verifies that the job is a free style project
describe jenkins_job('freestyle') do
it { should be_freestyle }
it { should be_freestyle_project }
end
be_maven / be_maven_project
Verifies that the job is a maven project
describe jenkins_job('maven') do
it { should be_maven }
it { should be_maven_project }
end
be_pipeline / be_pipeline_project
Verifies that the job is a pipeline project
describe jenkins_job('pipeleine') do
it { should be_pipeline }
it { should be_pipeline_project }
end
be_multibranch / be_multibranch_project
Verifies that the job is a multibranch project
describe jenkins_job('multibranch') do
it { should be_multibranch }
it { should be_multibranch_project }
end
be_folder / be_directory
Verifies that the job is a folder
describe jenkins_job('folder') do
it { should be_multibranch }
it { should be_multibranch_project }
end
have_job_type(type) / have_project_type(type)
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
have_job(job)
Checks if the folder conatins the specified job
describe jenkins_job('folder') do
it { should have_job 'job' }
end
have_job_count(count)
Checks if the folder conatins the specified number of jobs
describe jenkins_job('folder') do
it { should have_job_count 1 }
end
have_empty_job_list
Checks if the folder conatains no jobs
describe jenkins_job('folder') do
it { should have_empty_job_list }
end
Jobs inside folders
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
jenkins_plugin
Please note: This type requires curl to be installed on the target host
exists
Checks if the plugin exists and is installed
describe jenkins_plugin('ssh-slaves') do
it { should exist }
end
has_version
Check is plugin has a particular version
describe jenkins_plugin('ssh-slaves') do
it { should have_version '1.29.4' }
end
be_installed
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
nfs_export(export)
exist
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').('rw,root_squash') }
it { should have_host('192.168.1.0/16').(%q[rw root_squash subtree_check]) }
end
have_host
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').('rw,root_squash') }
# Check for a list of options as an array
it { should have_host('192.168.1.0/16').(%q[rw root_squash subtree_check]) }
end
rabbitmq_node_list
Please note: This type requires curl to be installed on the target host
have_count
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
rabbitmq_user_permission
Please note: This type requires curl to be installed on the target host
read_from_queue(vhost, queue)
Verifies that the user can read from the specifed queue on the specified vhost
describe ('MyUser') do
it { should read_from_queue('MyVhost', 'Q1') }
end
write_to_queue(vhost, queue)
Verifies that the user can read from the specifed queue on the specified vhost
describe ('MyUser') do
it { should write_to_queue('MyVhost', 'Q1') }
end
configure_queue(vhost, queue)
Verifies that the user can configure the specified queue on the specified vhost
describe ('MyUser') do
it { should configure_queue('MyVhost', 'Q1') }
end
Supports the same additional parameters as the curl matcher
rabbitmq_vhost_list
Please note: This type requires curl to be installed on the target host
have_vhost(vhost)
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
rabbitmq_vhost_policy(policy, vhost)
Please note: This type requires curl to be installed on the target host
exists
Verifies that the specified policy exists on a given vhost
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should exist }
end
have_ha_mode(mode)
Verifies the high availability mode
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should have_ha_mode 'exactly' }
end
have_ha_nodes(count)
Verifies the number of high availability nodes
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should have_ha_nodes 2 }
end
have_ha_sync_mode(mode)
Verifies the high availability syncronisation mode
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should have_ha_sync_mode 'automatic' }
end
apply_to(type)
Verifies that the policy is applies to type
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should apply_to }
end
mirror_exchanges
Verifies the policy applies to onlyexchanges
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should mirror_exchanges }
end
mirror_queues
Verifies the policy applies to both queues and exchanges
describe rabbitmq_vhost_policy('ha-all', 'MyVhost') do
it { should mirror_queues }
end
mirror_all
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
sudo_user
Check the sudo permissions of a given user
exist
Checks if the user exists and is in the sudoers file
describe sudo_user('someuser') do
it { should exist }
end
have_sudo_disabled
Ensures the user has no sudo permssions
describe sudo_user('someuser') do
it { should have_sudo_disabled }
end
be_allowed_to_run_command
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
be_allowed_to_run_anything
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
unix_pam(pamfile, dir='/etc/pam.d' )
Provides a type and matchers for checking UNIX plugable authenticaton modules (PAM)
exist
Checks that the pamfile exists in the given directory (default = /etc/pam.d)
describe unix_pam('su') do
it { should exist }
end
have_authentication(module)/have_auth(module)
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
have_session(module)
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)
have_account(module)
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)
have_password(module)
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)
Development
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.
Contributing
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.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
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.