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
@@ -28,9 +28,9 @@ module Berkshelf
28
28
  def get_source_definition(source)
29
29
  definition = "cookbook '#{source.name}'"
30
30
 
31
- if source.location.is_a?(CookbookSource::GitLocation)
31
+ if source.location.is_a?(GitLocation)
32
32
  definition += ", :git => '#{source.location.uri}', :ref => '#{source.location.branch || 'HEAD'}'"
33
- elsif source.location.is_a?(CookbookSource::PathLocation)
33
+ elsif source.location.is_a?(PathLocation)
34
34
  definition += ", :path => '#{source.location.path}'"
35
35
  else
36
36
  definition += ", :locked_version => '#{source.locked_version}'"
@@ -6,8 +6,10 @@ module Berkshelf
6
6
  attr_reader :graph
7
7
 
8
8
  # @param [Downloader] downloader
9
- # @param [Array<CookbookSource>, CookbookSource] sources
10
- def initialize(downloader, sources = Array.new)
9
+ # @param [Hash] options
10
+ #
11
+ # @option options [Array<CookbookSource>, CookbookSource] sources
12
+ def initialize(downloader, options = {})
11
13
  @downloader = downloader
12
14
  @graph = Solve::Graph.new
13
15
  @sources = Hash.new
@@ -16,11 +18,11 @@ module Berkshelf
16
18
  # not, then one of the dependencies of a source that is added
17
19
  # may take precedence over an explicitly set source that appears
18
20
  # later in the iterator.
19
- Array(sources).each do |source|
21
+ Array(options[:sources]).each do |source|
20
22
  add_source(source, false)
21
23
  end
22
24
 
23
- Array(sources).each do |source|
25
+ Array(options[:sources]).each do |source|
24
26
  add_source_dependencies(source)
25
27
  end
26
28
  end
@@ -41,7 +43,7 @@ module Berkshelf
41
43
  raise DuplicateSourceDefined, "A source named '#{source.name}' is already present."
42
44
  end
43
45
 
44
- set_source(source)
46
+ @sources[source.name] = source
45
47
  use_source(source) || install_source(source)
46
48
 
47
49
  graph.artifacts(source.name, source.cached_cookbook.version)
@@ -66,7 +68,7 @@ module Berkshelf
66
68
  source.cached_cookbook.dependencies.each do |name, constraint|
67
69
  next if has_source?(name)
68
70
 
69
- add_source(CookbookSource.new(name, constraint))
71
+ add_source(CookbookSource.new(name, constraint: constraint))
70
72
  end
71
73
  end
72
74
 
@@ -116,17 +118,12 @@ module Berkshelf
116
118
 
117
119
  attr_reader :downloader
118
120
 
119
- # @param [CookbookSource] source
120
- def set_source(source)
121
- @sources[source.name] = source
122
- end
123
-
124
121
  # @param [Berkshelf::CookbookSource] source
125
122
  #
126
123
  # @return [Boolean]
127
124
  def install_source(source)
128
- downloader.download!(source)
129
- Berkshelf.formatter.install source.name, source.cached_cookbook.version, source.location
125
+ cached_cookbook, location = downloader.download(source)
126
+ Berkshelf.formatter.install source.name, cached_cookbook.version, location
130
127
  end
131
128
 
132
129
  # Use the given source to create a constraint solution if the source has been downloaded or can
@@ -148,7 +145,7 @@ module Berkshelf
148
145
  cached = source.cached_cookbook
149
146
  source.location.validate_cached(cached)
150
147
  else
151
- if source.location.is_a?(CookbookSource::GitLocation)
148
+ if source.location.is_a?(GitLocation)
152
149
  return false
153
150
  end
154
151
 
@@ -158,7 +155,7 @@ module Berkshelf
158
155
  get_source(source).cached_cookbook = cached
159
156
  end
160
157
 
