-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRakefile
303 lines (257 loc) · 9.14 KB
/
Rakefile
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
291
292
293
294
295
296
297
298
299
300
301
302
303
require 'erb'
require 'fileutils'
LANGUAGES = %w(elixir ruby nodejs javascript)
PROCESSMON_PATH = "support/processmon/processmon"
def get_app
ENV['app'].tap do |app|
raise "Specify which app you want to run using app=path" if app.nil?
raise "#{app} not found" unless File.exist?(app)
end.delete_suffix("/")
end
def clone_from_git(path, repo, branch: nil)
if File.exist?(path)
puts "#{path} already present"
reset_repo(path, :branch => branch)
else
puts "Cloning #{repo} into #{path}"
branch_arg = "--branch #{branch}" if branch
run_command "git clone #{branch_arg} [email protected]:appsignal/#{repo}.git #{path}"
puts "Cloned #{repo} at #{path} is at commit #{`cd #{path} && git rev-parse HEAD`.strip}"
end
end
def reset_repo(path, branch: "main")
if File.exist?(path)
puts "Resetting #{path}"
run_command "cd #{path} && git fetch origin"
run_command "cd #{path} && git switch -f #{branch}"
run_command "cd #{path} && git reset --hard origin/#{branch}"
run_command "cd #{path} && git clean -dfx ."
else
puts "#{path} not present"
end
end
def run_command(command)
puts "Running '#{command}'"
# Spawn child process with parent process STDIN, STDOUT and STDERR
pid = spawn({}, command, :in => $stdin, :out => $stdout, :err => $stderr)
# Register child process so we can wait for it to exit gracefully later
child_processes << [pid, command]
# Wait for child process to end
_pid, status = Process.wait2(pid)
# Exit with the error status code if an error occurred in the child process
exit status.exitstatus unless status.success?
end
def child_processes
@child_processes ||= []
end
# "Trap" an interrupt from the user and wait for the child processes to end
# first before exiting this process. Docker compose if interrupted gracefully
# stops all its containers first before truely exiting, wait for this graceful
# exit.
Signal.trap "INT" do
return if child_processes.empty?
child_processes.each do |(pid, command)|
begin
Process.kill(0, pid) # Check if process still exists
puts "Waiting for child process to end: #{pid}: #{command}"
_pid, status = Process.wait2(pid)
# Exit with the error status code if an error occurred in the child process
exit status.exitstatus unless status.success?
rescue Errno::ESRCH
# Do nothing, child process is no longer running
end
end
# There were no errors, so gracefully exit process here
exit 0
end
def render_erb(file)
ERB.new(File.read(file)).result
end
namespace :app do
desc "Open the browser pointing to the app"
task :open do
run_command "open http://localhost:4001"
end
desc "Create a test setup skeleton"
task :new do
@app = ENV['app']
raise "#{@app} already exists" if File.exist?(@app)
# Create directories
FileUtils.mkdir_p "#{@app}"
FileUtils.mkdir_p "#{@app}/app"
FileUtils.mkdir_p "#{@app}/commands"
# Copy command scripts
%w(console diagnose demo run).each do |command|
FileUtils.cp "support/templates/skeleton/commands/#{command}", "#{@app}/commands/#{command}"
end
# Copy readme
FileUtils.cp "support/templates/skeleton/README.md", "#{@app}/README.md"
# Render Dockerfile
File.write "#{@app}/Dockerfile", render_erb("support/templates/skeleton/Dockerfile.erb")
# Render docker compose file
File.write "#{@app}/docker-compose.yml", render_erb("support/templates/skeleton/docker-compose.yml.erb")
puts "Generated test setup skeleton in #{@app}. Next:"
puts "- Add your code in the app directory"
puts "- Fill out the TODO markers in the generated files"
end
def build_app
unless File.exist?("appsignal_key.env")
raise "No push api key set yet, run rake global:set_push_api_key key=<key>"
end
unless File.exist?(PROCESSMON_PATH)
puts "Processmon not present. Building processmon..."
Rake::Task["global:install_processmon"].invoke
end
@app = get_app
puts "Starting #{@app}"
puts "Copying processmon"
FileUtils.rm_f "#{@app}/commands/processmon"
FileUtils.cp "support/processmon/processmon", "#{@app}/commands/"
run_hook @app, :before_build
puts "Building environment..."
options = ""
build_args = ENV["build_arg"]
if build_args
options = "--build-arg=#{build_args}"
end
run_command "cd #{@app} && docker compose build #{options}"
puts "Cleaning processmon"
FileUtils.rm_f "#{@app}/commands/processmon"
end
desc "Start a test app"
task :up do
build_app
puts "Starting compose..."
run_command "cd #{@app} && docker compose up --abort-on-container-exit"
end
desc "Start a test app and run the tests on it"
task :test do
build_app
puts "Building the tests container..."
run_command "cd #{@app} && docker compose build --build-arg TESTING=true tests"
puts "Starting compose with the tests..."
run_command "cd #{@app} && docker compose --profile tests up --abort-on-container-exit --exit-code-from tests"
end
desc "Attach to app and get bash"
task :bash do
@app = get_app
puts "Starting bash in #{@app}"
run_command "cd #{@app} && docker compose exec --workdir /app app /bin/bash"
end
desc "Attach to app and get a console"
task :console do
@app = get_app
if File.exist?("#{@app}/commands/console")
puts "Starting console in #{@app}"
run_command "cd #{@app} && docker compose exec app /commands/console"
else
puts "Starting a console in #{@app} is not supported"
end
end
desc "Attach to app and run diagnose"
task :diagnose do
@app = get_app
if File.exist?("#{@app}/commands/diagnose")
puts "Runing diagnose in #{@app}"
run_command "cd #{@app} && docker compose exec app /commands/diagnose"
else
puts "Running diagnose in #{@app} is not supported"
end
end
desc "Attach to app and run demo"
task :demo do
@app = get_app
if File.exist?("#{@app}/commands/demo")
puts "Runing demo in #{@app}"
run_command "cd #{@app} && docker compose exec app /commands/demo"
else
puts "Running demo in #{@app} is not supported"
end
end
desc "Restart the app container, needed when making changes in the integration"
task :restart do
@app = get_app
puts "Restarting #{@app}"
run_command "cd #{@app} && docker compose restart app"
end
desc "Bring compose down and remove cached app docker image"
task :down do
@app = get_app
puts "Bringing compose down..."
run_command "cd #{@app} && docker compose --profile tests down --rmi=local"
run_command "docker image rm -f #{@app}:latest"
end
namespace :tail do
desc "Tail appsignal.log"
task :appsignal do
@app = get_app
run_command "cd #{@app} && docker compose exec app touch /tmp/appsignal.log"
run_command "cd #{@app} && docker compose exec app tail -f /tmp/appsignal.log"
end
end
namespace :less do
desc "Less +F appsignal.log"
task :appsignal do
@app = get_app
run_command "cd #{@app} && docker compose exec app touch /tmp/appsignal.log"
run_command "cd #{@app} && docker compose exec app less +F /tmp/appsignal.log"
end
end
end
namespace :integrations do
desc "Clone and reset integrations"
task :clone do
# Clone Ruby
clone_from_git("ruby/integration", "appsignal-ruby")
# Clone Elixir, it currently consists of multiple repos
FileUtils.mkdir_p("elixir/integration")
clone_from_git("elixir/integration/appsignal-elixir", "appsignal-elixir")
clone_from_git("elixir/integration/appsignal-elixir-phoenix", "appsignal-elixir-phoenix")
clone_from_git("elixir/integration/appsignal-elixir-plug", "appsignal-elixir-plug")
# Clone Node.js
clone_from_git("nodejs/integration", "appsignal-nodejs")
# Clone JavaScript
clone_from_git("javascript/integration", "appsignal-javascript")
# Clone Python
clone_from_git("python/integration", "appsignal-python")
end
desc "Remove integrations"
task :clean do
run_command("rm -rf ruby/integration")
run_command("rm -rf elixir/integration")
run_command("rm -rf nodejs/integration")
run_command("rm -rf javascript/integration")
run_command("rm -rf python/integration")
end
end
namespace :global do
desc "Update global files"
task :update => [:update_readme]
desc "Update the readme using the template"
task :update_readme do
@apps = LANGUAGES.map do |language|
Dir["#{language}/*"].reject do |dir|
dir.end_with?("integration")
end.sort
end.flatten
File.write "README.md", render_erb("support/templates/README.md.erb")
end
desc "Set the push api key to use"
task :set_push_api_key do
@key = ENV['key'] or raise "No key provided"
puts "Setting push api key in appsignal_key.env"
File.write "appsignal_key.env", render_erb("support/templates/appsignal_key.env.erb")
end
desc "Install bundled processmon"
task :install_processmon do
run_command("cd support/processmon && ./build.sh")
end
end
def run_hook(app, event)
if event == :before_build
hook_file = "hooks/#{event}"
hook_file_path = File.join(@app, hook_file)
return unless File.exist? hook_file_path
run_command("cd #{@app} && ./#{hook_file}")
end
end