-
Notifications
You must be signed in to change notification settings - Fork 28
/
deploy.rb
239 lines (222 loc) · 8.99 KB
/
deploy.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
239
dep 'ready for update.repo', :git_ref_data, :env do
deprecated! "2013-12-12", :method_name => "'benhoskings:ready for update.repo'", :callpoint => false, :instead => "'common:ready for update.repo'"
env.default!(ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'production')
requires [
'valid git_ref_data.repo'.with(git_ref_data),
'clean.repo',
'before deploy'.with(ref_info[:old_id], ref_info[:new_id], ref_info[:branch], env)
]
end
dep 'up to date.repo', :git_ref_data, :env do
deprecated! "2013-12-12", :method_name => "'benhoskings:up to date.repo'", :callpoint => false, :instead => "'common:up to date.repo'"
env.default!(ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'production')
requires [
'on correct branch.repo'.with(ref_info[:branch]),
'HEAD up to date.repo'.with(ref_info),
'app bundled'.with(:root => '.', :env => env),
# This and 'after deploy' below are separated so the deps in 'current dir'
# they refer to load from the new code checked out by 'HEAD up to date.repo'.
# Normally it would be fine because dep loading is lazy, but the "if Dep('...')"
# checks trigger a source load when called.
'on deploy'.with(ref_info[:old_id], ref_info[:new_id], ref_info[:branch], env),
'unicorn restarted',
'maintenance page down',
'after deploy'.with(ref_info[:old_id], ref_info[:new_id], ref_info[:branch], env)
]
end
dep 'before deploy', :old_id, :new_id, :branch, :env do
deprecated! "2013-12-12", :method_name => "'benhoskings:before deploy'", :callpoint => false, :instead => "'common:before deploy'"
requires 'current dir:before deploy'.with(old_id, new_id, branch, env) if Dep('current dir:before deploy')
end
dep 'on deploy', :old_id, :new_id, :branch, :env do
deprecated! "2013-12-12", :method_name => "'benhoskings:on deploy'", :callpoint => false, :instead => "'common:on deploy'"
requires 'current dir:on deploy'.with(old_id, new_id, branch, env) if Dep('current dir:on deploy')
end
dep 'after deploy', :old_id, :new_id, :branch, :env do
deprecated! "2013-12-12", :method_name => "'benhoskings:after deploy'", :callpoint => false, :instead => "'common:after deploy'"
requires 'current dir:after deploy'.with(old_id, new_id, branch, env) if Dep('current dir:after deploy')
end
dep 'valid git_ref_data.repo', :git_ref_data do
deprecated! "2013-12-12", :method_name => "'benhoskings:valid git_ref_data.repo'", :callpoint => false, :instead => "'common:valid git_ref_data.repo'"
met? {
git_ref_data[ref_data_regexp] || unmeetable!("Invalid git_ref_data '#{git_ref_data}'.")
}
end
dep 'clean.repo' do
deprecated! "2013-12-12", :method_name => "'benhoskings:clean.repo'", :callpoint => false, :instead => "'common:clean.repo'"
setup {
# Clear git's internal cache, which sometimes says the repo is dirty when it isn't.
repo.repo_shell "git diff"
}
met? { repo.clean? || unmeetable!("The remote repo has local changes.") }
end
dep 'branch exists.repo', :branch do
deprecated! "2013-12-12", :method_name => "'benhoskings:branch exists.repo'", :callpoint => false, :instead => "'common:branch exists.repo'"
met? {
repo.branches.include? branch
}
meet {
log_block "Creating #{branch}" do
repo.branch! branch
end
}
end
dep 'on correct branch.repo', :branch do
deprecated! "2013-12-12", :method_name => "'benhoskings:on correct branch.repo'", :callpoint => false, :instead => "'common:on correct branch.repo'"
requires 'branch exists.repo'.with(branch)
met? {
repo.current_branch == branch
}
meet {
log_block "Checking out #{branch}" do
repo.checkout! branch
end
}
end
dep 'HEAD up to date.repo', :old_id, :new_id, :branch do
deprecated! "2013-12-12", :method_name => "'benhoskings:HEAD up to date.repo'", :callpoint => false, :instead => "'common:HEAD up to date.repo'"
met? {
(repo.current_full_head == new_id && repo.clean?).tap {|result|
if result
log_ok "#{branch} is up to date at #{repo.current_head}."
else
log "#{branch} needs updating: #{old_id[0...7]}..#{new_id[0...7]}"
end
}
}
meet {
if old_id[/^0+$/]
log "Starting HEAD at #{new_id[0...7]} (a #{shell("git rev-list #{new_id} | wc -l").strip}-commit history) since the repo is blank."
else
log shell("git diff --stat #{old_id}..#{new_id}")
end
repo.reset_hard!(new_id)
}
end
dep 'unicorn restarted', :pidfile, :old_pidfile do
deprecated! "2013-12-12", :method_name => "'benhoskings:unicorn restarted'", :callpoint => false, :instead => "'common:unicorn restarted'"
pidfile.default!('tmp/pids/unicorn.pid')
old_pidfile.default!("#{pidfile}.oldbin")
def running? pid
if pid.nil?
false
else
Process.getpgid(pid.to_i)
end
rescue Errno::ESRCH
false
end
def wait_for timeout, message, &block
waited_for, result = 0, nil
log_block message do
while !(result = yield) && (waited_for < timeout)
waited_for += 0.2
sleep 0.2
end
result
end
end
setup {
@original_pid = pidfile.p.read.try(:chomp)
}
def restarted?(current_pid)
# The app has restarted if:
!old_pidfile.p.exists? && # A restart isn't in progress,
running?(current_pid) && # The current pid is running,
(current_pid != @original_pid) # And the value has changed.
end
met? {
current_pid = pidfile.p.read.try(:chomp)
if !running?(current_pid)
if @attempted_restart
log_error "Unicorn exited! (Possible cause: no free RAM on host.)"
else
log "There are no unicorns running: not attempting a restart."
true
end
else
restarted?(current_pid).tap {|result|
if !@attempted_restart
log "The unicorn master is running with pid #{current_pid}."
elsif !result
log_warn "The new unicorn failed to start. (The old one is still running, though.)"
else
log_ok "Restarted OK (pid #{@original_pid} -> #{pidfile.p.read.try(:chomp)})."
end
}
end
}
meet {
shell "kill -USR2 #{'tmp/pids/unicorn.pid'.p.read.try(:chomp)}"
@attempted_restart = true
# 1) The current pidfile is moved to old_pidfile.
# 2) The new master starts and writes pidfile.
# 3) On launch success, old_pidfile is deleted; on failure,
# old_pidfile is moved back into place.
wait_for(10, "Current unicorn moving aside") { pidfile.p.exists? && old_pidfile.p.exists? }
wait_for(10, "New unicorn forking") { pidfile.p.exists? }
wait_for(120, "New unicorn booting") { !old_pidfile.p.exists? }
}
end
dep 'maintenance page up' do
deprecated! "2013-12-12", :method_name => "'benhoskings:maintenance page up'", :callpoint => false, :instead => "'common:maintenance page up'"
met? {
!'public/system/maintenance.html.off'.p.exists? or
'public/system/maintenance.html'.p.exists?
}
meet { 'public/system/maintenance.html.off'.p.copy 'public/system/maintenance.html' }
end
dep 'maintenance page down' do
deprecated! "2013-12-12", :method_name => "'benhoskings:maintenance page down'", :callpoint => false, :instead => "'common:maintenance page down'"
met? { !'public/system/maintenance.html'.p.exists? }
meet { 'public/system/maintenance.html'.p.rm }
end
dep 'when path changed', :path, :dep_spec, :old_id, :new_id, :env do
deprecated! "2013-12-12", :method_name => "'benhoskings:when path changed'", :callpoint => false, :instead => "'common:when path changed'"
def effective_old_id
# If there is no initial commit (first push or branch change), git
# replace git's '0000000' with a parentless commit (usually there's
# just one, the initial repo commit).
old_id[/^0+$/] ? shell('git rev-list HEAD | tail -n1') : old_id
end
def pending
shell(
"git diff --numstat #{effective_old_id}..#{new_id}"
).split("\n").grep(
/^[\d\s\-]+#{path}/
)
end
setup {
if pending.empty?
log "No changes within #{path.inspect} - not running '#{dep_spec}'."
else
log "#{pending.length} change#{'s' unless pending.length == 1} within #{path.inspect}:"
if pending.length < 30
pending.each {|p| log p }
else
pending[0...15].each {|p| log p }
log "[and #{pending.length - 15} more]"
end
requires dep_spec.to_s.with(:env => env, :deploying => 'yes')
end
}
end
dep 'assets precompiled', :env, :deploying, :template => 'task' do
deprecated! "2013-12-12", :method_name => "'benhoskings:assets precompiled'", :callpoint => false, :instead => "'common:assets precompiled'"
run {
shell "bundle exec rake assets:precompile:primary RAILS_GROUPS=assets RAILS_ENV=#{env}"
}
end
dep 'delayed job restarted', :template => 'task' do
deprecated! "2013-12-12", :method_name => "'benhoskings:delayed job restarted'", :callpoint => false, :instead => "'common:delayed job restarted'"
run {
output = shell?('ps ux | grep -v grep | grep "rake jobs:work"')
if output.nil?
log "`rake jobs:work` isn't running."
true
else
pid = output.scan(/^\w+\s+(\d+)\s+/).flatten.first
log_shell "Sending SIGTERM to #{pid}", "kill -s TERM #{pid}"
end
}
end