berkshelf 0.4.0.rc4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +2 -0
  2. data/Guardfile +6 -3
  3. data/features/default_locations.feature +122 -0
  4. data/features/install.feature +20 -4
  5. data/features/lockfile.feature +1 -6
  6. data/features/update.feature +2 -3
  7. data/generator_files/Berksfile.erb +2 -0
  8. data/generator_files/gitignore.erb +6 -0
  9. data/lib/berkshelf.rb +6 -10
  10. data/lib/berkshelf/berksfile.rb +203 -14
  11. data/lib/berkshelf/cached_cookbook.rb +5 -1
  12. data/lib/berkshelf/cli.rb +4 -0
  13. data/lib/berkshelf/cookbook_source.rb +49 -91
  14. data/lib/berkshelf/cookbook_store.rb +2 -0
  15. data/lib/berkshelf/downloader.rb +71 -51
  16. data/lib/berkshelf/errors.rb +7 -3
  17. data/lib/berkshelf/formatters.rb +6 -6
  18. data/lib/berkshelf/location.rb +171 -0
  19. data/lib/berkshelf/locations/chef_api_location.rb +252 -0
  20. data/lib/berkshelf/locations/git_location.rb +76 -0
  21. data/lib/berkshelf/locations/path_location.rb +38 -0
  22. data/lib/berkshelf/locations/site_location.rb +150 -0
  23. data/lib/berkshelf/lockfile.rb +2 -2
  24. data/lib/berkshelf/resolver.rb +12 -15
  25. data/lib/berkshelf/uploader.rb +2 -9
  26. data/lib/berkshelf/version.rb +1 -1
  27. data/spec/fixtures/lockfile_spec/without_lock/.gitkeep +0 -0
  28. data/spec/support/chef_api.rb +7 -1
  29. data/spec/unit/berkshelf/berksfile_spec.rb +157 -12
  30. data/spec/unit/berkshelf/cached_cookbook_spec.rb +19 -0
  31. data/spec/unit/berkshelf/cookbook_generator_spec.rb +1 -0
  32. data/spec/unit/berkshelf/cookbook_source_spec.rb +25 -35
  33. data/spec/unit/berkshelf/cookbook_store_spec.rb +3 -3
  34. data/spec/unit/berkshelf/downloader_spec.rb +171 -43
  35. data/spec/unit/berkshelf/formatters_spec.rb +13 -16
  36. data/spec/unit/berkshelf/{cookbook_source/location_spec.rb → location_spec.rb} +10 -10
  37. data/spec/unit/berkshelf/{cookbook_source → locations}/chef_api_location_spec.rb +4 -4
  38. data/spec/unit/berkshelf/{cookbook_source → locations}/git_location_spec.rb +8 -8
  39. data/spec/unit/berkshelf/{cookbook_source → locations}/path_location_spec.rb +5 -5
  40. data/spec/unit/berkshelf/{cookbook_source → locations}/site_location_spec.rb +17 -3
  41. data/spec/unit/berkshelf/lockfile_spec.rb +26 -17
  42. data/spec/unit/berkshelf/resolver_spec.rb +6 -5
  43. data/spec/unit/berkshelf/uploader_spec.rb +6 -4
  44. metadata +27 -31
  45. data/lib/berkshelf/cookbook_source/chef_api_location.rb +0 -256
  46. data/lib/berkshelf/cookbook_source/git_location.rb +0 -78
  47. data/lib/berkshelf/cookbook_source/location.rb +0 -167
  48. data/lib/berkshelf/cookbook_source/path_location.rb +0 -40
  49. data/lib/berkshelf/cookbook_source/site_location.rb +0 -149
  50. data/lib/berkshelf/dsl.rb +0 -45
  51. data/lib/berkshelf/tx_result.rb +0 -12
  52. data/lib/berkshelf/tx_result_set.rb +0 -37
  53. data/spec/fixtures/lockfile_spec/without_lock/Berksfile.lock +0 -5
  54. data/spec/unit/berkshelf/dsl_spec.rb +0 -42
  55. data/spec/unit/berkshelf/tx_result_set_spec.rb +0 -77
  56. data/spec/unit/berkshelf/tx_result_spec.rb +0 -21
@@ -87,8 +87,8 @@ module Berkshelf
87
87
 
88
88
  describe "#cookbooks" do
89
89
  before(:each) do
90
- CookbookSource.new("nginx", "0.101.2").download(subject.storage_path)
91
- CookbookSource.new("mysql", "1.2.6").download(subject.storage_path)
90
+ generate_cookbook(subject.storage_path, "nginx", "0.101.2")
91
+ generate_cookbook(subject.storage_path, "mysql", "1.2.6")
92
92
  end
93
93
 
94
94
  it "returns a list of CachedCookbooks" do
@@ -97,7 +97,7 @@ module Berkshelf
97
97
  end
98
98
  end
99
99
 
100
- it "return an instance of CachedCookbook for every downloaded cookbook" do
100
+ it "contains a CachedCookbook for every cookbook in the storage path" do
101
101
  subject.cookbooks.should have(2).items
102
102
  end
103
103
 
@@ -2,81 +2,209 @@ require 'spec_helper'
2
2
 
3
3
  module Berkshelf
4
4
  describe Downloader do
5
+ describe "ClassMethods" do
6
+ subject { Downloader }
7
+
8
+ describe "::initialize" do
9
+ context "when no value for locations is given" do
10
+ it "sets the @locations instance variable to a blank array" do
11
+ downloader = subject.new(double('store'))
12
+
13
+ downloader.instance_variable_get(:@locations).should be_a(Array)
14
+ downloader.instance_variable_get(:@locations).should have(0).items
15
+ end
16
+ end
17
+
18
+ context "when an explicit value of locations is given" do
19
+ let(:locations) do
20
+ [
21
+ {
22
+ type: :chef_api,
23
+ value: double('capi'),
24
+ options: double('capi_opts')
25
+ },
26
+ {
27
+ type: :chef_api,
28
+ value: double('capi2'),
29
+ options: double('capi_opts2')
30
+ }
31
+ ]
32
+ end
33
+
34
+ it "sets the @locations instance variable to the given locations" do
35
+ downloader = subject.new(double('store'), locations: locations)
36
+
37
+ downloader.instance_variable_get(:@locations).should eql(locations)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
5
43
  subject { Downloader.new(CookbookStore.new(tmp_path)) }
6
- let(:source) { CookbookSource.new("sparkle_motion") }
7
44
 
8
- describe "#enqueue" do
9
- it "should add a source to the queue" do
10
- subject.enqueue(source)
45
+ describe "#download" do
46
+ let(:source) { double('source', name: "artifact", version_constraint: "= 0.10.0") }
47
+ let(:location) { double('location') }
48
+ let(:cached_cookbook) { double('cached') }
49
+
50
+ context "when the source has a location" do
51
+ before(:each) do
52
+ source.stub(:location).and_return(location)
53
+ location.should_receive(:download).with(subject.storage_path).and_return(cached_cookbook)
54
+ source.should_receive(:cached_cookbook=).with(cached_cookbook)
55
+ end
56
+
57
+ it "sends 'download' to the source's location and sets the source's cached_cookbook to the result" do
58
+ subject.download(source).should be_true
59
+ end
60
+
61
+ it "returns an Array containing the cached_cookbook and location used to download" do
62
+ result = subject.download(source)
11
63
 
12
- subject.queue.should have(1).source
64
+ result.should be_a(Array)
65
+ result[0].should eql(cached_cookbook)
66
+ result[1].should eql(location)
67
+ end
13
68
  end
14
69
 
