berkshelf 1.4.0.rc1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.gitignore +1 -0
  2. data/.ruby-version +1 -1
  3. data/berkshelf.gemspec +2 -1
  4. data/features/contingent_command.feature +18 -0
  5. data/features/cookbook_command.feature +13 -11
  6. data/features/info_command.feature +39 -0
  7. data/features/step_definitions/filesystem_steps.rb +32 -0
  8. data/features/step_definitions/gem_steps.rb +3 -2
  9. data/features/support/env.rb +2 -0
  10. data/generator_files/Berksfile.erb +5 -0
  11. data/generator_files/Vagrantfile.erb +10 -0
  12. data/generator_files/default_test.rb.erb +11 -0
  13. data/generator_files/helpers.rb.erb +7 -0
  14. data/lib/berkshelf.rb +4 -3
  15. data/lib/berkshelf/berksfile.rb +5 -15
  16. data/lib/berkshelf/cached_cookbook.rb +18 -0
  17. data/lib/berkshelf/chef/config.rb +21 -26
  18. data/lib/berkshelf/cli.rb +40 -5
  19. data/lib/berkshelf/community_rest.rb +17 -1
  20. data/lib/berkshelf/cookbook_generator.rb +4 -0
  21. data/lib/berkshelf/cookbook_source.rb +21 -18
  22. data/lib/berkshelf/init_generator.rb +13 -3
  23. data/lib/berkshelf/locations/github_location.rb +1 -1
  24. data/lib/berkshelf/locations/path_location.rb +1 -1
  25. data/lib/berkshelf/resolver.rb +10 -6
  26. data/lib/berkshelf/test.rb +37 -0
  27. data/lib/berkshelf/version.rb +1 -1
  28. data/spec/spec_helper.rb +5 -1
  29. data/spec/unit/berkshelf/community_rest_spec.rb +1 -0
  30. data/spec/unit/berkshelf/cookbook_source_spec.rb +201 -181
  31. data/spec/unit/berkshelf/init_generator_spec.rb +40 -1
  32. data/spec/unit/berkshelf/locations/path_location_spec.rb +10 -0
  33. data/spec/unit/berkshelf/resolver_spec.rb +6 -7
  34. data/spec/unit/berkshelf/ui_spec.rb +2 -1
  35. data/spec/unit/chef/config_spec.rb +79 -4
  36. metadata +34 -8
data/.gitignore CHANGED
@@ -23,3 +23,4 @@ tmp
23
23
  *.sw[op]
24
24
  \.\#*
25
25
  rerun.txt
26
+ .rspec
@@ -1 +1 @@
1
- 1.9.3-p327
1
+ 1.9.3-p392
@@ -40,8 +40,9 @@ Gem::Specification.new do |s|
40
40
  s.add_dependency 'json', '>= 1.5.0'
41
41
  s.add_dependency 'multi_json', '~> 1.5'
42
42
  s.add_dependency 'solve', '>= 0.4.2'
43
- s.add_dependency 'thor', '~> 0.16.0'
43
+ s.add_dependency 'thor', '~> 0.18.0'
44
44
  s.add_dependency 'retryable'
45
+ s.add_dependency 'addressable'
45
46
 
46
47
  s.add_development_dependency 'aruba'
47
48
  s.add_development_dependency 'cane'
@@ -0,0 +1,18 @@
1
+ Feature: contingent command
2
+ As a user with a Berksfile
3
+ I want a way to the cookbooks that depend on another
4
+ So that I can better understand my infrastructure
5
+
6
+ Scenario: Running the contingent command against a cookbook
7
+ Given I write to "Berksfile" with:
8
+ """
9
+ cookbook "database", "1.3.12"
10
+ """
11
+ And I successfully run `berks install`
12
+ When I run `berks contingent mysql`
13
+ Then the output should contain:
14
+ """
15
+ Cookbooks contingent upon mysql:
16
+ * database (1.3.12)
17
+ """
18
+ And the exit status should be 0
@@ -15,14 +15,16 @@ Feature: cookbook command
15
15
  And the exit status should be 0
16
16
 
17
17
  Examples:
18
- | option | feature |
19
- | foodcritic | Foodcritic |
20
- | scmversion | SCMVersion |
21
- | no-bundler | no Bundler |
22
- | skip-git | no Git |
23
- | skip-vagrant | no Vagrant |
18
+ | option | feature |
19
+ | foodcritic | Foodcritic |
20
+ | chef-minitest | Chef-Minitest |
21
+ | scmversion | SCMVersion |
22
+ | no-bundler | no Bundler |
23
+ | skip-git | no Git |
24
+ | skip-vagrant | no Vagrant |
24
25
 
25
26
  Scenario Outline: creating a new cookbook skeleton with options without the supporting gem installed
27
+ Given the gem "<gem>" is not installed
26
28
  When I run the cookbook command to create "sparkle_motion" with options:
27
29
  | --<option> |
28
30
  Then I should have a new cookbook skeleton "sparkle_motion" with <feature> support
@@ -33,10 +35,10 @@ Feature: cookbook command
33
35
  | option | feature | gem |
34
36
  | foodcritic | Foodcritic | foodcritic |
35
37
  | scmversion | SCMVersion | thor-scmversion |
36
- # | no-bundler | no Bundler | bundler |
37
38
 
38
- @pending
39
39
  Scenario: creating a new cookbook skeleton with bundler support without bundler installed
40
- Given pending "Bundler is used in tests, so it always appears available. Need to mock out the Gem::Specification.find_by_name, but aruba is out of process testing."
41
-
42
-
40
+ Given the gem "bundler" is not installed
41
+ When I run the cookbook command to create "sparkle_motion"
42
+ Then I should have a new cookbook skeleton "sparkle_motion"
43
+ And the output should contain a warning to suggest supporting the default for "bundler" by installing "bundler"
44
+ And the exit status should be 0
@@ -0,0 +1,39 @@
1
+ Feature: info command
2
+ As a user
3
+ I want to be able to view the metadata information of a cached cookbook
4
+ So that I can troubleshoot bugs or satisfy my own curiosity
5
+
6
+ Scenario: Running the info command with an installed cookbook name
7
+ Given the cookbook store has the cookbooks:
8
+ | mysql | 2.1.2 |
9
+ | mysql | 1.2.4 |
10
+ | mysql | 0.10.0 |
11
+ When I successfully run `berks info mysql`
12
+ Then the output should contain "Name: mysql"
13
+ Then the output should contain "Version: 2.1.2"
14
+ Then the output should contain "Description: A fabulous new cookbook"
15
+ Then the output should contain "Author: YOUR_COMPANY_NAME"
16
+ Then the output should contain "Email: YOUR_EMAIL"
17
+ Then the output should contain "License: none"
18
+ And the exit status should be 0
19
+
20
+ Scenario: Running the info command with an installed cookbook name and a version
21
+ Given the cookbook store has the cookbooks:
22
+ | mysql | 2.1.2 |
23
+ | mysql | 1.2.4 |
24
+ | mysql | 0.10.0 |
25
+ When I successfully run `berks info mysql --version 1.2.4`
26
+ Then the output should contain "Name: mysql"
27
+ Then the output should contain "Version: 1.2.4"
28
+ Then the output should contain "Description: A fabulous new cookbook"
29
+ Then the output should contain "Author: YOUR_COMPANY_NAME"
30
+ Then the output should contain "Email: YOUR_EMAIL"
31
+ Then the output should contain "License: none"
32
+ And the exit status should be 0
33
+
34
+ Scenario: Running the info command with a not installed cookbook name
35
+ Given the cookbook store has the cookbooks:
36
+ | mysql | 2.1.2 |
37
+ When I run `berks info build-essential`
38
+ Then the output should contain "Cookbook 'build-essential' was not installed by your Berksfile"
39
+ And the CLI should exit with the status code for error "CookbookNotFound"
@@ -115,6 +115,38 @@ Then /^I should have a new cookbook skeleton "(.*?)"$/ do |name|
115
115
  }
116
116
  end
117
117
 
118
+ Then /^I should have a new cookbook skeleton "(.*?)" with Chef-Minitest support$/ do |name|
119
+ steps %Q{ Then I should have a new cookbook skeleton "#{name}" }
120
+
121
+ cb_path = Pathname.new(current_dir).join(name)
122
+ cb_path.should have_structure {
123
+ file "Berksfile" do
124
+ contains "cookbook \"minitest-handler\""
125
+ end
126
+ file "Vagrantfile" do
127
+ contains "recipe[minitest-handler::default]"
128
+ end
129
+ directory "files" do
130
+ directory "default" do
131
+ directory "tests" do
132
+ directory "minitest" do
133
+ file "default_test.rb" do
134
+ contains "describe '#{name}::default' do"
135
+ contains "include Helpers::#{name.capitalize}"
136
+ end
137
+ directory "support" do
138
+ file "helpers.rb" do
139
+ contains "module #{name.capitalize}"
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ }
147
+ end
148
+
149
+
118
150
  Then /^I should have a new cookbook skeleton "(.*?)" with Foodcritic support$/ do |name|
119
151
  steps %Q{ Then I should have a new cookbook skeleton "#{name}" }
120
152
 
@@ -1,5 +1,6 @@
1
1
  Given /^the gem "(.*)" is not installed$/ do |gem_name|
2
- # Because aruba is out of process, need to figure out how to mock the Gem::Specification.find_by_name call to pretend gems are not available.
2
+ # @see berkshelf/test.rb
3
+ set_env 'MISSING_GEMS', [ENV['MISSING_GEMS'], gem_name].compact.join(',')
3
4
  end
4
5
 
5
6
  Then /^the output should contain a warning to suggest supporting the option "(.*?)" by installing "(.*?)"$/ do |option, gem_name|
@@ -7,5 +8,5 @@ Then /^the output should contain a warning to suggest supporting the option "(.*
7
8
  end
8
9
 
9
10
  Then /^the output should contain a warning to suggest supporting the default for "(.*?)" by installing "(.*?)"$/ do |option, gem_name|
10
- step "the output should contain \"By default, this cookbook was generated to support #{gem_name}, however, #{gem_name} is not installed.\nTo skip support for #{gem_name}, use --#{option}\"\nTo install #{gem_name}: gem install #{gem_name}"
11
+ step "the output should contain \"By default, this cookbook was generated to support #{gem_name}, however, #{gem_name} is not installed.\nTo skip support for #{gem_name}, use --no-#{option}\"\nTo install #{gem_name}: gem install #{gem_name}"
11
12
  end
@@ -1,3 +1,5 @@
1
+ ENV['RUBY_ENV'] == 'test'
2
+
1
3
  require 'rubygems'
2
4
  require 'bundler'
3
5
  require 'spork'
@@ -1,4 +1,9 @@
1
1
  site :opscode
2
+ <% if options[:chef_minitest] -%>
3
+ group :integration do
4
+ cookbook "minitest-handler"
5
+ end
6
+ <% end -%>
2
7
  <% if options[:metadata_entry] -%>
3
8
 
4
9
  metadata
@@ -68,6 +68,10 @@ Vagrant.configure("2") do |config|
68
68
  # The path to the Berksfile to use with Vagrant Berkshelf
69
69
  # config.berkshelf.berksfile_path = "./Berksfile"
70
70
 
71
+ # Enabling the Berkshelf plugin. To enable this globally, add this configuration
72
+ # option to your ~/.vagrant.d/Vagrantfile file
73
+ config.berkshelf.enabled = true
74
+
71
75
  # An array of symbols representing groups of cookbook described in the Vagrantfile
72
76
  # to exclusively install and copy to Vagrant's shelf.
73
77
  # config.berkshelf.only = []
@@ -83,6 +87,9 @@ Vagrant.configure("2") do |config|
83
87
  chef.validation_key_path = "<%= options[:berkshelf_config].chef.validation_key_path %>"
84
88
 
85
89
  chef.run_list = [
90
+ <% if options[:chef_minitest] -%>
91
+ "recipe[minitest-handler::default]",
92
+ <% end -%>
86
93
  "recipe[<%= cookbook_name %>::default]"
87
94
  ]
88
95
  end
@@ -97,6 +104,9 @@ Vagrant.configure("2") do |config|
97
104
  }
98
105
 
99
106
  chef.run_list = [
107
+ <% if options[:chef_minitest] -%>
108
+ "recipe[minitest-handler::default]",
109
+ <% end -%>
100
110
  "recipe[<%= cookbook_name %>::default]"
101
111
  ]
102
112
  end
@@ -0,0 +1,11 @@
1
+ require File.expand_path('../support/helpers', __FILE__)
2
+
3
+ describe '<%= cookbook_name %>::default' do
4
+
5
+ include Helpers::<%= cookbook_name.capitalize.sub('-','_') %>
6
+
7
+ # Example spec tests can be found at http://git.io/Fahwsw
8
+ it 'runs no tests by default' do
9
+ end
10
+
11
+ end
@@ -0,0 +1,7 @@
1
+ module Helpers
2
+ module <%= cookbook_name.capitalize.sub('-','_') %>
3
+ include MiniTest::Chef::Assertions
4
+ include MiniTest::Chef::Context
5
+ include MiniTest::Chef::Resources
6
+ end
7
+ end
@@ -1,11 +1,11 @@
1
- require 'multi_json'
2
- require 'ridley'
3
1
  require 'chozo/core_ext'
4
2
  require 'active_support/core_ext'
5
3
  require 'archive/tar/minitar'
6
4
  require 'forwardable'
7
5
  require 'hashie'
6
+ require 'multi_json'
8
7
  require 'pathname'
8
+ require 'ridley'
9
9
  require 'solve'
10
10
  require 'thor'
11
11
  require 'tmpdir'
@@ -13,9 +13,10 @@ require 'uri'
13
13
  require 'zlib'
14
14
  require 'celluloid'
15
15
 
16
- require 'berkshelf/version'
17
16
  require 'berkshelf/core_ext'
18
17
  require 'berkshelf/errors'
18
+ require 'berkshelf/test' if ENV['RUBY_ENV'] == 'test'
19
+ require 'berkshelf/version'
19
20
  require 'thor/monkies'
20
21
 
21
22
  JSON.create_id = nil
@@ -264,7 +264,7 @@ module Berkshelf
264
264
 
265
265
  options[:constraint] = constraint
266
266
 
267
- @sources[name] = CookbookSource.new(name, options)
267
+ @sources[name] = CookbookSource.new(self, name, options)
268
268
  end
269
269
 
270
270
  # @param [#to_s] source
@@ -365,10 +365,7 @@ module Berkshelf
365
365
  #
366
366
  # @return [Array<Berkshelf::CachedCookbook>]
367
367
  def install(options = {})
368
- resolver = Resolver.new(
369
- self.downloader,
370
- sources: sources(options)
371
- )
368
+ resolver = Resolver.new(self, sources: sources(options))
372
369
 
373
370
  @cached_cookbooks = resolver.resolve
374
371
  write_lockfile(resolver.sources) unless lockfile_present?
@@ -389,10 +386,7 @@ module Berkshelf
389
386
  # @option cookbooks [String, Array] :cookbooks
390
387
  # Names of the cookbooks to retrieve sources for
391
388
  def update(options = {})
392
- resolver = Resolver.new(
393
- self.downloader,
394
- sources: sources(options)
395
- )
389
+ resolver = Resolver.new(self, sources: sources(options))
396
390
 
397
391
  cookbooks = resolver.resolve
398
392
  sources = resolver.sources
@@ -503,9 +497,9 @@ module Berkshelf
503
497
  raise UploadFailure, "Missing required attribute in your Berkshelf configuration: chef.client_key"
504
498
  end
505
499
 
506
- conn = Ridley.new(ridley_options)
507
500
  solution = resolve(options)
508
501
  upload_opts = options.slice(:force, :freeze)
502
+ conn = Ridley.new(ridley_options)
509
503
 
510
504
  solution.each do |cb|
511
505
  Berkshelf.formatter.upload(cb.cookbook_name, cb.version, conn.server_url)
@@ -596,11 +590,7 @@ module Berkshelf
596
590
  #
597
591
  # @return <Berkshelf::Resolver>
598
592
  def resolver(options = {})
