geminabox 0.5.2 → 0.6.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/README.markdown CHANGED
@@ -1,12 +1,18 @@
1
- # Gem in a Box
1
+ # Gem in a Box – Really simple rubygem hosting
2
+ ##
2
3
 
3
- ![screen shot](http://pics.tomlea.co.uk/55c320/geminabox.png)
4
+ Geminabox let's you host your own gems, and push new gems to it just like with rubygems.org.
5
+ The bundler dependencies API is supported out of the box.
6
+ Authentication it left up to either the web server, or the Rack stack.
7
+ For basic auth, try [Rack::Auth](http://rack.rubyforge.org/doc/Rack/Auth/Basic.html).
8
+
9
+
10
+
11
+
12
+ ![screen shot](http://pics.tomlea.co.uk/bbbba6/geminabox.png)
4
13
 
5
- ## Really simple rubygem hosting
6
14
 
7
- Gem in a box is a simple [sinatra][sinatra] app to allow you to host your own in-house gems.
8
15
 
9
- It has no security, or authentication so you should handle this yourself.
10
16
 
11
17
  ## Server Setup
12
18
 
@@ -17,7 +23,7 @@ Create a config.ru as follows:
17
23
  require "rubygems"
18
24
  require "geminabox"
19
25
 
20
- Geminabox.data = "/var/geminabox-data" # or wherever
26
+ Geminabox.data = "/var/geminabox-data" # ... or wherever
21
27
  run Geminabox
22
28
 
23
29
  And finally, hook up the config.ru as you normally would ([passenger][passenger], [thin][thin], [unicorn][unicorn], whatever floats your boat).
data/lib/geminabox.rb CHANGED
@@ -1,10 +1,9 @@
1
1
  require 'rubygems'
2
- require "digest/md5"
3
- require "builder"
2
+ require 'digest/md5'
3
+ require 'builder'
4
4
  require 'sinatra/base'
5
5
  require 'rubygems/builder'
6
- require "rubygems/indexer"
7
-
6
+ require 'rubygems/indexer'
8
7
  require 'hostess'
9
8
 
10
9
  class Geminabox < Sinatra::Base
@@ -43,6 +42,20 @@ class Geminabox < Sinatra::Base
43
42
  erb :atom, :layout => false
44
43
  end
45
44
 
45
+ get '/api/v1/dependencies' do
46
+ query_gems = params[:gems].split(',')
47
+ deps = load_gems.gems.select {|gem| query_gems.include?(gem.name) }.map do |gem|
48
+ spec = spec_for(gem.name, gem.number)
49
+ {
50
+ :name => gem.name,
51
+ :number => gem.number.version,
52
+ :platform => gem.platform,
53
+ :dependencies => spec.dependencies.select {|dep| dep.type == :runtime}.map {|dep| [dep.name, dep.requirement.to_s] }
54
+ }
55
+ end
56
+ Marshal.dump(deps)
57
+ end
58
+
46
59
  get '/upload' do
47
60
  erb :upload
48
61
  end
@@ -59,27 +72,37 @@ class Geminabox < Sinatra::Base
59
72
  end
60
73
 
61
74
  post '/upload' do
62
- return "Please ensure #{File.expand_path(Geminabox.data)} is writable by the geminabox web server." unless File.writable? Geminabox.data
75
+ if File.exists? Geminabox.data
76
+ error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is writable by the geminabox web server." ) unless File.writable? Geminabox.data
77
+ error_response( 500, "Please ensure #{File.expand_path(Geminabox.data)} is a directory." ) unless File.directory? Geminabox.data
78
+ else
79
+ begin
80
+ FileUtils.mkdir_p(settings.data)
81
+ rescue Errno::EACCES, Errno::ENOENT, RuntimeError => e
82
+ error_response( 500, "Could not create #{File.expand_path(Geminabox.data)}.\n#{e}\n#{e.message}" )
83
+ end
84
+ end
85
+
63
86
  unless params[:file] && (tmpfile = params[:file][:tempfile]) && (name = params[:file][:filename])
64
87
  @error = "No file selected"
65
- return erb(:upload)
88
+ halt [400, erb(:upload)]
66
89
  end
67
90
 
68
- tmpfile.binmode
91
+ FileUtils.mkdir_p(File.join(settings.data, "gems"))
69
92
 
70
- Dir.mkdir(File.join(settings.data, "gems")) unless File.directory? File.join(settings.data, "gems")
71
-
72
- dest_filename = File.join(settings.data, "gems", File.basename(name))
93
+ tmpfile.binmode
73
94
 
95
+ gem_name = File.basename(name)
96
+ dest_filename = File.join(settings.data, "gems", gem_name)
74
97
 
75
98
  if Geminabox.disallow_replace? and File.exist?(dest_filename)
76
99
  existing_file_digest = Digest::SHA1.file(dest_filename).hexdigest
77
100
  tmpfile_digest = Digest::SHA1.file(tmpfile.path).hexdigest
78
101
 
79
102
  if existing_file_digest != tmpfile_digest
80
- return error_response(409, "Gem already exists, you must delete the existing version first.")
103
+ error_response(409, "Updating an existing gem is not permitted.\nYou should either delete the existing version, or change your version number.")
81
104
  else
82
- return [200, "Ignoring upload, you uploaded the same thing previously."]
105
+ error_response(200, "Ignoring upload, you uploaded the same thing previously.")
83
106
  end
84
107
  end
85
108
 
@@ -89,12 +112,22 @@ class Geminabox < Sinatra::Base
89
112
  end
90
113
  end
91
114
  reindex
92
- redirect url("/")
115
+
116
+ if api_request?
117
+ "Gem #{gem_name} received and indexed."
118
+ else
119
+ redirect url("/")
120
+ end
93
121
  end
94
122
 
95
123
  private
96
124
 
125
+ def api_request?
126
+ request.accept.first == "text/plain"
127
+ end
128
+
97
129
  def error_response(code, message)
130
+ halt [code, message] if api_request?
98
131
  html = <<HTML
99
132
  <html>
100
133
  <head><title>Error - #{code}</title></head>
@@ -104,7 +137,7 @@ private
104
137
  </body>
105
138
  </html>
106
139
  HTML
107
- [code, html]
140
+ halt [code, html]
108
141
  end
109
142
 
110
143
  def reindex(force_rebuild = false)
@@ -132,13 +165,14 @@ HTML
132
165
  end
133
166
 
134
167
  def load_gems
135
- %w(specs prerelease_specs).inject(GemVersionCollection.new){|gems, specs_file_type|
136
- specs_file_path = File.join(settings.data, "#{specs_file_type}.#{Gem.marshal_version}.gz")
137
- if File.exists?(specs_file_path)
138
- gems |= Geminabox::GemVersionCollection.new(Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path))))
139
- end
140
- gems
141
- }
168
+ @loaded_gems ||=
169
+ %w(specs prerelease_specs).inject(GemVersionCollection.new){|gems, specs_file_type|
170
+ specs_file_path = File.join(settings.data, "#{specs_file_type}.#{Gem.marshal_version}.gz")
171
+ if File.exists?(specs_file_path)
172
+ gems |= Geminabox::GemVersionCollection.new(Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path))))
173
+ end
174
+ gems
175
+ }
142
176
  end
