capistrano-docker-cloud 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 80e86ce3e62045cd06a1c371fc22e6d4f7b20333
4
+ data.tar.gz: 42d178fda17226ffe9d369db7879e5398a23aee7
5
+ SHA512:
6
+ metadata.gz: 6f003869c4196581f4809d9a12b51f355bd133466793e6adcb6ab8b39043fbbeecf4ce672e8a2a3be5010152f85c4af18f9a2fc0f6c38f3206331e076a15bdbc
7
+ data.tar.gz: a7b64a9d31cb3591cd179721b9b5078166b2ca0279c6afe9378c0d596ecc3cff6c9a0b2abb825509e3f16ce4d06e5a852311b39fcd30155f7a9d3d788c5bbf25
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,27 @@
1
+ # master
2
+
3
+ * Your contribution here!
4
+
5
+ # 0.2.2 (Jul 1 2016)
6
+
7
+ - Ignore endpoint URI update when not configured
8
+
9
+ # 0.2.1 (Jul 1 2016)
10
+
11
+ - Remove the load balancer redeploy
12
+
13
+ The HAProxy image is reload automatically when updating the links.
14
+
15
+ # 0.2.0 (Jun 25 2016)
16
+
17
+ - Service creation for your image
18
+ - Service start
19
+ - Load balancer update and redeploy using the new service
20
+
21
+ This release is not yet ready as there is a downtime in case the application is
22
+ taking some times to boot (as of now, the gem is only waiting on the Docker
23
+ Cloud status, which is not the application status).
24
+
25
+ # 0.1.0 (Jun 23 2016)
26
+
27
+ Initial release
@@ -0,0 +1,37 @@
1
+ # How to use it
2
+ # =============
3
+ #
4
+ # Visit http://blog.zedroot.org/using-docker-to-maintain-a-ruby-gem/
5
+
6
+ # ~~~~ Image base ~~~~
7
+ # Base image with the latest Ruby only
8
+ FROM ruby:2.3.0-slim
9
+ MAINTAINER Guillaume Hain zedtux@zedroot.org
10
+
11
+
12
+ # ~~~~ Set up the environment ~~~~
13
+ ENV DEBIAN_FRONTEND noninteractive
14
+
15
+ RUN mkdir -p /gem/
16
+ WORKDIR /gem/
17
+ ADD . /gem/
18
+
19
+ # ~~~~ OS Maintenance & Rails Preparation ~~~~
20
+ # Rubygems and Bundler
21
+ RUN apt-get update && \
22
+ apt-get install -y git build-essential && \
23
+ touch ~/.gemrc && \
24
+ echo "gem: --no-ri --no-rdoc" >> ~/.gemrc && \
25
+ gem install rubygems-update && \
26
+ update_rubygems && \
27
+ gem install bundler && \
28
+ bundle install && \
29
+ apt-get remove --purge -y build-essential && \
30
+ apt-get autoclean -y && \
31
+ apt-get clean
32
+
33
+ # Import the gem source code
34
+ VOLUME .:/gem/
35
+
36
+ ENTRYPOINT ["bundle", "exec"]
37
+ CMD ["rake", "-T"]
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capistrano-rails.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Guillaume Hain
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,88 @@
1
+ # Capistrano::DockerCloud
2
+
3
+ [Docker cloud](https://cloud.docker.com/) specific tasks for Capistrano v3:
4
+
5
+ - `cap docker_cloud:deploy`
6
+
7
+ ## TODO
8
+
9
+ - [ ] Add a `docker_cloud:rollback` task [#1](https://github.com/YourCursus/capistrano-docker-cloud/issues/1)
10
+ - [ ] Add a task for A/B testing [#2](https://github.com/YourCursus/capistrano-docker-cloud/issues/2)
11
+ - [ ] Add a task to delete old Docker image tags [#3](https://github.com/YourCursus/capistrano-docker-cloud/issues/3)
12
+ - [ ] Add more options to be configured on the Capistrano `deploy.rb` file
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ group :development do
20
+ gem 'capistrano', '~> 3.1'
21
+ gem 'capistrano-docker-cloud'
22
+ end
23
+ ```
24
+
25
+ Run the following command to install the gems:
26
+
27
+ ```
28
+ bundle install
29
+ ```
30
+
31
+ Then run the generator to create a basic set of configuration files:
32
+
33
+ ```
34
+ bundle exec cap install
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ```ruby
40
+ # Capfile
41
+ require 'capistrano/docker-cloud'
42
+ ```
43
+
44
+ Please note that any `require`s should be placed in `Capfile`, not in `config/deploy.rb`.
45
+
46
+ You can tweak some Docker Cloud's specific options in `config/deploy.rb`:
47
+
48
+ ```ruby
49
+ # Docker image name to deploy
50
+ set :docker_image, 'me/my-image'
51
+
52
+ # Docker Cloud credentials
53
+ set :docker_cloud_credentials, {
54
+ username: 'username',
55
+ api_key: 'my-api-key'
56
+ }
57
+ ```
58
+
59
+ ## Debugging
60
+
61
+ #### RestClient
62
+
63
+ Under the hood, this Capistrano gem is using
64
+ [Docker cloud](https://cloud.docker.com/) which is using the [RestClient](https://github.com/rest-client/rest-client) gem.
65
+
66
+ You can see all the RestClient requests setting the `RESTCLIENT_LOG` environment
67
+ variable like the following:
68
+
69
+ ```
70
+ $ cap staging docker_cloud:deploy RESTCLIENT_LOG=stdout
71
+ ```
72
+
73
+ #### Capistrano tasks
74
+
75
+ Of course you can also pass `--trace` in order to reveal the backtrace in case
76
+ of an error:
77
+
78
+ ```
79
+ $ cap staging docker_cloud:deploy --trace
80
+ ```
81
+
82
+ ## Contributing
83
+
84
+ 1. Fork it
85
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
86
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
87
+ 4. Push to the branch (`git push origin my-new-feature`)
88
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'capistrano/docker-cloud/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'capistrano-docker-cloud'
8
+ gem.version = Capistrano::DockerCloud::VERSION
9
+ gem.authors = ['Guillaume Hain']
10
+ gem.email = ['zedtux@zedroot.org']
11
+ gem.description = %q{Docker cloud specific Capistrano tasks}
12
+ gem.summary = %q{Docker cloud specific Capistrano tasks}
13
+ gem.homepage = 'https://github.com/YourCursus/capistrano-docker-cloud'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_dependency 'capistrano', '~> 3.1'
20
+ gem.add_dependency 'capistrano-bundler', '~> 1.1'
21
+ gem.add_dependency 'docker_cloud', '~> 0.3.0'
22
+ end
@@ -0,0 +1,2 @@
1
+ require 'capistrano/bundler'
2
+ load File.expand_path('../tasks/docker-cloud.rake', __FILE__)
@@ -0,0 +1,82 @@
1
+ require 'docker_cloud'
2
+ require 'capistrano/docker-cloud/helpers/collection_helper'
3
+
4
+ module Capistrano
5
+ module DockerCloud
6
+ class RecordNotFound < Exception; end
7
+
8
+ class Base
9
+ include Capistrano::DockerCloud::Helpers::CollectionHelper
10
+
11
+ # ~~~~ Class Methods ~~~~
12
+ def self.subclass_name
13
+ # FIXME : Find a better way to archive this.
14
+ ancestors.first.to_s.split('::').last.downcase + 's'
15
+ end
16
+
17
+ def self.find(uuid)
18
+ object = resource.get(uuid)
19
+ return object if object
20
+ raise Capistrano::DockerCloud::RecordNotFound
21
+ rescue RestClient::ResourceNotFound => error
22
+ puts "ERROR: #{error.response}"
23
+ end
24
+
25
+ def self.find_by(options = {})
26
+ case
27
+ when options.key?(:name)
28
+ new(find_by_name(options[:name]))
29
+ else
30
+ raise "You can only search #{subclass_name} by `:name`."
31
+ end
32
+ end
33
+
34
+ def self.find_by_name(name)
35
+ resource.all.detect do |stack|
36
+ stack.name.downcase == name
37
+ end
38
+ end
39
+
40
+ def self.resource
41
+ connection.send("#{subclass_name}")
42
+ end
43
+
44
+ def self.configure(username, api_key)
45
+ @@dockercloud_username = username
46
+ @@dockercloud_api_key = api_key
47
+ end
48
+
49
+ def self.connection
50
+ @@connection ||= ::DockerCloud::Client.new(@@dockercloud_username,
51
+ @@dockercloud_api_key)
52
+ end
53
+
54
+ #
55
+ # Rails like belongs_to association
56
+ #
57
+ def self.belongs_to(name)
58
+ define_method("#{name.to_s}=") do |object|
59
+ instance_variable_set("@#{name.to_s}", object)
60
+ end
61
+ define_method("#{name.to_s}") { instance_variable_get("@#{name.to_s}") }
62
+ end
63
+
64
+ def self.redeploy(uuid)
65
+ resource.redeploy(uuid)
66
+ end
67
+
68
+ # ~~~~ Instance Methods ~~~~
69
+ def connection
70
+ self.class.connection
71
+ end
72
+
73
+ def reload!
74
+ initialize(self.class.find(uuid))
75
+ end
76
+
77
+ def redeploy
78
+ self.class.redeploy(uuid)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,138 @@
1
+ require 'capistrano/docker-cloud/base'
2
+ require 'capistrano/docker-cloud/service'
3
+ require 'capistrano/docker-cloud/stack'
4
+ require 'capistrano/docker-cloud/helpers/collection_helper'
5
+
6
+ module Capistrano
7
+ module DockerCloud
8
+ class Client
9
+ include Capistrano::DockerCloud::Helpers::CollectionHelper
10
+
11
+ def initialize(capistrano_instance)
12
+ @capistrano = capistrano_instance
13
+ end
14
+
15
+ def deploy
16
+ # Ensure all is fine before to deploy
17
+ ensure_credential_exists!
18
+ ensure_docker_image_tag_to_deploy_is_defined!
19
+ prepare_dockercloud_connection
20
+ ensure_stack_exists_with_name!(stage_name)
21
+ ensure_no_container_with_image_to_deploy_already_exists!
22
+ ensure_stack_has_a_load_balancer!
23
+
24
+ # Deploy the image
25
+ create_new_service!
26
+ start_created_service!
27
+ wait_service_to_boot!
28
+ switch_load_balancer_linked_service!
29
+ end
30
+
31
+ private
32
+
33
+ def fetch(key)
34
+ @capistrano.fetch(key)
35
+ end
36
+
37
+ def stage_name
38
+ fetch(:stage).to_s.downcase
39
+ end
40
+
41
+ def ensure_credential_exists!
42
+ @credentials = fetch(:docker_cloud_credentials)
43
+ return if @credentials
44
+ raise 'You have not defined the :docker_cloud_credentials option in ' \
45
+ 'capistrano deploy.rb file.'
46
+ end
47
+
48
+ def ensure_docker_image_tag_to_deploy_is_defined!
49
+ return if fetch(:docker_image_tag)
50
+ raise 'No tag defined to be used to deploy the Docker image ' \
51
+ "#{fetch(:docker_image)}."
52
+ end
53
+
54
+ def prepare_dockercloud_connection
55
+ Capistrano::DockerCloud::Base.configure(@credentials[:username],
56
+ @credentials[:api_key])
57
+ end
58
+
59
+ def ensure_stack_exists_with_name!(name)
60
+ @capistrano.info "Looking for the Stack named #{name} ..."
61
+ @stack = Capistrano::DockerCloud::Stack.find_by(name: name)
62
+ return if @stack
63
+ raise "Unable to find the Stack with name #{name}."
64
+ end
65
+
66
+ def image_name_to_deploy
67
+ @image_name_to_deploy ||= "#{fetch(:docker_image)}:" \
68
+ "#{fetch(:docker_image_tag)}"
69
+ end
70
+
71
+ def ensure_no_container_with_image_to_deploy_already_exists!
72
+ @capistrano.info "Ensuring no service is running the image " \
73
+ "#{image_name_to_deploy} ..."
74
+ service = @stack.find_service_by_image_name(image_name_to_deploy).first
75
+ if service.nil? || %w(Terminating Terminated).include?(service.state)
76
+ return
77
+ end
78
+ raise "The service #{service.name} is already running the image " \
79
+ "#{image_name_to_deploy}."
80
+ end
81
+
82
+ def ensure_stack_has_a_load_balancer!
83
+ @capistrano.info "Checking the presence of a Load Balancer ..."
84
+ if @stack.has_a_load_balancer?
85
+ @load_balancer = @stack.load_balancer_service
86
+ return
87
+ end
88
+ raise "The stack #{stage_name} is missing a Load Balancer. Please " \
89
+ 'add one using the dockercloud:haproxy Docker image.'
90
+ end
91
+
92
+ #
93
+ # Extract image name from the capistrano `docker_image` option.
94
+ #
95
+ # Example:
96
+ #
97
+ # - yourcursus/cursus => cursus
98
+ # - ruby => ruby
99
+ #
100
+ def docker_image_name
101
+ fetch(:docker_image).match(/^(?:.*\/)?(.*)$/)[1]
102
+ end
103
+
104
+ def docker_image_tag
105
+ fetch(:docker_image_tag).gsub(/[\.\_]/, '-')
106
+ end
107
+
108
+ def service_name
109
+ @service_name ||= "#{docker_image_name}-#{docker_image_tag}"
110
+ end
111
+
112
+ def create_new_service!
113
+ @capistrano.info "Creating a new service #{service_name} with image " \
114
+ "#{image_name_to_deploy} ..."
115
+ @stack.build_service(image: image_name_to_deploy, name: service_name)
116
+ @stack.save
117
+ end
118
+
119
+ def start_created_service!
120
+ @capistrano.info "Starting newly created service #{service_name} ..."
121
+ @stack.start_service(name: service_name)
122
+ end
123
+
124
+ def wait_service_to_boot!
125
+ @capistrano.info "Waiting #{service_name} container booting ..."
126
+ @stack.waiting_service_boot(name: service_name)
127
+ @capistrano.info "Waiting #{service_name} application booting ..."
128
+ @stack.waiting_service_application_boot(name: service_name)
129
+ end
130
+
131
+ def switch_load_balancer_linked_service!
132
+ @capistrano.info 'Switching the Load Balancer linked services to ' \
133
+ "#{service_name} ..."
134
+ @load_balancer.switch!(service_name: service_name)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,15 @@
1
+ module Capistrano
2
+ module DockerCloud
3
+ module Helpers
4
+ module CollectionHelper
5
+ def first_or_raise_error!(objects, name)
6
+ unless objects.size == 1
7
+ raise "1 object expected with #{name}, #{objects.size} objects " \
8
+ 'found'
9
+ end
10
+ objects.first
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,145 @@
1
+ module Capistrano
2
+ module DockerCloud
3
+ class Service < Capistrano::DockerCloud::Base
4
+
5
+ # https://docs.docker.com/apidocs/docker-cloud/#update-an-existing-service
6
+ ATTRIBUTES = [
7
+ :autorestart, :autodestroy, :container_envvars, :container_ports,
8
+ :cpu_shares, :entrypoint, :image, :linked_to_service, :memory, :net,
9
+ :privileged, :roles, :run_command, :sequential_deployment, :tags,
10
+ :target_num_containers, :deployment_strategy, :autoredeploy, :pid,
11
+ :working_dir, :nickname,
12
+ # Special keys
13
+ :name, :image_name
14
+ ]
15
+
16
+ attr_accessor *ATTRIBUTES, :uuid
17
+
18
+ belongs_to :stack
19
+
20
+ def initialize(service)
21
+ ATTRIBUTES.each do |attribute|
22
+ value = default_value_for(attribute)
23
+ value = service.info[attribute] if service
24
+ self.send("#{attribute.to_s}=", value)
25
+ end
26
+ if service
27
+ # The Service API update is expecting an `image` attribute instead of
28
+ # `image_name`
29
+ self.image = service.info[:image_name]
30
+ self.uuid = service.uuid
31
+ end
32
+ end
33
+
34
+ def load_balancer?
35
+ image_name =~ /^dockercloud\/haproxy\:/
36
+ end
37
+
38
+ def linked_to_service_for_update
39
+ links = linked_to_service.dup
40
+ links.each { |link| link.delete(:from_service) }
41
+ end
42
+
43
+ def container_ports_for_update
44
+ ports = container_ports.dup
45
+ ports.each do |port|
46
+ port.delete(:endpoint_uri)
47
+ port.delete(:port_name)
48
+ end
49
+ end
50
+
51
+ def update(options)
52
+ options.each do |key, value|
53
+ self.send("#{key.to_s}=", value)
54
+ end
55
+ end
56
+
57
+ def save
58
+ connection.services.update(uuid, build_update_hash)
59
+ reload!
60
+ rescue RestClient::BadRequest => error
61
+ puts "ERROR: #{error.response}"
62
+ end
63
+
64
+ def default_value_for(attribute)
65
+ case attribute
66
+ when :privileged, :autoredeploy, :sequential_deployment
67
+ false
68
+ when :autorestart, :autodestroy
69
+ 'OFF'
70
+ when :net
71
+ 'bridge'
72
+ when :deployment_strategy
73
+ 'EMPTIEST_NODE'
74
+ when :pid
75
+ 'none'
76
+ when :target_num_containers
77
+ 1
78
+ when :tags
79
+ []
80
+ when :nickname
81
+ ''
82
+ when :image, :name, :image_name
83
+ nil
84
+ end
85
+ end
86
+
87
+ def build_update_hash
88
+ attributes = {}
89
+ ignored_attributes = [:nickname, :image_name]
90
+ calling = caller.first.match(/^.*\/([a-z\_\-]+)\.rb\:.*$/)[1]
91
+ ignored_attributes << :name unless calling == 'stack'
92
+ ATTRIBUTES.each do |attribute|
93
+ next if ignored_attributes.include?(attribute)
94
+
95
+ value = self.send("#{attribute.to_s}")
96
+ next if value.nil? || value == []
97
+ next if default_value_for(attribute) == value
98
+
99
+ case attribute
100
+ when :linked_to_service
101
+ attributes[attribute] = linked_to_service_for_update
102
+ when :container_ports
103
+ attributes[attribute] = container_ports_for_update
104
+ else
105
+ attributes[attribute] = value
106
+ end
107
+ end
108
+ attributes
109
+ end
110
+
111
+ def switch!(options)
112
+ unless load_balancer?
113
+ raise 'This service is not a Load Balancer, therefor you cannot ' \
114
+ 'call switch.'
115
+ end
116
+
117
+ update_links_with_service!(name: options[:service_name])
118
+ save
119
+ end
120
+
121
+ def update_links_with_service!(options)
122
+ links = linked_to_service.dup
123
+
124
+ services = stack.find_service_by_name(options[:name])
125
+ switch_to = first_or_raise_error!(services, options[:name])
126
+
127
+ to_be_replaced = remove_service_with_same_image_than(switch_to)
128
+
129
+ links.delete_if { |link| link[:name] == to_be_replaced.name }
130
+ links << { name: options[:name], to_service: "/api/app/v1/yourcursus/service/#{switch_to.uuid}/" }
131
+
132
+ self.linked_to_service = links
133
+ end
134
+
135
+ private
136
+
137
+ def remove_service_with_same_image_than(switch_to)
138
+ services = stack.find_service_by_image_name(switch_to.image_name,
139
+ tag: false)
140
+ services.delete_if { |service| service.name == switch_to.name }
141
+ first_or_raise_error!(services, switch_to.name)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,141 @@
1
+ module Capistrano
2
+ module DockerCloud
3
+ class Stack < Capistrano::DockerCloud::Base
4
+
5
+ def initialize(stack)
6
+ @original_stack = stack
7
+ sync_services_from_stack
8
+ end
9
+
10
+ # ~~~~ Class Methods ~~~~
11
+
12
+ # ~~~~ Instance Methods ~~~~
13
+ # TODO : Refactor this code in order to move it in the Service class
14
+ def find_service_by_name(name)
15
+ @original_stack.services.select do |service|
16
+ compare(service.name, name)
17
+ end
18
+ end
19
+
20
+ def find_service_by_image_name(name, options = {})
21
+ options[:tag] = true unless options.key?(:tag)
22
+
23
+ @original_stack.services.select do |service|
24
+ compare(service.image_name, name, tag: options[:tag])
25
+ end
26
+ end
27
+
28
+ def build_service(options = {})
29
+ services = find_service_by_image_name(options[:image], tag: false)
30
+ service = first_or_raise_error!(services, options[:image])
31
+ service = Capistrano::DockerCloud::Service.new(service)
32
+ service.update(image: options[:image], name: options[:name])
33
+ @services << service
34
+ end
35
+
36
+ def save
37
+ connection.stacks.update(@original_stack.uuid, build_update_body)
38
+ reload!
39
+ rescue RestClient::BadRequest => error
40
+ puts "ERROR: #{error.response}"
41
+ end
42
+
43
+ def has_a_load_balancer?
44
+ # TODO : Refactor this code in order to move it in the Service class
45
+ @load_balancer_service = @original_stack.services.detect do |service|
46
+ service.image_name =~ /^dockercloud\/haproxy\:/
47
+ end
48
+ end
49
+
50
+ def load_balancer_service
51
+ service = Capistrano::DockerCloud::Service.new(@load_balancer_service)
52
+ service.stack = self
53
+ service
54
+ end
55
+
56
+ def start_service(options)
57
+ services = find_service_by_name(options[:name])
58
+ service = first_or_raise_error!(services, options[:name])
59
+ service.start
60
+ end
61
+
62
+ def uuid
63
+ @original_stack.uuid
64
+ end
65
+
66
+ def waiting_service_boot(options)
67
+ options[:timeout] ||= 60
68
+ services = find_service_by_name(options[:name])
69
+ service = first_or_raise_error!(services, options[:name])
70
+
71
+ Timeout.timeout(options[:timeout]) do
72
+ begin
73
+ sleep 0.5
74
+ service.reload
75
+ end until service.state == 'Running'
76
+ end
77
+ rescue Timeout::Error
78
+ raise "The service #{options[:name]} was not able to boot in less " \
79
+ "than #{options[:timeout]} seconds. (Service state was " \
80
+ "#{service.state.inspect})"
81
+ end
82
+
83
+ def waiting_service_application_boot(options)
84
+ options[:timeout] ||= 60
85
+ services = find_service_by_name(options[:name])
86
+ service = first_or_raise_error!(services, options[:name])
87
+
88
+ uri = service.containers.flat_map do |container|
89
+ container.info[:container_ports].map do |container_port|
90
+ next unless container_port[:endpoint_uri]
91
+ container_port[:endpoint_uri].gsub(/^tcp\:\/\//, 'http://')
92
+ end
93
+ end
94
+
95
+ result = 0
96
+
97
+ Timeout.timeout(options[:timeout]) do
98
+ while result != 200 do
99
+ sleep 0.5
100
+ result = uri.map do |url|
101
+ begin
102
+ response = RestClient.get(url)
103
+ response.code
104
+ rescue Errno::ECONNREFUSED
105
+ 500
106
+ end
107
+ end.reduce(0, :+) / uri.size
108
+ end
109
+ end
110
+ rescue Timeout::Error
111
+ raise "The service #{options[:name]} application was not able to boot" \
112
+ " in less than #{options[:timeout]} seconds."
113
+ end
114
+
115
+ private
116
+
117
+ def sync_services_from_stack(from_stack = nil)
118
+ @services = []
119
+ @services = (from_stack || @original_stack).services.map do |service|
120
+ Capistrano::DockerCloud::Service.new(service)
121
+ end
122
+ end
123
+
124
+ def compare(source, from, options = {})
125
+ compare_source = source.dup
126
+ compare_from = from.dup
127
+
128
+ if options[:tag] == false
129
+ compare_source = compare_source.gsub(/\:.*$/, '')
130
+ compare_from = compare_from.gsub(/\:.*$/, '')
131
+ end
132
+
133
+ compare_source == compare_from
134
+ end
135
+
136
+ def build_update_body
137
+ { services: @services.map(&:build_update_hash) }
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,5 @@
1
+ module Capistrano
2
+ module DockerCloud
3
+ VERSION = '0.2.2'
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ require 'capistrano/docker-cloud/client'
2
+
3
+ namespace :docker_cloud do
4
+ desc 'Deploy with zero-downtime on Docker cloud'
5
+ task :deploy do
6
+ run_locally do
7
+ set :docker_image_tag, ask('What tag do you want to deploy?', 'latest')
8
+ Capistrano::DockerCloud::Client.new(self).deploy
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+
3
+ DOCKER_IMAGE_NAME="$USER/capistrano-docker-cloud"
4
+ LIBRARY_NEW_VERSION=`cat lib/**/*.rb | grep VERSION | awk '{ print $3 }' | tr -d "'"`
5
+
6
+ LIBRARY_UPDATED=`git status --porcelain | grep -v "lib/capistrano/docker-cloud/version.rb"`
7
+ if [[ -n "$LIBRARY_UPDATED" ]]; then
8
+ echo "Your repository is not clean !"
9
+ exit 1
10
+ fi
11
+
12
+ echo "Ensuring Docker image $DOCKER_IMAGE_NAME exists ..."
13
+ EXISTING_DOCKER_IMAGE=`docker images | grep "$DOCKER_IMAGE_NAME"`
14
+ if [[ -z "$EXISTING_DOCKER_IMAGE" ]]; then
15
+ echo "Building the Docker image ..."
16
+ docker build -t "$DOCKER_IMAGE_NAME" .
17
+ fi
18
+
19
+ echo "Releasing gem ..."
20
+ docker run --rm -v ~/.gitconfig:/root/.gitconfig \
21
+ -v ~/.ssh/:/root/.ssh/ \
22
+ -v ~/.gem/:/root/.gem/ \
23
+ -v `pwd`:/gem/ "$DOCKER_IMAGE_NAME" rake release
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capistrano-docker-cloud
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Guillaume Hain
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capistrano
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: capistrano-bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: docker_cloud
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.0
55
+ description: Docker cloud specific Capistrano tasks
56
+ email:
57
+ - zedtux@zedroot.org
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - CHANGELOG.md
64
+ - Dockerfile
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - capistrano-rails.gemspec
70
+ - lib/capistrano/docker-cloud.rb
71
+ - lib/capistrano/docker-cloud/base.rb
72
+ - lib/capistrano/docker-cloud/client.rb
73
+ - lib/capistrano/docker-cloud/helpers/collection_helper.rb
74
+ - lib/capistrano/docker-cloud/service.rb
75
+ - lib/capistrano/docker-cloud/stack.rb
76
+ - lib/capistrano/docker-cloud/version.rb
77
+ - lib/capistrano/tasks/docker-cloud.rake
78
+ - make_new_release.sh
79
+ homepage: https://github.com/YourCursus/capistrano-docker-cloud
80
+ licenses: []
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.6.6
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Docker cloud specific Capistrano tasks
102
+ test_files: []