berkshelf 0.4.0 → 0.5.0.rc1

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 (51) hide show
  1. data/berkshelf.gemspec +8 -3
  2. data/bin/berks +1 -1
  3. data/features/groups_install.feature +67 -0
  4. data/features/install.feature +1 -71
  5. data/features/json_formatter.feature +0 -17
  6. data/features/step_definitions/filesystem_steps.rb +1 -3
  7. data/features/update.feature +3 -6
  8. data/features/vendor_install.feature +20 -0
  9. data/generator_files/Vagrantfile.erb +25 -3
  10. data/lib/berkshelf.rb +27 -17
  11. data/lib/berkshelf/base_generator.rb +5 -0
  12. data/lib/berkshelf/berksfile.rb +82 -77
  13. data/lib/berkshelf/cli.rb +37 -26
  14. data/lib/berkshelf/cookbook_source.rb +14 -4
  15. data/lib/berkshelf/errors.rb +4 -1
  16. data/lib/berkshelf/formatters.rb +69 -5
  17. data/lib/berkshelf/formatters/human_readable.rb +26 -8
  18. data/lib/berkshelf/formatters/json.rb +51 -23
  19. data/lib/berkshelf/init_generator.rb +4 -4
  20. data/lib/berkshelf/location.rb +29 -6
  21. data/lib/berkshelf/locations/chef_api_location.rb +23 -5
  22. data/lib/berkshelf/locations/git_location.rb +13 -6
  23. data/lib/berkshelf/locations/path_location.rb +8 -4
  24. data/lib/berkshelf/locations/site_location.rb +9 -3
  25. data/lib/berkshelf/ui.rb +34 -0
  26. data/lib/berkshelf/uploader.rb +37 -103
  27. data/lib/berkshelf/vagrant.rb +65 -0
  28. data/lib/berkshelf/vagrant/action/clean.rb +24 -0
  29. data/lib/berkshelf/vagrant/action/install.rb +47 -0
  30. data/lib/berkshelf/vagrant/action/set_ui.rb +17 -0
  31. data/lib/berkshelf/vagrant/action/upload.rb +40 -0
  32. data/lib/berkshelf/vagrant/config.rb +70 -0
  33. data/lib/berkshelf/vagrant/middleware.rb +52 -0
  34. data/lib/berkshelf/version.rb +1 -1
  35. data/lib/thor/monkies.rb +3 -0
  36. data/lib/thor/monkies/hash_with_indifferent_access.rb +13 -0
  37. data/lib/vagrant_init.rb +2 -0
  38. data/spec/spec_helper.rb +5 -0
  39. data/spec/support/chef_api.rb +6 -1
  40. data/spec/unit/berkshelf/berksfile_spec.rb +93 -55
  41. data/spec/unit/berkshelf/formatters_spec.rb +99 -1
  42. data/spec/unit/berkshelf/init_generator_spec.rb +1 -1
  43. data/spec/unit/berkshelf/location_spec.rb +44 -7
  44. data/spec/unit/berkshelf/lockfile_spec.rb +2 -2
  45. data/spec/unit/berkshelf/uploader_spec.rb +2 -20
  46. data/spec/unit/berkshelf_spec.rb +2 -2
  47. metadata +100 -14
  48. data/features/without.feature +0 -26
  49. data/lib/berkshelf/core_ext/fileutils.rb +0 -90
  50. data/lib/berkshelf/core_ext/kernel.rb +0 -33
  51. data/spec/unit/berkshelf/core_ext/fileutils_spec.rb +0 -20
@@ -0,0 +1,24 @@
1
+ module Berkshelf
2
+ module Vagrant
3
+ module Action
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class Clean
6
+ attr_reader :shelf
7
+
8
+ def initialize(app, env)
9
+ @app = app
10
+ @shelf = Berkshelf::Vagrant.shelf_for(env)
11
+ end
12
+
13
+ def call(env)
14
+ if Berkshelf::Vagrant.chef_solo?(env[:global_config])
15
+ Berkshelf.formatter.msg "cleaning Vagrant's shelf"
16
+ FileUtils.remove_dir(self.shelf, fore: true)
17
+ end
18
+
19
+ @app.call(env)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ module Berkshelf
2
+ module Vagrant
3
+ module Action
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ # @author Andrew Garson <andrew.garson@gmail.com>
6
+ class Install
7
+ attr_reader :config
8
+ attr_reader :shelf
9
+ attr_reader :berksfile
10
+
11
+ def initialize(app, env)
12
+ @app = app
13
+ @shelf = Berkshelf::Vagrant.shelf_for(env)
14
+ @config = env[:global_config].berkshelf
15
+ Berkshelf.config_path = @config.config_path
16
+ Berkshelf.load_config
17
+ @berksfile = Berksfile.from_file(@config.berksfile_path)
18
+ end
19
+
20
+ def call(env)
21
+ if Berkshelf::Vagrant.chef_solo?(env[:global_config])
22
+ configure_cookbooks_path(env)
23
+ install(env)
24
+ end
25
+
26
+ @app.call(env)
27
+ end
28
+
29
+ private
30
+
31
+ def install(env)
32
+ Berkshelf.formatter.msg "installing cookbooks..."
33
+ opts = {
34
+ path: self.shelf
35
+ }.merge(self.config.to_hash).symbolize_keys!
36
+ berksfile.install(opts)
37
+ end
38
+
39
+ def configure_cookbooks_path(env)
40
+ Berkshelf::Vagrant.provisioners(:chef_solo, env[:global_config]).each do |provisioner|
41
+ provisioner.config.cookbooks_path.unshift(self.shelf)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ module Berkshelf
2
+ module Vagrant
3
+ module Action
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class SetUI
6
+ def initialize(app, env)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ Berkshelf.ui = ::Vagrant::UI::Colored.new("Berkshelf")
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ module Berkshelf
2
+ module Vagrant
3
+ module Action
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class Upload
6
+ attr_reader :berksfile
7
+ attr_reader :node_name
8
+ attr_reader :client_key
9
+
10
+ def initialize(app, env)
11
+ @app = app
12
+ @node_name = env[:global_config].berkshelf.node_name
13
+ @client_key = env[:global_config].berkshelf.client_key
14
+ @berksfile = Berksfile.from_file(env[:global_config].berkshelf.berksfile_path)
15
+ end
16
+
17
+ def call(env)
18
+ if Berkshelf::Vagrant.chef_client?(env[:global_config])
19
+ upload(env)
20
+ end
21
+
22
+ @app.call(env)
23
+ end
24
+
25
+ private
26
+
27
+ def upload(env)
28
+ Berkshelf::Vagrant.provisioners(:chef_client, env[:global_config]).each do |provisioner|
29
+ Berkshelf.formatter.msg "uploading cookbooks to '#{provisioner.config.chef_server_url}'"
30
+ berksfile.upload(
31
+ provisioner.config.chef_server_url,
32
+ node_name: self.node_name,
33
+ client_key: self.client_key
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,70 @@
1
+ module Berkshelf
2
+ module Vagrant
3
+ # @author Jamie Winsor <jamie@vialstudios.com>
4
+ # @author Andrew Garson <andrew.garson@gmail.com>
5
+ class Config < ::Vagrant::Config::Base
6
+ # return [String]
7
+ # path to a knife configuration file
8
+ attr_reader :config_path
9
+
10
+ # @return [String]
11
+ # path to the Berksfile to use with Vagrant
12
+ attr_reader :berksfile_path
13
+
14
+ # @return [String]
15
+ # A path to a client key on disk to use with the Chef Client provisioner to
16
+ # upload cookbooks installed by Berkshelf.
17
+ attr_reader :client_key
18
+
19
+ # @return [String]
20
+ # A client name (node_name) to use with the Chef Client provisioner to upload
21
+ # cookbooks installed by Berkshelf.
22
+ attr_accessor :node_name
23
+
24
+ # @return [Array<Symbol>]
25
+ # cookbooks in all other groups except for these will be installed
26
+ # and copied to Vagrant's shelf
27
+ attr_accessor :except
28
+
29
+ # @return [Array<Symbol>]
30
+ # only cookbooks in these groups will be installed and copied to
31
+ # Vagrant's shelf
32
+ attr_accessor :only
33
+
34
+ def initialize
35
+ @config_path = Berkshelf::DEFAULT_CONFIG
36
+ @berksfile_path = File.join(Dir.pwd, Berkshelf::DEFAULT_FILENAME)
37
+ @only = Array.new
38
+ @except = Array.new
39
+ end
40
+
41
+ def config_path=(value)
42
+ @config_path = File.expand_path(value)
43
+ end
44
+
45
+ def berksfile_path=(value)
46
+ @berksfile_path = File.expand_path(value)
47
+ end
48
+
49
+ def client_key=(value)
50
+ @client_key = File.expand_path(value)
51
+ end
52
+
53
+ def validate(env, errors)
54
+ if !except.empty? && !only.empty?
55
+ errors.add("A value for berkshelf.empty and berkshelf.only cannot both be defined.")
56
+ end
57
+
58
+ if Berkshelf::Vagrant.chef_client?(env.config.global)
59
+ if node_name.nil?
60
+ errors.add("A value for berkshelf.node_name is required when using the chef_client provisioner.")
61
+ end
62
+
63
+ if client_key.nil?
64
+ errors.add("A value for berkshelf.client_key is required when using the chef_client provisioner.")
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,52 @@
1
+ module Berkshelf
2
+ module Vagrant
3
+ # @author Jamie Winsor <jamie@vialstudios.com>
4
+ #
5
+ # Middleware stacks for use with Vagrant
6
+ module Middleware
7
+ class << self
8
+ # Return the Berkshelf install middleware stack. When placed in the action chain
9
+ # this stack will find retrieve and resolve the cookbook dependencies describe
10
+ # in your configured Berksfile.
11
+ #
12
+ # Cookbooks will installed into a temporary directory, called a Shelf, and mounted
13
+ # into the VM. This mounted path will be appended to the chef_solo.cookbooks_path value.
14
+ #
15
+ # @return [::Vagrant::Action::Builder]
16
+ def install
17
+ @install ||= ::Vagrant::Action::Builder.new do
18
+ use Berkshelf::Vagrant::Action::SetUI
19
+ use Berkshelf::Vagrant::Action::Install
20
+ end
21
+ end
22
+
23
+ # Return the Berkshelf upload middleware stack. When placed in the action chain
24
+ # this stack will upload cookbooks to a Chef Server if the Chef-Client provisioner
25
+ # is used. The Chef Server where the cookbooks will be uploaded to is the same Chef
26
+ # Server used in the Chef-Client provisioner.
27
+ #
28
+ # Nothing will be done if the Chef-Solo provisioner is used.
29
+ #
30
+ # @return [::Vagrant::Action::Builder]
31
+ def upload
32
+ @upload ||= ::Vagrant::Action::Builder.new do
33
+ use Berkshelf::Vagrant::Action::SetUI
34
+ use Berkshelf::Vagrant::Action::Upload
35
+ end
36
+ end
37
+
38
+ # Return the Berkshelf clean middleware stack. When placed in the action chain
39
+ # this stack will clean up any temporary directories or files created by the other
40
+ # middleware stacks.
41
+ #
42
+ # @return [::Vagrant::Action::Builder]
43
+ def clean
44
+ @clean ||= ::Vagrant::Action::Builder.new do
45
+ use Berkshelf::Vagrant::Action::SetUI
46
+ use Berkshelf::Vagrant::Action::Clean
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,3 @@
1
1
  module Berkshelf
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0.rc1"
3
3
  end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/monkies/*.rb"].sort.each do |path|
2
+ require "thor/monkies/#{File.basename(path, '.rb')}"
3
+ end
@@ -0,0 +1,13 @@
1
+ class Thor
2
+ module CoreExt #:nodoc:
3
+ class HashWithIndifferentAccess < ::Hash
4
+ def has_key?(key)
5
+ super(convert_key(key))
6
+ end
7
+
8
+ def fetch(key, default = nil)
9
+ super(convert_key(key), default)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,2 @@
1
+ # This file is automatically loaded by Vagrant
2
+ require 'berkshelf/vagrant'
@@ -46,6 +46,11 @@ Spork.prefork do
46
46
  config.before(:each) do
47
47
  clean_tmp_path
48
48
  Berkshelf.cookbook_store = Berkshelf::CookbookStore.new(tmp_path.join("downloader_tmp"))
49
+ Berkshelf.ui.mute!
50
+ end
51
+
52
+ config.after(:each) do
53
+ Berkshelf.ui.unmute!
49
54
  end
50
55
  end
51
56
 
@@ -100,7 +100,12 @@ EOF
100
100
  end
101
101
 
102
102
  def uploader
103
- @uploader ||= Berkshelf::Uploader.new(Chef::Config[:chef_server_url])
103
+ @uploader ||= Berkshelf::Uploader.new(
104
+ server_url: Chef::Config[:chef_server_url],
105
+ client_name: Chef::Config[:node_name],
106
+ client_key: Chef::Config[:client_key],
107
+ organization: Berkshelf::ChefAPILocation.extract_organization(Chef::Config[:chef_server_url])
108
+ )
104
109
  end
105
110
  end
106
111
  end
@@ -14,7 +14,7 @@ EOF
14
14
  describe "ClassMethods" do
15
15
  subject { Berksfile }
16
16
 
17
- describe "#from_file" do
17
+ describe "::from_file" do
18
18
  let(:cookbook_file) { fixtures_path.join('lockfile_spec', 'with_lock', 'Berksfile') }
19
19
 
20
20
  it "reads a Berksfile and returns an instance Berksfile" do
@@ -32,29 +32,12 @@ EOF
32
32
  end
33
33
  end
34
34
 
35
- describe "#filter_sources" do
36
- context "given one of the sources is a member of one of the excluded groups" do
37
- let(:excluded_groups) { [:nautilus, :skarner] }
38
- let(:source_one) { double('source_one') }
39
- let(:source_two) { double('source_two') }
40
-
41
- before(:each) do
42
- source_one.stub(:groups) { [:nautilus] }
43
- source_two.stub(:groups) { [:riven] }
44
- @sources = [source_one, source_two]
45
- end
46
-
47
- it "returns an array without sources that were members of the excluded groups" do
48
- result = subject.filter_sources(@sources, excluded_groups)
49
-
50
- result.should_not include(source_one)
51
- end
52
-
53
- it "does not remove sources that were not a member of the excluded groups" do
54
- result = subject.filter_sources(@sources, excluded_groups)
55
-
56
- result.should include(source_two)
57
- end
35
+ describe "::vendor" do
36
+ it "returns the expanded filepath of the vendor directory" do
37
+ cached_cookbooks = Array.new
38
+ tmpdir = Dir.mktmpdir(nil, tmp_path)
39
+
40
+ subject.vendor(cached_cookbooks, tmpdir).should eql(tmpdir)
58
41
  end
59
42
  end
60
43
  end
@@ -174,6 +157,13 @@ EOF
174
157
  end
175
158
 
176
159
  describe "#sources" do
160
+ let(:groups) do
161
+ [
162
+ :nautilus,
163
+ :skarner
164
+ ]
165
+ end
166
+
177
167
  it "returns all CookbookSources added to the instance of Berksfile" do
178
168
  subject.add_source(source_one.name)
179
169
  subject.add_source(source_two.name)
@@ -183,11 +173,43 @@ EOF
183
173
  subject.should have_source(source_two.name)
184
174
  end
185
175
 
186
- context "given the option :exclude" do
187
- it "filters the sources before returning them" do
188
- subject.class.should_receive(:filter_sources).with(subject.sources, :nautilus)
176
+ context "given the option :except" do
177
+ before(:each) do
178
+ source_one.stub(:groups) { [:default, :skarner] }
179
+ source_two.stub(:groups) { [:default, :nautilus] }
180
+ end
181
+
182
+ it "returns all of the sources except the ones in the given groups" do
183
+ subject.add_source(source_one.name, nil, group: [:default, :skarner])
184
+ subject.add_source(source_two.name, nil, group: [:default, :nautilus])
185
+ filtered = subject.sources(except: :nautilus)
186
+
187
+ filtered.should have(1).item
188
+ filtered.first.name.should eql(source_one.name)
189
+ end
190
+ end
191
+
192
+ context "given the option :only" do
193
+ before(:each) do
194
+ source_one.stub(:groups) { [:default, :skarner] }
195
+ source_two.stub(:groups) { [:default, :nautilus] }
196
+ end
197
+
198
+ it "returns only the sources in the givne groups" do
199
+ subject.add_source(source_one.name, nil, group: [:default, :skarner])
200
+ subject.add_source(source_two.name, nil, group: [:default, :nautilus])
201
+ filtered = subject.sources(only: :nautilus)
189
202
 
190
- subject.sources(exclude: :nautilus)
203
+ filtered.should have(1).item
204
+ filtered.first.name.should eql(source_two.name)
205
+ end
206
+ end
207
+
208
+ context "when a value for :only and :except is given" do
209
+ it "raises an ArgumentError" do
210
+ lambda {
211
+ subject.sources(only: [:default], except: [:other])
212
+ }.should raise_error(Berkshelf::ArgumentError, "Cannot specify both :except and :only")
191
213
  end
192
214
  end
193
215
  end
@@ -233,6 +255,26 @@ EOF
233
255
  resolver.should_receive(:sources).and_return([])
234
256
  end
235
257
 
258
+ let(:cached_cookbooks) do
259
+ [
260
+ double('cached_one'),
261
+ double('cached_two')
262
+ ]
263
+ end
264
+
265
+ it "returns the result from sending the message resolve to resolver" do
266
+ resolver.should_receive(:resolve).and_return(cached_cookbooks)
267
+
268
+ subject.install.should eql(cached_cookbooks)
269
+ end
270
+
271
+ it "sets a value for self.cached_cookbooks equivalent to the return value" do
272
+ resolver.should_receive(:resolve).and_return(cached_cookbooks)
273
+ subject.install
274
+
275
+ subject.cached_cookbooks.should eql(cached_cookbooks)
276
+ end
277
+
236
278
  it "creates a new resolver and finds a solution by calling resolve on the resolver" do
237
279
  resolver.should_receive(:resolve)
238
280
 
@@ -258,42 +300,38 @@ EOF
258
300
  end
259
301
  end
260
302
 
261
- context "when given a value for :shims pointing to a valid path"do
262
- let(:cached_one) { double('cached_one', cookbook_name: 'nginx', path: fixtures_path.join("cookbooks", "nginx-0.100.5")) }
263
- let(:cached_two) { double('cached_two', cookbook_name: 'example_cookbook', path: fixtures_path.join("cookbooks", "example_cookbook-0.5.0")) }
264
- let(:shims_path) { tmp_path.join("cookbook_shims") }
303
+ context "when a value for :path is given" do
304
+ before(:each) { resolver.should_receive(:resolve) }
265
305
 
266
- before(:each) do
267
- resolver.stub(:resolve).and_return([cached_one, cached_two])
268
- end
306
+ it "sends the message 'vendor' to Berksfile with the value for :path" do
307
+ path = double('path')
308
+ subject.class.should_receive(:vendor).with(subject.cached_cookbooks, path)
269
309
 
270
- it "sends a message to write_shims with the given directory and the resolver's solution" do
271
- subject.should_receive(:write_shims).with(shims_path, [cached_one, cached_two])
272
- subject.install(shims: shims_path)
310
+ subject.install(path: path)
273
311
  end
274
312
  end
275
- end
276
313
 
277
- describe "#write_shims" do
278
- let(:cached_one) { double('cached_one', cookbook_name: 'nginx', path: fixtures_path.join("cookbooks", "nginx-0.100.5")) }
279
- let(:cached_two) { double('cached_two', cookbook_name: 'example_cookbook', path: fixtures_path.join("cookbooks", "example_cookbook-0.5.0")) }
280
- let(:shims_path) { tmp_path.join("cookbook_shims") }
314
+ context "when a value for :except is given" do
315
+ before(:each) { resolver.should_receive(:resolve) }
281
316
 
282
- before(:each) { subject.write_shims(shims_path, [cached_one, cached_two]) }
317
+ it "filters the sources and gives the results to the Resolver initializer" do
318
+ filtered = double('sources')
319
+ subject.should_receive(:sources).with(except: [:skip_me]).and_return(filtered)
320
+ Resolver.should_receive(:new).with(anything, sources: filtered)
283
321
 
284
- it "writes a directory at the given path" do
285
- shims_path.should exist
286
- shims_path.should be_directory
322
+ subject.install(except: [:skip_me])
323
+ end
287
324
  end
288
325
 
289
- it "writes a symlink of the name of each source within the given directory" do
290
- linked_path_one = shims_path.join(cached_one.cookbook_name)
291
- linked_path_two = shims_path.join(cached_two.cookbook_name)
292
-
293
- linked_path_one.should exist
294
- linked_path_one.should be_cookbook
295
- linked_path_two.should exist
296
- linked_path_two.should be_cookbook
326
+ context "when a value for :only is given" do
327
+ before(:each) { resolver.should_receive(:resolve) }
328
+
329
+ it "filters the sources and gives the results to the Resolver initializer" do
330
+ filtered = double('sources')
331
+ subject.should_receive(:sources).with(only: [:skip_me]).and_return(filtered)
332
+
333
+ subject.install(only: [:skip_me])
334
+ end
297
335
  end
298
336
  end
299
337