-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmarch.rb
194 lines (156 loc) · 6.4 KB
/
march.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
require 'yaml'
require 'set'
require 'net/scp'
require 'net/ssh'
current_stage_name = ARGV[0]
if current_stage_name.nil?
raise 'No stage specified: please run `march STAGE COMMAND`, where stage is something like production/staging'
end
VALID_COMMANDS = %w[deploy logs]
command = ARGV[1]
if command.nil?
raise "No command specified: please run `march STAGE #{VALID_COMMANDS.join('|')}"
end
YAML_PATH='march/config.yml'
raise 'No march/config.yml found' unless File.exists? YAML_PATH
march_config = YAML::load_file(YAML_PATH)
deploy_path = march_config['deploy_path']
raise 'Please specify deploy_path in march/config.yml' if deploy_path.nil?
go_binary_name = march_config['go_binary_name']
raise 'Please specify go_binary_name in march/config.yml' if go_binary_name.nil?
env = march_config['env']
env = {} if env.nil?
# time for stages yeaaaah
server_defaults = march_config['server_defaults']
# p server_defaults
stages = march_config['stages']
raise 'No stages specified in march/config.yml' if stages.nil? || stages.empty?
REQUIRED_SERVER_CONFIG_KEYS = Set.new(%w[host go_os user port])
current_stage_servers = nil
stages.each do |stage_name, stage_config|
raise "No servers on stage #{stage_name}" if stage_config['servers'].nil?
servers = stage_config['servers']
if stage_name == current_stage_name
current_stage_servers = servers
end
servers.each.with_index do |server_config, index|
server_config = server_defaults.merge(server_config) # have stage_config overwrite defaults
specified_keys = server_config.keys
# puts "#{stage_name}.servers[#{index}] => #{server_config}"
missing_keys = REQUIRED_SERVER_CONFIG_KEYS - specified_keys
unless missing_keys.empty?
raise "Server \##{index} #{stage_name} was missing #{missing_keys.to_a.join(', ')}"
end
servers[index] = server_config
end
end
puts current_stage_servers
case command.to_sym
when :deploy
puts 'deploying...'
puts 'building...'
# Build the binary
required_oses = current_stage_servers.map { |server_config| server_config['go_os'] }.uniq
Dir.mkdir 'march/build' unless Dir.exist? 'march/build'
required_oses.each do |os|
system("/bin/bash -c \"GOOS=#{os} go build -o march/build/#{os}\"")
end
remote_assets_path = "#{deploy_path}/assets"
puts 'writing launch script...'
# Write the launch script
env_string = env.to_a.map { |pair| pair.join('=') }.join(' ')
script = <<-eos
#!/bin/bash
while true; do
echo "Starting #{go_binary_name} process..." >> #{deploy_path}/#{go_binary_name}.log
MARCH_ASSETS_PATH=#{remote_assets_path} #{env_string} #{deploy_path}/#{go_binary_name} 2>&1 >> #{deploy_path}/#{go_binary_name}.log
done
eos
local_launch_script_path = "march/build/#{go_binary_name}.sh"
File.open(local_launch_script_path, 'w') { |f| f.write(script) }
puts 'uploading...'
current_stage_servers.each do |server|
Net::SSH.start(server['host'], server['user'], port: server['port']) do |ssh|
ssh.exec! "mkdir -p #{deploy_path}"
def signal_first_match_for(matcher, ssh, signal)
output = find_matches_for(matcher, ssh)
pid_matches = output.match(/[1-9]\d+/)
# puts stdout
# puts pid_matches
unless pid_matches.nil?
puts "killing existing instance of #{matcher}..."
pid = pid_matches.to_a.first
ssh.exec!("kill #{signal} #{pid}")
end
end
def find_matches_for(matcher, ssh)
ssh.exec!("ps aux | grep \"#{matcher}\" | grep -v \"grep #{matcher}\"")
end
def signal_all_processes_matching(matcher, ssh, signal)
signal_first_match_for(matcher, ssh, signal) until find_matches_for(matcher, ssh).empty?
end
def sigkill_all_processes_matching(matcher, ssh)
signal_all_processes_matching matcher, ssh, '-9'
end
def sigint_all_processes_matching(matcher, ssh)
signal_all_processes_matching matcher, ssh, '-2'
end
sigkill_all_processes_matching "#{deploy_path}/#{go_binary_name}.sh$", ssh # kill the parent script to prevent the child restarting when we interrupt it
sigint_all_processes_matching "#{deploy_path}/#{go_binary_name}$", ssh # interrupt the child script
sigkill_all_processes_matching "#{deploy_path}/#{go_binary_name}.log$", ssh # kill all log processes started by march
ssh.exec! "rm -rf #{remote_assets_path}" # need to do this otherwise cp -r gets confused and tries to copy ./assets => deploy_path/assets/assets
end
puts 'uploading binary...'
local_binary_path = "march/build/#{server['go_os']}"
remote_binary_path = "#{deploy_path}/#{go_binary_name}"
puts "#{local_binary_path} => #{remote_binary_path}"
Net::SCP.upload!(server['host'], server['user'],
local_binary_path, remote_binary_path,
ssh: { port: server['port'] })
puts 'uploading launch script...'
Net::SCP.upload!(server['host'], server['user'],
local_launch_script_path,
"#{deploy_path}/#{go_binary_name}.sh", # destination
ssh: { port: server['port'] })
if Dir.exists? 'assets'
puts 'copying assets...'
Net::SCP.upload!(server['host'], server['user'],
'assets', remote_assets_path,
ssh: { port: server['port'] }, recursive: true)
end
puts 'starting ssh session...'
Net::SSH.start(server['host'], server['user'], port: server['port']) do |ssh|
puts 'launching binary...'
launch_command = "/usr/bin/nohup #{deploy_path}/#{go_binary_name}.sh > nohup.out &"
puts launch_command
ssh.exec!(launch_command) do |_, stream, data|
stdout << data if stream == :stdout
stderr << data if stream == :stderr
end
end
end
puts 'successfully deployed, check logs for more details'
when :logs
server = current_stage_servers.first
begin
session = nil
channel = nil
trap 'INT' do
session.shutdown!
exit!
end
Net::SSH.start(server['host'], server['user'], port: server['port']) do |ssh|
session = ssh
channel = ssh.exec("tail -f -n 50 #{deploy_path}/#{go_binary_name}.log") do |ch, stream, data|
puts data if stream == :stdout
end
channel.wait
end
rescue SystemExit, Interrupt
puts 'Received sigint. Aborting.'
session.shutdown!
exit!
end
else
raise 'Invalid command; this should never execute as the validation should have stopped this above'
end