berkshelf 0.4.0.rc4 → 0.4.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.
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"