fly.io-rails 0.0.8-x86_64-darwin → 0.0.9-x86_64-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/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: d622a5b51f10f261b059832d1f4b5413a513bb01bd6f8228dd5bebbf35f4a0b1
|
4
|
+
data.tar.gz: fc30f4acd2b22efeb09bb62713829c915a91db94d4e9b0300b807032c8c4ca19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe60d5d89639fdb5ad0126d0ba4786bd6147eb41fe224fdab9f8e7cba848002aa74e2afb90d0c1c8949eecf928d9d4f04fa628647609fdab133c3d8681bd1b34
|
7
|
+
data.tar.gz: 8e36510e35a2a182e7b9e82965c2b7900c3ce3d4e024866c7f75579c78ae71cfc1377a73f01abdfacb69898c180e545c053b3536e662db1b2f05faadf8ee4e9d
|
@@ -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: x86_64-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-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fly-ruby
|
@@ -38,6 +38,8 @@ files:
|
|
38
38
|
- exe/x86_64-darwin/flyctl
|
39
39
|
- lib/fly.io-rails.rb
|
40
40
|
- lib/fly.io-rails/generators.rb
|
41
|
+
- lib/fly.io-rails/hcl.rb
|
42
|
+
- lib/fly.io-rails/machines.rb
|
41
43
|
- lib/fly.io-rails/platforms.rb
|
42
44
|
- lib/fly.io-rails/utils.rb
|
43
45
|
- lib/fly.io-rails/version.rb
|