geminabox 0.11.1 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of geminabox might be problematic. Click here for more details.
- data/MIT-LICENSE +20 -0
- data/README.markdown +14 -2
- data/lib/geminabox.rb +47 -213
- data/lib/geminabox/disk_cache.rb +69 -68
- data/lib/geminabox/gem_list_merge.rb +51 -0
- data/lib/geminabox/gem_store.rb +77 -75
- data/lib/geminabox/gem_store_error.rb +8 -7
- data/lib/geminabox/gem_version.rb +31 -27
- data/lib/geminabox/gem_version_collection.rb +55 -53
- data/lib/geminabox/hostess.rb +46 -0
- data/lib/geminabox/incoming_gem.rb +52 -47
- data/lib/geminabox/proxy.rb +13 -0
- data/lib/geminabox/proxy/copier.rb +29 -0
- data/lib/geminabox/proxy/file_handler.rb +93 -0
- data/lib/geminabox/proxy/hostess.rb +81 -0
- data/lib/geminabox/proxy/splicer.rb +67 -0
- data/lib/geminabox/rubygems_dependency.rb +27 -0
- data/lib/geminabox/server.rb +279 -0
- data/lib/geminabox/version.rb +3 -1
- data/lib/rubygems/commands/inabox_command.rb +1 -1
- metadata +25 -77
- data/lib/hostess.rb +0 -42
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 Tom Lea, Jack Foy, and Rob Nichols
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
CHANGED
@@ -26,7 +26,7 @@ Create a config.ru as follows:
|
|
26
26
|
require "geminabox"
|
27
27
|
|
28
28
|
Geminabox.data = "/var/geminabox-data" # ... or wherever
|
29
|
-
run Geminabox
|
29
|
+
run Geminabox::Server
|
30
30
|
|
31
31
|
Start your gem server with 'rackup' to run WEBrick or hook up the config.ru as you normally would ([passenger][passenger], [thin][thin], [unicorn][unicorn], whatever floats your boat).
|
32
32
|
|
@@ -36,6 +36,18 @@ RubyGems supports generating indexes for the so called legacy versions (< 1.2),
|
|
36
36
|
|
37
37
|
Geminabox.build_legacy = false
|
38
38
|
|
39
|
+
## RubyGems Proxy
|
40
|
+
|
41
|
+
Geminabox can be configured to pull gems, it does not currently have, from rubygems.org. To enable this mode you can either:
|
42
|
+
|
43
|
+
Set RUBYGEM_PROXY to true in the environment:
|
44
|
+
|
45
|
+
RUBYGEMS_PROXY=true rackup
|
46
|
+
|
47
|
+
Or in config.ru (before the run command), set:
|
48
|
+
|
49
|
+
Geminabox.rubygems_proxy = true
|
50
|
+
|
39
51
|
## Client Usage
|
40
52
|
|
41
53
|
Since version 0.10, Geminabox supports the standard gemcutter push API:
|
@@ -88,7 +100,7 @@ Simples!
|
|
88
100
|
|
89
101
|
## Licence
|
90
102
|
|
91
|
-
Fork it, mod it, choose it, use it, make it better. All under the
|
103
|
+
Fork it, mod it, choose it, use it, make it better. All under the MIT License.
|
92
104
|
|
93
105
|
[WTFBPPL]: http://tomlea.co.uk/WTFBPPL.txt
|
94
106
|
[sinatra]: http://www.sinatrarb.com/
|
data/lib/geminabox.rb
CHANGED
@@ -4,234 +4,68 @@ require 'builder'
|
|
4
4
|
require 'sinatra/base'
|
5
5
|
require 'rubygems/indexer'
|
6
6
|
require 'rubygems/package'
|
7
|
-
require 'hostess'
|
8
|
-
require 'geminabox/version'
|
9
|
-
require 'geminabox/gem_store'
|
10
|
-
require 'geminabox/gem_store_error'
|
11
7
|
require 'rss/atom'
|
12
8
|
require 'tempfile'
|
9
|
+
require 'json'
|
13
10
|
|
14
|
-
|
15
|
-
enable :static, :methodoverride
|
11
|
+
module Geminabox
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
set :build_legacy, false
|
20
|
-
set :incremental_updates, true
|
21
|
-
set :views, File.join(File.dirname(__FILE__), *%w[.. views])
|
22
|
-
set :allow_replace, false
|
23
|
-
set :gem_permissions, 0644
|
24
|
-
set :allow_delete, true
|
25
|
-
use Hostess
|
13
|
+
require_relative 'geminabox/version'
|
14
|
+
require_relative 'geminabox/proxy'
|
26
15
|
|
27
|
-
|
28
|
-
|
29
|
-
! allow_replace
|
30
|
-
end
|
31
|
-
|
32
|
-
def allow_delete?
|
33
|
-
allow_delete
|
34
|
-
end
|
35
|
-
|
36
|
-
def fixup_bundler_rubygems!
|
37
|
-
return if @post_reset_hook_applied
|
38
|
-
Gem.post_reset{ Gem::Specification.all = nil } if defined? Bundler and Gem.respond_to? :post_reset
|
39
|
-
@post_reset_hook_applied = true
|
40
|
-
end
|
41
|
-
|
42
|
-
def reindex(force_rebuild = false)
|
43
|
-
Geminabox.fixup_bundler_rubygems!
|
44
|
-
force_rebuild = true unless Geminabox.incremental_updates
|
45
|
-
if force_rebuild
|
46
|
-
indexer.generate_index
|
47
|
-
dependency_cache.flush
|
48
|
-
else
|
49
|
-
begin
|
50
|
-
require 'geminabox/indexer'
|
51
|
-
updated_gemspecs = Geminabox::Indexer.updated_gemspecs(indexer)
|
52
|
-
Geminabox::Indexer.patch_rubygems_update_index_pre_1_8_25(indexer)
|
53
|
-
indexer.update_index
|
54
|
-
updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
|
55
|
-
rescue => e
|
56
|
-
puts "#{e.class}:#{e.message}"
|
57
|
-
puts e.backtrace.join("\n")
|
58
|
-
reindex(:force_rebuild)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def indexer
|
64
|
-
Gem::Indexer.new(Geminabox.data, :build_legacy => Geminabox.build_legacy)
|
65
|
-
end
|
66
|
-
|
67
|
-
def dependency_cache
|
68
|
-
@dependency_cache ||= Geminabox::DiskCache.new(File.join(Geminabox.data, "_cache"))
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
autoload :GemVersionCollection, "geminabox/gem_version_collection"
|
73
|
-
autoload :GemVersion, "geminabox/gem_version"
|
74
|
-
autoload :DiskCache, "geminabox/disk_cache"
|
75
|
-
autoload :IncomingGem, "geminabox/incoming_gem"
|
76
|
-
|
77
|
-
before do
|
78
|
-
headers 'X-Powered-By' => "geminabox #{GeminaboxVersion}"
|
79
|
-
end
|
80
|
-
|
81
|
-
get '/' do
|
82
|
-
@gems = load_gems
|
83
|
-
@index_gems = index_gems(@gems)
|
84
|
-
erb :index
|
85
|
-
end
|
86
|
-
|
87
|
-
get '/atom.xml' do
|
88
|
-
@gems = load_gems
|
89
|
-
erb :atom, :layout => false
|
90
|
-
end
|
91
|
-
|
92
|
-
get '/api/v1/dependencies' do
|
93
|
-
query_gems = params[:gems].to_s.split(',')
|
94
|
-
deps = query_gems.inject([]){|memo, query_gem| memo + gem_dependencies(query_gem) }
|
95
|
-
Marshal.dump(deps)
|
16
|
+
def self.geminabox_path(file)
|
17
|
+
File.join File.dirname(__FILE__), 'geminabox', file
|
96
18
|
end
|
97
19
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
gems = Hash[load_gems.by_name]
|
109
|
-
@gem = gems[params[:gemname]]
|
110
|
-
halt 404 unless @gem
|
111
|
-
erb :gem
|
112
|
-
end
|
113
|
-
|
114
|
-
delete '/gems/*.gem' do
|
115
|
-
unless Geminabox.allow_delete?
|
116
|
-
error_response(403, 'Gem deletion is disabled - see https://github.com/cwninja/geminabox/issues/115')
|
117
|
-
end
|
118
|
-
File.delete file_path if File.exists? file_path
|
119
|
-
self.class.reindex(:force_rebuild)
|
120
|
-
redirect url("/")
|
121
|
-
end
|
20
|
+
autoload :Hostess, geminabox_path('hostess')
|
21
|
+
autoload :GemStore, geminabox_path('gem_store')
|
22
|
+
autoload :GemStoreError, geminabox_path('gem_store_error')
|
23
|
+
autoload :RubygemsDependency, geminabox_path('rubygems_dependency')
|
24
|
+
autoload :GemListMerge, geminabox_path('gem_list_merge')
|
25
|
+
autoload :GemVersion, geminabox_path('gem_version')
|
26
|
+
autoload :GemVersionCollection, geminabox_path('gem_version_collection')
|
27
|
+
autoload :Server, geminabox_path('server')
|
28
|
+
autoload :DiskCache, geminabox_path('disk_cache')
|
29
|
+
autoload :IncomingGem, geminabox_path('incoming_gem')
|
122
30
|
|
123
|
-
|
124
|
-
unless params[:file] && params[:file][:filename] && (tmpfile = params[:file][:tempfile])
|
125
|
-
@error = "No file selected"
|
126
|
-
halt [400, erb(:upload)]
|
127
|
-
end
|
128
|
-
handle_incoming_gem(IncomingGem.new(tmpfile))
|
129
|
-
end
|
31
|
+
class << self
|
130
32
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
33
|
+
attr_accessor(
|
34
|
+
:data,
|
35
|
+
:public_folder,
|
36
|
+
:build_legacy,
|
37
|
+
:incremental_updates,
|
38
|
+
:views,
|
39
|
+
:allow_replace,
|
40
|
+
:gem_permissions,
|
41
|
+
:allow_delete,
|
42
|
+
:rubygems_proxy
|
43
|
+
)
|
44
|
+
|
45
|
+
def set_defaults(defaults)
|
46
|
+
defaults.each do |method, default|
|
47
|
+
variable = "@#{method}"
|
48
|
+
instance_variable_set(variable, default) unless instance_variable_get(variable)
|
137
49
|
end
|
138
50
|
end
|
139
|
-
end
|
140
|
-
|
141
|
-
private
|
142
|
-
|
143
|
-
def handle_incoming_gem(gem)
|
144
|
-
begin
|
145
|
-
GemStore.create(gem, params[:overwrite])
|
146
|
-
rescue GemStoreError => error
|
147
|
-
error_response error.code, error.reason
|
148
|
-
end
|
149
51
|
|
150
|
-
|
151
|
-
|
152
|
-
else
|
153
|
-
redirect url("/")
|
52
|
+
def settings
|
53
|
+
Server.settings
|
154
54
|
end
|
155
55
|
end
|
156
56
|
|
157
|
-
|
158
|
-
request.accept.first != "text/html"
|
159
|
-
end
|
57
|
+
set_defaults(
|
160
58
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
</html>
|
171
|
-
HTML
|
172
|
-
halt [code, html]
|
173
|
-
end
|
174
|
-
|
175
|
-
def indexer
|
176
|
-
Gem::Indexer.new(settings.data, :build_legacy => settings.build_legacy)
|
177
|
-
end
|
178
|
-
|
179
|
-
def file_path
|
180
|
-
File.expand_path(File.join(settings.data, *request.path_info))
|
181
|
-
end
|
182
|
-
|
183
|
-
def dependency_cache
|
184
|
-
@dependency_cache ||= Geminabox::DiskCache.new(File.join(settings.data, "_cache"))
|
185
|
-
end
|
59
|
+
data: File.join(File.dirname(__FILE__), *%w[.. data]),
|
60
|
+
public_folder: File.join(File.dirname(__FILE__), *%w[.. public]),
|
61
|
+
build_legacy: false,
|
62
|
+
incremental_updates: true,
|
63
|
+
views: File.join(File.dirname(__FILE__), *%w[.. views]),
|
64
|
+
allow_replace: false,
|
65
|
+
gem_permissions: 0644,
|
66
|
+
rubygems_proxy: (ENV['RUBYGEMS_PROXY'] == 'true'),
|
67
|
+
allow_delete: true
|
186
68
|
|
187
|
-
|
188
|
-
|
189
|
-
specs_file_path = File.join(settings.data, "#{specs_file_type}.#{Gem.marshal_version}.gz")
|
190
|
-
if File.exists?(specs_file_path)
|
191
|
-
Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
|
192
|
-
else
|
193
|
-
[]
|
194
|
-
end
|
195
|
-
}.inject(:|)
|
196
|
-
end
|
197
|
-
|
198
|
-
def load_gems
|
199
|
-
@loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
|
200
|
-
end
|
201
|
-
|
202
|
-
def index_gems(gems)
|
203
|
-
Set.new(gems.map{|gem| gem.name[0..0].downcase})
|
204
|
-
end
|
205
|
-
|
206
|
-
helpers do
|
207
|
-
def spec_for(gem_name, version)
|
208
|
-
spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{gem_name}-#{version}.gemspec.rz")
|
209
|
-
Marshal.load(Gem.inflate(File.read(spec_file))) if File.exists? spec_file
|
210
|
-
end
|
211
|
-
|
212
|
-
# Return a list of versions of gem 'gem_name' with the dependencies of each version.
|
213
|
-
def gem_dependencies(gem_name)
|
214
|
-
dependency_cache.marshal_cache(gem_name) do
|
215
|
-
load_gems.
|
216
|
-
select { |gem| gem_name == gem.name }.
|
217
|
-
map { |gem| [gem, spec_for(gem.name, gem.number)] }.
|
218
|
-
reject { |(_, spec)| spec.nil? }.
|
219
|
-
map do |(gem, spec)|
|
220
|
-
{
|
221
|
-
:name => gem.name,
|
222
|
-
:number => gem.number.version,
|
223
|
-
:platform => gem.platform,
|
224
|
-
:dependencies => runtime_dependencies(spec)
|
225
|
-
}
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def runtime_dependencies(spec)
|
231
|
-
spec.
|
232
|
-
dependencies.
|
233
|
-
select { |dep| dep.type == :runtime }.
|
234
|
-
map { |dep| [dep.name, dep.requirement.to_s] }
|
235
|
-
end
|
236
|
-
end
|
69
|
+
)
|
70
|
+
|
237
71
|
end
|
data/lib/geminabox/disk_cache.rb
CHANGED
@@ -1,72 +1,73 @@
|
|
1
1
|
require "fileutils"
|
2
|
+
module Geminabox
|
3
|
+
class Geminabox::DiskCache
|
4
|
+
attr_reader :root_path
|
5
|
+
|
6
|
+
def initialize(root_path)
|
7
|
+
@root_path = root_path
|
8
|
+
ensure_dir_exists!
|
9
|
+
end
|
10
|
+
|
11
|
+
def flush_key(key)
|
12
|
+
path = path(key_hash(key))
|
13
|
+
FileUtils.rm_f(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def flush
|
17
|
+
FileUtils.rm_rf(root_path)
|
18
|
+
ensure_dir_exists!
|
19
|
+
end
|
20
|
+
|
21
|
+
def cache(key)
|
22
|
+
key_hash = key_hash(key)
|
23
|
+
read(key_hash) || write(key_hash, yield)
|
24
|
+
end
|
25
|
+
|
26
|
+
def marshal_cache(key)
|
27
|
+
key_hash = key_hash(key)
|
28
|
+
marshal_read(key_hash) || marshal_write(key_hash, yield)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def ensure_dir_exists!
|
34
|
+
FileUtils.mkdir_p(root_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def key_hash(key)
|
38
|
+
Digest::MD5.hexdigest(key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def path(key_hash)
|
42
|
+
File.join(root_path, key_hash)
|
43
|
+
end
|
44
|
+
|
45
|
+
def read(key_hash)
|
46
|
+
read_int(key_hash) { |path| File.read(path) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def marshal_read(key_hash)
|
50
|
+
read_int(key_hash) { |path| Marshal.load(File.open(path)) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_int(key_hash)
|
54
|
+
path = path(key_hash)
|
55
|
+
yield(path) if File.exists?(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
def write(key_hash, value)
|
59
|
+
write_int(key_hash) { |f| f << value }
|
60
|
+
value
|
61
|
+
end
|
62
|
+
|
63
|
+
def marshal_write(key_hash, value)
|
64
|
+
write_int(key_hash) { |f| Marshal.dump(value, f) }
|
65
|
+
value
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_int(key_hash)
|
69
|
+
File.open(path(key_hash), 'wb') { |f| yield(f) }
|
70
|
+
end
|
2
71
|
|
3
|
-
class Geminabox::DiskCache
|
4
|
-
attr_reader :root_path
|
5
|
-
|
6
|
-
def initialize(root_path)
|
7
|
-
@root_path = root_path
|
8
|
-
ensure_dir_exists!
|
9
|
-
end
|
10
|
-
|
11
|
-
def flush_key(key)
|
12
|
-
path = path(key_hash(key))
|
13
|
-
FileUtils.rm_f(path)
|
14
|
-
end
|
15
|
-
|
16
|
-
def flush
|
17
|
-
FileUtils.rm_rf(root_path)
|
18
|
-
ensure_dir_exists!
|
19
|
-
end
|
20
|
-
|
21
|
-
def cache(key)
|
22
|
-
key_hash = key_hash(key)
|
23
|
-
read(key_hash) || write(key_hash, yield)
|
24
72
|
end
|
25
|
-
|
26
|
-
def marshal_cache(key)
|
27
|
-
key_hash = key_hash(key)
|
28
|
-
marshal_read(key_hash) || marshal_write(key_hash, yield)
|
29
|
-
end
|
30
|
-
|
31
|
-
protected
|
32
|
-
|
33
|
-
def ensure_dir_exists!
|
34
|
-
FileUtils.mkdir_p(root_path)
|
35
|
-
end
|
36
|
-
|
37
|
-
def key_hash(key)
|
38
|
-
Digest::MD5.hexdigest(key)
|
39
|
-
end
|
40
|
-
|
41
|
-
def path(key_hash)
|
42
|
-
File.join(root_path, key_hash)
|
43
|
-
end
|
44
|
-
|
45
|
-
def read(key_hash)
|
46
|
-
read_int(key_hash) { |path| File.read(path) }
|
47
|
-
end
|
48
|
-
|
49
|
-
def marshal_read(key_hash)
|
50
|
-
read_int(key_hash) { |path| Marshal.load(File.open(path)) }
|
51
|
-
end
|
52
|
-
|
53
|
-
def read_int(key_hash)
|
54
|
-
path = path(key_hash)
|
55
|
-
yield(path) if File.exists?(path)
|
56
|
-
end
|
57
|
-
|
58
|
-
def write(key_hash, value)
|
59
|
-
write_int(key_hash) { |f| f << value }
|
60
|
-
value
|
61
|
-
end
|
62
|
-
|
63
|
-
def marshal_write(key_hash, value)
|
64
|
-
write_int(key_hash) { |f| Marshal.dump(value, f) }
|
65
|
-
value
|
66
|
-
end
|
67
|
-
|
68
|
-
def write_int(key_hash)
|
69
|
-
File.open(path(key_hash), 'wb') { |f| yield(f) }
|
70
|
-
end
|
71
|
-
|
72
73
|
end
|