15
- it "should not allow you to add an invalid source" do
16
- lambda {
17
- subject.enqueue("a string, not a source")
18
- }.should raise_error(ArgumentError)
70
+ context "when the source does not have a location" do
71
+ before(:each) do
72
+ source.stub(:location).and_return(nil)
73
+ subject.stub(:locations).and_return([{type: :chef_api, value: :knife, options: Hash.new}])
74
+ end
75
+
76
+ it "sends the 'download' message to the default location" do
77
+ Location.should_receive(:init).with(source.name, source.version_constraint, chef_api: :knife).and_return(location)
78
+ location.should_receive(:download).with(subject.storage_path).and_return(cached_cookbook)
79
+ source.should_receive(:cached_cookbook=).with(cached_cookbook)
80
+
81
+ subject.download(source)
82
+ end
19
83
  end
20
84
  end
21
85
 
22
- describe "#dequeue" do
23
- before(:each) { subject.enqueue(source) }
86
+ describe "#locations" do
87
+ let(:type) { :site }
88
+ let(:value) { double('value') }
89
+ let(:options) { double('options') }
90
+
91
+ it "returns an array of Hashes representing locations" do
92
+ subject.add_location(type, value, options)
93
+
94
+ subject.locations.each { |l| l.should be_a(Hash) }
95
+ end
24
96
 
25
- it "should remove a source from the queue" do
26
- subject.dequeue(source)
97
+ context "when no locations are explicitly added" do
98
+ subject { Downloader.new(double('store')) }
27
99
 
28
- subject.queue.should be_empty
100
+ it "returns an array of default locations" do
101
+ subject.locations.should eql(Downloader::DEFAULT_LOCATIONS)
102
+ end
103
+ end
104
+
105
+ context "when locations are explicitly added" do
106
+ let(:locations) do
107
+ [
108
+ {
109
+ type: :chef_api,
110
+ value: double('capi'),
111
+ options: double('capi_opts')
112
+ },
113
+ {
114
+ type: :chef_api,
115
+ value: double('capi2'),
116
+ options: double('capi_opts2')
117
+ }
118
+ ]
119
+ end
120
+
121
+ subject { Downloader.new(double('store'), locations: locations) }
122
+
123
+ it "contains only the locations passed to the initializer" do
124
+ subject.locations.should eql(locations)
125
+ end
126
+
127
+ it "does not include the array of default locations" do
128
+ subject.locations.should_not include(Downloader::DEFAULT_LOCATIONS)
129
+ end
29
130
  end
30
131
  end
31
132
 
32
- describe "#download_all" do
33
- let(:source_one) { CookbookSource.new("nginx") }
34
- let(:source_two) { CookbookSource.new("mysql") }
133
+ describe "#add_location" do
134
+ let(:type) { :site }
135
+ let(:value) { double('value') }
136
+ let(:options) { double('options') }
137
+
138
+ it "adds a hash to the end of the array of locations" do
139
+ subject.add_location(type, value, options)
35
140
 
36
- before(:each) do
37
- subject.enqueue source_one
38
- subject.enqueue source_two
141
+ subject.locations.should have(1).item
39
142
  end
40
143
 
41
- it "should remove each item from the queue after a successful download" do
42
- subject.download_all
144
+ it "adds a hash with a type, value, and options key" do
145
+ subject.add_location(type, value, options)
43
146
 
44
- subject.queue.should be_empty
147
+ subject.locations.last.should have_key(:type)
148
+ subject.locations.last.should have_key(:value)
149
+ subject.locations.last.should have_key(:options)
45
150
  end
46
151
 
47
- it "should not remove the item from the queue if the download failed" do
48
- subject.enqueue CookbookSource.new("does_not_exist_no_way")
49
- subject.download_all
152
+ it "sets the value of the given 'value' to the value of the key 'value'" do
153
+ subject.add_location(type, value, options)
50
154
 
51
- subject.queue.should have(1).sources
155
+ subject.locations.last[:value].should eql(value)
52
156
  end
53
157
 
54
- it "should return a TXResultSet" do
55
- results = subject.download_all
158
+ it "sets the value of the given 'type' to the value of the key 'type'" do
159
+ subject.add_location(type, value, options)
56
160
 
