Module: Dockerspec::RSpec::Resources

Defined in:
lib/dockerspec/rspec/resources.rb,
lib/dockerspec/rspec/resources/its_container.rb

Overview

Some resources included inside RSpec::Core::ExampleGroup to build and run Docker containers.

Load the Test Engine You Want to Use

If you want to run Serverspec tests, you need to require the dockerspec/serverspec path:

require 'dockerspec/serverspec'

If you want to run Infrataster tests, you need to require the dockerspec/infrataster path:

require 'dockerspec/infrataster'

Of course, you can load both engines:

require 'dockerspec/serverspec'
require 'dockerspec/infrataster'

RSpec Settings

  • dockerfile_path: The dockerfile path.
  • rm_build: Whether to remove the build after the run.
  • log_level: Log level to use by default.
  • docker_wait: Seconds to wait before running the tests.
  • container_name: Docker container to test with Docker Compose.

All the RSpec settings are optional.

Examples:

RSpec Settings

RSpec.configure do |config|
  config.log_level = :silent
end

Defined Under Namespace

Classes: ItsContainer

Instance Method Summary collapse

Instance Method Details

#described_container(value = nil) ⇒ Symbol

Sets or gets the latest run container name.

Used to call the Infrataster #server method.

Examples:

Testing a Docker Container

describe docker_run('myapp') do
  describe server(described_container) do
    describe http('/') do
      it 'responds content including "My App Homepage"' do
        expect(response.body).to match(/My App Homepage/i)
      end
    end
  end
end

Testing with Docker Compose

describe docker_compose('docker-compose.yml', wait: 60) do
  its_container(:wordpress) do
    describe server(described_container) do
      describe http('/wp-admin/install.php') do
        it 'responds content including "Wordpress Installation"' do
          expect(response.body).to match(/WordPress .* Installation/i)
        end
      end
    end
  end
end

Parameters:

  • value (Symbol, String) (defaults to: nil)

    The container name.

Returns:

  • (Symbol)

    The container name.



589
590
591
592
593
594
# File 'lib/dockerspec/rspec/resources.rb', line 589

def described_container(value = nil)
  # rubocop:disable Style/ClassVars
  @@described_container = value unless value.nil?
  # rubocop:enable Style/ClassVars
  @@described_container.to_sym
end

#described_image(value = nil) ⇒ String

Sets or gets the latest run container name.

This can be used to avoid adding a tag to the build image.

Examples:

describe docker_build('.') do
  describe docker_run(described_image, family: 'debian') do
    # [...]
  end
end

Parameters:

  • value (String) (defaults to: nil)

    The docker image id.

Returns:

  • (String)

    The docker image id.



547
548
549
550
551
552
# File 'lib/dockerspec/rspec/resources.rb', line 547

def described_image(value = nil)
  # rubocop:disable Style/ClassVars
  @@described_image = value unless value.nil?
  # rubocop:enable Style/ClassVars
  @@described_image
end

#docker_build(*opts) ⇒ Dockerspec::Builder

Builds a Docker image.

The image can be build from a path or from a string.

See the Builder::ConfigHelpers documentation for more information about the available RSpec resource helpers.

Examples:

A Simple Example

describe 'My Dockerfile' do
  describe docker_build('.') do
    it { should have_maintainer /John Doe/ }
    it { should have_cmd ['/bin/dash'] }
    it { should have_expose '80' }
  end
end

A Complete Example

describe docker_build(path: '.') do
  it { should have_maintainer 'John Doe "[email protected]"' }
  it { should have_maintainer(/John Doe/) }
  it { should have_cmd %w(2 2000) }
  it { should have_label 'description' }
  it { should have_label 'description' => 'My Container' }
  it { should have_expose '80' }
  it { should have_expose(/80$/) }
  it { should have_env 'container' }
  it { should have_env 'container' => 'docker' }
  it { should have_env 'PATH' => '/tmp/bin:/sbin:/bin' }
  it { should have_entrypoint ['sleep'] }
  it { should have_volume '/volume1' }
  it { should have_volume %r{/vol.*2} }
  it { should have_user 'nobody' }
  it { should have_workdir '/opt' }
  it { should have_workdir %r{^/op} }
  it { should have_onbuild 'RUN echo onbuild' }
  it { should have_stopsignal 'SIGTERM' }
end

Checking the Attribute Values Using the its Method

describe docker_build(path: '.') do
  its(:maintainer) { should eq 'John Doe "[email protected]"' }
  its(:cmd) { should eq %w(2 2000) }
  its(:labels) { should include 'description' }
  its(:labels) { should include 'description' => 'My Container' }
  its(:exposes) { should include '80' }
  its(:env) { should include 'container' }
  its(:env) { should include 'container' => 'docker' }
  its(:entrypoint) { should eq ['sleep'] }
  its(:volumes) { should include '/volume1' }
  its(:user) { should eq 'nobody' }
  its(:workdir) { should eq '/opt' }
  its(:onbuilds) { should include 'RUN echo onbuild' }
  its(:stopsignal) { should eq 'SIGTERM' }
end

Checking Its Size and OS

describe docker_build(path: '.') do
  its(:size) { should be < 20 * 2**20 } # 20M
  its(:arch) { should eq 'amd64' }
  its(:os) { should eq 'linux' }
end

Building from a File

describe docker_build(path: '../dockerfiles/Dockerfile-nginx') do
  # [...]
end

Building from a Template

describe docker_build(template: 'Dockerfile1.erb') do
  # [...]
end

Building from a Template with a Context

describe docker_build(
  template: 'Dockerfile1.erb', context: {version: '8'}
) do
  it { should have_maintainer(/John Doe/) }
  it { should have_cmd %w(/bin/sh) }
  # [...]
end

Building from a String

describe docker_build(string: "FROM nginx:1.9\n [...]") do
  # [...]
end

Building from a Docker Image ID

describe docker_build(id: '07d362aea98d') do
  # [...]
end

Building from a Docker Image Name

describe docker_build(id: 'nginx:1.9') do
  # [...]
end

Parameters:

  • opts (String, Hash)

    The :path or a list of options.

Options Hash (*opts):

  • :path (String) — default: '.'

    The directory or file that contains the Dockerfile. By default tries to read it from the DOCKERFILE_PATH environment variable and uses '.' if it is not set.

  • :string (String)

    Use this string as Dockerfile instead of :path. Not set by default.

  • :template (String)

    Use this Erubis template file as Dockerfile.

  • :id (String)

    Use this Docker image ID instead of a Dockerfile.

  • :rm (Boolean)

    Whether to remove the generated docker images after running the tests. By default only removes them if it is running on a CI machine.

  • :context (Hash, Erubis::Context) — default: {}

    Template context used when the :template source is used.

  • :tag (String)

    Repository tag to be applied to the resulting image.

  • :log_level (Integer, Symbol)

    Sets the docker library verbosity level. Possible values: :silent or 0 (no output), :ci or 1 (enables some outputs recommended for CI environments), :info or 2 (gives information about main build steps), :debug or 3 (outputs all the provided information in its raw original form).

Returns:

Raises:

See Also:



207
208
209
210
211
212
# File 'lib/dockerspec/rspec/resources.rb', line 207

def docker_build(*opts)
  builder = Dockerspec::Builder.new(*opts)
  builder.build
  described_image(builder.id)
  builder
end

#docker_compose(*opts) ⇒ String

Runs Docker Compose.

By default tries to detect the most appropriate Docker backend: native or LXC.

Examples:

Testing Containers from a YAML File

describe docker_compose('docker-compose.yml', wait: 15) do
  its_container(:myapp) do
    describe process('apache2') do
      it { should be_running }
    end
    # [...]
  end
  its_container(:db) do
    describe process('mysqld') do
      it { should be_running }
    end
    # [...]
  end
end

Testing Only One Container from a Directory

describe docker_compose('data/', container: :myapp, wait: 15) do
  describe process('apache2') do
    it { should be_running }
  end
  # [...]
end

Avoid Automatic OS Detection to Speed Up the Tests

describe docker_compose(
  'data/', container: :myapp, family: 'debian', wait: 15
) do
  describe process('apache2') do
    it { should be_running }
  end
  # [...]
