berkshelf 3.1.5 → 3.2.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/berkshelf.gemspec +6 -5
  4. data/features/commands/search.feature +2 -2
  5. data/features/commands/vendor.feature +6 -2
  6. data/features/commands/verify.feature +29 -0
  7. data/features/config.feature +13 -48
  8. data/features/step_definitions/filesystem_steps.rb +2 -2
  9. data/features/step_definitions/gem_steps.rb +3 -1
  10. data/features/step_definitions/utility_steps.rb +2 -2
  11. data/generator_files/Vagrantfile.erb +30 -30
  12. data/generator_files/metadata.rb.erb +0 -1
  13. data/lib/berkshelf.rb +5 -2
  14. data/lib/berkshelf/berksfile.rb +41 -41
  15. data/lib/berkshelf/cli.rb +11 -1
  16. data/lib/berkshelf/community_rest.rb +1 -0
  17. data/lib/berkshelf/config.rb +18 -4
  18. data/lib/berkshelf/cookbook_store.rb +1 -1
  19. data/lib/berkshelf/downloader.rb +4 -0
  20. data/lib/berkshelf/errors.rb +0 -1
  21. data/lib/berkshelf/file_syncer.rb +134 -0
  22. data/lib/berkshelf/locations/base.rb +6 -1
  23. data/lib/berkshelf/locations/git.rb +2 -2
  24. data/lib/berkshelf/lockfile.rb +14 -2
  25. data/lib/berkshelf/uploader.rb +10 -17
  26. data/lib/berkshelf/validator.rb +37 -0
  27. data/lib/berkshelf/version.rb +1 -1
  28. data/lib/berkshelf/visualizer.rb +13 -6
  29. data/spec/spec_helper.rb +1 -1
  30. data/spec/support/kitchen.rb +3 -1
  31. data/spec/support/matchers/file_system_matchers.rb +1 -1
  32. data/spec/support/matchers/filepath_matchers.rb +38 -2
  33. data/spec/support/shared_examples/formatter.rb +7 -7
  34. data/spec/unit/berkshelf/berksfile_spec.rb +51 -21
  35. data/spec/unit/berkshelf/cached_cookbook_spec.rb +5 -5
  36. data/spec/unit/berkshelf/cli_spec.rb +1 -1
  37. data/spec/unit/berkshelf/community_rest_spec.rb +12 -12
  38. data/spec/unit/berkshelf/config_spec.rb +4 -4
  39. data/spec/unit/berkshelf/cookbook_generator_spec.rb +2 -2
  40. data/spec/unit/berkshelf/cookbook_store_spec.rb +6 -6
  41. data/spec/unit/berkshelf/core_ext/file_utils_spec.rb +3 -3
  42. data/spec/unit/berkshelf/core_ext/pathname_spec.rb +23 -6
  43. data/spec/unit/berkshelf/dependency_spec.rb +4 -4
  44. data/spec/unit/berkshelf/downloader_spec.rb +5 -1
  45. data/spec/unit/berkshelf/errors_spec.rb +1 -1
  46. data/spec/unit/berkshelf/file_syncer_spec.rb +206 -0
  47. data/spec/unit/berkshelf/init_generator_spec.rb +19 -22
  48. data/spec/unit/berkshelf/installer_spec.rb +6 -6
  49. data/spec/unit/berkshelf/locations/base_spec.rb +17 -8
  50. data/spec/unit/berkshelf/locations/git_spec.rb +34 -34
  51. data/spec/unit/berkshelf/locations/path_spec.rb +3 -3
  52. data/spec/unit/berkshelf/lockfile_parser_spec.rb +1 -1
  53. data/spec/unit/berkshelf/lockfile_spec.rb +50 -36
  54. data/spec/unit/berkshelf/packager_spec.rb +6 -4
  55. data/spec/unit/berkshelf/resolver/graph_spec.rb +3 -3
  56. data/spec/unit/berkshelf/resolver_spec.rb +3 -3
  57. data/spec/unit/berkshelf/shell_spec.rb +30 -24
  58. data/spec/unit/berkshelf/uploader_spec.rb +10 -36
  59. data/spec/unit/berkshelf/validator_spec.rb +30 -0
  60. data/spec/unit/berkshelf/visualizer_spec.rb +17 -2
  61. metadata +34 -15
  62. data/lib/berkshelf/mixin/dsl_eval.rb +0 -58
  63. data/spec/unit/berkshelf/mixin/dsl_eval_spec.rb +0 -55
@@ -134,7 +134,7 @@ module Berkshelf
134
134
  def install
135
135
  if options[:path]