143
177
 
144
178
  def index_gems(gems)
@@ -0,0 +1,58 @@
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.verify_mode = OpenSSL::SSL::VERIFY_NONE
13
+ end
14
+
15
+ def extract_username_and_password_from_url!(url)
16
+ uri = URI.parse(url.to_s)
17
+ @username, @password = uri.user, uri.password
18
+ uri.user = uri.password = nil
19
+ uri.path = uri.path + "/" unless uri.path.end_with?("/")
20
+ @url = uri.to_s
21
+ end
22
+
23
+ def url_for(path)
24
+ url + path.to_s
25
+ end
26
+
27
+ def push(gemfile)
28
+ response = http_client.post(url_for(:upload), {'file' => File.open(gemfile, "rb")}, {'Accept' => 'text/plain'})
29
+
30
+ if response.status < 400
31
+ response.body
32
+ else
33
+ raise GeminaboxClient::Error, "Error (#{response.code} received)\n\n#{response.body}"
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ class GeminaboxClient::Error < RuntimeError
40
+ end
41
+
42
+ module GeminaboxClient::GemLocator
43
+ def find_gem(dir)
44
+ gemname = File.split(dir).last
45
+ glob_matcher = "{pkg/,}#{gemname}-*.gem"
46
+ latest_gem_for(gemname, Dir.glob(glob_matcher)) or raise Gem::CommandLineError, NO_GEM_PROVIDED_ERROR_MESSAGE
47
+ end
48
+
49
+ def latest_gem_for(gemname, files)
50
+ regexp_matcher = %r{(?:pkg/)#{gemname}-(#{Gem::Version::VERSION_PATTERN})\.gem}
51
+ sorter = lambda{|v| Gem::Version.new(regexp_matcher.match(v)[1]) }
52
+ files.grep(regexp_matcher).max_by(&sorter)
53
+ end
54
+
55
+ extend self
56
+
57
+ 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)"
58
+ end
@@ -1,5 +1,4 @@
1
- require 'uri'
2
- require 'yaml'
1
+ require 'rubygems/command'
3
2
 
