engineyard-serverside 1.3.7 → 1.4.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.
- data/bin/engineyard-serverside +1 -1
- data/lib/engineyard-serverside.rb +37 -33
- data/lib/engineyard-serverside/bundle_installer.rb +3 -1
- data/lib/engineyard-serverside/cli.rb +196 -194
- data/lib/engineyard-serverside/configuration.rb +109 -107
- data/lib/engineyard-serverside/deploy.rb +273 -271
- data/lib/engineyard-serverside/deploy_hook.rb +57 -55
- data/lib/engineyard-serverside/deprecation.rb +27 -0
- data/lib/engineyard-serverside/lockfile_parser.rb +80 -78
- data/lib/engineyard-serverside/logged_output.rb +56 -54
- data/lib/engineyard-serverside/server.rb +67 -64
- data/lib/engineyard-serverside/strategies/git.rb +110 -108
- data/lib/engineyard-serverside/task.rb +48 -45
- data/lib/engineyard-serverside/version.rb +3 -1
- data/spec/custom_deploy_spec.rb +5 -5
- data/spec/deploy_hook_spec.rb +3 -3
- data/spec/deprecation_spec.rb +25 -0
- data/spec/git_strategy_spec.rb +1 -1
- data/spec/lockfile_parser_spec.rb +4 -4
- data/spec/real_deploy_spec.rb +13 -7
- data/spec/restart_spec.rb +4 -4
- data/spec/server_spec.rb +24 -24
- data/spec/spec_helper.rb +15 -13
- metadata +8 -5
data/bin/engineyard-serverside
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
$LOAD_PATH.push(File.expand_path("engineyard-serverside", File.dirname(__FILE__)))
|
2
1
|
$LOAD_PATH.unshift File.expand_path('vendor/thor/lib', File.dirname(__FILE__))
|
3
2
|
$LOAD_PATH.unshift File.expand_path('vendor/open4/lib', File.dirname(__FILE__))
|
4
3
|
$LOAD_PATH.unshift File.expand_path('vendor/escape/lib', File.dirname(__FILE__))
|
@@ -9,44 +8,49 @@ require 'escape'
|
|
9
8
|
require 'json'
|
10
9
|
require 'dataflow'
|
11
10
|
|
12
|
-
require '
|
13
|
-
require '
|
14
|
-
require '
|
15
|
-
require '
|
16
|
-
require '
|
17
|
-
require '
|
18
|
-
require '
|
19
|
-
require '
|
20
|
-
require '
|
11
|
+
require 'engineyard-serverside/version'
|
12
|
+
require 'engineyard-serverside/strategies/git'
|
13
|
+
require 'engineyard-serverside/task'
|
14
|
+
require 'engineyard-serverside/server'
|
15
|
+
require 'engineyard-serverside/deploy'
|
16
|
+
require 'engineyard-serverside/deploy_hook'
|
17
|
+
require 'engineyard-serverside/lockfile_parser'
|
18
|
+
require 'engineyard-serverside/bundle_installer'
|
19
|
+
require 'engineyard-serverside/cli'
|
20
|
+
require 'engineyard-serverside/configuration'
|
21
|
+
require 'engineyard-serverside/deprecation'
|
21
22
|
|
22
23
|
module EY
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
module Serverside
|
25
|
+
|
26
|
+
def self.node
|
27
|
+
@node ||= deep_indifferentize(JSON.parse(dna_json))
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
def self.dna_json
|
31
|
+
@dna_json ||= if File.exist?('/etc/chef/dna.json')
|
32
|
+
`sudo cat /etc/chef/dna.json`
|
33
|
+
else
|
34
|
+
{}.to_json
|
35
|
+
end
|
36
|
+
end
|
34
37
|
|
35
|
-
|
38
|
+
RemoteFailure = Class.new StandardError
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
private
|
41
|
+
def self.deep_indifferentize(thing)
|
42
|
+
if thing.kind_of?(Hash)
|
43
|
+
indifferent_hash = Thor::CoreExt::HashWithIndifferentAccess.new
|
44
|
+
thing.each do |k, v|
|
45
|
+
indifferent_hash[k] = deep_indifferentize(v)
|
46
|
+
end
|
47
|
+
indifferent_hash
|
48
|
+
elsif thing.kind_of?(Array)
|
49
|
+
thing.map {|x| deep_indifferentize(x)}
|
50
|
+
else
|
51
|
+
thing
|
43
52
|
end
|
44
|
-
indifferent_hash
|
45
|
-
elsif thing.kind_of?(Array)
|
46
|
-
thing.map {|x| deep_indifferentize(x)}
|
47
|
-
else
|
48
|
-
thing
|
49
53
|
end
|
54
|
+
|
50
55
|
end
|
51
|
-
|
52
56
|
end
|
@@ -2,255 +2,257 @@ require 'thor'
|
|
2
2
|
require 'pathname'
|
3
3
|
|
4
4
|
module EY
|
5
|
-
|
6
|
-
|
5
|
+
module Serverside
|
6
|
+
class CLI < Thor
|
7
|
+
include Dataflow
|
8
|
+
|
9
|
+
def self.start(*)
|
10
|
+
super
|
11
|
+
rescue RemoteFailure
|
12
|
+
exit(1)
|
13
|
+
end
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
exit(1)
|
12
|
-
end
|
15
|
+
method_option :migrate, :type => :string,
|
16
|
+
:desc => "Run migrations with this deploy",
|
17
|
+
:aliases => ["-m"]
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
method_option :branch, :type => :string,
|
20
|
+
:desc => "Git ref to deploy, defaults to master. May be a branch, a tag, or a SHA",
|
21
|
+
:aliases => %w[-b --ref --tag]
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
23
|
+
method_option :repo, :type => :string,
|
24
|
+
:desc => "Remote repo to deploy",
|
25
|
+
:aliases => ["-r"]
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
method_option :app, :type => :string,
|
28
|
+
:required => true,
|
29
|
+
:desc => "Application to deploy",
|
30
|
+
:aliases => ["-a"]
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
:aliases => ["-a"]
|
32
|
+
method_option :framework_env, :type => :string,
|
33
|
+
:desc => "Ruby web framework environment",
|
34
|
+
:aliases => ["-e"]
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
:aliases => ["-e"]
|
36
|
+
method_option :config, :type => :string,
|
37
|
+
:desc => "Additional configuration"
|
34
38
|
|
35
|
-
|
36
|
-
|
39
|
+
method_option :stack, :type => :string,
|
40
|
+
:desc => "Web stack (so we can restart it correctly)"
|
37
41
|
|
38
|
-
|
39
|
-
|
42
|
+
method_option :instances, :type => :array,
|
43
|
+
:desc => "Hostnames of instances to deploy to, e.g. --instances localhost app1 app2"
|
40
44
|
|
41
|
-
|
42
|
-
|
45
|
+
method_option :instance_roles, :type => :hash,
|
46
|
+
:default => {},
|
47
|
+
:desc => "Roles of instances, keyed on hostname, comma-separated. e.g. instance1:app_master,etc instance2:db,memcached ..."
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
method_option :instance_names, :type => :hash,
|
50
|
+
:default => {},
|
51
|
+
:desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
|
47
52
|
|
48
|
-
|
49
|
-
|
50
|
-
|
53
|
+
method_option :verbose, :type => :boolean,
|
54
|
+
:default => false,
|
55
|
+
:desc => "Verbose output",
|
56
|
+
:aliases => ["-v"]
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
58
|
+
desc "deploy", "Deploy code from /data/<app>"
|
59
|
+
def deploy(default_task=:deploy)
|
60
|
+
config = EY::Serverside::Deploy::Configuration.new(options)
|
61
|
+
EY::Serverside::Server.load_all_from_array(assemble_instance_hashes(config))
|
56
62
|
|
57
|
-
|
58
|
-
|
59
|
-
config = EY::Deploy::Configuration.new(options)
|
60
|
-
EY::Server.load_all_from_array(assemble_instance_hashes(config))
|
63
|
+
EY::Serverside::LoggedOutput.verbose = options[:verbose]
|
64
|
+
EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-deploy.log")
|
61
65
|
|
62
|
-
|
63
|
-
EY::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-deploy.log")
|
66
|
+
invoke :propagate
|
64
67
|
|
65
|
-
|
68
|
+
EY::Serverside::Deploy.new(config).send(default_task)
|
69
|
+
end
|
66
70
|
|
67
|
-
|
68
|
-
|
71
|
+
method_option :app, :type => :string,
|
72
|
+
:required => true,
|
73
|
+
:desc => "Which application's hooks to run",
|
74
|
+
:aliases => ["-a"]
|
69
75
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
:aliases => ["-a"]
|
76
|
+
method_option :release_path, :type => :string,
|
77
|
+
:desc => "Value for #release_path in hooks (mostly for internal coordination)",
|
78
|
+
:aliases => ["-r"]
|
74
79
|
|
75
|
-
|
76
|
-
|
77
|
-
:aliases => ["-r"]
|
80
|
+
method_option :current_roles, :type => :array,
|
81
|
+
:desc => "Value for #current_roles in hooks"
|
78
82
|
|
79
|
-
|
80
|
-
|
83
|
+
method_option :framework_env, :type => :string,
|
84
|
+
:required => true,
|
85
|
+
:desc => "Ruby web framework environment",
|
86
|
+
:aliases => ["-e"]
|
81
87
|
|
82
|
-
|
83
|
-
|
84
|
-
:desc => "Ruby web framework environment",
|
85
|
-
:aliases => ["-e"]
|
88
|
+
method_option :config, :type => :string,
|
89
|
+
:desc => "Additional configuration"
|
86
90
|
|
87
|
-
|
88
|
-
|
91
|
+
method_option :current_name, :type => :string,
|
92
|
+
:desc => "Value for #current_name in hooks"
|
89
93
|
|
90
|
-
|
91
|
-
|
94
|
+
desc "hook [NAME]", "Run a particular deploy hook"
|
95
|
+
def hook(hook_name)
|
96
|
+
EY::Serverside::DeployHook.new(options).run(hook_name)
|
97
|
+
end
|
92
98
|
|
93
|
-
desc "hook [NAME]", "Run a particular deploy hook"
|
94
|
-
def hook(hook_name)
|
95
|
-
EY::DeployHook.new(options).run(hook_name)
|
96
|
-
end
|
97
99
|
|
100
|
+
method_option :app, :type => :string,
|
101
|
+
:required => true,
|
102
|
+
:desc => "Application to deploy",
|
103
|
+
:aliases => ["-a"]
|
98
104
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
105
|
+
method_option :framework_env, :type => :string,
|
106
|
+
:required => true,
|
107
|
+
:desc => "Ruby web framework environment",
|
108
|
+
:aliases => ["-e"]
|
103
109
|
|
104
|
-
|
105
|
-
|
106
|
-
:desc => "Ruby web framework environment",
|
107
|
-
:aliases => ["-e"]
|
110
|
+
method_option :stack, :type => :string,
|
111
|
+
:desc => "Web stack (so we can restart it correctly)"
|
108
112
|
|
109
|
-
|
110
|
-
|
113
|
+
method_option :instances, :type => :array,
|
114
|
+
:desc => "Hostnames of instances to deploy to, e.g. --instances localhost app1 app2"
|
111
115
|
|
112
|
-
|
113
|
-
|
116
|
+
method_option :instance_roles, :type => :hash,
|
117
|
+
:default => {},
|
118
|
+
:desc => "Roles of instances, keyed on hostname, comma-separated. e.g. instance1:app_master,etc instance2:db,memcached ..."
|
114
119
|
|
115
|
-
|
116
|
-
|
117
|
-
|
120
|
+
method_option :instance_names, :type => :hash,
|
121
|
+
:default => {},
|
122
|
+
:desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
|
118
123
|
|
119
|
-
|
120
|
-
|
121
|
-
|
124
|
+
method_option :verbose, :type => :boolean,
|
125
|
+
:default => false,
|
126
|
+
:desc => "Verbose output",
|
127
|
+
:aliases => ["-v"]
|
128
|
+
desc "integrate", "Integrate other instances into this cluster"
|
129
|
+
def integrate
|
130
|
+
EY::Serverside::LoggedOutput.verbose = options[:verbose]
|
131
|
+
EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-integrate.log")
|
122
132
|
|
123
|
-
|
124
|
-
|
125
|
-
:desc => "Verbose output",
|
126
|
-
:aliases => ["-v"]
|
127
|
-
desc "integrate", "Integrate other instances into this cluster"
|
128
|
-
def integrate
|
129
|
-
EY::LoggedOutput.verbose = options[:verbose]
|
130
|
-
EY::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-integrate.log")
|
133
|
+
app_dir = Pathname.new "/data/#{options[:app]}"
|
134
|
+
current_app_dir = app_dir + "current"
|
131
135
|
|
132
|
-
|
133
|
-
|
136
|
+
# so that we deploy to the same place there that we have here
|
137
|
+
integrate_options = options.dup
|
138
|
+
integrate_options[:release_path] = current_app_dir.realpath.to_s
|
134
139
|
|
135
|
-
|
136
|
-
|
137
|
-
integrate_options[:release_path] = current_app_dir.realpath.to_s
|
140
|
+
# we have to deploy the same SHA there as here
|
141
|
+
integrate_options[:branch] = (current_app_dir + 'REVISION').read.strip
|
138
142
|
|
139
|
-
|
140
|
-
integrate_options[:branch] = (current_app_dir + 'REVISION').read.strip
|
143
|
+
config = EY::Serverside::Deploy::Configuration.new(integrate_options)
|
141
144
|
|
142
|
-
|
145
|
+
EY::Serverside::Server.load_all_from_array(assemble_instance_hashes(config))
|
143
146
|
|
144
|
-
|
147
|
+
invoke :propagate
|
145
148
|
|
146
|
-
|
149
|
+
EY::Serverside::Server.all.each do |server|
|
150
|
+
server.sync_directory app_dir
|
151
|
+
# we're just about to recreate this, so it has to be gone
|
152
|
+
# first. otherwise, non-idempotent deploy hooks could screw
|
153
|
+
# things up, and since we don't control deploy hooks, we must
|
154
|
+
# assume the worst.
|
155
|
+
server.run("rm -rf #{current_app_dir}")
|
156
|
+
end
|
147
157
|
|
148
|
-
|
149
|
-
|
150
|
-
# we're just about to recreate this, so it has to be gone
|
151
|
-
# first. otherwise, non-idempotent deploy hooks could screw
|
152
|
-
# things up, and since we don't control deploy hooks, we must
|
153
|
-
# assume the worst.
|
154
|
-
server.run("rm -rf #{current_app_dir}")
|
158
|
+
# deploy local-ref to other instances into /data/$app/local-current
|
159
|
+
EY::Serverside::Deploy.new(config).cached_deploy
|
155
160
|
end
|
156
161
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
method_option :app, :type => :string,
|
162
|
-
:required => true,
|
163
|
-
:desc => "Application to deploy",
|
164
|
-
:aliases => ["-a"]
|
162
|
+
method_option :app, :type => :string,
|
163
|
+
:required => true,
|
164
|
+
:desc => "Application to deploy",
|
165
|
+
:aliases => ["-a"]
|
165
166
|
|
166
|
-
|
167
|
-
|
167
|
+
method_option :stack, :type => :string,
|
168
|
+
:desc => "Web stack (so we can restart it correctly)"
|
168
169
|
|
169
|
-
|
170
|
-
|
170
|
+
method_option :instances, :type => :array,
|
171
|
+
:desc => "Hostnames of instances to deploy to, e.g. --instances localhost app1 app2"
|
171
172
|
|
172
|
-
|
173
|
-
|
174
|
-
|
173
|
+
method_option :instance_roles, :type => :hash,
|
174
|
+
:default => {},
|
175
|
+
:desc => "Roles of instances, keyed on hostname, comma-separated. e.g. instance1:app_master,etc instance2:db,memcached ..."
|
175
176
|
|
176
|
-
|
177
|
-
|
178
|
-
|
177
|
+
method_option :instance_names, :type => :hash,
|
178
|
+
:default => {},
|
179
|
+
:desc => "Instance names, keyed on hostname. e.g. instance1:name1 instance2:name2"
|
179
180
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
181
|
+
method_option :verbose, :type => :boolean,
|
182
|
+
:default => false,
|
183
|
+
:desc => "Verbose output",
|
184
|
+
:aliases => ["-v"]
|
185
|
+
desc "restart", "Restart app servers, conditionally enabling maintenance page"
|
186
|
+
def restart
|
187
|
+
EY::Serverside::LoggedOutput.verbose = options[:verbose]
|
188
|
+
EY::Serverside::LoggedOutput.logfile = File.join(ENV['HOME'], "#{options[:app]}-restart.log")
|
188
189
|
|
189
|
-
|
190
|
-
|
190
|
+
config = EY::Serverside::Deploy::Configuration.new(options)
|
191
|
+
EY::Serverside::Server.load_all_from_array(assemble_instance_hashes(config))
|
191
192
|
|
192
|
-
|
193
|
+
invoke :propagate
|
193
194
|
|
194
|
-
|
195
|
-
|
195
|
+
EY::Serverside::Deploy.new(config).restart_with_maintenance_page
|
196
|
+
end
|
196
197
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
198
|
+
desc "install_bundler [VERSION]", "Make sure VERSION of bundler is installed (in system ruby)"
|
199
|
+
def install_bundler(version)
|
200
|
+
egrep_escaped_version = version.gsub(/\./, '\.')
|
201
|
+
# the grep "bundler " is so that gems like bundler08 don't get
|
202
|
+
# their versions considered too
|
203
|
+
#
|
204
|
+
# the [,$] is to stop us from looking for e.g. 0.9.2, seeing
|
205
|
+
# 0.9.22, and mistakenly thinking 0.9.2 is there
|
206
|
+
has_bundler_cmd = "gem list bundler | grep \"bundler \" | egrep -q '#{egrep_escaped_version}[,)]'"
|
207
|
+
|
208
|
+
unless system(has_bundler_cmd)
|
209
|
+
system("gem install bundler -q --no-rdoc --no-ri -v '#{version}'")
|
210
|
+
end
|
209
211
|
end
|
210
|
-
end
|
211
212
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
213
|
+
desc "propagate", "Propagate the engineyard-serverside gem to the other instances in the cluster. This will install exactly version #{EY::Serverside::VERSION}."
|
214
|
+
def propagate
|
215
|
+
config = EY::Serverside::Deploy::Configuration.new
|
216
|
+
gem_filename = "engineyard-serverside-#{EY::Serverside::VERSION}.gem"
|
217
|
+
local_gem_file = File.join(Gem.dir, 'cache', gem_filename)
|
218
|
+
remote_gem_file = File.join(Dir.tmpdir, gem_filename)
|
219
|
+
gem_binary = File.join(Gem.default_bindir, 'gem')
|
220
|
+
|
221
|
+
barrier(*(EY::Serverside::Server.all.find_all do |server|
|
222
|
+
!server.local? # of course this machine has it
|
223
|
+
end.map do |server|
|
224
|
+
need_later do
|
225
|
+
egrep_escaped_version = EY::Serverside::VERSION.gsub(/\./, '\.')
|
226
|
+
# the [,)] is to stop us from looking for e.g. 0.5.1, seeing
|
227
|
+
# 0.5.11, and mistakenly thinking 0.5.1 is there
|
228
|
+
has_gem_cmd = "#{gem_binary} list engineyard-serverside | grep \"engineyard-serverside\" | egrep -q '#{egrep_escaped_version}[,)]'"
|
229
|
+
|
230
|
+
if !server.run(has_gem_cmd) # doesn't have this exact version
|
231
|
+
puts "~> Installing engineyard-serverside on #{server.hostname}"
|
232
|
+
|
233
|
+
system(Escape.shell_command([
|
234
|
+
'scp', '-i', "#{ENV['HOME']}/.ssh/internal",
|
235
|
+
"-o", "StrictHostKeyChecking=no",
|
236
|
+
local_gem_file,
|
237
|
+
"#{config.user}@#{server.hostname}:#{remote_gem_file}",
|
238
|
+
]))
|
239
|
+
server.run("sudo #{gem_binary} install --no-rdoc --no-ri '#{remote_gem_file}'")
|
240
|
+
end
|
239
241
|
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
242
|
+
end))
|
243
|
+
end
|
243
244
|
|
244
|
-
|
245
|
+
private
|
245
246
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
247
|
+
def assemble_instance_hashes(config)
|
248
|
+
options[:instances].collect { |hostname|
|
249
|
+
{ :hostname => hostname,
|
250
|
+
:roles => options[:instance_roles][hostname].to_s.split(','),
|
251
|
+
:name => options[:instance_names][hostname],
|
252
|
+
:user => config.user,
|
253
|
+
}
|
252
254
|
}
|
253
|
-
|
255
|
+
end
|
254
256
|
end
|
255
257
|
end
|
256
258
|
end
|