136
136
  # TODO: Remove in Berkshelf 4.0
137
- Berkshelf.formatter.deprecation "`berks install --path [PATH}` has been replaced by `berks vendor`."
137
+ Berkshelf.formatter.deprecation "`berks install --path [PATH]` has been replaced by `berks vendor`."
138
138
  Berkshelf.formatter.deprecation "Re-run your command as `berks vendor [PATH]` or see `berks help vendor`."
139
139
  exit(1)
140
140
  end
@@ -387,6 +387,16 @@ module Berkshelf
387
387
  berksfile.vendor(path)
388
388
  end
389
389
 
390
+ method_option :berksfile,
391
+ type: :string,
392
+ default: nil
393
+ desc "verify", "Perform a quick validation on the contents of your resolved cookbooks"
394
+ def verify
395
+ berksfile = Berksfile.from_options(options)
396
+ berksfile.verify
397
+ Berkshelf.formatter.msg "Verified."
398
+ end
399
+
390
400
  method_option :berksfile,
391
401
  type: :string,
392
402
  default: nil,
@@ -18,6 +18,7 @@ module Berkshelf
18
18
  else
19
19
  raise Berkshelf::UnknownCompressionType.new(target)
20
20
  end
21
+ FileUtils.rm_rf Dir.glob("#{destination}/**/PaxHeader")
21
22
  destination
22
23
  end
23
24
 
@@ -60,7 +60,19 @@ module Berkshelf
60
60
  # @param [Hash] options
61
61
  # @see {Buff::Config::JSON}
62
62
  def initialize(path = self.class.path, options = {})
63
- super(path, options)
63
+ super(path, options).tap do
64
+ # Deprecation
65
+ if !self.vagrant.omnibus.enabled.nil?
66
+ Berkshelf.ui.warn "`vagrant.omnibus.enabled' is deprecated and " \
67
+ "will be removed in a future release. Please remove the " \
68
+ "`enabled' attribute from your Berkshelf config."
69
+ end
70
+ if !self.vagrant.vm.box_url.nil?
71
+ Berkshelf.ui.warn "`vagrant.vm.box_url' is deprecated and " \
72
+ "will be removed in a future release. Please remove the " \
73
+ "`box_url' attribute from your Berkshelf config."
74
+ end
75
+ end
64
76
  end
65
77
 
66
78
  attribute 'chef.chef_server_url',
@@ -97,19 +109,21 @@ module Berkshelf
97
109
  type: String,
98
110
  default: 'chef/ubuntu-14.04',
99
111
  required: true
112
+ # @todo Deprecated, remove?
100
113
  attribute 'vagrant.vm.box_url',
101
114
  type: String,
102
- default: 'https://vagrantcloud.com/chef/ubuntu-14.04/version/1/provider/virtualbox.box',
103
- required: true
115
+ default: nil
104
116
  attribute 'vagrant.vm.forward_port',
105
117
  type: Hash,
106
118
  default: Hash.new
107
119
  attribute 'vagrant.vm.provision',
108
120
  type: String,
109
121
  default: 'chef_solo'
122
+ # @todo Deprecated, remove. There's a really weird tri-state here where
123
+ # nil is used to represent an unset value, just FYI
110
124
  attribute 'vagrant.omnibus.enabled',
111
125
  type: Buff::Boolean,
112
- default: true
126
+ default: nil
113
127
  attribute 'vagrant.omnibus.version',
114
128
  type: String,
115
129
  default: 'latest'
@@ -121,7 +121,7 @@ module Berkshelf
121
121
  msg << "of a name attribute as a bug.\n\n"
122
122
  msg << "You can remove each cookbook in #{skipped_cookbooks} from the Berkshelf shelf "
123
123
  msg << "by using the `berks shelf uninstall` command:\n\n"
124
- msg << " $ berkshelf shelf uninstall <name>"
124
+ msg << " $ berks shelf uninstall <name>"
125
125
  Berkshelf.log.warn msg
126
126
  end
127
127
 
@@ -133,6 +133,10 @@ module Berkshelf
133
133
  end.first
134
134
 
135
135
  (unpack_dir + cookbook_directory).to_s
136
+ when :file_store
137
+ tmp_dir = Dir.mktmpdir
138
+ FileUtils.cp_r(remote_cookbook.location_path, tmp_dir)
139
+ File.join(tmp_dir, name)
136
140
  else
137
141
  raise RuntimeError, "unknown location type #{remote_cookbook.location_type}"
138
142
  end
@@ -394,7 +394,6 @@ module Berkshelf
394
394
  end
395
395
 
396
396
  class DuplicateDemand < BerkshelfError; set_status_code(138); end