161
- path = source.location.is_a?(CookbookSource::PathLocation) ? "#{source.location}" : nil
158
+ path = source.location.is_a?(PathLocation) ? "#{source.location}" : nil
162
159
  Berkshelf.formatter.use cached.cookbook_name, cached.version, path
163
160
 
164
161
  true
@@ -41,15 +41,8 @@ module Berkshelf
41
41
  # Freeze the uploaded Cookbook on the Chef Server so that it cannot be
42
42
  # overwritten
43
43
  #
44
- # @return [TXResult]
44
+ # @return [Boolean]
45
45
  def upload(cookbook, options = {})
46
- upload!(cookbook, options)
47
- rescue BerkshelfError => e
48
- TXResult.new(:error, e.message)
49
- end
50
-
51
- # @see #upload
52
- def upload!(cookbook, options = {})
53
46
  cookbook.validate!
54
47
 
55
48
  checksums = cookbook.checksums.dup
@@ -58,7 +51,7 @@ module Berkshelf
58
51
  commit_sandbox(new_sandbox)
59
52
  save_cookbook(cookbook, options)
60
53
 
61
- TXResult.new(:ok, "#{cookbook.cookbook_name} (#{cookbook.version}) uploaded to: '#{server_url}'")
54
+ true
62
55
  end
63
56
 
64
57
  private
@@ -1,3 +1,3 @@
1
1
  module Berkshelf
2
- VERSION = "0.4.0.rc4"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -14,7 +14,7 @@ module Berkshelf
14
14
 
15
15
  def upload_cookbook(path)
16
16
  cached = CachedCookbook.from_store_path(path)
17
- uploader.upload!(cached)
17
+ uploader.upload(cached)
18
18
  end
19
19
 
20
20
  # Remove all versions of all cookbooks from the Chef Server defined in your
@@ -83,6 +83,12 @@ EOF
83
83
  end
84
84
  end
85
85
 
86
+ if options[:recommendations]
87
+ options[:recommendations].each do |name, constraint|
88
+ metadata << "recommends '#{name}', '#{constraint}'\n"
89
+ end
90
+ end
91
+
86
92
  File.write(cookbook_path.join("metadata.rb"), metadata)
87
93
  cookbook_path
88
94
  end
@@ -62,20 +62,125 @@ EOF
62
62
  let(:source_one) { double('source_one', name: "nginx") }
63
63
  let(:source_two) { double('source_two', name: "mysql") }
64
64
 
