spitball 0.1.5 → 0.2.0
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.
- data/README.md +19 -0
- data/VERSION +1 -1
- data/lib/spitball/file_lock.rb +19 -7
- data/lib/spitball/remote.rb +5 -1
- data/lib/spitball.rb +6 -9
- data/spec/spec_helper.rb +31 -0
- data/spec/spitball_spec.rb +143 -6
- data/spitball.gemspec +2 -2
- metadata +5 -5
data/README.md
CHANGED
@@ -8,3 +8,22 @@ deployment.
|
|
8
8
|
Also comes with `spitball-server`, a small sinatra app that you can run
|
9
9
|
on a dedicated build server. The `spitball` command line client can then
|
10
10
|
pull packages down from said server.
|
11
|
+
|
12
|
+
### Usage
|
13
|
+
|
14
|
+
Usage: spitball [options] GEMFILE ARCHIVE
|
15
|
+
|
16
|
+
options:
|
17
|
+
-h, --host HOST Get the tarball from a remote spitball server
|
18
|
+
-p, --port PORT Specify the remote server port. Default 8080
|
19
|
+
--without a,b,c Excluded groups in the tarball. Does not apply to remote spitballs
|
20
|
+
--version
|
21
|
+
|
22
|
+
environment variables:
|
23
|
+
SPITBALL_CACHE Specifies the cache dir. Defaults to /tmp/spitball
|
24
|
+
|
25
|
+
### TODO
|
26
|
+
|
27
|
+
Lots of things are changing in bundler 1.0. We're stuck on 0.9.5 for
|
28
|
+
now, but once we get to 1.0, this tool will probably work with lock
|
29
|
+
files instead of gem files, for more predictable builds.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/spitball/file_lock.rb
CHANGED
@@ -1,19 +1,31 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class Spitball::FileLock
|
4
|
+
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def acquire_lock
|
6
12
|
|
7
13
|
File.open(pre_lock_path, 'w') {|f| f.write Process.pid }
|
8
14
|
|
9
|
-
system "ln #{pre_lock_path} #{
|
10
|
-
File.read(
|
15
|
+
system "ln #{pre_lock_path} #{path} > /dev/null 2>&1"
|
16
|
+
File.read(path).to_i == Process.pid
|
11
17
|
ensure
|
12
18
|
FileUtils.rm_f pre_lock_path
|
13
19
|
end
|
14
20
|
|
15
21
|
# seems silly to lock to release lock
|
16
|
-
def release_lock
|
17
|
-
FileUtils.rm_f
|
22
|
+
def release_lock
|
23
|
+
FileUtils.rm_f path if acquire_lock
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def pre_lock_path
|
29
|
+
"#{path}_pre_#{Process.pid}"
|
18
30
|
end
|
19
31
|
end
|
data/lib/spitball/remote.rb
CHANGED
@@ -3,6 +3,8 @@ require 'uri'
|
|
3
3
|
|
4
4
|
class Spitball::Remote
|
5
5
|
|
6
|
+
WAIT_SECONDS = 30
|
7
|
+
|
6
8
|
def initialize(gemfile, host, port)
|
7
9
|
@gemfile = gemfile
|
8
10
|
@host = host
|
@@ -25,12 +27,14 @@ class Spitball::Remote
|
|
25
27
|
when '201' # Created
|
26
28
|
Net::HTTP.get(URI.parse(res['Location']))
|
27
29
|
when '202' # Accepted
|
28
|
-
|
30
|
+
(WAIT_SECONDS / 2).times do
|
29
31
|
sleep 2
|
30
32
|
try = Net::HTTP.get_response(URI.parse(res['Location']))
|
31
33
|
next if try.code != '200'
|
32
34
|
return try.body
|
33
35
|
end
|
36
|
+
|
37
|
+
raise SpitballServerFailure, "Spitball build timed out. The build failed or it's just taking a while..."
|
34
38
|
else
|
35
39
|
raise SpitballServerFailure, "Expected 2xx response code. Got #{res.code}."
|
36
40
|
end
|
data/lib/spitball.rb
CHANGED
@@ -13,7 +13,6 @@ class Spitball
|
|
13
13
|
VERSION = '1.0'
|
14
14
|
|
15
15
|
include Spitball::Digest
|
16
|
-
include Spitball::FileLock
|
17
16
|
|
18
17
|
attr_reader :gemfile, :options
|
19
18
|
|
@@ -35,11 +34,13 @@ class Spitball
|
|
35
34
|
Spitball::Repo.make_cache_dir
|
36
35
|
|
37
36
|
unless cached?
|
38
|
-
|
37
|
+
lock = Spitball::FileLock.new(bundle_path('lock'))
|
38
|
+
|
39
|
+
if lock.acquire_lock
|
39
40
|
begin
|
40
41
|
create_bundle
|
41
42
|
ensure
|
42
|
-
release_lock
|
43
|
+
lock.release_lock
|
43
44
|
end
|
44
45
|
elsif sync
|
45
46
|
sleep 0.1 until cached?
|
@@ -52,14 +53,10 @@ class Spitball
|
|
52
53
|
|
53
54
|
File.open(gemfile_path, 'w') {|f| f.write gemfile }
|
54
55
|
|
55
|
-
if system "cd #{bundle_path} && bundle install #{bundle_path} --disable-shared-gems #{without_clause}
|
56
|
-
FileUtils.rm_rf File.join(bundle_path, "cache")
|
57
|
-
|
56
|
+
if system "cd #{bundle_path} && bundle install #{bundle_path} --disable-shared-gems #{without_clause}"
|
58
57
|
system "tar czf #{tarball_path}.#{Process.pid} -C #{bundle_path} ."
|
59
58
|
system "mv #{tarball_path}.#{Process.pid} #{tarball_path}"
|
60
|
-
|
61
59
|
else
|
62
|
-
#FileUtils.rm_rf gemfile_path
|
63
60
|
raise BundleCreationFailure, "Bundle build failure."
|
64
61
|
end
|
65
62
|
|
@@ -67,7 +64,7 @@ class Spitball
|
|
67
64
|
end
|
68
65
|
|
69
66
|
def without_clause
|
70
|
-
without = options[:without] || []
|
67
|
+
without = Array(options[:without] || [])
|
71
68
|
return '' if without.empty?
|
72
69
|
|
73
70
|
"--without=#{without.join(',')}"
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
SPEC_DIR = File.dirname(__FILE__)
|
2
2
|
|
3
|
+
SPEC_BIN_PATH = File.expand_path('bin', SPEC_DIR)
|
4
|
+
|
3
5
|
SPITBALL_CACHE = ENV['SPITBALL_CACHE'] = File.expand_path('cache', SPEC_DIR)
|
4
6
|
|
7
|
+
ENV['PATH'] = [SPEC_BIN_PATH, ENV['PATH']].join(':') unless ENV['PATH'].include? SPEC_BIN_PATH
|
8
|
+
|
5
9
|
$: << File.expand_path("../lib", SPEC_DIR)
|
6
10
|
|
7
11
|
require 'rubygems'
|
@@ -14,10 +18,12 @@ Spec::Runner.configure do |config|
|
|
14
18
|
config.mock_with :rr
|
15
19
|
config.before do
|
16
20
|
purge_test_cache
|
21
|
+
purge_bin
|
17
22
|
end
|
18
23
|
|
19
24
|
config.after :all do
|
20
25
|
purge_test_cache
|
26
|
+
purge_bin
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
@@ -27,3 +33,28 @@ end
|
|
27
33
|
def purge_test_cache
|
28
34
|
FileUtils.rm_rf SPITBALL_CACHE
|
29
35
|
end
|
36
|
+
|
37
|
+
def make_bundler
|
38
|
+
bundle_path = File.expand_path('bundle', SPEC_BIN_PATH)
|
39
|
+
FileUtils.mkdir_p SPEC_BIN_PATH
|
40
|
+
File.open(bundle_path, 'w') { |f| yield f }
|
41
|
+
FileUtils.chmod(0755, bundle_path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def use_success_bundler
|
45
|
+
make_bundler do |f|
|
46
|
+
f.puts "#!/bin/sh"
|
47
|
+
f.puts "mkdir -p $2/gems"
|
48
|
+
f.puts "echo WIN > $2/gems/gem"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def use_fail_bundler
|
53
|
+
make_bundler do |f|
|
54
|
+
f.puts "exit 1"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def purge_bin
|
59
|
+
FileUtils.rm_rf File.expand_path('bin', SPEC_DIR)
|
60
|
+
end
|
data/spec/spitball_spec.rb
CHANGED
@@ -1,35 +1,132 @@
|
|
1
1
|
require 'spec/spec_helper'
|
2
2
|
|
3
3
|
describe Spitball do
|
4
|
-
|
4
|
+
before do
|
5
|
+
use_success_bundler
|
6
|
+
|
7
|
+
@gemfile = <<-end_gemfile
|
8
|
+
source :rubygems
|
9
|
+
gem "activerecord"
|
10
|
+
end_gemfile
|
11
|
+
|
12
|
+
@spitball = Spitball.new(@gemfile)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "cached?" do
|
16
|
+
it "returns true if the tarball has already been cached" do
|
17
|
+
@spitball.should_not be_cached
|
18
|
+
@spitball.cache!
|
19
|
+
@spitball.should be_cached
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "cache!" do
|
24
|
+
it "returns if the spitball is cached" do
|
25
|
+
mock(@spitball).cached? { true }
|
26
|
+
mock.instance_of(Spitball::FileLock).acquire_lock.never
|
27
|
+
mock.instance_of(Spitball::FileLock).release_lock.never
|
28
|
+
mock(@spitball).create_bundle(anything).never
|
29
|
+
|
30
|
+
@spitball.cache!
|
31
|
+
end
|
32
|
+
|
33
|
+
it "creates the bundle if it acquires the lock" do
|
34
|
+
mock.instance_of(Spitball::FileLock).acquire_lock { true }
|
35
|
+
mock(@spitball).create_bundle
|
36
|
+
mock.instance_of(Spitball::FileLock).release_lock
|
37
|
+
|
38
|
+
@spitball.cache!
|
39
|
+
end
|
40
|
+
|
41
|
+
it "does not create the bundle if it does not acquire the lock" do
|
42
|
+
mock.instance_of(Spitball::FileLock).release_lock.never
|
43
|
+
mock.instance_of(Spitball::FileLock).acquire_lock { false }
|
44
|
+
mock(@spitball).create_bundle.never
|
45
|
+
|
46
|
+
@spitball.cache!(false)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "blocks if it does not acquire the lock and sync is true (default)" do
|
50
|
+
cached = false
|
51
|
+
done_caching = false
|
52
|
+
|
53
|
+
mock.instance_of(Spitball::FileLock).acquire_lock { false }
|
54
|
+
stub(@spitball).cached? { cached }
|
55
|
+
|
56
|
+
t = Thread.new do
|
57
|
+
@spitball.cache!
|
58
|
+
done_caching = true
|
59
|
+
end
|
60
|
+
|
61
|
+
sleep 0.5
|
62
|
+
done_caching.should_not == true
|
63
|
+
|
64
|
+
cached = true
|
65
|
+
t.join
|
66
|
+
done_caching.should == true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "create_bundle" do
|
71
|
+
it "generates a bundle at the bundle_path" do
|
72
|
+
@spitball.create_bundle
|
73
|
+
|
74
|
+
File.exist?(@spitball.tarball_path).should == true
|
75
|
+
end
|
76
|
+
|
77
|
+
it "raises an exception if bundle creation fails" do
|
78
|
+
use_fail_bundler
|
79
|
+
|
80
|
+
lambda { @spitball.create_bundle }.should raise_error(Spitball::BundleCreationFailure)
|
81
|
+
File.exist?(@spitball.tarball_path).should_not == true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "without_clause" do
|
86
|
+
it "returns a --without bundler option if :without is set" do
|
87
|
+
Spitball.new('gemfile', :without => "system").without_clause.should == '--without=system'
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns an empty string if without is not set" do
|
91
|
+
Spitball.new('gemfile').without_clause.should == ''
|
92
|
+
end
|
93
|
+
|
94
|
+
it "allows multiple groups" do
|
95
|
+
Spitball.new('gemfile', :without => ["system", "test"]).without_clause.should == '--without=system,test'
|
96
|
+
end
|
5
97
|
end
|
6
98
|
end
|
7
99
|
|
8
100
|
describe Spitball::FileLock do
|
9
|
-
include Spitball::FileLock
|
10
|
-
|
11
101
|
describe "acquire_lock" do
|
12
102
|
before do
|
13
103
|
Spitball::Repo.make_cache_dir
|
14
104
|
@lock_path = File.expand_path('test.lock', SPITBALL_CACHE)
|
105
|
+
@lock = Spitball::FileLock.new(@lock_path)
|
15
106
|
end
|
16
107
|
|
17
108
|
it "returns true if the lock is acquired" do
|
18
|
-
|
109
|
+
@lock.acquire_lock.should == true
|
19
110
|
end
|
20
111
|
|
21
112
|
it "returns false if the lock is not acquired" do
|
22
|
-
fork { acquire_lock
|
113
|
+
fork { @lock.acquire_lock; exit! }
|
23
114
|
Process.wait
|
24
115
|
|
25
|
-
|
116
|
+
@lock.acquire_lock.should == false
|
26
117
|
end
|
27
118
|
end
|
28
119
|
end
|
29
120
|
|
30
121
|
describe Spitball::Repo do
|
122
|
+
before do
|
123
|
+
Spitball::Repo.make_cache_dir
|
124
|
+
end
|
125
|
+
|
31
126
|
describe "make_cache_dir" do
|
32
127
|
it "creates the correct cache dir" do
|
128
|
+
FileUtils.rm_rf(SPITBALL_CACHE)
|
129
|
+
|
33
130
|
File.exist?(SPITBALL_CACHE).should_not == true
|
34
131
|
Spitball::Repo.make_cache_dir
|
35
132
|
File.exist?(SPITBALL_CACHE).should == true
|
@@ -49,4 +146,44 @@ describe Spitball::Repo do
|
|
49
146
|
Spitball::Repo.path('digest', 'tgz').should =~ %r[bundle_digest\.tgz$]
|
50
147
|
end
|
51
148
|
end
|
149
|
+
|
150
|
+
describe "exist?" do
|
151
|
+
it "returns true if tarball for a digest has been exists" do
|
152
|
+
Spitball::Repo.exist?('digest').should_not == true
|
153
|
+
File.open(Spitball::Repo.path('digest', 'tgz'), 'w') {|f| f.write 'tarball!' }
|
154
|
+
Spitball::Repo.exist?('digest').should == true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "gemfile" do
|
159
|
+
it "returns the contents of the cached gemfile for a digest" do
|
160
|
+
gemfile = 'gem :memcached'
|
161
|
+
File.open(Spitball::Repo.path('digest', 'gemfile'), 'w') {|f| f.write gemfile }
|
162
|
+
Spitball::Repo.gemfile('digest').should == gemfile
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "list_cached" do
|
167
|
+
it "returns a list of cached bundles" do
|
168
|
+
File.open(Spitball::Repo.path('digest', 'tgz'), 'w') {|f| f.write 'tarball!' }
|
169
|
+
File.open(Spitball::Repo.path('digest2', 'tgz'), 'w') {|f| f.write 'tarball2!' }
|
170
|
+
|
171
|
+
Spitball::Repo.list_cached.should == ['digest', 'digest2']
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe Spitball::Digest do
|
177
|
+
it "generates a digest based on the spitball's options and gemfile" do
|
178
|
+
[Spitball.new('gemfile contents', :without => "system").digest,
|
179
|
+
Spitball.new('gemfile contents 2', :without => "system").digest,
|
180
|
+
Spitball.new('gemfile', :without => "other_group").digest,
|
181
|
+
Spitball.new('gemfile').digest
|
182
|
+
].uniq.length.should == 4
|
183
|
+
end
|
184
|
+
|
185
|
+
it "provides a hash equal to the digest's hash"do
|
186
|
+
spitball = Spitball.new('gemfile contents')
|
187
|
+
spitball.hash.should == spitball.digest.hash
|
188
|
+
end
|
52
189
|
end
|
data/spitball.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{spitball}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Matt Freels", "Brandon Mitchell"]
|
12
|
-
s.date = %q{2010-07-
|
12
|
+
s.date = %q{2010-07-29}
|
13
13
|
s.description = %q{Use bundler to generate gem tarball packages.}
|
14
14
|
s.email = %q{freels@twitter.com}
|
15
15
|
s.executables = ["spitball", "spitball-server"]
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spitball
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt Freels
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-07-
|
19
|
+
date: 2010-07-29 00:00:00 -07:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|