397
- class VendorError < BerkshelfError; set_status_code(139); end
398
397
  class LockfileNotFound < BerkshelfError
399
398
  set_status_code(140)
400
399
 
@@ -0,0 +1,134 @@
1
+ require 'fileutils'
2
+
3
+ module Berkshelf
4
+ module FileSyncer
5
+ extend self
6
+
7
+ # Files to be ignored during a directory globbing
8
+ IGNORED_FILES = %w(. ..).freeze
9
+
10
+ #
11
+ # Glob across the given pattern, accounting for dotfiles, removing Ruby's
12
+ # dumb idea to include +'.'+ and +'..'+ as entries.
13
+ #
14
+ # @param [String] pattern
15
+ # the path or glob pattern to get all files from
16
+ #
17
+ # @return [Array<String>]
18
+ # the list of all files
19
+ #
20
+ def glob(pattern)
21
+ Dir.glob(pattern, File::FNM_DOTMATCH).sort.reject do |file|
22
+ basename = File.basename(file)
23
+ IGNORED_FILES.include?(basename)
24
+ end
25
+ end
26
+
27
+ #
28
+ # Copy the files from +source+ to +destination+, while removing any files
29
+ # in +destination+ that are not present in +source+.
30
+ #
31
+ # The method accepts an optional +:exclude+ parameter to ignore files and
32
+ # folders that match the given pattern(s). Note the exclude pattern behaves
33
+ # on paths relative to the given source. If you want to exclude a nested
34
+ # directory, you will need to use something like +**/directory+.
35
+ #
36
+ # @raise ArgumentError
37
+ # if the +source+ parameter is not a directory
38
+ #
39
+ # @param [String] source
40
+ # the path on disk to sync from
41
+ # @param [String] destination
42
+ # the path on disk to sync to
43
+ #
44
+ # @option options [String, Array<String>] :exclude
45
+ # a file, folder, or globbing pattern of files to ignore when syncing
46
+ #
47
+ # @return [true]
48
+ #
49
+ def sync(source, destination, options = {})
50
+ unless File.directory?(source)
51
+ raise ArgumentError, "`source' must be a directory, but was a " \
52
+ "`#{File.ftype(source)}'! If you just want to sync a file, use " \
53
+ "the `copy' method instead."
54
+ end
55
+
56
+ # Reject any files that match the excludes pattern
57
+ excludes = Array(options[:exclude]).map do |exclude|
58
+ [exclude, "#{exclude}/*"]
59
+ end.flatten
60
+
61
+ source_files = glob(File.join(source, '**/*'))
62
+ source_files = source_files.reject do |source_file|
63
+ basename = relative_path_for(source_file, source)
64
+ excludes.any? { |exclude| File.fnmatch?(exclude, basename, File::FNM_DOTMATCH) }
65
+ end
66
+
67
+ # Ensure the destination directory exists
68
+ FileUtils.mkdir_p(destination) unless File.directory?(destination)
69
+
70
+ # Copy over the filtered source files
71
+ source_files.each do |source_file|
72
+ relative_path = relative_path_for(source_file, source)
73
+
74
+ # Create the parent directory
75
+ parent = File.join(destination, File.dirname(relative_path))
76
+ FileUtils.mkdir_p(parent) unless File.directory?(parent)
77
+
78
+ case File.ftype(source_file).to_sym
79
+ when :directory
80
+ FileUtils.mkdir_p("#{destination}/#{relative_path}")
81
+ when :link
82
+ target = File.readlink(source_file)
83
+
84
+ Dir.chdir(destination) do
85
+ FileUtils.ln_sf(target, "#{destination}/#{relative_path}")
86
+ end
87
+ when :file
88
+ FileUtils.cp(source_file, "#{destination}/#{relative_path}")
89
+ else
90
+ type = File.ftype(source_file)
91
+ raise RuntimeError, "Unknown file type: `#{type}' at " \
92
+ "`#{source_file}'. Failed to sync `#{source_file}' to " \
93
+ "`#{destination}/#{relative_path}'!"
94
+ end
95
+ end
96
+
97
+ # Remove any files in the destination that are not in the source files
98
+ destination_files = glob("#{destination}/**/*")
99
+
100
+ # Calculate the relative paths of files so we can compare to the
101
+ # source.
102
+ relative_source_files = source_files.map do |file|
103
+ relative_path_for(file, source)
104
+ end
105
+ relative_destination_files = destination_files.map do |file|
106
+ relative_path_for(file, destination)
107
+ end
108
+
109
+ # Remove any extra files that are present in the destination, but are
110
+ # not in the source list
111
+ extra_files = relative_destination_files - relative_source_files
112
+ extra_files.each do |file|
113
+ FileUtils.rm_rf(File.join(destination, file))
114
+ end
115
+
116
+ true
117
+ end
118
+
119
+ private
120
+ #
121
+ # The relative path of the given +path+ to the +parent+.
122
+ #
123
+ # @param [String] path
124
+ # the path to get relative with
125
+ # @param [String] parent
126
+ # the parent where the path is contained (hopefully)
127
+ #
128
+ # @return [String]
129
+ #
130
+ def relative_path_for(path, parent)
131
+ Pathname.new(path).relative_path_from(Pathname.new(parent)).to_s
132
+ end
133
+ end
134
+ end
@@ -59,7 +59,12 @@ module Berkshelf
59
59
  raise NotACookbook.new(path)
