fly-rails 0.3.5

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.
@@ -0,0 +1,652 @@
1
+ require 'open3'
2
+ require 'thor'
3
+ require 'toml'
4
+ require 'active_support'
5
+ require 'active_support/core_ext/string/inflections'
6
+ require 'fly-rails/machines'
7
+ require 'fly-rails/utils'
8
+ require 'fly-rails/dsl'
9
+ require 'fly-rails/scanner'
10
+
11
+ module Fly
12
+ class Actions < Thor::Group
13
+ include Thor::Actions
14
+ include Thor::Base
15
+ include Thor::Shell
16
+ include Fly::Scanner
17
+ attr_accessor :options, :dockerfile, :ignorefile
18
+
19
+ def initialize(app=nil, options={})
20
+ # placate thor
21
+ @options = {}
22
+ @destination_stack = [Dir.pwd]
23
+
24
+ # extract options
25
+ app ? self.app = app : app = self.app
26
+ regions = options[:region]&.flatten || []
27
+ @avahi = options[:avahi]
28
+ @litefs = options[:litefs]
29
+ @nats = options[:nats]
30
+ @nomad = options[:nomad]
31
+ @passenger = options[:passenger]
32
+ @serverless = options[:serverless]
33
+ @eject = options[:eject]
34
+
35
+ # prepare template variables
36
+ @ruby_version = RUBY_VERSION
37
+ @bundler_version = Bundler::VERSION
38
+ @node = File.exist? 'node_modules'
39
+ @yarn = File.exist? 'yarn.lock'
40
+ @node_version = @node ? `node --version`.chomp.sub(/^v/, '') : '16.17.0'
41
+ @yarn_version = @yarn ? `yarn --version`.chomp : 'latest'
42
+ @org = Fly::Machines.org
43
+
44
+ @set_stage = @nomad ? 'set' : 'set --stage'
45
+
46
+ # determine region
47
+ if !regions or regions.empty?
48
+ @regions = JSON.parse(`flyctl regions list --json --app #{app}`)['Regions'].
49
+ map {|region| region['Code']} rescue []
50
+ else
51
+ @regions = regions
52
+ end
53
+
54
+ @region = @regions.first || 'iad'
55
+ @regions = [@region] if @regions.empty?
56
+
57
+ # Process DSL
58
+ @config = Fly::DSL::Config.new
59
+ if File.exist? 'config/fly.rb'
60
+ @config.instance_eval IO.read('config/fly.rb')
61
+ @image = @config.image
62
+ end
63
+
64
+ # set additional variables based on application source
65
+ scan_rails_app
66
+ @redis = :internal if options[:redis]
67
+ if File.exist? 'Procfile.fly'
68
+ @redis = :internal if IO.read('Procfile.fly') =~ /^redis/
69
+ end
70
+
71
+ if options[:anycable] and not @anycable
72
+ # read and remove original config
73
+ original_config = YAML.load_file 'config/cable.yml'
74
+ File.unlink 'config/cable.yml'
75
+
76
+ # add and configure anycable-rails
77
+ say_status :run, 'bundle add anycable-rails'
78
+ Bundler.with_original_env do
79
+ system 'bundle add anycable-rails'
80
+ system 'bin/rails generate anycable:setup --skip-heroku --skip-procfile-dev --skip-jwt --devenv=skip'
81
+ end
82
+
83
+ # insert action_cable_meta_tag
84
+ insert_into_file 'app/views/layouts/application.html.erb',
85
+ " <%= action_cable_meta_tag %>\n",
86
+ after: "<%= csp_meta_tag %>\n"
87
+
88
+ # copy production environment to original config
89
+ anycable_config = YAML.load_file 'config/cable.yml'
90
+ original_config['production'] = anycable_config['production']
91
+ File.write 'config/cable.yml', YAML.dump(original_config)
92
+
93
+ @anycable = true
94
+ end
95
+
96
+ @nginx = @passenger || (@anycable and not @deploy)
97
+
98
+ # determine processes
99
+ @procs = {web: 'bin/rails server'}
100
+ @procs[:web] = "nginx -g 'daemon off;'" if @nginx
101
+ @procs[:rails] = "bin/rails server -p 8081" if @nginx and not @passenger
102
+ @procs[:worker] = 'bundle exec sidekiq' if @sidekiq
103
+ @procs[:redis] = 'redis-server /etc/redis/redis.conf' if @redis == :internal
104
+ @procs.merge! 'anycable-rpc': 'bundle exec anycable --rpc-host=0.0.0.0:50051',
105
+ 'anycable-go': 'env /usr/local/bin/anycable-go --port=8082 --host 0.0.0.0 --rpc_host=localhost:50051' if @anycable
106
+ end
107
+
108
+ def app
109
+ return @app if @app
110
+ self.app = TOML.load_file('fly.toml')['app']
111
+ end
112
+
113
+ def render template
114
+ template = ERB.new(IO.read(File.expand_path(template, source_paths.last)), trim_mode: '-')
115
+ template.result(binding).chomp
116
+ end
117
+
118
+ def app_template template_file, destination
119
+ app
120
+ template template_file, destination
121
+ end
122
+
123
+ def app=(app)
124
+ @app = app
125
+ @appName = @app.gsub('-', '_').camelcase(:lower)
126
+ end
127
+
128
+ source_paths.push File::expand_path('../generators/templates', __dir__)
129
+
130
+ def generate_toml
131
+ app_template 'fly.toml.erb', 'fly.toml'
132
+ end
133
+
134
+ def generate_fly_config
135
+ select_image
136
+ app_template 'fly.rb.erb', 'config/fly.rb'
137
+ end
138
+
139
+ def select_image
140
+ return @image if @image and @image.include? ":#{@ruby_version}-"
141
+
142
+ tags = []
143
+
144
+ debian_releases = %w(stretch buster bullseye bookworm)
145
+
146
+ Net::HTTP.start('quay.io', 443, use_ssl: true) do |http|
147
+ (1..).each do |page|
148
+ request = Net::HTTP::Get.new "/api/v1/repository/evl.ms/fullstaq-ruby/tag/?page=#{page}&limit=100"
149
+ response = http.request request
150
+ body = JSON.parse(response.body)
151
+ tags += body['tags'].map {|tag| tag['name']}.grep /jemalloc-\w+-slim/
152
+ break unless body['has_additional']
153
+ end
154
+ end
155
+
156
+ ruby_releases = tags.group_by {|tag| tag.split('-').first}.
157
+ map do |release, tags|
158
+ [release, tags.max_by {|tag| debian_releases.find_index(tag[/jemalloc-(\w+)-slim/, 1]) || -1}]
159
+ end.sort.to_h
160
+
161
+ unless ruby_releases[@ruby_version]
162
+ @ruby_version = ruby_releases.keys.find {|release| release >= @ruby_version} ||
163
+ ruby_releases.keys.last
164
+ end
165
+
166
+ @image = 'quay.io/evl.ms/fullstaq-ruby:' + ruby_releases[@ruby_version]
167
+ end
168
+
169
+ def generate_dockerfile
170
+ if @eject or File.exist? 'Dockerfile'
171
+ @dockerfile = 'Dockerfile'
172
+ else
173
+ tmpfile = Tempfile.new('Dockerfile')
174
+ @dockerfile = tmpfile.path
175
+ tmpfile.unlink
176
+ at_exit { File.unlink @dockerfile }
177
+ end
178
+
179
+ if @eject or not File.exist? @dockerfile
180
+ select_image
181
+ app_template 'Dockerfile.erb', @dockerfile
182
+ end
183
+ end
184
+
185
+ def generate_dockerignore
186
+ if @eject or File.exist? '.dockerignore'
187
+ @ignorefile = '.dockerignore'
188
+ elsif File.exist? '.gitignore'
189
+ @ignorefile = '.gitignore'
190
+ else
191
+ tmpfile = Tempfile.new('Dockerignore')
192
+ @ignoreile = tmpfile.path
193
+ tmpfile.unlink
194
+ at_exit { Filee.unlink @ignorefile }
195
+ end
196
+
197
+ if @eject or not File.exist? @ignorefile
198
+ app_template 'dockerignore.erb', @ignorefile
199
+ end
200
+ end
201
+
202
+ def generate_nginx_conf
203
+ return unless @passenger
204
+ app_template 'nginx.conf.erb', 'config/nginx.conf'
205
+
206
+ if @serverless
207
+ app_template 'hook_detached_process.erb', 'config/hook_detached_process'
208
+ FileUtils.chmod 'u+x', 'config/hook_detached_process'
209
+ end
210
+ end
211
+
212
+ def generate_terraform
213
+ app_template 'main.tf.erb', 'main.tf'
214
+ end
215
+
216
+ def generate_raketask
217
+ app_template 'fly.rake.erb', 'lib/tasks/fly.rake'
218
+ end
219
+
220
+ def generate_procfile
221
+ return unless @procs.length > 1
222
+ app_template 'Procfile.fly.erb', 'Procfile.fly'
223
+ end
224
+
225
+ def generate_litefs
226
+ app_template 'litefs.yml.erb', 'config/litefs.yml'
227
+ end
228
+
229
+ def generate_key
230
+ credentials = nil
231
+ if File.exist? 'config/credentials/production.key'
232
+ credentials = 'config/credentials/production.key'
233
+ elsif File.exist? 'config/master.key'
234
+ credentials = 'config/master.key'
235
+ end
236
+
237
+ if credentials
238
+ say_status :run, "flyctl secrets #{@set_stage} RAILS_MASTER_KEY from #{credentials}"
239
+ system "flyctl secrets #{@set_stage} RAILS_MASTER_KEY=#{IO.read(credentials).chomp}"
240
+ puts
241
+ end
242
+ end
243
+
244
+ def generate_patches
245
+ if false # @redis_cable and not @anycable and @redis != :internal and
246
+ not File.exist? 'config/initializers/action_cable.rb'
247
+
248
+ app
249
+ template 'patches/action_cable.rb', 'config/initializers/action_cable.rb'
250
+ end
251
+ end
252
+
253
+ def generate_ipv4
254
+ cmd = 'flyctl ips allocate-v4'
255
+ say_status :run, cmd
256
+ system cmd
257
+ end
258
+
259
+ def generate_ipv6
260
+ cmd = 'flyctl ips allocate-v6'
261
+ say_status :run, cmd
262
+ system cmd
263
+ end
264
+
265
+ def create_volume(app, region, size)
266
+ name = "#{app.gsub('-', '_')}_volume"
267
+ volumes = JSON.parse(`flyctl volumes list --json`)
268
+
269
+ volume = volumes.find {|volume| volume['Name'] == name and volume['Region'] == region}
270
+ unless volume
271
+ cmd = "flyctl volumes create #{name} --app #{app} --region #{region} --size #{size}"
272
+ say_status :run, cmd
273
+ system cmd
274
+ volumes = JSON.parse(`flyctl volumes list --json`)
275
+ volume = volumes.find {|volume| volume['Name'] == name and volume['Region'] == region}
276
+ end
277
+
278
+ volume && volume['id']
279
+ end
280
+
281
+ def create_postgres(app, org, region, vm_size, volume_size, cluster_size)
282
+ cmd = "flyctl postgres create --name #{app}-db --org #{org} --region #{region} --vm-size #{vm_size} --volume-size #{volume_size} --initial-cluster-size #{cluster_size}"
283
+ cmd += ' --machines' unless @nomad
284
+ say_status :run, cmd
285
+ output = FlyIoRails::Utils.tee(cmd)
286
+ output[%r{postgres://\S+}]
287
+ end
288
+
289
+ def create_redis(app, org, region, eviction)
290
+ # see if redis is already defined
291
+ name = `flyctl redis list`.lines[1..-2].map(&:split).
292
+ find {|tokens| tokens[1] == org}&.first
293
+
294
+ if name
295
+ secret = `flyctl redis status #{name}`[%r{redis://\S+}]
296
+ return secret if secret
297
+ end
298
+
299
+ # create a new redis
300
+ cmd = "flyctl redis create --org #{org} --name #{app}-redis --region #{region} --no-replicas #{eviction} --plan #{@config.redis.plan}"
301
+ say_status :run, cmd
302
+ output = FlyIoRails::Utils.tee(cmd)
303
+ output[%r{redis://[-\w:@.]+}]
304
+ end
305
+
306
+ def bundle_gems
307
+ if @anycable and not @gemfile.include? 'anycable-rails'
308
+ cmd = 'bundle add anycable-rails'
309
+ say_status :run, cmd
310
+ Bundler.with_original_env { system cmd }
311
+ exit $?.exitstatus unless $?.success?
312
+ end
313
+
314
+ if @postgresql and not @gemfile.include? 'pg'
315
+ cmd = 'bundle add pg'
316
+ say_status :run, cmd
317
+ Bundler.with_original_env { system cmd }
318
+ exit $?.exitstatus unless $?.success?
319
+ end
320
+
321
+ if @redis and not @gemfile.include? 'redis'
322
+ cmd = 'bundle add redis'
323
+ say_status :run, cmd
324
+ Bundler.with_original_env { system cmd }
325
+ exit $?.exitstatus unless $?.success?
326
+ end
327
+ end
328
+
329
+ def release(app, options)
330
+ start = Fly::Machines.create_and_start_machine(app, options)
331
+ machine = start[:id]
332
+
333
+ if not machine
334
+ STDERR.puts 'Error starting release machine'
335
+ PP.pp start, STDERR
336
+ exit 1
337
+ end
338
+
339
+ status = Fly::Machines.wait_for_machine app, machine,
340
+ timeout: 60, state: 'started'
341
+
342
+ # wait for release to copmlete
343
+ 5.times do
344
+ status = Fly::Machines.wait_for_machine app, machine,
345
+ instance_id: start[:instance_id], timeout: 60, state: 'stopped'
346
+ break if status[:ok]
347
+ end
348
+
349
+ if status and status[:ok]
350
+ event = nil
351
+ 300.times do
352
+ status = Fly::Machines.get_a_machine app, start[:id]
353
+ event = status[:events]&.first
354
+ break if event[:type] == 'exit'
355
+ sleep 0.2
356
+ end
357
+
358
+ exit_code = event&.dig(:request, :MonitorEvent, :exit_event, :exit_code)
359
+ Fly::Machines.delete_machine app, machine if machine
360
+ exit_code ||= 0 if event&.dig(:request, :MonitorEvent, :exit_event, :signal) == -1
361
+ return event, exit_code, machine
362
+ else
363
+ return status, nil, nil
364
+ end
365
+ end
366
+
367
+ def launch(app)
368
+ secrets = JSON.parse(`flyctl secrets list --json`).
369
+ map {|secret| secret["Name"]}
370
+
371
+ unless secrets.include? 'RAILS_MASTER_KEY'
372
+ generate_key
373
+ end
374
+
375
+ if @sqlite3
376
+ if @litefs
377
+ @regions.each do |region|
378
+ @volume = create_volume(app, region, @config.sqlite3.size)
379
+ end
380
+ else
381
+ @volume = create_volume(app, @region, @config.sqlite3.size)
382
+ end
383
+ elsif @postgresql and not secrets.include? 'DATABASE_URL'
384
+ unless (IO.read('config/fly.rb').include?('postgres') rescue true)
385
+ source_paths.each do |path|
386
+ template = File.join(path, 'fly.rb.erb')
387
+ next unless File.exist? template
388
+ insert = IO.read(template)[/<% if @postgresql -%>\n(.*?)<% end/m, 1]
389
+ append_to_file 'config/fly.rb', insert if insert
390
+ break
391
+ end
392
+ end
393
+
394
+ secret = create_postgres(app, @org, @region,
395
+ @config.postgres.vm_size,
396
+ @config.postgres.volume_size,
397
+ @config.postgres.initial_cluster_size)
398
+
399
+ if secret
400
+ cmd = "flyctl secrets #{@set_stage} DATABASE_URL=#{secret}"
401
+ say_status :run, cmd
402
+ system cmd
403
+ end
404
+ end
405
+
406
+ if @redis and @redis != :internal and not secrets.include? 'REDIS_URL'
407
+ # Set eviction policy to true if a cache provider, else false.
408
+ eviction = @redis_cache ? '--enable-eviction' : '--disable-eviction'
409
+
410
+ secret = create_redis(app, @org, @region, eviction)
411
+
412
+ if secret
413
+ cmd = "flyctl secrets #{@set_stage} REDIS_URL=#{secret}"
414
+ say_status :run, cmd
415
+ system cmd
416
+ end
417
+ end
418
+ end
419
+
420
+ def release_task_defined?
421
+ if File.exist? 'lib/tasks/fly.rake'
422
+ Rake.load_rakefile 'lib/tasks/fly.rake'
423
+ else
424
+ Tempfile.create ['fly', '.rake'] do |file|
425
+ IO.write file.path, render('fly.rake.erb')
426
+ Rake.load_rakefile file.path
427
+ end
428
+ end
429
+
430
+ if Rake::Task.task_defined? 'fly:release'
431
+ task = Rake::Task['fly:release']
432
+ not (task.actions.empty? and task.prereqs.empty?)
433
+ else
434
+ false
435
+ end
436
+ end
437
+
438
+ def deploy(app, image)
439
+ launch(app)
440
+
441
+ # default config
442
+ config = {
443
+ image: image,
444
+ guest: {
445
+ cpus: @config.machine.cpus,
446
+ cpu_kind: @config.machine.cpu_kind,
447
+ memory_mb: @config.machine.memory_mb
448
+ },
449
+ services: [
450
+ {
451
+ ports: [
452
+ {port: 443, handlers: ["tls", "http"]},
453
+ {port: 80, handlers: ["http"]}
454
+ ],
455
+ protocol: "tcp",
456
+ internal_port: 8080
457
+ }
458
+ ]
459
+ }
460
+
461
+ # start proxy, if necessary
462
+ Fly::Machines::fly_api_hostname!
463
+
464
+ # only run release step if there is a non-empty release task in fly.rake
465
+ if release_task_defined?
466
+ # build config for release machine, overriding server command
467
+ release_config = config.dup
468
+ release_config.delete :services
469
+ release_config.delete :mounts
470
+ release_config[:processes] = [{
471
+ name: 'release',
472
+ entrypoint: [],
473
+ cmd: ['bin/rails', 'fly:release'],
474
+ env: {},
475
+ user: 'root'
476
+ }]
477
+
478
+ # perform release
479
+ say_status :fly, 'bin/rails fly:release'
480
+ event, exit_code, machine = release(app, region: @region, config: release_config)
481
+
482
+ if exit_code != 0
483
+ STDERR.puts 'Error performing release'
484
+ STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
485
+ STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
486
+ exit 1
487
+ end
488
+ end
489
+
490
+ # stop previous instances - list will fail on first run
491
+ stdout, stderr, status = Open3.capture3('fly machines list --json')
492
+ existing_machines = []
493
+ unless stdout.empty?
494
+ JSON.parse(stdout).each do |list|
495
+ existing_machines << list['name']
496
+ next if list['id'] == machine or list['state'] == 'destroyed'
497
+ cmd = "fly machines remove --force #{list['id']}"
498
+ say_status :run, cmd
499
+ system cmd
500
+ end
501
+ end
502
+
503
+ # configure sqlite3 (can be overridden by fly.toml)
504
+ if @sqlite3
505
+ config[:mounts] = [
506
+ { volume: @volume, path: '/mnt/volume' }
507
+ ]
508
+
509
+ config[:env] = {
510
+ "DATABASE_URL" => "sqlite3:///mnt/volume/production.sqlite3"
511
+ }
512
+
513
+ if @litefs
514
+ config[:env]['DATABASE_URL'] = "sqlite3:///data/production.sqlite3"
515
+ end
516
+ end
517
+
518
+ # process toml overrides
519
+ toml = (TOML.load_file('fly.toml') rescue {})
520
+ config[:env] = toml['env'] if toml['env']
521
+ config[:services] = toml['services'] if toml['services']
522
+ if toml['mounts']
523
+ mounts = toml['mounts']
524
+ volume = JSON.parse(`flyctl volumes list --json`).
525
+ find {|volume| volume['Name'] == mounts['source'] and volume['Region'] == @region}
526
+ if volume
527
+ config[:mounts] = [ { volume: volume['id'], path: mounts['destination'] } ]
528
+ else
529
+ STDERR.puts "volume #{mounts['source']} not found in region #{@region}"
530
+ exit 1
531
+ end
532
+ end
533
+
534
+ # start app
535
+ machines = {}
536
+ options = {region: @region, config: config}
537
+ say_status :fly, "start #{app}"
538
+ if not toml['processes'] or toml['processes'].empty?
539
+ options[:name] = "#{app}-machine"
540
+ taken = existing_machines.find {|name| name.start_with? options[:name]}
541
+ options[:name] = taken == options[:name] ? "#{taken}-2" : taken.next if taken
542
+
543
+ start = Fly::Machines.create_and_start_machine(app, options)
544
+ machines['app'] = start[:id]
545
+ else
546
+ config[:env] ||= {}
547
+ config[:env]['NATS_SERVER'] = 'localhost'
548
+ toml['processes'].each do |name, entrypoint|
549
+ options[:name] = "#{app}-machine-#{name}"
550
+ taken = existing_machines.find {|name| name.start_with? options[:name]}
551
+ options[:name] = taken == options[:name] ? "#{taken}-2" : taken.next if taken
552
+
553
+ config[:env]['SERVER_COMMAND'] = entrypoint
554
+ start = Fly::Machines.create_and_start_machine(app, options)
555
+
556
+ if start['error']
557
+ STDERR.puts "ERROR: #{start['error']}"
558
+ exit 1
559
+ end
560
+
561
+ machines[name] = start[:id]
562
+
563
+ config.delete :mounts
564
+ config.delete :services
565
+
566
+ if config[:env]['NATS_SERVER'] = 'localhost'
567
+ config[:env]['NATS_SERVER'] = start[:private_ip]
568
+ end
569
+ end
570
+ end
571
+
572
+ if machines.empty?
573
+ STDERR.puts 'Error starting application'
574
+ PP.pp start, STDERR
575
+ exit 1
576
+ end
577
+
578
+ timeout = Time.now + 300
579
+ while Time.now < timeout and not machines.empty?
580
+ machines.each do |name, machine|
581
+ status = Fly::Machines.wait_for_machine app, machine,
582
+ timeout: 10, status: 'started'
583
+ machines.delete name if status[:ok]
584
+ end
585
+ end
586
+
587
+ unless machines.empty?
588
+ STDERR.puts 'Timeout waiting for application to start'
589
+ end
590
+ end
591
+
592
+ def terraform(app, image)
593
+ # find first machine using the image ref in terraform config file
594
+ machine = Fly::HCL.parse(IO.read('main.tf')).
595
+ map {|block| block.dig(:resource, 'fly_machine')}.compact.
596
+ find {|machine| machine.values.first[:image] == 'var.image_ref'}
597
+ if not machine
598
+ STDERR.puts 'unable to find fly_machine with image = var.image_ref in main.rf'
599
+ exit 1
600
+ end
601
+
602
+ # extract HCL configuration for the machine
603
+ config = machine.values.first
604
+
605
+ # delete HCL specific configuration items
606
+ %i(services for_each region app name depends_on).each do |key|
607
+ config.delete key
608
+ end
609
+
610
+ # move machine configuration into guest object
611
+ config[:guest] = {
612
+ cpus: config.delete(:cpus),
613
+ memory_mb: config.delete(:memorymb),
614
+ cpu_kind: config.delete(:cputype)
615
+ }
616
+
617
+ # release machines should have no services or mounts
618
+ config.delete :services
619
+ config.delete :mounts
620
+
621
+ # override start command
622
+ config[:env] ||= {}
623
+ config[:env]['SERVER_COMMAND'] = 'bin/rails fly:release'
624
+
625
+ # fill in image
626
+ config[:image] = image
627
+
628
+ # start proxy, if necessary
629
+ endpoint = Fly::Machines::fly_api_hostname!
630
+
631
+ # perform release, if necessary
632
+ if release_task_defined?
633
+ say_status :fly, config[:env]['SERVER_COMMAND']
634
+ event, exit_code, machine = release(app, region: @region, config: config)
635
+ else
636
+ exit_code = 0
637
+ end
638
+
639
+ if exit_code == 0
640
+ # use terraform apply to deploy
641
+ ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
642
+ ENV['FLY_HTTP_ENDPOINT'] = endpoint if endpoint
643
+ system "terraform apply -auto-approve -var=\"image_ref=#{image}\""
644
+ else
645
+ STDERR.puts 'Error performing release'
646
+ STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
647
+ STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
648
+ exit 1
649
+ end
650
+ end
651
+ end
652
+ end
@@ -0,0 +1,42 @@
1
+ $:.unshift File.expand_path('lib')
2
+ require 'fly-rails/actions'
3
+
4
+ require 'bundler'
5
+ require 'pp'
6
+
7
+ def check_git
8
+ return if Dir.exist? '/srv/fly-rails/lib'
9
+
10
+ spec = Bundler::Definition.build('Gemfile', nil, []).dependencies.
11
+ find {|spec| spec.name == 'fly-rails'}
12
+
13
+ if spec.git
14
+ if `which git`.empty? and File.exist? '/etc/debian_version'
15
+ system 'apt-get update'
16
+ system 'apt-get install -y git'
17
+ end
18
+
19
+ system `git clone --depth 1 #{spec.git} /srv/fly-rails`
20
+ exit 1 unless Dir.exist? '/srv/fly-rails/lib'
21
+ ENV['RUBYLIB'] = '/srv/fly-rails/lib'
22
+ exec "ruby -r fly-rails/deploy -e #{caller_locations(1,1)[0].label}"
23
+ end
24
+ end
25
+
26
+ def dump_config
27
+ action = Fly::Actions.new
28
+
29
+ config = {}
30
+ action.instance_variables.sort.each do |name|
31
+ config[name] = action.instance_variable_get(name)
32
+ end
33
+
34
+ File.open('/srv/config', 'w') {|file| PP.pp(config, file)}
35
+ end
36
+
37
+ def build_gems
38
+ check_git
39
+ dump_config
40
+
41
+ system 'rake -f lib/tasks/fly.rake fly:build_gems'
42
+ end