shuttle-deploy 0.2.0.beta1
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.
- checksums.yaml +15 -0
- data/.gitignore +25 -0
- data/.magnum.yml +1 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/LICENSE +18 -0
- data/README.md +230 -0
- data/Rakefile +10 -0
- data/bin/shuttle +60 -0
- data/examples/rails.yml +22 -0
- data/examples/static.yml +10 -0
- data/examples/wordpress.yml +34 -0
- data/lib/shuttle/config.rb +5 -0
- data/lib/shuttle/deploy.rb +58 -0
- data/lib/shuttle/deployment/nodejs.rb +48 -0
- data/lib/shuttle/deployment/php.rb +17 -0
- data/lib/shuttle/deployment/rails.rb +116 -0
- data/lib/shuttle/deployment/ruby.rb +40 -0
- data/lib/shuttle/deployment/static.rb +5 -0
- data/lib/shuttle/deployment/wordpress/cli.rb +27 -0
- data/lib/shuttle/deployment/wordpress/core.rb +50 -0
- data/lib/shuttle/deployment/wordpress/plugins.rb +112 -0
- data/lib/shuttle/deployment/wordpress/vip.rb +84 -0
- data/lib/shuttle/deployment/wordpress.rb +191 -0
- data/lib/shuttle/errors.rb +5 -0
- data/lib/shuttle/helpers.rb +50 -0
- data/lib/shuttle/runner.rb +153 -0
- data/lib/shuttle/session.rb +52 -0
- data/lib/shuttle/support/bundler.rb +45 -0
- data/lib/shuttle/support/foreman.rb +7 -0
- data/lib/shuttle/support/thin.rb +59 -0
- data/lib/shuttle/target.rb +23 -0
- data/lib/shuttle/tasks.rb +264 -0
- data/lib/shuttle/version.rb +3 -0
- data/lib/shuttle.rb +35 -0
- data/shuttle-deploy.gemspec +28 -0
- data/spec/deploy_spec.rb +4 -0
- data/spec/fixtures/.gitkeep +0 -0
- data/spec/fixtures/static.yml +11 -0
- data/spec/helpers_spec.rb +42 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/target_spec.rb +41 -0
- metadata +232 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
module Shuttle
|
2
|
+
class Ruby < Shuttle::Deploy
|
3
|
+
include Shuttle::Support::Bundler
|
4
|
+
include Shuttle::Support::Thin
|
5
|
+
|
6
|
+
def setup
|
7
|
+
unless ruby_installed?
|
8
|
+
error "Please install Ruby first"
|
9
|
+
end
|
10
|
+
|
11
|
+
unless bundle_installed?
|
12
|
+
install_bundler
|
13
|
+
end
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def deploy
|
19
|
+
setup
|
20
|
+
update_code
|
21
|
+
checkout_code
|
22
|
+
bundle_install
|
23
|
+
thin_restart
|
24
|
+
link_shared_paths
|
25
|
+
link_release
|
26
|
+
end
|
27
|
+
|
28
|
+
def link_shared_paths
|
29
|
+
ssh.run("mkdir -p #{release_path('tmp')}")
|
30
|
+
ssh.run("ln -s #{shared_path('pids')} #{release_path('tmp/pids')}")
|
31
|
+
ssh.run("ln -s #{shared_path('log')} #{release_path('log')}")
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def ruby_installed?
|
37
|
+
ssh.run("which ruby").success?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Shuttle
|
2
|
+
module WordpressCli
|
3
|
+
CLI_GIT = 'https://github.com/wp-cli/wp-cli.git'
|
4
|
+
CLI_PATH = '/usr/local/share/wp-cli'
|
5
|
+
|
6
|
+
# Check if CLI is installed
|
7
|
+
# @return [Boolean]
|
8
|
+
def cli_installed?
|
9
|
+
ssh.run("which wp").success?
|
10
|
+
end
|
11
|
+
|
12
|
+
# Install wordpress CLI
|
13
|
+
# @return [Boolean]
|
14
|
+
def cli_install
|
15
|
+
log "Installing wordpress CLI"
|
16
|
+
|
17
|
+
ssh.run("sudo git clone --recursive --quiet #{CLI_GIT} #{CLI_PATH}")
|
18
|
+
ssh.run("cd #{CLI_PATH} && sudo utils/dev-build")
|
19
|
+
|
20
|
+
if cli_installed?
|
21
|
+
log "Wordpress CLI installed"
|
22
|
+
else
|
23
|
+
error "Unable to install wordpress CLI"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Shuttle
|
2
|
+
module WordpressCore
|
3
|
+
# Get wordpress shared core path
|
4
|
+
# @return [String]
|
5
|
+
def core_path
|
6
|
+
@core_path ||= shared_path('wordpress/core')
|
7
|
+
end
|
8
|
+
|
9
|
+
# Check if wordpress core is installed
|
10
|
+
# @return [Boolean]
|
11
|
+
def core_installed?
|
12
|
+
ssh.directory_exists?(core_path) &&
|
13
|
+
!ssh.capture("ls #{core_path}").empty?
|
14
|
+
end
|
15
|
+
|
16
|
+
# Install wordpress shared core
|
17
|
+
# @param [Boolean] overwrite existing code
|
18
|
+
# @return [Boolean]
|
19
|
+
def core_install(overwrite=true)
|
20
|
+
if core_installed? && overwrite == true
|
21
|
+
core_remove
|
22
|
+
end
|
23
|
+
|
24
|
+
log "Installing wordpress core"
|
25
|
+
|
26
|
+
unless ssh.directory_exists?(core_path)
|
27
|
+
ssh.run("mkdir -p #{core_path}")
|
28
|
+
end
|
29
|
+
|
30
|
+
result = ssh.run("cd #{core_path} && wp core download")
|
31
|
+
|
32
|
+
if result.success?
|
33
|
+
log "Wordpress core installed"
|
34
|
+
else
|
35
|
+
error "Unable to install wordpress core: #{result.output}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Remove wordpress shared core
|
40
|
+
# @return [Boolean]
|
41
|
+
def core_remove
|
42
|
+
if ssh.directory_exists?(core_path)
|
43
|
+
log "Removing wordpress shared core"
|
44
|
+
ssh.run("rm -rf #{core_path}")
|
45
|
+
end
|
46
|
+
|
47
|
+
ssh.directory_exists?(core_path)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Shuttle
|
2
|
+
module WordpressPlugins
|
3
|
+
# Install wordpresss plugin
|
4
|
+
# @param [String] plugin name slug
|
5
|
+
def plugin_install(name)
|
6
|
+
log "Installing plugin: #{name}"
|
7
|
+
|
8
|
+
res = ssh.run("cd #{release_path} && wp plugin install #{name}")
|
9
|
+
if !res.success?
|
10
|
+
error "Unable to install plugin '#{name}'. Reason: #{res.output}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def plugin_custom_install(name, url)
|
15
|
+
log "Installing custom plugin: #{name} -> #{url}"
|
16
|
+
|
17
|
+
if git_url?(url)
|
18
|
+
install_git_plugin(name, url)
|
19
|
+
elsif file_url?(url)
|
20
|
+
install_file_plugin(name, url)
|
21
|
+
else
|
22
|
+
error "Valid git URL or archive URL is required for plugin: #{name}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check if wordpress plugin is installed
|
27
|
+
# @return [Boolean]
|
28
|
+
def plugin_installed?(name)
|
29
|
+
raise "Not Implemented"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Check if provided plugin url is a git repository
|
35
|
+
# @return [Boolean]
|
36
|
+
def git_url?(url)
|
37
|
+
url.include?('.git') || url.include?('git@') || url.include?('git://')
|
38
|
+
end
|
39
|
+
|
40
|
+
# Check if provided plugin url is a file
|
41
|
+
# @return [Boolean]
|
42
|
+
def file_url?(url)
|
43
|
+
name = File.basename(url)
|
44
|
+
name.include?('.zip') || name.include?('.tar.gz')
|
45
|
+
end
|
46
|
+
|
47
|
+
def install_git_plugin(plugin_name, url)
|
48
|
+
ssh.run "cd #{release_path}/wp-content/plugins"
|
49
|
+
res = ssh.run "git clone #{url} #{plugin_name}"
|
50
|
+
|
51
|
+
if res.failure?
|
52
|
+
error "Unable to install plugin '#{plugin_name}'. Reason: #{res.output}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Init submodules if any
|
56
|
+
if ssh.file_exists?("#{release_path}/wp-content/plugins/#{plugin_name}/.gitmodules")
|
57
|
+
log "Initializing git submodules for #{plugin_name}"
|
58
|
+
|
59
|
+
res = ssh.run("cd #{plugin_name} && git submodule update --init --recursive")
|
60
|
+
|
61
|
+
if res.failure?
|
62
|
+
error "Unable to update submodules for #{plugin_name}: #{res.output}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Cleanup git folder
|
67
|
+
ssh.run("rm -rf #{release_path}/wp-content/plugins/#{plugin_name}/.git")
|
68
|
+
end
|
69
|
+
|
70
|
+
def install_file_plugin(plugin_name, url)
|
71
|
+
name = File.basename(url)
|
72
|
+
plugin_path = "#{release_path}/wp-content/plugins/"
|
73
|
+
|
74
|
+
if ssh.file_exists?("/tmp/#{name}")
|
75
|
+
ssh.run("rm -f /tmp/#{name}")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Download file first
|
79
|
+
log "Downloading #{url}"
|
80
|
+
result = ssh.run("cd /tmp && wget #{url}")
|
81
|
+
|
82
|
+
if result.failure?
|
83
|
+
error "Unable to download file from #{url}"
|
84
|
+
end
|
85
|
+
|
86
|
+
log "Extracting #{name} to plugins directory"
|
87
|
+
|
88
|
+
if name.include?('.zip')
|
89
|
+
check_unzip
|
90
|
+
|
91
|
+
if ssh.run("unzip /tmp/#{name} -d #{plugin_path}").failure?
|
92
|
+
error "Unable to extract plugin"
|
93
|
+
end
|
94
|
+
elsif name.include?('.tar.gz')
|
95
|
+
if ssh.run("tar -xzf #{name} -C #{plugin_path}").failure?
|
96
|
+
error "Unable to extract plugin"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def check_unzip
|
102
|
+
if ssh.run("which unzip").failure?
|
103
|
+
log "Unzip utility is missing. Installing..."
|
104
|
+
|
105
|
+
ssh.run("sudo apt-get update")
|
106
|
+
if ssh.run("sudo apt-get -y install unzip").failure?
|
107
|
+
error "Unable to install unzip utility"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Shuttle
|
2
|
+
module WordpressVip
|
3
|
+
VIP_URL = "https://vip-svn.wordpress.com/plugins/"
|
4
|
+
|
5
|
+
# Get wordpress VIP shared path
|
6
|
+
# @return [String]
|
7
|
+
def vip_path
|
8
|
+
@vip_path ||= shared_path('wordpress/vip')
|
9
|
+
end
|
10
|
+
|
11
|
+
# Check if wordpress VIP is required
|
12
|
+
# @return [Boolean]
|
13
|
+
def vip_required?
|
14
|
+
!config.wordpress.vip.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
# Check if wordpress VIP is installed
|
18
|
+
# @return [Boolean]
|
19
|
+
def vip_installed?
|
20
|
+
ssh.directory_exists?(vip_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Update wordpress VIP
|
24
|
+
def vip_update
|
25
|
+
if vip_installed?
|
26
|
+
ssh.run("rm -rf #{vip_path}")
|
27
|
+
end
|
28
|
+
|
29
|
+
vip_install
|
30
|
+
end
|
31
|
+
|
32
|
+
def vip_install
|
33
|
+
log "Installing wordpress VIP"
|
34
|
+
|
35
|
+
vip = vip_get_config
|
36
|
+
|
37
|
+
options = [
|
38
|
+
"--username #{vip.user}",
|
39
|
+
"--password #{vip.password}",
|
40
|
+
"--non-interactive",
|
41
|
+
VIP_URL,
|
42
|
+
vip_path
|
43
|
+
].join(' ')
|
44
|
+
|
45
|
+
cmd = "svn co #{options}"
|
46
|
+
|
47
|
+
res = ssh.run(cmd, &method(:stream_output))
|
48
|
+
|
49
|
+
if res.success?
|
50
|
+
log "Wordpress VIP installed"
|
51
|
+
else
|
52
|
+
raise DeployError, "Unable to install wordpress VIP. Reason: #{res.output}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def vip_get_config
|
57
|
+
data = config.wordpress.vip
|
58
|
+
if data.nil?
|
59
|
+
error "Please add VIP credentials to config."
|
60
|
+
end
|
61
|
+
|
62
|
+
if !data.user
|
63
|
+
error "VIP user is empty. Please set :user parameter"
|
64
|
+
end
|
65
|
+
|
66
|
+
if !data.password
|
67
|
+
error "VIP password is empty. Please set :password parameter"
|
68
|
+
end
|
69
|
+
|
70
|
+
data
|
71
|
+
end
|
72
|
+
|
73
|
+
def vip_link
|
74
|
+
ssh.run("mkdir -p #{release_path}/wp-content/themes/vip")
|
75
|
+
result = ssh.run("cp -a #{vip_path} #{release_path('wp-content/themes/vip/plugins')}")
|
76
|
+
|
77
|
+
if result.success?
|
78
|
+
log "Wordpress VIP is linked"
|
79
|
+
else
|
80
|
+
error "Unable to link VIP: #{result.output}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'shuttle/deployment/wordpress/core'
|
2
|
+
require 'shuttle/deployment/wordpress/cli'
|
3
|
+
require 'shuttle/deployment/wordpress/vip'
|
4
|
+
require 'shuttle/deployment/wordpress/plugins'
|
5
|
+
|
6
|
+
module Shuttle
|
7
|
+
class Wordpress < Php
|
8
|
+
include WordpressCli
|
9
|
+
include WordpressCore
|
10
|
+
include WordpressVip
|
11
|
+
include WordpressPlugins
|
12
|
+
|
13
|
+
def setup
|
14
|
+
if config.wordpress.nil?
|
15
|
+
error "Please add :wordpress section to your config"
|
16
|
+
end
|
17
|
+
|
18
|
+
super
|
19
|
+
|
20
|
+
setup_shared_dirs
|
21
|
+
check_dependencies
|
22
|
+
cli_install if !cli_installed?
|
23
|
+
core_install if !core_installed?
|
24
|
+
check_config
|
25
|
+
end
|
26
|
+
|
27
|
+
def deploy
|
28
|
+
setup
|
29
|
+
update_code
|
30
|
+
link_shared_data
|
31
|
+
checkout_theme
|
32
|
+
|
33
|
+
if vip_required?
|
34
|
+
vip_install if !vip_installed?
|
35
|
+
vip_link
|
36
|
+
end
|
37
|
+
|
38
|
+
if !site_installed?
|
39
|
+
site_install
|
40
|
+
network_install
|
41
|
+
end
|
42
|
+
|
43
|
+
check_plugins
|
44
|
+
activate_theme
|
45
|
+
|
46
|
+
link_release
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_dependencies
|
50
|
+
if !svn_installed?
|
51
|
+
log "Installing Subversion"
|
52
|
+
if ssh.run("sudo apt-get install -y subversion").success?
|
53
|
+
log "Subversion installed"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def setup_shared_dirs
|
59
|
+
dirs = [
|
60
|
+
'wordpress',
|
61
|
+
'wordpress/uploads',
|
62
|
+
'wordpress/core',
|
63
|
+
'wordpress/plugins'
|
64
|
+
]
|
65
|
+
|
66
|
+
dirs.each do |path|
|
67
|
+
ssh.run("mkdir -p #{shared_path(path)}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_config
|
72
|
+
mysql = config.wordpress.mysql
|
73
|
+
if mysql.nil?
|
74
|
+
error "Missing :mysql section of the config."
|
75
|
+
end
|
76
|
+
|
77
|
+
cmd = [
|
78
|
+
"wp core config",
|
79
|
+
"--dbname=#{mysql.database}",
|
80
|
+
"--dbhost=#{mysql.host || 'localhost'}",
|
81
|
+
"--dbuser=#{mysql.user}"
|
82
|
+
]
|
83
|
+
|
84
|
+
cmd << "--dbpass=#{mysql.password}" if mysql.password
|
85
|
+
|
86
|
+
res = ssh.run("cd #{core_path} && #{cmd.join(' ')}")
|
87
|
+
if res.success?
|
88
|
+
log "A new wordpress config has been generated"
|
89
|
+
else
|
90
|
+
error "Unable to generate config"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def check_config
|
95
|
+
if !ssh.file_exists?(shared_path('wp-config.php'))
|
96
|
+
log "Creating wordpress config at 'shared/wp-config.php'"
|
97
|
+
generate_config
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def site_installed?
|
102
|
+
ssh.run("cd #{release_path} && wp").success?
|
103
|
+
end
|
104
|
+
|
105
|
+
def site_install
|
106
|
+
if config.wordpress.site
|
107
|
+
site = config.wordpress.site
|
108
|
+
|
109
|
+
cmd = [
|
110
|
+
"wp core install",
|
111
|
+
"--url=#{site.url}",
|
112
|
+
"--title=#{site.title}",
|
113
|
+
"--admin_name=#{site.admin_name}",
|
114
|
+
"--admin_email=#{site.admin_email}",
|
115
|
+
"--admin_password=#{site.admin_password}"
|
116
|
+
].join(' ')
|
117
|
+
|
118
|
+
result = ssh.run("cd #{release_path} && #{cmd}")
|
119
|
+
if result.failure?
|
120
|
+
error "Failed to setup site. #{result.output}"
|
121
|
+
end
|
122
|
+
else
|
123
|
+
error "Please define :site section"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def network_install
|
128
|
+
if config.wordpress.network
|
129
|
+
network = config.wordpress.network
|
130
|
+
|
131
|
+
cmd = [
|
132
|
+
"wp core install-network",
|
133
|
+
"--title=#{network.title}",
|
134
|
+
].join(' ')
|
135
|
+
|
136
|
+
result = ssh.run("cd #{release_path} && #{cmd}")
|
137
|
+
if result.failure?
|
138
|
+
error "Failed to setup WP network. #{result.output}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def link_shared_data
|
144
|
+
log "Linking shared data"
|
145
|
+
|
146
|
+
ssh.run("cp -a #{core_path} #{release_path}")
|
147
|
+
ssh.run("cp #{shared_path('wp-config.php')} #{release_path('wp-config.php')}")
|
148
|
+
ssh.run("ln -s #{shared_path('wordpress/uploads')} #{release_path('wp-content/uploads')}")
|
149
|
+
end
|
150
|
+
|
151
|
+
def check_plugins
|
152
|
+
plugins = config.wordpress.plugins
|
153
|
+
|
154
|
+
if plugins
|
155
|
+
if plugins.kind_of?(Array)
|
156
|
+
plugins.each do |p|
|
157
|
+
if p.kind_of?(String)
|
158
|
+
plugin_install(p)
|
159
|
+
elsif p.kind_of?(Hash)
|
160
|
+
name, url = p.to_a.flatten.map(&:to_s)
|
161
|
+
plugin_custom_install(name, url)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
else
|
165
|
+
error "Config file has invalid plugins section"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def checkout_theme
|
171
|
+
if config.wordpress
|
172
|
+
if config.wordpress.theme
|
173
|
+
checkout_code("wp-content/themes/#{config.wordpress.theme}")
|
174
|
+
else
|
175
|
+
error "Theme name is not defined."
|
176
|
+
end
|
177
|
+
else
|
178
|
+
error "Config does not contain 'wordpress' section"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def activate_theme
|
183
|
+
name = config.wordpress.theme
|
184
|
+
result = ssh.run("cd #{release_path} && wp theme activate #{name}")
|
185
|
+
|
186
|
+
if result.failure?
|
187
|
+
error "Unable to activate theme. Error: #{result.output}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Shuttle
|
2
|
+
module Helpers
|
3
|
+
LEVEL_COLORS = {
|
4
|
+
'info' => :green,
|
5
|
+
'warning' => :yellow,
|
6
|
+
'error' => :red
|
7
|
+
}
|
8
|
+
|
9
|
+
def log(message, level='info')
|
10
|
+
prefix = "----->".send(LEVEL_COLORS[level])
|
11
|
+
STDOUT.puts("#{prefix} #{message}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def error(message)
|
15
|
+
log("ERROR: #{message}", 'error')
|
16
|
+
raise DeployError, message
|
17
|
+
end
|
18
|
+
|
19
|
+
def git_installed?
|
20
|
+
ssh.run("which git").success?
|
21
|
+
end
|
22
|
+
|
23
|
+
def svn_installed?
|
24
|
+
ssh.run("which svn").success?
|
25
|
+
end
|
26
|
+
|
27
|
+
def release_exists?
|
28
|
+
ssh.directory_exists?(release_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def stream_output(buff)
|
32
|
+
str = buff.split("\n").map { |str| " #{str}"}.join("\n")
|
33
|
+
STDOUT.puts(str)
|
34
|
+
end
|
35
|
+
|
36
|
+
def git_remote
|
37
|
+
result = ssh.run("cd #{scm_path} && git remote -v")
|
38
|
+
|
39
|
+
if result.success?
|
40
|
+
result.output.scan(/^origin\t(.+)\s\(fetch\)/).flatten.first
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def deployer_hostname
|
47
|
+
`hostname`.strip
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|