fly-rails 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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