4
3
  class Gem::Commands::InaboxCommand < Gem::Command
5
4
  def description
@@ -26,70 +25,40 @@ class Gem::Commands::InaboxCommand < Gem::Command
26
25
  end
27
26
  end
28
27
 
28
+ def last_minute_requires!
29
+ require 'yaml'
30
+ require File.expand_path("../../../geminabox_client.rb", __FILE__)
31
+ end
32
+
29
33
  def execute
34
+ last_minute_requires!
30
35
  return configure if options[:configure]
31
- setup
32
- send_gem
33
- end
36
+ configure unless geminabox_host
34
37
 
35
- def setup
36
38
  if options[:args].size == 0
37
- @gemfiles = [find_gem]
39
+ say "You didn't specify a gem, looking for one in . and in ./pkg/..."
40
+ gemfiles = [GeminaboxClient::GemLocator.find_gem(Dir.pwd)]
38
41
  else
39
- @gemfiles = get_all_gem_names
42
+ gemfiles = get_all_gem_names
40
43
  end
41
- configure unless geminabox_host
42
- end
43
44
 
44
- def find_gem
45
- say "You didn't specify a gem, looking for one in pkg..."
46
- path, directory = File.split(Dir.pwd)
47
- possible_gems = Dir.glob("pkg/#{directory}-*.gem")
48
- raise Gem::CommandLineError, "Couldn't find a gem in pkg, please specify a gem name on the command line (e.g. gem inabox GEMNAME)" unless possible_gems.any?
49
- name_regexp = Regexp.new("^pkg/#{directory}-")
50
- possible_gems.sort_by{ |a| Gem::Version.new(a.sub(name_regexp,'')) }.last
45
+ send_gems(gemfiles)
51
46
  end
52
47
 
53
- def send_gem
54
- # sanitize printed URL if a password is present
55
- url = URI.join(geminabox_host, "upload")
56
-
57
- url_for_presentation = url.clone
58
- url_for_presentation.password = '***' if url_for_presentation.password
59
-
48
+ def send_gems(gemfiles)
49
+ client = GeminaboxClient.new(geminabox_host)
60
50
 
