Skip to content

Commit

Permalink
Merge pull request sous-chefs#1049 from smcavallo/lint
Browse files Browse the repository at this point in the history
Chefspec tests for creating containers and windows support
  • Loading branch information
tas50 authored Dec 17, 2018
2 parents 7e668a9 + 8b583f2 commit edc4aea
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 56 deletions.
24 changes: 17 additions & 7 deletions libraries/docker_container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class DockerContainer < DockerBase
# Used to store the state of the Docker container
property :container, Docker::Container, desired_state: false

# Used to store the state of the Docker container create options
property :create_options, Hash, default: {}, desired_state: false

# Used by :stop action. If the container takes longer than this
# many seconds to stop, kill it instead. A nil value (the default) means
# never kill the container.
Expand Down Expand Up @@ -436,10 +439,10 @@ def ip_address_from_container_networks(container)
# We can't assume it will be 'bridged'
# It might also not match the new_resource value
if container.info['NetworkSettings'] &&
container.info['NetworkSettings']['Networks'] &&
container.info['NetworkSettings']['Networks'].values[0] &&
container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig'] &&
container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig']['IPv4Address']
container.info['NetworkSettings']['Networks'] &&
container.info['NetworkSettings']['Networks'].values[0] &&
container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig'] &&
container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig']['IPv4Address']
# Return the ip address listed
container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig']['IPv4Address']
end
Expand Down Expand Up @@ -557,10 +560,17 @@ def load_container_labels
} if new_resource.network_mode
config.merge! net_config

config.merge(
'Healthcheck' => new_resource.health_check
) unless new_resource.health_check.empty?
# Remove any options not supported in windows
if platform?('windows')
config['HostConfig'].delete('MemorySwappiness')
end

unless new_resource.health_check.empty?
config['Healthcheck'] = new_resource.health_check
end

# Store the state of the options and create the container
new_resource.create_options = config
Docker::Container.create(config, connection)
end
end
Expand Down
55 changes: 55 additions & 0 deletions spec/libraries/container_networks_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'spec_helper'
require 'docker'
require_relative '../../libraries/docker_base'
require_relative '../../libraries/docker_container'

describe DockerCookbook::DockerContainer do
let(:resource) { DockerCookbook::DockerContainer.new('hello_world') }

it 'has a default action of [:run]' do
expect(resource.action).to eql([:run])
end

describe 'gets ip_address_from_container_networks' do
let(:options) { { 'id' => rand(10_000).to_s } }
subject do
Docker::Container.send(:new, Docker.connection, options)
end
# https://docs.docker.com/engine/api/version-history/#v121-api-changes
context 'when docker API < 1.21' do
let(:ip_address) { '10.0.0.1' }
let(:options) do
{
'id' => rand(10_000).to_s,
'IPAddress' => ip_address,
}
end
it 'gets ip_address as nil' do
actual = resource.ip_address_from_container_networks(subject)
expect { resource.ip_address_from_container_networks(subject) }.not_to raise_error
expect(actual).to eq(nil)
end
end
context 'when docker API > 1.21' do
let(:ip_address) { '10.0.0.1' }
let(:options) do
{
'id' => rand(10_000).to_s,
'NetworkSettings' => {
'Networks' => {
'bridge' => {
'IPAMConfig' => {
'IPv4Address' => ip_address,
},
},
},
},
}
end
it 'gets ip_address' do
actual = resource.ip_address_from_container_networks(subject)
expect(actual).to eq(ip_address)
end
end
end
end
157 changes: 114 additions & 43 deletions spec/libraries/container_spec.rb
Original file line number Diff line number Diff line change
@@ -1,55 +1,126 @@
# Load all the libraries
require 'spec_helper'
require 'chef'
require 'docker'
Dir['libraries/*.rb'].each { |f| require File.expand_path(f) }
require 'excon'

