berkshelf 1.4.0.rc1 → 1.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 (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"