luban 0.2.0

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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +37 -0
  6. data/Rakefile +1 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +7 -0
  9. data/exe/luban +3 -0
  10. data/lib/luban/deployment/cli/application/authenticator.rb +106 -0
  11. data/lib/luban/deployment/cli/application/base.rb +179 -0
  12. data/lib/luban/deployment/cli/application/builder.rb +67 -0
  13. data/lib/luban/deployment/cli/application/publisher.rb +215 -0
  14. data/lib/luban/deployment/cli/application/repository.rb +175 -0
  15. data/lib/luban/deployment/cli/application/scm/git.rb +49 -0
  16. data/lib/luban/deployment/cli/application/scm/rsync.rb +47 -0
  17. data/lib/luban/deployment/cli/application.rb +5 -0
  18. data/lib/luban/deployment/cli/command.rb +360 -0
  19. data/lib/luban/deployment/cli/package/binary.rb +241 -0
  20. data/lib/luban/deployment/cli/package/dependency.rb +49 -0
  21. data/lib/luban/deployment/cli/package/dependency_set.rb +71 -0
  22. data/lib/luban/deployment/cli/package/installer/core.rb +98 -0
  23. data/lib/luban/deployment/cli/package/installer/install.rb +330 -0
  24. data/lib/luban/deployment/cli/package/installer/paths.rb +81 -0
  25. data/lib/luban/deployment/cli/package/installer.rb +3 -0
  26. data/lib/luban/deployment/cli/package/service.rb +17 -0
  27. data/lib/luban/deployment/cli/package/worker.rb +43 -0
  28. data/lib/luban/deployment/cli/package.rb +6 -0
  29. data/lib/luban/deployment/cli/project.rb +94 -0
  30. data/lib/luban/deployment/cli.rb +4 -0
  31. data/lib/luban/deployment/configuration/core.rb +67 -0
  32. data/lib/luban/deployment/configuration/filter.rb +54 -0
  33. data/lib/luban/deployment/configuration/question.rb +38 -0
  34. data/lib/luban/deployment/configuration/server.rb +70 -0
  35. data/lib/luban/deployment/configuration/server_set.rb +86 -0
  36. data/lib/luban/deployment/configuration.rb +5 -0
  37. data/lib/luban/deployment/error.rb +5 -0
  38. data/lib/luban/deployment/helpers/configuration.rb +159 -0
  39. data/lib/luban/deployment/helpers/utils.rb +180 -0
  40. data/lib/luban/deployment/helpers.rb +2 -0
  41. data/lib/luban/deployment/packages/bundler.rb +81 -0
  42. data/lib/luban/deployment/packages/git.rb +37 -0
  43. data/lib/luban/deployment/packages/openssl.rb +59 -0
  44. data/lib/luban/deployment/packages/ruby.rb +125 -0
  45. data/lib/luban/deployment/packages/rubygems.rb +89 -0
  46. data/lib/luban/deployment/packages/yaml.rb +33 -0
  47. data/lib/luban/deployment/parameters.rb +160 -0
  48. data/lib/luban/deployment/runner.rb +99 -0
  49. data/lib/luban/deployment/templates/envrc.erb +30 -0
  50. data/lib/luban/deployment/templates/unset_envrc.erb +28 -0
  51. data/lib/luban/deployment/version.rb +5 -0
  52. data/lib/luban/deployment/worker/base.rb +71 -0
  53. data/lib/luban/deployment/worker/controller.rb +11 -0
  54. data/lib/luban/deployment/worker/local.rb +19 -0
  55. data/lib/luban/deployment/worker/remote.rb +55 -0
  56. data/lib/luban/deployment/worker/task.rb +25 -0
  57. data/lib/luban/deployment/worker.rb +4 -0
  58. data/lib/luban/deployment.rb +8 -0
  59. data/lib/luban.rb +4 -0
  60. data/luban.gemspec +29 -0
  61. metadata +174 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f3bb8577686b082a631040457727ce47b0999543