end

Parameters:

  • opts (String, Hash)

    The :file or a list of options.

Options Hash (*opts):

  • :file (String)

    The compose YAML file or a directory containing the 'docker-compose.yml' file.

  • :container (Symbol, String)

    The name of the container to test. It is better to use #its_container if you want to test multiple containers.

  • :rm (Boolean) — default: calculated

    Whether to remove the Docker containers afterwards.

  • :family (Symbol, String) — default: calculated

    The OS family. It's automatically detected by default, but can be used to speed up the tests. Some possible values: :alpine, :arch, :coreos, :debian, :gentoo, :nixos, :plamo, :poky, :redhat, :suse.

  • :backend (Symbol) — default: calculated

    Docker backend to use: :docker_compose, :docker_compose_lxc.

Returns:

  • (String)

    A description of the object.

Raises:



434
435
436
437
438
439
440
441
# File 'lib/dockerspec/rspec/resources.rb', line 434

def docker_compose(*opts)
  runner = Dockerspec::Configuration.compose_runner.new(*opts)
  runner.run
  # Disable storing Runner object on RSpec metadata, to avoid calling its
  # {Runner#restore_rspec_context} method that it is also called in
  # {ItsContainer#restore_rspec_context}:
  runner.to_s
end

#docker_run(*opts) ⇒ Dockerspec::Runner::Docker

Runs a docker image.

See the Serverspec Resource Types documentation to see the available resources.

By default tries to detect the most appropriate Docker backend: native or LXC.

Examples:

A Basic Example to Test the HTTPd Service

describe docker_build('.', tag: 'myapp') do
  describe docker_run('myapp') do
    describe service('httpd') do
      it { should be_enabled }
      it { should be_running }
    end
    # [...]
  end
end

Avoid Automatic OS Detection to Speed Up the Tests

describe docker_build('.', tag: 'myapp') do
  describe docker_run('myapp', family: 'debian') do
    # [...]
  end
end

Using Hash Format

describe docker_build('.', tag: 'myapp') do
  describe docker_run(tag: 'myapp', family: 'debian') do
    # [...]
  end
end

Force a Specific Docker Backend

describe docker_build('.', tag: 'myapp') do
  describe docker_run('myapp', backend: :lxc) do
    # [...]
  end
end

Use a Backend Not Included by Default

# specinfra-backend-docker_nsenter gem must be installed by hand
require 'specinfra/backend/docker_nsenter'
describe docker_build('.', tag: 'myapp') do
  describe docker_run('myapp', backend: :nsenter) do
    # [...]
  end
end

Running a Container Image Tag

describe docker_run('debian:8') do
  # [...]
end

Testing FROM Dockerfile Instruction

# FROM debian:8
describe docker_run('myapp') do
  describe file('/etc/debian_version') do
    it { should be_file }
    its(:content) { should match /^8\./ }
  end
  # Another way to check it:
  describe command('lsb_release -ri') do
    its(:stdout) { should match /^Distributor ID:\s+Debian/ }
    its(:stdout) { should match /^Release:\s+8\./ }
  end
end

Testing COPY and ADD Dockerfile Instructions

# COPY docker-entrypoint.sh /entrypoint.sh
describe docker_run('myapp') do
  describe file('/entrypoint.sh') do
    it { should be_file }
    its(:content) { should match /^exec java -jar myapp\.jar/ }
  end
end

Testing RUN Dockerfile Instructions

describe docker_run('myapp') do
  # RUN apt-get install -y wget
  describe package('wget') do
    it { should be_installed }
  end
  # RUN useradd -g myapp -d /opt/myapp myapp
  describe user('myapp') do
    it { should exist }
    it { should belong_to_group 'myapp' }
    it { should have_home_directory '/opt/myapp' }
  end
end

Testing with Infrataster

require 'dockerspec/infrataster'
describe docker_run('nginx') do
  describe server(described_container) do
    describe http('/') do
      it 'responds content including "Welcome to nginx!"' do
        expect(response.body).to include 'Welcome to nginx!'
      end
      it 'responds as "nginx" server' do
        expect(response.headers['server']).to match(/nginx/i)
      end
    end
  end
