fly.io-rails 0.1.1-x86_64-darwin → 0.1.3-x86_64-darwin

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ecdac6ea9c27372c9f51d70add0f343940a1bfca16c26501e9ceb15fb989ee3
4
- data.tar.gz: a0c29f6f12b131be05f716e21c9efa0b91427db9cbbda8f8625eaccea12f8b4e
3
+ metadata.gz: fa1f124c8df9c737e5097b1105a32aa02d1381c3cd65fd76ce4ffbbb39e69b93
4
+ data.tar.gz: 88366a8b758ffd61e0f42dd38d2bfd7e4fa658ea1180e540c3065345d0c30f94
5
5
  SHA512:
6
- metadata.gz: d301a4cb2ca9f9fe85962feb5785abbfdcc91226bb5a2d4049bae90b0744fb5e12b12512092800c302bd70d2822a3d6dd9f6f145f75c34166a2bee8163ef6d72
7
- data.tar.gz: 879f6e285cf4180d72f174926b368d20967e46f1d32154398cba2e1a245a444e05badfc6d7c4ee2cae42f32392968517b389b20542f97f2c8b80a7510677e07a
6
+ metadata.gz: 758bc4077bec4154d105ace2272184f842e113d94b93553b77c26c7927c124b072ed4e1cf73f0066d8edcf4606aa931f086485947ccec296e67e8d6df0c770ef
7
+ data.tar.gz: d45b5d5168640072014031b2180fc821311ceeee65d2793d621634d581edfcad8d39dadffefca66f059d65e0da335fb1d54c9ff3c238b7fe3f9a4726c8f8cffa
@@ -1,18 +1,23 @@
1
+ require 'open3'
1
2
  require 'thor'
3
+ require 'toml'
2
4
  require 'active_support'
3
5
  require 'active_support/core_ext/string/inflections'
4
6
  require 'fly.io-rails/machines'
5
7
  require 'fly.io-rails/utils'
8
+ require 'fly.io-rails/dsl'
9
+ require 'fly.io-rails/scanner'
6
10
 
7
11
  module Fly
8
12
  class Actions < Thor::Group
9
13
  include Thor::Actions
10
14
  include Thor::Base
11
15
  include Thor::Shell
16
+ include Fly::Scanner
12
17
  attr_accessor :options
13
18
 
14
- def initialize(app = nil)
15
- self.app = app if app
19
+ def initialize(app, regions = nil)
20
+ self.app = app
16
21
 
17
22
  @ruby_version = RUBY_VERSION
18
23
  @bundler_version = Bundler::VERSION
@@ -20,10 +25,25 @@ module Fly
20
25
  @yarn = File.exist? 'yarn.lock'
21
26
  @node_version = @node ? `node --version`.chomp.sub(/^v/, '') : '16.17.0'
22
27
  @org = Fly::Machines.org
23
- @regions = []
24
28
 
25
29
  @options = {}
26
30
  @destination_stack = [Dir.pwd]
31
+
32
+ if !regions or regions.empty?
33
+ @regions = JSON.parse(`flyctl regions list --json --app #{app}`)['Regions'].
34
+ map {|region| region['Code']} rescue []
35
+ else
36
+ @regions = regions
37
+ end
38
+
39
+ @region = @regions.first || 'iad'
40
+
41
+ @config = Fly::DSL::Config.new
42
+ if File.exist? 'config/fly.rb'
43
+ @config.instance_eval IO.read('config/fly.rb')
44
+ end
45
+
46
+ scan_rails_app
27
47
  end
28
48
 
29
49
  def app
@@ -31,6 +51,11 @@ module Fly
31
51
  self.app = TOML.load_file('fly.toml')['app']
32
52
  end
33
53
 
54
+ def app_template template_file, destination
55
+ app
56
+ template template_file, destination
57
+ end
58
+
34
59
  def app=(app)
35
60
  @app = app
36
61
  @appName = @app.gsub('-', '_').camelcase(:lower)
@@ -39,35 +64,51 @@ module Fly
39
64
  source_paths.push File::expand_path('../generators/templates', __dir__)
40
65
 
41
66
  def generate_toml
42
- app
43
- template 'fly.toml.erb', 'fly.toml'
67
+ app_template 'fly.toml.erb', 'fly.toml'
68
+ end
69
+
70
+ def generate_fly_config
71
+ app_template 'fly.rb.erb', 'config/fly.rb'
44
72
  end
45
73
 
46
74
  def generate_dockerfile
47
- app
48
- template 'Dockerfile.erb', 'Dockerfile'
75
+ app_template 'Dockerfile.erb', 'Dockerfile'
49
76
  end
50
77
 
