geminabox-jgraichen 0.12.2.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/MIT-LICENSE +20 -0
- data/README.markdown +109 -0
- data/lib/geminabox.rb +76 -0
- data/lib/geminabox/disk_cache.rb +73 -0
- data/lib/geminabox/gem_list_merge.rb +51 -0
- data/lib/geminabox/gem_store.rb +99 -0
- data/lib/geminabox/gem_store_error.rb +13 -0
- data/lib/geminabox/gem_version.rb +40 -0
- data/lib/geminabox/gem_version_collection.rb +65 -0
- data/lib/geminabox/hostess.rb +46 -0
- data/lib/geminabox/incoming_gem.rb +70 -0
- data/lib/geminabox/indexer.rb +46 -0
- 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 +88 -0
- data/lib/geminabox/proxy/splicer.rb +67 -0
- data/lib/geminabox/rubygems_dependency.rb +27 -0
- data/lib/geminabox/server.rb +285 -0
- data/lib/geminabox/version.rb +3 -0
- data/lib/geminabox_client.rb +60 -0
- data/lib/rubygems/commands/inabox_command.rb +91 -0
- data/lib/rubygems_plugin.rb +2 -0
- data/public/favicon.ico +0 -0
- data/public/jquery.js +16 -0
- data/public/master.css +155 -0
- data/public/master.js +5 -0
- data/views/atom.erb +29 -0
- data/views/gem.erb +38 -0
- data/views/index.erb +56 -0
- data/views/layout.erb +16 -0
- data/views/upload.erb +8 -0
- metadata +167 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
module Geminabox
|
3
|
+
module Proxy
|
4
|
+
class Splicer < FileHandler
|
5
|
+
|
6
|
+
def self.make(file_name)
|
7
|
+
splicer = new(file_name)
|
8
|
+
splicer.create
|
9
|
+
splicer
|
10
|
+
end
|
11
|
+
|
12
|
+
def create
|
13
|
+
File.open(splice_path, 'w'){|f| f.write(new_content)}
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_content
|
17
|
+
if local_file_exists?
|
18
|
+
merge_content
|
19
|
+
else
|
20
|
+
remote_content
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def splice_path
|
25
|
+
proxy_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def splice_folder_path
|
29
|
+
proxy_folder_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def splice_file_exists?
|
33
|
+
file_exists? splice_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def merge_content
|
37
|
+
if gzip?
|
38
|
+
merge_gziped_content
|
39
|
+
else
|
40
|
+
merge_text_content
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def gzip?
|
45
|
+
/\.gz$/ =~ file_name
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def merge_gziped_content
|
50
|
+
package(unpackage(local_content) | unpackage(remote_content))
|
51
|
+
end
|
52
|
+
|
53
|
+
def unpackage(content)
|
54
|
+
Marshal.load(Gem.gunzip(content))
|
55
|
+
end
|
56
|
+
|
57
|
+
def package(content)
|
58
|
+
Gem.gzip(Marshal.dump(content))
|
59
|
+
end
|
60
|
+
|
61
|
+
def merge_text_content
|
62
|
+
local_content.to_s + remote_content.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Geminabox
|
5
|
+
module RubygemsDependency
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def for(*gems)
|
10
|
+
|
11
|
+
url = [
|
12
|
+
rubygems_uri,
|
13
|
+
'?gems=',
|
14
|
+
gems.map(&:to_s).join(',')
|
15
|
+
].join
|
16
|
+
body = HTTPClient.get_content(url)
|
17
|
+
JSON.parse(body)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rubygems_uri
|
21
|
+
"https://bundler.rubygems.org/api/v1/dependencies.json"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,285 @@
|
|
1
|
+
module Geminabox
|
2
|
+
|
3
|
+
class Server < Sinatra::Base
|
4
|
+
enable :static, :methodoverride
|
5
|
+
|
6
|
+
def self.delegate_to_geminabox(*delegate_methods)
|
7
|
+
delegate_methods.each{|m| set m, Geminabox.send(m)}
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate_to_geminabox(
|
11
|
+
:public_folder,
|
12
|
+
:data,
|
13
|
+
:build_legacy,
|
14
|
+
:incremental_updates,
|
15
|
+
:views,
|
16
|
+
:allow_replace,
|
17
|
+
:gem_permissions,
|
18
|
+
:allow_delete,
|
19
|
+
:rubygems_proxy
|
20
|
+
)
|
21
|
+
|
22
|
+
if Server.rubygems_proxy
|
23
|
+
use Proxy::Hostess
|
24
|
+
else
|
25
|
+
use Hostess
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def disallow_replace?
|
30
|
+
! allow_replace
|
31
|
+
end
|
32
|
+
|
33
|
+
def allow_delete?
|
34
|
+
allow_delete
|
35
|
+
end
|
36
|
+
|
37
|
+
def fixup_bundler_rubygems!
|
38
|
+
return if @post_reset_hook_applied
|
39
|
+
Gem.post_reset{ Gem::Specification.all = nil } if defined? Bundler and Gem.respond_to? :post_reset
|
40
|
+
@post_reset_hook_applied = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def reindex(force_rebuild = false)
|
44
|
+
fixup_bundler_rubygems!
|
45
|
+
force_rebuild = true unless incremental_updates
|
46
|
+
if force_rebuild
|
47
|
+
indexer.generate_index
|
48
|
+
dependency_cache.flush
|
49
|
+
else
|
50
|
+
begin
|
51
|
+
require 'geminabox/indexer'
|
52
|
+
updated_gemspecs = Geminabox::Indexer.updated_gemspecs(indexer)
|
53
|
+
Geminabox::Indexer.patch_rubygems_update_index_pre_1_8_25(indexer)
|
54
|
+
indexer.update_index
|
55
|
+
updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
|
56
|
+
rescue => e
|
57
|
+
puts "#{e.class}:#{e.message}"
|
58
|
+
puts e.backtrace.join("\n")
|
59
|
+
reindex(:force_rebuild)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def indexer
|
65
|
+
Gem::Indexer.new(data, :build_legacy => build_legacy)
|
66
|
+
end
|
67
|
+
|
68
|
+
def dependency_cache
|
69
|
+
@dependency_cache ||= Geminabox::DiskCache.new(File.join(data, "_cache"))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
before do
|
76
|
+
headers 'X-Powered-By' => "geminabox #{Geminabox::VERSION}"
|
77
|
+
end
|
78
|
+
|
79
|
+
get '/' do
|
80
|
+
@gems = load_gems
|
81
|
+
@index_gems = index_gems(@gems)
|
82
|
+
erb :index
|
83
|
+
end
|
84
|
+
|
85
|
+
get '/atom.xml' do
|
86
|
+
@gems = load_gems
|
87
|
+
erb :atom, :layout => false
|
88
|
+
end
|
89
|
+
|
90
|
+
get '/api/v1/dependencies' do
|
91
|
+
query_gems.any? ? Marshal.dump(gem_list) : 200
|
92
|
+
end
|
93
|
+
|
94
|
+
get '/api/v1/dependencies.json' do
|
95
|
+
query_gems.any? ? gem_list.to_json : {}
|
96
|
+
end
|
97
|
+
|
98
|
+
get '/upload' do
|
99
|
+
erb :upload
|
100
|
+
end
|
101
|
+
|
102
|
+
get '/reindex' do
|
103
|
+
self.class.reindex(:force_rebuild)
|
104
|
+
redirect url("/")
|
105
|
+
end
|
106
|
+
|
107
|
+
get '/gems/:gemname' do
|
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 self.class.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
|
122
|
+
|
123
|
+
post '/upload' do
|
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(Geminabox::IncomingGem.new(tmpfile))
|
129
|
+
end
|
130
|
+
|
131
|
+
post '/api/v1/gems' do
|
132
|
+
begin
|
133
|
+
handle_incoming_gem(Geminabox::IncomingGem.new(request.body))
|
134
|
+
rescue Object => o
|
135
|
+
File.open "/tmp/debug.txt", "a" do |io|
|
136
|
+
io.puts o, o.backtrace
|
137
|
+
end
|
138
|
+
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
|
+
|
150
|
+
if api_request?
|
151
|
+
"Gem #{gem.name} received and indexed."
|
152
|
+
else
|
153
|
+
redirect url("/")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def api_request?
|
158
|
+
request.accept.first != "text/html"
|
159
|
+
end
|
160
|
+
|
161
|
+
def error_response(code, message)
|
162
|
+
halt [code, message] if api_request?
|
163
|
+
html = <<HTML
|
164
|
+
<html>
|
165
|
+
<head><title>Error - #{code}</title></head>
|
166
|
+
<body>
|
167
|
+
<h1>Error - #{code}</h1>
|
168
|
+
<p>#{message}</p>
|
169
|
+
</body>
|
170
|
+
</html>
|
171
|
+
HTML
|
172
|
+
halt [code, html]
|
173
|
+
end
|
174
|
+
|
175
|
+
def file_path
|
176
|
+
File.expand_path(File.join(settings.data, *request.path_info))
|
177
|
+
end
|
178
|
+
|
179
|
+
def dependency_cache
|
180
|
+
self.class.dependency_cache
|
181
|
+
end
|
182
|
+
|
183
|
+
def all_gems
|
184
|
+
all_gems_with_duplicates.inject(:|)
|
185
|
+
end
|
186
|
+
|
187
|
+
def all_gems_with_duplicates
|
188
|
+
specs_files_paths.map do |specs_file_path|
|
189
|
+
if File.exists?(specs_file_path)
|
190
|
+
Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
|
191
|
+
else
|
192
|
+
[]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def specs_file_types
|
198
|
+
[:specs, :prerelease_specs]
|
199
|
+
end
|
200
|
+
|
201
|
+
def specs_files_paths
|
202
|
+
specs_file_types.map do |specs_file_type|
|
203
|
+
File.join(settings.data, spec_file_name(specs_file_type))
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def spec_file_name(specs_file_type)
|
208
|
+
[specs_file_type, Gem.marshal_version, 'gz'].join('.')
|
209
|
+
end
|
210
|
+
|
211
|
+
def load_gems
|
212
|
+
@loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
|
213
|
+
end
|
214
|
+
|
215
|
+
def index_gems(gems)
|
216
|
+
Set.new(gems.map{|gem| gem.name[0..0].downcase})
|
217
|
+
end
|
218
|
+
|
219
|
+
def gem_list
|
220
|
+
settings.rubygems_proxy ? combined_gem_list : local_gem_list
|
221
|
+
end
|
222
|
+
|
223
|
+
def query_gems
|
224
|
+
params[:gems].to_s.split(',')
|
225
|
+
end
|
226
|
+
|
227
|
+
def local_gem_list
|
228
|
+
query_gems.map{|query_gem| gem_dependencies(query_gem) }.flatten(1)
|
229
|
+
end
|
230
|
+
|
231
|
+
def remote_gem_list
|
232
|
+
RubygemsDependency.for(*query_gems)
|
233
|
+
end
|
234
|
+
|
235
|
+
def combined_gem_list
|
236
|
+
GemListMerge.from(local_gem_list, remote_gem_list)
|
237
|
+
end
|
238
|
+
|
239
|
+
helpers do
|
240
|
+
def spec_for(gem_name, version, platform = default_platform)
|
241
|
+
filename = [gem_name, version]
|
242
|
+
filename.push(platform) if platform != default_platform
|
243
|
+
spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{filename.join("-")}.gemspec.rz")
|
244
|
+
Marshal.load(Gem.inflate(File.read(spec_file))) if File.exists? spec_file
|
245
|
+
end
|
246
|
+
|
247
|
+
def default_platform
|
248
|
+
'ruby'
|
249
|
+
end
|
250
|
+
|
251
|
+
# Return a list of versions of gem 'gem_name' with the dependencies of each version.
|
252
|
+
def gem_dependencies(gem_name)
|
253
|
+
dependency_cache.marshal_cache(gem_name) do
|
254
|
+
load_gems.
|
255
|
+
select { |gem| gem_name == gem.name }.
|
256
|
+
map { |gem| [gem, spec_for(gem.name, gem.number, gem.platform)] }.
|
257
|
+
reject { |(_, spec)| spec.nil? }.
|
258
|
+
map do |(gem, spec)|
|
259
|
+
{
|
260
|
+
:name => gem.name,
|
261
|
+
:number => gem.number.version,
|
262
|
+
:platform => gem.platform,
|
263
|
+
:dependencies => runtime_dependencies(spec)
|
264
|
+
}
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
|
271
|
+
def runtime_dependencies(spec)
|
272
|
+
spec.
|
273
|
+
dependencies.
|
274
|
+
select { |dep| dep.type == :runtime }.
|
275
|
+
map { |dep| name_and_requirements_for(dep) }
|
276
|
+
end
|
277
|
+
|
278
|
+
def name_and_requirements_for(dep)
|
279
|
+
name = dep.name.kind_of?(Array) ? dep.name.first : dep.name
|
280
|
+
[name, dep.requirement.to_s]
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'httpclient'
|
3
|
+
|
4
|
+
class GeminaboxClient
|
5
|
+
attr_reader :url, :http_client
|
6
|
+
|
7
|
+
def initialize(url)
|
8
|
+
extract_username_and_password_from_url!(url)
|
9
|
+
@http_client = HTTPClient.new
|
10
|
+
@http_client.set_auth(url_for(:upload), @username, @password) if @username or @password
|
11
|
+
@http_client.www_auth.basic_auth.challenge(url_for(:upload)) # Workaround: https://github.com/nahi/httpclient/issues/63
|
12
|
+
@http_client.ssl_config.ssl_version = nil # https://github.com/nahi/httpclient/pull/186
|
13
|
+
@http_client.send_timeout = 0
|
14
|
+
@http_client.receive_timeout = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def extract_username_and_password_from_url!(url)
|
18
|
+
uri = URI.parse(url.to_s)
|
19
|
+
@username, @password = uri.user, uri.password
|
20
|
+
uri.user = uri.password = nil
|
21
|
+
uri.path = uri.path + "/" unless uri.path.end_with?("/")
|
22
|
+
@url = uri.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def url_for(path)
|
26
|
+
url + path.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def push(gemfile, options = {})
|
30
|
+
response = http_client.post(url_for(:upload), { 'file' => File.open(gemfile, "rb"), 'overwrite' => !!options[:overwrite] }, { 'Accept' => 'text/plain' })
|
31
|
+
|
32
|
+
if response.status < 300
|
33
|
+
response.body
|
34
|
+
else
|
35
|
+
raise GeminaboxClient::Error, "Error (#{response.code} received)\n\n#{response.body}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class GeminaboxClient::Error < RuntimeError
|
42
|
+
end
|
43
|
+
|
44
|
+
module GeminaboxClient::GemLocator
|
45
|
+
def find_gem(dir)
|
46
|
+
gemname = File.split(dir).last
|
47
|
+
glob_matcher = "{pkg/,}#{gemname}-*.gem"
|
48
|
+
latest_gem_for(gemname, Dir.glob(glob_matcher)) or raise Gem::CommandLineError, NO_GEM_PROVIDED_ERROR_MESSAGE
|
49
|
+
end
|
50
|
+
|
51
|
+
def latest_gem_for(gemname, files)
|
52
|
+
regexp_matcher = %r{(?:pkg/)#{gemname}-(#{Gem::Version::VERSION_PATTERN})\.gem}
|
53
|
+
sorter = lambda{|v| Gem::Version.new(regexp_matcher.match(v)[1]) }
|
54
|
+
files.grep(regexp_matcher).max_by(&sorter)
|
55
|
+
end
|
56
|
+
|
57
|
+
extend self
|
58
|
+
|
59
|
+
NO_GEM_PROVIDED_ERROR_MESSAGE = "Couldn't find a gem in pkg, please specify a gem name on the command line (e.g. gem inabox GEMNAME)"
|
60
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'rubygems/command'
|
2
|
+
|
3
|
+
class Gem::Commands::InaboxCommand < Gem::Command
|
4
|
+
def description
|
5
|
+
'Push a gem up to your GemInABox'
|
6
|
+
end
|
7
|
+
|
8
|
+
def arguments
|
9
|
+
"GEM built gem to push up"
|
10
|
+
end
|
11
|
+
|
12
|
+
def usage
|
13
|
+
"#{program_name} GEM"
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
super 'inabox', description
|
18
|
+
|
19
|
+
add_option('-c', '--configure', "Configure GemInABox") do |value, options|
|
20
|
+
options[:configure] = true
|
21
|
+
end
|
22
|
+
|
23
|
+
add_option('-g', '--host HOST', "Host to upload to.") do |value, options|
|
24
|
+
options[:host] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
add_option('-o', '--overwrite', "Overwrite Gem.") do |value, options|
|
28
|
+
options[:overwrite] = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def last_minute_requires!
|
33
|
+
require 'yaml'
|
34
|
+
require File.expand_path("../../../geminabox_client.rb", __FILE__)
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute
|
38
|
+
last_minute_requires!
|
39
|
+
return configure if options[:configure]
|
40
|
+
configure unless geminabox_host
|
41
|
+
|
42
|
+
if options[:args].size == 0
|
43
|
+
say "You didn't specify a gem, looking for one in . and in ./pkg/..."
|
44
|
+
gemfiles = [GeminaboxClient::GemLocator.find_gem(Dir.pwd)]
|
45
|
+
else
|
46
|
+
gemfiles = get_all_gem_names
|
47
|
+
end
|
48
|
+
|
49
|
+
send_gems(gemfiles)
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_gems(gemfiles)
|
53
|
+
client = GeminaboxClient.new(geminabox_host)
|
54
|
+
|
55
|
+
gemfiles.each do |gemfile|
|
56
|
+
say "Pushing #{File.basename(gemfile)} to #{client.url}..."
|
57
|
+
begin
|
58
|
+
say client.push(gemfile, options)
|
59
|
+
rescue GeminaboxClient::Error => e
|
60
|
+
alert_error e.message
|
61
|
+
terminate_interaction(1)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def config_path
|
67
|
+
File.join(Gem.user_home, '.gem', 'geminabox')
|
68
|
+
end
|
69
|
+
|
70
|
+
def configure
|
71
|
+
say "Enter the root url for your personal geminabox instance (e.g. http://gems/)."
|
72
|
+
host = ask("Host:")
|
73
|
+
self.geminabox_host = host
|
74
|
+
end
|
75
|
+
|
76
|
+
def geminabox_host
|
77
|
+
@geminabox_host ||= options[:host] || Gem.configuration.load_file(config_path)[:host]
|
78
|
+
end
|
79
|
+
|
80
|
+
def geminabox_host=(host)
|
81
|
+
config = Gem.configuration.load_file(config_path).merge(:host => host)
|
82
|
+
|
83
|
+
dirname = File.dirname(config_path)
|
84
|
+
Dir.mkdir(dirname) unless File.exists?(dirname)
|
85
|
+
|
86
|
+
File.open(config_path, 'w') do |f|
|
87
|
+
f.write config.to_yaml
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|