end

Parameters:

  • opts (String, Hash)

    The :tag or a list of options. This configuration options will be passed to the Testing Engines like Infrataster. So you can include your Infrataster server configuration here.

Options Hash (*opts):

  • :tag (String)

    The Docker image tag name to run.

  • :id (String)

    The Docker container ID to use instead of starting a new container.

  • :rm (Boolean) — default: calculated

    Whether to remove the Docker container afterwards.

  • :path (String)

    The environment PATH value of the container.

  • :env (Hash, Array)

    Some ENV instructions to add to the container.

  • :family (Symbol, String) — default: calculated

    The OS family. It's automatically detected by default, but can be used to speed up the tests. Some possible values: :alpine, :arch, :coreos, :debian, :gentoo, :nixos, :plamo, :poky, :redhat, :suse.

  • :backend (Symbol) — default: calculated

    Docker backend to use: :docker, :lxc.

Returns:

Raises:



356
357
358
359
360
361
362
# File 'lib/dockerspec/rspec/resources.rb', line 356

def docker_run(*opts)
  runner = Dockerspec::Configuration.docker_runner.new(*opts)
  runner.run
  runner.restore_rspec_context
  described_container(runner.container_name)
  runner
end

#its_container(container, *opts) { ... } ⇒ Object

Selects the container to test inside #docker_compose.

Examples:

Testing Multiple Containers

describe docker_compose('docker-compose.yml', wait: 15) do
  its_container(:myapp) do
    describe process('apache2') do
      it { should be_running }
      its(:args) { should match(/-DFOREGROUND/) }
    end
    # [...]
  end
  its_container(:db) do
    describe process('mysqld') do
      it { should be_running }
    end
    # [...]
  end
end

Avoid Automatic OS Detection to Speed Up the Tests

describe docker_compose('docker-compose.yml', wait: 15) do
  its_container('myapp', family: 'centos') do
    describe process('httpd') do
      it { should be_running }
    end
    # [...]
  end
  its_container('db', family: 'debian') do
    describe process('mysqld') do
      it { should be_running }
    end
    # [...]
  end
end

Testing a Database with Infrataster using Docker Compose

require 'dockerspec/infrataster'
# After including `gem 'infrataster-plugin-mysql'` in your Gemfile:
require 'infrataster-plugin-mysql'
describe docker_compose('docker-compose.yml', wait: 60) do
  its_container(:db, mysql: { user: 'root', password: 'example' }) do
    describe server(described_container) do
      describe mysql_query('SHOW STATUS') do
        it 'returns positive uptime' do
          row = results.find { |r| r['Variable_name'] == 'Uptime' }
          expect(row['Value'].to_i).to be > 0
        end
      end
    end
  end
end

Parameters:

  • container (Symbol, String)

    The name of the container to test.

  • opts (Hash)

    A list of options. This configuration options will be passed to the Testing Engines like Infrataster. So you can include your Infrataster server configuration here.

Options Hash (*opts):

  • :family (Symbol, String) — default: calculated

    The OS family. It's automatically detected by default, but can be used to speed up the tests. Some possible values: :alpine, :arch, :coreos, :debian, :gentoo, :nixos, :plamo, :poky, :redhat, :suse.

Yields:

  • [] the block to run with the tests.

Returns:

  • void

Raises:

  • (Dockerspec::DockerComposeError)

    Raises this exception when you call its_container without calling docker_compose.



517
518
519
520
521
522
523
524
525
526
527
# File 'lib/dockerspec/rspec/resources.rb', line 517

def its_container(container, *opts, &block)
  compose = Runner::Compose.current_instance
  if compose.nil?
    raise ItsContainerError, ItsContainer::NO_DOCKER_COMPOSE_MESSAGE
  end
  container_opts = opts[0].is_a?(Hash) ? opts[0] : {}
  its_container = ItsContainer.new(container, compose)
  its_container.restore_rspec_context(container_opts)
  described_container(compose.container_name)
  describe(its_container, *opts, &block)
end