gemstash 1.0.0.pre.1-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|