describe DockerCookbook::DockerContainer do
let(:resource) { DockerCookbook::DockerContainer.new('hello_world') }
require_relative '../../libraries/docker_base'
require_relative '../../libraries/docker_container'

it 'has a default action of [:run]' do
expect(resource.action).to eql([:run])
describe 'docker_container' do
step_into :docker_container
platform 'ubuntu'

# Info returned by docker api
# https://docs.docker.com/engine/api/v1.39/#tag/Container
let(:container) do
{
'Id' => '123456789',
'IPAddress' => '10.0.0.1',
'Image' => 'ubuntu:bionic',
'Names' => ['/hello_world'],
'Config' => { 'Labels' => {} },
'HostConfig' => { 'RestartPolicy' => { 'Name' => 'unless-stopped',
'MaximumRetryCount' => 1 },
'Binds' => [],
'ReadonlyRootfs' => false },
'State' => 'not running',
'Warnings' => [],
}.to_json
end
# https://docs.docker.com/engine/api/v1.39/#tag/Image
let(:image) do
{ 'Id' => 'bf119e2',
'Repository' => 'ubuntu', 'Tag' => 'bionic',
'Created' => 1_364_102_658, 'Size' => 24_653,
'VirtualSize' => 180_116_135,
'Config' => { 'Labels' => {} } }.to_json
end
# https://docs.docker.com/engine/api/v1.39/#operation/SystemInfo
let(:info) do
{ 'Labels' => {} }.to_json
end
# https://docs.docker.com/engine/api/v1.39/#operation/ContainerCreate
let(:create) do
{
'Id' => 'e90e34656806',
'Warnings' => [],
}.to_json
end

describe 'gets ip_address_from_container_networks' do
let(:options) { { 'id' => rand(10_000).to_s } }
subject do
Docker::Container.send(:new, Docker.connection, options)
end
# https://docs.docker.com/engine/api/version-history/#v121-api-changes
context 'when docker API < 1.21' do
let(:ip_address) { '10.0.0.1' }
let(:options) do
{
'id' => rand(10_000).to_s,
'IPAddress' => ip_address
}
end
it 'gets ip_address as nil' do
actual = resource.ip_address_from_container_networks(subject)
expect { resource.ip_address_from_container_networks(subject) }.not_to raise_error
expect(actual).to eq(nil)
before do
# Ensure docker api calls are mocked
# It is low level much easier to do in Excon
# Plus, the low level mock allows testing this cookbook
# for multiple docker apis and docker-api gems
# https://github.com/excon/excon#stubs
Excon.defaults[:mock] = true
Excon.stub({ method: :get, path: '/v1.16/containers/hello_world/json' }, body: container, status: 200)
Excon.stub({ method: :get, path: '/v1.16/images/ubuntu:bionic/json' }, body: image, status: 200)
Excon.stub({ method: :get, path: '/v1.16/info' }, body: info, status: 200)
Excon.stub({ method: :delete, path: '/v1.16/containers/123456789' }, body: '', status: 200)
Excon.stub({ method: :post, path: '/v1.16/containers/create' }, body: create, status: 200)
Excon.stub({ method: :get, path: '/v1.16/containers/123456789/start' }, body: '', status: 200)
end

context 'creates a docker container with default options' do
recipe do
docker_container 'hello_world' do
tag 'ubuntu:latest'
action :create
end
end
context 'when docker API > 1.21' do
let(:ip_address) { '10.0.0.1' }
let(:options) do
{
'id' => rand(10_000).to_s,
'NetworkSettings' => {
'Networks' => {
'bridge' => {
'IPAMConfig' => {
'IPv4Address' => ip_address
}
}
}
}
}

it {
expect { chef_run }.to_not raise_error
expect(chef_run).to create_docker_container('hello_world').with(
tag: 'ubuntu:latest',
create_options: { 'name' => 'hello_world', 'Image' => 'hello_world:ubuntu:latest', 'Labels' => {}, 'Cmd' => nil, 'AttachStderr' => false, 'AttachStdin' => false, 'AttachStdout' => false, 'Domainname' => '', 'Entrypoint' => nil, 'Env' => [], 'ExposedPorts' => {}, 'Hostname' => nil, 'MacAddress' => nil, 'NetworkDisabled' => false, 'OpenStdin' => false, 'StdinOnce' => false, 'Tty' => false, 'User' => '', 'Volumes' => {}, 'WorkingDir' => '', 'HostConfig' => { 'Binds' => nil, 'CapAdd' => nil, 'CapDrop' => nil, 'CgroupParent' => '', 'CpuShares' => 0, 'CpusetCpus' => '', 'Devices' => [], 'Dns' => [], 'DnsSearch' => [], 'ExtraHosts' => nil, 'IpcMode' => '', 'Init' => nil, 'KernelMemory' => 0, 'Links' => nil, 'LogConfig' => nil, 'Memory' => 0, 'MemorySwap' => 0, 'MemorySwappiness' => 0, 'MemoryReservation' => 0, 'NetworkMode' => 'bridge', 'OomKillDisable' => false, 'OomScoreAdj' => -500, 'Privileged' => false, 'PidMode' => '', 'PortBindings' => {}, 'PublishAllPorts' => false, 'RestartPolicy' => { 'Name' => nil, 'MaximumRetryCount' => 0 }, 'ReadonlyRootfs' => false, 'Runtime' => 'runc', 'SecurityOpt' => nil, 'Sysctls' => {}, 'Ulimits' => nil, 'UsernsMode' => '', 'UTSMode' => '', 'VolumesFrom' => nil, 'VolumeDriver' => nil }, 'NetworkingConfig' => { 'EndpointsConfig' => { 'bridge' => { 'IPAMConfig' => { 'IPv4Address' => nil }, 'Aliases' => [] } } } }
)
}
end

context 'creates a docker container with healthcheck options' do
recipe do
docker_container 'hello_world' do
tag 'ubuntu:latest'
health_check(
'Test' =>
[
'string',
],
'Interval' => 0,
'Timeout' => 0,
'Retries' => 0,
'StartPeriod' => 0
)
action :create
end
it 'gets ip_address' do
actual = resource.ip_address_from_container_networks(subject)
expect(actual).to eq(ip_address)
end

it {
expect { chef_run }.to_not raise_error
expect(chef_run).to create_docker_container('hello_world').with(
tag: 'ubuntu:latest',
create_options: { 'name' => 'hello_world', 'Image' => 'hello_world:ubuntu:latest', 'Labels' => {}, 'Cmd' => nil, 'AttachStderr' => false, 'AttachStdin' => false, 'AttachStdout' => false, 'Domainname' => '', 'Entrypoint' => nil, 'Env' => [], 'ExposedPorts' => {}, 'Hostname' => nil, 'MacAddress' => nil, 'NetworkDisabled' => false, 'OpenStdin' => false, 'StdinOnce' => false, 'Tty' => false, 'User' => '', 'Volumes' => {}, 'WorkingDir' => '', 'HostConfig' => { 'Binds' => nil, 'CapAdd' => nil, 'CapDrop' => nil, 'CgroupParent' => '', 'CpuShares' => 0, 'CpusetCpus' => '', 'Devices' => [], 'Dns' => [], 'DnsSearch' => [], 'ExtraHosts' => nil, 'IpcMode' => '', 'Init' => nil, 'KernelMemory' => 0, 'Links' => nil, 'LogConfig' => nil, 'Memory' => 0, 'MemorySwap' => 0, 'MemorySwappiness' => 0, 'MemoryReservation' => 0, 'NetworkMode' => 'bridge', 'OomKillDisable' => false, 'OomScoreAdj' => -500, 'Privileged' => false, 'PidMode' => '', 'PortBindings' => {}, 'PublishAllPorts' => false, 'RestartPolicy' => { 'Name' => nil, 'MaximumRetryCount' => 0 }, 'ReadonlyRootfs' => false, 'Runtime' => 'runc', 'SecurityOpt' => nil, 'Sysctls' => {}, 'Ulimits' => nil, 'UsernsMode' => '', 'UTSMode' => '', 'VolumesFrom' => nil, 'VolumeDriver' => nil }, 'NetworkingConfig' => { 'EndpointsConfig' => { 'bridge' => { 'IPAMConfig' => { 'IPv4Address' => nil }, 'Aliases' => [] } } }, 'Healthcheck' => { 'Test' => ['string'], 'Interval' => 0, 'Timeout' => 0, 'Retries' => 0, 'StartPeriod' => 0 } }
)
}
end

