fly.io-rails 0.1.1-aarch64-linux → 0.1.3-aarch64-linux
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 +4 -4
- data/lib/fly.io-rails/actions.rb +179 -126
- data/lib/fly.io-rails/dsl.rb +79 -0
- data/lib/fly.io-rails/scanner.rb +26 -0
- data/lib/fly.io-rails/utils.rb +26 -8
- data/lib/fly.io-rails/version.rb +1 -1
- data/lib/generators/fly/app_generator.rb +30 -0
- data/lib/generators/fly/terraform_generator.rb +30 -0
- data/lib/generators/templates/fly.rake.erb +12 -0
- data/lib/generators/templates/fly.rb.erb +25 -0
- data/lib/generators/templates/fly.toml.erb +53 -1
- data/lib/generators/templates/main.tf.erb +9 -1
- data/lib/generators/templates/patches/action_cable.rb +20 -0
- data/lib/tasks/fly.rake +3 -8
- metadata +8 -3
- data/lib/generators/terraform_generator.rb +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f41938cae3b641c62ba20f5f9d96f71c8412d37ea5375486a74e391134e359f
|
4
|
+
data.tar.gz: 36475f27a01dd21b781c7c2ca0e7075147f21e9139916c889f430ed51fdff066
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0944e97fce0219230fcdd51c43c70917e37695a362bedb508b1b7a3296892931ddde0c659e6366616fc1451398452aaf0d3e4faacafa8ec73a0c6fc044d6acb
|
7
|
+
data.tar.gz: 934e65fdf6387696783331f41f6eceaf6073f1ba1c4683e5c61b0371682a904e410916f750626ba9e44efaaec5f7e9a16ead1d53a2dd909c2b966d1ef62f5779
|
data/lib/fly.io-rails/actions.rb
CHANGED
@@ -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
|
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
|
-
|
43
|
-
|
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
|
-
|
48
|
-
template 'Dockerfile.erb', 'Dockerfile'
|
75
|
+
app_template 'Dockerfile.erb', 'Dockerfile'
|
49
76
|
end
|
50
77
|
|
51
78
|
def generate_dockerignore
|
52
|
-
|
53
|
-
template 'dockerignore.erb', '.dockerignore'
|
79
|
+
app_template 'dockerignore.erb', '.dockerignore'
|
54
80
|
end
|
55
81
|
|
56
82
|
def generate_terraform
|
57
|
-
|
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
|
-
|
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
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
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
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
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
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
213
|
-
|
214
|
-
|
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
|
-
|
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
|
-
#
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
#
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
246
|
-
|
293
|
+
config[:env] = {
|
294
|
+
"DATABASE_URL" => "sqlite3:///mnt/volume/production.sqlite3"
|
295
|
+
}
|
296
|
+
end
|
247
297
|
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
319
|
+
status = Fly::Machines.wait_for_machine app, machine,
|
267
320
|
timeout: 60, status: 'started'
|
268
|
-
|
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
|
-
|
283
|
-
|
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
|
-
|
343
|
+
config.delete key
|
291
344
|
end
|
292
345
|
|
293
346
|
# move machine configuration into guest object
|
294
347
|
config[:guest] = {
|
295
|
-
|
296
|
-
|
297
|
-
|
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
|
-
|
318
|
-
|
319
|
-
|
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
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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
|
-
|
336
|
-
|
388
|
+
# delete release machine
|
389
|
+
Fly::Machines.delete_machine app, machine
|
337
390
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
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
|
data/lib/fly.io-rails/utils.rb
CHANGED
@@ -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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
data/lib/fly.io-rails/version.rb
CHANGED
@@ -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 = "
|
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
|
-
|
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
|
-
|
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]
|
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.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: aarch64-linux
|
6
6
|
authors:
|
7
7
|
- Sam Ruby
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-09-
|
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/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/
|
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
|