4
+ data.tar.gz: 657f3f393403953ea5606b288f997f212beb4ab3
5
+ SHA512:
6
+ metadata.gz: 7344a5bdf345d666bed583166e90ed40b773fb2f7bdb224102b1d2eefc2de24a270d4b5ae23dbe1eb6fc4a36538ec100d335730134efe25c507474e39b659ff2
7
+ data.tar.gz: 960aa2296bb31efdae8025a9180188867b94603871977ca307c3b6ac35540d5e99f993115c0e54b38a34849af9e68595d153e399ba25b81461efa3605db0fe8f
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in luban.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Chi Man Lei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Luban
2
+
3
+ Luban is a framework to manage server automation and application deployment.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'luban'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install luban
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec luban` to use the code located in this directory, ignoring other installed copies of this gem.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( https://github.com/[my-github-username]/luban/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "luban"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/exe/luban ADDED
@@ -0,0 +1,3 @@
1
+ require "luban"
2
+
3
+ Luban::Deployment::Runner.new.run
@@ -0,0 +1,106 @@
1
+ module Luban
2
+ module Deployment
3
+ class Application
4
+ class Authenticator < Luban::Deployment::Worker::Base
5
+ def private_key_file_name
6
+ @private_key_file_name ||= "id_#{authen_key_type}"
7
+ end
8
+
9
+ def authen_keys_path
10
+ @authen_keys_path ||= Pathname.new(user_home).join('.ssh')
11
+ end
12
+
13
+ def private_key_file_path
14
+ @private_key_file ||= authen_keys_path.join(private_key_file_name)
15
+ end
16
+
17
+ def public_key_file_path
18
+ @public_key_file_path ||= authen_keys_path.join("#{private_key_file_name}.pub")
19
+ end
20
+
21
+ def authorized_keys_file_path
22
+ @authorized_keys_file_path ||= authen_keys_path.join('authorized_keys')
23
+ end
24
+
25
+ def public_key
26
+ @public_key ||= get_public_key
27
+ end
28
+
29
+ def keyget_command
30
+ @keyget_command ||= "cat #{public_key_file_path} 2>&1"
31
+ end
32
+
33
+ def keygen_command
34
+ @keygen_command ||= "ssh-keygen -t #{authen_key_type} -f #{private_key_file_path} -N '' 2>&1"
35
+ end
36
+
37
+ def get_public_key
38
+ generate_key_pairs
39
+ capture(keyget_command)
40
+ end
41
+
42
+ def generate_key_pairs
43
+ execute(keygen_command) unless key_pairs_generated?
44
+ end
45
+
46
+ def key_pairs_generated?
47
+ file?(private_key_file_path) and file?(public_key_file_path)
48
+ end
49
+
50
+ def app; task.opts.app; end
51
+
52
+ def promptless_authen
53
+ if promptless_authen_enabled?
54
+ update_result "Skipped! Promptless authentication has been enabled ALREADY.",
55
+ status: :skipped, public_key: public_key
56
+ else
57
+ setup_password_authen
58
+ generate_key_pairs
59
+ add_authorized_keys
60
+ update_result "Promptless authentication is enabled.", public_key: public_key
61
+ end
62
+ end
63
+
64
+ def promptless_authen_enabled?
65
+ origin_auth_methods = host.ssh_options[:auth_methods]
66
+ host.ssh_options[:auth_methods] = %w(publickey)
67
+ capture('echo ok') == 'ok'
68
+ rescue Net::SSH::AuthenticationFailed
69
+ false
70
+ ensure
71
+ if origin_auth_methods.nil?
72
+ host.ssh_options.delete(:auth_methods)
73
+ else
74
+ host.ssh_options[:auth_methods] = origin_auth_methods
75
+ end
76
+ end
77
+
78
+ protected
79
+
80
+ def setup_password_authen
81
+ host.user, host.password = user, nil if host.user.nil?
82
+ host.password = app.password_for(host.user) if host.password.nil?
83
+ host.ssh_options[:auth_methods] = %w(keyboard-interactive)
84
+ end
85
+
86
+ def add_authorized_keys
87
+ public_keys = task.opts.public_keys || []
88
+ public_keys.uniq!
89
+ if file?(authorized_keys_file_path)
90
+ public_keys.each { |k| add_authorized_key(k) unless key_authorized?(k) }
91
+ else
92
+ public_keys.each { |k| add_authorized_key(k) }
93
+ end
94
+ end
95
+
96
+ def add_authorized_key(key)
97
+ execute("umask 077; echo #{key} >> #{authorized_keys_file_path} 2>&1")
98
+ end
99
+
100
+ def key_authorized?(key)
101
+ test("grep -v \"^#\" #{authorized_keys_file_path} | grep -Fxq \"#{key}\"")
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,179 @@
1
+ module Luban
2
+ module Deployment
3
+ class Application < Luban::Deployment::Command
4
+ include Luban::Deployment::Parameters::Project
5
+ include Luban::Deployment::Parameters::Application
6
+ include Luban::Deployment::Command::Tasks::Install
7
+ include Luban::Deployment::Command::Tasks::Deploy
8
+ include Luban::Deployment::Command::Tasks::Control
9
+
10
+ attr_reader :packages
11
+ attr_reader :services
12
+
13
+ def has_source?; !source.empty?; end
14
+ def has_profile?; !profile.empty?; end
15
+ def has_packages?; !packages.empty?; end
16
+ def has_services?; !services.empty?; end
17
+
18
+ def installable?; has_source? or has_packages?; end
19
+ def deployable?; has_source? or has_profile? or has_services?; end
20
+ def controllable?; has_source? or has_profile? or has_services?; end
21
+
22
+ def use_package?(package_name, package_version, servers: [])
23
+ package_name = package_name.to_sym
24
+ packages.has_key?(package_name) and
25
+ packages[package_name].has_version?(package_version) and
26
+ packages[package_name].config.servers.any? { |s| servers.include?(s) }
27
+ end
28
+
29
+ def other_package_users_for(package_name, package_version, servers: [])
30
+ parent.package_users_for(package_name, package_version,
31
+ exclude: [name], servers: servers)
32
+ end
33
+
34
+ def package(name, version:, **opts)
35
+ yield opts if block_given?
36
+ name = name.to_sym
37
+ pkg = if has_command?(name)
38
+ commands[name]
39
+ else
40
+ command(name, base: Luban::Deployment::Package::Base.package_class(name))
41
+ end
42
+ pkg.update_package_options(version, opts)
43
+ services[name] = pkg if pkg.is_a?(Luban::Deployment::Package::Service)
44
+ packages[name] = pkg
45
+ end
46
+ alias_method :require_package, :package
47
+
48
+ def source(from = nil, **opts)
49
+ from.nil? ? @source : (@source = opts.merge(name: 'app', from: from))
50
+ end
51
+
52
+ def profile(from = nil, **opts)
53
+ from.nil? ? @profile : (@profile = opts.merge(name: 'profile', from: from))
54
+ end
55
+
56
+ def password_for(user); parent.password_for(user); end
57
+
58
+ def promptless_authen(args:, opts:)
59
+ opts = opts.merge(app: self, public_keys: Array(public_key!(args: args, opts: opts)))
60
+ opts[:roles] << scm_role unless scm_role.nil?
61
+ promptless_authen!(args: args, opts: opts)
62
+ end
63
+
64
+ def build(args:, opts:)
65
+ show_app_environment
66
+ promptless_authen(args: args, opts: opts)
67
+ build_repositories(args: args, opts: opts)
68
+ build!(args: args, opts: opts)
69
+ install_all(args: args, opts: opts)
70
+ end
71
+
72
+ def destroy(args:, opts:)
73
+ uninstall_all(args: args, opts: opts)
74
+ destroy!(args: args, opts: opts)
75
+ end
76
+
77
+ %i(install_all uninstall_all).each do |action|
78
+ define_method(action) do |args:, opts:|
79
+ packages.each_value { |p| p.send(__method__, args: args, opts: opts) }
80
+ end
81
+ end
82
+
83
+ %i(cleanup binstubs show_current show_summary which whence).each do |action|
84
+ define_method(action) do |args:, opts:|
85
+ show_app_environment
86
+ packages.each_value { |p| p.send(__method__, args: args, opts: opts) }
87
+ end
88
+ end
89
+
90
+ alias_method :cleanup_packages, :cleanup
91
+ def cleanup(args:, opts:)
92
+ cleanup_packages(args: args, opts: opts)
93
+ cleanup!(args: args, opts: opts)
94
+ end
95
+
96
+ def destroy_project(args:, opts:)
97
+ show_app_environment
98
+ destroy!(args: args, opts: opts.merge(destroy_project: true))
99
+ end
100
+
101
+ def deploy(args:, opts:)
102
+ show_app_environment
103
+ deploy_releases(args: args, opts: opts)
104
+ end
105
+
106
+ protected
107
+
108
+ def set_parameters
109
+ super
110
+ copy_parameters_from_parent(:stage, :project)
111
+ @packages = {}
112
+ @services = {}
113
+ @source = {}
114
+ @profile = {}
115
+ end
116
+
117
+ def validate_parameters
118
+ super
119
+ validate_project_parameters
120
+ validate_application_parameters
121
+ end
122
+
123
+ def set_default_parameters
124
+ super
125
+ set_default_project_parameters
126
+ set_default :application, self.class.name.split(':').last.downcase
127
+ set_default_application_parameters
128
+ set_default_profile
129
+ end
130
+
131
+ def set_default_profile
132
+ profile_path = config_finder[:application].stage_config_path.join('profile')
133
+ profile(profile_path, scm: :rsync) if profile_path.directory?
134
+ end
135
+
136
+ def load_configuration
137
+ config_finder[:project].load_configuration
138
+ config_finder[:application].load_configuration
139
+ end
140
+
141
+ def setup_descriptions
142
+ desc "Manage application #{display_name}"
143
+ long_desc "Manage the deployment of application #{display_name} in #{parent.class.name}"
144
+ end
145
+
146
+ def show_app_environment
147
+ puts "#{display_name} in #{parent.class.name}"
148
+ end
149
+
150
+ %i(build destroy cleanup).each do |m|
151
+ define_task_method("#{m}!", task: m, worker: :builder)
152
+ end
153
+
154
+ def build_repositories(args:, opts:)
155
+ build_repository!(args: args, opts: opts.merge(repository: profile)) if has_profile?
156
+ build_repository!(args: args, opts: opts.merge(repository: source)) if has_source?
157
+ end
158
+
159
+ def deploy_releases(args:, opts:)
160
+ deploy_release(args: args, opts: opts.merge(repository: profile)) if has_profile?
161
+ deploy_release(args: args, opts: opts.merge(repository: source)) if has_source?
162
+ end
163
+
164
+ def deploy_release(args:, opts:)
165
+ package_release!(args: args, opts: opts)[:release].tap do |release|
166
+ unless release.nil?
167
+ publish_release!(args: args, opts: opts.merge(release: release))
168
+ end
169
+ end
170
+ end
171
+
172
+ define_task_method("promptless_authen!", task: :promptless_authen, worker: :authenticator)
173
+ define_task_method("public_key!", task: :public_key, worker: :authenticator, locally: true)
174
+ define_task_method("build_repository!", task: :build, worker: :repository, locally: true)
175
+ define_task_method("package_release!", task: :package, worker: :repository, locally: true)
176
+ define_task_method("publish_release!", task: :publish, worker: :publisher)
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,67 @@
1
+ module Luban
2
+ module Deployment
3
+ class Application
4
+ class Builder < Luban::Deployment::Worker::Remote
5
+ def envrc_template_file
6
+ @envrc_template_file ||= find_template_file("envrc.erb")
7
+ end
8
+
9
+ def unset_envrc_template_file
10
+ @unset_envrc_template_file ||= find_template_file("unset_envrc.erb")
11
+ end
12
+
13
+ def build
14
+ bootstrap
15
+ create_envrc_files
16
+ end
17
+
18
+ def destroy
19
+ task.opts.destroy_project ? destroy_project : destroy_app
20
+ end
21
+
22
+ def destroy_project
23
+ rm(etc_path.join('*', "#{stage}.#{project}.*"))
24
+ rmdir(project_path)
25
+ update_result "The project environment is destroyed."
26
+ end
27
+
28
+ def destroy_app
29
+ rm(etc_path.join('*', "#{stage}.#{project}.#{application}.*"))
30
+ rm(app_path)
31
+ update_result "The application environment is destroyed."
32
+ end
33
+
34
+ def cleanup
35
+ execute("find #{tmp_path}/* -type f|xargs rm -f")
36
+ update_result "Temporary files in app environment is cleaned up."
37
+ end
38
+
39
+ protected
40
+
41
+ def bootstrap
42
+ assure_dirs(etc_path, tmp_path,
43
+ app_bin_path, app_tmp_path,
44
+ releases_path, shared_path)
45
+ assure_linked_dirs
46
+ end
47
+
48
+ def assure_linked_dirs
49
+ return if linked_dirs.empty?
50
+ linked_dirs.each do |dir|
51
+ linked_dir = shared_path.join(dir)
52
+ assure(:directory, linked_dir) { mkdir(linked_dir) }
53
+ end
54
+ end
55
+
56
+ def create_envrc_files
57
+ upload_by_template(file_to_upload: envrc_file,
58
+ template_file: envrc_template_file,
59
+ auto_revision: true)
60
+ upload_by_template(file_to_upload: unset_envrc_file,
61
+ template_file: unset_envrc_template_file,
62
+ auto_revision: true)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,215 @@
1
+ module Luban
2
+ module Deployment
3
+ class Application
4
+ class Publisher < Luban::Deployment::Worker::Remote
5
+ def release_name; task.opts.release[:name]; end
6
+ def release_tag; task.opts.release[:tag]; end
7
+ def release_package_path; task.opts.release[:path]; end
8
+ def release_md5; task.opts.release[:md5]; end
9
+ def bundled_gems; task.opts.release[:bundled_gems]; end
10
+ def locked_gemfile; bundled_gems[:locked_gemfile]; end
11
+ def gems_source; bundled_gems[:gems_cache]; end
12
+ def gems; bundled_gems[:gems]; end
13
+
14
+ def publish_app?; release_name == 'app'; end
15
+ def publish_profile?; release_name == 'profile'; end
16
+
17
+ def display_name
18
+ @display_name ||= "#{application} #{release_name} (release: #{release_tag})"
19
+ end
20
+
21
+ def releases_path
22
+ @releases_path ||= super.join(release_name)
23
+ end
24
+
25
+ def release_path
26
+ @release_path ||= releases_path.join(release_tag)
27
+ end
28
+
29
+ def releases_log_path
30
+ @releases_log_path ||= app_path.join('releases.log')
31
+ end
32
+
33
+ def gemfile
34
+ @gemfile ||= release_path.join('Gemfile')
35
+ end
36
+
37
+ def bundle_cmd
38
+ @bundle_cmd ||= app_bin_path.join('bundle')
39
+ end
40
+
41
+ def bundle_config_path
42
+ @bundle_config_path ||= shared_path.join('.bundle')
43
+ end
44
+
45
+ def bundle_path
46
+ @bundle_path ||= shared_path.join('vendor').join('bundle')
47
+ end
48
+
49
+ def gems_cache_path
50
+ @gems_cache_path ||= shared_path.join('vendor').join('cache')
51
+ end
52
+
53
+ def bundle_without
54
+ @bundle_without ||= %w(development test)
55
+ end
56
+
57
+ def bundle_flags
58
+ @bundle_flags ||= %w(--deployment --quiet)
59
+ end
60
+
61
+ def bundle_linked_dirs
62
+ @bundle_linked_dirs ||= %w(.bundle vendor/cache vendor/bundle)
63
+ end
64
+
65
+ def published?
66
+ get_releases.include?(release_tag)
67
+ end
68
+
69
+ def publish
70
+ assure_dirs(releases_path)
71
+ if published?
72
+ if force?
73
+ publish!
74
+ else
75
+ update_result "Skipped! #{display_name} has been published ALREADY.", status: :skipped
76
+ return
77
+ end
78
+ else
79
+ publish!
80
+ end
81
+
82
+ if published?
83
+ update_result "Successfully published #{display_name}."
84
+ else
85
+ update_result "Failed to publish #{display_name}", status: :failed, level: :error
86
+ end
87
+ end
88
+
89
+ def after_publish
90
+ create_symlinks
91
+ bundle_gems
92
+ end
93
+
94
+ protected
95
+
96
+ def get_releases
97
+ capture(:ls, '-xt', releases_path).split
98
+ end
99
+
100
+ def publish!
101
+ rollout_release
102
+ cleanup_releases
103
+ end
104
+
105
+ def rollout_release
106
+ upload_to = app_tmp_path.join(release_package_path.basename)
107
+ upload!(release_package_path.to_s, upload_to.to_s)
108
+ if md5_matched?(upload_to, release_md5) and
109
+ test(:tar, "-xzf #{upload_to} -C #{releases_path}")
110
+ create_symlinks
111
+ update_releases_log
112
+ else
113
+ rm(release_path)
114
+ end
115
+ ensure
116
+ rm(upload_to)
117
+ end
118
+
119
+ def create_symlinks
120
+ send("create_#{release_name}_symlinks")
121
+ end
122
+
123
+ def create_profile_symlinks
124
+ create_release_symlink(shared_path)
125
+ create_shared_symlinks_for(:directory, linked_dirs)
126
+ create_shared_symlinks_for(:directory, bundle_linked_dirs) if file?(gemfile)
127
+ end
128
+
129
+ def create_app_symlinks
130
+ create_release_symlink(app_path)
131
+ create_shared_symlinks_for(:directory, linked_dirs | %w(profile))
132
+ create_shared_symlinks_for(:directory, bundle_linked_dirs) if file?(gemfile)
133
+ create_shared_symlinks_for(:file, linked_files)
134
+ end
135
+
136
+ def create_release_symlink(target_dir)
137
+ assure_symlink(release_path, target_dir.join(release_name))
138
+ end
139
+
140
+ def create_shared_symlinks_for(type, linked_paths)
141
+ linked_paths.each do |path|
142
+ target_path = release_path.join(path)
143
+ assure_dirs(target_path.dirname)
144
+ source_path = shared_path.join(path)
145
+ assure_symlink(source_path, target_path)
146
+ end
147
+ end
148
+
149
+ def update_releases_log
150
+ execute %{echo "[$(date -u)][#{user}] #{release_log_message}" >> #{releases_log_path}}
151
+ end
152
+
153
+ def release_log_message
154
+ "Release #{display_name} in #{stage} #{project} is published successfully."
155
+ end
156
+
157
+ def cleanup_releases
158
+ releases = get_releases
159
+ if releases.count > keep_releases
160
+ releases_to_keep = releases.first(keep_releases)
161
+ unless releases_to_keep.include?(release_tag)
162
+ releases_to_keep[-1] = release_tag
163
+ end
164
+ releases_to_remove = releases - releases_to_keep
165
+ releases_to_remove.each do |release|
166
+ rm(releases_path.join(release))
167
+ end
168
+ info "Removed #{releases_to_remove.count} old releases."
169
+ else
170
+ info "No old releases to remove (keeping most recent #{keep_releases} releases)."
171
+ end
172
+ end
173
+
174
+ def bundle_gems
175
+ assure_dirs(bundle_config_path, gems_cache_path, bundle_path)
176
+ sync_gems_cache
177
+ sync_locked_gemfile
178
+ install_gems_from_cache
179
+ end
180
+
181
+ def sync_gems_cache
182
+ capture(:ls, '-xt', gems_cache_path).split.each do |gem_name|
183
+ rm(gems_cache_path.join(gem_name)) unless gems.has_key?(gem_name)
184
+ end
185
+ gems.each_pair do |gem_name, md5|
186
+ gem_path = gems_cache_path.join(gem_name)
187
+ unless md5_matched?(gem_path, md5)
188
+ upload!(gems_source.join(gem_name).to_s, gem_path.to_s)
189
+ end
190
+ end
191
+ end
192
+
193
+ def sync_locked_gemfile
194
+ gemfile_lock_path = release_path.join(locked_gemfile[:path].basename)
195
+ unless md5_matched?(gemfile_lock_path, locked_gemfile[:md5])
196
+ upload!(locked_gemfile[:path].to_s, gemfile_lock_path.to_s)
197
+ end
198
+ end
199
+
200
+ def install_gems_from_cache
201
+ within(release_path) do
202
+ options = []
203
+ options << "--gemfile #{gemfile}"
204
+ options << "--path #{bundle_path}"
205
+ unless test(bundle_cmd, :check, *options)
206
+ options << "--without #{bundle_without.join(' ')}"
207
+ options << bundle_flags.join(' ')
208
+ execute(bundle_cmd, :install, *options)
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end