65
- subject do
66
- cbf = Berksfile.new(tmp_path.join("Berksfile"))
67
- cbf.add_source(source_one)
68
- cbf.add_source(source_two)
69
- cbf
65
+ subject { Berksfile.new(tmp_path.join("Berksfile")) }
66
+
67
+ describe "#cookbook" do
68
+ let(:name) { "artifact" }
69
+ let(:constraint) { double('constraint') }
70
+ let(:default_options) { { group: [] } }
71
+
72
+ it "sends the add_source message with the name, constraint, and options to the instance of the includer" do
73
+ subject.should_receive(:add_source).with(name, constraint, default_options)
74
+
75
+ subject.cookbook name, constraint, default_options
76
+ end
77
+
78
+ it "merges the default options into specified options" do
79
+ subject.should_receive(:add_source).with(name, constraint, path: "/Users/reset", group: [])
80
+
81
+ subject.cookbook name, constraint, path: "/Users/reset"
82
+ end
83
+
84
+ it "converts a single specified group option into an array of groups" do
85
+ subject.should_receive(:add_source).with(name, constraint, group: [:production])
86
+
87
+ subject.cookbook name, constraint, group: :production
88
+ end
89
+
90
+ context "when no constraint specified" do
91
+ it "sends the add_source message with a nil value for constraint" do
92
+ subject.should_receive(:add_source).with(name, nil, default_options)
93
+
94
+ subject.cookbook name, default_options
95
+ end
96
+ end
97
+
98
+ context "when no options specified" do
99
+ it "sends the add_source message with an empty Hash for the value of options" do
100
+ subject.should_receive(:add_source).with(name, constraint, default_options)
101
+
102
+ subject.cookbook name, constraint
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#group' do
108
+ let(:name) { "artifact" }
109
+ let(:group) { "production" }
110
+
111
+ it "sends the add_source message with an array of groups determined by the parameter passed to the group block" do
112
+ subject.should_receive(:add_source).with(name, nil, group: [group])
113
+
114
+ subject.group group do
115
+ subject.cookbook name
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "#metadata" do
121
+ let(:cb_path) { fixtures_path.join('cookbooks/example_cookbook') }
122
+ subject { Berksfile.new(cb_path.join("Berksfile")) }
123
+
124
+ before(:each) { Dir.chdir(cb_path) }
125
+
126
+ it "sends the add_source message with an explicit version constraint and the path to the cookbook" do
127
+ subject.should_receive(:add_source).with("example_cookbook", "= 0.5.0", path: cb_path.to_s)
128
+
129
+ subject.metadata
130
+ end
131
+ end
132
+
133
+ describe "#site" do
134
+ let(:uri) { "http://opscode/v1" }
135
+
136
+ it "sends the add_location to the instance of the implementing class with a SiteLocation" do
137
+ subject.should_receive(:add_location).with(:site, uri)
138
+
139
+ subject.site(uri)
140
+ end
141
+
142
+ context "given the symbol :opscode" do
143
+ it "sends an add_location message with the default Opscode Community API as the first parameter" do
144
+ subject.should_receive(:add_location).with(:site, :opscode)
145
+
146
+ subject.site(:opscode)
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "#chef_api" do
152
+ let(:uri) { "http://chef:8080/" }
153
+
154
+ it "sends and add_location message with the type :chef_api and the given URI" do
155
+ subject.should_receive(:add_location).with(:chef_api, uri, {})
156
+
157
+ subject.chef_api(uri)
158
+ end
159
+
160
+ it "also sends any options passed" do
161
+ options = { node_name: "reset", client_key: "/Users/reset/.chef/reset.pem" }
162
+ subject.should_receive(:add_location).with(:chef_api, uri, options)
163
+
164
+ subject.chef_api(uri, options)
165
+ end
166
+
167
+ context "given the symbol :knife" do
168
+ it "sends an add_location message with the the type :chef_api and the URI :knife" do
169
+ subject.should_receive(:add_location).with(:chef_api, :knife, {})
170
+
171
+ subject.chef_api(:knife)
172
+ end
173
+ end
70
174
  end
71
175
 
72
176
  describe "#sources" do
73
177
  it "returns all CookbookSources added to the instance of Berksfile" do
74
- result = subject.sources
178
+ subject.add_source(source_one.name)
179
+ subject.add_source(source_two.name)
75
180
 
76
- result.should have(2).items
77
- result.should include(source_one)
78
- result.should include(source_two)
181
+ subject.sources.should have(2).items
182
+ subject.should have_source(source_one.name)
183
+ subject.should have_source(source_two.name)
79
184
  end
80
185
 
81
186
  context "given the option :exclude" do
@@ -89,6 +194,7 @@ EOF
89
194
 
90
195
  describe "#groups" do
91
196
  before(:each) do
197
+ subject.stub(:sources) { [source_one, source_two] }
92
198
  source_one.stub(:groups) { [:nautilus, :skarner] }
93
199
  source_two.stub(:groups) { [:nautilus, :riven] }
94
200
  end
@@ -101,9 +207,8 @@ EOF
101
207
  end
102
208
 
103
209
  it "returns an Array of CookbookSources who are members of the group for value" do
104
- subject.groups[:nautilus].should include(source_one)
105
- subject.groups[:nautilus].should include(source_two)
106
- subject.groups[:riven].should_not include(source_one)
210
+ subject.groups[:nautilus].should have(2).items
211
+ subject.groups[:riven].should have(1).item
107
212
  end
108
213
  end
109
214
 
@@ -205,5 +310,45 @@ EOF
205
310
  subject.load(content).should be_a(Berksfile)
