gemstash 1.0.0.pre.1
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 +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
|