berkshelf 2.0.3 → 2.0.4

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 (42) hide show
  1. data/CHANGELOG.md +7 -0
  2. data/Gemfile +2 -0
  3. data/berkshelf.gemspec +5 -4
  4. data/features/berksfile.feature +56 -0
  5. data/features/install_command.feature +99 -13
  6. data/features/json_formatter.feature +1 -1
  7. data/features/lockfile.feature +50 -23
  8. data/features/step_definitions/filesystem_steps.rb +14 -1
  9. data/features/step_definitions/json_steps.rb +1 -1
  10. data/features/update_command.feature +2 -2
  11. data/features/upload_command.feature +0 -1
  12. data/lib/berkshelf.rb +1 -15
  13. data/lib/berkshelf/berksfile.rb +27 -21
  14. data/lib/berkshelf/cli.rb +2 -3
  15. data/lib/berkshelf/commands/test_command.rb +4 -2
  16. data/lib/berkshelf/community_rest.rb +6 -0
  17. data/lib/berkshelf/cookbook_source.rb +15 -37
  18. data/lib/berkshelf/core_ext/rbzip2.rb +8 -0
  19. data/lib/berkshelf/downloader.rb +56 -47
  20. data/lib/berkshelf/errors.rb +9 -2
  21. data/lib/berkshelf/formatters/human_readable.rb +10 -3
  22. data/lib/berkshelf/formatters/json.rb +7 -3
  23. data/lib/berkshelf/git.rb +2 -1
  24. data/lib/berkshelf/init_generator.rb +18 -12
  25. data/lib/berkshelf/location.rb +4 -14
  26. data/lib/berkshelf/locations/chef_api_location.rb +0 -1
  27. data/lib/berkshelf/locations/git_location.rb +1 -2
  28. data/lib/berkshelf/locations/path_location.rb +35 -11
  29. data/lib/berkshelf/locations/site_location.rb +0 -1
  30. data/lib/berkshelf/resolver.rb +18 -14
  31. data/lib/berkshelf/version.rb +1 -1
  32. data/spec/fixtures/cookbooks/example_cookbook/Berksfile +1 -0
  33. data/spec/unit/berkshelf/berksfile_spec.rb +30 -2
  34. data/spec/unit/berkshelf/community_rest_spec.rb +49 -11
  35. data/spec/unit/berkshelf/cookbook_source_spec.rb +11 -7
  36. data/spec/unit/berkshelf/downloader_spec.rb +1 -1
  37. data/spec/unit/berkshelf/location_spec.rb +0 -6
  38. data/spec/unit/berkshelf/locations/chef_api_location_spec.rb +0 -4
  39. data/spec/unit/berkshelf/locations/git_location_spec.rb +0 -5
  40. data/spec/unit/berkshelf/locations/path_location_spec.rb +0 -41
  41. data/spec/unit/berkshelf_spec.rb +0 -25
  42. metadata +37 -16
@@ -20,7 +20,7 @@ Then /^the file "(.*?)" should contain JSON:$/ do |file, data|
20
20
  end
21
21
 
22
22
  Then /^the output should contain JSON:$/ do |data|
23
- target = JSON.pretty_generate(JSON.parse(ERB.new(data).result(binding)).sort_by_key)
23
+ target = JSON.pretty_generate(JSON.parse(data).sort_by_key)
24
24
  actual = JSON.pretty_generate(JSON.parse(all_output).sort_by_key)
25
25
 
26
26
  expect(actual).to eq(target)
@@ -44,7 +44,7 @@ Feature: Updating a cookbook defined by a Berksfile
44
44
  And I write to "Berksfile.lock" with:
45
45
  """
46
46
  {
47
- "sha":"9d10199aa2652f9e965149c4346db20c78e97553",
47
+ "sha":"69b2e00e970d2bb6a9b1d09aeb3e6a17ef3df955",
48
48
  "sources":{
49
49
  "berkshelf-cookbook-fixture":{
50
50
  "locked_version":"0.1.0",
@@ -89,7 +89,7 @@ Feature: Updating a cookbook defined by a Berksfile
89
89
  And I write to "Berksfile.lock" with:
90
90
  """
