capistrano-docker-cloud 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []