geminabox 3.0.0 → 4.0.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.
- checksums.yaml +4 -4
- data/README.md +151 -52
- data/lib/geminabox/compact_index_api.rb +77 -0
- data/lib/geminabox/compact_indexer.rb +280 -0
- data/lib/geminabox/gem_version.rb +8 -0
- data/lib/geminabox/gem_version_collection.rb +12 -0
- data/lib/geminabox/hostess.rb +4 -1
- data/lib/geminabox/server.rb +64 -49
- data/lib/geminabox/version.rb +1 -1
- data/lib/geminabox.rb +28 -13
- data/public/master.css +377 -107
- data/public/master.js +133 -10
- data/views/_version_row.erb +16 -0
- data/views/gem.erb +14 -27
- data/views/index.erb +43 -48
- data/views/layout.erb +16 -0
- data/views/upload.erb +3 -5
- metadata +34 -11
- data/lib/geminabox/gem_list_merge.rb +0 -40
- data/lib/geminabox/proxy/copier.rb +0 -37
- data/lib/geminabox/proxy/file_handler.rb +0 -97
- data/lib/geminabox/proxy/hostess.rb +0 -78
- data/lib/geminabox/proxy/splicer.rb +0 -85
- data/lib/geminabox/proxy.rb +0 -14
- data/lib/geminabox/rubygems_dependency.rb +0 -32
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'rubygems/util'
|
|
4
|
+
|
|
3
5
|
module Geminabox
|
|
4
6
|
|
|
5
7
|
# This class represents a sorted collection of Geminabox::GemVersion objects.
|
|
@@ -7,6 +9,16 @@ module Geminabox
|
|
|
7
9
|
class GemVersionCollection
|
|
8
10
|
include Enumerable
|
|
9
11
|
|
|
12
|
+
# Loads the collection recorded in the legacy Marshal specs indexes
|
|
13
|
+
# (specs.4.8.gz and prerelease_specs.4.8.gz) under data_dir.
|
|
14
|
+
def self.from_specs_index(data_dir)
|
|
15
|
+
specs = %w[specs prerelease_specs].flat_map do |type|
|
|
16
|
+
path = File.join(data_dir, "#{type}.#{Gem.marshal_version}.gz")
|
|
17
|
+
File.exist?(path) ? Marshal.load(Gem::Util.gunzip(Gem.read_binary(path))) : []
|
|
18
|
+
end
|
|
19
|
+
new(specs.uniq)
|
|
20
|
+
end
|
|
21
|
+
|
|
10
22
|
# Array of Geminabox::GemVersion objects, or an array of [name, version,
|
|
11
23
|
# platform] triples.
|
|
12
24
|
def initialize(initial_gems=[])
|
data/lib/geminabox/hostess.rb
CHANGED
|
@@ -23,7 +23,10 @@ module Geminabox
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
get '/quick/Marshal.4.8/*.gemspec.rz' do
|
|
26
|
-
|
|
26
|
+
# rubygems.org serves the deflated quick specs as octet-stream, and the
|
|
27
|
+
# compact-index conformance suite asserts it; clients inflate the body
|
|
28
|
+
# regardless of this header.
|
|
29
|
+
content_type('application/octet-stream')
|
|
27
30
|
serve
|
|
28
31
|
end
|
|
29
32
|
|
data/lib/geminabox/server.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'reentrant_flock'
|
|
4
4
|
require 'rubygems/util'
|
|
5
|
+
require 'rss'
|
|
5
6
|
|
|
6
7
|
module Geminabox
|
|
7
8
|
|
|
@@ -11,11 +12,7 @@ module Geminabox
|
|
|
11
12
|
set :views, Geminabox.views
|
|
12
13
|
set :host_authorization, { permitted_hosts: [] }
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
use Proxy::Hostess
|
|
16
|
-
else
|
|
17
|
-
use Hostess
|
|
18
|
-
end
|
|
15
|
+
use Hostess
|
|
19
16
|
|
|
20
17
|
class << self
|
|
21
18
|
def disallow_replace?
|
|
@@ -42,6 +39,7 @@ module Geminabox
|
|
|
42
39
|
if force_rebuild
|
|
43
40
|
indexer.generate_index
|
|
44
41
|
dependency_cache.flush
|
|
42
|
+
compact_indexer.reindex
|
|
45
43
|
else
|
|
46
44
|
begin
|
|
47
45
|
require 'geminabox/indexer'
|
|
@@ -49,6 +47,7 @@ module Geminabox
|
|
|
49
47
|
return if updated_gemspecs.empty?
|
|
50
48
|
indexer.update_index
|
|
51
49
|
updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
|
|
50
|
+
compact_indexer.reindex
|
|
52
51
|
rescue Errno::ENOENT
|
|
53
52
|
with_rlock { reindex(:force_rebuild) }
|
|
54
53
|
rescue => e
|
|
@@ -68,6 +67,10 @@ module Geminabox
|
|
|
68
67
|
@dependency_cache ||= Geminabox::DiskCache.new(File.join(Geminabox.data, "_cache"))
|
|
69
68
|
end
|
|
70
69
|
|
|
70
|
+
def compact_indexer
|
|
71
|
+
Geminabox::CompactIndexer.new(Geminabox.data)
|
|
72
|
+
end
|
|
73
|
+
|
|
71
74
|
def with_rlock(&block)
|
|
72
75
|
file_class.open(Geminabox.lockfile, File::RDWR | File::CREAT) do |f|
|
|
73
76
|
ReentrantFlock.synchronize(f, File::LOCK_EX | File::LOCK_NB, &block)
|
|
@@ -90,6 +93,7 @@ module Geminabox
|
|
|
90
93
|
end
|
|
91
94
|
|
|
92
95
|
get '/' do
|
|
96
|
+
content_type :html
|
|
93
97
|
@gems = load_gems
|
|
94
98
|
@index_gems = index_gems(@gems)
|
|
95
99
|
@allow_upload = self.class.allow_upload?
|
|
@@ -98,15 +102,40 @@ module Geminabox
|
|
|
98
102
|
end
|
|
99
103
|
|
|
100
104
|
get '/atom.xml' do
|
|
105
|
+
content_type 'application/atom+xml'
|
|
101
106
|
@gems = load_gems
|
|
102
107
|
erb :atom, :layout => false
|
|
103
108
|
end
|
|
104
109
|
|
|
110
|
+
helpers Geminabox::CompactIndexApi
|
|
111
|
+
|
|
112
|
+
get '/versions' do
|
|
113
|
+
bootstrap_compact_index(self.class.compact_indexer.versions_path)
|
|
114
|
+
serve_compact_file(self.class.compact_indexer.versions_path)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
get '/names' do
|
|
118
|
+
bootstrap_compact_index(self.class.compact_indexer.names_path)
|
|
119
|
+
serve_compact_file(self.class.compact_indexer.names_path)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
get '/info/:name' do
|
|
123
|
+
# Reject anything that is not a gem-name shape before touching the
|
|
124
|
+
# filesystem. This also blocks NUL bytes and empty names, which would
|
|
125
|
+
# otherwise raise ArgumentError from File.file? and 500.
|
|
126
|
+
halt 404 unless params[:name] =~ /\A[a-zA-Z0-9_.-]+\z/
|
|
127
|
+
path = self.class.compact_indexer.info_path(params[:name])
|
|
128
|
+
heal_missing_info(params[:name], path) unless File.file?(path)
|
|
129
|
+
serve_compact_file(path)
|
|
130
|
+
end
|
|
131
|
+
|
|
105
132
|
get '/api/v1/dependencies' do
|
|
133
|
+
content_type 'application/octet-stream'
|
|
106
134
|
query_gems.any? ? Marshal.dump(gem_list) : 200
|
|
107
135
|
end
|
|
108
136
|
|
|
109
137
|
get '/api/v1/dependencies.json' do
|
|
138
|
+
content_type :json
|
|
110
139
|
query_gems.any? ? gem_list.to_json : {}
|
|
111
140
|
end
|
|
112
141
|
|
|
@@ -115,6 +144,7 @@ module Geminabox
|
|
|
115
144
|
error_response(403, 'Gem uploading is disabled')
|
|
116
145
|
end
|
|
117
146
|
|
|
147
|
+
content_type :html
|
|
118
148
|
erb :upload
|
|
119
149
|
end
|
|
120
150
|
|
|
@@ -135,6 +165,7 @@ module Geminabox
|
|
|
135
165
|
@gem = gems[params[:gemname]]
|
|
136
166
|
@allow_delete = self.class.allow_delete?
|
|
137
167
|
halt 404 unless @gem
|
|
168
|
+
content_type :html
|
|
138
169
|
erb :gem
|
|
139
170
|
end
|
|
140
171
|
|
|
@@ -159,8 +190,16 @@ module Geminabox
|
|
|
159
190
|
halt 400 unless request.form_data?
|
|
160
191
|
|
|
161
192
|
serialize_update do
|
|
162
|
-
|
|
163
|
-
|
|
193
|
+
# A yank targets one platform. The client omits the platform param for
|
|
194
|
+
# a plain-ruby gem, so an absent/blank value means the ruby platform;
|
|
195
|
+
# without this a `gem yank foo -v 1.0.0` would also destroy foo's java
|
|
196
|
+
# and mingw builds sharing that version.
|
|
197
|
+
requested_platform = params['platform'].to_s.empty? ? 'ruby' : params['platform']
|
|
198
|
+
gems = load_gems.select do |gem|
|
|
199
|
+
params['gem_name'] == gem.name &&
|
|
200
|
+
params['version'] == gem.number.version &&
|
|
201
|
+
(gem.ruby? ? 'ruby' : gem.platform) == requested_platform
|
|
202
|
+
end
|
|
164
203
|
halt 404, 'Gem not found' if gems.size == 0
|
|
165
204
|
gems.each do |gem|
|
|
166
205
|
gem_path = File.expand_path(File.join(Geminabox.data, 'gems',
|
|
@@ -215,6 +254,22 @@ module Geminabox
|
|
|
215
254
|
self.class.with_rlock(&block)
|
|
216
255
|
end
|
|
217
256
|
|
|
257
|
+
def bootstrap_compact_index(path)
|
|
258
|
+
return if File.exist?(path)
|
|
259
|
+
serialize_update { self.class.compact_indexer.reindex }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Rebuild info/NAME on the read path when it is missing but versions.list
|
|
263
|
+
# still lists the gem, so a partially corrupted index does not 404 a gem
|
|
264
|
+
# Bundler was told exists. The ledger-membership check reads only
|
|
265
|
+
# versions.list and takes no lock, so an unknown name stays a cheap 404
|
|
266
|
+
# with no per-gem stats -- arbitrary /info probes cannot force writes.
|
|
267
|
+
def heal_missing_info(name, path)
|
|
268
|
+
indexer = self.class.compact_indexer
|
|
269
|
+
return unless indexer.ledger_lists?(name)
|
|
270
|
+
serialize_update { indexer.heal_info(name) unless File.file?(path) }
|
|
271
|
+
end
|
|
272
|
+
|
|
218
273
|
def handle_incoming_gem(gem)
|
|
219
274
|
begin
|
|
220
275
|
GemStore.create(gem, params[:overwrite])
|
|
@@ -261,36 +316,8 @@ HTML
|
|
|
261
316
|
self.class.dependency_cache
|
|
262
317
|
end
|
|
263
318
|
|
|
264
|
-
def all_gems
|
|
265
|
-
all_gems_with_duplicates.inject(:|)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def all_gems_with_duplicates
|
|
269
|
-
specs_files_paths.map do |specs_file_path|
|
|
270
|
-
if File.exist?(specs_file_path)
|
|
271
|
-
Marshal.load(Gem::Util.gunzip(Gem.read_binary(specs_file_path)))
|
|
272
|
-
else
|
|
273
|
-
[]
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def specs_file_types
|
|
279
|
-
[:specs, :prerelease_specs]
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def specs_files_paths
|
|
283
|
-
specs_file_types.map do |specs_file_type|
|
|
284
|
-
File.join(Geminabox.data, spec_file_name(specs_file_type))
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def spec_file_name(specs_file_type)
|
|
289
|
-
[specs_file_type, Gem.marshal_version, 'gz'].join('.')
|
|
290
|
-
end
|
|
291
|
-
|
|
292
319
|
def load_gems
|
|
293
|
-
@loaded_gems ||= Geminabox::GemVersionCollection.
|
|
320
|
+
@loaded_gems ||= Geminabox::GemVersionCollection.from_specs_index(Geminabox.data)
|
|
294
321
|
end
|
|
295
322
|
|
|
296
323
|
def index_gems(gems)
|
|
@@ -298,25 +325,13 @@ HTML
|
|
|
298
325
|
end
|
|
299
326
|
|
|
300
327
|
def gem_list
|
|
301
|
-
|
|
328
|
+
query_gems.map{|query_gem| gem_dependencies(query_gem) }.flatten(1)
|
|
302
329
|
end
|
|
303
330
|
|
|
304
331
|
def query_gems
|
|
305
332
|
params[:gems].to_s.split(',')
|
|
306
333
|
end
|
|
307
334
|
|
|
308
|
-
def local_gem_list
|
|
309
|
-
query_gems.map{|query_gem| gem_dependencies(query_gem) }.flatten(1)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
def remote_gem_list
|
|
313
|
-
RubygemsDependency.for(*query_gems)
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
def combined_gem_list
|
|
317
|
-
GemListMerge.merge(local_gem_list, remote_gem_list, strategy: Geminabox.rubygems_proxy_merge_strategy)
|
|
318
|
-
end
|
|
319
|
-
|
|
320
335
|
helpers do
|
|
321
336
|
def href(text)
|
|
322
337
|
if text && (text.start_with?('http://') || text.start_with?('https://'))
|
data/lib/geminabox/version.rb
CHANGED
data/lib/geminabox.rb
CHANGED
|
@@ -17,7 +17,6 @@ module Geminabox
|
|
|
17
17
|
class Error < StandardError ; end
|
|
18
18
|
|
|
19
19
|
require_relative 'geminabox/version'
|
|
20
|
-
require_relative 'geminabox/proxy'
|
|
21
20
|
require_relative 'geminabox/http_adapter'
|
|
22
21
|
|
|
23
22
|
def self.geminabox_path(file)
|
|
@@ -27,10 +26,10 @@ module Geminabox
|
|
|
27
26
|
autoload :Hostess, geminabox_path('hostess')
|
|
28
27
|
autoload :GemStore, geminabox_path('gem_store')
|
|
29
28
|
autoload :GemStoreError, geminabox_path('gem_store_error')
|
|
30
|
-
autoload :RubygemsDependency, geminabox_path('rubygems_dependency')
|
|
31
|
-
autoload :GemListMerge, geminabox_path('gem_list_merge')
|
|
32
29
|
autoload :GemVersion, geminabox_path('gem_version')
|
|
33
30
|
autoload :GemVersionCollection, geminabox_path('gem_version_collection')
|
|
31
|
+
autoload :CompactIndexer, geminabox_path('compact_indexer')
|
|
32
|
+
autoload :CompactIndexApi, geminabox_path('compact_index_api')
|
|
34
33
|
autoload :Server, geminabox_path('server')
|
|
35
34
|
autoload :DiskCache, geminabox_path('disk_cache')
|
|
36
35
|
autoload :IncomingGem, geminabox_path('incoming_gem')
|
|
@@ -45,14 +44,9 @@ module Geminabox
|
|
|
45
44
|
:allow_replace,
|
|
46
45
|
:gem_permissions,
|
|
47
46
|
:allow_delete,
|
|
48
|
-
:rubygems_proxy,
|
|
49
|
-
:rubygems_proxy_merge_strategy,
|
|
50
47
|
:http_adapter,
|
|
51
48
|
:lockfile,
|
|
52
49
|
:retry_interval,
|
|
53
|
-
:allow_remote_failure,
|
|
54
|
-
:ruby_gems_url,
|
|
55
|
-
:bundler_ruby_gems_url,
|
|
56
50
|
:allow_upload,
|
|
57
51
|
:on_gem_received
|
|
58
52
|
)
|
|
@@ -80,17 +74,38 @@ module Geminabox
|
|
|
80
74
|
views: File.join(File.dirname(__FILE__), *%w[.. views]),
|
|
81
75
|
allow_replace: false,
|
|
82
76
|
gem_permissions: 0644,
|
|
83
|
-
rubygems_proxy: (ENV['RUBYGEMS_PROXY'] == 'true'),
|
|
84
|
-
rubygems_proxy_merge_strategy: ENV.fetch('RUBYGEMS_PROXY_MERGE_STRATEGY') { :local_gems_take_precedence_over_remote_gems }.to_sym,
|
|
85
77
|
allow_delete: true,
|
|
86
78
|
http_adapter: HttpClientAdapter.new,
|
|
87
79
|
lockfile: File.join(ENV.fetch('TMPDIR', Dir.tmpdir), 'geminabox.lockfile'),
|
|
88
80
|
retry_interval: 60,
|
|
89
|
-
allow_remote_failure: false,
|
|
90
|
-
ruby_gems_url: 'https://rubygems.org/',
|
|
91
|
-
bundler_ruby_gems_url: 'https://bundler.rubygems.org/',
|
|
92
81
|
allow_upload: true,
|
|
93
82
|
on_gem_received: nil
|
|
94
83
|
)
|
|
95
84
|
|
|
85
|
+
# Removed in 4.0 with the RubyGems proxy. Warn-and-ignore (rather than
|
|
86
|
+
# NoMethodError at boot) so stale config.ru lines don't break upgrades.
|
|
87
|
+
# Drop these shims in 5.0.
|
|
88
|
+
[
|
|
89
|
+
:rubygems_proxy,
|
|
90
|
+
:rubygems_proxy_merge_strategy,
|
|
91
|
+
:allow_remote_failure,
|
|
92
|
+
:ruby_gems_url,
|
|
93
|
+
:bundler_ruby_gems_url
|
|
94
|
+
].each do |setting|
|
|
95
|
+
define_singleton_method("#{setting}=") do |_value|
|
|
96
|
+
warn "[REMOVED] Geminabox.#{setting} was removed in Geminabox 4.0 " \
|
|
97
|
+
'along with the RubyGems proxy and has no effect. Remove this ' \
|
|
98
|
+
'line from your config.ru. Migration notes: ' \
|
|
99
|
+
'https://github.com/geminabox/geminabox/pull/735'
|
|
100
|
+
end
|
|
101
|
+
define_singleton_method(setting) { nil }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if ENV['RUBYGEMS_PROXY'] == 'true' || ENV.key?('RUBYGEMS_PROXY_MERGE_STRATEGY')
|
|
105
|
+
warn '[REMOVED] The RUBYGEMS_PROXY and RUBYGEMS_PROXY_MERGE_STRATEGY ' \
|
|
106
|
+
'environment variables were removed in Geminabox 4.0; Geminabox ' \
|
|
107
|
+
'now always serves only local gems. Migration notes: ' \
|
|
108
|
+
'https://github.com/geminabox/geminabox/pull/735'
|
|
109
|
+
end
|
|
110
|
+
|
|
96
111
|
end
|