luban 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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