fly.io-rails 0.0.20 → 0.1.0

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: 76e2a638b08c3d778fbdac62956b4730c9436bd133c3cbaedc758f9fd79f7c08
4
- data.tar.gz: d1b6bf847c64e993f4b23d1e35fdfc47c94e91d40d3a50aab723c912efda9475
3
+ metadata.gz: 135b867385fc6527e5879d853de374af267a79a5517c78092253211121df544a
4
+ data.tar.gz: ed9c255fd3753e3f566a11eb3f8a79d455e41adb079174adffdb133944064e36
5
5
  SHA512:
6
- metadata.gz: 564d0bc23fa62c2d382ef078395e94bb4c2fb3771913265478d3e3492dfbca9f25cb0fce5b38dd5dcb9d973691fcea2cfc7341fcfc25e036e9abec930e54618f
7
- data.tar.gz: 5ddd57513b37cdaaa80ce946b67c4af3614541b71c4b3f9b71554b390fb52d7c8de412a69151d7a514d67bb89a33cf86e613c44f946d10cf2249d83e661ffdcf
6
+ metadata.gz: fcf3225e3bc2d499d3cb5ece10b7c074de6b0d65e1a1368fe83e3de16d8e4e42f0f08ae50b73f42409361a465ffdf3adf246b13aacefbbc476f41561384645fc
7
+ data.tar.gz: 1a71284951420ea1b311f85b6f84c65be623211e94827c153f86386bc62b71527e9c355baed1d23c00e27be7d3afe2220aa74725f82989f78afacfb186d0bfe4
@@ -0,0 +1,291 @@
1
+ require 'thor'
2
+ require 'active_support'
3
+ require 'active_support/core_ext/string/inflections'
4
+ require 'fly.io-rails/machines'
5
+ require 'fly.io-rails/utils'
6
+
7
+ module Fly
8
+ class Actions < Thor::Group
9
+ include Thor::Actions
10
+ include Thor::Base
11
+ include Thor::Shell
12
+ attr_accessor :options
13
+
14
+ def initialize(app = nil)
15
+ self.app = app if app
16
+
17
+ @ruby_version = RUBY_VERSION
18
+ @bundler_version = Bundler::VERSION
19
+ @node = File.exist? 'node_modules'
20
+ @yarn = File.exist? 'yarn.lock'
21
+ @node_version = @node ? `node --version`.chomp.sub(/^v/, '') : '16.17.0'
22
+ @org = Fly::Machines.org
23
+ @regions = []
24
+
25
+ @options = {}
26
+ @destination_stack = [Dir.pwd]
27
+ end
28
+
29
+ def app
30
+ return @app if @app
31
+ self.app = TOML.load_file('fly.toml')['app']
32
+ end
33
+
34
+ def app=(app)
35
+ @app = app
36
+ @appName = @app.gsub('-', '_').camelcase(:lower)
37
+ end
38
+
39
+ source_paths.push File::expand_path('../generators/templates', __dir__)
40
+
41
+ def generate_toml
42
+ app
43
+ template 'fly.toml.erb', 'fly.toml'
44
+ end
45
+
46
+ def generate_dockerfile
47
+ app
48
+ template 'Dockerfile.erb', 'Dockerfile'
49
+ end
50
+
51
+ def generate_dockerignore
52
+ app
53
+ template 'dockerignore.erb', '.dockerignore'
54
+ end
55
+
56
+ def generate_terraform
57
+ app
58
+ template 'main.tf.erb', 'main.tf'
59
+ end
60
+
61
+ def generate_raketask
62
+ app
63
+ template 'fly.rake.erb', 'lib/tasks/fly.rake'
64
+ end
65
+
66
+ def generate_all
67
+ generate_dockerfile
68
+ generate_dockerignore
69
+ generate_terraform
70
+ generate_raketask
71
+ end
72
+
73
+ def generate_ipv4
74
+ cmd = 'flyctl ips allocate-v4'
75
+ say_status :run, cmd
76
+ system cmd
77
+ end
78
+
79
+ def generate_ipv6
80
+ cmd = 'flyctl ips allocate-v6'
81
+ say_status :run, cmd
82
+ system cmd
83
+ end
84
+
85
+ def create_volume(app, region, size)
86
+ volume = "#{app.gsub('-', '_')}_volume"
87
+ volumes = JSON.parse(`flyctl volumes list --json`).
88
+ map {|volume| volume[' Name']}
89
+
90
+ unless volumes.include? volume
91
+ cmd = "flyctl volumes create #{volume} --app #{app} --region #{region} --size #{size}"
92
+ say_status :run, cmd
93
+ system cmd
94
+ end
95
+
96
+ volume
97
+ end
98
+
99
+ def create_postgres(app, org, region, vm_size, volume_size, cluster_size)
100
+ cmd = "fly postgres create --name #{app}-db --org #{org} --region #{region} --vm-size #{vm_size} --volume-size #{volume_size} --initial-cluster-size #{cluster_size}"
101
+ say_status :run, cmd
102
+ output = FlyIoRails::Utils.tee(cmd)
103
+ output[%r{postgres://\S+}]
104
+ end
105
+
106
+ def release(app, config)
107
+ start = Fly::Machines.create_start_machine(app, config: config)
108
+ machine = start[:id]
109
+
110
+ if !machine
111
+ STDERR.puts 'Error starting release machine'
112
+ PP.pp start, STDERR
113
+ exit 1
114
+ end
115
+
116
+ # wait for release to copmlete
117
+ status = nil
118
+ 5.times do
119
+ status = Fly::Machines.wait_for_machine app, machine,
120
+ timeout: 60, status: 'stopped'
121
+ return machine if status[:ok]
122
+ end
123
+
124
+ STDERR.puts status.to_json
125
+ exit 1
126
+ end
127
+
128
+ def deploy(app, image)
129
+ regions = JSON.parse(`flyctl regions list --json`)['Regions'].
130
+ map {|region| region['Code']} rescue []
131
+ region = regions.first || 'iad'
132
+
133
+ secrets = JSON.parse(`fly secrets list --json`).
134
+ map {|secret| secret["Name"]}
135
+
136
+ config = {
137
+ region: region,
138
+ app: app,
139
+ name: "#{app}-machine",
140
+ image: image,
141
+ guest: {
142
+ cpus: 1,
143
+ cpu_kind: "shared",
144
+ memory_mb: 256,
145
+ },
146
+ services: [
147
+ {
148
+ ports: [
149
+ {port: 443, handlers: ["tls", "http"]},
150
+ {port: 80, handlers: ["http"]}
151
+ ],
152
+ protocol: "tcp",
153
+ internal_port: 8080
154
+ }
155
+ ]
156
+ }
157
+
158
+ database = YAML.load_file('config/database.yml').
159
+ dig('production', 'adapter') rescue nil
160
+ cable = YAML.load_file('config/cable.yml').
161
+ dig('production', 'adapter') rescue nil
162
+
163
+ if database == 'sqlite3'
164
+ volume = create_volume(app, region, 3)
165
+
166
+ config[:mounts] = [
167
+ { volume: volume, path: '/mnt/volume' }
168
+ ]
169
+
170
+ config[:env] = {
171
+ "DATABASE_URL" => "sqlite3:///mnt/volume/production.sqlite3"
172
+ }
173
+ elsif database == 'postgresql' and not secrets.include? 'DATABASE_URL'
174
+ secret = create_postgres(app, @org, region, 'shared-cpu-1x', 1, 1)
175
+
176
+ cmd = "fly secrets set DATABASE_URL=#{secret}"
177
+ say_status :run, cmd
178
+ system cmd
179
+ end
180
+
181
+ # build config for release machine, overriding server command
182
+ release_config = config.dup
183
+ release_config.delete :services
184
+ release_config.delete :mounts
185
+ release_config[:env] = { 'SERVER_COMMAND' => 'bin/rails fly:release' }
186
+
187
+ # perform release
188
+ say_status :fly, release_config[:env]['SERVER_COMMAND']
189
+ machine = release(app, release_config)
190
+ Fly::Machines.delete_machine app, machine if machine
191
+
192
+ # start proxy, if necessary
193
+ endpoint = Fly::Machines::fly_api_hostname!
194
+
195
+ # start app
196
+ say_status :fly, "start #{app}"
197
+ start = Fly::Machines.create_start_machine(app, config: config)
198
+ machine = start[:id]
199
+
200
+ if !machine
201
+ STDERR.puts 'Error starting application'
202
+ PP.pp start, STDERR
203
+ exit 1
204
+ end
205
+
206
+ 5.times do
207
+ status = Fly::Machines.wait_for_machine app, machine,
208
+ timeout: 60, status: 'started'
209
+ return if status[:ok]
210
+ end
211
+
212
+ STDERR.puts 'Timeout waiting for application to start'
213
+ end
214
+
215
+ def terraform(app, image)
216
+ # update main.tf with the image name
217
+ tf = IO.read('main.tf')
218
+ tf[/^\s*image\s*=\s*"(.*?)"/, 1] = image.strip
219
+ IO.write 'main.tf', tf
220
+
221
+ # find first machine in terraform config file
222
+ machines = Fly::HCL.parse(IO.read('main.tf')).find {|block|
223
+ block.keys.first == :resource and
224
+ block.values.first.keys.first == 'fly_machine'}
225
+
226
+ # extract HCL configuration for the machine
227
+ config = machines.values.first.values.first.values.first
228
+
229
+ # delete HCL specific configuration items
230
+ %i(services for_each region app name depends_on).each do |key|
231
+ config.delete key
232
+ end
233
+
234
+ # move machine configuration into guest object
235
+ config[:guest] = {
236
+ cpus: config.delete(:cpus),
237
+ memory_mb: config.delete(:memorymb),
238
+ cpu_kind: config.delete(:cputype)
239
+ }
240
+
241
+ # release machines should have no services or mounts
242
+ config.delete :services
243
+ config.delete :mounts
244
+
245
+ # override start command
246
+ config[:env] ||= {}
247
+ config[:env]['SERVER_COMMAND'] = 'bin/rails fly:release'
248
+
249
+ # start proxy, if necessary
250
+ endpoint = Fly::Machines::fly_api_hostname!
251
+
252
+ # start release machine
253
+ STDERR.puts "--> #{config[:env]['SERVER_COMMAND']}"
254
+ start = Fly::Machines.create_start_machine(app, config: config)
255
+ machine = start[:id]
256
+
257
+ if !machine
258
+ STDERR.puts 'Error starting release machine'
259
+ PP.pp start, STDERR
260
+ exit 1
261
+ end
262
+
263
+ # wait for release to copmlete
264
+ event = nil
265
+ 90.times do
266
+ sleep 1
267
+ status = Fly::Machines.get_a_machine app, machine
268
+ event = status[:events]&.first
269
+ break if event && event[:type] == 'exit'
270
+ end
271
+
272
+ # extract exit code
273
+ exit_code = event.dig(:request, :exit_event, :exit_code)
274
+
275
+ if exit_code == 0
276
+ # delete release machine
277
+ Fly::Machines.delete_machine app, machine
278
+
279
+ # use terraform apply to deploy
280
+ ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
281
+ ENV['FLY_HTTP_ENDPOINT'] = endpoint if endpoint
282
+ system 'terraform apply -auto-approve'
283
+ else
284
+ STDERR.puts 'Error performing release'
285
+ STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
286
+ STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
287
+ exit 1
288
+ end
289
+ end
290
+ end
291
+ end
@@ -98,9 +98,9 @@ module Fly
98
98
  post "/v1/apps/#{app}/machines", options
99
99
  end
100
100
 
101
- # wait_for_machine_to_start 'user-functions', '73d8d46dbee589'
102
- def self.wait_for_machine_to_start app, machine, timeout=60
103
- get "/v1/apps/#{app}/machines/#{machine}/wait?timeout=#{timeout}"
101
+ # wait_for_machine 'user-functions', '73d8d46dbee589'
102
+ def self.wait_for_machine app, machine, options = {timeout:60, status: 'started'}
103
+ get "/v1/apps/#{app}/machines/#{machine}/wait?#{options.to_query}"
104
104
  end
105
105
 
106
106
  # get_a_machine machine 'user-functions', '73d8d46dbee589'
@@ -1,3 +1,3 @@
1
1
  module Fly_io
2
- VERSION = '0.0.20'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'open3'
2
- require 'fly.io-rails/machines'
2
+ require 'fly.io-rails/actions'
3
3
 
4
- class TerraformGenerator < Rails::Generators::Base
4
+ class Fly::Generators::TerraformGenerator < Rails::Generators::Base
5
5
  include FlyIoRails::Utils
6
6
 
7
7
  class_option :name, type: :string, required: false
@@ -30,18 +30,8 @@ class TerraformGenerator < Rails::Generators::Base
30
30
  @regions = options[:regions].flatten
31
31
  end
32
32
 
33
- @ruby_version = RUBY_VERSION
34
- @bundler_version = Bundler::VERSION
35
- @node = File.exist? 'node_modules'
36
- @yarn = File.exist? 'yarn.lock'
37
- @node_version = @node ? `node --version`.chomp.sub(/^v/, '') : '16.17.0'
38
- @appName = @app.gsub('-', '_').camelcase(:lower)
39
- @org = Fly::Machines.org
40
-
41
- template 'Dockerfile.erb', 'Dockerfile'
42
- template 'dockerignore.erb', '.dockerignore'
43
- template 'main.tf.erb', 'main.tf'
44
- template 'fly.rake.erb', 'lib/tasks/fly.rake'
33
+ action = Fly::Actions.new(@app)
34
+ action.generate_all
45
35
 
46
36
  credentials = nil
47
37
  if File.exist? 'config/credentials/production.key'
@@ -60,4 +50,3 @@ class TerraformGenerator < Rails::Generators::Base
60
50
  tee 'terraform init'
61
51
  end
62
52
  end
63
-
data/lib/tasks/fly.rake CHANGED
@@ -1,90 +1,45 @@
1
1
  require 'fly.io-rails/machines'
2
2
  require 'fly.io-rails/hcl'
3
+ require 'fly.io-rails/actions'
4
+ require 'toml'
3
5
 
4
6
  namespace :fly do
5
7
  desc 'Deploy fly application'
6
8
  task :deploy do
7
- # build and push an image
8
- out = FlyIoRails::Utils.tee 'fly deploy --build-only --push'
9
- image = out[/image:\s+(.*)/, 1]
10
-
11
- exit 1 unless image
12
-
13
- # update main.tf with the image name
14
- tf = IO.read('main.tf')
15
- tf[/^\s*image\s*=\s*"(.*?)"/, 1] = image.strip
16
- IO.write 'main.tf', tf
9
+ include FlyIoRails::Utils
17
10
 
18
- # find first machine in terraform config file
19
- machines = Fly::HCL.parse(IO.read('main.tf')).find {|block|
20
- block.keys.first == :resource and
21
- block.values.first.keys.first == 'fly_machine'}
22
-
23
- # extract HCL configuration for the machine
24
- config = machines.values.first.values.first.values.first
11
+ # Get app name, creating one if necessary
12
+ if File.exist? 'fly.toml'
13
+ app = TOML.load_file('fly.toml')['app']
14
+ else
15
+ output = FlyIoRails::Utils.tee(
16
+ "flyctl apps create --generate-name --org personal --machines"
17
+ )
25
18
 
26
- # extract fly application name
27
- app = config[:app]
19
+ exit 1 unless output =~ /^New app created: /
28
20
 
29
- # delete HCL specific configuration items
30
- %i(services for_each region app name depends_on).each do |key|
31
- config.delete key
21
+ @app = app = output.split.last
32
22
  end
33
23
 
34
- # move machine configuration into guest object
35
- config[:guest] = {
36
- cpus: config.delete(:cpus),
37
- memory_mb: config.delete(:memorymb),
38
- cpu_kind: config.delete(:cputype)
39
- }
40
-
41
- # release machines should have no services or mounts
42
- config.delete :services
43
- config.delete :mounts
44
-
45
- # override start command
46
- config[:env] ||= {}
47
- config[:env]['SERVER_COMMAND'] = 'bin/rails fly:release'
24
+ # ensure fly.toml and Dockerfile are present
25
+ action = Fly::Actions.new(app)
26
+ action.generate_toml if @app
27
+ action.generate_dockerfile unless File.exist? 'Dockerfile'
28
+ action.generate_dockerignore unless File.exist? '.dockerignore'
29
+ action.generate_raketask unless File.exist? 'lib/tasks/fly.rake'
48
30
 
49
- # start proxy, if necessary
50
- endpoint = Fly::Machines::fly_api_hostname!
51
-
52
- # start release machine
53
- STDERR.puts "--> #{config[:env]['SERVER_COMMAND']}"
54
- start = Fly::Machines.create_start_machine(app, config: config)
55
- machine = start[:id]
56
-
57
- if !machine
58
- STDERR.puts 'Error starting release machine'
59
- PP.pp start, STDERR
60
- exit 1
61
- end
62
-
63
- # wait for release to copmlete
64
- event = nil
65
- 90.times do
66
- sleep 1
67
- status = Fly::Machines.get_a_machine app, machine
68
- event = status[:events]&.first
69
- break if event && event[:type] == 'exit'
70
- end
31
+ # build and push an image
32
+ out = FlyIoRails::Utils.tee 'fly deploy --build-only --push'
33
+ image = out[/image:\s+(.*)/, 1].strip
71
34
 
72
- # extract exit code
73
- exit_code = event.dig(:request, :exit_event, :exit_code)
74
-
75
- if exit_code == 0
76
- # delete release machine
77
- Fly::Machines.delete_machine app, machine
35
+ exit 1 unless image
78
36
 
79
- # use terraform apply to deploy
80
- ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
81
- ENV['FLY_HTTP_ENDPOINT'] = endpoint if endpoint
82
- system 'terraform apply -auto-approve'
37
+ if File.exist? 'main.tf'
38
+ action.terraform(app, image)
83
39
  else
84
- STDERR.puts 'Error performing release'
85
- STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
86
- STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
87
- exit 1
40
+ action.generate_ipv4 if @app
41
+ action.generate_ipv6 if @app
42
+ action.deploy(app, image)
88
43
  end
89
44
  end
90
45
  end
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.0.20
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-22 00:00:00.000000000 Z
11
+ date: 2022-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fly-ruby
@@ -50,6 +50,7 @@ files:
50
50
  - Rakefile
51
51
  - exe/flyctl
52
52
  - lib/fly.io-rails.rb
53
+ - lib/fly.io-rails/actions.rb
53
54
  - lib/fly.io-rails/generators.rb
54
55
  - lib/fly.io-rails/hcl.rb
55
56
  - lib/fly.io-rails/machines.rb