shuttle-deploy 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +25 -0
  3. data/.magnum.yml +1 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +18 -0
  7. data/README.md +230 -0
  8. data/Rakefile +10 -0
  9. data/bin/shuttle +60 -0
  10. data/examples/rails.yml +22 -0
  11. data/examples/static.yml +10 -0
  12. data/examples/wordpress.yml +34 -0
  13. data/lib/shuttle/config.rb +5 -0
  14. data/lib/shuttle/deploy.rb +58 -0
  15. data/lib/shuttle/deployment/nodejs.rb +48 -0
  16. data/lib/shuttle/deployment/php.rb +17 -0
  17. data/lib/shuttle/deployment/rails.rb +116 -0
  18. data/lib/shuttle/deployment/ruby.rb +40 -0
  19. data/lib/shuttle/deployment/static.rb +5 -0
  20. data/lib/shuttle/deployment/wordpress/cli.rb +27 -0
  21. data/lib/shuttle/deployment/wordpress/core.rb +50 -0
  22. data/lib/shuttle/deployment/wordpress/plugins.rb +112 -0
  23. data/lib/shuttle/deployment/wordpress/vip.rb +84 -0
  24. data/lib/shuttle/deployment/wordpress.rb +191 -0
  25. data/lib/shuttle/errors.rb +5 -0
  26. data/lib/shuttle/helpers.rb +50 -0
  27. data/lib/shuttle/runner.rb +153 -0
  28. data/lib/shuttle/session.rb +52 -0
  29. data/lib/shuttle/support/bundler.rb +45 -0
  30. data/lib/shuttle/support/foreman.rb +7 -0
  31. data/lib/shuttle/support/thin.rb +59 -0
  32. data/lib/shuttle/target.rb +23 -0
  33. data/lib/shuttle/tasks.rb +264 -0
  34. data/lib/shuttle/version.rb +3 -0
  35. data/lib/shuttle.rb +35 -0
  36. data/shuttle-deploy.gemspec +28 -0
  37. data/spec/deploy_spec.rb +4 -0
  38. data/spec/fixtures/.gitkeep +0 -0
  39. data/spec/fixtures/static.yml +11 -0
  40. data/spec/helpers_spec.rb +42 -0
  41. data/spec/spec_helper.rb +15 -0
  42. data/spec/target_spec.rb +41 -0
  43. metadata +232 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZTM3MDZmNjBlN2EzYTRkZmRlNDc5OTk1ZWM5MGU0MzhmMGVhZTg2Mg==
