9. Create a Custom Resource - The Docker Example
Now let's try a more complicated example. Let's say we want to create a resource that can parse a docker-compose
file.
9.1. Create new profile and setup docker files
First, let's write our docker compose file docker-compose.yml
version: '3'
services:
workstation:
container_name: workstation
image: learnchef/inspec_workstation
stdin_open: true
tty: true
links:
- target
volumes:
- .:/root
target:
image: learnchef/inspec_target
stdin_open: true
tty: true
We will continue writing our controls to check against this docker file:
inspec init profile docker-workstations
9.2. Develop controls to test/run profile
In the controls/example.rb
file, write the control:
describe yaml('file_name') do
its('setting') { should_not eq 'value' }
end
We need to replace the file_name
above with the location of the docker-compose.yml
file. We also need to change the setting
to grab the tag we want to retrieve. Finally we need to change value
with the actual value as shown in the docker compose file.
describe yaml('/path/to/docker-compose.yml') do
its(['services', 'workstation', 'image']) { should eq 'learnchef/inspec_workstation' }
its(['services', 'workstation', 'volumes']) { should cmp '.:/root' }
end
Now if we test this control using the following command we should see all the tests pass.
inspec exec docker-workstations
9.3. Rewrite test to utilize resource
Going back to the control, we will write it using a resource that doesn't exist called docker-compose-config that is going to take a path as a parameter.
describe yaml('/path/to/docker-compose.yml') do
its(['services', 'workstation', 'image']) { should eq 'learnchef/inspec_workstation' }
its(['services', 'workstation', 'volumes']) { should cmp '.:/root' }
end
describe docker_compose_config('/path/to/docker-compose.yml') do
its('services.workstation.image') { should eq 'learnchef/inspec_workstation' }
its('services.workstation.volumes') { should cmp '.:/root' }
end
Now we should see an error if we go back to terminal and run the same command to execute a scan
inspec exec docker-workstations
We should get an error saying the docker_compose_config
method does not yet exist. That's because we have not yet defined this resource.
9.4. Develop docker resource
In the libraries
directory of the profile we will make a docker_compose_config.rb
file, , the content of the file should look like this:
# encoding: utf-8
# copyright: 2019, The Authors
class DockerComposeConfig < Inspec.resource(1)
name 'docker_compose_config'
end
Using InSpec Init to Create the Resource
Alternatively, you can use inspec init resource <your-resource-name>
to create the template for your custom resource. However, make sure you check that the "lib" folder is renamed to "libraries", or that InSpec recognizes the location of your custom resource.
Now when we save and run the profile again using:
inspec exec docker-workstations
We will get an error saying we gave it the wrong number of arguments: was given 1 but expected 0
. This is because every class in Ruby that has a parameter must have an initialize function to accept that parameter.
# encoding: utf-8
# copyright: 2019, The Authors
class DockerComposeConfig < Inspec.resource(1)
name 'docker_compose_config'
def initialize(path)
@path = path
end
end
Now let's run the profile once more.
inspec exec docker-workstations
This time the profile runs, but we get a message that the docker_compose_config
resource does not have the services
method. So let's define that method now:
# encoding: utf-8
# copyright: 2019, The Authors
class DockerComposeConfig < Inspec.resource(1)
name 'docker_compose_config'
def initialize(path)
@path = path
end
def services
end
end
Start by just defining the services
method. Then, let's run the profile once more.
inspec exec docker-workstations
Now we got a different failure that tells us a nil
value was returned. So now we will go ahead and define the services method. We will use an already existing InSpec resource to parse the path file.
# encoding: utf-8
# copyright: 2019, The Authors
class DockerComposeConfig < Inspec.resource(1)
name 'docker_compose_config'
def initialize(path)
@path = path
@yaml = inspec.yaml(path)
end
def services
@yaml['services']
end
end
Now let's run the profile once more.
inspec exec docker-workstations
You will notice that it parses it correctly, but instead of our result we end up getting a hash. We need to convert the hash into an object that appears like other objects so that we may use our dot notation. So we will wrap our hash in a Ruby class called a Hashie::Mash
. This gives us a quick way to convert a hash into a Ruby object with a number of methods attached to it. You will have to import the Hashie library by running gem install hashie
and import it in the resource file to be used. It and is written as follows:
# encoding: utf-8
# copyright: 2019, The Authors
require "hashie/mash"
class DockerComposeConfig < Inspec.resource(1)
name 'docker_compose_config'
def initialize(path)
@path = path
@yaml = inspec.yaml(path)
end
def services
Hashie::Mash.new(@yaml['services'])
end
end
Lets run the profile again.
inspec exec docker-workstations
Everything passed!
Check your work
Check your work with the InSpec video below that walks through this docker resource example!