51
78
  def generate_dockerignore
52
- app
53
- template 'dockerignore.erb', '.dockerignore'
79
+ app_template 'dockerignore.erb', '.dockerignore'
54
80
  end
55
81
 
56
82
  def generate_terraform
57
- app
58
- template 'main.tf.erb', 'main.tf'
83
+ app_template 'main.tf.erb', 'main.tf'
59
84
  end
60
85
 
61
86
  def generate_raketask
62
- app
63
- template 'fly.rake.erb', 'lib/tasks/fly.rake'
87
+ app_template 'fly.rake.erb', 'lib/tasks/fly.rake'
64
88
  end
65
89
 
66
- def generate_all
67
- generate_dockerfile
68
- generate_dockerignore
69
- generate_terraform
70
- generate_raketask
90
+ def generate_key
91
+ credentials = nil
92
+ if File.exist? 'config/credentials/production.key'
93
+ credentials = 'config/credentials/production.key'
94
+ elsif File.exist? 'config/master.key'
95
+ credentials = 'config/master.key'
96
+ end
97
+
98
+ if credentials
99
+ say_status :run, "flyctl secrets set --stage RAILS_MASTER_KEY from #{credentials}"
100
+ system "flyctl secrets set --stage RAILS_MASTER_KEY=#{IO.read(credentials).chomp}"
101
+ puts
102
+ end
103
+
104
+ ENV['FLY_API_TOKEN'] = `flyctl auth token`
105
+ end
106
+
107
+ def generate_patches
108
+ if @redis_cable and not File.exist? 'config/initializers/action_cable.rb'
109
+ app
110
+ template 'patches/action_cable.rb', 'config/initializers/action_cable.rb'
111
+ end
71
112
  end
72
113
 
73
114
  def generate_ipv4
@@ -114,7 +155,7 @@ module Fly
114
155
  end
115
156
 
116
157
  # create a new redis
117
- cmd = "flyctl redis create --org #{org} --name #{app}-redis --region #{region} --no-replicas #{eviction} --plan Free"
158
+ cmd = "flyctl redis create --org #{org} --name #{app}-redis --region #{region} --no-replicas #{eviction} --plan #{@config.redis.plan}"
118
159
  say_status :run, cmd
119
160
  output = FlyIoRails::Utils.tee(cmd)
