gemstash 1.0.0.pre.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +139 -0
- data/Rakefile +35 -0
- data/bin/console +14 -0
- data/bin/gemstash +3 -0
- data/bin/setup +5 -0
- data/docs/config.md +136 -0
- data/docs/debug.md +24 -0
- data/docs/deploy.md +30 -0
- data/docs/mirror.md +30 -0
- data/docs/multiple_sources.md +68 -0
- data/docs/private_gems.md +140 -0
- data/docs/reference.md +308 -0
- data/exe/gemstash +3 -0
- data/gemstash.gemspec +47 -0
- data/gemstash.png +0 -0
- data/lib/gemstash.rb +26 -0
- data/lib/gemstash/authorization.rb +87 -0
- data/lib/gemstash/cache.rb +79 -0
- data/lib/gemstash/cli.rb +71 -0
- data/lib/gemstash/cli/authorize.rb +69 -0
- data/lib/gemstash/cli/base.rb +46 -0
- data/lib/gemstash/cli/setup.rb +173 -0
- data/lib/gemstash/cli/start.rb +52 -0
- data/lib/gemstash/cli/status.rb +21 -0
- data/lib/gemstash/cli/stop.rb +21 -0
- data/lib/gemstash/config.ru +13 -0
- data/lib/gemstash/configuration.rb +41 -0
- data/lib/gemstash/db.rb +15 -0
- data/lib/gemstash/db/authorization.rb +20 -0
- data/lib/gemstash/db/dependency.rb +50 -0
- data/lib/gemstash/db/rubygem.rb +14 -0
- data/lib/gemstash/db/version.rb +51 -0
- data/lib/gemstash/dependencies.rb +93 -0
- data/lib/gemstash/env.rb +150 -0
- data/lib/gemstash/gem_fetcher.rb +50 -0
- data/lib/gemstash/gem_pusher.rb +125 -0
- data/lib/gemstash/gem_source.rb +37 -0
- data/lib/gemstash/gem_source/dependency_caching.rb +40 -0
- data/lib/gemstash/gem_source/private_source.rb +139 -0
- data/lib/gemstash/gem_source/rack_middleware.rb +22 -0
- data/lib/gemstash/gem_source/upstream_source.rb +183 -0
- data/lib/gemstash/gem_unyanker.rb +61 -0
- data/lib/gemstash/gem_yanker.rb +61 -0
- data/lib/gemstash/http_client.rb +77 -0
- data/lib/gemstash/logging.rb +93 -0
- data/lib/gemstash/migrations/01_gem_dependencies.rb +41 -0
- data/lib/gemstash/migrations/02_authorizations.rb +12 -0
- data/lib/gemstash/puma.rb +6 -0
- data/lib/gemstash/rack_env_rewriter.rb +66 -0
- data/lib/gemstash/specs_builder.rb +93 -0
- data/lib/gemstash/storage.rb +207 -0
- data/lib/gemstash/upstream.rb +65 -0
- data/lib/gemstash/version.rb +4 -0
- data/lib/gemstash/web.rb +97 -0
- metadata +304 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
|
3
|
+
module Gemstash
|
4
|
+
# Class that supports unyanking a gem from the private repository of gems.
|
5
|
+
class GemUnyanker
|
6
|
+
include Gemstash::Env::Helper
|
7
|
+
|
8
|
+
# This error is thrown when unyanking a non-existing gem name.
|
9
|
+
class UnknownGemError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
# This error is thrown when unyanking a non-existing gem version.
|
13
|
+
class UnknownVersionError < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
# This error is thrown when unyanking a non-yanked gem version.
|
17
|
+
class NotYankedVersionError < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(auth_key, gem_name, slug)
|
21
|
+
@auth_key = auth_key
|
22
|
+
@gem_name = gem_name
|
23
|
+
@slug = slug
|
24
|
+
end
|
25
|
+
|
26
|
+
def unyank
|
27
|
+
check_auth
|
28
|
+
update_database
|
29
|
+
invalidate_cache
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def storage
|
35
|
+
@storage ||= Gemstash::Storage.for("private").for("gems")
|
36
|
+
end
|
37
|
+
|
38
|
+
def full_name
|
39
|
+
@full_name ||= "#{@gem_name}-#{@slug}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_auth
|
43
|
+
Gemstash::Authorization.check(@auth_key, "unyank")
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_database
|
47
|
+
gemstash_env.db.transaction do
|
48
|
+
raise UnknownGemError, "Cannot unyank an unknown gem!" unless Gemstash::DB::Rubygem[name: @gem_name]
|
49
|
+
version = Gemstash::DB::Version.find_by_full_name(full_name)
|
50
|
+
raise UnknownVersionError, "Cannot unyank an unknown version!" unless version
|
51
|
+
raise NotYankedVersionError, "Cannot unyank a non-yanked version!" if version.indexed
|
52
|
+
version.reindex
|
53
|
+
storage.resource(version.storage_id).update_properties(indexed: true)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def invalidate_cache
|
58
|
+
gemstash_env.cache.invalidate_gem("private", @gem_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
|
3
|
+
module Gemstash
|
4
|
+
# Class that supports yanking a gem from the private repository of gems.
|
5
|
+
class GemYanker
|
6
|
+
include Gemstash::Env::Helper
|
7
|
+
|
8
|
+
# This error is thrown when yanking a non-existing gem name.
|
9
|
+
class UnknownGemError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
# This error is thrown when yanking a non-existing gem version.
|
13
|
+
class UnknownVersionError < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
# This error is thrown when yanking an already yanked gem version.
|
17
|
+
class YankedVersionError < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(auth_key, gem_name, slug)
|
21
|
+
@auth_key = auth_key
|
22
|
+
@gem_name = gem_name
|
23
|
+
@slug = slug
|
24
|
+
end
|
25
|
+
|
26
|
+
def yank
|
27
|
+
check_auth
|
28
|
+
update_database
|
29
|
+
invalidate_cache
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def storage
|
35
|
+
@storage ||= Gemstash::Storage.for("private").for("gems")
|
36
|
+
end
|
37
|
+
|
38
|
+
def full_name
|
39
|
+
@full_name ||= "#{@gem_name}-#{@slug}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_auth
|
43
|
+
Gemstash::Authorization.check(@auth_key, "yank")
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_database
|
47
|
+
gemstash_env.db.transaction do
|
48
|
+
raise UnknownGemError, "Cannot yank an unknown gem!" unless Gemstash::DB::Rubygem[name: @gem_name]
|
49
|
+
version = Gemstash::DB::Version.find_by_full_name(full_name)
|
50
|
+
raise UnknownVersionError, "Cannot yank an unknown version!" unless version
|
51
|
+
raise YankedVersionError, "Cannot yank an already yanked version!" unless version.indexed
|
52
|
+
version.deindex
|
53
|
+
storage.resource(version.storage_id).update_properties(indexed: false)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def invalidate_cache
|
58
|
+
gemstash_env.cache.invalidate_gem("private", @gem_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
require "faraday"
|
3
|
+
require "faraday_middleware"
|
4
|
+
|
5
|
+
module Gemstash
|
6
|
+
#:nodoc:
|
7
|
+
class WebError < StandardError
|
8
|
+
attr_reader :code
|
9
|
+
|
10
|
+
def initialize(message, code)
|
11
|
+
@code = code
|
12
|
+
super(message)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#:nodoc:
|
17
|
+
class ConnectionError < WebError
|
18
|
+
def initialize(message)
|
19
|
+
super(message, 502) # Bad Gateway
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#:nodoc:
|
24
|
+
class HTTPClient
|
25
|
+
include Gemstash::Logging
|
26
|
+
|
27
|
+
DEFAULT_USER_AGENT = "Gemstash/#{Gemstash::VERSION}"
|
28
|
+
|
29
|
+
def self.for(upstream)
|
30
|
+
client = Faraday.new(upstream.to_s) do |config|
|
31
|
+
config.use FaradayMiddleware::FollowRedirects
|
32
|
+
config.adapter :net_http
|
33
|
+
end
|
34
|
+
user_agent = "#{upstream.user_agent} " unless upstream.user_agent.to_s.empty?
|
35
|
+
user_agent = user_agent.to_s + DEFAULT_USER_AGENT
|
36
|
+
|
37
|
+
new(client, user_agent: user_agent)
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(client = nil, user_agent: nil)
|
41
|
+
@client = client
|
42
|
+
@user_agent = user_agent || DEFAULT_USER_AGENT
|
43
|
+
end
|
44
|
+
|
45
|
+
def get(path)
|
46
|
+
response = with_retries do
|
47
|
+
@client.get(path) do |req|
|
48
|
+
req.headers["User-Agent"] = @user_agent
|
49
|
+
req.options.open_timeout = 2
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
raise Gemstash::WebError.new(response.body, response.status) unless response.success?
|
54
|
+
|
55
|
+
if block_given?
|
56
|
+
yield(response.body, response.headers)
|
57
|
+
else
|
58
|
+
response.body
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def with_retries(times: 3, &block)
|
65
|
+
loop do
|
66
|
+
times -= 1
|
67
|
+
begin
|
68
|
+
return block.call
|
69
|
+
rescue Faraday::ConnectionFailed => e
|
70
|
+
log_error("Connection failure", e)
|
71
|
+
raise(ConnectionError, e.message) unless times > 0
|
72
|
+
log.info "retrying... #{times} more times"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "puma/events"
|
3
|
+
|
4
|
+
module Gemstash
|
5
|
+
#:nodoc:
|
6
|
+
module Logging
|
7
|
+
LEVELS = {
|
8
|
+
debug: Logger::DEBUG,
|
9
|
+
info: Logger::INFO,
|
10
|
+
warn: Logger::WARN,
|
11
|
+
error: Logger::ERROR,
|
12
|
+
fatal: Logger::FATAL
|
13
|
+
}
|
14
|
+
|
15
|
+
def log
|
16
|
+
Gemstash::Logging.logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def log_error(message, error, level: :error)
|
20
|
+
log.add(LEVELS[level]) do
|
21
|
+
"#{message} - #{error.message} (#{error.class})\n #{error.backtrace.join("\n ")}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.setup_logger(logfile)
|
26
|
+
@logger = Logger.new(logfile, 2, 10_485_760)
|
27
|
+
@logger.level = Logger::INFO
|
28
|
+
@logger.datetime_format = "%d/%b/%Y:%H:%M:%S %z"
|
29
|
+
@logger.formatter = proc do |severity, datetime, _progname, msg|
|
30
|
+
if msg.end_with?("\n")
|
31
|
+
"[#{datetime}] - #{severity} - #{msg}"
|
32
|
+
else
|
33
|
+
"[#{datetime}] - #{severity} - #{msg}\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@logger
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.logger
|
40
|
+
@logger ||= setup_logger($stdout)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.reset
|
44
|
+
@logger.close if @logger
|
45
|
+
@logger = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Rack middleware to set the Rack logger to the Gemstash logger.
|
49
|
+
class RackMiddleware
|
50
|
+
def initialize(app)
|
51
|
+
@app = app
|
52
|
+
end
|
53
|
+
|
54
|
+
def call(env)
|
55
|
+
env["rack.logger"] = Gemstash::Logging.logger
|
56
|
+
@app.call(env)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Logger that looks like a stream, for Puma and Rack to log to.
|
61
|
+
class StreamLogger
|
62
|
+
def self.puma_events
|
63
|
+
Puma::Events.new(for_stdout, for_stderr)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.for_stdout
|
67
|
+
new(Logger::INFO)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.for_stderr
|
71
|
+
new(Logger::ERROR)
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(level)
|
75
|
+
@level = level
|
76
|
+
end
|
77
|
+
|
78
|
+
def flush
|
79
|
+
end
|
80
|
+
|
81
|
+
def sync=(_value)
|
82
|
+
end
|
83
|
+
|
84
|
+
def write(message)
|
85
|
+
Gemstash::Logging.logger.add(@level, message)
|
86
|
+
end
|
87
|
+
|
88
|
+
def puts(message)
|
89
|
+
Gemstash::Logging.logger.add(@level, message)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table :rubygems do
|
4
|
+
primary_key :id
|
5
|
+
String :name, :size => 255, :null => false
|
6
|
+
DateTime :created_at, :null => false
|
7
|
+
DateTime :updated_at, :null => false
|
8
|
+
index [:name], :unique => true
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :versions do
|
12
|
+
primary_key :id
|
13
|
+
Integer :rubygem_id, :null => false
|
14
|
+
String :storage_id, :size => 255, :null => false
|
15
|
+
String :number, :size => 255, :null => false
|
16
|
+
String :platform, :size => 255, :null => false
|
17
|
+
String :full_name, :size => 255, :null => false
|
18
|
+
TrueClass :indexed, :default => true, :null => false
|
19
|
+
TrueClass :prerelease, :null => false
|
20
|
+
DateTime :created_at, :null => false
|
21
|
+
DateTime :updated_at, :null => false
|
22
|
+
index [:rubygem_id, :number, :platform], :unique => true
|
23
|
+
index [:indexed]
|
24
|
+
index [:indexed, :prerelease]
|
25
|
+
index [:number]
|
26
|
+
index [:full_name], :unique => true
|
27
|
+
index [:storage_id], :unique => true
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table :dependencies do
|
31
|
+
primary_key :id
|
32
|
+
Integer :version_id, :null => false
|
33
|
+
String :rubygem_name, :size => 255, :null => false
|
34
|
+
String :requirements, :size => 255, :null => false
|
35
|
+
DateTime :created_at, :null => false
|
36
|
+
DateTime :updated_at, :null => false
|
37
|
+
index [:version_id]
|
38
|
+
index [:rubygem_name]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table :authorizations do
|
4
|
+
primary_key :id
|
5
|
+
String :auth_key, :size => 2056, :null => false
|
6
|
+
String :permissions, :size => 255, :null => false
|
7
|
+
DateTime :created_at, :null => false
|
8
|
+
DateTime :updated_at, :null => false
|
9
|
+
index [:auth_key], :unique => true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
module Gemstash
|
5
|
+
# Detects patterns in the Rack env variables related to URI and rewrites
|
6
|
+
# them, extracting parameters.
|
7
|
+
class RackEnvRewriter
|
8
|
+
attr_reader :regexp
|
9
|
+
|
10
|
+
def initialize(regexp)
|
11
|
+
@regexp = regexp
|
12
|
+
end
|
13
|
+
|
14
|
+
def for(rack_env)
|
15
|
+
Context.new(self, rack_env)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Context containing the logic and the actual Rack environment.
|
19
|
+
class Context
|
20
|
+
include Gemstash::Logging
|
21
|
+
extend Forwardable
|
22
|
+
def_delegators :@rewriter, :regexp
|
23
|
+
|
24
|
+
def initialize(rewriter, rack_env)
|
25
|
+
@rewriter = rewriter
|
26
|
+
@rack_env = rack_env
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches?
|
30
|
+
matches_request_uri? && matches_path_info?
|
31
|
+
end
|
32
|
+
|
33
|
+
def rewrite
|
34
|
+
check_match
|
35
|
+
log_start = "Rewriting '#{@rack_env["REQUEST_URI"]}'"
|
36
|
+
@rack_env["REQUEST_URI"][@request_uri_match.begin(0)...@request_uri_match.end(0)] = ""
|
37
|
+
@rack_env["PATH_INFO"][@path_info_match.begin(0)...@path_info_match.end(0)] = ""
|
38
|
+
log.info "#{log_start} to '#{@rack_env["REQUEST_URI"]}'"
|
39
|
+
end
|
40
|
+
|
41
|
+
def captures
|
42
|
+
@params ||= begin
|
43
|
+
check_match
|
44
|
+
@path_info_match.names.inject({}) do |result, name|
|
45
|
+
result[name] = @path_info_match[name]
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def matches_request_uri?
|
54
|
+
@request_uri_match ||= @rack_env["REQUEST_URI"].match(regexp)
|
55
|
+
end
|
56
|
+
|
57
|
+
def matches_path_info?
|
58
|
+
@path_info_match ||= @rack_env["PATH_INFO"].match(regexp)
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_match
|
62
|
+
raise "Rack env did not match!" unless @request_uri_match && @path_info_match
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
require "stringio"
|
3
|
+
require "zlib"
|
4
|
+
|
5
|
+
module Gemstash
|
6
|
+
# Builds a Marshal'ed and GZipped array of arrays containing specs as:
|
7
|
+
# [name, Gem::Version, platform]
|
8
|
+
class SpecsBuilder
|
9
|
+
attr_reader :result
|
10
|
+
|
11
|
+
# Used for the /private/specs.4.8.gz endpoint. Fetches non-prerelease,
|
12
|
+
# indexed private gems.
|
13
|
+
def self.all
|
14
|
+
new.build
|
15
|
+
end
|
16
|
+
|
17
|
+
# Used for the /private/prerelease_specs.4.8.gz endpoint. Fetches
|
18
|
+
# prerelease, indexed private gems.
|
19
|
+
def self.prerelease
|
20
|
+
new(prerelease: true).build
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.invalidate_stored
|
24
|
+
storage = Gemstash::Storage.for("private").for("specs_collection")
|
25
|
+
storage.resource("specs.4.8.gz").delete(:specs)
|
26
|
+
storage.resource("prerelease_specs.4.8.gz").delete(:specs)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(prerelease: false)
|
30
|
+
@prerelease = prerelease
|
31
|
+
end
|
32
|
+
|
33
|
+
def build
|
34
|
+
fetch_from_storage
|
35
|
+
return result if result
|
36
|
+
fetch_versions
|
37
|
+
marshal
|
38
|
+
gzip
|
39
|
+
store_result
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def storage
|
46
|
+
@storage ||= Gemstash::Storage.for("private").for("specs_collection")
|
47
|
+
end
|
48
|
+
|
49
|
+
def fetch_resource
|
50
|
+
if @prerelease
|
51
|
+
storage.resource("prerelease_specs.4.8.gz")
|
52
|
+
else
|
53
|
+
storage.resource("specs.4.8.gz")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def fetch_from_storage
|
58
|
+
specs = fetch_resource
|
59
|
+
return unless specs.exist?(:specs)
|
60
|
+
@result = specs.load(:specs).content(:specs)
|
61
|
+
rescue
|
62
|
+
# On the off-chance of a race condition between specs.exist? and specs.load
|
63
|
+
@result = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def fetch_versions
|
67
|
+
@versions = Gemstash::DB::Version.for_spec_collection(prerelease: @prerelease).map(&:to_spec)
|
68
|
+
end
|
69
|
+
|
70
|
+
def marshal
|
71
|
+
@marshal ||= Marshal.dump(@versions)
|
72
|
+
end
|
73
|
+
|
74
|
+
def gzip
|
75
|
+
@result ||= begin
|
76
|
+
output = StringIO.new
|
77
|
+
gz = Zlib::GzipWriter.new(output)
|
78
|
+
|
79
|
+
begin
|
80
|
+
gz.write(@marshal)
|
81
|
+
ensure
|
82
|
+
gz.close
|
83
|
+
end
|
84
|
+
|
85
|
+
output.string
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def store_result
|
90
|
+
fetch_resource.save(specs: @result)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|