Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy version #138

Merged
merged 24 commits into from Nov 21, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5896b90
Removes duplicate indexer method
reggieb Oct 9, 2013
1312670
Adds facility to get list of gem dependencies from rubygems
reggieb Oct 9, 2013
4c9d6e4
Adds facility to set proxy via ENV
reggieb Oct 11, 2013
d9e0108
Adds tool to rubygems with local gem lists
reggieb Nov 6, 2013
fc928ae
Forces GemListMerge to output arrays of hashes with symbols as keys
reggieb Nov 6, 2013
6508b34
Temporarily fixes name space problem with hostess.
reggieb Nov 6, 2013
069bc9b
Fixes name spacing. Main server class moves from Geminabox to Geminab…
reggieb Nov 6, 2013
85eb2e8
Patches runtime_dependencies in case dependency name is an array
reggieb Nov 6, 2013
7b5cc6b
Moves ownership of settings from Sinatra instance to parent module
reggieb Nov 7, 2013
28dfa09
Adds console for bash shell
reggieb Nov 7, 2013
595c6e1
Fixes lack of json support in Geminabox module
reggieb Nov 7, 2013
b32438f
Refactored all_gems to be built in a number of method steps.
reggieb Nov 7, 2013
c59ea8f
Fixes problem with VERSION not being loaded.
reggieb Nov 7, 2013
3ceaf63
Adds test of dependency api json version
reggieb Nov 7, 2013
6d40b68
Preparing app for new version.
reggieb Nov 7, 2013
d4f174b
Bump to version 0.12.0
reggieb Nov 7, 2013
a5163a3
Moves get_from_rubygems_if_not_local so that it only acts when pullin…
reggieb Nov 11, 2013
35869ea
Alters dependencies actions to return nil rather than an empty array.
reggieb Nov 15, 2013
14a4d16
Revert "Alters dependencies actions to return nil rather than an empt…
reggieb Nov 15, 2013
5a83566
Tidies up name method
reggieb Nov 15, 2013
4c8c5ff
Correct error in test units path
reggieb Nov 15, 2013
48bae20
Uses new Splicer to splice files in proxy mode.
reggieb Nov 15, 2013
81e4199
Splits proxy functionality out into sub module
reggieb Nov 17, 2013
f6a7c15
Adds proxy copier to handle process of copying local or remote files …
reggieb Nov 17, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -3,6 +3,8 @@ source "https://rubygems.org"
gemspec
group :test do
gem 'rake'
gem 'rack-test'
gem 'minitest'
gem 'capybara-mechanize'
gem 'webmock'
end
20 changes: 20 additions & 0 deletions MIT-LICENSE
@@ -0,0 +1,20 @@
Copyright 2012 Tom Lea, Jack Foy, and Rob Nichols

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 changes: 14 additions & 2 deletions README.markdown
Expand Up @@ -26,7 +26,7 @@ Create a config.ru as follows:
require "geminabox"

Geminabox.data = "/var/geminabox-data" # ... or wherever
run Geminabox
run Geminabox::Server

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).

Expand All @@ -36,6 +36,18 @@ RubyGems supports generating indexes for the so called legacy versions (< 1.2),

Geminabox.build_legacy = false

## RubyGems Proxy

Geminabox can be configured to pull gems, it does not currently have, from rubygems.org. To enable this mode you can either:

Set RUBYGEM_PROXY to true in the environment:

RUBYGEMS_PROXY=true rackup

Or in config.ru (before the run command), set:

Geminabox.rubygems_proxy = true

## Client Usage

Since version 0.10, Geminabox supports the standard gemcutter push API:
Expand Down Expand Up @@ -88,7 +100,7 @@ Simples!

## Licence

Fork it, mod it, choose it, use it, make it better. All under the [do what the fuck you want to + beer/pizza public license][WTFBPPL].
Fork it, mod it, choose it, use it, make it better. All under the MIT License.

[WTFBPPL]: http://tomlea.co.uk/WTFBPPL.txt
[sinatra]: http://www.sinatrarb.com/
Expand Down
2 changes: 1 addition & 1 deletion config.ru
@@ -1,4 +1,4 @@
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
require "geminabox"

run Geminabox
run Geminabox::Server
4 changes: 4 additions & 0 deletions console.sh
@@ -0,0 +1,4 @@
#!/bin/bash

echo Opening Ruby shell with Geminabox loaded
irb -r ./lib/geminabox
13 changes: 5 additions & 8 deletions geminabox.gemspec
Expand Up @@ -2,28 +2,25 @@ require File.expand_path('../lib/geminabox/version', __FILE__)