5
+ data.tar.gz: !binary |-
6
+ NGRkOTVmY2IzYzU3MDZmMTRmNWQ1YzE3MTYzMjk4NGFlZjc4NzdkNQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZjBhNjhmOGQyNjNjMTA3MDJjNzM3MjcxOTBhMjIzMmJmYmU2YTk2ZmI0MjMz
10
+ MDM0MTc5YzMwZjlmMTQxNTdmNGQyZjJhOTdlYjRkMWY5ODUwMDJhYjY2MDY5
11
+ ZjRmYTczN2VhOGYyNjNiNGY5OTc2ZWQ3YWQ0YTI3MDE2MGI1ZjM=
12
+ data.tar.gz: !binary |-
13
+ M2RkNzViNGNlZmY2ZWRjMTczOTVhNWFkNmYyMmRiZjQ3ZTE4NmNjNzViNWM3
14
+ YTVjMGI5ZjViYzhiNjQ0MzUwOGRhZDFlMDFlZmM0MDNiNTJjZGFiZGViMWE4
15
+ NTA4MTQyMTI4MGZmOGQ1YTYyMjI4ODVmNGYyMzJkNzE0MDAwNjg=
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ *~
6
+ .DS_Store
7
+ .\#*
8
+ .bundle
9
+ .config
10
+ .yardoc
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ \#*
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
24
+ tmtags
25
+ Gemfile.lock
data/.magnum.yml ADDED
@@ -0,0 +1 @@
1
+ ruby: 1.9.3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=documentation
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2012-2013 Dan Sosedoff.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # Shuttle
2
+
3
+ Shuttle is a minimalistic application deployment tool designed for small applications
4
+ and one-server deployments. Configuration is stored as YAML-encoded file, no need to use ruby code.
5
+ Operations are performed on SSH connection with target server.
6
+
7
+ *Under heavy development*
8
+
9
+ ## Install
10
+
11
+ Clone repository and run:
12
+
13
+ ```
14
+ rake install
15
+ ```
16
+
17
+ Supported ruby versions:
18
+
19
+ - 1.8.7
20
+ - 1.9.2
21
+ - 1.9.3
22
+ - 2.0.0
23
+
24
+ ## Structure
25
+
26
+ Deployment structure is very similar to capistrano.
27
+
28
+ Application files will be stored in `deploy_to` path that you specify in config.
29
+ Directory structure:
30
+
31
+ - `releases` - Main directory to store all application releases
32
+ - `current` - Symbolic link to the latest release
33
+ - `shared` - Shared directory to store assets, configs, etc
34
+ - `scm` - Code repository directory
35
+ - `version` - File that contains current release number
36
+
37
+ Shared directory structure:
38
+
39
+ - `tmp` - Temporary files
40
+ - `pids` - Shared process IDs files
41
+ - `log` - Shared log files
42
+
43
+ ## Process
44
+
45
+ Deployment flow is split into steps:
46
+
47
+ - Establish connection with target server
48
+ - Prepare application structure. It'll create all required directories or skip if they already exist.
49
+ - Clone repository or check out latest code. Submodules will be automatically updated as well.
50
+ - Switch to specified branch (`master` by default)
51
+ - Create a new release directory and checkout application code
52
+ - Perform strategy-related tasks.
53
+ - Create a symbolic link to the latest release
54
+ - Clean up old releases (default count: 5)
55
+
56
+ ## Strategies
57
+
58
+ Available deployment strategies:
59
+
60
+ - `static`
61
+ - `wordpress`
62
+ - `ruby`
63
+ - `rails`
64
+ - `nodejs`
65
+
66
+ ### Static Strategy
67
+
68
+ This is a default strategy that does not perform any application-related tasks.
69
+ Example configuration:
70
+
71
+ ```yaml
72
+ app:
73
+ name: my-application
74
+ strategy: static
75
+ git: git@github.com:my-site.git
76
+
77
+ target:
78
+ host: my-host.com
79
+ user: username
80
+ password: password
81
+ deploy_to: /home/deployer/www
82
+ ```
83
+
84
+ ### Wordpress Strategy
85
+
86
+ This strategy is designed to deploy wordpress sites developed as a separate theme.
87
+ It requires `subversion` installed on the server (will be automatically installed).
88
+
89
+ ## Deployment Config
90
+
91
+ Deployment config has a few main sections: `app` and `target`.
92
+
93
+ ### Application
94
+
95
+ Application section defines deployment strategy, source code location and other options:
96
+
97
+ ```yml
98
+ app:
99
+ name: my-app
100
+ strategy: static
101
+ git: https://site-url.com/repo.git
102
+ branch: master
103
+ keep_releases: 5
104
+ ```
105
+
106
+ Options:
107
+
108
+ - `name` - Your application name
109
+ - `strategy` - Deployment strategy. Defaults to `static`
110
+ - `git` - Git repository url
111
+ - `branch` - Git repository branch. Defaults to `master`
112
+ - `keep_releases` - Number of releases to keep. Defaults to `10`
113
+
114
+ You can also use Subversion as a main source:
115
+
116
+ ```yml
117
+ app:
118
+ svn: http://site-url.com/repo.git
119
+ ```
120
+
121
+ If your repository requires authentication, use url in the following format:
122
+
123
+ ```
124
+ http://username:password@yourdomain.com/project
125
+ ```
126
+
127
+ ### Target
128
+
129
+ Target is a set of remote machine credentials:
130
+
131
+ ```yml
132
+ target:
133
+ host: yourdomain.com
134
+ user: deployer
135
+ password: password
136
+ deploy_to: /home/deployer/myapp
137
+ ```
138
+
139
+ Options:
140
+
141
+ - `host` - Remote server host or ip
142
+ - `user` - Remote server user account
143
+ - `password` - Optional password. Use passwordless authentication if possible.
144
+ - `deploy_to` - Primary directory where all releases will be stored
145
+
146
+ You can also define multiple targets per config if environments does not have any specific
147
+ configuration settings:
148
+
149
+ ```yml
150
+ targets:
151
+ production:
152
+ host: mydomain.com
153
+ user: deployer
154
+ deploy_to: /home/production/myapp
155
+ staging:
156
+ host: mydomain.com
157
+ user: deployer
158
+ deploy_to: /home/staging/myapp
159
+ ```
160
+
161
+ ## Usage
162
+
163
+ To execute a new deploy, simply type (in your project folder):
164
+
165
+ ```
166
+ shuttle deploy
167
+ ```
168
+
169
+ Output will look like this:
170
+
171
+ ```
172
+ Shuttle v0.2.0
173
+
174
+ -----> Connected to deployer@mysite.com
175
+ -----> Preparing application structure
176
+ -----> Fetching latest code
177
+ -----> Using branch 'master'
178
+ -----> Linking release
179
+ -----> Release v35 has been deployed
180
+ -----> Cleaning up old releases: 1
181
+
182
+ Execution time: 2s
183
+ ```
184
+
185
+ If using multiple targets in config, you can specify which target to use with:
186
+
187
+ ```
188
+ shuttle staging deploy
189
+ ```
190
+
191
+ Specify a path to config with `-f` flag:
192
+
193
+ ```
194
+ shuttle -f /path/to/config.yml deploy
195
+ ```
196
+
197
+ To run in debug mode, add `-d` flag:
198
+
199
+ ```
200
+ shuttle deploy -d
201
+ ```
202
+
203
+ ## Test
204
+
205
+ To run project test suite execute:
206
+
207
+ ```
208
+ bundle exec rake test
209
+ ```
210
+
211
+ ## License
212
+
213
+ Copyright (c) 2012-2013 Dan Sosedoff.
214
+
215
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
216
+ this software and associated documentation files (the "Software"), to deal in
217
+ the Software without restriction, including without limitation the rights to
218
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
219
+ the Software, and to permit persons to whom the Software is furnished to do so,
220
+ subject to the following conditions:
221
+
222
+ The above copyright notice and this permission notice shall be included in all
223
+ copies or substantial portions of the Software.
224
+
225
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
226
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
227
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
228
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
229
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
230
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = 'spec/*_spec.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :test
data/bin/shuttle ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'rubygems'
7
+ require 'optparse'
8
+ require 'shuttle'
9
+
10
+ config_name = Dir.pwd.split('/').last + ".yml"
11
+
12
+ options = {
13
+ :path => File.join(ENV['HOME'], '.shuttle', config_name),
14
+ :target => 'production',
15
+ :log => false
16
+ }
17
+
18
+ optparse = OptionParser.new do |opts|
19
+ opts.on('-v', '--version', 'Show version') do
20
+ puts "Shuttle version #{Shuttle::VERSION}"
21
+ exit 0
22
+ end
23
+
24
+ opts.on('-e', '--environment NAME', 'Deployment target environment') do |v|
25
+ options[:target] = v
26
+ end
27
+
28
+ opts.on('-d', '--debug', 'Enable debugging') do
29
+ options[:log] = true
30
+ end
31
+
32
+ opts.on('-f', '--file PATH', 'Configuration file path') do |v|
33
+ options[:path] = v
34
+ end
35
+ end
36
+
37
+ begin
38
+ optparse.parse!
39
+ rescue OptionParser::ParseError => e
40
+ puts "Error: #{e.message}"
41
+ exit 1
42
+ end
43
+
44
+ case ARGV.size
45
+ when 2
46
+ options[:target] = ARGV.shift
47
+ command = ARGV.shift
48
+ when 1
49
+ command = ARGV.shift
50
+ else
51
+ puts "Command required"
52
+ exit 1
53
+ end
54
+
55
+ begin
56
+ runner = Shuttle::Runner.new(options)
57
+ runner.execute(command.dup)
58
+ rescue Shuttle::ConfigError => err
59
+ puts "Error: #{err.message}."
60
+ end
@@ -0,0 +1,22 @@
1
+ app:
2
+ name: rails-app
3
+ strategy: rails
4
+ git: git@github.com:user/repo.git
5
+
6
+ target:
7
+ host: rails-app.com
8
+ user: deployer
9
+ password: password
10
+ deploy_to: /home/deployer/www
11
+
12
+ rails:
13
+ environment: production
14
+ precompile_assets: true
15
+ start_server: true
16
+ shared_paths:
17
+ static_assets: public/static
18
+
19
+ thin:
20
+ host: 127.0.0.1
21
+ port: 9000
22
+ servers: 5
@@ -0,0 +1,10 @@
1
+ app:
2
+ name: sample-site
3
+ strategy: static
4
+ git: git@github.com/username/sample-site.git
5
+
6
+ target:
7
+ host: sample-site.com
8
+ user: deployer
9
+ password: password
10
+ deploy_to: /home/deployer/www
@@ -0,0 +1,34 @@
1
+ app:
2
+ name: sample-site
3
+ strategy: wordpress
4
+ git: git@github.com/username/sample-site.git
5
+ branch: production
6
+
7
+ target:
8
+ host: sample-site.com
9
+ user: deployer
10
+ password: password
11
+ deploy_to: /home/deployer/www
12
+
13
+ wordpress:
14
+ theme: theme_name
15
+ site:
16
+ title: "Site Title"
17
+ url: "http://sample-site.com"
18
+ admin_name: "admin"
19
+ admin_email: "admin@admin.com"
20
+ admin_password: "password"
21
+ mysql:
22
+ host: 127.0.0.1
23
+ user: mysql-user
24
+ password: mysql-password
25
+ database: mysql-dadtabase
26
+ plugins:
27
+ - advanced-custom-fields: git://github.com/elliotcondon/acf.git
28
+ - be-subpages-widget
29
+ - contact-form-7
30
+ - contact-form-7-to-database-extension
31
+ - featured-page-widget
32
+ - share-this
33
+ - twitter-widget-pro
34
+ - types
@@ -0,0 +1,5 @@
1
+ module Shuttle
2
+ class Config
3
+ # TODO
4
+ end
5
+ end
@@ -0,0 +1,58 @@
1
+ module Shuttle
2
+ class Deploy
3
+ include Shuttle::Tasks
4
+ include Shuttle::Helpers
5
+
6
+ attr_reader :ssh
7
+ attr_reader :target
8
+ attr_reader :environment
9
+ attr_reader :version
10
+ attr_reader :config
11
+
12
+ def initialize(config, ssh, target, environment)
13
+ @config = config
14
+ @target = target
15
+ @ssh = ssh
16
+ @environment = environment
17
+
18
+ if ssh.file_exists?(version_path)
19
+ res = ssh.capture("cat #{version_path}")
20
+ @version = (res.empty? ? 1 : Integer(res) + 1).to_s
21
+ else
22
+ @version = 1
23
+ end
24
+ end
25
+
26
+ def deploy_path(path=nil)
27
+ [target.deploy_to, path].compact.join('/')
28
+ end
29
+
30
+ def shared_path(path=nil)
31
+ [deploy_path, 'shared', path].compact.join('/')
32
+ end
33
+
34
+ def current_path(path=nil)
35
+ [deploy_path, 'current', path].compact.join('/')
36
+ end
37
+
38
+ def version_path
39
+ deploy_path('version')
40
+ end
41
+
42
+ def release_path(path=nil)
43
+ [deploy_path, 'releases', version, path].compact.join('/')
44
+ end
45
+
46
+ def scm_path
47
+ deploy_path('scm')
48
+ end
49
+
50
+ def deploy
51
+ setup
52
+ update_code
53
+ checkout_code
54
+ link_release
55
+ cleanup_releases
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,48 @@
1
+ module Shuttle
2
+ class Nodejs < Shuttle::Deploy
3
+ def setup
4
+ if node_installed?
5
+ log "Using Node.js v#{node_version}, Npm v#{npm_version}"
6
+ else
7
+ error "Node.js is not installed."
8
+ end
9
+
10
+ super
11
+ end
12
+
13
+ def deploy
14
+ setup
15
+ update_code
16
+ checkout_code
17
+ install_dependencies
18
+ link_release
19
+ cleanup_releases
20
+ end
21
+
22
+ private
23
+
24
+ def node_installed?
25
+ ssh.run("which node").success?
26
+ end
27
+
28
+ def node_version
29
+ ssh.run("node -v").output.strip.gsub('v', '')
30
+ end
31
+
32
+ def npm_version
33
+ ssh.run("npm -v").output.strip
34
+ end
35
+
36
+ def install_dependencies
37
+ if ssh.file_exists?("#{release_path}/package.json")
38
+ log "Installing application dependencies"
39
+
40
+ result = ssh.run("cd #{release_path} && npm install")
41
+
42
+ if result.failure?
43
+ error "Unable to install dependencies: #{result.output}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ module Shuttle
2
+ class Php < Shuttle::Deploy
3
+ def setup
4
+ unless php_installed?
5
+ error "Please install PHP first"
6
+ end
7
+
8
+ super
9
+ end
10
+
11
+ private
12
+
13
+ def php_installed?
14
+ ssh.run("which php").success?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,116 @@
1
+ module Shuttle
2
+ class Rails < Shuttle::Deploy
3
+ include Shuttle::Support::Bundler
4
+ include Shuttle::Support::Thin
5
+
6
+ def rails_env
7
+ if config.rails && config.rails.environment
8
+ config.rails.environment
9
+ else
10
+ environment
11
+ end
12
+ end
13
+
14
+ def precompile_assets?
15
+ config.rails && config.rails.precompile_assets != false
16
+ end
17
+
18
+ def start_server?
19
+ if config.rails && !config.rails.start_server.nil?
20
+ config.rails.start_server
21
+ else
22
+ true
23
+ end
24
+ end
25
+
26
+ def setup_bundler
27
+ if !bundler_installed?
28
+ log "Bundler is missing. Installing"
29
+
30
+ res = ssh.run("sudo gem install bundler")
31
+ if res.success?
32
+ log "Bundler v#{bundler_version} installed"
33
+ else
34
+ error "Unable to install bundler: #{res.output}"
35
+ end
36
+ end
37
+ end
38
+
39
+ def rake(command)
40
+ res = ssh.run("cd #{release_path} && rake #{command}")
41
+ if res.failure?
42
+ error "Unable to run rake command: #{command}. Reason: #{res.output}"
43
+ end
44
+ end
45
+
46
+ def precompile_assets
47
+ if precompile_assets?
48
+ log "Precompiling assets"
49
+ rake 'assets:precompile'
50
+ end
51
+ end
52
+
53
+ def deploy
54
+ ssh.export('RACK_ENV', rails_env)
55
+ ssh.export('RAILS_ENV', rails_env)
56
+
57
+ log "Rails environment is set to #{rails_env}"
58
+
59
+ setup
60
+ setup_bundler
61
+ update_code
62
+ checkout_code
63
+ bundle_install
64
+ migrate_database
65
+ precompile_assets
66
+ link_shared_paths
67
+
68
+ if start_server?
69
+ thin_restart
70
+ end
71
+
72
+ link_release
73
+ cleanup_releases
74
+ end
75
+
76
+ def migrate_database
77
+ return if !ssh.file_exists?(release_path('db/schema.rb'))
78
+
79
+ migrate = true # Will migrate by default
80
+ schema = ssh.read_file(release_path('db/schema.rb'))
81
+ schema_file = shared_path('schema')
82
+ checksum = Digest::SHA1.hexdigest(schema)
83
+
84
+ if ssh.file_exists?(schema_file)
85
+ old_checksum = ssh.read_file(schema_file).strip
86
+ if old_checksum == checksum
87
+ migrate = false
88
+ end
89
+ end
90
+
91
+ if migrate == true
92
+ log "Migrating database"
93
+ rake 'db:migrate'
94
+ ssh.run("echo #{checksum} > #{schema_file}")
95
+ else
96
+ log "Database migration skipped"
97
+ end
98
+ end
99
+
100
+ def link_shared_paths
101
+ ssh.run("mkdir -p #{release_path('tmp')}")
102
+ ssh.run("rm -rf #{release_path}/log")
103
+ ssh.run("ln -s #{shared_path('pids')} #{release_path('tmp/pids')}")
104
+ ssh.run("ln -s #{shared_path('log')} #{release_path('log')}")
105
+
106
+ if config.rails
107
+ if config.rails.shared_paths
108
+ config.rails.shared_paths.each_pair do |name, path|
109
+ log "Linking shared path: #{name}"
110
+ ssh.run("ln -s #{shared_path}/#{name} #{release_path}/#{path}")
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end