206
311
  end
207
312
  end
313
+
314
+ describe "#add_source" do
315
+ let(:name) { "cookbook_one" }
316
+ let(:constraint) { "= 1.2.0" }
317
+ let(:location) { { site: "http://site" } }
318
+
319
+ before(:each) do
320
+ subject.add_source(name, constraint, location)
321
+ end
322
+
323
+ it "adds new cookbook source to the list of sources" do
324
+ subject.sources.should have(1).source
325
+ end
326
+
327
+ it "adds a cookbook source with a 'name' of the given name" do
328
+ subject.sources.first.name.should eql(name)
329
+ end
330
+
331
+ it "adds a cookbook source with a 'version_constraint' of the given constraint" do
332
+ subject.sources.first.version_constraint.to_s.should eql(constraint)
333
+ end
334
+
335
+ it "raises DuplicateSourceDefined if multiple sources of the same name are found" do
336
+ lambda {
337
+ subject.add_source(name)
338
+ }.should raise_error(DuplicateSourceDefined)
339
+ end
340
+ end
341
+
342
+ describe "#add_location" do
343
+ let(:type) { :site }
344
+ let(:value) { double('value') }
345
+ let(:options) { double('options') }
346
+
347
+ it "delegates 'add_location' to the downloader" do
348
+ subject.downloader.should_receive(:add_location).with(type, value, options)
349
+
350
+ subject.add_location(type, value, options)
351
+ end
352
+ end
208
353
  end
209
354
  end
@@ -460,5 +460,24 @@ module Berkshelf
460
460
  parse_json(@json)['json_class'].should eql("Chef::CookbookVersion")
461
461
  end
462
462
  end
463
+
464
+ describe "#dependencies" do
465
+ let(:dependencies) { { "mysql" => "= 1.2.0", "ntp" => ">= 0.0.0" } }
466
+ let(:recommendations) { { "database" => ">= 0.0.0" } }
467
+
468
+ let(:cb_path) do
469
+ generate_cookbook(Berkshelf.cookbook_store.to_s, "sparkle", "0.1.0", dependencies: dependencies, recommendations: recommendations)
470
+ end
471
+
472
+ subject { CachedCookbook.from_store_path(cb_path) }
473
+
474
+ it "contains depends from the cookbook metadata" do
475
+ subject.dependencies.should include(dependencies)
476
+ end
477
+
478
+ it "contains recommendations from the cookbook metadata" do
479
+ subject.dependencies.should include(recommendations)
480
+ end
481
+ end
463
482
  end
464
483
  end
@@ -50,6 +50,7 @@ module Berkshelf
50
50
  contains "description \"Installs/Configures sparkle_motion\""
51
51
  end
52
52
  file "Berksfile" do
53
+ contains "site :opscode"
53
54
  contains "metadata"
54
55
  end
55
56
  file "Gemfile"
@@ -11,8 +11,8 @@ module Berkshelf
11
11
  context "given no location key (i.e. :git, :path, :site)" do
12
12
  let(:source) { subject.new(cookbook_name) }
13
13
 
14
- it "uses a default SiteLocation pointing to the opscode community api" do
15
- source.location.api_uri.should eql(subject::SiteLocation::OPSCODE_COMMUNITY_API)
14
+ it "sets a nil value for location" do
15
+ source.location.should be_nil
16
16
  end
17
17
  end
18
18
 
@@ -25,7 +25,7 @@ module Berkshelf
25
25
  end
26
26
 
27
27
  context "given a value for constraint" do
28
- let(:source) { subject.new(cookbook_name, "~> 1.0.84") }
28
+ let(:source) { subject.new(cookbook_name, constraint: "~> 1.0.84") }
29
29
 
30
30
  it "returns a Solve::Constraint for the given version for version_constraint" do
31
31
  source.version_constraint.to_s.should eql("~> 1.0.84")
