gemgate 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ AWS_ACCESS_KEY_ID=changeme
2
+ AWS_SECRET_ACCESS_KEY=changeme
3
+ GEMGATE_AUTH=foo:bar
4
+ S3_BUCKET=gemgate-development
5
+ S3_KEY_PREFIX=deadbeef
@@ -0,0 +1,2 @@
1
+ .env
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1 @@
1
+ web: bundle exec rackup -I lib -p $PORT
@@ -0,0 +1,46 @@
1
+ # gemgate
2
+
3
+ gemgate receives built gems and makes them available via S3.
4
+
5
+ Example use:
6
+
7
+ ```bash
8
+ $ gem build foobar.gemspec #=> produces foobar-0.0.1.gem
9
+ $ curl -F file=@foobar-0.0.1.gem -u foo:bar https://gemgate.herokuapp.com/
10
+ $ gem sources -a https://gemgate.s3.amazonaws.com/deadbeef/
11
+ $ gem install foobar -v 0.0.1
12
+ ```
13
+
14
+ ## Running locally
15
+
16
+ ```bash
17
+ $ cp .env.sample .env
18
+ # edit .env
19
+ $ foreman start
20
+ $ curl -F file=@foobar-0.0.1.gem -u $GEMGATE_AUTH http://localhost:5000/
21
+ $ gem sources -a https://gemgate-development.s3.amazonaws.com/$S3_KEY_PREFIX
22
+ $ gem install foobar -v 0.0.1
23
+ ```
24
+
25
+ ## Deployment on Heroku
26
+
27
+ ```bash
28
+ $ heroku create gemgate-production -s cedar
29
+ $ heroku config:add -a gemgate-production \
30
+ AWS_ACCESS_KEY_ID=... \
31
+ AWS_SECRET_ACCESS_KEY=... \
32
+ GEMGATE_AUTH=foo:bar \
33
+ S3_BUCKET=... \
34
+ S3_KEY_PREFIX=...
35
+ $ git push heroku master
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ * `GEMGATE_AUTH`: User and password to require for basic auth, joined by `:`
41
+ * `AWS_ACCESS_KEY_ID`: The AWS access key to use when communicating with S3
42
+ * `AWS_SECRET_ACCESS_KEY`: The AWS secret access key to use when communicating with S3
43
+ * `S3_BUCKET`: Name of the S3 bucket to use
44
+ * `S3_KEY_PREFIX`: Probably random string (eg created with `openssl rand -hex 32`) to prefix file/directory keys with. Mainly for security.
45
+
46
+ It's recommended you use [IAM credentials](https://gist.github.com/34e08aaf5e5e87814c72) with bucket-specific access for S3.
@@ -0,0 +1,8 @@
1
+ begin
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+ rescue LoadError
8
+ end
@@ -0,0 +1,3 @@
1
+ require "gemgate"
2
+
3
+ run Gemgate::Web
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path("../lib/gemgate/version", __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ["Dan Peterson"]
7
+ gem.email = ["dpiddy@gmail.com"]
8
+ gem.description = %q{Host a private gem repository at S3}
9
+ gem.summary = %q{Host a private gem repository at S3}
10
+ gem.homepage = "https://github.com/dpiddy/gemgate"
11
+
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
+ gem.files = `git ls-files`.split("\n")
14
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ gem.name = "gemgate"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = Gemgate::VERSION
18
+
19
+ gem.add_dependency "sinatra", "~> 1.3.2"
20
+ gem.add_dependency "fog", "~> 1.5.0"
21
+
22
+ gem.add_development_dependency "rack-test"
23
+ gem.add_development_dependency "rake"
24
+ gem.add_development_dependency "rspec"
25
+ end
@@ -0,0 +1,11 @@
1
+ require "fog"
2
+
3
+ require "gemgate/gem_files"
4
+ require "gemgate/gem_wrapper"
5
+ require "gemgate/index"
6
+ require "gemgate/specs_index"
7
+ require "gemgate/quick_marshal_specs"
8
+ require "gemgate/repository"
9
+ require "gemgate/web"
10
+
11
+ require "gemgate/storage/s3"
@@ -0,0 +1,9 @@
1
+ module Gemgate
2
+ class GemFiles
3
+ attr_accessor :storage
4
+
5
+ def add(gem)
6
+ storage.create("gems/#{gem.filename}", gem.data)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ module Gemgate
2
+ class GemWrapper
3
+ def self.from_path(path)
4
+ new.tap do |gem_wrapper|
5
+ gem_wrapper.spec = Gem::Package.open(File.open(path)) {|p| p.metadata }
6
+ gem_wrapper.path = path
7
+ end
8
+ end
9
+
10
+ attr_accessor :spec, :path
11
+
12
+ def filename
13
+ spec.file_name
14
+ end
15
+
16
+ def spec_filename
17
+ spec.spec_name
18
+ end
19
+
20
+ def name
21
+ spec.name
22
+ end
23
+
24
+ def version
25
+ spec.version
26
+ end
27
+
28
+ def prerelease?
29
+ spec.version.prerelease?
30
+ end
31
+
32
+ def platform
33
+ platform = spec.original_platform
34
+ platform = Gem::Platform::RUBY if platform.nil? or platform.empty?
35
+ platform
36
+ end
37
+
38
+ def data
39
+ File.open(path)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ module Gemgate
2
+ class Index
3
+ attr_accessor :storage
4
+ attr_writer :specs, :latest_specs, :prerelease_specs, :quick_marshal_specs
5
+
6
+ def add(gem)
7
+ specs.add(gem)
8
+ latest_specs.add(gem)
9
+ prerelease_specs.add(gem)
10
+ quick_marshal_specs.add(gem)
11
+ end
12
+
13
+ private
14
+
15
+ def specs
16
+ @specs ||= SpecsIndex.new("specs.4.8.gz").tap do |s|
17
+ s.storage = storage
18
+ s.conditions << lambda {|g| !g.prerelease? }
19
+ end
20
+ end
21
+
22
+ def latest_specs
23
+ @latest_specs ||= SpecsIndex.new("latest_specs.4.8.gz").tap do |s|
24
+ s.storage = storage
25
+ s.conditions << lambda {|g| !g.prerelease? }
26
+ end
27
+ end
28
+
29
+ def prerelease_specs
30
+ @prerelease_specs ||= SpecsIndex.new("prerelease_specs.4.8.gz").tap do |s|
31
+ s.storage = storage
32
+ s.conditions << lambda {|g| g.prerelease? }
33
+ end
34
+ end
35
+
36
+ def quick_marshal_specs
37
+ @quick_marshal_specs || QuickMarshalSpecs.new.tap {|l| l.storage = storage }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ module Gemgate
2
+ class QuickMarshalSpecs
3
+ class Entry
4
+ def initialize(gem)
5
+ @gem = gem
6
+ end
7
+
8
+ def filename
9
+ "quick/Marshal.4.8/#{@gem.spec_filename}.rz"
10
+ end
11
+
12
+ def spec
13
+ @gem.spec
14
+ end
15
+ end
16
+
17
+ attr_accessor :storage
18
+
19
+ def add(gem)
20
+ entry = Entry.new(gem)
21
+
22
+ storage.create(entry.filename, to_deflated_marshal(entry.spec))
23
+ end
24
+
25
+ private
26
+
27
+ def to_deflated_marshal(data)
28
+ Gem.deflate(Marshal.dump(data))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Gemgate
2
+ class Repository
3
+ attr_writer :realizer, :gem_files, :index
4
+
5
+ def add_gem(path)
6
+ gem = realizer.call(path)
7
+
8
+ gem_files.add(gem)
9
+ index.add(gem)
10
+ end
11
+
12
+ private
13
+
14
+ def realizer
15
+ @realizer || GemWrapper.public_method(:from_path)
16
+ end
17
+
18
+ def gem_files
19
+ @gem_files ||= GemFiles.new.tap {|f| f.storage = storage }
20
+ end
21
+
22
+ def storage
23
+ @storage ||= Storage::S3.new
24
+ end
25
+
26
+ def index
27
+ @index ||= Index.new.tap {|i| i.storage = storage }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ module Gemgate
2
+ class SpecsIndex
3
+ attr_accessor :storage
4
+ attr_reader :conditions
5
+
6
+ def initialize(filename)
7
+ @filename = filename
8
+ @conditions = []
9
+ end
10
+
11
+ def add(gem)
12
+ if conditions.all? {|c| c.call(gem) }
13
+ update_with(gem)
14
+ else
15
+ ensure_exists
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def ensure_exists
22
+ unless existing_file_data
23
+ store([])
24
+ end
25
+ end
26
+
27
+ def update_with(gem)
28
+ # create our own instance of Gem::Version for the incoming gem version.
29
+ # this works around an issue where the @hash instance variable is already set
30
+ # in the YAML-dumped metadata in the uploaded gem, which breaks our uniq'ing of the
31
+ # index.
32
+ store(existing_data + [[gem.name, Gem::Version.create(gem.version.version), gem.platform]])
33
+ end
34
+
35
+ def existing_file_data
36
+ @existing_file_data ||= storage.get(@filename)
37
+ end
38
+
39
+ def existing_data
40
+ if existing_file_data
41
+ from_gzipped_marshal(existing_file_data).map do |(name, version, platform)|
42
+ # create our own Gem::Version here, too
43
+ [name, Gem::Version.create(version.version), platform]
44
+ end
45
+ else
46
+ []
47
+ end
48
+ end
49
+
50
+ def store(data)
51
+ storage.update(@filename, to_gzipped_marshal(data.uniq))
52
+ end
53
+
54
+ def to_gzipped_marshal(data)
55
+ Gem.gzip(Marshal.dump(data))
56
+ end
57
+
58
+ def from_gzipped_marshal(data)
59
+ Marshal.load(Gem.gunzip(data))
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,48 @@
1
+ module Gemgate
2
+ module Storage
3
+ class S3
4
+
5
+ class Error < StandardError; end
6
+
7
+ def initialize(env = ENV)
8
+ @env = env
9
+ end
10
+
11
+ def get(path)
12
+ if file = remote_directory.files.get(prefix(path))
13
+ file.body
14
+ end
15
+ end
16
+
17
+ def create(path, body)
18
+ remote_directory.files.create(:key => prefix(path), :body => body, :acl => "public-read")
19
+ end
20
+
21
+ def update(path, body)
22
+ create(path, body)
23
+ end
24
+
25
+ private
26
+
27
+ def env!(name)
28
+ @env[name] or raise "ENV[#{name}] must be set"
29
+ end
30
+
31
+ def prefix(path)
32
+ File.join(env!("S3_KEY_PREFIX"), path)
33
+ end
34
+
35
+ def remote_directory
36
+ fog.directories.get(remote_directory_name) or raise Error, "Bucket `#{remote_directory_name}` doesn't exist"
37
+ end
38
+
39
+ def remote_directory_name
40
+ env!("S3_BUCKET")
41
+ end
42
+
43
+ def fog
44
+ Fog::Storage.new(:provider => "AWS", :aws_access_key_id => env!("AWS_ACCESS_KEY_ID"), :aws_secret_access_key => env!("AWS_SECRET_ACCESS_KEY"))
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Gemgate
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,40 @@
1
+ require "sinatra/base"
2
+
3
+ module Gemgate
4
+ class Web < Sinatra::Base
5
+
6
+ class << self
7
+ attr_accessor :repository
8
+ end
9
+
10
+ def self.env!(name)
11
+ ENV[name] or raise "ENV[#{name}] must be set"
12
+ end
13
+
14
+ def self.auth
15
+ env!("GEMGATE_AUTH").split(":")
16
+ end
17
+
18
+ use Rack::Auth::Basic, "gemgate" do |username, password|
19
+ [username, password] == auth
20
+ end
21
+
22
+ configure :test do
23
+ enable :raise_errors
24
+ end
25
+
26
+ disable :show_exceptions
27
+
28
+ post "/" do
29
+ repository.add_gem(params[:file][:tempfile].path)
30
+
31
+ status 200
32
+ end
33
+
34
+ helpers do
35
+ def repository
36
+ @repository ||= self.class.repository || Repository.new
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,96 @@
1
+ require "spec_helper"
2
+
3
+ describe "acceptance: push" do
4
+ it "basically works" do
5
+ post "/", :file => Rack::Test::UploadedFile.new(fixture("foobar-0.0.1.gem"), "application/octet-stream")
6
+
7
+ last_response.status.should == 200
8
+
9
+ file = directory.files.get("deadbeef/gems/foobar-0.0.1.gem")
10
+ file.should_not be_nil, "gems/foobar-0.0.1.gem should exist"
11
+ file.public_url.should_not be_nil, "gems/foobar-0.0.1.gem should be public"
12
+ file.body.tap {|b| b.force_encoding("binary") }.should == fixture_read("foobar-0.0.1.gem")
13
+
14
+ file = directory.files.get("deadbeef/latest_specs.4.8.gz")
15
+ file.should_not be_nil, "latest_specs.4.8.gz should exist"
16
+ file.public_url.should_not be_nil, "latest_specs.4.8.gz should be public"
17
+
18
+ latest_specs = Marshal.load(Gem.gunzip(file.body))
19
+ latest_specs.should == [["foobar", Gem::Version.new("0.0.1"), "ruby"]]
20
+
21
+ file = directory.files.get("deadbeef/specs.4.8.gz")
22
+ file.should_not be_nil, "specs.4.8.gz should exist"
23
+ file.public_url.should_not be_nil, "specs.4.8.gz should be public"
24
+
25
+ specs = Marshal.load(Gem.gunzip(file.body))
26
+ specs.should == latest_specs
27
+
28
+ file = directory.files.get("deadbeef/prerelease_specs.4.8.gz")
29
+ file.should_not be_nil, "prerelease_specs.4.8.gz should exist"
30
+ file.public_url.should_not be_nil, "prerelease_specs.4.8.gz should be public"
31
+
32
+ prerelease_specs = Marshal.load(Gem.gunzip(file.body))
33
+ prerelease_specs.should == []
34
+
35
+ file = directory.files.get("deadbeef/quick/Marshal.4.8/foobar-0.0.1.gemspec.rz")
36
+ file.should_not be_nil, "quick spec should exist"
37
+ file.public_url.should_not be_nil, "quick spec should be public"
38
+
39
+ quick_spec = Marshal.load(Gem.inflate(file.body))
40
+ quick_spec.should == Gemgate::GemWrapper.from_path(fixture("foobar-0.0.1.gem")).spec
41
+ end
42
+
43
+ it "basically works for a prerelease gem" do
44
+ post "/", :file => Rack::Test::UploadedFile.new(fixture("foobar-0.0.1.pre.gem"), "application/octet-stream")
45
+
46
+ last_response.status.should == 200
47
+
48
+ file = directory.files.get("deadbeef/gems/foobar-0.0.1.pre.gem")
49
+ file.should_not be_nil, "gems/foobar-0.0.1.pre.gem should exist"
50
+ file.public_url.should_not be_nil, "gems/foobar-0.0.1.pre.gem should be public"
51
+ file.body.tap {|b| b.force_encoding("binary") }.should == fixture_read("foobar-0.0.1.pre.gem")
52
+
53
+ file = directory.files.get("deadbeef/latest_specs.4.8.gz")
54
+ file.should_not be_nil, "latest_specs.4.8.gz should exist"
55
+ file.public_url.should_not be_nil, "latest_specs.4.8.gz should be public"
56
+
57
+ latest_specs = Marshal.load(Gem.gunzip(file.body))
58
+ latest_specs.should == []
59
+
60
+ file = directory.files.get("deadbeef/prerelease_specs.4.8.gz")
61
+ file.should_not be_nil, "prerelease_specs.4.8.gz should exist"
62
+ file.public_url.should_not be_nil, "prerelease_specs.4.8.gz should be public"
63
+
64
+ prerelease_specs = Marshal.load(Gem.gunzip(file.body))
65
+ prerelease_specs.should == [["foobar", Gem::Version.new("0.0.1.pre"), "ruby"]]
66
+
67
+ file = directory.files.get("deadbeef/quick/Marshal.4.8/foobar-0.0.1.pre.gemspec.rz")
68
+ file.should_not be_nil, "quick spec should exist"
69
+ file.public_url.should_not be_nil, "quick spec should be public"
70
+
71
+ quick_spec = Marshal.load(Gem.inflate(file.body))
72
+ quick_spec.should == Gemgate::GemWrapper.from_path(fixture("foobar-0.0.1.pre.gem")).spec
73
+ end
74
+
75
+ it "does nothing if no authentication info is provded" do
76
+ header "Authorization", nil
77
+
78
+ post "/", :file => Rack::Test::UploadedFile.new(fixture("foobar-0.0.1.pre.gem"), "application/octet-stream")
79
+
80
+ last_response.status.should == 401
81
+
82
+ file = directory.files.get("deadbeef/gems/foobar-0.0.1.pre.gem")
83
+ file.should be_nil, "gems/foobar-0.0.1.pre.gem should not exist"
84
+ end
85
+
86
+ it "does nothing if no authentication info is provded but is incorrect" do
87
+ basic_authorize "in", "correct"
88
+
89
+ post "/", :file => Rack::Test::UploadedFile.new(fixture("foobar-0.0.1.pre.gem"), "application/octet-stream")
90
+
91
+ last_response.status.should == 401
92
+
93
+ file = directory.files.get("deadbeef/gems/foobar-0.0.1.pre.gem")
94
+ file.should be_nil, "gems/foobar-0.0.1.pre.gem should not exist"
95
+ end
96
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::GemFiles do
4
+ it "adds a gem using storage" do
5
+ gem = mock("gem")
6
+ gem.should_receive(:filename) { "foobar-0.0.1.gem" }
7
+ gem.should_receive(:data) { "data" }
8
+
9
+ storage = mock("storage")
10
+ storage.should_receive(:create).with("gems/foobar-0.0.1.gem", "data")
11
+
12
+ subject.storage = storage
13
+
14
+ subject.add(gem)
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::GemWrapper do
4
+ describe ".from_path" do
5
+ it "creates a new instance from the provided path" do
6
+ described_class.from_path(fixture("foobar-0.0.1.gem")).should be_a(described_class)
7
+ end
8
+
9
+ it "sets the spec on the new instance" do
10
+ gem_wrapper = described_class.from_path(fixture("foobar-0.0.1.gem"))
11
+
12
+ gem_wrapper.spec.should == Gem::Package.open(File.open(fixture("foobar-0.0.1.gem"))) {|p| p.metadata }
13
+ end
14
+
15
+ it "sets the path on the new instance" do
16
+ gem_wrapper = described_class.from_path(fixture("foobar-0.0.1.gem"))
17
+
18
+ gem_wrapper.path.should == fixture("foobar-0.0.1.gem")
19
+ end
20
+ end
21
+
22
+ subject { described_class.from_path(fixture("foobar-0.0.1.gem")) }
23
+
24
+ it "returns its filename" do
25
+ subject.filename.should == "foobar-0.0.1.gem"
26
+ end
27
+
28
+ it "returns its spec filename" do
29
+ subject.spec_filename.should == "foobar-0.0.1.gemspec"
30
+ end
31
+
32
+ it "returns its prerelease status" do
33
+ subject.prerelease?.should be_false
34
+ end
35
+
36
+ it "returns an io with its data" do
37
+ subject.data.should be_a(IO)
38
+ end
39
+
40
+ it "returns its name" do
41
+ subject.name.should == "foobar"
42
+ end
43
+
44
+ it "returns its version" do
45
+ subject.version.should == Gem::Version.new("0.0.1")
46
+ end
47
+
48
+ it "returns its platform" do
49
+ subject.platform.should == "ruby"
50
+ end
51
+ end
@@ -0,0 +1,83 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::Index do
4
+ it "adds a gem to latest specs" do
5
+ gem = stub("gem")
6
+
7
+ specs = stub("specs").as_null_object
8
+
9
+ latest_specs = mock("latest specs")
10
+ latest_specs.should_receive(:add).with(gem)
11
+
12
+ prerelease_specs = stub("latest specs").as_null_object
13
+
14
+ quick_marshal_specs = stub("quick marshal specs").as_null_object
15
+
16
+ subject.specs = specs
17
+ subject.latest_specs = latest_specs
18
+ subject.prerelease_specs = prerelease_specs
19
+ subject.quick_marshal_specs = quick_marshal_specs
20
+
21
+ subject.add(gem)
22
+ end
23
+
24
+ it "adds a gem to specs" do
25
+ gem = stub("gem")
26
+
27
+ specs = mock("specs")
28
+ specs.should_receive(:add).with(gem)
29
+
30
+ latest_specs = stub("latest specs").as_null_object
31
+
32
+ prerelease_specs = stub("latest specs").as_null_object
33
+
34
+ quick_marshal_specs = stub("quick marshal specs").as_null_object
35
+
36
+ subject.specs = specs
37
+ subject.prerelease_specs = prerelease_specs
38
+ subject.latest_specs = latest_specs
39
+ subject.quick_marshal_specs = quick_marshal_specs
40
+
41
+ subject.add(gem)
42
+ end
43
+
44
+ it "adds a gem to the prerelease specs" do
45
+ gem = stub("gem")
46
+
47
+ specs = stub("specs").as_null_object
48
+
49
+ latest_specs = stub("latest specs").as_null_object
50
+
51
+ prerelease_specs = mock("prerelease specs")
52
+ prerelease_specs.should_receive(:add).with(gem)
53
+
54
+ quick_marshal_specs = stub("quick marshal specs").as_null_object
55
+
56
+ subject.specs = specs
57
+ subject.latest_specs = latest_specs
58
+ subject.prerelease_specs = prerelease_specs
59
+ subject.quick_marshal_specs = quick_marshal_specs
60
+
61
+ subject.add(gem)
62
+ end
63
+
64
+ it "adds a gem to the quick marshal specs" do
65
+ gem = stub("gem")
66
+
67
+ specs = stub("specs").as_null_object
68
+
69
+ latest_specs = stub("latest specs").as_null_object
70
+
71
+ prerelease_specs = stub("latest specs").as_null_object
72
+
73
+ quick_marshal_specs = mock("quick marshal specs")
74
+ quick_marshal_specs.should_receive(:add).with(gem)
75
+
76
+ subject.specs = specs
77
+ subject.latest_specs = latest_specs
78
+ subject.prerelease_specs = prerelease_specs
79
+ subject.quick_marshal_specs = quick_marshal_specs
80
+
81
+ subject.add(gem)
82
+ end
83
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::QuickMarshalSpecs do
4
+ it "adds a quick marshal spec to storage for a gem" do
5
+ storage = mock("storage")
6
+ storage.should_receive(:create).with("quick/Marshal.4.8/foobar-0.0.1.gemspec.rz", Gem.deflate(Marshal.dump("spec")))
7
+
8
+ gem = mock("gem")
9
+ gem.should_receive(:spec_filename) { "foobar-0.0.1.gemspec" }
10
+ gem.should_receive(:spec) { "spec" }
11
+
12
+ subject.storage = storage
13
+
14
+ subject.add(gem)
15
+ end
16
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::Repository do
4
+ it "uses the gem realizer to realize a gem binary blob" do
5
+ realizer = mock("realizer")
6
+ realizer.should_receive(:call).with("hi")
7
+
8
+ gem_files = stub("gem_files").as_null_object
9
+
10
+ index = stub("index").as_null_object
11
+
12
+ subject.realizer = realizer
13
+ subject.gem_files = gem_files
14
+ subject.index = index
15
+
16
+ subject.add_gem("hi")
17
+ end
18
+
19
+ it "adds the realized gem to the gem files" do
20
+ gem = stub("gem")
21
+
22
+ realizer = stub("realizer", :call => gem)
23
+
24
+ gem_files = mock("gem_files")
25
+ gem_files.should_receive(:add).with(gem)
26
+
27
+ index = stub("index").as_null_object
28
+
29
+ subject.realizer = realizer
30
+ subject.gem_files = gem_files
31
+ subject.index = index
32
+
33
+ subject.add_gem("hi")
34
+ end
35
+
36
+ it "adds the realized gem to the index" do
37
+ gem = stub("gem")
38
+
39
+ realizer = stub("realizer", :call => gem)
40
+
41
+ gem_files = stub("gem_files").as_null_object
42
+
43
+ index = mock("index")
44
+ index.should_receive(:add).with(gem)
45
+
46
+ subject.realizer = realizer
47
+ subject.gem_files = gem_files
48
+ subject.index = index
49
+
50
+ subject.add_gem("hi")
51
+ end
52
+ end
@@ -0,0 +1,50 @@
1
+ ENV["RACK_ENV"] = "test"
2
+
3
+ ENV["AWS_ACCESS_KEY_ID"] = "foobar"
4
+ ENV["AWS_SECRET_ACCESS_KEY"] = "foobar"
5
+ ENV["GEMGATE_AUTH"] = "foo:bar"
6
+ ENV["S3_BUCKET"] = "gemgate-test"
7
+ ENV["S3_KEY_PREFIX"] = "deadbeef"
8
+
9
+ require "gemgate"
10
+
11
+ require "rack/test"
12
+
13
+ RSpec.configure do |c|
14
+ c.include Rack::Test::Methods
15
+
16
+ c.before do
17
+ Fog.mock!
18
+ Fog::Mock.reset
19
+
20
+ Gemgate::Web.repository = nil
21
+
22
+ directory.save
23
+
24
+ basic_authorize *ENV["GEMGATE_AUTH"].split(":")
25
+ end
26
+
27
+ def app
28
+ Gemgate::Web
29
+ end
30
+
31
+ def fixture(name)
32
+ File.join(File.expand_path("../fixtures", __FILE__), name)
33
+ end
34
+
35
+ def fixture_read(name)
36
+ File.read(fixture(name), encoding: "binary")
37
+ end
38
+
39
+ def directory
40
+ @directory ||= fog.directories.new(:key => ENV["S3_BUCKET"])
41
+ end
42
+
43
+ def fog
44
+ @fog ||= Fog::Storage.new(
45
+ :provider => "AWS",
46
+ :aws_access_key_id => ENV["AWS_ACCESS_KEY_ID"],
47
+ :aws_secret_access_key => ENV["AWS_SECRET_ACCESS_KEY"]
48
+ )
49
+ end
50
+ end
@@ -0,0 +1,92 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::SpecsIndex do
4
+ subject { described_class.new("foobar_specs.4.8.gz") }
5
+
6
+ it "adds a gem using storage when there is no existing file" do
7
+ gem = mock("gem")
8
+ gem.should_receive(:name) { "foobar" }
9
+ gem.should_receive(:version) { Gem::Version.create("0.0.1") }
10
+ gem.should_receive(:platform) { "ruby" }
11
+
12
+ storage = mock("storage")
13
+ storage.should_receive(:get).with("foobar_specs.4.8.gz") { nil }
14
+ storage.should_receive(:update).with("foobar_specs.4.8.gz", Gem.gzip(Marshal.dump([["foobar", Gem::Version.create("0.0.1"), "ruby"]])))
15
+
16
+ subject.storage = storage
17
+
18
+ subject.add(gem)
19
+ end
20
+
21
+ it "adds a gem using storage when there is an existing file" do
22
+ gem = mock("gem")
23
+ gem.should_receive(:name) { "foobar" }
24
+ gem.should_receive(:version) { Gem::Version.create("0.0.1") }
25
+ gem.should_receive(:platform) { "ruby" }
26
+
27
+ storage = mock("storage")
28
+ storage.should_receive(:get).with("foobar_specs.4.8.gz") { Gem.gzip(Marshal.dump([["something", Gem::Version.create("0.0.2"), "ruby"]])) }
29
+ storage.should_receive(:update).with("foobar_specs.4.8.gz", Gem.gzip(Marshal.dump([["something", Gem::Version.create("0.0.2"), "ruby"], ["foobar", Gem::Version.create("0.0.1"), "ruby"]])))
30
+
31
+ subject.storage = storage
32
+
33
+ subject.add(gem)
34
+ end
35
+
36
+ it "adds if all provided conditions are true" do
37
+ gem = stub("gem", :name => "foobar", :version => Gem::Version.create("0.0.1"), :platform => "ruby")
38
+
39
+ storage = mock("storage")
40
+ storage.should_receive(:get).with("foobar_specs.4.8.gz") { nil }
41
+ storage.should_receive(:update).with("foobar_specs.4.8.gz", Gem.gzip(Marshal.dump([["foobar", Gem::Version.create("0.0.1"), "ruby"]])))
42
+
43
+ subject.storage = storage
44
+
45
+ subject.conditions << lambda {|g| g.name == "foobar" } << lambda {|g| g.version == Gem::Version.create("0.0.1") }
46
+
47
+ subject.add(gem)
48
+ end
49
+
50
+ it "skips adding if not all provided conditions are true" do
51
+ gem = stub("gem", :name => "foobar", :version => Gem::Version.create("0.0.1"), :platform => "ruby")
52
+
53
+ storage = mock("storage")
54
+ storage.should_receive(:get).with("foobar_specs.4.8.gz") { Gem.gzip(Marshal.dump([])) }
55
+ storage.should_not_receive(:update)
56
+
57
+ subject.storage = storage
58
+
59
+ subject.conditions << lambda {|g| g.name == "foobar" } << lambda {|g| g.version == "0.0.2" }
60
+
61
+ subject.add(gem)
62
+ end
63
+
64
+ it "ensures the file exists even when not adding" do
65
+ gem = stub("gem", :name => "foobar", :version => Gem::Version.create("0.0.1"), :platform => "ruby")
66
+
67
+ storage = mock("storage")
68
+ storage.should_receive(:get).with("foobar_specs.4.8.gz") { nil }
69
+ storage.should_receive(:update).with("foobar_specs.4.8.gz", Gem.gzip(Marshal.dump([])))
70
+
71
+ subject.storage = storage
72
+
73
+ subject.conditions << lambda {|g| g.name == "foobar" } << lambda {|g| g.version == Gem::Version.create("0.0.2") }
74
+
75
+ subject.add(gem)
76
+ end
77
+
78
+ it "uniqs the added data" do
79
+ gem = mock("gem")
80
+ gem.should_receive(:name) { "foobar" }
81
+ gem.should_receive(:version) { Gem::Version.create("0.0.1") }
82
+ gem.should_receive(:platform) { "ruby" }
83
+
84
+ storage = mock("storage")
85
+ storage.should_receive(:get).with("foobar_specs.4.8.gz") { Gem.gzip(Marshal.dump([["foobar", Gem::Version.create("0.0.1"), "ruby"]])) }
86
+ storage.should_receive(:update).with("foobar_specs.4.8.gz", Gem.gzip(Marshal.dump([["foobar", Gem::Version.create("0.0.1"), "ruby"]])))
87
+
88
+ subject.storage = storage
89
+
90
+ subject.add(gem)
91
+ end
92
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::Storage::S3 do
4
+ subject { described_class.new("AWS_ACCESS_KEY_ID" => "foobar", "AWS_SECRET_ACCESS_KEY" => "foobar", "S3_BUCKET" => "gemgate-test", "S3_KEY_PREFIX" => "ultrasecure") }
5
+
6
+ it "creates a public file with the given path and body" do
7
+ subject.create("foobar", "hello")
8
+
9
+ remote_file = remote_directory.files.detect {|f| f.key == "ultrasecure/foobar" }
10
+ remote_file.should_not be_nil
11
+
12
+ remote_file.body.should == "hello"
13
+
14
+ remote_file.public_url.should_not be_nil
15
+ end
16
+
17
+ it "updates a public file that already exists" do
18
+ remote_directory.files.create(:key => "ultrasecure/foobar", :body => "first")
19
+
20
+ subject.update("foobar", "second")
21
+
22
+ remote_file = remote_directory.files.detect {|f| f.key == "ultrasecure/foobar" }
23
+ remote_file.body.should == "second"
24
+
25
+ remote_file.public_url.should_not be_nil
26
+ end
27
+
28
+ it "creates a public file that doesn't exist when updating" do
29
+ subject.update("foobar", "created")
30
+
31
+ remote_file = remote_directory.files.detect {|f| f.key == "ultrasecure/foobar" }
32
+ remote_file.body.should == "created"
33
+
34
+ remote_file.public_url.should_not be_nil
35
+ end
36
+
37
+ it "gets an existing file's data" do
38
+ remote_directory.files.create(:key => "ultrasecure/foobar", :body => "hello")
39
+
40
+ subject.get("foobar").should == "hello"
41
+ end
42
+
43
+ it "returns nil when getting a file that doesn't exist" do
44
+ subject.get("foobar").should be_nil
45
+ end
46
+
47
+ it "errors if the bucket doesn't exist" do
48
+ remote_directory.destroy
49
+
50
+ expect { subject.get("foobar") }.to raise_error(described_class::Error, "Bucket `gemgate-test` doesn't exist")
51
+ end
52
+
53
+ def remote_directory
54
+ fog.directories.get("gemgate-test")
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe Gemgate::Web do
4
+ describe "POST /" do
5
+ it "adds the gem to the repository" do
6
+ uploaded_file = Rack::Test::UploadedFile.new(fixture("foobar-0.0.1.gem"), "application/octet-stream")
7
+
8
+ repository = mock("repository")
9
+ repository.should_receive(:add_gem).with(kind_of(String))
10
+
11
+ described_class.repository = repository
12
+
13
+ post "/", :file => uploaded_file
14
+ end
15
+
16
+ it "does not add the gem to the repository if authentication fails" do
17
+ uploaded_file = Rack::Test::UploadedFile.new(fixture("foobar-0.0.1.gem"), "application/octet-stream")
18
+
19
+ repository = mock("repository")
20
+ repository.should_not_receive(:add_gem)
21
+
22
+ described_class.repository = repository
23
+
24
+ basic_authorize "in", "valid"
25
+
26
+ post "/", :file => uploaded_file
27
+ end
28
+
29
+ it "does not add the gem to the repository if authentication info is not provided" do
30
+ uploaded_file = Rack::Test::UploadedFile.new(fixture("foobar-0.0.1.gem"), "application/octet-stream")
31
+
32
+ repository = mock("repository")
33
+ repository.should_not_receive(:add_gem)
34
+
35
+ described_class.repository = repository
36
+
37
+ header "Authorization", nil
38
+
39
+ post "/", :file => uploaded_file
40
+ end
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gemgate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dan Peterson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: fog
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.5.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rack-test
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Host a private gem repository at S3
95
+ email:
96
+ - dpiddy@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .env.sample
102
+ - .gitignore
103
+ - Gemfile
104
+ - Procfile
105
+ - README.md
106
+ - Rakefile
107
+ - config.ru
108
+ - gemgate.gemspec
109
+ - lib/gemgate.rb
110
+ - lib/gemgate/gem_files.rb
111
+ - lib/gemgate/gem_wrapper.rb
112
+ - lib/gemgate/index.rb
113
+ - lib/gemgate/quick_marshal_specs.rb
114
+ - lib/gemgate/repository.rb
115
+ - lib/gemgate/specs_index.rb
116
+ - lib/gemgate/storage/s3.rb
117
+ - lib/gemgate/version.rb
118
+ - lib/gemgate/web.rb
119
+ - spec/acceptance/push_spec.rb
120
+ - spec/fixtures/foobar-0.0.1.gem
121
+ - spec/fixtures/foobar-0.0.1.pre.gem
122
+ - spec/gem_files_spec.rb
123
+ - spec/gem_wrapper_spec.rb
124
+ - spec/index_spec.rb
125
+ - spec/quick_marshal_specs_spec.rb
126
+ - spec/repository_spec.rb
127
+ - spec/spec_helper.rb
128
+ - spec/specs_index_spec.rb
129
+ - spec/storage/s3_spec.rb
130
+ - spec/web_spec.rb
131
+ homepage: https://github.com/dpiddy/gemgate
132
+ licenses: []
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 1.8.23
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: Host a private gem repository at S3
155
+ test_files:
156
+ - spec/acceptance/push_spec.rb
157
+ - spec/fixtures/foobar-0.0.1.gem
158
+ - spec/fixtures/foobar-0.0.1.pre.gem
159
+ - spec/gem_files_spec.rb
160
+ - spec/gem_wrapper_spec.rb
161
+ - spec/index_spec.rb
162
+ - spec/quick_marshal_specs_spec.rb
163
+ - spec/repository_spec.rb
164
+ - spec/spec_helper.rb
165
+ - spec/specs_index_spec.rb
166
+ - spec/storage/s3_spec.rb
167
+ - spec/web_spec.rb