Gem::Specification.new do |s|
s.name = 'geminabox'
s.version = GeminaboxVersion
s.version = Geminabox::VERSION
s.summary = 'Really simple rubygem hosting'
s.description = 'A sinatra based gem hosting app, with client side gem push style functionality.'
s.authors = ['Tom Lea', 'Jack Foy']
s.email = ['contrib@tomlea.co.uk', 'jack@foys.net']
s.authors = ['Tom Lea', 'Jack Foy', 'Rob Nichols']
s.email = ['contrib@tomlea.co.uk', 'jack@foys.net', 'rob@undervale.co.uk']
s.homepage = 'http://tomlea.co.uk/p/gem-in-a-box'

s.has_rdoc = true
s.extra_rdoc_files = %w[README.markdown]
s.rdoc_options = %w[--main README.markdown]

s.files = %w[README.markdown] + Dir['{lib,public,views}/**/*']
s.license = 'MIT-LICENSE'
s.files = %w[MIT-LICENSE README.markdown] + Dir['{lib,public,views}/**/*']
s.require_paths = ['lib']

s.add_dependency('sinatra', [">= 1.2.7"])
s.add_dependency('builder')
s.add_dependency('httpclient', [">= 2.2.7"])
s.add_dependency('nesty')
s.add_development_dependency('rake')
s.add_development_dependency('rack-test')
s.add_development_dependency('minitest')
s.add_development_dependency('capybara')
s.add_development_dependency('capybara-mechanize')
s.add_development_dependency('pry')
end
260 changes: 47 additions & 213 deletions lib/geminabox.rb
Expand Up @@ -4,234 +4,68 @@
require 'sinatra/base'
require 'rubygems/indexer'
require 'rubygems/package'
require 'hostess'
require 'geminabox/version'
require 'geminabox/gem_store'
require 'geminabox/gem_store_error'
require 'rss/atom'
require 'tempfile'
require 'json'

class Geminabox < Sinatra::Base
enable :static, :methodoverride
module Geminabox

set :public_folder, File.join(File.dirname(__FILE__), *%w[.. public])
set :data, File.join(File.dirname(__FILE__), *%w[.. data])
set :build_legacy, false
set :incremental_updates, true
set :views, File.join(File.dirname(__FILE__), *%w[.. views])
set :allow_replace, false
set :gem_permissions, 0644
set :allow_delete, true
use Hostess
require_relative 'geminabox/version'
require_relative 'geminabox/proxy'

class << self
def disallow_replace?
! allow_replace
end

def allow_delete?
allow_delete
end

def fixup_bundler_rubygems!
return if @post_reset_hook_applied
Gem.post_reset{ Gem::Specification.all = nil } if defined? Bundler and Gem.respond_to? :post_reset
@post_reset_hook_applied = true
end

def reindex(force_rebuild = false)
Geminabox.fixup_bundler_rubygems!
force_rebuild = true unless Geminabox.incremental_updates
if force_rebuild
indexer.generate_index
dependency_cache.flush
else
begin
require 'geminabox/indexer'
updated_gemspecs = Geminabox::Indexer.updated_gemspecs(indexer)
Geminabox::Indexer.patch_rubygems_update_index_pre_1_8_25(indexer)
indexer.update_index
updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
rescue => e
puts "#{e.class}:#{e.message}"
puts e.backtrace.join("\n")
reindex(:force_rebuild)
end
end
end

def indexer
Gem::Indexer.new(Geminabox.data, :build_legacy => Geminabox.build_legacy)
end

def dependency_cache
@dependency_cache ||= Geminabox::DiskCache.new(File.join(Geminabox.data, "_cache"))
end
end

autoload :GemVersionCollection, "geminabox/gem_version_collection"
autoload :GemVersion, "geminabox/gem_version"
autoload :DiskCache, "geminabox/disk_cache"
autoload :IncomingGem, "geminabox/incoming_gem"

before do
headers 'X-Powered-By' => "geminabox #{GeminaboxVersion}"
end

get '/' do
@gems = load_gems
@index_gems = index_gems(@gems)
erb :index
end

get '/atom.xml' do
@gems = load_gems
erb :atom, :layout => false
end

get '/api/v1/dependencies' do
query_gems = params[:gems].to_s.split(',')
deps = query_gems.inject([]){|memo, query_gem| memo + gem_dependencies(query_gem) }
Marshal.dump(deps)
def self.geminabox_path(file)
File.join File.dirname(__FILE__), 'geminabox', file
end

get '/upload' do
erb :upload
end

get '/reindex' do
self.class.reindex(:force_rebuild)
redirect url("/")
end

