berkshelf 2.0.3 → 2.0.4

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