serverlessgems 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ee4f7043cd9fb6ace83079a2a82b3aeafb3ca2c9000e73fe6bff8dccd86b21f5
4
+ data.tar.gz: 4fc40b94cda06670351b39a401c9cb0eadb94030fd85f2c8d058590a75605f59
5
+ SHA512:
6
+ metadata.gz: 76e24348551d21d0d1266c972f11d9782c3d1a9ea36a653c62b25e4404b3a5146562c79e22df2b0ab860c0f4754e17ebf3663e9bbea15c042c673a7160dae826
7
+ data.tar.gz: 9f4f5d19b9833cf94da12b410475b44a8b519171d260b5119ab802e1fd246c8fcc6e3ddb504d7d20683a6d1a0aa3d5e8185f6a5657ae0ecde5f6cc88c2bb1869
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,12 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/).
5
+
6
+ ## [0.1.0]
7
+ - Initial release
8
+ - Rename to serverlessgems
9
+ - Zeitwerk autoloader
10
+ - Use Serverless Gems API
11
+ - .jets/config.yml support
12
+
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in serverlessgems.gemspec
6
+ gemspec
7
+
8
+ group :development, :test do
9
+ gem "jets"
10
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 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.
@@ -0,0 +1,21 @@
1
+ # serverlessgems
2
+
3
+ [![BoltOps Badge](https://img.boltops.com/boltops/badges/boltops-badge.png)](https://www.boltops.com)
4
+
5
+ [Jets](http://rubyonjets.com/) is a Ruby Serverless Framework on AWS Lambda. It allows you to create serverless applications with a beautiful language: Ruby. It includes everything required to build an application and deploy it to AWS Lambda. Jets makes serverless simple.
6
+
7
+ This library works with serverlessgems API to download and extract pre-compiled gems that work on AWS Lambda.
8
+
9
+ ## Development
10
+
11
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
12
+
13
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
14
+
15
+ ## Contributing
16
+
17
+ Bug reports and pull requests are welcome on GitHub at [boltops-tools/serverlessgems](https://github.com/boltops-tools/serverlessgems)
18
+
19
+ ## License
20
+
21
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "jets/gems"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,19 @@
1
+ require "jets"
2
+
3
+ $:.unshift(File.expand_path("../../", __FILE__))
4
+ require "jets/gems/autoloader"
5
+ Jets::Gems::Autoloader.setup
6
+
7
+ require "memoist"
8
+ require "yaml"
9
+
10
+ module Jets
11
+ module Gems
12
+ def ruby_folder
13
+ major, minor, _ = RUBY_VERSION.split('.')
14
+ [major, minor, '0'].join('.') # 2.5.1 => 2.5.0
15
+ end
16
+
17
+ extend self
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ module Jets::Gems
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)
11
+
12
+ puts <<~EOL
13
+ Jets sends data about your gems to serverlessgems.com so that it can build the necessary Lambda layer.
14
+
15
+ Reporting gems generally allows Serverless Gems to build new gems within a few minutes.
16
+ So if you run into missing gems, you can try deploying again after a few minutes.
17
+ Non-reported gems may take several days or longer.
18
+
19
+ Serverless Gems only collects the info it needs to run the service. More info: https://www.serverlessgems.com/privacy
20
+
21
+ This message will only appear once on this machine.
22
+ You can also automatically skip this message by setting JETS_AGREE=yes or JETS_AGREE=no
23
+
24
+ Is it okay to send your gem data to Serverless Gems? (Y/n)?
25
+ EOL
26
+
27
+ answer = $stdin.gets.strip
28
+ value = answer =~ /y/i ? 'yes' : 'no'
29
+
30
+ write_file(value)
31
+ end
32
+
33
+ # Allow user to bypass prompt with JETS_AGREE=1 JETS_AGREE=yes etc
34
+ # Useful for CI/CD pipelines.
35
+ def bypass_prompt
36
+ agree = ENV['JETS_AGREE']
37
+ return false unless agree
38
+
39
+ if %w[1 yes true].include?(agree.downcase)
40
+ write_file('yes')
41
+ else
42
+ write_file('no')
43
+ end
44
+
45
+ true
46
+ end
47
+
48
+ def yes?
49
+ File.exist?(@agree_file) && IO.read(@agree_file).strip == 'yes'
50
+ end
51
+
52
+ def no?
53
+ File.exist?(@agree_file) && IO.read(@agree_file).strip == 'no'
54
+ end
55
+
56
+ def yes!
57
+ write_file("yes")
58
+ end
59
+
60
+ def no!
61
+ write_file("no")
62
+ end
63
+
64
+ def write_file(content)
65
+ FileUtils.mkdir_p(File.dirname(@agree_file))
66
+ IO.write(@agree_file, content)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,25 @@
1
+ module Jets::Gems
2
+ class Api
3
+ include Core
4
+
5
+ def endpoint
6
+ ENV['SG_API'] || Jets.config.gems.source || 'https://api.serverlessgems.com/api/v1'
7
+ end
8
+
9
+ def download_url(gem_name:, project:)
10
+ get("gem/download?gem_name=#{gem_name}&ruby_folder=#{ruby_folder}&project=#{project}")
11
+ end
12
+
13
+ def check_exist(gem_name:)
14
+ get("gem/exist?gem_name=#{gem_name}&ruby_folder=#{ruby_folder}")
15
+ end
16
+
17
+ def report_missing(gem_name:)
18
+ get("report/missing?gem_name=#{gem_name}&ruby_folder=#{ruby_folder}")
19
+ end
20
+
21
+ def ruby_folder
22
+ Jets::Gems.ruby_folder
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ class Jets::Gems::Api
2
+ module Concern
3
+ def api
4
+ Jets::Gems::Api.new
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,81 @@
1
+ class Jets::Gems::Api
2
+ module Core
3
+ extend Memoist
4
+
5
+ # Always translate raw json response to ruby Hash
6
+ def request(klass, path, data={})
7
+ url = url(path)
8
+ req = build_request(klass, url, data)
9
+ resp = http.request(req) # send request
10
+ load_json(url, resp)
11
+ end
12
+
13
+ def build_request(klass, url, data={})
14
+ req = klass.new(url) # url includes query string and uri.path does not, must used url
15
+ set_headers!(req)
16
+ if [Net::HTTP::Delete, Net::HTTP::Patch, Net::HTTP::Post, Net::HTTP::Put].include?(klass)
17
+ text = JSON.dump(data)
18
+ req.body = text
19
+ req.content_length = text.bytesize
20
+ end
21
+ req
22
+ end
23
+
24
+ def set_headers!(req)
25
+ req['Authorization'] = token if token
26
+ req['Content-Type'] = "application/vnd.api+json"
27
+ end
28
+
29
+ def token
30
+ Jets::Gems::Config.instance.data['key']
31
+ end
32
+
33
+ def load_json(url, res)
34
+ uri = URI(url)
35
+ if ok?(res.code)
36
+ JSON.load(res.body)
37
+ else
38
+ puts "Error: Non-successful http response status code: #{res.code}"
39
+ puts "headers: #{res.each_header.to_h.inspect}"
40
+ puts "ServerlessGems API #{url}" if ENV['SG_DEBUG']
41
+ raise "ServerlessGems API called failed: #{uri.host}"
42
+ end
43
+ end
44
+
45
+ # Note: 422 is Unprocessable Entity. This means an invalid data payload was sent.
46
+ # We want that to error and raise
47
+ def ok?(http_code)
48
+ http_code =~ /^20/ || http_code =~ /^40/
49
+ end
50
+
51
+ def http
52
+ uri = URI(endpoint)
53
+ http = Net::HTTP.new(uri.host, uri.port)
54
+ http.open_timeout = http.read_timeout = 30
55
+ http.use_ssl = true if uri.scheme == 'https'
56
+ http
57
+ end
58
+ memoize :http
59
+
60
+ # API does not include the /. IE: https://app.terraform.io/api/v2
61
+ def url(path)
62
+ "#{endpoint}/#{path}"
63
+ end
64
+
65
+ def get(path)
66
+ request(Net::HTTP::Get, path)
67
+ end
68
+
69
+ def post(path, data={})
70
+ request(Net::HTTP::Post, path, data)
71
+ end
72
+
73
+ def patch(path, data={})
74
+ request(Net::HTTP::Patch, path, data)
75
+ end
76
+
77
+ def delete(path, data={})
78
+ request(Net::HTTP::Delete, path, data)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,23 @@
1
+ require "zeitwerk"
2
+
3
+ module Jets::Gems
4
+ class Autoloader
5
+ class Inflector < Zeitwerk::Inflector
6
+ def camelize(basename, _abspath)
7
+ map = { cli: "CLI", version: "VERSION" }
8
+ map[basename.to_sym] || super
9
+ end
10
+ end
11
+
12
+ class << self
13
+ def setup
14
+ loader = Zeitwerk::Loader.new
15
+ loader.inflector = Inflector.new
16
+ lib = File.expand_path('../..', __dir__)
17
+ loader.push_dir(lib) # lib
18
+ loader.ignore("#{lib}/serverlessgems.rb")
19
+ loader.setup
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,192 @@
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
+ module Jets::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 serverlessgems source
12
+ end
13
+
14
+ def run!
15
+ puts "Gems source: #{gems_source}" if @options[:show_source]
16
+ run(exit_early: true)
17
+ end
18
+
19
+ def gems_source
20
+ ENV['SG_API'] || Jets.config.gems.source
21
+ end
22
+
23
+ # Checks whether the gem is found at the serverlessgems source.
24
+ def run(exit_early: false)
25
+ puts "Checking projects gems for binary Lambda gems..."
26
+ compiled_gems.each do |gem_name|
27
+ puts "Checking #{gem_name}..." if @options[:cli]
28
+ exist = Jets::Gems::Exist.new
29
+ @missing_gems << gem_name unless exist.check(gem_name)
30
+ end
31
+
32
+ if exit_early && !@missing_gems.empty?
33
+ # Exits early if not all the linux gems are available.
34
+ # Better to error now than deploy a broken package to AWS Lambda.
35
+ # Provide users with message about using their own serverlessgems source.
36
+ puts missing_message
37
+ Report.new(@options).report(@missing_gems) 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 pre-compiled gems:
51
+ <% missing_gems.each do |gem| %>
52
+ * <%= gem -%>
53
+ <% end %>
54
+
55
+ Your current serverlessgems source: #{gems_source}
56
+
57
+ Jets is unable to build a deployment package that will work on AWS Lambda without the required pre-compiled gems. To remedy this, you can:
58
+
59
+ * Use another gem that does not require compilation.
60
+ * Create your own custom layer with the gem: http://rubyonjets.com/docs/extras/custom-lambda-layers/
61
+ <% if agree.yes? -%>
62
+ * No need to report this to us, as we've already been notified.
63
+ <% elsif agree.no? -%>
64
+ * You have choosen not to report data to serverlessgems so we will not be notified about these missing gems.
65
+ * You can edit ~/.jets/agree to change this.
66
+ * Reporting gems generally allows Serverless Gems to build the missing gems within a few minutes.
67
+ * You can try redeploying again after a few minutes.
68
+ * Non-reported gems may take days or even longer to be built.
69
+ <% end -%>
70
+
71
+ Compiled gems usually take some time to figure out how to build as they each depend on different libraries and packages.
72
+ More info: http://rubyonjets.com/docs/serverlessgems/
73
+
74
+ EOL
75
+ erb = ERB.new(template, nil, '-') # trim mode https://stackoverflow.com/questions/4632879/erb-template-removing-the-trailing-line
76
+ erb.result(binding)
77
+ end
78
+
79
+ def agree
80
+ Agree.new
81
+ end
82
+ memoize :agree
83
+
84
+ # Context, observations, and history:
85
+ #
86
+ # Two ways to check if gem is compiled.
87
+ #
88
+ # 1. compiled_gem_paths - look for .so and .bundle extension files in the folder itself.
89
+ # 2. gemspec - uses the gemspec metadata.
90
+ #
91
+ # Observations:
92
+ #
93
+ # * The gemspec approach generally finds more compiled gems than the compiled_gem_paths approach.
94
+ # * So when using the compiled_gem_paths some compiled are missed and not properly detected like http-parser.
95
+ # * However, some gemspec found compiled gems like json are weird and they don't work when they get replaced.
96
+ #
97
+ # History:
98
+ #
99
+ # * Started with compiled_gem_paths approach
100
+ # * Tried to gemspec approach, but ran into json-2.1.0 gem issues. bundler removes? http://bit.ly/39T8uln
101
+ # * Went to selective checking approach with `cli: true` option. This helped gather more data.
102
+ # * jets deploy - compiled_gem_paths
103
+ # * jets gems:check - gemspec_compiled_gems
104
+ # * Going back to compiled_gem_paths with:
105
+ # * Using the `weird_gem?` check to filter out gems removed by bundler. Note: Only happens with specific versions of json.
106
+ # * Keeping compiled_gem_paths for Jets Afterburner mode. Default to gemspec_compiled_gems otherwise
107
+ #
108
+ #
109
+ def compiled_gems
110
+ # @use_gemspec option finds compile gems with Gem::Specification
111
+ # The normal build process does not use this and checks the file system.
112
+ # So @use_gemspec is only used for this command:
113
+ #
114
+ # jets gems:check
115
+ #
116
+ # This is because it seems like some gems like json are remove and screws things up.
117
+ # We'll filter out for the json gem as a hacky workaround, unsure if there are more
118
+ # gems though that exhibit this behavior.
119
+ if @options[:use_gemspec] == false
120
+ # Afterburner mode
121
+ compiled_gems = compiled_gem_paths.map { |p| gem_name_from_path(p) }.uniq
122
+ # Double check that the gems are also in the gemspec list since that
123
+ # one is scoped to Bundler and will only included gems used in the project.
124
+ # This handles the possiblity of stale gems leftover from previous builds
125
+ # in the cache.
126
+ # TODO: figure out if we need
127
+ # compiled_gems.select { |g| gemspec_compiled_gems.include?(g) }
128
+ else
129
+ # default when use_gemspec not set
130
+ #
131
+ # jets deploy
132
+ # jets gems:check
133
+ #
134
+ gemspec_compiled_gems
135
+ end
136
+ end
137
+
138
+ # Use pre-compiled gem because the gem could have development header shared
139
+ # object file dependencies. The shared dependencies are packaged up as part
140
+ # of the pre-compiled gem so it is available in the Lambda execution environment.
141
+ #
142
+ # Example paths:
143
+ # Macosx:
144
+ # opt/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/nokogiri-1.8.1
145
+ # opt/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/byebug-9.1.0
146
+ # Official AWS Lambda Linux AMI:
147
+ # opt/ruby/gems/2.5.0/extensions/x86_64-linux/2.5.0-static/nokogiri-1.8.1
148
+ # Circleci Ubuntu based Linux:
149
+ # opt/ruby/gems/2.5.0/extensions/x86_64-linux/2.5.0/pg-0.21.0
150
+ def compiled_gem_paths
151
+ Dir.glob("#{Jets.build_root}/stage/opt/ruby/gems/*/extensions/**/**/*.{so,bundle}")
152
+ end
153
+
154
+ # Input: opt/ruby/gems/2.5.0/extensions/x86_64-darwin-16/2.5.0-static/byebug-9.1.0
155
+ # Output: byebug-9.1.0
156
+ def gem_name_from_path(path)
157
+ regexp = %r{opt/ruby/gems/\d+\.\d+\.\d+/extensions/.*?/.*?/(.*?)/}
158
+ path.match(regexp)[1] # gem_name
159
+ end
160
+
161
+ # So can also check for compiled gems with Gem::Specification
162
+ # But then also includes the json gem, which then bundler removes?
163
+ # We'll figure out the the json gems.
164
+ # https://gist.github.com/tongueroo/16f4aa5ac5393424103347b0e529495e
165
+ #
166
+ # This is a faster way to check but am unsure if there are more gems than just
167
+ # json that exhibit this behavior. So only using this technique for this commmand:
168
+ #
169
+ # jets gems:check
170
+ #
171
+ # Thanks: https://gist.github.com/aelesbao/1414b169a79162b1d795 and
172
+ # https://stackoverflow.com/questions/5165950/how-do-i-get-a-list-of-gems-that-are-installed-that-have-native-extensions
173
+ def gemspec_compiled_gems
174
+ specs = Gem::Specification.each.select { |spec| spec.extensions.any? }
175
+ specs.reject! { |spec| weird_gem?(spec.name) }
176
+ specs.map(&:full_name)
177
+ end
178
+
179
+ # Filter out the weird special case gems that bundler deletes?
180
+ # Probably to fix some bug.
181
+ #
182
+ # $ bundle show json
183
+ # The gem json has been deleted. It was installed at:
184
+ # /home/ec2-user/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/json-2.1.0
185
+ #
186
+ def weird_gem?(name)
187
+ command = "bundle show #{name} 2>&1"
188
+ output = `#{command}`
189
+ output.include?("has been deleted")
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,13 @@
1
+ require "singleton"
2
+
3
+ module Jets::Gems
4
+ class Config
5
+ include Singleton
6
+ extend Memoist
7
+
8
+ def data
9
+ Token.new.data
10
+ end
11
+ memoize :data
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ class Jets::Gems::Config
2
+ class Token
3
+ def key
4
+ data['key']
5
+ end
6
+
7
+ def data
8
+ load_yaml("#{ENV['HOME']}/.jets/config.yml")
9
+ end
10
+
11
+ # Ensure a Hash is returned
12
+ def load_yaml(path)
13
+ return {} unless File.exist?(path)
14
+
15
+ data = YAML.load_file(path)
16
+ if data.is_a?(Hash)
17
+ data
18
+ else
19
+ puts "WARN: #{path} is not in the correct format. Loading an empty hash.".color(:yellow)
20
+ {}
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require 'open-uri'
2
+
3
+ module Jets::Gems
4
+ class Exist
5
+ include Jets::Gems::Api::Concern
6
+
7
+ # We check all the availability before even downloading so we can provide a
8
+ # full list of gems they might want to research all at once instead of incrementally
9
+ #
10
+ # Examples:
11
+ #
12
+ # check(single_gem)
13
+ # check(list, of, gems)
14
+ def check(*gem_names)
15
+ gem_names = gem_names.flatten
16
+ exists = gem_names.inject({}) do |hash, gem_name|
17
+ exist = gem_exist?(gem_name)
18
+ hash[gem_name] = exist
19
+ hash.merge(hash)
20
+ end
21
+
22
+ exists.values.all? {|v| v } # all_exist
23
+ end
24
+
25
+ def gem_exist?(gem_name)
26
+ data = api.check_exist(gem_name: gem_name)
27
+ data["exist"]
28
+ rescue SocketError, OpenURI::HTTPError, OpenSSL::SSL::SSLError
29
+ false
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,65 @@
1
+ require "open-uri"
2
+
3
+ module Jets::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
+
12
+ @downloads_root = options[:downloads_root] || "/tmp/jets/#{Jets.config.project_name}/serverlessgems"
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)
39
+ say "File already downloaded #{dest}"
40
+ return dest
41
+ end
42
+
43
+ say "Downloading..."
44
+ FileUtils.mkdir_p(File.dirname(dest)) # ensure parent folder exists
45
+
46
+ File.open(dest, 'wb') do |saved_file|
47
+ URI.open(url, 'rb') do |read_file|
48
+ saved_file.write(read_file.read)
49
+ end
50
+ end
51
+ dest
52
+ end
53
+
54
+ @@log_level = :info # default level is :info
55
+ # @@log_level = :debug # uncomment to debug
56
+ def log_level=(val)
57
+ @@log_level = val
58
+ end
59
+
60
+ def say(message, level=:info)
61
+ enabled = @@log_level == :debug || level == :debug
62
+ puts(message) if enabled
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,102 @@
1
+ require "gems"
2
+
3
+ module Jets::Gems::Extract
4
+ class Gem < Base
5
+ VERSION_PATTERN = /-(\d+\.\d+.*)/
6
+ include Jets::Gems::Api::Concern
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.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 = Gems.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
+ url = gem_url
54
+ basename = File.basename(url).gsub(/\?.*/,'') # remove query string info
55
+ tarball_dest = download_file(url, download_path(basename))
56
+ unless tarball_dest
57
+ message = "Url: #{url} not found"
58
+ if @options[:exit_on_error]
59
+ say message
60
+ exit
61
+ else
62
+ raise NotFound.new(message)
63
+ end
64
+ end
65
+ say "Downloaded to: #{tarball_dest}"
66
+ tarball_dest
67
+ end
68
+
69
+ # full_gem_name: byebug-9.1.0
70
+ def gem_url
71
+ data = api.download_url(gem_name: full_gem_name, project: Jets.config.project_name)
72
+ if data["download_url"]
73
+ data["download_url"]
74
+ else
75
+ puts data["message"].color(:red)
76
+ exit 1
77
+ end
78
+ end
79
+
80
+ def download_path(filename)
81
+ "#{@downloads_root}/downloads/gems/#{filename}"
82
+ end
83
+
84
+ # Finds any currently install gems that matched with the gem name and version
85
+ # and remove them first.
86
+ # We clean up the current install gems first in case it was previously installed
87
+ # and has different *.so files that can be accidentally required. This
88
+ # happened with the pg gem.
89
+ def remove_current_gem
90
+ say "Removing current #{full_gem_name} gem installation:"
91
+ gem_dirs = Dir.glob("#{project_root}/**/*").select do |path|
92
+ File.directory?(path) &&
93
+ path =~ %r{vendor/gems} &&
94
+ File.basename(path) == full_gem_name
95
+ end
96
+ gem_dirs.each do |dir|
97
+ say " rm -rf #{dir}"
98
+ FileUtils.rm_rf(dir)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,23 @@
1
+ require "net/http"
2
+
3
+ module Jets::Gems
4
+ class Report
5
+ include Jets::Gems::Api::Concern
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
+ api.report_missing(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,5 @@
1
+ module Jets
2
+ module Gems
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require "jets/gems"
@@ -0,0 +1,31 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "jets/gems/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "serverlessgems"
7
+ spec.version = Jets::Gems::VERSION
8
+ spec.authors = ["Tung Nguyen"]
9
+ spec.email = ["tongueroo@gmail.com"]
10
+
11
+ spec.summary = "Client Library works with Serverless Gems API for Jets Ruby Serverless Framework"
12
+ spec.homepage = "https://github.com/boltops-tools/serverlessgems"
13
+ spec.license = "MIT"
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "gems"
25
+ spec.add_dependency "memoist"
26
+ spec.add_dependency "zeitwerk"
27
+
28
+ spec.add_development_dependency "bundler"
29
+ spec.add_development_dependency "rake"
30
+ spec.add_development_dependency "rspec"
31
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serverlessgems
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: 2021-01-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gems
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: memoist
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: zeitwerk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - tongueroo@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - CHANGELOG.md
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - bin/console
112
+ - bin/setup
113
+ - lib/jets/gems.rb
114
+ - lib/jets/gems/agree.rb
115
+ - lib/jets/gems/api.rb
116
+ - lib/jets/gems/api/concern.rb
117
+ - lib/jets/gems/api/core.rb
118
+ - lib/jets/gems/autoloader.rb
119
+ - lib/jets/gems/check.rb
120
+ - lib/jets/gems/config.rb
121
+ - lib/jets/gems/config/token.rb
122
+ - lib/jets/gems/exist.rb
123
+ - lib/jets/gems/extract/base.rb
124
+ - lib/jets/gems/extract/gem.rb
125
+ - lib/jets/gems/report.rb
126
+ - lib/jets/gems/version.rb
127
+ - lib/serverlessgems.rb
128
+ - serverlessgems.gemspec
129
+ homepage: https://github.com/boltops-tools/serverlessgems
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.1.4
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Client Library works with Serverless Gems API for Jets Ruby Serverless Framework
152
+ test_files: []