91
91
  {
92
- "sha":"9d10199aa2652f9e965149c4346db20c78e97553",
92
+ "sha":"69b2e00e970d2bb6a9b1d09aeb3e6a17ef3df955",
93
93
  "sources":{
94
94
  "berkshelf-cookbook-fixture":{
95
95
  "locked_version":"0.1.0",
@@ -293,4 +293,3 @@ Feature: Uploading cookbooks to a Chef Server
293
293
  And the Chef server should not have the cookbooks:
294
294
  | ekaf | 2.0.0 |
295
295
  And the exit status should be 0
296
-
data/lib/berkshelf.rb CHANGED
@@ -13,6 +13,7 @@ require 'thor'
13
13
  require 'tmpdir'
14
14
  require 'uri'
15
15
  require 'zlib'
16
+ require 'rbzip2'
16
17
 
17
18
  require_relative 'berkshelf/core_ext'
18
19
  require_relative 'berkshelf/errors'
@@ -89,21 +90,6 @@ module Berkshelf
89
90
  @cookbook_store ||= CookbookStore.new(cookbooks_dir)
90
91
  end
91
92
 
92
- # Ascend the directory structure from the given path to find a
93
- # metadata.rb file of a Chef Cookbook. If no metadata.rb file
94
- # was found, nil is returned.
95
- #
96
- # @return [Pathname]
97
- # path to metadata.rb
98
- def find_metadata(path = Dir.pwd)
99
- path = Pathname.new(path)
100
- path.ascend do |potential_root|
101
- if potential_root.entries.collect(&:to_s).include?('metadata.rb')
102
- return potential_root.join('metadata.rb')
103
- end
104
- end
105
- end
106
-
107
93
  # Get the appropriate Formatter object based on the formatter
108
94
  # classes that have been registered.
109
95
  #
@@ -90,9 +90,9 @@ module Berkshelf
90
90
  end
91
91
 
92
92
  # @return [String]
93
- # the shasum for the Berksfile
93
+ # the shasum for the sources in the Berksfile (or metadata/path locations)
94
94
  def sha
95
- @sha ||= Digest::SHA1.hexdigest File.read(filepath.to_s)
95
+ @sha ||= Digest::SHA1.hexdigest(shaable_contents.join("\n"))
96
96
  end
97
97
 
98
98
  # Add a cookbook source to the Berksfile to be retrieved and have it's dependencies recursively retrieved
@@ -193,23 +193,14 @@ module Berkshelf
193
193
  def metadata(options = {})
194
194
  path = options[:path] || File.dirname(filepath)
195
195
 
196
- metadata_file = Berkshelf.find_metadata(path)
196
+ metadata_path = File.expand_path(File.join(path, 'metadata.rb'))
197
+ metadata = Ridley::Chef::Cookbook::Metadata.from_file(metadata_path)
197
198
 
198
- unless metadata_file
199
- raise CookbookNotFound, "No 'metadata.rb' found at #{path}"
200
- end
201
-
202
- metadata = Ridley::Chef::Cookbook::Metadata.from_file(metadata_file.to_s)
203
-
204
- name = if metadata.name.empty? || metadata.name.nil?
205
- File.basename(File.dirname(metadata_file))
206
- else
207
- metadata.name
208
- end
199
+ shaable_contents << File.read(metadata_path)
209
200
 
210
- constraint = "= #{metadata.version}"
201
+ name = metadata.name.presence || File.basename(File.expand_path(path))
211
202
 
212
- add_source(name, constraint, path: File.dirname(metadata_file))
203
+ add_source(name, nil, { path: path, metadata: true })
213
204
  end
214
205
 
215
206
  # Add a 'Site' default location which will be used to resolve cookbook sources that do not
@@ -274,6 +265,11 @@ module Berkshelf
274
265
  end
275
266
  end
276
267
 
268
+ if options[:path]
269
+ metadata_file = File.expand_path(File.join(options[:path], 'metadata.rb'))
270
+ shaable_contents << File.read(metadata_file)
271
+ end
272
+
277
273
  options[:constraint] = constraint
278
274
 
279
275
  @sources[name] = CookbookSource.new(self, name, options)
@@ -389,10 +385,11 @@ module Berkshelf
389
385
  # sources are considered to be "unlocked". If a lockfile is specified, a
390
386
  # definition is created via the following algorithm:
391
387
  #
392
- # - Compare the SHA of the current Berksfile with the last-known SHA.
393
- # - If the SHAs match, the Berksfile has not been updated, so we rely
394
- # solely on the locked sources.
395
- # - If the SHAs don't match, then the Berksfile has diverged from the
388
+ # - Compare the SHA of the current sources (as JSON) with the last-known
389
+ # SHA of the sources.
390
+ # - If the SHAs match, the sources have not been updated, so we can rely
391
+ # solely on the locked ones.
392
+ # - If the SHAs don't match, then the sources have diverged from the
396
393
  # lockfile, which means some sources are outdated. For each unlocked
397
394
  # source, see if there exists a locked version that still satisfies
398
395
  # the version constraint in the Berksfile. If there exists such a
@@ -791,7 +788,8 @@ module Berkshelf
791
788
  path = cookbook.path.to_s
792
789
 
793
790
  files = Dir.glob(File.join(path, '**', '*.rb')).select do |f|
794
- f =~ /[[:space:]]/
791
+ parent = Pathname.new(path).dirname.to_s
792
+ f.gsub(parent, '') =~ /[[:space:]]/
795
793
  end
796
794
 
797
795
  raise Berkshelf::InvalidCookbookFiles.new(cookbook, files) unless files.empty?
@@ -824,5 +822,13 @@ module Berkshelf
824
822
  end
825
823
  end
826
824
  end
825
+
826
+ # The contents of the files that we want to SHA for caching against
827
+ # the lockfile.
828
+ #
829
+ # @return [Array<String>]
830
+ def shaable_contents
831
+ @shaable_contents ||= [File.read(self.filepath)]
832
+ end
827
833
  end
828
834
  end
data/lib/berkshelf/cli.rb CHANGED
@@ -2,9 +2,8 @@ require 'berkshelf'
2
2
  require_relative 'config'
3
3
  require_relative 'init_generator'
4
4
  require_relative 'cookbook_generator'
5
-
6
- require 'berkshelf/commands/test_command'
7
- require 'berkshelf/commands/shelf'
5
+ require_relative 'commands/shelf'
6
+ require_relative 'commands/test_command'
8
7
 
9
8
  module Berkshelf
10
9
  class Cli < Thor
@@ -1,4 +1,6 @@
1
- require 'kitchen/cli'
1
+ begin
2
+ require 'kitchen/cli'
3
+ rescue LoadError; end
2
4
 
3
5
  module Berkshelf
4
6
  class TestCommand < Kitchen::CLI
@@ -8,4 +10,4 @@ module Berkshelf
8
10
  class Cli < Thor
9
11
  register(Berkshelf::TestCommand, 'test', 'test [COMMAND]', 'Testing tasks for your cookbook', hide: true)
10
12
  end
11
- end
13
+ end if defined?(Kitchen::CLI)
@@ -16,6 +16,8 @@ module Berkshelf
16
16
  Archive::Tar::Minitar.unpack(Zlib::GzipReader.new(File.open(target, 'rb')), destination)
17
17
  elsif is_tar_file(target)
18
18
  Archive::Tar::Minitar.unpack(target, destination)
19
+ elsif is_bzip2_file(target)
20
+ Archive::Tar::Minitar.unpack(RBzip2::Decompressor.new(File.open(target, 'rb')), destination)
19
21
  else
20
22
  raise Berkshelf::UnknownCompressionType.new(target)
21
23
  end
@@ -46,6 +48,10 @@ module Berkshelf
46
48
  def is_tar_file(path)
47
49
  IO.binread(path, 8, 257).to_s == "ustar\x0000"
48
50
  end
51
+
52
+ def is_bzip2_file(path)
53
+ IO.binread(path, 3) == 'BZh'
54
+ end
49
55
  end
50
56
 
51
57
  V1_API = 'http://cookbooks.opscode.com/api/v1/cookbooks'.freeze
@@ -93,6 +93,8 @@ module Berkshelf
93
93
  # a URL pointing to a community API endpoint
94
94
  # @option options [String] :path
95
95
  # a filepath to the cookbook on your local disk
96
+ # @option options [String] :metadata
97
+ # use the metadata at the given pat
96
98
  # @option options [Symbol, Array] :group
97
99
  # the group or groups that the cookbook belongs to
98
100
  # @option options [String] :ref
@@ -131,7 +133,7 @@ module Berkshelf
131
133
  #
132
134
  # @return [Array<CachedCookbook, Location>]
133
135
  def cached_and_location(options = {})
134
- from_path(options) || from_cache(options) || from_default(options)
136
+ from_path(options) || from_default(options)
135
137
  end
136
138
 
137
139
  # Returns true if the cookbook source has already been downloaded. A cookbook
@@ -192,14 +194,20 @@ module Berkshelf
192
194
 
193
195
  def to_hash
194
196
  {}.tap do |h|
195
- h[:locked_version] = locked_version.to_s
197
+ unless location.kind_of?(PathLocation)
198
+ h[:locked_version] = locked_version.to_s
199
+ end
196
200
 
197
201
  unless version_constraint.to_s == DEFAULT_CONSTRAINT
198
202
  h[:constraint] = version_constraint.to_s
199
203
  end
200
204
 
201
- if location.kind_of?(SiteLocation) && location.api_uri != CommunityREST::V1_API
202
- h[:site] = location.api_uri
205
+ if location.kind_of?(SiteLocation)
206
+ h[:site] = location.api_uri if location.api_uri != CommunityREST::V1_API
207
+ end
208
+
209
+ if location.kind_of?(PathLocation)
210
+ h[:path] = location.relative_path(berksfile.filepath)
203
211
  end
204
212
 
205
213
  if location.kind_of?(GitLocation)
@@ -207,11 +215,6 @@ module Berkshelf
207
215
  h[:ref] = location.ref
208
216
  h[:rel] = location.rel if location.rel
209
217
  end
210
-
211
- # Path is intentionally left relative here for cross-team compatibility
212
- if @options[:path]
213
- h[:path] = @options[:path].to_s
214
- end
215
218
  end.reject { |k,v| v.blank? }
216
219
  end
217
220
 
@@ -220,42 +223,24 @@ module Berkshelf
220
223
  end
221
224
 
222
225
  private
223
-
224
226
  # Attempt to load a CachedCookbook from a local file system path (if the :path
225
227
  # option was given). If one is found, the location and cached_cookbook is
226
228
  # updated. Otherwise, this method will raise a CookbookNotFound exception.
227
229
  #
228
- # @raises [Berkshelf::CookbookNotFound]
230
+ # @raise [Berkshelf::CookbookNotFound]
229
231
  # if no CachedCookbook exists at the given path
230
232
  #
231
233
  # @return [Berkshelf::CachedCookbook]
232
234
  def from_path(options = {})
233
235
  return nil unless options[:path]
234
236
 
235
- path = File.expand_path(PathLocation.normalize_path(options[:path]), File.dirname(berksfile.filepath))
236
- location = PathLocation.new(name, version_constraint, path: path)
237
- cached = CachedCookbook.from_path(location.path)
237
+ location = PathLocation.new(name, version_constraint, options)
238
238
 
239
- [ cached, location ]
239
+ [ location.cookbook, location ]
240
240
  rescue IOError => ex
241
241
  raise Berkshelf::CookbookNotFound, ex
242
242
  end
243
243
 
244
- # Attempt to load a CachedCookbook from the local CookbookStore. This will save
245
- # the need to make an http request to download a cookbook we already have cached
246
- # locally.
247
- #
248
- # @return [Berkshelf::CachedCookbook, nil]
249
- def from_cache(options = {})
250
- path = File.join(Berkshelf.cookbooks_dir, filename(options))
251
- return nil unless File.exists?(path)
252
-
253
- location = PathLocation.new(name, version_constraint, path: path)
254
- cached = CachedCookbook.from_path(path, name: name)
255
-
256
- [ cached, location ]
257
- end
258
-
259
244
  # Use the default location, and a nil CachedCookbook. If there is no location
260
245
  # specified,
261
246
  #
@@ -269,12 +254,5 @@ module Berkshelf
269
254
 
270
255
  [ nil, location ]
271
256
  end
272
-
273
- # The hypothetical location of this CachedCookbook, if it were to exist.
274
- #
275
- # @return [String]
276
- def filename(options = {})
277
- "#{name}-#{options[:locked_version]}"
278
- end
279
257
  end
280
258
  end
@@ -0,0 +1,8 @@
1
+ class RBzip2::Decompressor
2
+ def pos ; end
3
+ def pos=(*args) ; end
4
+
5
+ def eof?
6
+ @io.eof?
7
+ end
8
+ end
@@ -60,7 +60,7 @@ module Berkshelf
60
60
  @locations.select { |loc| loc[:type] == type && loc[:value] == value }.any?
61
61
  end
62
62
 
63
- # Downloads the given CookbookSource.
63
+ # Download the given CookbookSource.
64
64
  #
65
65
  # @param [CookbookSource] source
66
66
  # the source to download
@@ -69,69 +69,78 @@ module Berkshelf
69
69
  # an array containing the downloaded CachedCookbook and the Location used
70
70
  # to download the cookbook
71
71
  def download(source)
72
- cached_cookbook, location = if source.location
72
+ if source.location
73
73
  begin
74
- [source.location.download(storage_path), source.location]
75
- rescue CookbookValidationFailure; raise
76
- rescue
74
+ location = source.location
75
+ cached = download_location(source, location, true)
76
+ source.cached_cookbook = cached
77
+
78
+ return [cached, location]
79
+ rescue => e
80
+ raise if e.kind_of?(CookbookValidationFailure)
77
81
  Berkshelf.formatter.error "Failed to download '#{source.name}' from #{source.location}"
78
- raise
79
82
  end
80
83
  else
81
- search_locations(source)
82
- end
84
+ locations.each do |loc|
85
+ options = loc[:options].merge(loc[:type] => loc[:value])
86
+ location = Location.init(source.name, source.version_constraint, options)
83
87
 
84
- source.cached_cookbook = cached_cookbook
88
+ cached = download_location(source, location)
89
+ if cached
90
+ source.cached_cookbook = cached
91
+ return [cached, location]
92
+ end
93
+ end
94
+ end
85
95
 
86
- [cached_cookbook, location]
96
+ raise CookbookNotFound, "Cookbook '#{source.name}' not found in any of the default locations"
87
97
  end
88
98
 
89
99
  private
90
100
 
91
- # Searches locations for a CookbookSource. If the source does not contain a
92
- # value for {CookbookSource#location}, the default locations of this
93
- # downloader will be used to attempt to retrieve the source.
101
+ # Attempt to download the the given source from the given location, #
102
+ # raising an error if `raise_if_not_found` is specified.
103
+ #
104
+ # @raise [Bershelf::CookbookNotFound]
105
+ # if `raise_if_not_found` is true and the source could not be
106
+ # downloaded
94
107
  #
95
- # @param [CookbookSource] source
108
+ # @param [Berkshelf::CookbookSource] source
96
109
  # the source to download
110
+ # @param [~Berkshelf::Location] location
111
+ # the location to download from
112
+ # @param [Boolean] raise_if_not_found
113
+ # raise a {Berkshelf::CookbookNotFound} error if true, otherwise,
114
+ # return nil
97
115
  #
98
- # @return [Array]
99
- # an array containing the downloaded CachedCookbook and the Location used
100
- # to download the cookbook
101
- def search_locations(source)
102
- cached_cookbook = nil
103
- location = nil
104
-
105
- locations.each do |loc|
106
- location = Location.init(
107
- source.name,
108
- source.version_constraint,
109
- loc[:options].merge(loc[:type] => loc[:value])
110
- )
111
- begin
112
- cached_cookbook = location.download(storage_path)
113
- break
114
- rescue Berkshelf::CookbookNotFound
115
- cached_cookbook, location = nil
116
- next
117
- end
118
- end
119
-
120
- if cached_cookbook.nil?
121
- raise CookbookNotFound, "Cookbook '#{source.name}' not found in any of the default locations"
122
- end
123
-
124
- [ cached_cookbook, location ]
116
+ # @return [Berkshelf::CachedCookbook, nil]
117
+ # the downloaded cached cookbook, or nil if one was not found
118
+ def download_location(source, location, raise_if_not_found = false)
119
+ from_cache(source) || location.download(storage_path)
120
+ rescue Berkshelf::CookbookNotFound
121
+ raise if raise_if_not_found
122
+ nil
125
123
  end
126
124
 
127
-
128
- # Validates that a source is an instance of CookbookSource
125
+ # Load the cached cookbook from the cookbook store.
129
126
  #
130
- # @param [CookbookSource] source
127
+ # @param [Berkshelf::CookbookSource] source
128
+ # the source to find in the cache
131
129
  #
132
- # @return [Boolean]
133
- def validate_source(source)
134
- source.is_a?(Berkshelf::CookbookSource)
130
+ # @return [Berkshelf::CachedCookbook, nil]
131
+ def from_cache(source)
132
+ # Can't safely read a git location from cache
133
+ return nil if source.location.kind_of?(Berkshelf::GitLocation)
134
+
135
+ if source.locked_version
136
+ cookbook = cookbook_store.cookbook_path(source.name, source.locked_version)
137
+ path = File.expand_path(File.join(storage_path, cookbook))
138
+
139
+ return nil unless File.exists?(path)
140
+ return Berkshelf::CachedCookbook.from_path(path, name: source.name)
141
+ end
142
+
143
+ cookbook_store.satisfy(source.name, source.version_constraint)
135
144
  end
136
145
  end
137
146
  end