60
60
  end
61
61
 
62
- cookbook = CachedCookbook.from_path(path)
62
+ begin
63
+ cookbook = CachedCookbook.from_path(path)
64
+ rescue Ridley::Errors::RidleyError => e
65
+ raise InternalError, "The following error occurred while reading the " \
66
+ "cookbook `#{dependency.name}':\n#{e.class}: #{e.message}"
67
+ end
63
68
 
64
69
  unless @dependency.version_constraint.satisfies?(cookbook.version)
65
70
  raise CookbookValidationFailure.new(dependency, cookbook)
@@ -27,7 +27,7 @@ module Berkshelf
27
27
 
28
28
  # @see BaseLoation#installed?
29
29
  def installed?
30
- revision && install_path.exist?
30
+ !!(revision && install_path.exist?)
31
31
  end
32
32
 
33
33
  # Install this git cookbook into the cookbook store. This method leverages
@@ -71,7 +71,7 @@ module Berkshelf
71
71
  FileUtils.cp_r(scratch_path, install_path)
72
72
 
73
73
  # Remove the git history
74
- FileUtils.rm_rf(File.join(install_path, '.git'))
74
+ FileUtils.rm_rf(File.join(install_path, '.git'))
75
75
 
76
76
  install_path.chmod(0777 & ~File.umask)
77
77
  ensure
@@ -16,7 +16,10 @@ module Berkshelf
16
16
  # @param [Berkshelf::Berksfile] berksfile
17
17
  # the Berksfile associated with the Lockfile
18
18
  def from_berksfile(berksfile)
19
- filepath = File.join(File.dirname(File.expand_path(berksfile.filepath)), Lockfile::DEFAULT_FILENAME)
19
+ parent = File.expand_path(File.dirname(berksfile.filepath))
20
+ lockfile_name = "#{File.basename(berksfile.filepath)}.lock"
21
+
22
+ filepath = File.join(parent, lockfile_name)
20
23
  new(berksfile: berksfile, filepath: filepath)
21
24
  end
22
25
  end
@@ -36,7 +39,7 @@ module Berkshelf
36
39
  # the Berksfile for this Lockfile
37
40
  attr_reader :berksfile
38
41
 
39
- # @return [Hash]
42
+ # @return [Lockfile::Graph]
40
43
  # the dependency graph
41
44
  attr_reader :graph
42
45
 
@@ -213,6 +216,11 @@ module Berkshelf
213
216
  end
214
217
  end
215
218
 
219
+ # @return [Array<CachedCookbook>]
220
+ def cached
221
+ graph.locks.values.collect { |dependency| dependency.cached_cookbook }
222
+ end
223
+
216
224
  # The list of dependencies constrained in this lockfile.
217
225
  #
218
226
  # @return [Array<Berkshelf::Dependency>]
@@ -257,6 +265,10 @@ module Berkshelf
257
265
  @dependencies[Dependency.name(dependency)] = dependency
258
266
  end
259
267
 
268
+ def locks
269
+ graph.locks
270
+ end
271
+
260
272
  # Retrieve information about a given cookbook that is in this lockfile.
261
273
  #
262
274
  # @raise [DependencyNotFound]
@@ -31,7 +31,7 @@ module Berkshelf
31
31
  end
32
32
 
33
33
  # Perform all validations first to prevent partially uploaded cookbooks
34
- cookbooks.each { |cookbook| validate_files!(cookbook) }
34
+ Validator.validate_files(cookbooks)
35
35
 
36
36
  upload(cookbooks)
37
37
  cookbooks
@@ -100,23 +100,16 @@ module Berkshelf
100
100
  cookbooks[dependency] ||= lockfile.retrieve(dependency)
101
101
  end
102
102
 
