fly.io-rails 0.0.8-x64-mingw32 → 0.0.9-x64-mingw32

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: 51cda4c6db485a0353af506f6832e5c0865cf7e93cd37f4af3bffbb36f9961ce
4
- data.tar.gz: 1a378871a6e08b9aa66e88afa41fdc240b359564e8443b81811e0d03cd84c69a
3
+ metadata.gz: 1b5bb5da64268060f271756bb097f2bdde3879ab963b77f303616b661afa2f71
4
+ data.tar.gz: '0108eb3bb6a924d52e7ee03f66deb126005f1d50b09320910649eb8ef312a2b0'
5
5
  SHA512:
6
- metadata.gz: e3233ace9bb288ba700478546dfa2c42b627f3f19ef25bcc149174ae4c6dc1c4511c27bbcf749ac36abc52e53f9dad815223e2ab0001de01a7a46b56982ec3da
7
- data.tar.gz: 92f28e2010b5bca3a698f843f949f57889a977660e9278f2124ac75614478c1cc226e4652e484b7554547a9174a9623ca9e02df3f51adf24cfa144cbe465008a
6
+ metadata.gz: d079a398a6c9bb1a3cb3dbc66be2d5859756050673081ba11ba24eeb1ae63b7fdcc35b0d8daeefdfaa18387039c7d6f9999d0f9930b1e9287b88da24a4d0e2bd
7
+ data.tar.gz: f7e1032d3183a7ebb54f28a4e0de8bcfa464aa8d63140ac62d064ce95fcefaa8a0b7a3d0c2ca1b892a36fe29b21efce0ef11c1c8e7386898a0a78653b994a00e
@@ -0,0 +1,99 @@
1
+ require 'strscan'
2
+
3
+ module Fly
4
+ # a very liberal HCL scanner
5
+ module HCL
6
+ def self.parse(string)
7
+ result = []
8
+ name = nil
9
+ stack = []
10
+ block = {}
11
+ cursor = block
12
+ result.push block
13
+
14
+ hcl = StringScanner.new(string)
15
+ until hcl.eos?
16
+ hcl.scan(%r{\s*(\#.*|//.*|/\*[\S\s]*?\*/)*})
17
+
18
+ if hcl.scan(/[a-zA-Z]\S*|\d[.\d]*|"[^"]*"/)
19
+ if cursor.is_a? Array
20
+ cursor.push token(hcl.matched)
21
+ elsif name == nil
22
+ name = token(hcl.matched)
23
+ else
24
+ hash = {}
25
+ cursor[name] = hash
26
+ name = token(hcl.matched)
27
+ cursor = hash
28
+ end
29
+ elsif hcl.scan(/=/)
30
+ hcl.scan(/\s*/)
31
+ if hcl.scan(/\[/)
32
+ list = []
33
+ cursor[name] = list
34
+ name = nil
35
+ stack.push cursor
36
+ cursor = list
37
+ elsif hcl.scan(/\{/)
38
+ hash = {}
39
+ cursor[name] = hash
40
+ name = nil
41
+ stack.push cursor
42
+ cursor = hash
43
+ elsif hcl.scan(/.*/)
44
+ cursor[name] = token(hcl.matched)
45
+ name = nil
46
+ end
47
+ elsif hcl.scan(/\{/)
48
+ hash = {}
49
+ if cursor.is_a? Array
50
+ cursor << hash
51
+ else
52
+ cursor[name] = hash
53
+ end
54
+ name = nil
55
+ stack.push cursor
56
+ cursor = hash
57
+ elsif hcl.scan(/\[/)
58
+ list = []
59
+ stack.push cursor
60
+ cursor = list
61
+ elsif hcl.scan(/\}|\]/)
62
+ cursor = stack.pop
63
+
64
+ if stack.empty?
65
+ block = {}
66
+ cursor = block
67
+ result.push block
68
+ end
69
+ elsif hcl.scan(/[,=:]/)
70
+ nil
71
+ elsif hcl.scan(/.*/)
72
+ unless hcl.matched.empty?
73
+ STDERR.puts "unexpected input: #{hcl.matched.inspect}"
74
+ end
75
+ end
76
+ end
77
+
78
+ result.pop if result.last.empty?
79
+ result
80
+ end
81
+
82
+ private
83
+ def self.token(match)
84
+ if match =~ /^\d/
85
+ if match =~ /^\d+$/
86
+ match.to_i
87
+ else
88
+ match.to_f
89
+ end
90
+ elsif match =~ /^\w+$/
91
+ match.to_sym
92
+ elsif match =~ /^"(.*)"$/
93
+ $1
94
+ else
95
+ match
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,192 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module Fly
5
+ # Thin wrapper over https://fly.io/docs/reference/machines/
6
+ #
7
+ # *** WARNING ***
8
+ #
9
+ # No validation or escaping is done by this code. It is assuming that
10
+ # the caller is trusted and does pass through unsanitized user input.
11
+ #
12
+ module Machines
13
+ @@api_token = nil
14
+ @@fly_api_hostname = nil
15
+
16
+ # determine fly api hostname. Returns nil if no proxy is running
17
+ def self.fly_api_hostname
18
+ return @@fly_api_hostname if @@fly_api_hostname
19
+
20
+ Net::HTTP.get URI('http://_api.internal:4280')
21
+ @@fly_api_hostname = '_api.internal:4280'
22
+ rescue
23
+ begin
24
+ Net::HTTP.get URI('http://127.0.0.1:4280')
25
+ @@fly_api_hostname = '127.0.0.1:4280'
26
+ rescue
27
+ nil
28
+ end
29
+ end
30
+
31
+ # determine fly api hostname. Starts proxy if necessary
32
+ def self.fly_api_hostname!
33
+ hostname = fly_api_hostname
34
+ return hostname if hostname
35
+
36
+ org = 'personal'
37
+
38
+ if File.exist? 'fly.toml'
39
+ require 'toml'
40
+ app = TOML.load_file('fly.toml')['app']
41
+
42
+ apps = JSON.parse(`flyctl list apps --json`) rescue []
43
+
44
+ apps.each do |info|
45
+ org = info['Organization'] if info['ID'] == app
46
+ end
47
+ end
48
+
49
+ pid = fork { exec "flyctl machines api-proxy --org #{org}" }
50
+ at_exit { Process.kill "INT", pid }
51
+
52
+ # wait up to 12.7 seconds for the proxy to start
53
+ wait = 0.1
54
+ 6.times do
55
+ sleep wait
56
+ begin
57
+ Net::HTTP.get URI('http://127.0.0.1:4280')
58
+ @@fly_api_hostname = '127.0.0.1:4280'
59
+ break
60
+ rescue
61
+ wait *= 2
62
+ end
63
+ end
64
+
65
+ @@fy_api_hostname
66
+ end
67
+
68
+ # create_fly_application app_name: 'user-functions', org_slug: 'personal'
69
+ def self.create_fly_application options
70
+ post '/v1/apps', options
71
+ end
72
+
73
+ # get_application_details 'user-functions'
74
+ def self.get_application_details app
75
+ get "/v1/apps/#{app}"
76
+ end
77
+
78
+ # create_start_machine 'user-functions', name: 'quirky_machine', config: {
79
+ # image: 'flyio/fastify-functions',
80
+ # env: {'APP_ENV' => 'production'},
81
+ # services: [
82
+ # {
83
+ # ports: [
84
+ # {port: 443, handlers: ['tls', 'http']},
85
+ # {port: 80, handlers: ['http']}
86
+ # ],
87
+ # protocol: 'tcp',
88
+ # internal_protocol: 'tcp',
89
+ # }
90
+ # ]
91
+ # }
92
+ def self.create_start_machine app, options
93
+ post "/v1/apps/#{app}/machines", options
94
+ end
95
+
96
+ # wait_for_machine_to_start 'user-functions', '73d8d46dbee589'
97
+ def self.wait_for_machine_to_start app, machine, timeout=60
98
+ get "/v1/apps/#{app}/machines/#{machine}/wait?timeout=#{timeout}"
99
+ end
100
+
101
+ # get_a_machine machine 'user-functions', '73d8d46dbee589'
102
+ def self.get_a_machine app, machine
103
+ get "/v1/apps/#{app}/machines/#{machine}"
104
+ end
105
+
106
+ # update_a_machine 'user-functions', '73d8d46dbee589', config: {
107
+ # image: 'flyio/fastify-functions',
108
+ # guest: { memory_mb: 512, cpus: 2 }
109
+ # }
110
+ def self.update_a_machine app, machine, options
111
+ post "/v1/apps/#{app}/machines/#{machine}", options
112
+ end
113
+
114
+ # stop_machine machine 'user-functions', '73d8d46dbee589'
115
+ def self.stop_machine app, machine
116
+ post "/v1/apps/#{app}/machines/#{machine}/stop"
117
+ end
118
+
119
+ # start_machine machine 'user-functions', '73d8d46dbee589'
120
+ def self.start_machine app, machine
121
+ post "/v1/apps/#{app}/machines/#{machine}/stop"
122
+ end
123
+
124
+ # delete_machine machine 'user-functions', '73d8d46dbee589'
125
+ def self.delete_machine app, machine
126
+ delete "/v1/apps/#{app}/machines/#{machine}"
127
+ end
128
+
129
+ # list_machines machine 'user-functions'
130
+ def self.list_machines app, machine
131
+ get "/v1/apps/#{app}/machines"
132
+ end
133
+
134
+ # delete_application 'user-functions'
135
+ def self.delete_application app, force=false
136
+ delete "/v1/apps/#{app}?force=#{force}"
137
+ end
138
+
139
+ # generic get
140
+ def self.get(path)
141
+ api(path) {|uri| request = Net::HTTP::Get.new(uri) }
142
+ end
143
+
144
+ # generic post
145
+ def self.post(path, hash=nil)
146
+ api(path) do |uri|
147
+ request = Net::HTTP::Post.new(uri)
148
+ request.body = hash.to_json if hash
149
+ request
150
+ end
151
+ end
152
+
153
+ # generic delete
154
+ def self.delete(path)
155
+ api(path) {|uri| request = Net::HTTP::Delete.new(uri) }
156
+ end
157
+
158
+ # common processing for all APIs
159
+ def self.api(path, &make_request)
160
+ uri = URI("http://#{fly_api_hostname}#{path}")
161
+ http = Net::HTTP.new(uri.host, uri.port)
162
+
163
+ request = make_request.call(uri.request_uri)
164
+
165
+ @@api_token ||= `fly auth token`.chomp
166
+ headers = {
167
+ "Authorization" => "Bearer #{@@api_token}",
168
+ "Content-Type" => "application/json",
169
+ "Accept" => "application/json"
170
+ }
171
+ headers.each {|header, value| request[header] = value}
172
+
173
+ response = http.request(request)
174
+
175
+ if response.is_a? Net::HTTPSuccess
176
+ JSON.parse response.body, symbolize_names: true
177
+ else
178
+ body = response.body
179
+ begin
180
+ error = JSON.parse(body)
181
+ rescue
182
+ error = {body: body}
183
+ end
184
+
185
+ error[:status] = response.code
186
+ error[:message] = response.message
187
+
188
+ error
189
+ end
190
+ end
191
+ end
192
+ end
@@ -1,3 +1,3 @@
1
1
  module Fly_io
2
- VERSION = '0.0.8'
2
+ VERSION = '0.0.9'
3
3
  end
data/lib/tasks/fly.rake CHANGED
@@ -1,16 +1,84 @@
1
+ require 'fly.io-rails/machines'
2
+ require 'fly.io-rails/hcl'
3
+
1
4
  namespace :fly do
2
5
  desc 'Deploy fly application'
3
6
  task :deploy do
7
+ # build and push an image
4
8
  out = FlyIoRails::Utils.tee 'fly deploy --build-only --push'
5
9
  image = out[/image:\s+(.*)/, 1]
6
10
 
7
- if image
8
- tf = IO.read('main.tf')
9
- tf[/^\s*image\s*=\s*"(.*?)"/, 1] = image.strip
10
- IO.write 'main.tf', tf
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
17
+
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
25
+
26
+ # extract fly application name
27
+ app = config[:app]
28
+
29
+ # delete HCL specific configuration items
30
+ %i(services for_each region app name depends_on).each do |key|
31
+ config.delete key
32
+ end
33
+
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]['SERVER_COMMAND'] = 'bin/rails fly:release'
47
+
48
+ # start release machine
49
+ start = Fly::Machines.create_start_machine(app, config: config)
50
+ machine = start[:id]
51
+
52
+ if !machine
53
+ STDERR.puts 'Error starting release machine'
54
+ PP.pp start, STDERR
55
+ exit 1
56
+ end
57
+
58
+ # wait for release to copmlete
59
+ event = nil
60
+ 90.times do
61
+ sleep 1
62
+ status = Fly::Machines.get_a_machine app, machine
63
+ event = status[:events]&.first
64
+ break if event && event[:type] == 'exit'
65
+ end
66
+
67
+ # extract exit code
68
+ exit_code = event.dig(:request, :exit_event, :exit_code)
69
+
70
+ if exit_code == 0
71
+ # delete release machine
72
+ Fly::Machines.delete_machine app, machine
11
73
 
74
+ # use terraform apply to deploy
12
75
  ENV['FLY_API_TOKEN'] = `flyctl auth token`.chomp
13
76
  system 'terraform apply -auto-approve'
77
+ else
78
+ STDERR.puts 'Error performing release'
79
+ STDERR.puts (exit_code ? {exit_code: exit_code} : event).inspect
80
+ STDERR.puts "run 'flyctl logs --instance #{machine}' for more information"
81
+ exit 1
14
82
  end
15
83
  end
16
84
  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.8
4
+ version: 0.0.9
5
5
  platform: x64-mingw32
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-17 00:00:00.000000000 Z
11
+ date: 2022-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fly-ruby
@@ -39,6 +39,8 @@ files:
39
39
  - exe/x64-mingw32/wintun.dll
40
40
  - lib/fly.io-rails.rb
41
41
  - lib/fly.io-rails/generators.rb
42
+ - lib/fly.io-rails/hcl.rb
43
+ - lib/fly.io-rails/machines.rb
42
44
  - lib/fly.io-rails/platforms.rb
43
45
  - lib/fly.io-rails/utils.rb
44
46
  - lib/fly.io-rails/version.rb