Skip to content

Commit

Permalink
adding in type/provider for docker stack (#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
davejrt authored and sheenaajay committed Feb 18, 2019
1 parent 593d234 commit 686e2ae
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 14 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,8 @@ Please note you should supply your master docker-compose file as the first eleme

If you are using a v3.2 compose file or above on a Docker Swarm cluster, use the `docker::stack` class. Include the file resource before you run the stack command.

NOTE: this define will be deprecated in a future release in favor of the [docker type](#types)

To deploy the stack, add the following code to the manifest file:

```puppet
Expand Down Expand Up @@ -927,7 +929,6 @@ docker::plugin {'foo/fooplugin:latest'
ensure => 'absent',
force_remove => true,
}
thub.com
```

## Reference
Expand Down Expand Up @@ -974,6 +975,7 @@ thub.com
* docker_compose: A type that represents a docker compose file.
* docker_network: A type that represents a docker network.
* docker_volume: A type that represents a docker volume.
* docker_stack: A type that repsents a docker stack.

### Parameters

Expand Down Expand Up @@ -1057,6 +1059,20 @@ Additional options for the volume driver.

The location that the volume is mounted to.

The following parameters are available in the `docker_stack` type:

#### 'bundle_file'

A path to a Distributed Application Bundle file.

#### 'compose_files'

An array containing the docker compose file paths.

#### `up_args`

Arguments to be passed directly to docker stack deploy.

#### Docker class parameters

#### `version`
Expand Down
90 changes: 90 additions & 0 deletions lib/puppet/provider/docker_stack/ruby.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

require 'deep_merge'

Puppet::Type.type(:docker_stack).provide(:ruby) do
desc 'Support for Puppet running Docker Stacks'

mk_resource_methods
commands docker: 'docker'

def exists?
Puppet.info("Checking for stack #{name}")
stack_services = {}
stack_containers = []
resource[:compose_files].each do |file|
compose_file = YAML.safe_load(File.read(file), [], [], true)
# rubocop:disable Style/StringLiterals
containers = docker([
'ps',
'--format',
"{{.Label \"com.docker.swarm.service.name\"}}-{{.Image}}",
'--filter',
"label=com.docker.stack.namespace=#{name}",
]).split("\n").each do |c|
c.slice!("#{name}_")
end
stack_containers.push(*containers)
stack_containers.uniq!
# rubocop:enable Style/StringLiterals
case compose_file['version']
when %r{^3(\.[0-7])?$}
stack_services.merge!(compose_file['services'])
else
raise(Puppet::Error, "Unsupported docker compose file syntax version \"#{compose_file['version']}\"!")
end
end

if stack_services.count != stack_containers.count
return false
end
counts = Hash[*stack_services.each.map { |key, array|
image = (array['image']) ? array['image'] : get_image(key, stack_services)
Puppet.info("Checking for compose service #{key} #{image}")
["#{key}-#{image}", stack_containers.count("#{key}-#{image}")]
}.flatten]
# No containers found for the project
if counts.empty? ||
# Containers described in the compose file are not running
counts.any? { |_k, v| v.zero? }
false
else
true
end
end

def get_image(service_name, stack_services)
image = stack_services[service_name]['image']
unless image
if stack_services[service_name]['extends']
image = get_image(stack_services[service_name]['extends'], stack_services)
elsif stack_services[service_name]['build']
image = "#{name}_#{service_name}"
end
end
image
end

def create
Puppet.info("Running stack #{name}")
args = ['stack', 'deploy', compose_files, name].insert(1, bundle_file).insert(4, resource[:up_args]).compact
docker(args)
end

def destroy
Puppet.info("Removing docker stack #{name}")
rm_args = ['stack', 'rm', name]
docker(rm_args)
end

def bundle_file
return unless resource[:bundle_file].nil?
resource[:bundle_file].map { |x| ['-c', x] }.flatten
end

def compose_files
resource[:compose_files].map { |x| ['-c', x] }.flatten
end

private
end
33 changes: 33 additions & 0 deletions lib/puppet/type/docker_stack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

Puppet::Type.newtype(:docker_stack) do
@doc = 'A type representing a Docker Stack'

ensurable

newparam(:bundle_file) do
desc 'Path to a Distributed Application Bundle file.'
validate do |value|
raise _('bundle files should be a string') unless value.is_a? String
end
end

newparam(:compose_files, array_matching: :all) do
desc 'An array of Docker Compose Files paths.'
validate do |value|
raise _('compose files should be an array') unless value.is_a? Array
end
end

newparam(:up_args) do
desc 'Arguments to be passed directly to docker stack deploy.'
validate do |value|
raise _('up_args should be a String') unless value.is_a? String
end
end

newparam(:name) do
isnamevar
desc 'The name of the stack'
end
end
3 changes: 3 additions & 0 deletions manifests/stack.pp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@

include docker::params

deprecation('docker::stack','The docker stack define type will be deprecated in a future release. Please migrate to the docker_stack type/provider.')


$docker_command = "${docker::params::docker_command} stack"

if $::osfamily == 'windows' {
Expand Down
22 changes: 9 additions & 13 deletions spec/acceptance/stack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
tmp_path = 'C:/cygwin64/tmp'
test_container = 'nanoserver-sac2016'
wait_for_container_seconds = 120

else
docker_args = ''
tmp_path = '/tmp'
Expand All @@ -31,8 +32,7 @@ class { 'docker': #{docker_args} }

context 'Creating stack' do
let(:install) {"
docker::stack { 'web':
stack_name => 'web',
docker_stack { 'web':
compose_files => ['#{tmp_path}/docker-stack.yml'],
ensure => present,
}"
Expand Down Expand Up @@ -60,21 +60,20 @@ class { 'docker': #{docker_args} }

context 'Destroying stack' do
let(:install) {"
docker::stack { 'web':
stack_name => 'web',
docker_stack { 'web':
compose_files => ['#{tmp_path}/docker-stack.yml'],
ensure => present,
}"
}
let(:destroy) {"
docker::stack { 'web':
stack_name => 'web',
docker_stack { 'web':
compose_files => ['#{tmp_path}/docker-stack.yml'],
ensure => absent,
}"
}
it 'should run successfully' do
apply_manifest(destroy, :catch_failures=>true)
sleep 10
end

it 'should be idempotent' do
Expand All @@ -94,8 +93,7 @@ class { 'docker': #{docker_args} }

before(:all) do
@install_code = <<-code
docker::stack { 'web':
stack_name => 'web',
docker_stack { 'web':
compose_files => ['#{tmp_path}/docker-stack.yml', '#{tmp_path}/docker-stack-override.yml'],
ensure => present,
}
Expand All @@ -113,8 +111,7 @@ class { 'docker': #{docker_args} }
context 'Destroying project with multiple compose files' do
before(:all) do
@install_code = <<-code
docker::stack { 'web':
stack_name => 'web',
docker_stack { 'web':
compose_files => ['#{tmp_path}/docker-stack.yml', '#{tmp_path}/docker-stack-override.yml'],
ensure => present,
}
Expand All @@ -123,15 +120,14 @@ class { 'docker': #{docker_args} }
apply_manifest(@install_code, :catch_failures=>true)

@destroy_code = <<-code
docker::stack { 'web':
stack_name => 'web',
docker_stack { 'web':
compose_files => ['#{tmp_path}/docker-stack.yml', '#{tmp_path}/docker-stack-override.yml'],
ensure => absent,
}
code

apply_manifest(@destroy_code, :catch_failures=>true)
sleep 10 # wait for containers to stop
sleep 10# wait for containers to stop
end

it 'should be idempotent' do
Expand Down
36 changes: 36 additions & 0 deletions spec/unit/docker_stack_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'spec_helper'

stack = Puppet::Type.type(:docker_stack)

describe stack do

let :params do
[
:name,
:provider,
:up_args,
]
end

let :properties do
[
:ensure,
]
end

it 'should have expected properties' do
properties.each do |property|
expect(stack.properties.map(&:name)).to be_include(property)
end
end

it 'should have expected parameters' do
params.each do |param|
expect(stack.parameters).to be_include(param)
end
end

it 'should require up_args to be a string' do
expect(stack).to require_string_for('up_args')
end
end

0 comments on commit 686e2ae

Please sign in to comment.