-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserve.rb
144 lines (129 loc) · 4.69 KB
/
serve.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
require 'bundler'
Bundler.require
require 'securerandom'
require 'sinatra/custom_logger'
require 'logger'
require 'benchmark'
require 'open3'
# Wait for new command request to be executed
post '/' do
logger.debug 'Request for command execution'
return 403 unless can_process_cmd?
send_mail_validation
end
# Validate token for command execution
get '/:token' do
if verify_token(params[:token])
# Unlock thread for execution
settings.queue << 1 << 1
logger.debug 'Thread unlocked for command execution'
"Thanks for confirming token #{params[:token]}. The command has been started. You'll receive an email once it's finished."
else
status 403
'This token has either expired, already been confirmed or it never existed.'
end
end
private
# Validates new request can be initiated. This means that:
# * There is no command executing just now
def can_process_cmd?
settings.queue.empty?
end
# Generates a new secure token for future validation
def generate_token
SecureRandom.urlsafe_base64.tap do |token|
logger.debug "Generated token #{token}"
end
end
def self.initialize_command_thread(settings)
settings.logger.debug "Loaded with environments:\n" + ENV.map{|k,v| " #{k}=#{v}"}.join("\n")
Thread.abort_on_exception = true
Thread.new do
loop do
settings.logger.info "Waiting for command execution..."
settings.queue.pop
settings.cache[:token] = nil #invalidates this token
settings.logger.info "Start command execution"
cmd = "#{settings.cmd} #{settings.cache[:args]}"
settings.logger.debug "Will execute #{cmd}"
stdout_str, stderr_str, status = nil
time = Benchmark.measure do
stdout_str, stderr_str, status = Open3.capture3(ENV,cmd)
end
settings.queue.pop # release lock (two queue elements)
subject = (status.success? ? "SUCCESS" : "ERROR") + " executing #{cmd}"
Mail.deliver do
from settings.mail_from
to settings.mail_to
subject subject
add_file filename: 'stderr.txt', content: stderr_str if stderr_str && stderr_str !~ /\A\s*\z/
add_file filename: 'stdout.txt', content: stdout_str if stdout_str && stdout_str !~ /\A\s*\z/
body <<-BODY
Command finished in #{format('%.2f', time.real)} seconds
-------------------------------------------------------------------------------
Please refer to the attached file(s) for the standard and error output produced
by the command, if any.
BODY
end
end
settings.logger.info "Command executed"
end
end
def process_cmd_request
if settings.cache[:ts].nil? ||
settings.cache[:token].nil? ||
Time.now - settings.cache[:ts] > settings.cache_timeout ||
%w(true 1).include?(params[:force])
settings.cache[:ts] = Time.now
settings.cache[:token] = generate_token
settings.cache[:args] = params[:args]
end
return settings.cache[:token], settings.cache[:args]
end
def verify_token(token)
settings.cache[:ts] &&
settings.cache[:token] &&
Time.now() - settings.cache[:ts] < settings.cache_timeout &&
settings.cache[:token] == token
end
def send_mail_validation
token, args = process_cmd_request
token_url = uri("/#{token}", true)
html_body = erb(:mail, locals: {token_url: token_url, cmd: settings.cmd, args: args})
text_body = erb(:mail_txt, locals: {token_url: token_url, cmd: settings.cmd, args: args})
app = self
Mail.deliver do
from app.settings.mail_from
to app.settings.mail_to
subject app.settings.mail_subject
html_part do
content_type 'text/html; charset=UTF-8'
body html_body
end
text_part do
body text_body
end
end
end
configure do
set :logger, Logger.new(STDOUT)
set :cmd, ENV['CMD_AS'] || 'echo "Hello World"'
set :mail_to, ENV['MAIL_TO'] || '[email protected]'
set :mail_from , ENV['MAIL_FROM'] || '[email protected]'
set :mail_subject, ENV['MAIL_SUBJECT'] || 'Command as a service'
set :cache, Hash.new
set :cache_timeout, (ENV['CACHE_TIMEOUT'] || '300').to_i # Number of seconds till cache is valid
set :queue, SizedQueue.new(2)
enable :logging
initialize_command_thread settings
Mail.defaults do
delivery_method :smtp, {
address: ENV['MAIL_HOST'] || 'mail.example.net',
port: ENV['MAIL_PORT'] || '25',
user_name: ENV['MAIL_USER'],
password: ENV['MAIL_PASS'],
authentication: ENV['MAIL_AUTH'], # :plain, :login, :cram_md5, the default is no auth
enable_starttls_auto: !(ENV['MAIL_STARTTLS'].nil? || ENV['MAIL_STARTTLS'].empty?)
}
end
end