heighliner 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +182 -0
- data/exe/heighliner +86 -0
- data/lib/heighliner/after_dotter.rb +23 -0
- data/lib/heighliner/cli.rb +714 -0
- data/lib/heighliner/cli_options.rb +14 -0
- data/lib/heighliner/cmds/attach.rb +23 -0
- data/lib/heighliner/cmds/db_load.rb +29 -0
- data/lib/heighliner/cmds/db_reset.rb +22 -0
- data/lib/heighliner/cmds/db_reset_hard.rb +21 -0
- data/lib/heighliner/cmds/db_save.rb +26 -0
- data/lib/heighliner/cmds/deinit.rb +22 -0
- data/lib/heighliner/cmds/down.rb +21 -0
- data/lib/heighliner/cmds/init.rb +40 -0
- data/lib/heighliner/cmds/login.rb +21 -0
- data/lib/heighliner/cmds/logs.rb +19 -0
- data/lib/heighliner/cmds/root.rb +21 -0
- data/lib/heighliner/cmds/set.rb +127 -0
- data/lib/heighliner/cmds/show.rb +40 -0
- data/lib/heighliner/cmds/shutdown.rb +36 -0
- data/lib/heighliner/cmds/up.rb +57 -0
- data/lib/heighliner/command_runner.rb +54 -0
- data/lib/heighliner/config.rb +95 -0
- data/lib/heighliner/databases/mysql.rb +45 -0
- data/lib/heighliner/databases/postgres.rb +46 -0
- data/lib/heighliner/docker_control.rb +10 -0
- data/lib/heighliner/dotter.rb +21 -0
- data/lib/heighliner/error.rb +14 -0
- data/lib/heighliner/plugin.rb +68 -0
- data/lib/heighliner/plugins/database.rb +30 -0
- data/lib/heighliner/plugins/git_submodule.rb +23 -0
- data/lib/heighliner/service.rb +60 -0
- data/lib/heighliner/steerfile.rb +120 -0
- data/lib/heighliner/version.rb +5 -0
- data/lib/heighliner.rb +52 -0
- metadata +233 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'heighliner/command_runner'
|
|
4
|
+
require 'active_support/core_ext/object/blank'
|
|
5
|
+
|
|
6
|
+
module Heighliner
|
|
7
|
+
# The commandline
|
|
8
|
+
class Cli
|
|
9
|
+
extend Heighliner::CliOptions
|
|
10
|
+
|
|
11
|
+
attr_reader :use_steerfile
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@use_steerfile = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def set_config
|
|
18
|
+
# This is here for backwards compatibility since it can be used in Steerfiles.
|
|
19
|
+
# It would be a good idea to deprecate this and make it more abstract.
|
|
20
|
+
@work_dir = Config.work_dir
|
|
21
|
+
@config_dir = Config.config_dir
|
|
22
|
+
@config_file = Config.config_file
|
|
23
|
+
@steerfile = Config.steerfile
|
|
24
|
+
@config = Config.config
|
|
25
|
+
@out = Config.out
|
|
26
|
+
@info_out = Config.info_out
|
|
27
|
+
|
|
28
|
+
@steerfile.validate! if @use_steerfile
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# At first I did this in the constructor but the problem with that is Optimist
|
|
32
|
+
# will parse the entire commandline for the first Cli command registered.
|
|
33
|
+
# That means no matter what you call -h or --help on, it will always return the help
|
|
34
|
+
# for the first subcommand. Fixed this by only running define_options when
|
|
35
|
+
# a command is run. We can't just run the constructor at that point because
|
|
36
|
+
# we need each Cli class to be constructed in the beginning so we can add their
|
|
37
|
+
# usage text to the output of `heighliner -h`.
|
|
38
|
+
def define_options(global_opts = [])
|
|
39
|
+
# We can't just call usage within the options block because that actually shifts
|
|
40
|
+
# the scope to Optimist::Parser. We can still reference variables but we can't
|
|
41
|
+
# call instance methods of a Heighliner::Cli class.
|
|
42
|
+
u = usage
|
|
43
|
+
Optimist.options do
|
|
44
|
+
banner u
|
|
45
|
+
|
|
46
|
+
global_opts.each { |o| opt(*o) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.register(name, klass)
|
|
51
|
+
@subcommands ||= {}
|
|
52
|
+
@subcommands[name] = klass.new
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.run_command(name, global_opts)
|
|
56
|
+
cmd = @subcommands[name]
|
|
57
|
+
opts = cmd.define_options(global_opts + cmd.class.options)
|
|
58
|
+
|
|
59
|
+
# The define_options method has stripped all arguments from the cli so now
|
|
60
|
+
# all that we're left with in ARGV are the subcommand to be run and possibly
|
|
61
|
+
# its own subcommands. We remove the subcommand here so each subcommand can
|
|
62
|
+
# easily use ARGV.shift to access its own subcommands.
|
|
63
|
+
ARGV.shift
|
|
64
|
+
|
|
65
|
+
Heighliner::Config.load(Dir.pwd, use_steerfile: cmd.use_steerfile)
|
|
66
|
+
|
|
67
|
+
# We do all this work in here instead of the exe/heighliner file because we
|
|
68
|
+
# want -h options to output before we check if a Steerfile exists.
|
|
69
|
+
# If we do it in exe/heighliner, people won't be able to check help messages
|
|
70
|
+
# unless they create a Steerfile first.
|
|
71
|
+
if opts[:quiet]
|
|
72
|
+
Config.out = File.open(File::NULL, 'w')
|
|
73
|
+
Config.info_out = File.open(File::NULL, 'w')
|
|
74
|
+
elsif opts[:verbose] || Config.always_verbose?
|
|
75
|
+
Config.out = $stderr
|
|
76
|
+
Config.info_out = Heighliner::AfterDotter.new(dotter: Heighliner::Dotter.new)
|
|
77
|
+
else
|
|
78
|
+
Config.out = Heighliner::Dotter.new
|
|
79
|
+
Config.info_out = Heighliner::AfterDotter.new(dotter: Config.out)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
cmd.set_config
|
|
83
|
+
|
|
84
|
+
cmd.execute(opts)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.all_subcommands_usage
|
|
88
|
+
output = ''
|
|
89
|
+
|
|
90
|
+
@subcommands.each do |name, klass|
|
|
91
|
+
name_s = name.to_s
|
|
92
|
+
|
|
93
|
+
output += "#{name_s}\n"
|
|
94
|
+
output += name_s.gsub(/./, '-')
|
|
95
|
+
output += "\n"
|
|
96
|
+
output += klass.usage
|
|
97
|
+
output += "\n\n"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
output
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def stop_app
|
|
104
|
+
Config.info_out.puts 'Stopping application'
|
|
105
|
+
killrm app_container_name
|
|
106
|
+
stop_services
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def start_services
|
|
110
|
+
services.each do |service|
|
|
111
|
+
Config.info_out.puts "Starting service: #{service.name}"
|
|
112
|
+
run_if_dead(
|
|
113
|
+
service.shared_name, service.start_docker_command
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def stop_services
|
|
119
|
+
services.each do |service|
|
|
120
|
+
Config.info_out.puts "Stopping service: #{service.name}"
|
|
121
|
+
killrm service.shared_name
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def ensure_db_volume
|
|
128
|
+
create_if_volume_not_exist db_volume_name
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def setup_db
|
|
132
|
+
ensure_db_volume
|
|
133
|
+
start_db
|
|
134
|
+
return if File.exist?(default_db_image)
|
|
135
|
+
return unless db_present?
|
|
136
|
+
|
|
137
|
+
# Some databases keep state around, best to clean it.
|
|
138
|
+
stop_db
|
|
139
|
+
delete_db_volume
|
|
140
|
+
start_db
|
|
141
|
+
|
|
142
|
+
Config.info_out.puts 'Provisioning database'
|
|
143
|
+
killrm "#{envname}-apptemp"
|
|
144
|
+
CommandRunner.run! Config.out, "docker run -ti
|
|
145
|
+
--rm
|
|
146
|
+
--name #{envname}-apptemp
|
|
147
|
+
--network #{Config.config[:networkname]}
|
|
148
|
+
#{app_params}
|
|
149
|
+
heighliner:#{envname}-#{current_branch} #{db_reset_command}"
|
|
150
|
+
|
|
151
|
+
save_db('default')
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def save_db(name)
|
|
155
|
+
return unless db_present?
|
|
156
|
+
|
|
157
|
+
killrm db_container_name
|
|
158
|
+
save_db_state_from container: db_volume_name, to_file: db_image_path(name)
|
|
159
|
+
start_db
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def load_db(name)
|
|
163
|
+
return unless db_present?
|
|
164
|
+
|
|
165
|
+
check_db_image_exists(name)
|
|
166
|
+
killrm db_container_name
|
|
167
|
+
CommandRunner.run Config.out, "docker volume rm #{db_volume_name}"
|
|
168
|
+
delete_db_volume
|
|
169
|
+
create_if_volume_not_exist db_volume_name
|
|
170
|
+
load_db_state_from file: db_image_path(name), to_container: db_volume_name
|
|
171
|
+
start_db
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def check_db_image_exists(name)
|
|
175
|
+
return if File.exist?(db_image_path(name))
|
|
176
|
+
|
|
177
|
+
Optimist.die 'No saved state exists with that name'
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def save_db_state_from(container:, to_file:)
|
|
181
|
+
Config.info_out.puts 'Saving database state'
|
|
182
|
+
File.write(to_file, '')
|
|
183
|
+
CommandRunner.run Config.out, "docker run --rm
|
|
184
|
+
-v #{container}:#{db_data_directory}
|
|
185
|
+
-v #{to_file}:#{to_file}
|
|
186
|
+
ruby:alpine
|
|
187
|
+
tar cvjf #{to_file} #{db_data_directory}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def load_db_state_from(file:, to_container:)
|
|
191
|
+
Config.info_out.puts 'Loading database state'
|
|
192
|
+
CommandRunner.run Config.out, "docker run --rm
|
|
193
|
+
-v #{to_container}:#{db_data_directory}
|
|
194
|
+
-v #{file}:#{file}
|
|
195
|
+
ruby:alpine
|
|
196
|
+
tar xvjf #{file} -C #{db_data_directory}
|
|
197
|
+
--strip #{db_data_directory.scan(%r{/}).count}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def stop_db
|
|
201
|
+
return unless db_present?
|
|
202
|
+
|
|
203
|
+
Config.info_out.puts 'Stopping database'
|
|
204
|
+
killrm db_container_name
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def start_db
|
|
208
|
+
return unless db_present?
|
|
209
|
+
|
|
210
|
+
Config.info_out.puts 'Starting up database'
|
|
211
|
+
run_if_dead db_container_name, "docker run -d
|
|
212
|
+
-p #{db_port}:#{db_expose}
|
|
213
|
+
-v #{db_volume_name}:#{db_data_directory}
|
|
214
|
+
--name #{db_container_name}
|
|
215
|
+
--network #{network_name}
|
|
216
|
+
#{db_params}
|
|
217
|
+
#{db_image}
|
|
218
|
+
#{db_commands}"
|
|
219
|
+
wait_for_db unless db_waitscript.nil?
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def delete_db_volume
|
|
223
|
+
CommandRunner.run Config.out, "docker volume rm #{db_volume_name}"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def current_branch_db_image_dir
|
|
227
|
+
"#{Config.config_dir}/databases/#{envname}/#{current_branch}"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def db_image_path(name)
|
|
231
|
+
if name.start_with?('./')
|
|
232
|
+
path = "#{home_dir_loc}/#{name.sub('./', '')}"
|
|
233
|
+
Config.info_out.puts "Database image path is: #{path}"
|
|
234
|
+
return path
|
|
235
|
+
end
|
|
236
|
+
FileUtils.mkdir_p current_branch_db_image_dir
|
|
237
|
+
"#{current_branch_db_image_dir}/#{name}.tar.bz"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def default_db_image
|
|
241
|
+
db_image_path('default')
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def attach_app
|
|
245
|
+
start_services
|
|
246
|
+
|
|
247
|
+
puts 'Attaching to app...'
|
|
248
|
+
|
|
249
|
+
cmd = (ARGV || []).join(' ')
|
|
250
|
+
killrm app_container_name
|
|
251
|
+
|
|
252
|
+
attach_mounts = Config.steerfile.attach_mounts
|
|
253
|
+
volumes = attach_mounts.map { |from, to| "-v #{`pwd`.chomp}/#{from}:#{to}" }.join(' ')
|
|
254
|
+
|
|
255
|
+
cmd = "docker run -ti
|
|
256
|
+
--name #{app_container_name}
|
|
257
|
+
--network #{network_name}
|
|
258
|
+
--dns #{ip_of_container(Config.config[:shared_names][:dns])}
|
|
259
|
+
--dns-search #{http_suffix}
|
|
260
|
+
-p #{app_port}:#{app_expose}
|
|
261
|
+
-e DEV_APPLICATION_HOST=#{envname}.#{http_suffix}
|
|
262
|
+
-e VIRTUAL_HOST=#{envname}.#{http_suffix}
|
|
263
|
+
-e VIRTUAL_PORT=#{app_expose}
|
|
264
|
+
#{volumes}
|
|
265
|
+
#{app_params}
|
|
266
|
+
heighliner:#{envname}-#{current_branch} #{cmd}".tr("\n", ' ')
|
|
267
|
+
|
|
268
|
+
puts cmd
|
|
269
|
+
system cmd
|
|
270
|
+
|
|
271
|
+
stop_services
|
|
272
|
+
|
|
273
|
+
Config.out.puts 'Cleaning up...'
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def start_app
|
|
277
|
+
start_services
|
|
278
|
+
|
|
279
|
+
Config.info_out.puts 'Starting up application'
|
|
280
|
+
killrm app_container_name
|
|
281
|
+
CommandRunner.run! Config.out, "docker run -d
|
|
282
|
+
--name #{app_container_name}
|
|
283
|
+
--network #{network_name}
|
|
284
|
+
--dns #{ip_of_container(Config.config[:shared_names][:dns])}
|
|
285
|
+
--dns-search #{http_suffix}
|
|
286
|
+
-p #{app_port}:#{app_expose}
|
|
287
|
+
-e DEV_APPLICATION_HOST=#{envname}.#{http_suffix}
|
|
288
|
+
-e VIRTUAL_HOST=#{envname}.#{http_suffix}
|
|
289
|
+
-e VIRTUAL_PORT=#{app_expose}
|
|
290
|
+
#{app_params}
|
|
291
|
+
heighliner:#{envname}-#{current_branch}"
|
|
292
|
+
wait_for_app
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def tmp_waitscript_name
|
|
296
|
+
"#{Config.config_dir}/#{envname}-dbwaitscript"
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def tmp_dockerfile_name
|
|
300
|
+
"#{Config.config_dir}/#{envname}-dockerfile"
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def tmp_db_waiter
|
|
304
|
+
"#{envname}-dbwait"
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def tmp_file_container
|
|
308
|
+
"#{envname}-tmpfiles"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def tmp_file_volume
|
|
312
|
+
"#{envname}-tmpfiles-vol"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def run_blocking_script(image, params, script, &block)
|
|
316
|
+
killrm tmp_db_waiter
|
|
317
|
+
killrm tmp_file_container
|
|
318
|
+
|
|
319
|
+
create_if_volume_not_exist tmp_file_volume
|
|
320
|
+
|
|
321
|
+
CommandRunner.run! Config.out, "docker create
|
|
322
|
+
-v #{tmp_file_volume}:/tmpvol
|
|
323
|
+
--name #{tmp_file_container} alpine"
|
|
324
|
+
|
|
325
|
+
File.write(tmp_waitscript_name, script)
|
|
326
|
+
|
|
327
|
+
CommandRunner.run! Config.out, "docker cp
|
|
328
|
+
#{tmp_waitscript_name}
|
|
329
|
+
#{tmp_file_container}:/tmpvol/wait.sh"
|
|
330
|
+
|
|
331
|
+
CommandRunner.run!(
|
|
332
|
+
Config.out,
|
|
333
|
+
"docker run --rm -ti
|
|
334
|
+
--name #{tmp_db_waiter}
|
|
335
|
+
--network #{network_name}
|
|
336
|
+
-v #{tmp_file_volume}:/tmpvol
|
|
337
|
+
#{params}
|
|
338
|
+
#{image} sh /tmpvol/wait.sh",
|
|
339
|
+
&block
|
|
340
|
+
)
|
|
341
|
+
ensure
|
|
342
|
+
killrm tmp_file_container
|
|
343
|
+
FileUtils.rm(tmp_waitscript_name)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def wait_for_app
|
|
347
|
+
return unless server_type == :http
|
|
348
|
+
|
|
349
|
+
Config.info_out.puts 'Waiting for server to start...'
|
|
350
|
+
|
|
351
|
+
http_code_extractor = "curl -s -o /dev/null -I -w \"\%<http_code>s\" http://#{app_container_name}:#{app_expose}"
|
|
352
|
+
unreachable_test = "#{http_code_extractor} | grep -q 000"
|
|
353
|
+
|
|
354
|
+
# This waitscript runs until curl returns a non-unreachable status code
|
|
355
|
+
# and then checks to see if its 200. If its not, it will raise an error.
|
|
356
|
+
wait_script = <<-SCRIPT
|
|
357
|
+
apk update
|
|
358
|
+
apk add curl
|
|
359
|
+
while #{unreachable_test}; do
|
|
360
|
+
echo 'o'
|
|
361
|
+
sleep 1
|
|
362
|
+
done
|
|
363
|
+
echo '#{http_code_extractor}'
|
|
364
|
+
echo $(#{http_code_extractor})
|
|
365
|
+
if [ "$(#{http_code_extractor})" != "200" ]; then
|
|
366
|
+
echo $(#{http_code_extractor})
|
|
367
|
+
else
|
|
368
|
+
echo '!'
|
|
369
|
+
fi
|
|
370
|
+
SCRIPT
|
|
371
|
+
run_blocking_script('alpine', '', wait_script) do |line|
|
|
372
|
+
# This script gets run every line that gets output.
|
|
373
|
+
# The '!' exclamation mark means success
|
|
374
|
+
# Three numbers means a status code has been returned
|
|
375
|
+
# If curl returns an error status the script will cut out and
|
|
376
|
+
# the app container died error will be displayed.
|
|
377
|
+
raise Heighliner::Error, "Failed with HTTP status: #{line}" if line =~ /^[0-9]{3}$/ && line != '200'
|
|
378
|
+
|
|
379
|
+
if line != '!' && container_dead?(app_container_name)
|
|
380
|
+
raise Heighliner::Error,
|
|
381
|
+
'App container died. Run `heighliner logs` to see why.'
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
Config.info_out.puts 'Started successfully!'
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def wait_for_db
|
|
389
|
+
return unless db_present?
|
|
390
|
+
|
|
391
|
+
Config.info_out.puts 'Waiting for database to start...'
|
|
392
|
+
run_blocking_script(db_image, db_waitscript_params, db_waitscript)
|
|
393
|
+
Config.info_out.puts 'Started.'
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def network_name
|
|
397
|
+
Config.config[:networkname]
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def services
|
|
401
|
+
@services ||= Config.steerfile.services.map { |name, info| Service.new(envname, name, info) }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def force_platform
|
|
405
|
+
Config.steerfile.platform || ''
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def db_port
|
|
409
|
+
Config.config[:envs][envname][:db_port]
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def db_expose
|
|
413
|
+
Config.steerfile.database[:port]
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def db_params
|
|
417
|
+
eval_template Config.steerfile.database[:params]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def db_image
|
|
421
|
+
image = Config.steerfile.database[:image]
|
|
422
|
+
platform = Config.steerfile.database[:platform].presence
|
|
423
|
+
platform ? "--platform #{platform} #{image}" : image
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def db_present?
|
|
427
|
+
db_image != 'none'
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def db_commands
|
|
431
|
+
eval_template Config.steerfile.database[:commands]
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def db_data_directory
|
|
435
|
+
Config.steerfile.database[:data_dir]
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def server_type
|
|
439
|
+
Config.steerfile.server_type
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def db_waitscript
|
|
443
|
+
eval_template Config.steerfile.database[:waitscript]
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def db_waitscript_params
|
|
447
|
+
eval_template Config.steerfile.database[:waitscript_params]
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def docker_file_contents
|
|
451
|
+
eval_template Config.steerfile.docker_file_contents
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def docker_build_args
|
|
455
|
+
Config.steerfile.docker_build_args
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def app_params
|
|
459
|
+
eval_template Config.steerfile.params
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def db_reset_command
|
|
463
|
+
eval_template Config.steerfile.database_reset_command
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def eval_template(value)
|
|
467
|
+
ERB.new(value).result(binding)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def app_port
|
|
471
|
+
Config.config[:envs][envname][:app_port]
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def app_expose
|
|
475
|
+
Config.steerfile.port
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def db_volume_name
|
|
479
|
+
"#{envname}-database"
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def app_container_name
|
|
483
|
+
"#{envname}-app"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def db_container_name
|
|
487
|
+
"#{envname}-db"
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def current_branch
|
|
491
|
+
`git branch | grep \\* | cut -d ' ' -f2`.chomp.gsub(/[^\-_0-9a-z]+/, '-')
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def ensure_env
|
|
495
|
+
return unless envname.nil?
|
|
496
|
+
|
|
497
|
+
Optimist.die('No environment? Please use heighliner init <name>')
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def http_suffix
|
|
501
|
+
Config.config[:http_suffix] || 'lvh.me'
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def copy_keyfile(file)
|
|
505
|
+
Config.info_out.puts "Loading certificate file: #{file}"
|
|
506
|
+
if Config.config[:cert_source][:folder]
|
|
507
|
+
Config.info_out.puts " Source: folder (#{Config.config[:cert_source][:folder]})"
|
|
508
|
+
CommandRunner.run! Config.out, "docker run --rm
|
|
509
|
+
-v #{Config.config[:shared_names][:certs]}:/certs
|
|
510
|
+
-v #{Config.config[:cert_source][:folder]}:/cert_source
|
|
511
|
+
alpine cp /cert_source/#{file} /certs/#{file}"
|
|
512
|
+
|
|
513
|
+
elsif Config.config[:cert_source][:url]
|
|
514
|
+
Config.info_out.puts " Source: URL (#{Config.config[:cert_source][:url]}/#{file})"
|
|
515
|
+
CommandRunner.run! Config.out, "docker run --rm
|
|
516
|
+
-v #{Config.config[:shared_names][:certs]}:/certs
|
|
517
|
+
alpine wget #{Config.config[:cert_source][:url]}/#{file}
|
|
518
|
+
-O /certs/#{file}"
|
|
519
|
+
|
|
520
|
+
elsif Config.config[:cert_source][:"1password"]
|
|
521
|
+
item = Config.config[:cert_source][:"1password"]
|
|
522
|
+
origfield = file.sub(/^#{http_suffix}\./, '')
|
|
523
|
+
token = ENV['OP_SERVICE_ACCOUNT_TOKEN']
|
|
524
|
+
|
|
525
|
+
field = origfield
|
|
526
|
+
if Config.config[:cert_source]["1password-fields"]
|
|
527
|
+
field = Config.config[:cert_source]["1password-fields"][origfield]
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
raise Heighliner::Error, 'OP_SERVICE_ACCOUNT_TOKEN is not set' unless token
|
|
531
|
+
|
|
532
|
+
Config.info_out.puts " Source: 1Password (item: #{item}, field: #{field} (#{origfield}) )"
|
|
533
|
+
|
|
534
|
+
# take it out
|
|
535
|
+
certstoredir = "#{ENV['CONTEXT_DIR']}/.tmp.certstore"
|
|
536
|
+
tmpfile = "#{certstoredir}/#{file}"
|
|
537
|
+
CommandRunner.run!(Config.out, "mkdir -p #{certstoredir}")
|
|
538
|
+
CommandRunner.run!(Config.out, "op read \"op://#{item}/#{field}\" > #{tmpfile}")
|
|
539
|
+
Config.info_out.puts("wrote into file #{tmpfile}")
|
|
540
|
+
CommandRunner.run!(Config.out, "ls #{tmpfile}")
|
|
541
|
+
|
|
542
|
+
# put it in
|
|
543
|
+
CommandRunner.run! Config.out, "docker run --rm
|
|
544
|
+
-v #{Config.config[:shared_names][:certs]}:/certs
|
|
545
|
+
-v #{certstoredir}:/tmpcert
|
|
546
|
+
alpine
|
|
547
|
+
cp /tmpcert/#{file} /certs/#{file}"
|
|
548
|
+
|
|
549
|
+
unless File.exist?(tmpfile) && File.size(tmpfile).positive?
|
|
550
|
+
raise Heighliner::Error,
|
|
551
|
+
"1Password field '#{field}' not found in item '#{item}'"
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
CommandRunner.run! Config.out, "docker run --rm
|
|
555
|
+
-v #{Config.config[:shared_names][:certs]}:/certs
|
|
556
|
+
-v #{tmpfile}:/cert_source
|
|
557
|
+
alpine cp /cert_source /certs/#{file}"
|
|
558
|
+
CommandRunner.run!(Config.out, "rm #{tmpfile}")
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def prepare_cert_volume!
|
|
563
|
+
Config.info_out.puts 'Preparing certificate volume'
|
|
564
|
+
create_if_volume_not_exist Config.config[:shared_names][:certs]
|
|
565
|
+
return unless Config.config[:cert_source]
|
|
566
|
+
|
|
567
|
+
%w[
|
|
568
|
+
chain.pem
|
|
569
|
+
crt
|
|
570
|
+
key
|
|
571
|
+
].each do |file_ext|
|
|
572
|
+
copy_keyfile("#{http_suffix}.#{file_ext}")
|
|
573
|
+
end
|
|
574
|
+
Config.info_out.puts 'Certificate loading complete'
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
def selenium_node_image
|
|
578
|
+
return ENV['OVERRIDE_SELENIUM_NODE_IMAGE'] unless ENV['OVERRIDE_SELENIUM_NODE_IMAGE'].nil?
|
|
579
|
+
|
|
580
|
+
if RUBY_PLATFORM.start_with?('arm64') || RUBY_PLATFORM.start_with?('aarch64')
|
|
581
|
+
# use the seleniarm image because its more stable in arm procs
|
|
582
|
+
# somehow the x64 image does not do well under qemu under arm
|
|
583
|
+
return 'seleniarm/standalone-chromium'
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# default to x64 image
|
|
587
|
+
'selenium/standalone-chrome-debug'
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def home_dir_loc
|
|
591
|
+
return ENV['_HEIGHLINER_USER_HOME'] if ENV['_HEIGHLINER_POS'] == 'docker'
|
|
592
|
+
|
|
593
|
+
ENV['HOME']
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def ensure_setup
|
|
597
|
+
ensure_env
|
|
598
|
+
|
|
599
|
+
setup if network.nil?
|
|
600
|
+
|
|
601
|
+
create_if_network_not_exist Config.config[:networkname]
|
|
602
|
+
if_container_dead Config.config[:shared_names][:nginx] do
|
|
603
|
+
prepare_cert_volume!
|
|
604
|
+
end
|
|
605
|
+
run_if_dead(
|
|
606
|
+
Config.config[:shared_names][:nginx],
|
|
607
|
+
"docker run -d
|
|
608
|
+
-p 80:80
|
|
609
|
+
-p 443:443
|
|
610
|
+
-v #{Config.config[:shared_names][:certs]}:/etc/nginx/certs
|
|
611
|
+
-v /var/run/docker.sock:/tmp/docker.sock:ro
|
|
612
|
+
--privileged
|
|
613
|
+
--name #{Config.config[:shared_names][:nginx]}
|
|
614
|
+
--network #{Config.config[:networkname]}
|
|
615
|
+
jwilder/nginx-proxy"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
innerdnsconffile = "#{ENV['HOME']}/.heighliner/dnsconf"
|
|
619
|
+
outerdnsconffile = "#{home_dir_loc}/.heighliner/dnsconf"
|
|
620
|
+
File.write(innerdnsconffile, <<~HOSTS)
|
|
621
|
+
log-queries
|
|
622
|
+
no-resolv
|
|
623
|
+
server=8.8.8.8
|
|
624
|
+
server=1.1.1.1
|
|
625
|
+
address=/.#{http_suffix}/#{ip_of_container(Config.config[:shared_names][:nginx])}
|
|
626
|
+
HOSTS
|
|
627
|
+
|
|
628
|
+
run_if_dead(
|
|
629
|
+
Config.config[:shared_names][:dns],
|
|
630
|
+
"docker run -d
|
|
631
|
+
--name #{Config.config[:shared_names][:dns]}
|
|
632
|
+
--network #{Config.config[:networkname]}
|
|
633
|
+
-v #{outerdnsconffile}:/etc/dnsmasq.conf:ro
|
|
634
|
+
degica/dnsmasq
|
|
635
|
+
"
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
start_chrome_container
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def start_chrome_container
|
|
642
|
+
run_if_dead(
|
|
643
|
+
Config.config[:shared_names][:chrome],
|
|
644
|
+
"docker run -d
|
|
645
|
+
-p 5900:5900
|
|
646
|
+
--shm-size='2g'
|
|
647
|
+
--name #{Config.config[:shared_names][:chrome]}
|
|
648
|
+
--network #{Config.config[:networkname]}
|
|
649
|
+
--dns #{ip_of_container(Config.config[:shared_names][:dns])}
|
|
650
|
+
#{selenium_node_image}"
|
|
651
|
+
)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def ip_of_container(containername)
|
|
655
|
+
networkname = ".NetworkSettings.Networks.#{Config.config[:networkname]}.IPAddress"
|
|
656
|
+
`docker inspect -f '{{#{networkname}}}' #{containername}`.chomp
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def network
|
|
660
|
+
`docker network inspect #{Config.config[:networkname]} 2>/dev/null`
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
def container_dead?(container)
|
|
664
|
+
x = JSON.parse(`docker inspect #{container} 2>/dev/null`)
|
|
665
|
+
x.empty? || x[0]['State']['Running'] == false
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def if_container_dead(container)
|
|
669
|
+
return unless container_dead?(container)
|
|
670
|
+
|
|
671
|
+
yield if block_given?
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def create_if_volume_not_exist(vol)
|
|
675
|
+
x = JSON.parse(`docker volume inspect #{vol} 2>/dev/null`)
|
|
676
|
+
return unless x.empty?
|
|
677
|
+
|
|
678
|
+
CommandRunner.run! Config.out, "docker volume create #{vol}"
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def create_if_network_not_exist(net)
|
|
682
|
+
out = `docker inspect #{net} 2>/dev/null`
|
|
683
|
+
out = "[]" if out.strip.empty?
|
|
684
|
+
x = JSON.parse(out)
|
|
685
|
+
return unless x.empty?
|
|
686
|
+
|
|
687
|
+
CommandRunner.run! Config.out, "docker network create #{net}"
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def run_if_dead(container, command)
|
|
691
|
+
if_container_dead container do
|
|
692
|
+
Config.info_out.puts "Starting up #{container}"
|
|
693
|
+
killrm container
|
|
694
|
+
CommandRunner.run Config.out, command
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def envname
|
|
699
|
+
Config.config[:envnames][Config.work_dir]
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def save_config
|
|
703
|
+
File.write(Config.config_file, Config.config.to_yaml)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def killrm(container)
|
|
707
|
+
x = JSON.parse(`docker inspect #{container} 2>/dev/null`)
|
|
708
|
+
return if x.empty?
|
|
709
|
+
|
|
710
|
+
CommandRunner.run Config.out, "docker kill #{container}" if x[0]['State'] && x[0]['State']['Running'] == true
|
|
711
|
+
CommandRunner.run Config.out, "docker rm #{container}" if x[0]['State']
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
end
|