context 'creates a docker container with default options for windows' do
platform 'windows'
recipe do
docker_container 'hello_world' do
tag 'ubuntu:latest'
action :create
end
end

it {
expect { chef_run }.to_not raise_error
expect(chef_run).to create_docker_container('hello_world').with(
tag: 'ubuntu:latest',
# Should be missing 'MemorySwappiness'
create_options: { 'name' => 'hello_world', 'Image' => 'hello_world:ubuntu:latest', 'Labels' => {}, 'Cmd' => nil, 'AttachStderr' => false, 'AttachStdin' => false, 'AttachStdout' => false, 'Domainname' => '', 'Entrypoint' => nil, 'Env' => [], 'ExposedPorts' => {}, 'Hostname' => nil, 'MacAddress' => nil, 'NetworkDisabled' => false, 'OpenStdin' => false, 'StdinOnce' => false, 'Tty' => false, 'User' => '', 'Volumes' => {}, 'WorkingDir' => '', 'HostConfig' => { 'Binds' => nil, 'CapAdd' => nil, 'CapDrop' => nil, 'CgroupParent' => '', 'CpuShares' => 0, 'CpusetCpus' => '', 'Devices' => [], 'Dns' => [], 'DnsSearch' => [], 'ExtraHosts' => nil, 'IpcMode' => '', 'Init' => nil, 'KernelMemory' => 0, 'Links' => nil, 'LogConfig' => nil, 'Memory' => 0, 'MemorySwap' => 0, 'MemoryReservation' => 0, 'NetworkMode' => 'bridge', 'OomKillDisable' => false, 'OomScoreAdj' => -500, 'Privileged' => false, 'PidMode' => '', 'PortBindings' => {}, 'PublishAllPorts' => false, 'RestartPolicy' => { 'Name' => nil, 'MaximumRetryCount' => 0 }, 'ReadonlyRootfs' => false, 'Runtime' => 'runc', 'SecurityOpt' => nil, 'Sysctls' => {}, 'Ulimits' => nil, 'UsernsMode' => '', 'UTSMode' => '', 'VolumesFrom' => nil, 'VolumeDriver' => nil }, 'NetworkingConfig' => { 'EndpointsConfig' => { 'bridge' => { 'IPAMConfig' => { 'IPv4Address' => nil }, 'Aliases' => [] } } } }
)
}
end
end
6 changes: 3 additions & 3 deletions spec/libraries/image_prune_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Load all the libraries
require 'chef'
Dir['libraries/*.rb'].each { |f| require File.expand_path(f) }
require 'spec_helper'
require_relative '../../libraries/docker_base'
require_relative '../../libraries/docker_image_prune'

describe DockerCookbook::DockerImagePrune do
let(:resource) { DockerCookbook::DockerImagePrune.new('rspec') }
Expand Down
6 changes: 3 additions & 3 deletions test/cookbooks/docker_test/recipes/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1223,15 +1223,15 @@
docker_container 'health_check' do
repo 'alpine'
tag '3.1'
health_check({
health_check(
'Test' =>
[
'string',
],
'Interval' => 0,
'Timeout' => 0,
'Retries' => 0,
'StartPeriod' => 0,
})
'StartPeriod' => 0
)
action :run
end

0 comments on commit edc4aea

Please sign in to comment.