serverlessgems 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.
@@ -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: []