57
- results.should be_a(TXResultSet)
161
+ subject.locations.last[:type].should eql(type)
58
162
  end
59
- end
60
163
 
61
- describe "#download" do
62
- let(:source) { CookbookSource.new("nginx") }
63
- let(:bad_source) { CookbookSource.new("donowaytexists") }
164
+ it "sets the value of the given 'options' to the value of the key 'options'" do
165
+ subject.add_location(type, value, options)
64
166
 
65
- it "returns a TXResult" do
66
- subject.download(source).should be_a(TXResult)
167
+ subject.locations.last[:options].should eql(options)
67
168
  end
68
169
 
69
- context "when successful" do
70
- it "returns a successesful TXResult" do
71
- subject.download(source).should be_success
72
- end
170
+ it "raises a DuplicateLocationDefined error if a location of the given type and value was already added" do
171
+ subject.add_location(type, value, options)
172
+
173
+ lambda {
174
+ subject.add_location(type, value, options)
175
+ }.should raise_error(DuplicateLocationDefined)
73
176
  end
74
177
 
75
- context "when failure" do
76
- it "returns a failed TXResult" do
77
- subject.download(bad_source).should be_failed
178
+ context "adding multiple locations" do
179
+ let(:type_2) { :site }
180
+ let(:value_2) { double('value_2') }
181
+ let(:options_2) { double('options_2') }
182
+
183
+ it "adds locations in the order they are added" do
184
+ subject.add_location(type, value, options)
185
+ subject.add_location(type_2, value_2, options_2)
186
+
187
+ subject.locations.should have(2).items
188
+
189
+ subject.locations[0][:value].should eql(value)
190
+ subject.locations[1][:value].should eql(value_2)
78
191
  end
79
192
  end
80
193
  end
194
+
195
+ describe "#has_location?" do
196
+ let(:type) { :site }
197
+ let(:value) { double('value') }
198
+
199
+ it "returns true if a source of the given type and value was already added" do
200
+ subject.add_location(type, value)
201
+
202
+ subject.has_location?(type, value).should be_true
203
+ end
204
+
205
+ it "returns false if a source of the given type and value was not added" do
206
+ subject.has_location?(type, value).should be_false
207
+ end
208
+ end
81
209
  end
82
210
  end
@@ -1,24 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Berkshelf
4
- module Formatters
5
- class TestFormatter
6
- include AbstractFormatter
4
+ describe Formatters::AbstractFormatter do
5
+ subject do
6
+ Class.new do
7
+ include Formatters::AbstractFormatter
8
+ end.new
7
9
  end
8
10
 
9
- describe AbstractFormatter do
10
-
11
- subject { TestFormatter.new }
12
-
13
- it "has abstract methods for all the messaging modes" do
14
- lambda { subject.install("my_coobook","1.2.3","http://community") }.should raise_error(MethodNotImplmentedError)
15
- lambda { subject.use("my_coobook","1.2.3") }.should raise_error(MethodNotImplmentedError)
16
- lambda { subject.use("my_coobook","1.2.3","http://community") }.should raise_error(MethodNotImplmentedError)
17
- lambda { subject.upload("my_coobook","1.2.3","http://chef_server") }.should raise_error(MethodNotImplmentedError)
18
- lambda { subject.shims_written("/Users/jcocktosten") }.should raise_error(MethodNotImplmentedError)
19
- lambda { subject.msg("something you should know") }.should raise_error(MethodNotImplmentedError)
20
- lambda { subject.error("whoa this is bad") }.should raise_error(MethodNotImplmentedError)
21
- end
11
+ it "has abstract methods for all the messaging modes" do
12
+ lambda { subject.install("my_coobook","1.2.3","http://community") }.should raise_error(AbstractFunction)
13
+ lambda { subject.use("my_coobook","1.2.3") }.should raise_error(AbstractFunction)
14
+ lambda { subject.use("my_coobook","1.2.3","http://community") }.should raise_error(AbstractFunction)
15
+ lambda { subject.upload("my_coobook","1.2.3","http://chef_server") }.should raise_error(AbstractFunction)
16
+ lambda { subject.shims_written("/Users/jcocktosten") }.should raise_error(AbstractFunction)
17
+ lambda { subject.msg("something you should know") }.should raise_error(AbstractFunction)
18
+ lambda { subject.error("whoa this is bad") }.should raise_error(AbstractFunction)
22
19
  end
