jets-api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/jets-api.png)](http://badge.fury.io/rb/jets-api)
|
4
|
+
|
5
|
+
[![BoltOps Badge](https://img.boltops.com/boltops/badges/boltops-badge.png)](https://www.boltops.com)
|
6
|
+
|
7
|
+
[![BoltOps Learn Badge](https://img.boltops.com/boltops-learn/boltops-learn.png)](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: []
|