@@ -37,7 +37,7 @@ module Berkshelf
37
37
  let(:source) { subject.new(cookbook_name, git: url) }
38
38
 
39
39
  it "initializes a GitLocation for location" do
40
- source.location.should be_a(subject::GitLocation)
40
+ source.location.should be_a(GitLocation)
41
41
  end
42
42
 
43
43
  it "points to the given Git URL" do
@@ -50,7 +50,7 @@ module Berkshelf
50
50
  let(:path) { fixtures_path.join("cookbooks", "example_cookbook").to_s }
51
51
 
52
52
  it "initializes a PathLocation for location" do
53
- subject.new(cookbook_name, path: path).location.should be_a(subject::PathLocation)
53
+ subject.new(cookbook_name, path: path).location.should be_a(PathLocation)
54
54
  end
55
55
 
56
56
  it "points to the specified path" do
@@ -141,7 +141,7 @@ module Berkshelf
141
141
  let(:source) { subject.new(cookbook_name, site: url) }
142
142
 
143
143
  it "initializes a SiteLocation for location" do
144
- source.location.should be_a(subject::SiteLocation)
144
+ source.location.should be_a(SiteLocation)
145
145
  end
146
146
 
147
147
  it "points to the specified URI" do
@@ -211,43 +211,33 @@ module Berkshelf
211
211
  end
212
212
  end
213
213
 
214
- describe "#download" do
215
- context "when download is successful" do
216
- it "sets a CachedCookbook to the cached_cookbook attr" do
217
- subject.download(tmp_path)
218
-
219
- subject.cached_cookbook.should be_a(Berkshelf::CachedCookbook)
220
- end
221
-
222
- it "returns an array containing the symbol :ok and the local_path" do
223
- result = subject.download(tmp_path)
214
+ describe "#downloaded?" do
215
+ it "returns true if self.cached_cookbook is not nil" do
216
+ subject.stub(:cached_cookbook) { double('cb') }
224
217
 
225
- result.should be_a(Array)
226
- result[0].should eql(:ok)
227
- result[1].should eql(subject.cached_cookbook)
228
- end
218
+ subject.downloaded?.should be_true
229
219
  end
230
220
 
231
- context "when the download fails" do
232
- let(:bad_cb_name) { "NOWAYTHISEXISTS" }
233
- subject { CookbookSource.new(bad_cb_name) }
234
-
235
- it "returns an array containing the symbol :error and the error message" do
236
- result = subject.download(tmp_path)
221
+ it "returns false if self.cached_cookbook is nil" do
222
+ subject.stub(:cached_cookbook) { nil }
237
223
 
238
- result.should be_a(Array)
239
- result[0].should eql(:error)
240
- result[1].should eql("Cookbook '#{bad_cb_name}' not found at site: 'http://cookbooks.opscode.com/api/v1/cookbooks'")
241
- end
224
+ subject.downloaded?.should be_false
242
225
  end
243
226
  end
244
227
 
245
- describe "#downloaded?" do
246
- subject{ CookbookSource.new("nginx", ">= 1.0.1") }
228
+ describe "#to_s" do
229
+ it "contains the name, constraint, and groups" do
230
+ source = CookbookSource.new("artifact", constraint: "= 0.10.0")
231
+
232
+ source.to_s.should eql("artifact (= 0.10.0) groups: [:default]")
233
+ end
247
234
 
248
- it "delegates the message ':downloaded?' to the location" do
249
- subject.location.should_receive(:downloaded?)
250
- subject.downloaded?
235
+ context "given a CookbookSource with an explicit location" do
236
+ it "contains the name, constraint, groups, and location" do
237
+ source = CookbookSource.new("artifact", constraint: "= 0.10.0", site: "http://cookbooks.opscode.com/api/v1/cookbooks")
238
+
239
+ source.to_s.should eql("artifact (= 0.10.0) groups: [:default] location: site: 'http://cookbooks.opscode.com/api/v1/cookbooks'")
240
+ end
251
241
  end
252
242
  end
253
243
  end