120
161
  output[%r{redis://\S+}]
@@ -125,9 +166,9 @@ module Fly
125
166
  machine = start[:id]
126
167
 
127
168
  if !machine
128
- STDERR.puts 'Error starting release machine'
129
- PP.pp start, STDERR
130
- exit 1
169
+ STDERR.puts 'Error starting release machine'
170
+ PP.pp start, STDERR
171
+ exit 1
131
172
  end
132
173
 
133
174
  status = Fly::Machines.wait_for_machine app, machine,
@@ -144,63 +185,31 @@ module Fly
144
185
  # wait for release to copmlete
145
186
  event = nil
146
187
  90.times do
147
- sleep 1
148
- status = Fly::Machines.get_a_machine app, machine
149
- event = status[:events]&.first
150
- return machine if event && event[:type] == 'exit'
188
+ sleep 1
189
+ status = Fly::Machines.get_a_machine app, machine
190
+ event = status[:events]&.first
191
+ return machine if event && event[:type] == 'exit'
151
192
  end
152
193
 
153
194
  STDERR.puts event.to_json
154
195
  exit 1
155
196
  end
156
197
 
157
- def deploy(app, image)
158
- regions = JSON.parse(`flyctl regions list --json`)['Regions'].
159
- map {|region| region['Code']} rescue []
160
- region = regions.first || 'iad'
161
-
198
+ def launch(app)
162
199
  secrets = JSON.parse(`flyctl secrets list --json`).
163
200
  map {|secret| secret["Name"]}
164
201
 
165
- config = {
166
- region: region,
167
- app: app,
168
- name: "#{app}-machine",
169
- image: image,
170
- guest: {
171
- cpus: 1,
172
- cpu_kind: "shared",
173
- memory_mb: 256,
174
- },
175
- services: [
176
- {
177
- ports: [
178
- {port: 443, handlers: ["tls", "http"]},
179
- {port: 80, handlers: ["http"]}
180
- ],
181
- protocol: "tcp",
182
- internal_port: 8080
183
- }
184
- ]
185
- }
186
-
187
- database = YAML.load_file('config/database.yml').
188
- dig('production', 'adapter') rescue nil
189
- cable = YAML.load_file('config/cable.yml').
190
- dig('production', 'adapter') rescue nil
191
-
192
- if database == 'sqlite3'
193
- volume = create_volume(app, region, 3)
202
+ unless secrets.include? 'RAILS_MASTER_KEY'
203
+ generate_key
204
+ end
194
205
 
195
- config[:mounts] = [
196
- { volume: volume, path: '/mnt/volume' }
197
- ]
198
-
199
- config[:env] = {
200
- "DATABASE_URL" => "sqlite3:///mnt/volume/production.sqlite3"
201
- }
202
- elsif database == 'postgresql' and not secrets.include? 'DATABASE_URL'
203
- secret = create_postgres(app, @org, region, 'shared-cpu-1x', 1, 1)
206
+ if @sqlite3
207
+ @volume = create_volume(app, @region, @config.sqlite3.size)
208
+ elsif @postgresql and not secrets.include? 'DATABASE_URL'
209
+ secret = create_postgres(app, @org, @region,
210
+ @config.postgres.vm_size,
211
+ @config.postgres.volume_size,
212
+ @config.postgres.initial_cluster_size)
204
213
 
205
214
  if secret
206
215
  cmd = "flyctl secrets set --stage DATABASE_URL=#{secret}"
@@ -209,20 +218,11 @@ module Fly
209
218
  end
210
219
  end
211
220
 
212
- # Enable redis if mentioned as a cache provider or a cable provider.
213
- # Set eviction policy to true if a cache provider, else false.
214
- eviction = nil
221
+ if @redis and not secrets.include? 'REDIS_URL'
222
+ # Set eviction policy to true if a cache provider, else false.
223
+ eviction = @redis_cache ? '--enable-eviction' : '--disable-eviction'
215
224
 
216
- if (YAML.load_file('config/cable.yml').dig('production', 'adapter') rescue false)
217
- eviction = '--disable-eviction'
218
- end
219
-
220
- if (IO.read('config/environments/production.rb') =~ /redis/i rescue false)
221
- eviction = '--enable-eviction'
222
- end
223
-
224
- if eviction and not secrets.include? 'REDIS_URL'
225
- secret = create_redis(app, @org, region, eviction)
225
+ secret = create_redis(app, @org, @region, eviction)
226
226
 
227
227
  if secret
228
228
  cmd = "flyctl secrets set --stage REDIS_URL=#{secret}"
@@ -230,25 +230,78 @@ module Fly
230
230
  system cmd
231
231
  end
232
232
  end
233
+ end
234
+
235
+ def deploy(app, image)
236
+ launch(app)
237
+
238
+ # default config
239
+ config = {
240
+ region: @region,
241
+ app: app,
242
+ name: "#{app}-machine",
243
+ image: image,
244
+ guest: {
245
+ cpus: @config.machine.cpus,
246
+ cpu_kind: @config.machine.cpu_kind,
247
+ memory_mb: @config.machine.memory_mb
248
+ },
249
+ services: [
250
+ {
251
+ ports: [
252
+ {port: 443, handlers: ["tls", "http"]},
253
+ {port: 80, handlers: ["http"]}
254
+ ],
255
+ protocol: "tcp",
256
+ internal_port: 8080
257
+ }
258
+ ]
259
+ }
233
260
 
234
- # build config for release machine, overriding server command
235
- release_config = config.dup
236
- release_config.delete :services
237
- release_config.delete :mounts
238
- release_config[:env] = { 'SERVER_COMMAND' => 'bin/rails fly:release' }
261
+ # only run release step if there is a non-empty release task in fly.rake
262
+ if (IO.read('lib/tasks/fly.rake') rescue '') =~ /^\s*task[ \t]*+:?release"?[ \t]*\S/
263
+ # build config for release machine, overriding server command
264
+ release_config = config.dup
265
+ release_config.delete :services
266
+ release_config.delete :mounts
267
+ release_config[:env] = { 'SERVER_COMMAND' => 'bin/rails fly:release' }
268
+
269
+ # perform release
270
+ say_status :fly, release_config[:env]['SERVER_COMMAND']
271
+ machine = release(app, release_config)
272
+ Fly::Machines.delete_machine app, machine if machine
273
+
274
+ # start proxy, if necessary
275
+ endpoint = Fly::Machines::fly_api_hostname!
276
+
277
+ # stop previous instances - list will fail on first run
278
+ stdout, stderr, status = Open3.capture3('fly machines list --json')
279
+ unless stdout.empty?
280
+ JSON.parse(stdout).each do |list|
281
+ next if list['id'] == machine
282
+ system "fly machines remove --force #{list['id']}"
283
+ end
284
+ end
285
+ end
239
286
 
240
- # perform release
241
- say_status :fly, release_config[:env]['SERVER_COMMAND']
242
- machine = release(app, release_config)
243
- Fly::Machines.delete_machine app, machine if machine
287
+ # configure sqlite3 (can be overridden by fly.toml)
288
+ if @sqlite3
289
+ config[:mounts] = [
290
+ { volume: @volume, path: '/mnt/volume' }
291
+ ]
244
292
 
245
- # start proxy, if necessary
246
- endpoint = Fly::Machines::fly_api_hostname!
293
+ config[:env] = {
294
+ "DATABASE_URL" => "sqlite3:///mnt/volume/production.sqlite3"
295
+ }
296
+ end
247
297
 
248
- # stop previous instances
249
- JSON.parse(`fly machines list --json`).each do |list|
250
- next if list['id'] == machine
251
- system "fly machines remove --force #{list['id']}"
298
+ # process toml overrides
299
+ toml = (TOML.load_file('fly.toml') rescue {})
300
+ config[:env] = toml['env'] if toml['env']
301
+ config[:services] = toml['services'] if toml['services']
302
+ if toml['mounts']
303
+ mounts = toml['mounts']
304
+ config[:mounts] = [ { volume: mounts['source'], path: mounts['destination'] } ]
252
305
  end
253
306
 
254
307
  # start app
@@ -257,15 +310,15 @@ module Fly
257
310
  machine = start[:id]
258
311
 
259
312
  if !machine
260
- STDERR.puts 'Error starting application'
261
- PP.pp start, STDERR
262
- exit 1
313
+ STDERR.puts 'Error starting application'
314
+ PP.pp start, STDERR
315
+ exit 1
263
316
  end
264
317
 
265
318
  5.times do
266
- status = Fly::Machines.wait_for_machine app, machine,
319
+ status = Fly::Machines.wait_for_machine app, machine,
267
320
  timeout: 60, status: 'started'
268
- return if status[:ok]
321
+ return if status[:ok]
269
322
  end
270
323
 
271
324
  STDERR.puts 'Timeout waiting for application to start'
@@ -279,22 +332,22 @@ module Fly
279
332
 
280
333
  # find first machine in terraform config file
281
334
  machines = Fly::HCL.parse(IO.read('main.tf')).find {|block|
282
- block.keys.first == :resource and
283
- block.values.first.keys.first == 'fly_machine'}
335
+ block.keys.first == :resource and
336
+ block.values.first.keys.first == 'fly_machine'}
284
337
 
285
338
  # extract HCL configuration for the machine
286
339
  config = machines.values.first.values.first.values.first
287
340
 
288
341
  # delete HCL specific configuration items
289
342
  %i(services for_each region app name depends_on).each do |key|
290
- config.delete key
343
+ config.delete key
291
344
  end
292
345
 
293
346
  # move machine configuration into guest object
294
347
  config[:guest] = {
295
- cpus: config.delete(:cpus),
296
- memory_mb: config.delete(:memorymb),
297
- cpu_kind: config.delete(:cputype)
348
+ cpus: config.delete(:cpus),
349
+ memory_mb: config.delete(:memorymb),
350
+ cpu_kind: config.delete(:cputype)
298
351
  }
299
352
 
300
353
  # release machines should have no services or mounts
@@ -314,36 +367,36 @@ module Fly
314
367
  machine = start[:id]
315
368
 
316
369
  if !machine
317
- STDERR.puts 'Error starting release machine'
318
- PP.pp start, STDERR
319
- exit 1
370
+ STDERR.puts 'Error starting release machine'
371
+ PP.pp start, STDERR
372
+ exit 1
320
373
  end
321
374
 
322
375
  # wait for release to copmlete
323
376
  event = nil
324
377
  90.times do
325
- sleep 1
326
- status = Fly::Machines.get_a_machine app, machine
327
- event = status[:events]&.first
328
- break if event && event[:type] == 'exit'
378
+ sleep 1
379
+ status = Fly::Machines.get_a_machine app, machine
380
+ event = status[:events]&.first
381
+ break if event && event[:type] == 'exit'
329
382
  end
330
383
 
331
384
  # extract exit code
332
385
  exit_code = event.dig(:request, :exit_event, :exit_code)
333
-
386
+
334
387
  if exit_code == 0
335
- # delete release machine
336
- Fly::Machines.delete_machine app, machine
388
+ # delete release machine
389
+ Fly::Machines.delete_machine app, machine
337
390
 
338
- # use terraform apply to deploy
339
- ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
340
- ENV['FLY_HTTP_ENDPOINT'] = endpoint if endpoint
341
- system 'terraform apply -auto-approve'
391
+ # use terraform apply to deploy
392
+ ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
393
+ ENV['FLY_HTTP_ENDPOINT'] = endpoint if endpoint
394
+ system 'terraform apply -auto-approve'
342
395
  else
343
- STDERR.puts 'Error performing release'
344
- STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
345
- STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
346
- exit 1
396
+ STDERR.puts 'Error performing release'
397
+ STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
398
+ STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
399
+ exit 1
347
400
  end
348
401
  end
349
402
  end
@@ -0,0 +1,79 @@
1
+ module Fly
2
+ module DSL
3
+ class Base
4
+ def initialize
5
+ @value = {}
6
+ end
7
+
8
+ def self.option name, default=nil
9
+ @options ||= {}
10
+ @options[name] = default
11
+
12
+ define_method name do |*args|
13
+ if args.length == 1
14
+ @value[name] = args.first
15
+ elsif args.length > 1
16
+ raise ArgumentError.new("wrong number of arguments (given #{args.length}, expected 0..1)")
17
+ end
18
+
19
+ @value.include?(name) ? @value[name] : default
20
+ end
21
+ end
22
+
23
+ def self.options
24
+ @options ||= {}
25
+ end
26
+ end
27
+
28
+ #############################################################
29
+
30
+ class Machine < Base
31
+ option :cpus, 1
32
+ option :cpu_kind, 'shared'
33
+ option :memory_mb, 256
34
+ end
35
+
36
+ class Postgres < Base
37
+ option :vm_size, 'shared-cpu-1x'
38
+ option :volume_size, 1
39
+ option :initial_cluster_size, 1
40
+ end
41
+
42
+ class Redis < Base
43
+ option :plan, "Free"
44
+ end
45
+
46
+ class Sqlite3 < Base
47
+ option :size, 3
48
+ end
49
+
50
+ #############################################################
51
+
52
+ class Config
53
+ @@blocks = {}
54
+
55
+ def initialize
56
+ @config = {}
57
+ end
58
+
59
+ def self.block name, kind
60
+ @@blocks[name] = kind
61
+
62
+ define_method name do |&block|
63
+ @config[name] ||= kind.new
64
+ @config[name].instance_eval(&block) if block
65
+ @config[name]
66
+ end
67
+ end
68
+
69
+ def self.blocks
70
+ @@blocks
71
+ end
72
+
73
+ block :machine, Machine
74
+ block :postgres, Postgres
75
+ block :redis, Redis
76
+ block :sqlite3, Sqlite3
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,26 @@
1
+ module Fly
2
+ module Scanner
3
+ # scan for major features - things that if present will likely affect
4
+ # more than one artifact that is generated.
5
+ def scan_rails_app
6
+ database = YAML.load_file('config/database.yml').
7
+ dig('production', 'adapter') rescue nil
8
+
9
+ if database == 'sqlite3'
10
+ @sqlite3 = true
11
+ elsif database == 'postgresql'
12
+ @postgresql = true
13
+ end
14
+
15
+ if (YAML.load_file('config/cable.yml').dig('production', 'adapter') rescue false)
16
+ @redis_cable = true
17
+ end
18
+
19
+ if (IO.read('config/environments/production.rb') =~ /redis/i rescue false)
20
+ @redis_cache = true
21
+ end
22
+
23
+ @redis = @redis_cable || @redis_cache
24
+ end
25
+ end
26
+ end
@@ -4,7 +4,7 @@ module FlyIoRails
4
4
  module Utils
5
5
 
6
6
  def tee cmd
7
- say_status :run, cmd
7
+ say_status :run, cmd if defined? say_status
8
8
  FlyIoRails::Utils.tee cmd
9
9
  end
10
10
 
@@ -12,21 +12,39 @@ module FlyIoRails
12
12
  data = []
13
13
 
14
14
  begin
15
- PTY.spawn( cmd ) do |stdin, stdout, pid|
16
- begin
17
- # Do stuff with the output here. Just printing to show it works
18
- stdin.each do |line|
15
+ PTY.spawn( cmd ) do |stdin, stdout, pid|
16
+ begin
17
+ # Do stuff with the output here. Just printing to show it works
18
+ stdin.each do |line|
19
19
  print line
20
20
  data << line
21
21
  end
22
- rescue Errno::EIO
23
- end
24
- end
22
+ rescue Errno::EIO
23
+ end
24
+ end
25
25
  rescue PTY::ChildExited
26
26
  end
27
27
 
28
28
  data.join
29
29
  end
30
30
 
31
+ def create_app(name=nil, org='personal', regions=[])
32
+ cmd = if name
33
+ "flyctl apps create #{name.inspect} --org #{org.inspect} --machines"
34
+ else
35
+ "flyctl apps create --generate-name --org #{org.inspect} --machines"
36
+ end
37
+
38
+ output = tee cmd
39
+ exit 1 unless output =~ /^New app created: /
40
+
41
+ @app = output.split.last
42
+
43
+ unless regions.empty?
44
+ @regions = regions.flatten
45
+ end
46
+
47
+ @app
48
+ end
31
49
  end
32
50
  end
@@ -1,3 +1,3 @@
1
1
  module Fly_io
2
- VERSION = '0.1.1'
2
+ VERSION = '0.1.3'
3
3
  end
@@ -0,0 +1,30 @@
1
+ require 'fly.io-rails/actions'
2
+
3
+ module Fly::Generators
4
+ class AppGenerator < Rails::Generators::Base
5
+ include FlyIoRails::Utils
6
+
7
+ class_option :name, type: :string, required: false
8
+ class_option :org, type: :string, default: 'personal'
9
+ class_option :region, type: :array, repeatable: true, default: []
10
+
11
+ def generate_app
12
+ source_paths.push File.expand_path('../templates', __dir__)
13
+
14
+ create_app(options[:name], options[:org], options)
15
+
16
+ action = Fly::Actions.new(@app, options[:region])
17
+
18
+ action.generate_toml
19
+ action.generate_fly_config unless File.exist? 'config/fly.rb'
20
+ action.generate_dockerfile unless File.exist? 'Dockerfile'
21
+ action.generate_dockerignore unless File.exist? '.dockerignore'
22
+ action.generate_raketask unless File.exist? 'lib/tasks/fly.rake'
23
+ action.generate_patches
24
+ action.generate_ipv4
25
+ action.generate_ipv6
26
+
27
+ action.launch(@app)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'fly.io-rails/actions'
2
+
3
+ module Fly::Generators
4
+ class TerraformGenerator < Rails::Generators::Base
5
+ include FlyIoRails::Utils
6
+
7
+ class_option :name, type: :string, required: false
8
+ class_option :org, type: :string, default: 'personal'
9
+ class_option :region, type: :array, repeatable: true, default: []
10
+
11
+ def terraform
12
+ source_paths.push File.expand_path('../templates', __dir__)
13
+
14
+ create_app(options[:name], options[:org], options[:region])
15
+
16
+ action = Fly::Actions.new(@app, options[:region])
17
+
18
+ action.generate_toml
19
+ action.generate_dockerfile
20
+ action.generate_dockerignore
21
+ action.generate_terraform
22
+ action.generate_raketask
23
+ action.generate_patches
24
+
25
+ action.generate_key
26
+
27
+ tee 'terraform init'
28
+ end
29
+ end
30
+ end
@@ -10,14 +10,22 @@ namespace :fly do
10
10
  # - changes to the filesystem made here are DISCARDED
11
11
  # - full access to secrets, databases
12
12
  # - failures here prevent deployment
13
+ <%- if @sqlite3 -%>
14
+ task :release
15
+ <%- else -%>
13
16
  task :release => 'db:migrate'
17
+ <%- end -%>
14
18
 
15
19
  # SERVER step:
16
20
  # - changes to the filesystem made here are deployed
17
21
  # - full access to secrets, databases
18
22
  # - failures here result in VM being stated, shutdown, and rolled back
19
23
  # to last successful deploy (if any).
24
+ <%- if @sqlite3 -%>
25
+ task :server => [:swapfile, 'db:migrate'] do
26
+ <%- else -%>
20
27
  task :server => :swapfile do
28
+ <%- end -%>
21
29
  sh 'bin/rails server'
22
30
  end
23
31
 
@@ -26,7 +34,11 @@ namespace :fly do
26
34
  # - performance critical applications should scale memory to the
27
35
  # point where swap is rarely used. 'fly scale help' for details.
28
36
  # - disable by removing dependency on the :server task, thus:
37
+ <%- if @sqlite3 -%>
38
+ # task :server => 'db:migrate' do
39
+ <%- else -%>
29
40
  # task :server do
41
+ <%- end -%>
30
42
  task :swapfile do
31
43
  sh 'fallocate -l 512M /swapfile'
32
44
  sh 'chmod 0600 /swapfile'
@@ -0,0 +1,25 @@
1
+ machine do
2
+ cpus 1
3
+ cpu_kind 'shared'
4
+ memory_mb 256
5
+ end
6
+ <% if @sqlite3 -%>
7
+
8
+ sqlite3 do
9
+ size 3
10
+ end
11
+ <% end -%>
12
+ <% if @postgresql -%>
13
+
14
+ postgres do
15
+ vm_size 'shared-cpu-1x'
16
+ volume_size 1
17
+ initial_cluster_size 1
18
+ end
19
+ <% end -%>
20
+ <% if @redis -%>
21
+
22
+ redis do
23
+ plan "Free"
24
+ end
25
+ <% end -%>
@@ -1,4 +1,56 @@
1
1
  app = "<%= @app %>"
2
+ kill_signal = "SIGINT"
3
+ kill_timeout = 5
4
+ processes = []
5
+
6
+ [build]
7
+ [build.args]
8
+ BUILD_COMMAND = "bin/rails fly:build"
9
+ SERVER_COMMAND = "bin/rails fly:server"
2
10
 
3
11
  [deploy]
4
- release_command = "bundle exec rails fly:release"
12
+ release_command = "bin/rails fly:release"
13
+
14
+ [env]
15
+ PORT = "8080"
16
+ <% if @sqlite3 -%>
17
+ DATABASE_URL = "sqlite3:///mnt/volume/production.sqlite3"
18
+
19
+ [mounts]
20
+ source = <%= "#{app.gsub('-', '_')}_volume".inspect %>
21
+ destination = "/mnt/volume"
22
+ <% end -%>
23
+
24
+ [experimental]
25
+ allowed_public_ports = []
26
+ auto_rollback = true
27
+
28
+ [[services]]
29
+ http_checks = []
30
+ internal_port = 8080
31
+ processes = ["app"]
32
+ protocol = "tcp"
33
+ script_checks = []
34
+ [services.concurrency]
35
+ hard_limit = 25
36
+ soft_limit = 20
37
+ type = "connections"
38
+
39
+ [[services.ports]]
40
+ force_https = true
41
+ handlers = ["http"]
42
+ port = 80
43
+
44
+ [[services.ports]]
45
+ handlers = ["tls", "http"]
46
+ port = 443
47
+
48
+ [[services.tcp_checks]]
49
+ grace_period = "1s"
50
+ interval = "15s"
51
+ restart_limit = 0
52
+ timeout = "2s"
53
+
54
+ [[statics]]
55
+ guest_path = "/app/public"
56
+ url_prefix = "/"
@@ -27,7 +27,9 @@ resource "fly_ip" "<%= @appName %>Ipv6" {
27
27
  type = "v6"
28
28
  }
29
29
 
30
+ <% unless @sqlite3 -%>
30
31
  /* Uncomment this if you want a volume
32
+ <% end -%>
31
33
  resource "fly_volume" "<%= @appName %>Volume" {
32
34
  for_each = toset(<%= @regions.inspect %>)
33
35
  region = each.value
@@ -36,7 +38,9 @@ resource "fly_volume" "<%= @appName %>Volume" {
36
38
  app = <%= @app.inspect %>
37
39
  size = 3
38
40
  }
41
+ <% unless @sqlite3 -%>
39
42
  */
43
+ <% end -%>
40
44
 
41
45
  # Start a fly machine
42
46
  resource "fly_machine" "<%= @appName %>Machine" {
@@ -71,7 +75,7 @@ resource "fly_machine" "<%= @appName %>Machine" {
71
75
  }
72
76
  ]
73
77
 
74
- /* Uncomment this if you want sqlite3 on a volume
78
+ <%- if @sqlite3 -%>
75
79
  env = {
76
80
  DATABASE_URL = "sqlite3:///mnt/db/production.sqlite3"
77
81
  }
@@ -83,6 +87,10 @@ resource "fly_machine" "<%= @appName %>Machine" {
83
87
  }
84
88
  ]
85
89
 
90
+ depends_on = [fly_volume.<%= @appName %>Volume]
91
+ <%- else -%>
92
+ /* Uncomment this if you want a volume
86
93
  depends_on = [fly_volume.<%= @appName %>Volume]
87
94
  */
95
+ <%- end -%>
88
96
  }