61
- @gemfiles.each do |gemfile|
62
- say "Pushing #{File.basename(gemfile)} to #{url_for_presentation}..."
63
-
64
- File.open(gemfile, "rb") do |file|
65
- request_body, request_headers = Multipart::MultipartPost.new.prepare_query("file" => file)
66
-
67
- proxy.start(url.host, url.port) {|con|
68
- req = Net::HTTP::Post.new(url.path, request_headers)
69
- req.basic_auth(url.user, url.password) if url.user
70
- handle_response(con.request(req, request_body))
71
- }
51
+ gemfiles.each do |gemfile|
52
+ say "Pushing #{File.basename(gemfile)} to #{client.url}..."
53
+ begin
54
+ say client.push(gemfile)
55
+ rescue GeminaboxClient::Error => e
56
+ alert_error e.message
57
+ terminate_interaction(1)
72
58
  end
73
59
  end
74
60
  end
75
61
 
76
- def proxy
77
- if proxy_info = ENV['http_proxy'] || ENV['HTTP_PROXY'] and uri = URI.parse(proxy_info)
78
- Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
79
- else
80
- Net::HTTP
81
- end
82
- end
83
-
84
- def handle_response(response)
85
- case response
86
- when Net::HTTPSuccess, Net::HTTPRedirection
87
- puts response.body
88
- else
89
- response.error!
90
- end
91
- end
92
-
93
62
  def config_path
94
63
  File.join(Gem.user_home, '.gem', 'geminabox')
95
64
  end
@@ -115,51 +84,4 @@ class Gem::Commands::InaboxCommand < Gem::Command
115
84
  end
116
85
  end
117
86
 
