From d3cf61196f2e448237c21fef6b29f64913c3e8ee Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Wed, 3 Jun 2015 21:08:03 -0500 Subject: [PATCH 01/17] Collect callbacks to run before stopping, and after starting an image We need to tell our load balancers about these two events. We can provide callbacks that allow us to do so. --- lib/centurion/deploy_dsl.rb | 12 ++++++++++++ spec/deploy_dsl_spec.rb | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/centurion/deploy_dsl.rb b/lib/centurion/deploy_dsl.rb index c4354146..c2444e64 100644 --- a/lib/centurion/deploy_dsl.rb +++ b/lib/centurion/deploy_dsl.rb @@ -97,6 +97,18 @@ def defined_restart_policy Centurion::Service::RestartPolicy.new(fetch(:restart_policy_name, 'on-failure'), fetch(:restart_policy_max_retry_count, 10)) end + def before_stopping_image(callback = nil, &block) + callbacks = fetch(:before_stopping_image_callbacks, []) + callbacks << (callback || block) + set(:before_stopping_image_callbacks, callbacks) + end + + def after_image_started(callback = nil, &block) + callbacks = fetch(:after_image_started_callbacks, []) + callbacks << (callback || block) + set(:after_image_started_callbacks, callbacks) + end + private def build_server_group diff --git a/spec/deploy_dsl_spec.rb b/spec/deploy_dsl_spec.rb index e5d8b02e..d8938154 100644 --- a/spec/deploy_dsl_spec.rb +++ b/spec/deploy_dsl_spec.rb @@ -117,4 +117,36 @@ class DeployDSLTest DeployDSLTest.set(:image, 'charlemagne') expect(DeployDSLTest.defined_service.image).to eq('charlemagne:roland') end + + describe '#before_stopping_image' do + it 'collects before_stopping_image callbacks as procs' do + callback = ->(server) { } + DeployDSLTest.before_stopping_image callback + expect(DeployDSLTest.fetch(:before_stopping_image_callbacks)).to eq([callback]) + end + + it 'collects before_stopping_image callbacks as blocks' do + DeployDSLTest.before_stopping_image do |_| + 'from the block' + end + callback = DeployDSLTest.fetch(:before_stopping_image_callbacks)[0] + expect(callback.call).to eq('from the block') + end + end + + describe '#after_image_started' do + it 'collects after_image_started callbacks as procs' do + callback = ->(server) { } + DeployDSLTest.after_image_started callback + expect(DeployDSLTest.fetch(:after_image_started_callbacks)).to eq([callback]) + end + + it 'collects after_image_started callbacks as blocks' do + DeployDSLTest.after_image_started do |_| + 'from the block' + end + callback = DeployDSLTest.fetch(:after_image_started_callbacks)[0] + expect(callback.call).to eq('from the block') + end + end end From afb8515d3e13d964a0e19a6e1856b009e6dee878 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Fri, 5 Jun 2015 21:10:05 -0500 Subject: [PATCH 02/17] add before stopping callbacks --- lib/centurion/deploy.rb | 5 ++++ lib/centurion/deploy_callbacks.rb | 18 ++++++++++++++ spec/deploy_callbacks_spec.rb | 40 +++++++++++++++++++++++++++++++ spec/deploy_spec.rb | 1 + 4 files changed, 64 insertions(+) create mode 100644 lib/centurion/deploy_callbacks.rb create mode 100644 spec/deploy_callbacks_spec.rb diff --git a/lib/centurion/deploy.rb b/lib/centurion/deploy.rb index f73db00d..3324a31b 100644 --- a/lib/centurion/deploy.rb +++ b/lib/centurion/deploy.rb @@ -1,11 +1,16 @@ require 'excon' require 'socket' +require_relative 'deploy_callbacks' + module Centurion; end module Centurion::Deploy + prepend Centurion::DeployCallbacks + FAILED_CONTAINER_VALIDATION = 100 + def stop_containers(target_server, service, timeout = 30) old_containers = target_server.find_containers_by_public_port(service.public_ports.first) info "Stopping container(s): #{old_containers.inspect}" diff --git a/lib/centurion/deploy_callbacks.rb b/lib/centurion/deploy_callbacks.rb new file mode 100644 index 00000000..f8070aba --- /dev/null +++ b/lib/centurion/deploy_callbacks.rb @@ -0,0 +1,18 @@ +module Centurion + # Callbacks to allow hooking into the deploy lifecycle. This could + # be useful to communicate with a loadbalancer, chat room, etc. + module DeployCallbacks + def stop_containers(target_server, service, timeout = 30) + before_stopping_container_callbacks.each do |callback| + callback.call target_server + end + super target_server, service, timeout + end + + private + + def before_stopping_container_callbacks + fetch :before_stopping_container_callbacks, [] + end + end +end diff --git a/spec/deploy_callbacks_spec.rb b/spec/deploy_callbacks_spec.rb new file mode 100644 index 00000000..65f93541 --- /dev/null +++ b/spec/deploy_callbacks_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'centurion' + +RSpec.describe Centurion::DeployCallbacks do + describe 'before stopping callback' do + let(:callback) { [double, double] } + let(:klass) do + Class.new do + prepend Centurion::DeployCallbacks + def stop_containers(server, service, timeout) + doing_it_now server, service, timeout + end + end + end + let(:object) do + klass.new.tap do |o| + allow(o).to receive(:fetch) + .with(:before_stopping_container_callbacks, []) + .and_return callbacks + end + end + let(:server) { double :server } + let(:service) { double :service } + let(:timeout) { double :timeout } + let(:callbacks) { [double, double] } + + it 'invokes all the callback before stopping the container' do + callbacks.each do |callback| + expect(callback).to receive(:call) + .with(server) + .ordered + end + expect(object).to receive(:doing_it_now) + .with(server, service, timeout) + .ordered + + object.stop_containers server, service, timeout + end + end +end diff --git a/spec/deploy_spec.rb b/spec/deploy_spec.rb index b83a1022..927dee56 100644 --- a/spec/deploy_spec.rb +++ b/spec/deploy_spec.rb @@ -22,6 +22,7 @@ before do allow(test_deploy).to receive(:fetch).and_return nil allow(test_deploy).to receive(:host_ip).and_return('172.16.0.1') + allow(test_deploy).to receive(:fetch).with(:before_stopping_container_callbacks, []).and_return([]) end describe '#http_status_ok?' do From 5be35fcd636df8f110be04ad0be1296730aa62b8 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Fri, 5 Jun 2015 21:21:47 -0500 Subject: [PATCH 03/17] add after starting callbacks --- lib/centurion/deploy_callbacks.rb | 17 ++++++++-- spec/deploy_callbacks_spec.rb | 53 +++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/lib/centurion/deploy_callbacks.rb b/lib/centurion/deploy_callbacks.rb index f8070aba..6f103171 100644 --- a/lib/centurion/deploy_callbacks.rb +++ b/lib/centurion/deploy_callbacks.rb @@ -2,11 +2,18 @@ module Centurion # Callbacks to allow hooking into the deploy lifecycle. This could # be useful to communicate with a loadbalancer, chat room, etc. module DeployCallbacks - def stop_containers(target_server, service, timeout = 30) + def stop_containers(server, service, timeout = 30) before_stopping_container_callbacks.each do |callback| - callback.call target_server + callback.call server + end + super server, service, timeout + end + + def start_new_container(server, service, restart_policy) + super server, service, restart_policy + after_new_container_started_callbacks.each do |callback| + callback.call server end - super target_server, service, timeout end private @@ -14,5 +21,9 @@ def stop_containers(target_server, service, timeout = 30) def before_stopping_container_callbacks fetch :before_stopping_container_callbacks, [] end + + def after_new_container_started_callbacks + fetch :after_new_container_started_callbacks, [] + end end end diff --git a/spec/deploy_callbacks_spec.rb b/spec/deploy_callbacks_spec.rb index 65f93541..829d88a7 100644 --- a/spec/deploy_callbacks_spec.rb +++ b/spec/deploy_callbacks_spec.rb @@ -2,16 +2,24 @@ require 'centurion' RSpec.describe Centurion::DeployCallbacks do - describe 'before stopping callback' do - let(:callback) { [double, double] } - let(:klass) do - Class.new do - prepend Centurion::DeployCallbacks - def stop_containers(server, service, timeout) - doing_it_now server, service, timeout - end + let(:server) { double :server } + let(:service) { double :service } + let(:callbacks) { [double, double] } + + let(:klass) do + Class.new do + prepend Centurion::DeployCallbacks + def stop_containers(server, service, timeout) + stopping_it_now server, service, timeout + end + + def start_new_container(server, service, restart_policy) + starting_it_now server, service, restart_policy end end + end + + describe 'before stopping callback' do let(:object) do klass.new.tap do |o| allow(o).to receive(:fetch) @@ -19,22 +27,41 @@ def stop_containers(server, service, timeout) .and_return callbacks end end - let(:server) { double :server } - let(:service) { double :service } let(:timeout) { double :timeout } - let(:callbacks) { [double, double] } - it 'invokes all the callback before stopping the container' do callbacks.each do |callback| expect(callback).to receive(:call) .with(server) .ordered end - expect(object).to receive(:doing_it_now) + expect(object).to receive(:stopping_it_now) .with(server, service, timeout) .ordered object.stop_containers server, service, timeout end end + + describe 'after started callback' do + let(:object) do + klass.new.tap do |o| + allow(o).to receive(:fetch) + .with(:after_new_container_started_callbacks, []) + .and_return callbacks + end + end + let(:restart_policy) { double } + it 'invokes all the callbacks after the container is started' do + expect(object).to receive(:starting_it_now) + .with(server, service, restart_policy) + .ordered + callbacks.each do |callback| + expect(callback).to receive(:call) + .with(server) + .ordered + end + + object.start_new_container server, service, restart_policy + end + end end From 0da02de6ab7cd3794fbf93624abc03b7208def2a Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Fri, 5 Jun 2015 21:37:51 -0500 Subject: [PATCH 04/17] converge on common names to store the callbacks --- lib/centurion/deploy_callbacks.rb | 7 ++++--- spec/deploy_callbacks_spec.rb | 4 ++-- spec/deploy_spec.rb | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/centurion/deploy_callbacks.rb b/lib/centurion/deploy_callbacks.rb index 6f103171..7c2d64ac 100644 --- a/lib/centurion/deploy_callbacks.rb +++ b/lib/centurion/deploy_callbacks.rb @@ -10,20 +10,21 @@ def stop_containers(server, service, timeout = 30) end def start_new_container(server, service, restart_policy) - super server, service, restart_policy + result = super server, service, restart_policy after_new_container_started_callbacks.each do |callback| callback.call server end + result end private def before_stopping_container_callbacks - fetch :before_stopping_container_callbacks, [] + fetch :before_stopping_image_callbacks, [] end def after_new_container_started_callbacks - fetch :after_new_container_started_callbacks, [] + fetch :after_image_started_callbacks, [] end end end diff --git a/spec/deploy_callbacks_spec.rb b/spec/deploy_callbacks_spec.rb index 829d88a7..49a8f6b5 100644 --- a/spec/deploy_callbacks_spec.rb +++ b/spec/deploy_callbacks_spec.rb @@ -23,7 +23,7 @@ def start_new_container(server, service, restart_policy) let(:object) do klass.new.tap do |o| allow(o).to receive(:fetch) - .with(:before_stopping_container_callbacks, []) + .with(:before_stopping_image_callbacks, []) .and_return callbacks end end @@ -46,7 +46,7 @@ def start_new_container(server, service, restart_policy) let(:object) do klass.new.tap do |o| allow(o).to receive(:fetch) - .with(:after_new_container_started_callbacks, []) + .with(:after_image_started_callbacks, []) .and_return callbacks end end diff --git a/spec/deploy_spec.rb b/spec/deploy_spec.rb index 927dee56..486ecdda 100644 --- a/spec/deploy_spec.rb +++ b/spec/deploy_spec.rb @@ -22,7 +22,8 @@ before do allow(test_deploy).to receive(:fetch).and_return nil allow(test_deploy).to receive(:host_ip).and_return('172.16.0.1') - allow(test_deploy).to receive(:fetch).with(:before_stopping_container_callbacks, []).and_return([]) + allow(test_deploy).to receive(:fetch).with(:before_stopping_image_callbacks, []).and_return([]) + allow(test_deploy).to receive(:fetch).with(:after_image_started_callbacks, []).and_return([]) end describe '#http_status_ok?' do From 7ebae131a82ff7585e484242f0aaaf33dddfba88 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Fri, 5 Jun 2015 21:38:25 -0500 Subject: [PATCH 05/17] make sure nil callbacks do not get added --- lib/centurion/deploy_dsl.rb | 2 ++ spec/deploy_dsl_spec.rb | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/centurion/deploy_dsl.rb b/lib/centurion/deploy_dsl.rb index c2444e64..88bc2420 100644 --- a/lib/centurion/deploy_dsl.rb +++ b/lib/centurion/deploy_dsl.rb @@ -98,12 +98,14 @@ def defined_restart_policy end def before_stopping_image(callback = nil, &block) + return unless callback || block callbacks = fetch(:before_stopping_image_callbacks, []) callbacks << (callback || block) set(:before_stopping_image_callbacks, callbacks) end def after_image_started(callback = nil, &block) + return unless callback || block callbacks = fetch(:after_image_started_callbacks, []) callbacks << (callback || block) set(:after_image_started_callbacks, callbacks) diff --git a/spec/deploy_dsl_spec.rb b/spec/deploy_dsl_spec.rb index d8938154..cda089e2 100644 --- a/spec/deploy_dsl_spec.rb +++ b/spec/deploy_dsl_spec.rb @@ -119,8 +119,13 @@ class DeployDSLTest end describe '#before_stopping_image' do + it 'does not add nil callbacks' do + DeployDSLTest.before_stopping_image + expect(DeployDSLTest.fetch(:before_stopping_image_callbacks, [])).to eq([]) + end + it 'collects before_stopping_image callbacks as procs' do - callback = ->(server) { } + callback = ->(_) {} DeployDSLTest.before_stopping_image callback expect(DeployDSLTest.fetch(:before_stopping_image_callbacks)).to eq([callback]) end @@ -135,6 +140,11 @@ class DeployDSLTest end describe '#after_image_started' do + it 'does not add nil callbacks' do + DeployDSLTest.after_image_started + expect(DeployDSLTest.fetch(:after_image_started_callbacks, [])).to eq([]) + end + it 'collects after_image_started callbacks as procs' do callback = ->(server) { } DeployDSLTest.after_image_started callback From 60538db16d1a32f956432f05fab5063fa55f05c4 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Fri, 5 Jun 2015 22:21:08 -0500 Subject: [PATCH 06/17] add notes in the readme about the callbacks --- README.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cfd4b0bc..1357c8ad 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ tools directly so you can use anything they currently support via the normal registry mechanism. If you haven't been using a registry, you should read up on how to do that -before trying to deploy anything with Centurion. +before trying to deploy anything with Centurion. Commercial Docker Registry Providers: - Docker, Inc. [provides repositories](https://index.docker.io/), and hosts the @@ -263,6 +263,22 @@ You have to set the following keys: end ``` +### Callbacks + +You can create callbacks to perform custom actions during a deploy. + +```ruby + task :production => :common do + before_stopping_image do |server| + my_loadbalancer.disable server.hostname + end + + after_image_started do |server| + my_loadbalancer.enable server.hostname + end + end +``` + Deploying --------- @@ -333,9 +349,9 @@ are the same everywhere. Settings are per-project. an individual container to come up before giving up as a failure. Defaults to 24 attempts. * `rolling_deploy_skip_ports` => Either a single port, or an array of ports - that should be skipped for status checks. By default status checking assumes - an HTTP server is on the other end and if you are deploying a container where some - ports are not HTTP services, this allows you to only health check the ports + that should be skipped for status checks. By default status checking assumes + an HTTP server is on the other end and if you are deploying a container where some + ports are not HTTP services, this allows you to only health check the ports that are. The default is an empty array. If you have non-HTTP services that you want to check, see Custom Health Checks in the previous section. @@ -393,14 +409,14 @@ Centurion needs to have access to some registry in order to pull images to remote Docker servers. This needs to be either a hosted registry (public or private), or [Dogestry](https://github.com/dogestry/dogestry). -#### Access to the registry +#### Access to the registry If you are not using either Dogestry, or the public registry, you may need to provide authentication credentials. Centurion needs to access the Docker registry hosting your images directly to retrive image ids and tags. This is supported in both the config file and also as command line arguments. -The command line arguments are: +The command line arguments are: * `--registry-user` => The username to pass to the registry * `--registry-password` => The password From e63cb604ef8fe43d5baa0e089275d509f3d28f13 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Fri, 5 Jun 2015 22:54:43 -0500 Subject: [PATCH 07/17] refactor callbacks --- lib/centurion/deploy_dsl.rb | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/lib/centurion/deploy_dsl.rb b/lib/centurion/deploy_dsl.rb index 88bc2420..b2711671 100644 --- a/lib/centurion/deploy_dsl.rb +++ b/lib/centurion/deploy_dsl.rb @@ -98,20 +98,35 @@ def defined_restart_policy end def before_stopping_image(callback = nil, &block) - return unless callback || block - callbacks = fetch(:before_stopping_image_callbacks, []) - callbacks << (callback || block) - set(:before_stopping_image_callbacks, callbacks) + collect_callback :before_stopping_image_callbacks, callback, &block end def after_image_started(callback = nil, &block) + collect_callback :after_image_started_callbacks, callback, &block + end + + private + + def collect_callback(name, callback = nil, &block) return unless callback || block - callbacks = fetch(:after_image_started_callbacks, []) + abort('Callback expects a lambda, proc, or block') if callback && !callback.respond_to?(:call) + callbacks = fetch(name, []) callbacks << (callback || block) - set(:after_image_started_callbacks, callbacks) + set(name, callbacks) + end + + def service_under_construction + service = fetch(:service, + Centurion::Service.from_hash( + fetch(:project), + image: fetch(:image), + hostname: fetch(:container_hostname), + dns: fetch(:custom_dns) + ) + ) + set(:service, service) end - private def build_server_group hosts, docker_path = fetch(:hosts, []), fetch(:docker_path) From 703377281d41b2df81037a49421ddc04538f7099 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Sun, 7 Jun 2015 13:06:28 -0500 Subject: [PATCH 08/17] collect after_health_check_ok callbacks --- lib/centurion/deploy_dsl.rb | 4 ++++ spec/deploy_dsl_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/centurion/deploy_dsl.rb b/lib/centurion/deploy_dsl.rb index b2711671..ac38dee0 100644 --- a/lib/centurion/deploy_dsl.rb +++ b/lib/centurion/deploy_dsl.rb @@ -105,6 +105,10 @@ def after_image_started(callback = nil, &block) collect_callback :after_image_started_callbacks, callback, &block end + def after_health_check_ok(callback = nil, &block) + collect_callback :after_health_check_ok_callbacks, callback, &block + end + private def collect_callback(name, callback = nil, &block) diff --git a/spec/deploy_dsl_spec.rb b/spec/deploy_dsl_spec.rb index cda089e2..a297f9c4 100644 --- a/spec/deploy_dsl_spec.rb +++ b/spec/deploy_dsl_spec.rb @@ -159,4 +159,25 @@ class DeployDSLTest expect(callback.call).to eq('from the block') end end + + describe '#after_health_check_ok' do + it 'does not add nil callbacks' do + DeployDSLTest.after_health_check_ok + expect(DeployDSLTest.fetch(:after_health_check_ok_callbacks, [])).to eq([]) + end + + it 'collects after_health_check_ok callbacks as procs' do + callback = ->(server) { } + DeployDSLTest.after_health_check_ok callback + expect(DeployDSLTest.fetch(:after_health_check_ok_callbacks)).to eq([callback]) + end + + it 'collects after_health_check_ok callbacks as blocks' do + DeployDSLTest.after_health_check_ok do |_| + 'from the block' + end + callback = DeployDSLTest.fetch(:after_health_check_ok_callbacks)[0] + expect(callback.call).to eq('from the block') + end + end end From fd9512076128809cdc64e9c6966f4b088786af0c Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Sun, 7 Jun 2015 13:22:11 -0500 Subject: [PATCH 09/17] add test that it returns a list of all the callbacks --- lib/centurion/deploy_dsl.rb | 10 +++-- spec/deploy_dsl_spec.rb | 80 ++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/lib/centurion/deploy_dsl.rb b/lib/centurion/deploy_dsl.rb index ac38dee0..86625782 100644 --- a/lib/centurion/deploy_dsl.rb +++ b/lib/centurion/deploy_dsl.rb @@ -112,11 +112,13 @@ def after_health_check_ok(callback = nil, &block) private def collect_callback(name, callback = nil, &block) - return unless callback || block - abort('Callback expects a lambda, proc, or block') if callback && !callback.respond_to?(:call) callbacks = fetch(name, []) - callbacks << (callback || block) - set(name, callbacks) + if callback || block + abort('Callback expects a lambda, proc, or block') if callback && !callback.respond_to?(:call) + callbacks << (callback || block) + set(name, callbacks) + end + callbacks end def service_under_construction diff --git a/spec/deploy_dsl_spec.rb b/spec/deploy_dsl_spec.rb index a297f9c4..b54e9494 100644 --- a/spec/deploy_dsl_spec.rb +++ b/spec/deploy_dsl_spec.rb @@ -123,61 +123,57 @@ class DeployDSLTest DeployDSLTest.before_stopping_image expect(DeployDSLTest.fetch(:before_stopping_image_callbacks, [])).to eq([]) end + end - it 'collects before_stopping_image callbacks as procs' do - callback = ->(_) {} - DeployDSLTest.before_stopping_image callback - expect(DeployDSLTest.fetch(:before_stopping_image_callbacks)).to eq([callback]) - end + describe 'callbacks' do + shared_examples_for 'a callback for' do |callback_name| + let(:callbacks) { DeployDSLTest.fetch("#{callback_name}_callbacks".to_sym, []) } - it 'collects before_stopping_image callbacks as blocks' do - DeployDSLTest.before_stopping_image do |_| - 'from the block' + it 'does not add nil callbacks' do + DeployDSLTest.send callback_name + expect(callbacks).to eq([]) end - callback = DeployDSLTest.fetch(:before_stopping_image_callbacks)[0] - expect(callback.call).to eq('from the block') - end - end - describe '#after_image_started' do - it 'does not add nil callbacks' do - DeployDSLTest.after_image_started - expect(DeployDSLTest.fetch(:after_image_started_callbacks, [])).to eq([]) - end + it 'collects callbacks as procs' do + callback = ->(_) {} + DeployDSLTest.send callback_name, callback + expect(callbacks).to eq([callback]) + end - it 'collects after_image_started callbacks as procs' do - callback = ->(server) { } - DeployDSLTest.after_image_started callback - expect(DeployDSLTest.fetch(:after_image_started_callbacks)).to eq([callback]) - end + it 'collects callbacks as blocks' do + DeployDSLTest.send callback_name do |_| + 'from the block' + end + callback = callbacks[0] + expect(callback.call).to eq('from the block') + end + + it 'returns a list of all callbacks when adding one' do + callback1 = ->(_) {} + callback2 = ->(_) {} + DeployDSLTest.send callback_name, callback1 + returned_callbacks = DeployDSLTest.send callback_name, callback2 + expect(returned_callbacks).to eq([callback1, callback2]) + end - it 'collects after_image_started callbacks as blocks' do - DeployDSLTest.after_image_started do |_| - 'from the block' + it 'returns a list of all callbacks when adding none' do + callback1 = ->(_) {} + DeployDSLTest.send callback_name, callback1 + returned_callbacks = DeployDSLTest.send callback_name + expect(returned_callbacks).to eq([callback1]) end - callback = DeployDSLTest.fetch(:after_image_started_callbacks)[0] - expect(callback.call).to eq('from the block') end - end - describe '#after_health_check_ok' do - it 'does not add nil callbacks' do - DeployDSLTest.after_health_check_ok - expect(DeployDSLTest.fetch(:after_health_check_ok_callbacks, [])).to eq([]) + describe '#before_stopping_image' do + it_behaves_like 'a callback for', :before_stopping_image end - it 'collects after_health_check_ok callbacks as procs' do - callback = ->(server) { } - DeployDSLTest.after_health_check_ok callback - expect(DeployDSLTest.fetch(:after_health_check_ok_callbacks)).to eq([callback]) + describe '#after_image_started' do + it_behaves_like 'a callback for', :after_image_started end - it 'collects after_health_check_ok callbacks as blocks' do - DeployDSLTest.after_health_check_ok do |_| - 'from the block' - end - callback = DeployDSLTest.fetch(:after_health_check_ok_callbacks)[0] - expect(callback.call).to eq('from the block') + describe '#after_health_check_ok' do + it_behaves_like 'a callback for', :after_health_check_ok end end end From 5543a5d98472d0a9b3cd3ca49a1bf8fa56f6cb9b Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Sun, 7 Jun 2015 19:51:27 -0500 Subject: [PATCH 10/17] Remove adding the callbacks by default in the deploy module Since prepend is a Ruby 2.0 thing, there is probably a better way to get this functionality without sprinkling all the callbacks into deploy. --- lib/centurion/deploy.rb | 1 - spec/deploy_spec.rb | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/centurion/deploy.rb b/lib/centurion/deploy.rb index 3324a31b..78fa82ed 100644 --- a/lib/centurion/deploy.rb +++ b/lib/centurion/deploy.rb @@ -6,7 +6,6 @@ module Centurion; end module Centurion::Deploy - prepend Centurion::DeployCallbacks FAILED_CONTAINER_VALIDATION = 100 diff --git a/spec/deploy_spec.rb b/spec/deploy_spec.rb index 486ecdda..b83a1022 100644 --- a/spec/deploy_spec.rb +++ b/spec/deploy_spec.rb @@ -22,8 +22,6 @@ before do allow(test_deploy).to receive(:fetch).and_return nil allow(test_deploy).to receive(:host_ip).and_return('172.16.0.1') - allow(test_deploy).to receive(:fetch).with(:before_stopping_image_callbacks, []).and_return([]) - allow(test_deploy).to receive(:fetch).with(:after_image_started_callbacks, []).and_return([]) end describe '#http_status_ok?' do From 238e9b77f2dbe9e23e7bfe942c08f08b9332e32e Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Sun, 7 Jun 2015 20:45:33 -0500 Subject: [PATCH 11/17] create dry callback test --- spec/deploy_callbacks_spec.rb | 87 ++++++++++++++++------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/spec/deploy_callbacks_spec.rb b/spec/deploy_callbacks_spec.rb index 49a8f6b5..241761c8 100644 --- a/spec/deploy_callbacks_spec.rb +++ b/spec/deploy_callbacks_spec.rb @@ -2,66 +2,59 @@ require 'centurion' RSpec.describe Centurion::DeployCallbacks do - let(:server) { double :server } - let(:service) { double :service } - let(:callbacks) { [double, double] } + shared_examples_for 'a callback' do |callback| + let(:server) { double :server } + let(:service) { double :service } + let(:callbacks) { [double, double] } + let(:callback_name) { "#{callback}_callbacks".to_sym } - let(:klass) do - Class.new do - prepend Centurion::DeployCallbacks - def stop_containers(server, service, timeout) - stopping_it_now server, service, timeout - end - - def start_new_container(server, service, restart_policy) - starting_it_now server, service, restart_policy + let(:klass) do + Class.new do + include Centurion::DeployCallbacks + def method_missing(method_name, *_args) + doing method_name + end end end - end - describe 'before stopping callback' do let(:object) do klass.new.tap do |o| - allow(o).to receive(:fetch) - .with(:before_stopping_image_callbacks, []) - .and_return callbacks + allow(o).to receive(:fetch).with(callback_name, []).and_return callbacks end end - let(:timeout) { double :timeout } - it 'invokes all the callback before stopping the container' do - callbacks.each do |callback| - expect(callback).to receive(:call) - .with(server) - .ordered - end - expect(object).to receive(:stopping_it_now) - .with(server, service, timeout) - .ordered + end + + shared_examples_for 'a before callback' do |callback, method_name| + include_examples 'a callback', callback - object.stop_containers server, service, timeout + it 'invokes all the callback before the method' do + callbacks.each { |cb| expect(cb).to receive(:call).with(server).ordered } + expect(object).to receive(:doing).with(method_name).ordered + subject end end - describe 'after started callback' do - let(:object) do - klass.new.tap do |o| - allow(o).to receive(:fetch) - .with(:after_image_started_callbacks, []) - .and_return callbacks - end - end - let(:restart_policy) { double } - it 'invokes all the callbacks after the container is started' do - expect(object).to receive(:starting_it_now) - .with(server, service, restart_policy) - .ordered - callbacks.each do |callback| - expect(callback).to receive(:call) - .with(server) - .ordered - end + shared_examples_for 'an after callback' do |callback, method_name| + include_examples 'a callback', callback - object.start_new_container server, service, restart_policy + it 'invokes all the callback before the method' do + expect(object).to receive(:doing).with(method_name).ordered + callbacks.each { |cb| expect(cb).to receive(:call).with(server).ordered } + subject end end + + describe 'before stopping callback' do + subject { object.stop_containers server, service } + it_behaves_like 'a before callback', + :before_stopping_image, + :stop_containers + end + + describe 'after started callback' do + subject { object.start_new_container server, service, double } + it_behaves_like 'an after callback', + :after_image_started, + :start_new_container + end end From 4896e71260b80f558317537984fba54188e6ae6c Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Sun, 7 Jun 2015 20:56:44 -0500 Subject: [PATCH 12/17] add after health check ok callback --- lib/centurion/deploy_callbacks.rb | 20 ++++++++++++++++++++ spec/deploy_callbacks_spec.rb | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/centurion/deploy_callbacks.rb b/lib/centurion/deploy_callbacks.rb index 7c2d64ac..3c6bc7d2 100644 --- a/lib/centurion/deploy_callbacks.rb +++ b/lib/centurion/deploy_callbacks.rb @@ -17,6 +17,22 @@ def start_new_container(server, service, restart_policy) result end + def wait_for_health_check_ok(health_check_method, server, port, endpoint, image_id, tag, sleep_time=5, retries=12) + result = super health_check_method, + server, + port, + endpoint, + image_id, + tag, + sleep_time, + retries + + after_health_check_ok_callbacks.each do |callback| + callback.call server + end + result + end + private def before_stopping_container_callbacks @@ -26,5 +42,9 @@ def before_stopping_container_callbacks def after_new_container_started_callbacks fetch :after_image_started_callbacks, [] end + + def after_health_check_ok_callbacks + fetch :after_health_check_ok_callbacks, [] + end end end diff --git a/spec/deploy_callbacks_spec.rb b/spec/deploy_callbacks_spec.rb index 241761c8..0f8ef2f5 100644 --- a/spec/deploy_callbacks_spec.rb +++ b/spec/deploy_callbacks_spec.rb @@ -57,4 +57,23 @@ def method_missing(method_name, *_args) :after_image_started, :start_new_container end + + describe 'after health check ok callback' do + let(:args) do + [ + double(:health_check_method), + server, + double(:port), + double(:endpoint), + double(:image_id), + double(:tag), + double(:sleep), + double(:retries) + ] + end + subject { object.wait_for_health_check_ok(*args) } + it_behaves_like 'an after callback', + :after_health_check_ok, + :wait_for_health_check_ok + end end From 32abdc8eb5512d1a5589a36214f382d9690d3b15 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Sun, 7 Jun 2015 21:00:52 -0500 Subject: [PATCH 13/17] dry up the callbacks helpers --- lib/centurion/deploy_callbacks.rb | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/centurion/deploy_callbacks.rb b/lib/centurion/deploy_callbacks.rb index 3c6bc7d2..6513b539 100644 --- a/lib/centurion/deploy_callbacks.rb +++ b/lib/centurion/deploy_callbacks.rb @@ -3,7 +3,7 @@ module Centurion # be useful to communicate with a loadbalancer, chat room, etc. module DeployCallbacks def stop_containers(server, service, timeout = 30) - before_stopping_container_callbacks.each do |callback| + callbacks(:before_stopping_image).each do |callback| callback.call server end super server, service, timeout @@ -11,7 +11,7 @@ def stop_containers(server, service, timeout = 30) def start_new_container(server, service, restart_policy) result = super server, service, restart_policy - after_new_container_started_callbacks.each do |callback| + callbacks(:after_image_started).each do |callback| callback.call server end result @@ -27,7 +27,7 @@ def wait_for_health_check_ok(health_check_method, server, port, endpoint, image_ sleep_time, retries - after_health_check_ok_callbacks.each do |callback| + callbacks(:after_health_check_ok).each do |callback| callback.call server end result @@ -35,16 +35,8 @@ def wait_for_health_check_ok(health_check_method, server, port, endpoint, image_ private - def before_stopping_container_callbacks - fetch :before_stopping_image_callbacks, [] - end - - def after_new_container_started_callbacks - fetch :after_image_started_callbacks, [] - end - - def after_health_check_ok_callbacks - fetch :after_health_check_ok_callbacks, [] + def callbacks(name) + fetch "#{name}_callbacks".to_sym, [] end end end From f255fe1b37e92261833c72a064b15b913e08f271 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Sun, 7 Jun 2015 21:03:16 -0500 Subject: [PATCH 14/17] Mix the callbacks in before the deploy This should let the callbacks have a chance to execute first and call up to the deploy module at the proper time. --- lib/tasks/deploy.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/deploy.rake b/lib/tasks/deploy.rake index 9fbb0757..40aff4dd 100644 --- a/lib/tasks/deploy.rake +++ b/lib/tasks/deploy.rake @@ -39,6 +39,7 @@ end namespace :deploy do include Centurion::Deploy + include Centurion::DeployCallbacks namespace :dogestry do task :validate_pull_image do From 590b2a3ff58393c6247ff2b0844beb1f0c53dc00 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Mon, 8 Jun 2015 20:32:35 -0500 Subject: [PATCH 15/17] add travis My initial approach used some Ruby 2.0 features. I thought it would be good to ensure backwards compatibility to older Rubies. --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..6e05c8fc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: ruby +rvm: + - 1.9.3 + - 2.0.0 + - 2.1.0 + - 2.2.0 From f0ef65fa2393e79f096a11b0fc1eb03e81cc4d01 Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Mon, 8 Jun 2015 20:58:30 -0500 Subject: [PATCH 16/17] add after_health_check_ok to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1357c8ad..7c06efd3 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,10 @@ You can create callbacks to perform custom actions during a deploy. end after_image_started do |server| + my_chat_server.post "#{server.hostname} started my image....waiting for health check" + end + + after_health_check_ok do |server| my_loadbalancer.enable server.hostname end end From aade6e70260290810d82a5f7bbeb7b7dba5e109a Mon Sep 17 00:00:00 2001 From: Mark Borcherding Date: Tue, 21 Jul 2015 21:50:00 -0500 Subject: [PATCH 17/17] Emit events in a more generic manner --- lib/centurion/deploy_callbacks.rb | 41 +++++++++++++----------------- lib/centurion/deploy_dsl.rb | 22 ++++++++-------- spec/deploy_callbacks_spec.rb | 20 +++++++-------- spec/deploy_dsl_spec.rb | 42 +++---------------------------- 4 files changed, 42 insertions(+), 83 deletions(-) diff --git a/lib/centurion/deploy_callbacks.rb b/lib/centurion/deploy_callbacks.rb index 6513b539..c2dc861b 100644 --- a/lib/centurion/deploy_callbacks.rb +++ b/lib/centurion/deploy_callbacks.rb @@ -3,40 +3,35 @@ module Centurion # be useful to communicate with a loadbalancer, chat room, etc. module DeployCallbacks def stop_containers(server, service, timeout = 30) - callbacks(:before_stopping_image).each do |callback| - callback.call server - end + emit :before_stopping_image, server super server, service, timeout end def start_new_container(server, service, restart_policy) - result = super server, service, restart_policy - callbacks(:after_image_started).each do |callback| - callback.call server - end - result + super(server, service, restart_policy).tap { emit :after_image_started, server } end def wait_for_health_check_ok(health_check_method, server, port, endpoint, image_id, tag, sleep_time=5, retries=12) - result = super health_check_method, - server, - port, - endpoint, - image_id, - tag, - sleep_time, - retries - - callbacks(:after_health_check_ok).each do |callback| - callback.call server - end - result + super(health_check_method, + server, + port, + endpoint, + image_id, + tag, + sleep_time, + retries).tap { emit :after_health_check_ok, server } end private - def callbacks(name) - fetch "#{name}_callbacks".to_sym, [] + def emit(name, *args) + callbacks[name].each do |callback| + callback.call(*args) + end + end + + def callbacks + fetch 'callbacks', Hash.new { [] } end end end diff --git a/lib/centurion/deploy_dsl.rb b/lib/centurion/deploy_dsl.rb index 86625782..9f45680a 100644 --- a/lib/centurion/deploy_dsl.rb +++ b/lib/centurion/deploy_dsl.rb @@ -98,27 +98,27 @@ def defined_restart_policy end def before_stopping_image(callback = nil, &block) - collect_callback :before_stopping_image_callbacks, callback, &block + on :before_stopping_image, callback, &block end def after_image_started(callback = nil, &block) - collect_callback :after_image_started_callbacks, callback, &block + on :after_image_started, callback, &block end def after_health_check_ok(callback = nil, &block) - collect_callback :after_health_check_ok_callbacks, callback, &block + on :after_health_check_ok, callback, &block + end + + def on(name, callback = nil, &block) + abort('A callback or block is require') unless callback || block + abort('Callback expects a lambda, proc, or block') if callback && !callback.respond_to?(:call) + callbacks[name] <<= (callback || block) end private - def collect_callback(name, callback = nil, &block) - callbacks = fetch(name, []) - if callback || block - abort('Callback expects a lambda, proc, or block') if callback && !callback.respond_to?(:call) - callbacks << (callback || block) - set(name, callbacks) - end - callbacks + def callbacks + fetch('callbacks') || set('callbacks', Hash.new { [] }) end def service_under_construction diff --git a/spec/deploy_callbacks_spec.rb b/spec/deploy_callbacks_spec.rb index 0f8ef2f5..56c4e999 100644 --- a/spec/deploy_callbacks_spec.rb +++ b/spec/deploy_callbacks_spec.rb @@ -2,11 +2,9 @@ require 'centurion' RSpec.describe Centurion::DeployCallbacks do - shared_examples_for 'a callback' do |callback| + shared_examples_for 'a callback' do let(:server) { double :server } let(:service) { double :service } - let(:callbacks) { [double, double] } - let(:callback_name) { "#{callback}_callbacks".to_sym } let(:klass) do Class.new do @@ -18,28 +16,30 @@ def method_missing(method_name, *_args) end let(:object) do - klass.new.tap do |o| - allow(o).to receive(:fetch).with(callback_name, []).and_return callbacks - end + klass.new + end + + before do + allow(object).to receive(:emit) end end shared_examples_for 'a before callback' do |callback, method_name| - include_examples 'a callback', callback + include_examples 'a callback' it 'invokes all the callback before the method' do - callbacks.each { |cb| expect(cb).to receive(:call).with(server).ordered } + expect(object).to receive(:emit).with(callback, server).ordered expect(object).to receive(:doing).with(method_name).ordered subject end end shared_examples_for 'an after callback' do |callback, method_name| - include_examples 'a callback', callback + include_examples 'a callback' it 'invokes all the callback before the method' do expect(object).to receive(:doing).with(method_name).ordered - callbacks.each { |cb| expect(cb).to receive(:call).with(server).ordered } + expect(object).to receive(:emit).with(callback, server).ordered subject end end diff --git a/spec/deploy_dsl_spec.rb b/spec/deploy_dsl_spec.rb index b54e9494..2973b46f 100644 --- a/spec/deploy_dsl_spec.rb +++ b/spec/deploy_dsl_spec.rb @@ -118,49 +118,13 @@ class DeployDSLTest expect(DeployDSLTest.defined_service.image).to eq('charlemagne:roland') end - describe '#before_stopping_image' do - it 'does not add nil callbacks' do - DeployDSLTest.before_stopping_image - expect(DeployDSLTest.fetch(:before_stopping_image_callbacks, [])).to eq([]) - end - end - describe 'callbacks' do shared_examples_for 'a callback for' do |callback_name| - let(:callbacks) { DeployDSLTest.fetch("#{callback_name}_callbacks".to_sym, []) } - - it 'does not add nil callbacks' do - DeployDSLTest.send callback_name - expect(callbacks).to eq([]) - end - - it 'collects callbacks as procs' do + it 'accepts procs' do callback = ->(_) {} + allow(DeployDSLTest).to receive(:on) + expect(DeployDSLTest).to receive(:on).with(callback_name, callback) DeployDSLTest.send callback_name, callback - expect(callbacks).to eq([callback]) - end - - it 'collects callbacks as blocks' do - DeployDSLTest.send callback_name do |_| - 'from the block' - end - callback = callbacks[0] - expect(callback.call).to eq('from the block') - end - - it 'returns a list of all callbacks when adding one' do - callback1 = ->(_) {} - callback2 = ->(_) {} - DeployDSLTest.send callback_name, callback1 - returned_callbacks = DeployDSLTest.send callback_name, callback2 - expect(returned_callbacks).to eq([callback1, callback2]) - end - - it 'returns a list of all callbacks when adding none' do - callback1 = ->(_) {} - DeployDSLTest.send callback_name, callback1 - returned_callbacks = DeployDSLTest.send callback_name - expect(returned_callbacks).to eq([callback1]) end end