@@ -0,0 +1,20 @@
1
+ # Restart Action Cable server on Redis connection failures.
2
+ # See: https://github.com/rails/rails/pull/45478
3
+ require 'action_cable/subscription_adapter/redis'
4
+
5
+ module ActionCableRedisListenerPatch
6
+ private
7
+
8
+ def ensure_listener_running
9
+ @thread ||= Thread.new do
10
+ Thread.current.abort_on_exception = true
11
+ conn = @adapter.redis_connection_for_subscriptions
12
+ listen conn
13
+ rescue ::Redis::BaseConnectionError
14
+ @thread = @raw_client = nil
15
+ ::ActionCable.server.restart
16
+ end
17
+ end
18
+ end
19
+
20
+ ActionCable::SubscriptionAdapter::Redis::Listener.prepend(ActionCableRedisListenerPatch)
data/lib/tasks/fly.rake CHANGED
@@ -12,25 +12,20 @@ namespace :fly do
12
12
  if File.exist? 'fly.toml'
13
13
  app = TOML.load_file('fly.toml')['app']
14
14
  else
15
- output = FlyIoRails::Utils.tee(
16
- "flyctl apps create --generate-name --org personal --machines"
17
- )
18
-
19
- exit 1 unless output =~ /^New app created: /
20
-
21
- @app = app = output.split.last
15
+ app = create_app
22
16
  end