get '/gems/:gemname' do
gems = Hash[load_gems.by_name]
@gem = gems[params[:gemname]]
halt 404 unless @gem
erb :gem
end

delete '/gems/*.gem' do
unless Geminabox.allow_delete?
error_response(403, 'Gem deletion is disabled - see https://github.com/cwninja/geminabox/issues/115')
end
File.delete file_path if File.exists? file_path
self.class.reindex(:force_rebuild)
redirect url("/")
end
autoload :Hostess, geminabox_path('hostess')
autoload :GemStore, geminabox_path('gem_store')
autoload :GemStoreError, geminabox_path('gem_store_error')
autoload :RubygemsDependency, geminabox_path('rubygems_dependency')
autoload :GemListMerge, geminabox_path('gem_list_merge')
autoload :GemVersion, geminabox_path('gem_version')
autoload :GemVersionCollection, geminabox_path('gem_version_collection')
autoload :Server, geminabox_path('server')
autoload :DiskCache, geminabox_path('disk_cache')
autoload :IncomingGem, geminabox_path('incoming_gem')

post '/upload' do
unless params[:file] && params[:file][:filename] && (tmpfile = params[:file][:tempfile])
@error = "No file selected"
halt [400, erb(:upload)]
end
handle_incoming_gem(IncomingGem.new(tmpfile))
end
class << self

post '/api/v1/gems' do
begin
handle_incoming_gem(IncomingGem.new(request.body))
rescue Object => o
File.open "/tmp/debug.txt", "a" do |io|
io.puts o, o.backtrace
attr_accessor(
:data,
:public_folder,
:build_legacy,
:incremental_updates,
:views,
:allow_replace,
:gem_permissions,
:allow_delete,
:rubygems_proxy
)

def set_defaults(defaults)
defaults.each do |method, default|
variable = "@#{method}"
instance_variable_set(variable, default) unless instance_variable_get(variable)
end
end
end

private

def handle_incoming_gem(gem)
begin
GemStore.create(gem, params[:overwrite])
rescue GemStoreError => error
error_response error.code, error.reason
end

if api_request?
"Gem #{gem.name} received and indexed."
else
redirect url("/")
def settings
Server.settings
end
end

def api_request?
request.accept.first != "text/html"
end
set_defaults(

def error_response(code, message)
halt [code, message] if api_request?
html = <<HTML
<html>
<head><title>Error - #{code}</title></head>
<body>
<h1>Error - #{code}</h1>
<p>#{message}</p>
</body>
</html>
HTML
halt [code, html]
end

def indexer
Gem::Indexer.new(settings.data, :build_legacy => settings.build_legacy)
end

def file_path
File.expand_path(File.join(settings.data, *request.path_info))
end

def dependency_cache
@dependency_cache ||= Geminabox::DiskCache.new(File.join(settings.data, "_cache"))
end
data: File.join(File.dirname(__FILE__), *%w[.. data]),
public_folder: File.join(File.dirname(__FILE__), *%w[.. public]),
build_legacy: false,
incremental_updates: true,
views: File.join(File.dirname(__FILE__), *%w[.. views]),
allow_replace: false,
gem_permissions: 0644,
rubygems_proxy: (ENV['RUBYGEMS_PROXY'] == 'true'),
allow_delete: true

def all_gems
%w(specs prerelease_specs).map{ |specs_file_type|
specs_file_path = File.join(settings.data, "#{specs_file_type}.#{Gem.marshal_version}.gz")
if File.exists?(specs_file_path)
Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
else
[]
end
}.inject(:|)
end

def load_gems
@loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
end

def index_gems(gems)
Set.new(gems.map{|gem| gem.name[0..0].downcase})
end

helpers do
def spec_for(gem_name, version)
spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{gem_name}-#{version}.gemspec.rz")
Marshal.load(Gem.inflate(File.read(spec_file))) if File.exists? spec_file
end

# Return a list of versions of gem 'gem_name' with the dependencies of each version.
def gem_dependencies(gem_name)
dependency_cache.marshal_cache(gem_name) do
load_gems.
select { |gem| gem_name == gem.name }.
map { |gem| [gem, spec_for(gem.name, gem.number)] }.
reject { |(_, spec)| spec.nil? }.
map do |(gem, spec)|
{
:name => gem.name,
:number => gem.number.version,
:platform => gem.platform,
:dependencies => runtime_dependencies(spec)
}
end
end
end

def runtime_dependencies(spec)
spec.
dependencies.
select { |dep| dep.type == :runtime }.
map { |dep| [dep.name, dep.requirement.to_s] }
end
end
)

end