-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathmorph_deploy.rb
executable file
·290 lines (235 loc) · 10.4 KB
/
morph_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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
require 'fileutils'
require 'cgi'
require 'net/https'
require 'yaml'
require 'highline/import'
load 'deploy'
# The svn repository is used to export the code into the temporary directory before
# uploading code into the Morph control panel. Currently only svn is supported,
# but you could change it to fit your need by changing the get_code task
set :repository, 'git://github.com/neves/escolar.git' # Set here your repository! Example: 'https://www.myrepo.com/myapp/trunk'
set :repo_line_number, __LINE__ - 1 # Needed to report missing repository later on
# The version name to set in the control panel. Defaults to date and time, but can be altered by passing it
# on the command line as -s version_name='The Version Name'
set :version_name, Time.now.utc.strftime('%Y-%m-%d %H:%M:%S')
# If you want to use a different scm or use a different export method, you can change it here
# Please note that the export to directory is removed before the checkout starts. If
# You want it to work differently, change the code in the get_code task
set :deploy_via, :checkout
set :scm, :git
# MORPH SETTINGS, please do not change
set :morph_host, "panel.mor.ph"
set :morph_port, 443
set :morph_tmp_dir, 'morph_tmp'
set :mex_key, "7ca0a495a98dc8d7f78dfbf5f952c11e2b92ff8f"
set :mv_cmd, PLATFORM.include?('mswin') ? 'ren' : 'mv'
set :morph_tmp_dir, 'morph_tmp'
set :release_path, morph_tmp_dir # needed in order to generate the correct scm command
set :get_code_using, :get_code
set :req_retries, 3
namespace :morph do
abort('*** ERROR: You need a MeX key!') if !exists?(:mex_key) || mex_key.nil?
# This is the entry point task. It gets the new code, then upload it into S3
# to a special folder used later for deployment. Finally it mark the version
# Uploaded to be deployed
task :deploy do
transaction do
set :mex_key, mex_app_key if exists?(:mex_app_key)
abort('*** ERROR: You need a MeX key!') if !exists?(:mex_key) || mex_key.nil?
update_code
send_request(true, 'Post', morph_host, morph_port, '/api/deploy/deploy', {}, nil, "*** ERROR: Could not deploy the application!")
say("Deploy Done.")
end
end
# Specialized command to deploy from a packaged gem
task :deploy_from_gem do
set :get_code_using, :get_code_from_gem
deploy
end
# This task calls the get_code helper, then upload the code into S3
task :update_code do
transaction do
s3_obj_name = upload_code_to_s3
say("Creating new appspace version...")
#create a version in CP
req_flds = { 'morph-version-name' => version_name, 'morph-version-s3-object' => s3_obj_name }
send_request(true, 'Post', morph_host, morph_port, '/versions/create2', req_flds, nil, "*** ERROR: Could not create a new version!")
say("Code Upload Done.")
end
end
# A task that create a temp dir, export the code ouf ot svn and tar
# the code preparing it for upload.
# If you are not using svn, or using a different source control tool
# you can customize this file to work with it. The requirement is that
# It will export the whole structure into the temp directory as set in
# morph_tmp_dir.
#
# You can choose to release a different version than head by setting the
# Environment variable 'REL_VER' to the version to use.
task :get_code do
on_rollback do
remove_files([morph_tmp_dir, 'code_update.tar.gz'])
end
# Make sure we have a repo to work from!
abort("***ERROR: Must specify the repository to check out from! See line #{repo_line_number} in #{__FILE__}.") if !repository
transaction do
# Clean up previous deploys
remove_files([morph_tmp_dir, 'code_update.tar.gz'])
#get latest code from from the repository
say("Downloading the code from the repository...")
system(strategy.send(:command))
abort('*** ERROR: Export from repository failed! Please check the repository setting at the start of the file') if $?.to_i != 0
# Verify that we have the expected rails structure
['/app', '/public', '/config/environment.rb', '/lib'].each do |e|
abort "*** ERROR: Rails directories are missing. Please make sure your set :repository is correct!" if !File.exist?("#{morph_tmp_dir}#{e}")
end
#create archive
system("tar -C #{morph_tmp_dir} -czf code_update.tar.gz --exclude='./.*' .")
abort('*** ERROR: Failed to tar the file for upload.') if $?.to_i != 0
# Verify that we have the expected rails structure in the archive
flist = `tar tzf code_update.tar.gz`
all_in = flist.include?('lib/') && flist.include?('app/') && flist.include?('config/environment.rb')
abort "***ERROR: code archive is missing the rails directories. Please check your checkout and tar" if !all_in
remove_files([morph_tmp_dir])
end
end
# Get the code from a packaged gem. Name comes from a setting passed to the command
# using the -s form
task :get_code_from_gem do
# Make sure we have the gem defined and that we have the gem file
if !exists?(:gem_file) || gem_file.nil?
abort("***ERROR: The gem file must be provided on the command line using -s.\n For example: cap -f morph_deploy.rb -s gem_file=/home/morph/my_app.gem morph:deploy_from_gem")
end
abort("***ERROR: gem file not found! Please check the file location and try again") if !File.exists?(gem_file)
# Remove older file
remove_files(["code_update.tar.gz"])
# Extract the data.tar.gz code from the gem
system("tar xf #{gem_file} data.tar.gz")
abort("***ERROR: Couldn't find the data.tar.gz file in the gem file provided!") if !File.exists?('data.tar.gz')
# rename it to upload_code.tar.gz
system("#{mv_cmd} data.tar.gz code_update.tar.gz")
end
# A task to get the S3 connection info and upload code_update.tar.gz
# Assumes another task prepared the tar.gz file
task :upload_code_to_s3 do
self.send get_code_using
abort "*** ERROR: Could not find archive to upload." unless File.exist?('code_update.tar.gz')
s3_data = ''
say('Getting upload information')
send_request(true, 'Get', morph_host, morph_port, '/api/s3/connection_data', {}, nil, "*** ERROR: Could not get the S3 connection data!") do |req, res|
s3_data = YAML.load(res.body)
end
if !s3_data.empty?
say('Uploading code to S3...')
File.open('code_update.tar.gz', 'rb') do |up_file|
send_request(false, 'Put', s3_data[:host], morph_port, s3_data[:path], s3_data[:header], up_file, "*** ERROR: Could not upload the code!")
end
end
s3_data[:obj_name]
end
# Helper to add the mex auth fields to the requests
def add_mex_fields(req)
if exists?(:deploy_key) and mex_key
req['morph-deploy-key'] = deploy_key
else
req['morph-user'] = morph_user
req['morph-pass'] = morph_pass
end
req['morph-app'] = mex_key
end
# Helper to generate REST requests, handle authentication and errors
def send_request(is_mex, req_type, host, port, url, fields_hash, up_file, error_msg)
tries_done = 0
res = ''
while res != :ok
if is_mex and (!exists?(:deploy_key) or !exists?(:mex_key))
say("*** Getting info for Morph authentication ***") if !exists?(:morph_user) || morph_user.nil? || !exists?(:morph_pass) || morph_pass.nil?
set(:morph_user, ask("Morph user: ")) if !exists?(:morph_user) || morph_user.nil?
set(:morph_pass, ask("Password: ") { |q| q.echo = false }) if !exists?(:morph_pass) || morph_pass.nil?
end
conn_start(host, port) do |http|
request = Module.module_eval("Net::HTTP::#{req_type}").new(url)
add_mex_fields(request) if is_mex
fields_hash.each_pair{|n,v| request[n] = v} # Add user defined header fields
# If a file is passed we upload it.
if up_file
request.content_length = up_file.lstat.size
request.body_stream = up_file # For uploads using streaming
else
request.content_length = 0
end
begin
response = http.request(request)
rescue Exception => ex
response = ex.to_s
end
# Handle auth errors and other errors!
res = verify_response(request, response, is_mex)
if res == :ok
yield(request, response) if block_given?
elsif exists?(:deploy_key) #if via deploy key, just exit
say("Incorrect deploy_key or mex_app_key")
exit
elsif res != :auth_fail # On auth_fail we just loop around
tries_done += 1
abort(error_msg) if tries_done > req_retries
end
end
end
end
# Helper to create a connection with all the needed setting
# And yeals to the block
def conn_start host = morph_host, port = morph_port
con = Net::HTTP.new(host, port)
con.use_ssl = true
# If you have cert files to use, change this to OpenSSL::SSL::VERIFY_PEER
# And set the ca_cert of the connection
con.verify_mode = OpenSSL::SSL::VERIFY_NONE
con.start
if block_given?
begin
return yield(con)
ensure
con.finish
end
end
con
end
# Helper to verify if a response is valid. Also handles authentication failures into MeX
def verify_response(request, response, is_mex)
res = :ok
if !response.is_a?(Net::HTTPOK)
if response.is_a?(Net::HTTPForbidden) && is_mex
say("Authentication failed! Please try again.")
set :morph_user, nil
set :morph_pass, nil
res = :auth_fail
else
if response.is_a?(String)
say("*** ERROR: connection failure: #{response}")
else
output_request_param(request, response)
end
res = :failure
end
end
return res
end
# Helper to output the results of REST calls to our servers or S3
def output_request_param(request, response)
puts "\n========== ERROR TRACE =========="
puts "+++++++ REQUEST HEADERS +++++++++"
request.each {|e,r| puts "#{e}: #{r}"}
puts "+++++++ RESPONSE HEADERS +++++++++"
response.each {|e,r| puts "#{e}: #{r}"}
puts "+++++++ RESPONSE BODY +++++++++"
puts response.body
puts "+++++++ RESPONSE TYPE +++++++++"
puts "#{response.class} (#{response.code})"
puts "========= END OF TRACE ==========\n"
end
def remove_files(lists)
FileUtils.cd(FileUtils.pwd)
FileUtils.rm_r(lists, :force => true) rescue ''
end
end