23
20
  end
24
21
  end
@@ -1,9 +1,9 @@
1
1
  module Berkshelf
2
- describe CookbookSource::Location do
2
+ describe Location do
3
3
  describe "ClassMethods Module" do
4
4
  subject do
5
5
  Class.new do
6
- include CookbookSource::Location
6
+ include Location
7
7
  end
8
8
  end
9
9
 
@@ -88,7 +88,7 @@ module Berkshelf
88
88
  end
89
89
 
90
90
  describe "ModuleFunctions" do
91
- subject { CookbookSource::Location }
91
+ subject { Location }
92
92
 
93
93
  describe "::init" do
94
94
  let(:name) { "artifact" }
@@ -97,25 +97,25 @@ module Berkshelf
97
97
  it "returns an instance of SiteLocation given a site: option key" do
98
98
  result = subject.init(name, constraint, site: "http://site/value")
99
99
 
100
- result.should be_a(CookbookSource::SiteLocation)
100
+ result.should be_a(SiteLocation)
101
101
  end
102
102
 
103
103
  it "returns an instance of PathLocation given a path: option key" do
104
104
  result = subject.init(name, constraint, path: "/Users/reset/code")
105
105
 
106
- result.should be_a(CookbookSource::PathLocation)
106
+ result.should be_a(PathLocation)
107
107
  end
108
108
 
109
109
  it "returns an instance of GitLocation given a git: option key" do
110
110
  result = subject.init(name, constraint, git: "git://github.com/something.git")
111
111
 
112
- result.should be_a(CookbookSource::GitLocation)
112
+ result.should be_a(GitLocation)
113
113
  end
114
114
 
115
115
  it "returns an instance of SiteLocation when no option key is given that matches a registered location_key" do
116
116
  result = subject.init(name, constraint)
117
117
 
118
- result.should be_a(CookbookSource::SiteLocation)
118
+ result.should be_a(SiteLocation)
119
119
  end
120
120
 
121
121
  context "given two location_keys" do
@@ -133,7 +133,7 @@ module Berkshelf
133
133
 
134
134
  subject do
135
135
  Class.new do
136
- include CookbookSource::Location
136
+ include Location
137
137
  end.new(name, constraint)
138
138
  end
139
139
 
@@ -142,10 +142,10 @@ module Berkshelf
142
142
  end
143
143
 
144
144
  describe "#download" do
145
- it "raises a NotImplementedError if not overridden" do
145
+ it "raises a AbstractFunction if not defined" do
146
146
  lambda {
147
147
  subject.download(double('destination'))
148
- }.should raise_error(NotImplementedError)
148
+ }.should raise_error(AbstractFunction)
149
149
  end
150
150
  end
151
151
 
@@ -1,11 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Berkshelf
4
- describe CookbookSource::ChefAPILocation do
4
+ describe ChefAPILocation do
5
5
  let(:test_chef_api) { "https://chefserver:8081" }
6
6
 
7
7
  describe "ClassMethods" do
8
- subject { CookbookSource::ChefAPILocation }
8
+ subject { ChefAPILocation }
9
9
  let(:valid_uri) { test_chef_api }
10
10
  let(:invalid_uri) { "notauri" }
11
11
  let(:constraint) { double('constraint') }
@@ -110,7 +110,7 @@ module Berkshelf
110
110
  end
111
111
 
112
112
  subject do
