gemstash 1.0.0.pre.1
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 +306 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
module Gemstash
|
5
|
+
#:nodoc:
|
6
|
+
class GemFetcher
|
7
|
+
def initialize(http_client)
|
8
|
+
@http_client = http_client
|
9
|
+
@valid_headers = Set.new(["etag", "content-type", "content-length", "last-modified"])
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch(gem_id, type, &block)
|
13
|
+
@http_client.get(path_for(gem_id, type)) do |body, headers|
|
14
|
+
properties = filter_headers(headers)
|
15
|
+
validate_download(body, properties)
|
16
|
+
yield body, properties
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def path_for(gem_id, type)
|
23
|
+
case type
|
24
|
+
when :gem
|
25
|
+
"gems/#{gem_id}"
|
26
|
+
when :spec
|
27
|
+
"quick/Marshal.4.8/#{gem_id}"
|
28
|
+
else
|
29
|
+
raise "Invalid type #{type.inspect}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def filter_headers(headers)
|
34
|
+
headers.inject({}) do|properties, (key, value)|
|
35
|
+
properties[key.downcase] = value if @valid_headers.include?(key.downcase)
|
36
|
+
properties
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_download(content, headers)
|
41
|
+
expected_size = content_length(headers)
|
42
|
+
raise "Incomplete download, only #{body.length} was downloaded out of #{expected_size}" \
|
43
|
+
if content.length < expected_size
|
44
|
+
end
|
45
|
+
|
46
|
+
def content_length(headers)
|
47
|
+
headers["content-length"].to_i
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
require "rubygems/package"
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
#:nodoc:
|
6
|
+
module Gemstash
|
7
|
+
# Class that supports pushing a new gem to the private repository of gems.
|
8
|
+
class GemPusher
|
9
|
+
include Gemstash::Env::Helper
|
10
|
+
|
11
|
+
# This error is thrown when pushing to an existing version.
|
12
|
+
class ExistingVersionError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
# This error is thrown when pushing to a yanked version.
|
16
|
+
class YankedVersionError < ExistingVersionError
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(auth_key, content)
|
20
|
+
@auth_key = auth_key
|
21
|
+
@content = content
|
22
|
+
end
|
23
|
+
|
24
|
+
def push
|
25
|
+
check_auth
|
26
|
+
store_gem
|
27
|
+
store_gemspec
|
28
|
+
save_to_database
|
29
|
+
invalidate_cache
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def gem
|
35
|
+
@gem ||= Gem::Package.new(StringIO.new(@content))
|
36
|
+
end
|
37
|
+
|
38
|
+
def storage
|
39
|
+
@storage ||= Gemstash::Storage.for("private").for("gems")
|
40
|
+
end
|
41
|
+
|
42
|
+
def full_name
|
43
|
+
@full_name ||= gem.spec.full_name
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_auth
|
47
|
+
Gemstash::Authorization.check(@auth_key, "push")
|
48
|
+
end
|
49
|
+
|
50
|
+
def store_gem
|
51
|
+
storage.resource(full_name).save({ gem: @content }, indexed: true)
|
52
|
+
end
|
53
|
+
|
54
|
+
def store_gemspec
|
55
|
+
spec = gem.spec
|
56
|
+
spec = Marshal.dump(spec)
|
57
|
+
spec = Zlib::Deflate.deflate(spec)
|
58
|
+
storage.resource(full_name).save(spec: spec)
|
59
|
+
end
|
60
|
+
|
61
|
+
def save_to_database
|
62
|
+
spec = gem.spec
|
63
|
+
|
64
|
+
gemstash_env.db.transaction do
|
65
|
+
gem_id = Gemstash::DB::Rubygem.find_or_insert(spec)
|
66
|
+
existing = Gemstash::DB::Version.find_by_spec(gem_id, spec)
|
67
|
+
|
68
|
+
if existing
|
69
|
+
if existing.indexed
|
70
|
+
raise ExistingVersionError, "Cannot push to an existing version!"
|
71
|
+
else
|
72
|
+
raise YankedVersionError, "Cannot push to a yanked version!"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
version_id = Gemstash::DB::Version.insert_by_spec(gem_id, spec)
|
77
|
+
Gemstash::DB::Dependency.insert_by_spec(version_id, spec)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def invalidate_cache
|
82
|
+
gemstash_env.cache.invalidate_gem("private", gem.spec.name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
unless Gem::Requirement.new(">= 2.4").satisfied_by?(Gem::Version.new(Gem::VERSION))
|
87
|
+
require "tempfile"
|
88
|
+
|
89
|
+
# Adds support for legacy versions of RubyGems
|
90
|
+
module LegacyRubyGemsSupport
|
91
|
+
def self.included(base)
|
92
|
+
base.class_eval do
|
93
|
+
alias_method :push_without_cleanup, :push
|
94
|
+
remove_method :push
|
95
|
+
remove_method :gem
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def push
|
100
|
+
push_without_cleanup
|
101
|
+
ensure
|
102
|
+
cleanup
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def gem
|
108
|
+
@gem ||= begin
|
109
|
+
@tempfile = Tempfile.new("gemstash-gem")
|
110
|
+
@tempfile.write(@content)
|
111
|
+
@tempfile.flush
|
112
|
+
Gem::Package.new(@tempfile.path)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def cleanup
|
117
|
+
return unless @tempfile
|
118
|
+
@tempfile.close
|
119
|
+
@tempfile.unlink
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
GemPusher.send(:include, LegacyRubyGemsSupport)
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
module Gemstash
|
5
|
+
#:nodoc:
|
6
|
+
module GemSource
|
7
|
+
autoload :DependencyCaching, "gemstash/gem_source/dependency_caching"
|
8
|
+
autoload :PrivateSource, "gemstash/gem_source/private_source"
|
9
|
+
autoload :RackMiddleware, "gemstash/gem_source/rack_middleware"
|
10
|
+
autoload :RedirectSource, "gemstash/gem_source/upstream_source"
|
11
|
+
autoload :RubygemsSource, "gemstash/gem_source/upstream_source"
|
12
|
+
autoload :UpstreamSource, "gemstash/gem_source/upstream_source"
|
13
|
+
|
14
|
+
def self.sources
|
15
|
+
@sources ||= [
|
16
|
+
Gemstash::GemSource::PrivateSource,
|
17
|
+
Gemstash::GemSource::RedirectSource,
|
18
|
+
Gemstash::GemSource::UpstreamSource,
|
19
|
+
Gemstash::GemSource::RubygemsSource
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Base GemSource for some common utilities.
|
24
|
+
class Base
|
25
|
+
extend Forwardable
|
26
|
+
extend Gemstash::Logging
|
27
|
+
include Gemstash::Logging
|
28
|
+
|
29
|
+
def_delegators :@app, :cache_control, :content_type, :env, :halt,
|
30
|
+
:headers, :http_client_for, :params, :redirect, :request
|
31
|
+
|
32
|
+
def initialize(app)
|
33
|
+
@app = app
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Gemstash
|
2
|
+
module GemSource
|
3
|
+
# Module for caching dependencies in a GemSource.
|
4
|
+
module DependencyCaching
|
5
|
+
API_REQUEST_LIMIT = 200
|
6
|
+
|
7
|
+
def serve_dependencies
|
8
|
+
gems = gems_from_params
|
9
|
+
|
10
|
+
if gems.length > API_REQUEST_LIMIT
|
11
|
+
halt 422, "Too many gems (use --full-index instead)"
|
12
|
+
end
|
13
|
+
|
14
|
+
content_type "application/octet-stream"
|
15
|
+
Marshal.dump dependencies.fetch(gems)
|
16
|
+
end
|
17
|
+
|
18
|
+
def serve_dependencies_json
|
19
|
+
gems = gems_from_params
|
20
|
+
|
21
|
+
if gems.length > API_REQUEST_LIMIT
|
22
|
+
halt 422, {
|
23
|
+
"error" => "Too many gems (use --full-index instead)",
|
24
|
+
"code" => 422
|
25
|
+
}.to_json
|
26
|
+
end
|
27
|
+
|
28
|
+
content_type "application/json;charset=UTF-8"
|
29
|
+
dependencies.fetch(gems).to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def gems_from_params
|
35
|
+
halt(200) if params[:gems].nil? || params[:gems].empty?
|
36
|
+
params[:gems].split(",").uniq
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
|
3
|
+
module Gemstash
|
4
|
+
module GemSource
|
5
|
+
# GemSource for privately stored gems.
|
6
|
+
class PrivateSource < Gemstash::GemSource::Base
|
7
|
+
include Gemstash::GemSource::DependencyCaching
|
8
|
+
include Gemstash::Env::Helper
|
9
|
+
|
10
|
+
def self.rack_env_rewriter
|
11
|
+
@rack_env_rewriter ||= Gemstash::RackEnvRewriter.new(%r{\A/private})
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.matches?(env)
|
15
|
+
rewriter = rack_env_rewriter.for(env)
|
16
|
+
return false unless rewriter.matches?
|
17
|
+
rewriter.rewrite
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def serve_root
|
22
|
+
halt 403, "Not yet supported"
|
23
|
+
end
|
24
|
+
|
25
|
+
def serve_add_gem
|
26
|
+
authenticated("Gemstash Private Gems") do
|
27
|
+
auth = request.env["HTTP_AUTHORIZATION"]
|
28
|
+
gem = request.body.read
|
29
|
+
Gemstash::GemPusher.new(auth, gem).push
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def serve_yank
|
34
|
+
authenticated("Gemstash Private Gems") do
|
35
|
+
auth = request.env["HTTP_AUTHORIZATION"]
|
36
|
+
gem_name = params[:gem_name]
|
37
|
+
Gemstash::GemYanker.new(auth, gem_name, slug_param).yank
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def serve_unyank
|
42
|
+
authenticated("Gemstash Private Gems") do
|
43
|
+
auth = request.env["HTTP_AUTHORIZATION"]
|
44
|
+
gem_name = params[:gem_name]
|
45
|
+
Gemstash::GemUnyanker.new(auth, gem_name, slug_param).unyank
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def serve_add_spec_json
|
50
|
+
halt 403, "Not yet supported"
|
51
|
+
end
|
52
|
+
|
53
|
+
def serve_remove_spec_json
|
54
|
+
halt 403, "Not yet supported"
|
55
|
+
end
|
56
|
+
|
57
|
+
def serve_names
|
58
|
+
halt 403, "Not yet supported"
|
59
|
+
end
|
60
|
+
|
61
|
+
def serve_versions
|
62
|
+
halt 403, "Not yet supported"
|
63
|
+
end
|
64
|
+
|
65
|
+
def serve_info(name)
|
66
|
+
halt 403, "Not yet supported"
|
67
|
+
end
|
68
|
+
|
69
|
+
def serve_marshal(id)
|
70
|
+
gem_full_name = id.sub(/\.gemspec\.rz\z/, "")
|
71
|
+
gem = fetch_gem(gem_full_name)
|
72
|
+
halt 404 unless gem.exist?(:spec)
|
73
|
+
content_type "application/octet-stream"
|
74
|
+
gem.load(:spec).content(:spec)
|
75
|
+
end
|
76
|
+
|
77
|
+
def serve_actual_gem(id)
|
78
|
+
halt 403, "Not yet supported"
|
79
|
+
end
|
80
|
+
|
81
|
+
def serve_gem(id)
|
82
|
+
gem_full_name = id.sub(/\.gem\z/, "")
|
83
|
+
gem = fetch_gem(gem_full_name)
|
84
|
+
content_type "application/octet-stream"
|
85
|
+
gem.content(:gem)
|
86
|
+
end
|
87
|
+
|
88
|
+
def serve_latest_specs
|
89
|
+
halt 403, "Not yet supported"
|
90
|
+
end
|
91
|
+
|
92
|
+
def serve_specs
|
93
|
+
content_type "application/octet-stream"
|
94
|
+
Gemstash::SpecsBuilder.all
|
95
|
+
end
|
96
|
+
|
97
|
+
def serve_prerelease_specs
|
98
|
+
content_type "application/octet-stream"
|
99
|
+
Gemstash::SpecsBuilder.prerelease
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def slug_param
|
105
|
+
version = params[:version]
|
106
|
+
platform = params[:platform]
|
107
|
+
|
108
|
+
if platform.to_s.empty?
|
109
|
+
version
|
110
|
+
else
|
111
|
+
"#{version}-#{platform}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def authenticated(realm)
|
116
|
+
yield
|
117
|
+
rescue Gemstash::NotAuthorizedError => e
|
118
|
+
headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
|
119
|
+
halt 401, e.message
|
120
|
+
end
|
121
|
+
|
122
|
+
def dependencies
|
123
|
+
@dependencies ||= Gemstash::Dependencies.for_private
|
124
|
+
end
|
125
|
+
|
126
|
+
def storage
|
127
|
+
@storage ||= Gemstash::Storage.for("private").for("gems")
|
128
|
+
end
|
129
|
+
|
130
|
+
def fetch_gem(gem_full_name)
|
131
|
+
gem = storage.resource(gem_full_name)
|
132
|
+
halt 404 unless gem.exist?(:gem)
|
133
|
+
gem.load(:gem)
|
134
|
+
halt 403, "That gem has been yanked" unless gem.properties[:indexed]
|
135
|
+
gem
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
|
3
|
+
module Gemstash
|
4
|
+
module GemSource
|
5
|
+
# Rack middleware to detect the gem source from the URL.
|
6
|
+
class RackMiddleware
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
Gemstash::GemSource.sources.each do |source|
|
13
|
+
next unless source.matches?(env)
|
14
|
+
env["gemstash.gem_source"] = source
|
15
|
+
break
|
16
|
+
end
|
17
|
+
|
18
|
+
@app.call(env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require "gemstash"
|
2
|
+
require "cgi"
|
3
|
+
|
4
|
+
module Gemstash
|
5
|
+
module GemSource
|
6
|
+
# GemSource that purely redirects to the upstream server.
|
7
|
+
class RedirectSource < Gemstash::GemSource::Base
|
8
|
+
def self.rack_env_rewriter
|
9
|
+
@rack_env_rewriter ||= Gemstash::RackEnvRewriter.new(%r{\A/redirect/(?<upstream_url>[^/]+)})
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.matches?(env)
|
13
|
+
rewriter = rack_env_rewriter.for(env)
|
14
|
+
return false unless rewriter.matches?
|
15
|
+
rewriter.rewrite
|
16
|
+
env["gemstash.upstream"] = rewriter.captures["upstream_url"]
|
17
|
+
capture_user_agent(env)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.capture_user_agent(env)
|
22
|
+
env["gemstash.user-agent"] = env["HTTP_USER_AGENT"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def serve_root
|
26
|
+
cache_control :public, :max_age => 31_536_000
|
27
|
+
redirect upstream.url(nil, request.query_string)
|
28
|
+
end
|
29
|
+
|
30
|
+
def serve_add_gem
|
31
|
+
halt 403, "Cannot add gem to an upstream server!"
|
32
|
+
end
|
33
|
+
|
34
|
+
def serve_yank
|
35
|
+
halt 403, "Cannot yank from an upstream server!"
|
36
|
+
end
|
37
|
+
|
38
|
+
def serve_unyank
|
39
|
+
halt 403, "Cannot unyank from an upstream server!"
|
40
|
+
end
|
41
|
+
|
42
|
+
def serve_add_spec_json
|
43
|
+
halt 403, "Cannot add spec to an upstream server!"
|
44
|
+
end
|
45
|
+
|
46
|
+
def serve_remove_spec_json
|
47
|
+
halt 403, "Cannot remove spec from an upstream server!"
|
48
|
+
end
|
49
|
+
|
50
|
+
def serve_dependencies
|
51
|
+
redirect upstream.url("api/v1/dependencies", request.query_string)
|
52
|
+
end
|
53
|
+
|
54
|
+
def serve_dependencies_json
|
55
|
+
redirect upstream.url("api/v1/dependencies.json", request.query_string)
|
56
|
+
end
|
57
|
+
|
58
|
+
def serve_names
|
59
|
+
redirect upstream.url("names", request.query_string)
|
60
|
+
end
|
61
|
+
|
62
|
+
def serve_versions
|
63
|
+
redirect upstream.url("versions", request.query_string)
|
64
|
+
end
|
65
|
+
|
66
|
+
def serve_info(name)
|
67
|
+
redirect upstream.url("info/#{name}", request.query_string)
|
68
|
+
end
|
69
|
+
|
70
|
+
def serve_marshal(id)
|
71
|
+
redirect upstream.url("quick/Marshal.4.8/#{id}", request.query_string)
|
72
|
+
end
|
73
|
+
|
74
|
+
def serve_actual_gem(id)
|
75
|
+
redirect upstream.url("fetch/actual/gem/#{id}", request.query_string)
|
76
|
+
end
|
77
|
+
|
78
|
+
def serve_gem(id)
|
79
|
+
redirect upstream.url("gems/#{id}", request.query_string)
|
80
|
+
end
|
81
|
+
|
82
|
+
def serve_latest_specs
|
83
|
+
redirect upstream.url("latest_specs.4.8.gz", request.query_string)
|
84
|
+
end
|
85
|
+
|
86
|
+
def serve_specs
|
87
|
+
redirect upstream.url("specs.4.8.gz", request.query_string)
|
88
|
+
end
|
89
|
+
|
90
|
+
def serve_prerelease_specs
|
91
|
+
redirect upstream.url("prerelease_specs.4.8.gz", request.query_string)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def upstream
|
97
|
+
@upstream ||= Gemstash::Upstream.new(env["gemstash.upstream"],
|
98
|
+
user_agent: env["gemstash.user-agent"])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# GemSource for gems in an upstream server.
|
103
|
+
class UpstreamSource < Gemstash::GemSource::RedirectSource
|
104
|
+
include Gemstash::GemSource::DependencyCaching
|
105
|
+
include Gemstash::Env::Helper
|
106
|
+
|
107
|
+
def self.rack_env_rewriter
|
108
|
+
@rack_env_rewriter ||= Gemstash::RackEnvRewriter.new(%r{\A/upstream/(?<upstream_url>[^/]+)})
|
109
|
+
end
|
110
|
+
|
111
|
+
def serve_marshal(id)
|
112
|
+
serve_cached(id, :spec)
|
113
|
+
end
|
114
|
+
|
115
|
+
def serve_gem(id)
|
116
|
+
serve_cached(id, :gem)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def serve_cached(id, key)
|
122
|
+
gem = fetch_gem(id, key)
|
123
|
+
headers.update(gem.properties[:headers][key]) if gem.properties[:headers] && gem.properties[:headers][key]
|
124
|
+
gem.content(key)
|
125
|
+
rescue Gemstash::WebError => e
|
126
|
+
halt e.code
|
127
|
+
end
|
128
|
+
|
129
|
+
def dependencies
|
130
|
+
@dependencies ||= begin
|
131
|
+
http_client = http_client_for(upstream)
|
132
|
+
Gemstash::Dependencies.for_upstream(upstream, http_client)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def storage
|
137
|
+
@storage ||= Gemstash::Storage.for("gem_cache")
|
138
|
+
@storage.for(upstream.host_id)
|
139
|
+
end
|
140
|
+
|
141
|
+
def gem_fetcher
|
142
|
+
@gem_fetcher ||= Gemstash::GemFetcher.new(http_client_for(upstream))
|
143
|
+
end
|
144
|
+
|
145
|
+
def fetch_gem(id, key)
|
146
|
+
gem_name = Gemstash::Upstream::GemName.new(upstream, id)
|
147
|
+
gem_resource = storage.resource(gem_name.name)
|
148
|
+
if gem_resource.exist?(key)
|
149
|
+
fetch_local_gem(gem_name, gem_resource, key)
|
150
|
+
else
|
151
|
+
fetch_remote_gem(gem_name, gem_resource, key)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def fetch_local_gem(gem_name, gem_resource, key)
|
156
|
+
log.info "Gem #{gem_name.name} exists, returning cached #{key}"
|
157
|
+
gem_resource.load(key)
|
158
|
+
end
|
159
|
+
|
160
|
+
def fetch_remote_gem(gem_name, gem_resource, key)
|
161
|
+
log.info "Gem #{gem_name.name} is not cached, fetching #{key}"
|
162
|
+
gem_fetcher.fetch(gem_name.id, key) do |content, properties|
|
163
|
+
gem_resource.save({ key => content }, headers: { key => properties })
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# GemSource for https://rubygems.org (specifically when defined by using the
|
169
|
+
# default upstream).
|
170
|
+
class RubygemsSource < Gemstash::GemSource::UpstreamSource
|
171
|
+
def self.matches?(env)
|
172
|
+
if env["HTTP_X_GEMFILE_SOURCE"].to_s.empty?
|
173
|
+
env["gemstash.upstream"] = env["gemstash.env"].config[:rubygems_url]
|
174
|
+
else
|
175
|
+
env["gemstash.upstream"] = env["HTTP_X_GEMFILE_SOURCE"]
|
176
|
+
end
|
177
|
+
capture_user_agent(env)
|
178
|
+
|
179
|
+
true
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|