fly.io-rails 0.0.21 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f61848b29215cf3f5b5517b0d60638b3b45994a1cc7372c3bb749e232848bfbc
4
- data.tar.gz: d44a9762a24e43be85fa83bb979efcaa5dd1c5f218cd24351d121ee65062e207
3
+ metadata.gz: 135b867385fc6527e5879d853de374af267a79a5517c78092253211121df544a
4
+ data.tar.gz: ed9c255fd3753e3f566a11eb3f8a79d455e41adb079174adffdb133944064e36
5
5
  SHA512:
6
- metadata.gz: 28e0865362aecfd714ffa45fa0a975fe20241b1da9eed606c519ebacc30168e02e3fa8e11a571271b6538a43580829f931e8387e89212d8aeeb8fc506d8f5c43
7
- data.tar.gz: bebf94184a390f514e652679cc8ca14bceaf97d7a5cefe70ef6734ada09e3b5706dd0343f8241adfcc73b015ca8cef289bccdafcf5e91c55a8a3d3f07db695d4
6
+ metadata.gz: fcf3225e3bc2d499d3cb5ece10b7c074de6b0d65e1a1368fe83e3de16d8e4e42f0f08ae50b73f42409361a465ffdf3adf246b13aacefbbc476f41561384645fc
7
+ data.tar.gz: 1a71284951420ea1b311f85b6f84c65be623211e94827c153f86386bc62b71527e9c355baed1d23c00e27be7d3afe2220aa74725f82989f78afacfb186d0bfe4
@@ -2,11 +2,13 @@ require 'thor'
2
2
  require 'active_support'
3
3
  require 'active_support/core_ext/string/inflections'
4
4
  require 'fly.io-rails/machines'
5
+ require 'fly.io-rails/utils'
5
6
 
6
7
  module Fly
7
8
  class Actions < Thor::Group
8
9
  include Thor::Actions
9
10
  include Thor::Base
11
+ include Thor::Shell
10
12
  attr_accessor :options
11
13
 
12
14
  def initialize(app = nil)
@@ -36,6 +38,11 @@ module Fly
36
38
 
37
39
  source_paths.push File::expand_path('../generators/templates', __dir__)
38
40
 
41
+ def generate_toml
42
+ app
43
+ template 'fly.toml.erb', 'fly.toml'
44
+ end
45
+
39
46
  def generate_dockerfile
40
47
  app
41
48
  template 'Dockerfile.erb', 'Dockerfile'
@@ -62,5 +69,223 @@ module Fly
62
69
  generate_terraform
63
70
  generate_raketask
64
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
65
290
  end
66
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.21'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'open3'
2
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
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fly.io-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby