fly.io-rails 0.1.1-arm64-darwin → 0.1.2-arm64-darwin
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/fly.io-rails/actions.rb +118 -84
- 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 +27 -8
- data/lib/fly.io-rails/version.rb +1 -1
- data/lib/generators/fly/app_generator.rb +29 -0
- data/lib/generators/fly/terraform_generator.rb +29 -0
- data/lib/generators/templates/fly.rake.erb +12 -0
- data/lib/generators/templates/fly.rb.erb +25 -0
- 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: 356701f60f98b167049abc2f37e2c85b184d07e84c47f7d6f031253dedbaa684
|
4
|
+
data.tar.gz: 6b830ae58c1c165b73b662caf5c27cf995147a9bd5c26a2f7a3a1e8a93a91666
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c58ce796bd1f9a9aa91f19cae3c6f1086642d025496ad1611b877cd75a2222a6c62c128857c3b75aecc7630eaa1bb78b64a9ef5a666344d1e68dfdcfc2b2bf32
|
7
|
+
data.tar.gz: '0627194c02f430f155f4b88ca4bd62fa3405462411eaa2211e4373d068444d4d00e41733d04d25b5e0e99e9d21449d902d5a7c3fdf85209b865817d103ecb5e3'
|
data/lib/fly.io-rails/actions.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
|
+
require 'open3'
|
1
2
|
require 'thor'
|
2
3
|
require 'active_support'
|
3
4
|
require 'active_support/core_ext/string/inflections'
|
4
5
|
require 'fly.io-rails/machines'
|
5
6
|
require 'fly.io-rails/utils'
|
7
|
+
require 'fly.io-rails/dsl'
|
8
|
+
require 'fly.io-rails/scanner'
|
6
9
|
|
7
10
|
module Fly
|
8
11
|
class Actions < Thor::Group
|
9
12
|
include Thor::Actions
|
10
13
|
include Thor::Base
|
11
14
|
include Thor::Shell
|
15
|
+
include Fly::Scanner
|
12
16
|
attr_accessor :options
|
13
17
|
|
14
|
-
def initialize(app = nil)
|
18
|
+
def initialize(app = nil, regions = nil)
|
15
19
|
self.app = app if app
|
16
20
|
|
17
21
|
@ruby_version = RUBY_VERSION
|
@@ -20,10 +24,25 @@ module Fly
|
|
20
24
|
@yarn = File.exist? 'yarn.lock'
|
21
25
|
@node_version = @node ? `node --version`.chomp.sub(/^v/, '') : '16.17.0'
|
22
26
|
@org = Fly::Machines.org
|
23
|
-
@regions = []
|
24
27
|
|
25
28
|
@options = {}
|
26
29
|
@destination_stack = [Dir.pwd]
|
30
|
+
|
31
|
+
if !regions or regions.empty?
|
32
|
+
@regions = JSON.parse(`flyctl regions list --json --app #{app}`)['Regions'].
|
33
|
+
map {|region| region['Code']} rescue []
|
34
|
+
else
|
35
|
+
@regions = regions
|
36
|
+
end
|
37
|
+
|
38
|
+
@region = @regions.first || 'iad'
|
39
|
+
|
40
|
+
@config = Fly::DSL::Config.new
|
41
|
+
if File.exist? 'config/fly.rb'
|
42
|
+
@config.instance_eval IO.read('config/fly.rb')
|
43
|
+
end
|
44
|
+
|
45
|
+
scan_rails_app
|
27
46
|
end
|
28
47
|
|
29
48
|
def app
|
@@ -43,6 +62,11 @@ module Fly
|
|
43
62
|
template 'fly.toml.erb', 'fly.toml'
|
44
63
|
end
|
45
64
|
|
65
|
+
def generate_fly_config
|
66
|
+
app
|
67
|
+
template 'fly.rb.erb', 'config/fly.rb'
|
68
|
+
end
|
69
|
+
|
46
70
|
def generate_dockerfile
|
47
71
|
app
|
48
72
|
template 'Dockerfile.erb', 'Dockerfile'
|
@@ -63,11 +87,28 @@ module Fly
|
|
63
87
|
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,10 +185,10 @@ 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
|
@@ -155,42 +196,38 @@ module Fly
|
|
155
196
|
end
|
156
197
|
|
157
198
|
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
199
|
|
162
200
|
secrets = JSON.parse(`flyctl secrets list --json`).
|
163
201
|
map {|secret| secret["Name"]}
|
164
202
|
|
165
203
|
config = {
|
166
|
-
region: region,
|
204
|
+
region: @region,
|
167
205
|
app: app,
|
168
206
|
name: "#{app}-machine",
|
169
207
|
image: image,
|
170
208
|
guest: {
|
171
|
-
cpus:
|
172
|
-
cpu_kind:
|
173
|
-
memory_mb:
|
209
|
+
cpus: @config.machine.cpus,
|
210
|
+
cpu_kind: @config.machine.cpu_kind,
|
211
|
+
memory_mb: @config.machine.memory_mb
|
174
212
|
},
|
175
213
|
services: [
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
214
|
+
{
|
215
|
+
ports: [
|
216
|
+
{port: 443, handlers: ["tls", "http"]},
|
217
|
+
{port: 80, handlers: ["http"]}
|
218
|
+
],
|
219
|
+
protocol: "tcp",
|
220
|
+
internal_port: 8080
|
221
|
+
}
|
184
222
|
]
|
185
223
|
}
|
186
224
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
dig('production', 'adapter') rescue nil
|
225
|
+
unless secrets.include? 'RAILS_MASTER_KEY'
|
226
|
+
generate_key
|
227
|
+
end
|
191
228
|
|
192
|
-
if
|
193
|
-
volume = create_volume(app, region,
|
229
|
+
if @sqlite3
|
230
|
+
volume = create_volume(app, @region, @config.sqlite3.size)
|
194
231
|
|
195
232
|
config[:mounts] = [
|
196
233
|
{ volume: volume, path: '/mnt/volume' }
|
@@ -199,8 +236,11 @@ module Fly
|
|
199
236
|
config[:env] = {
|
200
237
|
"DATABASE_URL" => "sqlite3:///mnt/volume/production.sqlite3"
|
201
238
|
}
|
202
|
-
elsif
|
203
|
-
secret = create_postgres(app, @org, region,
|
239
|
+
elsif @postgresql and not secrets.include? 'DATABASE_URL'
|
240
|
+
secret = create_postgres(app, @org, @region,
|
241
|
+
@config.postgres.vm_size,
|
242
|
+
@config.postgres.volume_size,
|
243
|
+
@config.postgres.initial_cluster_size)
|
204
244
|
|
205
245
|
if secret
|
206
246
|
cmd = "flyctl secrets set --stage DATABASE_URL=#{secret}"
|
@@ -209,20 +249,11 @@ module Fly
|
|
209
249
|
end
|
210
250
|
end
|
211
251
|
|
212
|
-
|
213
|
-
|
214
|
-
|
252
|
+
if @redis and not secrets.include? 'REDIS_URL'
|
253
|
+
# Set eviction policy to true if a cache provider, else false.
|
254
|
+
eviction = @redis_cache ? '--enable-eviction' : '--disable-eviction'
|
215
255
|
|
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)
|
256
|
+
secret = create_redis(app, @org, @region, eviction)
|
226
257
|
|
227
258
|
if secret
|
228
259
|
cmd = "flyctl secrets set --stage REDIS_URL=#{secret}"
|
@@ -245,10 +276,13 @@ module Fly
|
|
245
276
|
# start proxy, if necessary
|
246
277
|
endpoint = Fly::Machines::fly_api_hostname!
|
247
278
|
|
248
|
-
# stop previous instances
|
249
|
-
|
250
|
-
|
251
|
-
|
279
|
+
# stop previous instances - list will fail on first run
|
280
|
+
stdout, stderr, status = Open3.capture3('fly machines list --json')
|
281
|
+
unless stdout.empty?
|
282
|
+
JSON.parse(stdout).each do |list|
|
283
|
+
next if list['id'] == machine
|
284
|
+
system "fly machines remove --force #{list['id']}"
|
285
|
+
end
|
252
286
|
end
|
253
287
|
|
254
288
|
# start app
|
@@ -257,15 +291,15 @@ module Fly
|
|
257
291
|
machine = start[:id]
|
258
292
|
|
259
293
|
if !machine
|
260
|
-
|
261
|
-
|
262
|
-
|
294
|
+
STDERR.puts 'Error starting application'
|
295
|
+
PP.pp start, STDERR
|
296
|
+
exit 1
|
263
297
|
end
|
264
298
|
|
265
299
|
5.times do
|
266
|
-
|
300
|
+
status = Fly::Machines.wait_for_machine app, machine,
|
267
301
|
timeout: 60, status: 'started'
|
268
|
-
|
302
|
+
return if status[:ok]
|
269
303
|
end
|
270
304
|
|
271
305
|
STDERR.puts 'Timeout waiting for application to start'
|
@@ -279,22 +313,22 @@ module Fly
|
|
279
313
|
|
280
314
|
# find first machine in terraform config file
|
281
315
|
machines = Fly::HCL.parse(IO.read('main.tf')).find {|block|
|
282
|
-
|
283
|
-
|
316
|
+
block.keys.first == :resource and
|
317
|
+
block.values.first.keys.first == 'fly_machine'}
|
284
318
|
|
285
319
|
# extract HCL configuration for the machine
|
286
320
|
config = machines.values.first.values.first.values.first
|
287
321
|
|
288
322
|
# delete HCL specific configuration items
|
289
323
|
%i(services for_each region app name depends_on).each do |key|
|
290
|
-
|
324
|
+
config.delete key
|
291
325
|
end
|
292
326
|
|
293
327
|
# move machine configuration into guest object
|
294
328
|
config[:guest] = {
|
295
|
-
|
296
|
-
|
297
|
-
|
329
|
+
cpus: config.delete(:cpus),
|
330
|
+
memory_mb: config.delete(:memorymb),
|
331
|
+
cpu_kind: config.delete(:cputype)
|
298
332
|
}
|
299
333
|
|
300
334
|
# release machines should have no services or mounts
|
@@ -314,36 +348,36 @@ module Fly
|
|
314
348
|
machine = start[:id]
|
315
349
|
|
316
350
|
if !machine
|
317
|
-
|
318
|
-
|
319
|
-
|
351
|
+
STDERR.puts 'Error starting release machine'
|
352
|
+
PP.pp start, STDERR
|
353
|
+
exit 1
|
320
354
|
end
|
321
355
|
|
322
356
|
# wait for release to copmlete
|
323
357
|
event = nil
|
324
358
|
90.times do
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
359
|
+
sleep 1
|
360
|
+
status = Fly::Machines.get_a_machine app, machine
|
361
|
+
event = status[:events]&.first
|
362
|
+
break if event && event[:type] == 'exit'
|
329
363
|
end
|
330
364
|
|
331
365
|
# extract exit code
|
332
366
|
exit_code = event.dig(:request, :exit_event, :exit_code)
|
333
|
-
|
367
|
+
|
334
368
|
if exit_code == 0
|
335
|
-
|
336
|
-
|
369
|
+
# delete release machine
|
370
|
+
Fly::Machines.delete_machine app, machine
|
337
371
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
372
|
+
# use terraform apply to deploy
|
373
|
+
ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
|
374
|
+
ENV['FLY_HTTP_ENDPOINT'] = endpoint if endpoint
|
375
|
+
system 'terraform apply -auto-approve'
|
342
376
|
else
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
377
|
+
STDERR.puts 'Error performing release'
|
378
|
+
STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
|
379
|
+
STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
|
380
|
+
exit 1
|
347
381
|
end
|
348
382
|
end
|
349
383
|
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,40 @@ 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
|
+
template 'fly.toml.erb', 'fly.toml' if defined? template # rake tasks are on their own
|
43
|
+
|
44
|
+
unless regions.empty?
|
45
|
+
@regions = regions.flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
@app
|
49
|
+
end
|
31
50
|
end
|
32
51
|
end
|
data/lib/fly.io-rails/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
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_fly_config unless File.exist? 'config/fly.rb'
|
19
|
+
action.generate_dockerfile unless File.exist? 'Dockerfile'
|
20
|
+
action.generate_dockerignore unless File.exist? '.dockerignore'
|
21
|
+
action.generate_raketask unless File.exist? 'lib/tasks/fly.rake'
|
22
|
+
action.generate_patches
|
23
|
+
action.generate_ipv4
|
24
|
+
action.generate_ipv6
|
25
|
+
action.generate_key
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
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_dockerfile
|
19
|
+
action.generate_dockerignore
|
20
|
+
action.generate_terraform
|
21
|
+
action.generate_raketask
|
22
|
+
action.generate_patches
|
23
|
+
|
24
|
+
action.generate_key
|
25
|
+
|
26
|
+
tee 'terraform init'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
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 -%>
|
@@ -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.2
|
5
5
|
platform: arm64-darwin
|
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
|