berkshelf 3.1.5 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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?