118
- module Multipart
119
- require 'net/http'
120
- require 'cgi'
121
-
122
- class Param
123
- attr_accessor :k, :v
124
- def initialize( k, v )
125
- @k = k
126
- @v = v
127
- end
128
-
129
- def to_multipart
130
- return "Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n#{v}\r\n"
131
- end
132
- end
133
-
134
- class FileParam
135
- attr_accessor :k, :filename, :content
136
- def initialize( k, filename, content )
137
- @k = k
138
- @filename = filename
139
- @content = content
140
- end
141
-
142
- def to_multipart
143
- return "Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{filename}\"\r\n" + "Content-Transfer-Encoding: binary\r\n" + "Content-Type: application/octet-stream\r\n\r\n" + content + "\r\n"
144
- end
145
- end
146
-
147
- class MultipartPost
148
- BOUNDARY = 'tarsiers-rule0000'
149
- HEADER = {"Content-type" => "multipart/form-data, boundary=" + BOUNDARY + " "}
150
-
151
- def prepare_query(params)
152
- fp = []
153
- params.each {|k,v|
154
- if v.respond_to?(:read)
155
- fp.push(FileParam.new(k, v.path, v.read))
156
- else
157
- fp.push(Param.new(k,v))
158
- end
159
- }
160
- query = fp.collect {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
161
- return query, HEADER
162
- end
163
- end
164
- end
165
87
  end
@@ -1,7 +1 @@
1
- require 'rubygems/command_manager'
2
-
3
- require 'rubygems/command'
4
- require 'rubygems/dependency'
5
- require 'rubygems/version_option'
6
-
7
1
  Gem::CommandManager.instance.register_command :inabox
data/views/index.erb CHANGED
@@ -24,7 +24,7 @@
24
24
 
25
25
  <div class="details">
26
26
  <p>
27
- <% if spec = spec_for(name, versions.first.number) %>
27
+ <% if spec = spec_for(name, versions.newest.number) %>
28
28
  <%= spec.description %>
29
29
  <br/>
30
30
  <span class="author">– <%= spec.authors.map do |author|
data/views/layout.erb CHANGED
@@ -9,7 +9,11 @@
9
9
  <div id="content">
10
10
  <h1>Gem in a Box</h1>
11
11
  <p>
12
+ <small>If you did not need any credentials to get to this page</small>
12
13
  <pre><code>gem sources -a <%= url "/" %></code></pre>
14
+ <small>If you needed some credentials to get to this page</small>
15
+ <pre><code>gem sources -a <%= url.to_s.gsub('://','://username:password@') %></code></pre>
16
+ <small>and then ...</small>
13
17
  <pre><code>gem install geminabox<br />gem inabox [gemfile]</code></pre>
14
18
  </p>
15
19
  <%= yield %>
metadata CHANGED
@@ -1,77 +1,83 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: geminabox
3
- version: !ruby/object:Gem::Version
4
- hash: 15
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 5
9
- - 2
10
- version: 0.5.2
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Tom Lea
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-01-30 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
21
- requirement: &id001 !ruby/object:Gem::Requirement
22
- none: false
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- hash: 3
27
- segments:
28
- - 0
29
- version: "0"
30
- version_requirements: *id001
12
+ date: 2012-03-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
31
15
  name: sinatra
16
+ requirement: &70215540161800 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
32
22
  type: :runtime
33
23
  prerelease: false
34
- - !ruby/object:Gem::Dependency
35
- requirement: &id002 !ruby/object:Gem::Requirement
36
- none: false
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- hash: 3
41
- segments:
42
- - 0
43
- version: "0"
44
- version_requirements: *id002
24
+ version_requirements: *70215540161800
25
+ - !ruby/object:Gem::Dependency
45
26
  name: builder
27
+ requirement: &70215540161340 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
46
33
  type: :runtime
47
34
  prerelease: false
48
- - !ruby/object:Gem::Dependency
49
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *70215540161340
36
+ - !ruby/object:Gem::Dependency
37
+ name: httpclient
38
+ requirement: &70215540160880 !ruby/object:Gem::Requirement
50
39
  none: false
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- hash: 3
55
- segments:
56
- - 0
57
- version: "0"
58
- version_requirements: *id003
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70215540160880
47
+ - !ruby/object:Gem::Dependency
59
48
  name: rake
49
+ requirement: &70215540160460 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70215540160460
58
+ - !ruby/object:Gem::Dependency
59
+ name: minitest
60
+ requirement: &70215540160040 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
60
66
  type: :development
61
67
  prerelease: false
68
+ version_requirements: *70215540160040
62
69
  description: A sinatra based gem hosting app, with client side gem push style functionality.
63
70
  email: contrib@tomlea.co.uk
64
71
  executables: []
65
-
66
72
  extensions: []
67
-
68
- extra_rdoc_files:
73
+ extra_rdoc_files:
69
74
  - README.markdown
70
- files:
75
+ files:
71
76
  - README.markdown
72
77
  - lib/geminabox/gem_version.rb
73
78
  - lib/geminabox/gem_version_collection.rb
74
79
  - lib/geminabox.rb
80
+ - lib/geminabox_client.rb
75
81
  - lib/hostess.rb
76
82
  - lib/rubygems/commands/inabox_command.rb
77
83
  - lib/rubygems_plugin.rb
@@ -85,37 +91,34 @@ files:
85
91
  - views/upload.erb
86
92
  homepage: http://tomlea.co.uk/p/gem-in-a-box
87
93
  licenses: []
88
-
89
94
  post_install_message:
90
- rdoc_options:
95
+ rdoc_options:
91
96
  - --main
92
97
  - README.markdown
93
- require_paths:
98
+ require_paths:
94
99
  - lib
95
- required_ruby_version: !ruby/object:Gem::Requirement
100
+ required_ruby_version: !ruby/object:Gem::Requirement
96
101
  none: false
97
- requirements:
98
- - - ">="
99
- - !ruby/object:Gem::Version
100
- hash: 3
101
- segments:
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ segments:
102
107
  - 0
103
- version: "0"
104
- required_rubygems_version: !ruby/object:Gem::Requirement
108
+ hash: -3189528604600378611
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
110
  none: false
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- hash: 3
110
- segments:
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ segments:
111
116
  - 0
112
- version: "0"
117
+ hash: -3189528604600378611
113
118
  requirements: []
114
-
115
119
  rubyforge_project:
116
- rubygems_version: 1.8.15
120
+ rubygems_version: 1.8.10
117
121
  signing_key:
118
122
  specification_version: 3
119
123
  summary: Really simple rubygem hosting
120
124
  test_files: []
121
-