jets-api 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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +11 -0
- data/Rakefile +4 -0
- data/lib/jets/api/agree.rb +76 -0
- data/lib/jets/api/autoloader.rb +26 -0
- data/lib/jets/api/base.rb +25 -0
- data/lib/jets/api/client.rb +8 -0
- data/lib/jets/api/config.rb +62 -0
- data/lib/jets/api/core.rb +122 -0
- data/lib/jets/api/gems/check.rb +230 -0
- data/lib/jets/api/gems/exist.rb +13 -0
- data/lib/jets/api/gems/extract/base.rb +64 -0
- data/lib/jets/api/gems/extract/gem.rb +117 -0
- data/lib/jets/api/gems/registered.rb +13 -0
- data/lib/jets/api/gems/report.rb +23 -0
- data/lib/jets/api/gems.rb +30 -0
- data/lib/jets/api/project.rb +10 -0
- data/lib/jets/api/release.rb +20 -0
- data/lib/jets/api/stack.rb +15 -0
- data/lib/jets/api/version.rb +5 -0
- data/lib/jets/api.rb +38 -0
- data/lib/jets-api.rb +1 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ba93fd08108eb85d4cae6dfa1a37f2702213e111d35d09762c8dc30148f0b9c2
|
4
|
+
data.tar.gz: 528480b2fa8c3b4635295ac717778bf4d4fb628b97e8fef805df3b946d1029ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 51b3904fcfbce1d669a45a7692dbbc4c48652ea296890b8ff74a8328a0cdadd2c489fca79fff760d12a99c94c3787c06bc331bd45ad0dcee1289504326a41656
|
7
|
+
data.tar.gz: 335178b7d6b2af40a25db803dd9b5aa0974c537f4354776f1c753bcb61d5143e18d9dbf4eeaedc4ec67f0dd7ae447ba585ad605389a7ee4620f45bd90c4ab2f1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) Tung Nguyen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Jets API
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/jets-api)
|
4
|
+
|
5
|
+
[](https://www.boltops.com)
|
6
|
+
|
7
|
+
[](https://learn.boltops.com)
|
8
|
+
|
9
|
+
Jets API Client Library for Jets Ruby Serverless Framework.
|
10
|
+
|
11
|
+
This library works with Jets Pro API to help provide additional features to the [Jets Serverless Framework](https://rubyonjets.com).
|
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Jets::Api
|
2
|
+
class Agree
|
3
|
+
def initialize
|
4
|
+
@agree_file = "#{ENV['HOME']}/.jets/agree"
|
5
|
+
end
|
6
|
+
|
7
|
+
# Only prompts if hasnt prompted before and saved a ~/.jets/agree file
|
8
|
+
def prompt
|
9
|
+
return if bypass_prompt
|
10
|
+
return if File.exist?(@agree_file) && File.mtime(@agree_file) > Time.parse("2021-04-12")
|
11
|
+
|
12
|
+
puts <<~EOL
|
13
|
+
Jets uses precompiled binary gems from the Jets Api service to
|
14
|
+
provide a user-friendly developer experience. Jets Api
|
15
|
+
rate limits free gem download requests daily. You can upgrade to a paid plan
|
16
|
+
for unlimited gem download requests. Open Source projects may also qualify
|
17
|
+
for a free unlimited plan. More info:
|
18
|
+
|
19
|
+
https://pro.rubyonjets.com/rate-limits
|
20
|
+
|
21
|
+
Reporting gems to Jets Api also allows it to build new gems typically
|
22
|
+
within a few minutes. So if you run into missing gems, you can try deploying
|
23
|
+
again after a few minutes. Non-reported gems may take several days or longer.
|
24
|
+
Jets Api only collects the info it needs to run the service.
|
25
|
+
More info: https://pro.rubyonjets.com/privacy
|
26
|
+
This message will only appear once on this machine.
|
27
|
+
|
28
|
+
You can also automatically skip this message by setting:
|
29
|
+
JETS_AGREE=yes or JETS_AGREE=no
|
30
|
+
|
31
|
+
Is it okay to send your gem data to Jets Api? (Y/n)?
|
32
|
+
EOL
|
33
|
+
|
34
|
+
answer = $stdin.gets.strip
|
35
|
+
value = answer =~ /y/i ? 'yes' : 'no'
|
36
|
+
|
37
|
+
write_file(value)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allow user to bypass prompt with JETS_AGREE=1 JETS_AGREE=yes etc
|
41
|
+
# Useful for CI/CD pipelines.
|
42
|
+
def bypass_prompt
|
43
|
+
agree = ENV['JETS_AGREE']
|
44
|
+
return false unless agree
|
45
|
+
|
46
|
+
if %w[1 yes true].include?(agree.downcase)
|
47
|
+
write_file('yes')
|
48
|
+
else
|
49
|
+
write_file('no')
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def yes?
|
56
|
+
File.exist?(@agree_file) && IO.read(@agree_file).strip == 'yes'
|
57
|
+
end
|
58
|
+
|
59
|
+
def no?
|
60
|
+
File.exist?(@agree_file) && IO.read(@agree_file).strip == 'no'
|
61
|
+
end
|
62
|
+
|
63
|
+
def yes!
|
64
|
+
write_file("yes")
|
65
|
+
end
|
66
|
+
|
67
|
+
def no!
|
68
|
+
write_file("no")
|
69
|
+
end
|
70
|
+
|
71
|
+
def write_file(content)
|
72
|
+
FileUtils.mkdir_p(File.dirname(@agree_file))
|
73
|
+
IO.write(@agree_file, content)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "zeitwerk"
|
2
|
+
|
3
|
+
# Make sure to define module Jets in case Jets::Api is loaded before jets
|
4
|
+
module Jets
|
5
|
+
module Api
|
6
|
+
class Autoloader
|
7
|
+
class Inflector < Zeitwerk::Inflector
|
8
|
+
def camelize(basename, _abspath)
|
9
|
+
map = { cli: "CLI", version: "VERSION" }
|
10
|
+
map[basename.to_sym] || super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def setup
|
16
|
+
loader = Zeitwerk::Loader.new
|
17
|
+
loader.inflector = Inflector.new
|
18
|
+
lib = File.expand_path('../..', __dir__)
|
19
|
+
loader.push_dir(lib) # lib
|
20
|
+
loader.ignore("#{lib}/jets-api.rb")
|
21
|
+
loader.setup
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Jets::Api
|
2
|
+
class Base
|
3
|
+
include Jets::Api
|
4
|
+
delegate :global_params, to: :class
|
5
|
+
class << self
|
6
|
+
include Jets::Api
|
7
|
+
|
8
|
+
def global_params
|
9
|
+
Jets.boot
|
10
|
+
params = {}
|
11
|
+
params[:jets_env] = Jets.env.to_s
|
12
|
+
params[:jets_extra] = Jets.extra.to_s if Jets.extra
|
13
|
+
params[:name] = Jets.project_namespace
|
14
|
+
params[:region] = Jets.aws.region
|
15
|
+
params[:account] = Jets.aws.account
|
16
|
+
params[:project_id] = Jets.project_name
|
17
|
+
params[:jets_api_version] = Jets::Api::VERSION
|
18
|
+
params[:jets_version] = Jets::VERSION
|
19
|
+
params[:ruby_version] = RUBY_VERSION
|
20
|
+
params[:ruby_folder] = Jets::Api::Gems.ruby_folder
|
21
|
+
params
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Jets::Api
|
4
|
+
class Config
|
5
|
+
include Singleton
|
6
|
+
extend Memoist
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
@options = options
|
10
|
+
@config_path = "#{ENV['HOME']}/.jets/config.yml"
|
11
|
+
end
|
12
|
+
|
13
|
+
def token
|
14
|
+
data['token'] || data['key'] # keep key for backwards compatibility
|
15
|
+
end
|
16
|
+
|
17
|
+
def data
|
18
|
+
@data ||= load
|
19
|
+
end
|
20
|
+
|
21
|
+
# Ensure a Hash is returned
|
22
|
+
def load
|
23
|
+
return {} unless File.exist?(@config_path)
|
24
|
+
|
25
|
+
data = YAML.load_file(@config_path)
|
26
|
+
if data.is_a?(Hash)
|
27
|
+
data
|
28
|
+
else
|
29
|
+
puts "WARN: #{@config_path} is not in the correct format. Loading an empty hash.".color(:yellow)
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def prompt
|
35
|
+
puts <<~EOL
|
36
|
+
You are about to configure your #{pretty_path(@config_path)}
|
37
|
+
You can get a token from pro.rubyonjets.com
|
38
|
+
EOL
|
39
|
+
print "Please provide your token: "
|
40
|
+
$stdin.gets.strip
|
41
|
+
end
|
42
|
+
|
43
|
+
# interface method: do not remove
|
44
|
+
def update_token(token=nil)
|
45
|
+
token ||= prompt
|
46
|
+
write(token: token) # specify keys to allow
|
47
|
+
end
|
48
|
+
|
49
|
+
def write(values={})
|
50
|
+
data = load
|
51
|
+
data.merge!(values.deep_stringify_keys)
|
52
|
+
data.delete('key') if data.key?('key') # remove legacy key that used to store token
|
53
|
+
FileUtils.mkdir_p(File.dirname(@config_path))
|
54
|
+
IO.write(@config_path, YAML.dump(data))
|
55
|
+
puts "Updated #{pretty_path(@config_path)}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def pretty_path(path)
|
59
|
+
path.sub(ENV['HOME'], '~')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require "aws-sdk-core"
|
2
|
+
|
3
|
+
module Jets::Api
|
4
|
+
module Core
|
5
|
+
extend Memoist
|
6
|
+
|
7
|
+
@@max_retries = 3
|
8
|
+
|
9
|
+
# Always translate raw json response to ruby Hash
|
10
|
+
def request(klass, path, data={})
|
11
|
+
raw_response = data.delete(:raw_response)
|
12
|
+
url = url(path)
|
13
|
+
req = build_request(klass, url, data)
|
14
|
+
@retries = 0
|
15
|
+
begin
|
16
|
+
resp = http.request(req) # send request
|
17
|
+
raw_response ? resp : load_json(url, resp)
|
18
|
+
rescue SocketError, OpenURI::HTTPError, OpenSSL::SSL::SSLError, Errno::ECONNREFUSED => e
|
19
|
+
@retries += 1
|
20
|
+
if @retries <= @@max_retries
|
21
|
+
delay = 2**@retries
|
22
|
+
puts "Error: #{e.class} #{e.message} retrying after #{delay} seconds..." if ENV['JETS_API_DEBUG']
|
23
|
+
sleep delay
|
24
|
+
retry
|
25
|
+
else
|
26
|
+
puts "Error: #{e.class} #{e.message} giving up after #{@retries} retries"
|
27
|
+
raise Jets::Api::RequestError.new(e)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_request(klass, url, data={})
|
33
|
+
req = klass.new(url) # url includes query string and uri.path does not, must used url
|
34
|
+
set_headers!(req)
|
35
|
+
if [Net::HTTP::Delete, Net::HTTP::Patch, Net::HTTP::Post, Net::HTTP::Put].include?(klass)
|
36
|
+
text = JSON.dump(data)
|
37
|
+
req.body = text
|
38
|
+
req.content_length = text.bytesize
|
39
|
+
end
|
40
|
+
req
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_headers!(req)
|
44
|
+
req['Authorization'] = token if token
|
45
|
+
req['x-account'] = account if account
|
46
|
+
req['Content-Type'] = "application/vnd.api+json"
|
47
|
+
end
|
48
|
+
|
49
|
+
def token
|
50
|
+
Jets::Api.token
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_json(url, res)
|
54
|
+
uri = URI(url)
|
55
|
+
if ENV['JETS_API_DEBUG']
|
56
|
+
puts "res.code #{res.code}"
|
57
|
+
puts "res.body #{res.body}"
|
58
|
+
end
|
59
|
+
if processable?(res.code)
|
60
|
+
JSON.load(res.body)
|
61
|
+
else
|
62
|
+
puts "Error: Non-successful http response status code: #{res.code}"
|
63
|
+
puts "headers: #{res.each_header.to_h.inspect}"
|
64
|
+
puts "Jets API #{url}" if ENV['JETS_API_DEBUG']
|
65
|
+
raise "Jets API called failed: #{uri.host}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Note: 422 is Unprocessable Entity. This means an invalid data payload was sent.
|
70
|
+
# We want that to error and raise
|
71
|
+
def processable?(http_code)
|
72
|
+
http_code =~ /^20/ || http_code =~ /^40/
|
73
|
+
end
|
74
|
+
|
75
|
+
def http
|
76
|
+
uri = URI(endpoint)
|
77
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
78
|
+
http.open_timeout = http.read_timeout = 30
|
79
|
+
http.use_ssl = true if uri.scheme == 'https'
|
80
|
+
http
|
81
|
+
end
|
82
|
+
memoize :http
|
83
|
+
|
84
|
+
# API does not include the /. IE: https://app.terraform.io/api/v2
|
85
|
+
def url(path)
|
86
|
+
"#{endpoint}/#{path}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def get(path, query={})
|
90
|
+
path = path_with_query(path, query)
|
91
|
+
request(Net::HTTP::Get, path, raw_response: query[:raw_response])
|
92
|
+
end
|
93
|
+
|
94
|
+
def path_with_query(path, query={})
|
95
|
+
return path if query.empty?
|
96
|
+
separator = path.include?("?") ? "&" : "?"
|
97
|
+
"#{path}#{separator}#{query.to_query}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def post(path, data={})
|
101
|
+
request(Net::HTTP::Post, path, data)
|
102
|
+
end
|
103
|
+
|
104
|
+
def patch(path, data={})
|
105
|
+
request(Net::HTTP::Patch, path, data)
|
106
|
+
end
|
107
|
+
|
108
|
+
def delete(path, data={})
|
109
|
+
request(Net::HTTP::Delete, path, data)
|
110
|
+
end
|
111
|
+
|
112
|
+
def account
|
113
|
+
sts.get_caller_identity.account rescue nil
|
114
|
+
end
|
115
|
+
memoize :account
|
116
|
+
|
117
|
+
def sts
|
118
|
+
Aws::STS::Client.new
|
119
|
+
end
|
120
|
+
memoize :sts
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# Assumes gems were just built and checks the filesystem to find and detect for
|
2
|
+
# compiled gems. Unless the cli option is set to true, the it'll just check
|
3
|
+
# based on the gemspecs.
|
4
|
+
class Jets::Api::Gems
|
5
|
+
class Check
|
6
|
+
extend Memoist
|
7
|
+
|
8
|
+
attr_reader :missing_gems
|
9
|
+
def initialize(options={})
|
10
|
+
@options = options
|
11
|
+
@missing_gems = [] # keeps track of gems that are not found in any of the Jets Api source
|
12
|
+
end
|
13
|
+
|
14
|
+
delegate :endpoint, to: Jets::Api
|
15
|
+
|
16
|
+
def run!
|
17
|
+
puts "Jets API endpoint: #{endpoint}" if @options[:verbose]
|
18
|
+
run(exit_early: true)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks whether the gem is found at the Jets Api source.
|
22
|
+
def run(exit_early: false)
|
23
|
+
puts "Checking project for compiled gems..."
|
24
|
+
compiled_gems.each do |gem_name|
|
25
|
+
puts "Checking #{gem_name}..." if @options[:verbose]
|
26
|
+
exist = Jets::Api::Gems::Exist.new
|
27
|
+
data = exist.check(gem_name)
|
28
|
+
@missing_gems << data unless data["exist"]
|
29
|
+
end
|
30
|
+
|
31
|
+
if exit_early && !@missing_gems.empty?
|
32
|
+
# Exits early if not all the linux gems are available.
|
33
|
+
# Better to error now than deploy a broken package to AWS Lambda.
|
34
|
+
# Apivide users with message about missing gems.
|
35
|
+
puts missing_message
|
36
|
+
names = @missing_gems.map {|i| i['gem_name']}
|
37
|
+
Report.new(@options).report(names) if agree.yes?
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
|
41
|
+
compiled_gems
|
42
|
+
end
|
43
|
+
|
44
|
+
def missing?
|
45
|
+
!@missing_gems.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
def missing_message
|
49
|
+
template = <<-EOL
|
50
|
+
Your project requires compiled gems that are not currently available. Unavailable precompiled gems:
|
51
|
+
<% missing_gems.each do |missing_gem|
|
52
|
+
available = missing_gem['available'].reject { |v| missing_gem['gem_name'].include?(v) }
|
53
|
+
%>
|
54
|
+
* Unavailable: <%= missing_gem['gem_name'] -%> Available versions: <%= available.join(' ') %>
|
55
|
+
<% end %>
|
56
|
+
Your current Jets API endpoint: #{endpoint}
|
57
|
+
|
58
|
+
Jets is unable to build a deployment package that will work on AWS Lambda without the required precompiled gems.
|
59
|
+
To remedy this, you can:
|
60
|
+
|
61
|
+
* Use another gem that does not require compilation.
|
62
|
+
* Create your own custom layer with the gem: http://rubyonjets.com/docs/extras/custom-lambda-layers/
|
63
|
+
<% if agree.yes? -%>
|
64
|
+
* No need to report this to us, as we've already been notified.
|
65
|
+
* Usually, missing gems can be built within a few minutes.
|
66
|
+
* Some gems may take days or even longer.
|
67
|
+
<% elsif agree.no? -%>
|
68
|
+
* You have choosen not to report data to Jets Api so we will not be notified about these missing gems.
|
69
|
+
* You can edit ~/.jets/agree to change this.
|
70
|
+
* Reporting gems generally allows Jets Api to build the missing gems within a few minutes.
|
71
|
+
* You can try redeploying again after a few minutes.
|
72
|
+
* Non-reported gems may take days or even longer to be built.
|
73
|
+
<% end -%>
|
74
|
+
|
75
|
+
Compiled gems usually take some time to figure out how to build as they each depend on different libraries and packages.
|
76
|
+
More info: http://rubyonjets.com/docs/jets-pro/
|
77
|
+
|
78
|
+
EOL
|
79
|
+
erb = ERB.new(template, trim_mode: '-') # trim mode https://stackoverflow.com/questions/4632879/erb-template-removing-the-trailing-line
|
80
|
+
erb.result(binding)
|
81
|
+
end
|
82
|
+
|
83
|
+
def agree
|
84
|
+
Jets::Api::Agree.new
|
85
|
+
end
|
86
|
+
memoize :agree
|
87
|
+
|
88
|
+
# Context, observations, and history:
|
89
|
+
#
|
90
|
+
# Two ways to check if gem is compiled.
|
91
|
+
#
|
92
|
+
# 1. compiled_gem_paths - look for .so and .bundle extension files in the folder itself.
|
93
|
+
# 2. gemspec - uses the gemspec metadata.
|
94
|
+
#
|
95
|
+
# Observations:
|
96
|
+
#
|
97
|
+
# * The gemspec approach generally finds more compiled gems than the compiled_gem_paths approach.
|
98
|
+
# * So when using the compiled_gem_paths some compiled are missed and not properly detected like http-parser.
|
99
|
+
# * However, some gemspec found compiled gems like json are weird and they don't work when they get replaced.
|
100
|
+
#
|
101
|
+
# History:
|
102
|
+
#
|
103
|
+
# * Started with compiled_gem_paths approach
|
104
|
+
# * Tried to gemspec approach, but ran into json-2.1.0 gem issues. bundler removes? http://bit.ly/39T8uln
|
105
|
+
# * Went to selective checking approach with `cli: true` option. This helped gather more data.
|
106
|
+
# * jets deploy - compiled_gem_paths
|
107
|
+
# * jets gems:check - gemspec_compiled_gems
|
108
|
+
# * Going back to compiled_gem_paths with:
|
109
|
+
# * Using the `weird_gem?` check to filter out gems removed by bundler. Note: Only happens with specific versions of json.
|
110
|
+
# * Keeping compiled_gem_paths for Jets Afterburner mode. Default to gemspec_compiled_gems otherwise
|
111
|
+
#
|
112
|
+
#
|
113
|
+
def compiled_gems
|
114
|
+
# @use_gemspec option finds compile gems with Gem::Specification
|
115
|
+
# The normal build process does not use this and checks the file system.
|
116
|
+
# So @use_gemspec is only used for this command:
|
117
|
+
#
|
118
|
+
# jets gems:check
|
119
|
+
#
|
120
|
+
# This is because it seems like some gems like json are remove and screws things up.
|
121
|
+
# We'll filter out for the json gem as a hacky workaround, unsure if there are more
|
122
|
+
# gems though that exhibit this behavior.
|
123
|
+
if @options[:use_gemspec] == false
|
124
|
+
# Afterburner mode
|
125
|
+
compiled_gems = compiled_gem_paths.map { |p| gem_name_from_path(p) }.uniq
|
126
|
+
# Double check that the gems are also in the gemspec list since that
|
127
|
+
# one is scoped to Bundler and will only included gems used in the project.
|
128
|
+
# This handles the possiblity of stale gems leftover from previous builds
|
129
|
+
# in the cache.
|
130
|
+
# TODO: figure out if we need
|
131
|
+
# compiled_gems.select { |g| gemspec_compiled_gems.include?(g) }
|
132
|
+
else
|
133
|
+
# default when use_gemspec not set
|
134
|
+
#
|
135
|
+
# jets deploy
|
136
|
+
# jets gems:check
|
137
|
+
#
|
138
|
+
gems = gemspec_compiled_gems
|
139
|
+
gems += other_compiled_gems
|
140
|
+
gems += registered_compiled_gems
|
141
|
+
gems.uniq
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# note 2.7.0/gems vs 2.7.0/extensions
|
146
|
+
#
|
147
|
+
# /tmp/jets/demo/stage/opt/ruby/gems/2.7.0/gems/nokogiri-1.11.1-x86_64-darwin/
|
148
|
+
# /tmp/jets/demo/stage/opt/ruby/gems/2.7.0/extensions/x86_64-linux/2.7.0/
|
149
|
+
#
|
150
|
+
# Also the platform is appended to the gem folder now
|
151
|
+
#
|
152
|
+
# /tmp/jets/demo/stage/opt/ruby/gems/2.7.0/gems/nokogiri-1.11.1-x86_64-darwin
|
153
|
+
# /tmp/jets/demo/stage/opt/ruby/gems/2.7.0/gems/nokogiri-1.11.1-x86_64-linux
|
154
|
+
#
|
155
|
+
# On new 2021 macbook with m1 chip: the gems are being saved in a folder like so:
|
156
|
+
# nokogiri-1.12.5-arm64-darwin
|
157
|
+
# The GEM_REGEXP accounts for this case.
|
158
|
+
GEM_REGEXP = /-(arm|x)\d+.*-(darwin|linux)/
|
159
|
+
def other_compiled_gems
|
160
|
+
paths = Dir.glob("#{Jets.build_root}/stage/opt/ruby/gems/#{Jets::Api::Gems.ruby_folder}/gems/*{-darwin,-linux}")
|
161
|
+
paths.map { |p| File.basename(p).sub(GEM_REGEXP,'') }
|
162
|
+
end
|
163
|
+
|
164
|
+
def registered_compiled_gems
|
165
|
+
registered = Jets::Api::Gems::Registered.new
|
166
|
+
registered_gems = registered.all # no version numbers in this list
|
167
|
+
|
168
|
+
paths = Dir.glob("#{Jets.build_root}/stage/opt/ruby/gems/#{Jets::Api::Gems.ruby_folder}/gems/*")
|
169
|
+
project_gems = paths.map { |p| File.basename(p).sub(GEM_REGEXP,'') }
|
170
|
+
project_gems.select do |name|
|
171
|
+
name_only = name.sub(/-\d+\.\d+\.\d+.*/,'')
|
172
|
+
registered_gems.include?(name_only)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Use precompiled gem because the gem could have development header shared
|
177
|
+
# object file dependencies. The shared dependencies are packaged up as part
|
178
|
+
# of the precompiled gem so it is available in the Lambda execution environment.
|
179
|
+
#
|
180
|
+
# Example paths:
|
181
|
+
# Macosx:
|
182
|
+
# opt/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/nokogiri-1.8.1
|
183
|
+
# opt/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/byebug-9.1.0
|
184
|
+
# Official AWS Lambda Linux AMI:
|
185
|
+
# opt/ruby/gems/2.5.0/extensions/x86_64-linux/2.5.0-static/nokogiri-1.8.1
|
186
|
+
# Circleci Ubuntu based Linux:
|
187
|
+
# opt/ruby/gems/2.5.0/extensions/x86_64-linux/2.5.0/pg-0.21.0
|
188
|
+
def compiled_gem_paths
|
189
|
+
Dir.glob("#{Jets.build_root}/stage/opt/ruby/gems/*/extensions/**/**/*.{so,bundle}")
|
190
|
+
end
|
191
|
+
|
192
|
+
# Input: opt/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/byebug-9.1.0
|
193
|
+
# Output: byebug-9.1.0
|
194
|
+
def gem_name_from_path(path)
|
195
|
+
regexp = %r{opt/ruby/gems/\d+\.\d+\.\d+/extensions/.*?/.*?/(.*?)/}
|
196
|
+
path.match(regexp)[1] # gem_name
|
197
|
+
end
|
198
|
+
|
199
|
+
# So can also check for compiled gems with Gem::Specification
|
200
|
+
# But then also includes the json gem, which then bundler removes?
|
201
|
+
# We'll figure out the the json gems.
|
202
|
+
# https://gist.github.com/tongueroo/16f4aa5ac5393424103347b0e529495e
|
203
|
+
#
|
204
|
+
# This is a faster way to check but am unsure if there are more gems than just
|
205
|
+
# json that exhibit this behavior. So only using this technique for this commmand:
|
206
|
+
#
|
207
|
+
# jets gems:check
|
208
|
+
#
|
209
|
+
# Thanks: https://gist.github.com/aelesbao/1414b169a79162b1d795 and
|
210
|
+
# https://stackoverflow.com/questions/5165950/how-do-i-get-a-list-of-gems-that-are-installed-that-have-native-extensions
|
211
|
+
def gemspec_compiled_gems
|
212
|
+
specs = Gem::Specification.each.select { |spec| spec.extensions.any? }
|
213
|
+
specs.reject! { |spec| weird_gem?(spec.name) }
|
214
|
+
specs.map(&:full_name)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Filter out the weird special case gems that bundler deletes?
|
218
|
+
# Apibably to fix some bug.
|
219
|
+
#
|
220
|
+
# $ bundle show json
|
221
|
+
# The gem json has been deleted. It was installed at:
|
222
|
+
# /home/ec2-user/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/json-2.1.0
|
223
|
+
#
|
224
|
+
def weird_gem?(name)
|
225
|
+
command = "bundle show #{name} 2>&1"
|
226
|
+
output = `#{command}`
|
227
|
+
output.include?("has been deleted")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Jets::Api::Gems
|
2
|
+
class Exist
|
3
|
+
include Jets::Api
|
4
|
+
|
5
|
+
# gem_name IE: nokogiri-1.1.1
|
6
|
+
def check(gem_name)
|
7
|
+
Jets::Api::Gems.exist(gem_name: gem_name) # data = {"exist": ..., "available"}
|
8
|
+
rescue Jets::Api::RequestError => e
|
9
|
+
puts "WARNING: #{e.class}: #{e.message}"
|
10
|
+
{"exist" => false, gem_name: gem_name, available: [] }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
|
3
|
+
module Jets::Api::Gems::Extract
|
4
|
+
class Base
|
5
|
+
class NotFound < RuntimeError; end
|
6
|
+
|
7
|
+
attr_reader :project_root
|
8
|
+
def initialize(name, options={})
|
9
|
+
@name = name
|
10
|
+
@options = options
|
11
|
+
ruby_folder = Jets::Api::Gems.ruby_folder
|
12
|
+
@downloads_root = options[:downloads_root] || "/tmp/jets/#{Jets.config.project_name}/jets-api/#{ruby_folder}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def clean_downloads(folder)
|
16
|
+
path = "#{@downloads_root}/downloads/#{folder}"
|
17
|
+
say "Removing cache: #{path}"
|
18
|
+
FileUtils.rm_rf(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Using ` > /dev/null 2>&1` to suppress stderr message:
|
22
|
+
#
|
23
|
+
# lchmod (file attributes) error: Function not implemented
|
24
|
+
#
|
25
|
+
def unzip(zipfile_path, parent_folder_dest)
|
26
|
+
sh("cd #{parent_folder_dest} && unzip -qo #{zipfile_path} > /dev/null 2>&1")
|
27
|
+
end
|
28
|
+
|
29
|
+
def sh(command)
|
30
|
+
say "=> #{command}".color(:green)
|
31
|
+
success = system(command)
|
32
|
+
abort("Command Failed #{command}") unless success
|
33
|
+
success
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the dest path
|
37
|
+
def download_file(url, dest)
|
38
|
+
if File.exist?(dest) && ENV['JETS_API_FORCE_DOWNLOAD'].blank?
|
39
|
+
say "File already downloaded #{dest}"
|
40
|
+
return dest
|
41
|
+
end
|
42
|
+
|
43
|
+
say "Downloading..."
|
44
|
+
downloaded = URI.open(url, 'rb') { |read_file| read_file.read }
|
45
|
+
|
46
|
+
FileUtils.mkdir_p(File.dirname(dest)) # ensure parent folder exists
|
47
|
+
|
48
|
+
File.open(dest, 'wb') { |saved_file| saved_file.write(downloaded) }
|
49
|
+
|
50
|
+
dest
|
51
|
+
end
|
52
|
+
|
53
|
+
@@log_level = :info # default level is :info
|
54
|
+
# @@log_level = :debug # uncomment to debug
|
55
|
+
def log_level=(val)
|
56
|
+
@@log_level = val
|
57
|
+
end
|
58
|
+
|
59
|
+
def say(message, level=:info)
|
60
|
+
enabled = @@log_level == :debug || level == :debug
|
61
|
+
puts(message) if enabled
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "gems"
|
2
|
+
|
3
|
+
module Jets::Api::Gems::Extract
|
4
|
+
class Gem < Base
|
5
|
+
VERSION_PATTERN = /-(\d+\.\d+.*)/
|
6
|
+
include Jets::Api
|
7
|
+
|
8
|
+
def run
|
9
|
+
say "Will download and extract gem: #{full_gem_name}"
|
10
|
+
clean_downloads(:gems) if @options[:clean]
|
11
|
+
zipfile_path = download_gem
|
12
|
+
remove_current_gem if Jets.config.pro.clean || Jets.config.gems.clean
|
13
|
+
unzip_file(zipfile_path)
|
14
|
+
say("Gem #{full_gem_name} unpacked at #{project_root}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def unzip_file(zipfile_path)
|
18
|
+
dest = "#{Jets.build_root}/stage/opt"
|
19
|
+
say "Unpacking into #{dest}"
|
20
|
+
FileUtils.mkdir_p(dest)
|
21
|
+
unzip(zipfile_path, dest)
|
22
|
+
end
|
23
|
+
|
24
|
+
# ensure that we always have the full gem name
|
25
|
+
def full_gem_name
|
26
|
+
return @full_gem_name if @full_gem_name
|
27
|
+
|
28
|
+
if @name.match(VERSION_PATTERN)
|
29
|
+
@full_gem_name = @name
|
30
|
+
return @full_gem_name
|
31
|
+
end
|
32
|
+
|
33
|
+
# name doesnt have a version yet, so grab the latest version and add it
|
34
|
+
version = Api.versions(@name).first
|
35
|
+
@full_gem_name = "#{@name}-#{version["number"]}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def gem_name
|
39
|
+
full_gem_name.gsub(VERSION_PATTERN,'') # folder: byebug
|
40
|
+
end
|
41
|
+
|
42
|
+
# Downloads and extracts the linux gem into the proper directory.
|
43
|
+
# Extracts to: . (current directory)
|
44
|
+
#
|
45
|
+
# It produces a `bundled` folder.
|
46
|
+
# The folder contains the re-produced directory structure. Example with
|
47
|
+
# the gem: byebug-9.1.0
|
48
|
+
#
|
49
|
+
# vendor/gems/ruby/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/byebug-9.1.0
|
50
|
+
#
|
51
|
+
def download_gem
|
52
|
+
# download - also move to /tmp/jets/demo/compiled_gems folder
|
53
|
+
begin
|
54
|
+
@retries ||= 0
|
55
|
+
url = gem_url
|
56
|
+
basename = File.basename(url).gsub(/\?.*/,'') # remove query string info
|
57
|
+
tarball_dest = download_file(url, download_path(basename))
|
58
|
+
rescue OpenURI::HTTPError => e
|
59
|
+
url_without_query = url.gsub(/\?.*/,'')
|
60
|
+
puts "Error downloading #{url_without_query}"
|
61
|
+
@retries += 1
|
62
|
+
if @retries < 3
|
63
|
+
sleep 1
|
64
|
+
puts "Retrying download. Retry attempt: #{@retries}"
|
65
|
+
retry
|
66
|
+
else
|
67
|
+
raise e
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
unless tarball_dest
|
72
|
+
message = "Url: #{url} not found"
|
73
|
+
if @options[:exit_on_error]
|
74
|
+
say message
|
75
|
+
exit
|
76
|
+
else
|
77
|
+
raise NotFound.new(message)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
say "Downloaded to: #{tarball_dest}"
|
81
|
+
tarball_dest
|
82
|
+
end
|
83
|
+
|
84
|
+
# full_gem_name: byebug-9.1.0
|
85
|
+
def gem_url
|
86
|
+
resp = Jets::Api::Gems.download(gem_name: full_gem_name)
|
87
|
+
if resp["download_url"]
|
88
|
+
resp["download_url"]
|
89
|
+
else
|
90
|
+
puts resp["message"].color(:red)
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def download_path(filename)
|
96
|
+
"#{@downloads_root}/downloads/gems/#{filename}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Finds any currently install gems that matched with the gem name and version
|
100
|
+
# and remove them first.
|
101
|
+
# We clean up the current install gems first in case it was previously installed
|
102
|
+
# and has different *.so files that can be accidentally required. This
|
103
|
+
# happened with the pg gem.
|
104
|
+
def remove_current_gem
|
105
|
+
say "Removing current #{full_gem_name} gem installation:"
|
106
|
+
gem_dirs = Dir.glob("#{project_root}/**/*").select do |path|
|
107
|
+
File.directory?(path) &&
|
108
|
+
path =~ %r{vendor/gems} &&
|
109
|
+
File.basename(path) == full_gem_name
|
110
|
+
end
|
111
|
+
gem_dirs.each do |dir|
|
112
|
+
say " rm -rf #{dir}"
|
113
|
+
FileUtils.rm_rf(dir)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "net/http"
|
2
|
+
|
3
|
+
class Jets::Api::Gems
|
4
|
+
class Report
|
5
|
+
include Jets::Api
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def report(gems)
|
12
|
+
threads = []
|
13
|
+
gems.each do |gem_name|
|
14
|
+
threads << Thread.new do
|
15
|
+
Jets::Api::Gems.report(gem_name: gem_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
# Wait for request to finish because the command might finish before
|
19
|
+
# the Threads even send the request. So we join them just case
|
20
|
+
threads.each(&:join)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Jets::Api
|
2
|
+
class Gems < Base
|
3
|
+
class << self
|
4
|
+
def download(params={})
|
5
|
+
params = global_params.merge(params)
|
6
|
+
api.post("gems/download", params)
|
7
|
+
end
|
8
|
+
|
9
|
+
def exist(params={})
|
10
|
+
params = global_params.merge(params)
|
11
|
+
api.post("gems/exist", params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def report(params={})
|
15
|
+
params = global_params.merge(params)
|
16
|
+
api.post("gems/report", params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def registered(params={})
|
20
|
+
params = global_params.merge(params)
|
21
|
+
api.post("gems/registered", params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ruby_folder
|
25
|
+
major, minor, _ = RUBY_VERSION.split('.')
|
26
|
+
[major, minor, '0'].join('.') # 2.5.1 => 2.5.0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Jets::Api
|
2
|
+
class Release < Base
|
3
|
+
class << self
|
4
|
+
def list(params={})
|
5
|
+
params = global_params.merge(params)
|
6
|
+
api.get("releases", params)
|
7
|
+
end
|
8
|
+
|
9
|
+
def retrieve(id, params={})
|
10
|
+
params = global_params.merge(params)
|
11
|
+
api.get("releases/#{id}", params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(params={})
|
15
|
+
params = global_params.merge(params)
|
16
|
+
api.post("releases", params)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Jets::Api
|
2
|
+
class Stack < Base
|
3
|
+
class << self
|
4
|
+
def list(params={})
|
5
|
+
params = global_params.merge(params)
|
6
|
+
api.get("stacks", params)
|
7
|
+
end
|
8
|
+
|
9
|
+
def retrieve(id, params={})
|
10
|
+
params = global_params.merge(params)
|
11
|
+
api.get("stacks/#{id}", params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/jets/api.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "jets"
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path("../../", __FILE__))
|
4
|
+
require "jets/api/autoloader"
|
5
|
+
Jets::Api::Autoloader.setup
|
6
|
+
|
7
|
+
require "memoist"
|
8
|
+
require "yaml"
|
9
|
+
|
10
|
+
require "cli-format"
|
11
|
+
CliFormat.default_format = "table"
|
12
|
+
|
13
|
+
module Jets
|
14
|
+
module Api
|
15
|
+
class RequestError < StandardError
|
16
|
+
def initialize(original_error)
|
17
|
+
message = "#{original_error.class} #{original_error.message}"
|
18
|
+
super(message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
extend Memoist
|
23
|
+
def api
|
24
|
+
Jets::Api::Client.new
|
25
|
+
end
|
26
|
+
memoize :api
|
27
|
+
|
28
|
+
def token
|
29
|
+
Jets::Api::Config.instance.token
|
30
|
+
end
|
31
|
+
module_function :token
|
32
|
+
|
33
|
+
def endpoint
|
34
|
+
ENV['JETS_API'] || Jets.config.pro.endpoint || 'https://api.rubyonjets.com/v1'
|
35
|
+
end
|
36
|
+
module_function :endpoint
|
37
|
+
end
|
38
|
+
end
|
data/lib/jets-api.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "jets/api"
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jets-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tung Nguyen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-12-02 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- tongueroo@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- LICENSE.txt
|
21
|
+
- README.md
|
22
|
+
- Rakefile
|
23
|
+
- lib/jets-api.rb
|
24
|
+
- lib/jets/api.rb
|
25
|
+
- lib/jets/api/agree.rb
|
26
|
+
- lib/jets/api/autoloader.rb
|
27
|
+
- lib/jets/api/base.rb
|
28
|
+
- lib/jets/api/client.rb
|
29
|
+
- lib/jets/api/config.rb
|
30
|
+
- lib/jets/api/core.rb
|
31
|
+
- lib/jets/api/gems.rb
|
32
|
+
- lib/jets/api/gems/check.rb
|
33
|
+
- lib/jets/api/gems/exist.rb
|
34
|
+
- lib/jets/api/gems/extract/base.rb
|
35
|
+
- lib/jets/api/gems/extract/gem.rb
|
36
|
+
- lib/jets/api/gems/registered.rb
|
37
|
+
- lib/jets/api/gems/report.rb
|
38
|
+
- lib/jets/api/project.rb
|
39
|
+
- lib/jets/api/release.rb
|
40
|
+
- lib/jets/api/stack.rb
|
41
|
+
- lib/jets/api/version.rb
|
42
|
+
homepage: https://github.com/rubyonjets/jets-api
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
metadata:
|
46
|
+
homepage_uri: https://github.com/rubyonjets/jets-api
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 2.7.0
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubygems_version: 3.4.20
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Jets API Client Library for Jets Ruby Serverless Framework
|
66
|
+
test_files: []
|