113
- loc = CookbookSource::ChefAPILocation.new("nginx",
113
+ loc = ChefAPILocation.new("nginx",
114
114
  double('constraint', satisfies?: true),
115
115
  chef_api: :knife
116
116
  )
@@ -229,7 +229,7 @@ module Berkshelf
229
229
 
230
230
  describe "#to_s" do
231
231
  subject do
232
- CookbookSource::ChefAPILocation.new('nginx',
232
+ ChefAPILocation.new('nginx',
233
233
  double('constraint'),
234
234
  chef_api: :knife
235
235
  )
@@ -1,11 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Berkshelf
4
- describe CookbookSource::GitLocation do
4
+ describe GitLocation do
5
5
  let(:complacent_constraint) { double('comp-vconstraint', satisfies?: true) }
6
6
 
7
7
  describe "ClassMethods" do
8
- subject { CookbookSource::GitLocation }
8
+ subject { GitLocation }
9
9
 
10
10
  describe "::initialize" do
11
11
  it "raises InvalidGitURI if given an invalid Git URI for options[:git]" do
@@ -16,7 +16,7 @@ module Berkshelf
16
16
  end
17
17
  end
18
18
 
19
- subject { CookbookSource::GitLocation.new("artifact", complacent_constraint, git: "git://github.com/RiotGames/artifact-cookbook.git") }
19
+ subject { GitLocation.new("artifact", complacent_constraint, git: "git://github.com/RiotGames/artifact-cookbook.git") }
20
20
 
21
21
  describe "#download" do
22
22
  it "returns an instance of Berkshelf::CachedCookbook" do
@@ -40,7 +40,7 @@ module Berkshelf
40
40
  end
41
41
 
42
42
  context "given no ref/branch/tag options is given" do
43
- subject { CookbookSource::GitLocation.new("nginx", complacent_constraint, git: "git://github.com/opscode-cookbooks/nginx.git") }
43
+ subject { GitLocation.new("nginx", complacent_constraint, git: "git://github.com/opscode-cookbooks/nginx.git") }
44
44
 
45
45
  it "sets the branch attribute to the HEAD revision of the cloned repo" do
46
46
  subject.download(tmp_path)
@@ -50,7 +50,7 @@ module Berkshelf
50
50
  end
51
51
 
52
52
  context "given a git repo that does not exist" do
53
- subject { CookbookSource::GitLocation.new("doesnot_exist", complacent_constraint, git: "git://github.com/RiotGames/thisrepo_does_not_exist.git") }
53
+ subject { GitLocation.new("doesnot_exist", complacent_constraint, git: "git://github.com/RiotGames/thisrepo_does_not_exist.git") }
54
54
 
55
55
  it "raises a GitError" do
56
56
  lambda {
@@ -60,7 +60,7 @@ module Berkshelf
60
60
  end
61
61
 
62
62
  context "given a git repo that does not contain a cookbook" do
63
- subject { CookbookSource::GitLocation.new("doesnot_exist", complacent_constraint, git: "git://github.com/RiotGames/berkshelf.git") }
63
+ subject { GitLocation.new("doesnot_exist", complacent_constraint, git: "git://github.com/RiotGames/berkshelf.git") }
64
64
 
65
65
  it "raises a CookbookNotFound error" do
66
66
  lambda {
@@ -71,7 +71,7 @@ module Berkshelf
71
71
 
72
72
  context "given the content at the Git repo does not satisfy the version constraint" do
73
73
  subject do
74
- CookbookSource::GitLocation.new("nginx",
74
+ GitLocation.new("nginx",
75
75
  double('constraint', satisfies?: false),
76
76
  git: "git://github.com/opscode-cookbooks/nginx.git"
77
77
  )
@@ -86,7 +86,7 @@ module Berkshelf
86
86
 
87
87
  context "given a value for ref that is a tag or branch and not a commit hash" do
88
88
  subject do
89
- CookbookSource::GitLocation.new("artifact",
89
+ GitLocation.new("artifact",
90
90
  complacent_constraint,
91
91
  git: "git://github.com/RiotGames/artifact-cookbook.git",
92
92
  ref: "0.9.8"