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
@@ -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