23
17
 
24
18
  # ensure fly.toml and Dockerfile are present
25
19
  action = Fly::Actions.new(app)
26
20
  action.generate_toml if @app
21
+ action.generate_fly_config unless File.exist? 'config/fly.rb'
27
22
  action.generate_dockerfile unless File.exist? 'Dockerfile'
28
23
  action.generate_dockerignore unless File.exist? '.dockerignore'
29
24
  action.generate_raketask unless File.exist? 'lib/tasks/fly.rake'
30
25
 
31
26
  # build and push an image
32
27
  out = FlyIoRails::Utils.tee 'fly deploy --build-only --push'
33
- image = out[/image:\s+(.*)/, 1].strip
28
+ image = out[/image:\s+(.*)/, 1]&.strip
34
29
 
35
30
  exit 1 unless image
36
31
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fly.io-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: x86_64-darwin
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-24 00:00:00.000000000 Z
11
+ date: 2022-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fly-ruby
@@ -52,18 +52,23 @@ files:
52
52
  - exe/x86_64-darwin/flyctl
53
53
  - lib/fly.io-rails.rb
54
54
  - lib/fly.io-rails/actions.rb
55
+ - lib/fly.io-rails/dsl.rb
55
56
  - lib/fly.io-rails/generators.rb
56
57
  - lib/fly.io-rails/hcl.rb
