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 +4 -4
- data/lib/fly.io-rails/hcl.rb +99 -0
- data/lib/fly.io-rails/machines.rb +192 -0
- data/lib/fly.io-rails/version.rb +1 -1
- data/lib/tasks/fly.rake +72 -4
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b5bb5da64268060f271756bb097f2bdde3879ab963b77f303616b661afa2f71
|
4
|
+
data.tar.gz: '0108eb3bb6a924d52e7ee03f66deb126005f1d50b09320910649eb8ef312a2b0'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/fly.io-rails/version.rb
CHANGED
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
|
-
|
8
|
-
|
9
|
-
|
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
|
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.
|
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-
|
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
|