103
- cookbooks.values.sort
104
- end
105
-
106
- # Validate that the given cookbook does not have "bad" files. Currently
107
- # this means including spaces in filenames (such as recipes)
108
- #
109
- # @param [CachedCookbook] cookbook
110
- # the Cookbook to validate
111
- def validate_files!(cookbook)
112
- path = cookbook.path.to_s
113
-
114
- files = Dir.glob(File.join(path, '**', '*.rb')).select do |f|
115
- parent = Pathname.new(path).dirname.to_s
116
- f.gsub(parent, '') =~ /[[:space:]]/
103
+ # This is a temporary change and will be removed in a future release. We should
104
+ # add the ability to define a custom uploader which would allow the authors of Chef-Guard
105
+ # to define their upload strategy instead of using Ridley.
106
+ #
107
+ # See https://github.com/berkshelf/berkshelf/pull/1316 for details.
108
+ if Berkshelf.chef_config.knife[:chef_guard] == true
109
+ cookbooks.values
110
+ else
111
+ cookbooks.values.sort
117
112
  end
118
-
119
- raise InvalidCookbookFiles.new(cookbook, files) unless files.empty?
120
113
  end
121
114
  end
122
115
  end
@@ -0,0 +1,37 @@
1
+ module Berkshelf
2
+ module Validator
3
+ class << self
4
+ # Perform a complete cookbook validation checking:
5
+ # * File names for inappropriate characters
6
+ # * Invalid Ruby syntax
7
+ # * Invalid ERB templates
8
+ #
9
+ # @param [Array<CachedCookbook>, CachedCookbook] cookbooks
10
+ # the Cookbook(s) to validate
11
+ def validate(cookbooks)
12
+ Array(cookbooks).each do |cookbook|
13
+ validate_files(cookbook)
14
+ cookbook.validate
15
+ end
16
+ end
17
+
18
+ # Validate that the given cookbook does not have "bad" files. Currently
19
+ # this means including spaces in filenames (such as recipes)
20
+ #
21
+ # @param [Array<CachedCookbook>, CachedCookbook] cookbooks
22
+ # the Cookbook(s) to validate
23
+ def validate_files(cookbooks)
24
+ Array(cookbooks).each do |cookbook|
25
+ path = cookbook.path.to_s
26
+
27
+ files = Dir.glob(File.join(path, '**', '*.rb')).select do |f|
28
+ parent = Pathname.new(path).dirname.to_s
29
+ f.gsub(parent, '') =~ /[[:space:]]/
30
+ end
31
+
32
+ raise InvalidCookbookFiles.new(cookbook, files) unless files.empty?
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Berkshelf
2
- VERSION = "3.1.5"
2
+ VERSION = "3.2.0"
3
3
  end
@@ -11,7 +11,7 @@ module Berkshelf
11
11
  instance.node(item.name)
12
12
 
13
13
  item.dependencies.each do |name, version|
14
- instance.edge(item.name, name)
14
+ instance.edge(item.name, name, version)
15
15
  end
16
16
  end
17
17
  end
@@ -37,11 +37,11 @@ module Berkshelf
37
37
  nodes.each(&block)
38
38
  end
39
39
 
40
- def edge(a, b)
40
+ def edge(a, b, version)
41
41
  node(a)
42
42
  node(b)
43
43
 
44
- @nodes[a].add(b)
44
+ @nodes[a].add(b => version)
45
45
  end
46
46
 
47
47
  def adjacencies(object)
@@ -61,7 +61,14 @@ module Berkshelf
61
61
 
62
62
  nodes.each do |node|
63
63
  adjacencies(node).each do |edge|
64
- out << %| "#{node}" -> "#{edge}" [ fontsize = 10 ]\n|
64
+ edge.each do |name, version|
65
+ if version == Semverse::DEFAULT_CONSTRAINT
66
+ label = ""
67
+ else
68
+ label = " #{version}"
69
+ end
70
+ out << %| "#{node}" -> "#{name}" [ fontsize = 10, label = "#{label}" ]\n|
71
+ end
65
72
  end
66
73
  end
67
74
 
@@ -81,11 +88,11 @@ module Berkshelf
81
88
  tempfile.write(to_dot)
82
89
  tempfile.rewind
83
90
 
84
- unless Berkshelf.which('dot')
91
+ unless Berkshelf.which('dot') || Berkshelf.which('dot.exe')
85
92
  raise GraphvizNotInstalled.new
86
93
  end
87
94
 
88
- command = "dot -T png #{tempfile.path} -o #{outfile}"
95
+ command = %|dot -T png #{tempfile.path} -o "#{outfile}"|
89
96
  response = shell_out(command)
90
97
 
91
98
  unless response.success?