599
- Resolver.new(
600
- self.downloader,
601
- sources: sources(options),
602
- skip_dependencies: options[:skip_dependencies]
603
- )
593
+ Resolver.new(self, sources: sources(options), skip_dependencies: options[:skip_dependencies])
604
594
  end
605
595
 
606
596
  def write_lockfile(sources)
@@ -23,5 +23,23 @@ module Berkshelf
23
23
  def dependencies
24
24
  metadata.recommendations.merge(metadata.dependencies)
25
25
  end
26
+
27
+ def pretty_print
28
+ [].tap do |a|
29
+ a.push " Name: #{cookbook_name}" unless name.blank?
30
+ a.push " Version: #{version}" unless version.blank?
31
+ a.push " Description: #{metadata.description}" unless metadata.description.blank?
32
+ a.push " Author: #{metadata.maintainer}" unless metadata.maintainer.blank?
33
+ a.push " Email: #{metadata.maintainer_email}" unless metadata.maintainer_email.blank?
34
+ a.push " License: #{metadata.license}" unless metadata.license.blank?
35
+ a.push " Platforms: #{pretty_map(metadata.platforms, 14)}" unless metadata.platforms.blank?
36
+ a.push "Dependencies: #{pretty_map(dependencies, 14)}" unless dependencies.blank?
37
+ end.join("\n")
38
+ end
39
+
40
+ private
41
+ def pretty_map(hash, padding)
42
+ hash.map { |k,v| "#{k} (#{v})" }.join("\n" + ' '*padding)
43
+ end
26
44
  end
27
45
  end
@@ -8,15 +8,6 @@ module Berkshelf::Chef
8
8
  #
9
9
  # Inspired by and a dependency-free replacement for {https://raw.github.com/opscode/chef/11.4.0/lib/chef/config.rb}
10
10
  class Config
11
- # List taken from: http://wiki.opscode.com/display/chef/Chef+Configuration+Settings
12
- # Listed in order of preferred preference
13
- KNIFE_LOCATIONS = [
14
- './.chef/knife.rb',
15
- '~/.chef/knife.rb',
16
- '/etc/chef/solo.rb',
17
- '/etc/chef/client.rb'
18
- ].freeze
19
-
20
11
  class << self
21
12
  # Load and return a Chef::Config for Berkshelf. The location of the configuration to be loaded
22
13
  # can be configured by setting a value for {Berkshelf::Chef::Config.path=}
@@ -34,29 +25,27 @@ module Berkshelf::Chef
34
25
  # Return the most sensible path to the Chef configuration file. This can be configured by setting a
35
26
  # value for the 'BERKSHELF_CHEF_CONFIG' environment variable.
36
27
  #
37
- # If no value is set for the environment variable then a search will begin for a configuration at these paths
38
- # in this order:
39
- #
40
- # * './chef/knife.rb'
41
- # * '~/.chef/knife.rb',
42
- # * '/etc/chef/solo.rb'
43
- # * '/etc/chef/client.rb'
44
- #
45
28
  # @return [String, nil]
46
29
  def path
47
30
  @path ||= begin
48
- possibles = KNIFE_LOCATIONS.dup
31
+ possibles = []
32
+
33
+ possibles << ENV['BERKSHELF_CHEF_CONFIG'] if ENV['BERKSHELF_CHEF_CONFIG']
34
+ possibles << File.join(ENV['KNIFE_HOME'], 'knife.rb') if ENV['KNIFE_HOME']
35
+ possibles << File.join(working_dir, 'knife.rb') if working_dir
49
36
 
50
- unless ENV['BERKSHELF_CHEF_CONFIG'].nil?
51
- possibles.unshift(ENV['BERKSHELF_CHEF_CONFIG'])
52
- end
37
+ # Ascending search for .chef directory siblings
38
+ Pathname.new(working_dir).ascend do |file|
39
+ sibling_chef = File.join(file, '.chef')
40
+ possibles << File.join(sibling_chef, 'knife.rb')
41
+ end if working_dir
53
42
 
54
- location = possibles.find do |location|
55
- File.exists?(File.expand_path(location))
56
- end
57
- location ||= "~/.chef/knife.rb"
43
+ possibles << File.join(ENV['HOME'], '.chef', 'knife.rb') if ENV['HOME']
44
+ possibles.compact!
58
45
 
59
- File.expand_path(location)
46
+ location = possibles.find { |loc| File.exists?(File.expand_path(loc)) }
47
+
48
+ File.expand_path(location) unless location.nil?
60
49
  end
61
50
  end
62
51
 
@@ -67,6 +56,12 @@ module Berkshelf::Chef
67
56
  @instance = nil
68
57
  @path = value
69
58
  end
59
+
60
+ private
61
+
62
+ def working_dir
63
+ ENV['PWD'] || Dir.pwd
64
+ end
70
65
  end
71
66
 
72
67
  extend Berkshelf::Mixin::PathHelpers
@@ -15,7 +15,7 @@ module Berkshelf
15
15
  end
16
16
  else
17
17
  super
18
- Berkshelf.formatter.cleanup_hook unless config[:current_task].name == "help"
18
+ Berkshelf.formatter.cleanup_hook unless config[:current_command].name == "help"
19
19
  end
20
20
  end
21
21
  end
@@ -331,10 +331,8 @@ module Berkshelf
331
331
  desc: "Path to a Berksfile to operate off of.",
332
332
  aliases: "-b",
333
333
  banner: "PATH"
334
- desc "show [COOKBOOK]", "Display the source path on the local file system for the given cookbook"
335
- def show(name = nil)
336
- return list if name.nil?
337
-
334
+ desc "show COOKBOOK", "Display the source path on the local file system for the given cookbook"
335
+ def show(name)
338
336
  berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
339
337
  cookbook = Berkshelf.ui.mute { berksfile.resolve }.find{ |cookbook| cookbook.cookbook_name == name }
340
338
 
@@ -342,6 +340,40 @@ module Berkshelf
342
340
  Berkshelf.ui.say(cookbook.path)
343
341
  end
344
342
 
343
+ method_option :version,
344
+ type: :string,
345
+ desc: 'The version of the cookbook to display.',
346
+ aliases: '-v'
347
+ desc "info [COOKBOOK]", "Display name, author, copyright, and dependency information about a cookbook"
348
+ def info(name)
349
+ if options[:version]
350
+ cookbook = Berkshelf.cookbook_store.cookbook(name, options[:version])
351
+ else
352
+ cookbook = Berkshelf.cookbook_store.cookbooks(name).sort_by(&:version).last
353
+ end
354
+
355
+ raise CookbookNotFound, "Cookbook '#{name}' was not installed by your Berksfile" if cookbook.nil?
356
+ Berkshelf.ui.say(cookbook.pretty_print)
357
+ end
358
+
359
+ method_option :berksfile,
360
+ type: :string,
361
+ default: File.join(Dir.pwd, Berkshelf::DEFAULT_FILENAME),
362
+ desc: "Path to a Berksfile to operate off of.",
363
+ aliases: "-b",
364
+ banner: "PATH"
365
+ desc "contingent COOKBOOK", "Display a list of cookbooks that depend on the given cookbook"
366
+ def contingent(name)
367
+ berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
368
+
369
+ Berkshelf.ui.say "Cookbooks contingent upon #{name}:"
370
+ sources = Berkshelf.ui.mute { berksfile.resolve }.sort.each do |cookbook|
371
+ if cookbook.dependencies.include?(name)
372
+ Berkshelf.ui.say " * #{cookbook.cookbook_name} (#{cookbook.version})"
373
+ end
374
+ end
375
+ end
376
+
345
377
  desc "version", "Display version and copyright information"
346
378
  def version
347
379
  Berkshelf.formatter.msg version_header
@@ -352,6 +384,9 @@ module Berkshelf
352
384
  method_option :foodcritic,
353
385
  type: :boolean,
354
386
  desc: "Creates a Thorfile with Foodcritic support to lint test your cookbook"
387
+ method_option :chef_minitest,
388
+ type: :boolean,
389
+ desc: "Creates chef-minitest support files and directories, adds minitest-handler cookbook to run_list of Vagrantfile"
355
390
  method_option :scmversion,
356
391
  type: :boolean,
357
392
  desc: "Creates a Thorfile with SCMVersion support to manage versions for continuous integration"