57
58
  - lib/fly.io-rails/machines.rb
58
59
  - lib/fly.io-rails/platforms.rb
60
+ - lib/fly.io-rails/scanner.rb
59
61
  - lib/fly.io-rails/utils.rb
60
62
  - lib/fly.io-rails/version.rb
63
+ - lib/generators/fly/app_generator.rb
64
+ - lib/generators/fly/terraform_generator.rb
61
65
  - lib/generators/templates/Dockerfile.erb
62
66
  - lib/generators/templates/dockerignore.erb
63
67
  - lib/generators/templates/fly.rake.erb
68
+ - lib/generators/templates/fly.rb.erb
64
69
  - lib/generators/templates/fly.toml.erb
65
70
  - lib/generators/templates/main.tf.erb
66
- - lib/generators/terraform_generator.rb
71
+ - lib/generators/templates/patches/action_cable.rb
67
72
  - lib/tasks/fly.rake
68
73
  homepage: https://github.com/rubys/fly-io.rails
69
74
  licenses:
@@ -1,52 +0,0 @@
1
- require 'open3'
2
- require 'fly.io-rails/actions'
3
-
4
- class Fly::Generators::TerraformGenerator < Rails::Generators::Base
5
- include FlyIoRails::Utils
6
-
7
- class_option :name, type: :string, required: false
8
- class_option :org, type: :string, default: 'personal'
9
- class_option :region, type: :array, repeatable: true, default: []
10
-
11
- def terraform
12
- source_paths.push File.expand_path('./templates', __dir__)
13
-
14
- cmd = if options[:name]
15
- "flyctl apps create #{options[:name].inspect} --org #{options[:org].inspect}"
16
- else
17
- "flyctl apps create --generate-name --org #{options[:org].inspect}"
18
- end
19
-
20
- output = tee cmd
21
- exit 1 unless output =~ /^New app created: /
22
-
23
- @app = output.split.last
24
- template 'fly.toml.erb', 'fly.toml'
25
-
26
- if options[:region].empty?
27
- @regions = JSON.parse(`flyctl regions list --json`)['Regions'].
28
- map {|region| region['Code']}
29
- else
30
- @regions = options[:regions].flatten
31
- end
32
-
33
- action = Fly::Actions.new(@app)
34
- action.generate_all
35
-
36
- credentials = nil
37
- if File.exist? 'config/credentials/production.key'
38
- credentials = 'config/credentials/production.key'
39
- elsif File.exist? 'config/master.key'
40
- credentials = 'config/master.key'
41
- end
42
-
43
- if credentials
44
- say_status :run, "flyctl secrets set RAILS_MASTER_KEY from #{credentials}"
45
- system "flyctl secrets set RAILS_MASTER_KEY=#{IO.read(credentials).chomp}"
46
- puts
47
- end
48
-
49
- ENV['FLY_API_TOKEN'] = `flyctl auth token`
50
- tee 'terraform init'
51
- end
52
- end