Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: ruby
rvm:
- 1.9.3
- 2.0.0
- 2.1.0
- 2.2.0
32 changes: 26 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -263,6 +263,26 @@ 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_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
```

Deploying
---------

Expand Down Expand Up @@ -333,9 +353,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.

Expand Down Expand Up @@ -393,14 +413,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

Expand Down
4 changes: 4 additions & 0 deletions lib/centurion/deploy.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
require 'excon'
require 'socket'

require_relative 'deploy_callbacks'

module Centurion; end

module Centurion::Deploy

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}"
Expand Down
37 changes: 37 additions & 0 deletions lib/centurion/deploy_callbacks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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(server, service, timeout = 30)
emit :before_stopping_image, server
super server, service, timeout
end

def start_new_container(server, service, restart_policy)
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)
super(health_check_method,
server,
port,
endpoint,
image_id,
tag,
sleep_time,
retries).tap { emit :after_health_check_ok, server }
end

private

def emit(name, *args)
callbacks[name].each do |callback|
callback.call(*args)
end
end

def callbacks
fetch 'callbacks', Hash.new { [] }
end
end
end
35 changes: 35 additions & 0 deletions lib/centurion/deploy_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,43 @@ 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)
on :before_stopping_image, callback, &block
end

def after_image_started(callback = nil, &block)
on :after_image_started, callback, &block
end

def after_health_check_ok(callback = nil, &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 callbacks
fetch('callbacks') || set('callbacks', Hash.new { [] })
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


def build_server_group
hosts, docker_path = fetch(:hosts, []), fetch(:docker_path)
Centurion::DockerServerGroup.new(hosts, docker_path, build_tls_params)
Expand Down
1 change: 1 addition & 0 deletions lib/tasks/deploy.rake
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ end

namespace :deploy do
include Centurion::Deploy
include Centurion::DeployCallbacks

namespace :dogestry do
task :validate_pull_image do
Expand Down
79 changes: 79 additions & 0 deletions spec/deploy_callbacks_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require 'spec_helper'
require 'centurion'

RSpec.describe Centurion::DeployCallbacks do
shared_examples_for 'a callback' do
let(:server) { double :server }
let(:service) { double :service }

let(:klass) do
Class.new do
include Centurion::DeployCallbacks
def method_missing(method_name, *_args)
doing method_name
end
end
end

let(:object) do
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'

it 'invokes all the callback before the method' do
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'

it 'invokes all the callback before the method' do
expect(object).to receive(:doing).with(method_name).ordered
expect(object).to receive(:emit).with(callback, 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

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
23 changes: 23 additions & 0 deletions spec/deploy_dsl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,27 @@ class DeployDSLTest
DeployDSLTest.set(:image, 'charlemagne')
expect(DeployDSLTest.defined_service.image).to eq('charlemagne:roland')
end

describe 'callbacks' do
shared_examples_for 'a callback for' do |callback_name|
it 'accepts procs' do
callback = ->(_) {}
allow(DeployDSLTest).to receive(:on)
expect(DeployDSLTest).to receive(:on).with(callback_name, callback)
DeployDSLTest.send callback_name, callback
end
end

describe '#before_stopping_image' do
it_behaves_like 'a callback for', :before_stopping_image
end

describe '#after_image_started' do
it_behaves_like 'a callback for', :after_image_started
end

describe '#after_health_check_ok' do
it_behaves_like 'a callback for', :after_health_check_ok
end
end
end