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.
- data/CHANGELOG.md +7 -0
- data/Gemfile +2 -0
- data/berkshelf.gemspec +5 -4
- data/features/berksfile.feature +56 -0
- data/features/install_command.feature +99 -13
- data/features/json_formatter.feature +1 -1
- data/features/lockfile.feature +50 -23
- data/features/step_definitions/filesystem_steps.rb +14 -1
- data/features/step_definitions/json_steps.rb +1 -1
- data/features/update_command.feature +2 -2
- data/features/upload_command.feature +0 -1
- data/lib/berkshelf.rb +1 -15
- data/lib/berkshelf/berksfile.rb +27 -21
- data/lib/berkshelf/cli.rb +2 -3
- data/lib/berkshelf/commands/test_command.rb +4 -2
- data/lib/berkshelf/community_rest.rb +6 -0
- data/lib/berkshelf/cookbook_source.rb +15 -37
- data/lib/berkshelf/core_ext/rbzip2.rb +8 -0
- data/lib/berkshelf/downloader.rb +56 -47
- data/lib/berkshelf/errors.rb +9 -2
- data/lib/berkshelf/formatters/human_readable.rb +10 -3
- data/lib/berkshelf/formatters/json.rb +7 -3
- data/lib/berkshelf/git.rb +2 -1
- data/lib/berkshelf/init_generator.rb +18 -12
- data/lib/berkshelf/location.rb +4 -14
- data/lib/berkshelf/locations/chef_api_location.rb +0 -1
- data/lib/berkshelf/locations/git_location.rb +1 -2
- data/lib/berkshelf/locations/path_location.rb +35 -11
- data/lib/berkshelf/locations/site_location.rb +0 -1
- data/lib/berkshelf/resolver.rb +18 -14
- data/lib/berkshelf/version.rb +1 -1
- data/spec/fixtures/cookbooks/example_cookbook/Berksfile +1 -0
- data/spec/unit/berkshelf/berksfile_spec.rb +30 -2
- data/spec/unit/berkshelf/community_rest_spec.rb +49 -11
- data/spec/unit/berkshelf/cookbook_source_spec.rb +11 -7
- data/spec/unit/berkshelf/downloader_spec.rb +1 -1
- data/spec/unit/berkshelf/location_spec.rb +0 -6
- data/spec/unit/berkshelf/locations/chef_api_location_spec.rb +0 -4
- data/spec/unit/berkshelf/locations/git_location_spec.rb +0 -5
- data/spec/unit/berkshelf/locations/path_location_spec.rb +0 -41
- data/spec/unit/berkshelf_spec.rb +0 -25
- 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(
|
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":"
|
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":"
|
92
|
+
"sha":"69b2e00e970d2bb6a9b1d09aeb3e6a17ef3df955",
|
93
93
|
"sources":{
|
94
94
|
"berkshelf-cookbook-fixture":{
|
95
95
|
"locked_version":"0.1.0",
|
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
|
#
|
data/lib/berkshelf/berksfile.rb
CHANGED
@@ -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
|
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
|
-
|
196
|
+
metadata_path = File.expand_path(File.join(path, 'metadata.rb'))
|
197
|
+
metadata = Ridley::Chef::Cookbook::Metadata.from_file(metadata_path)
|
197
198
|
|
198
|
-
|
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
|
-
|
201
|
+
name = metadata.name.presence || File.basename(File.expand_path(path))
|
211
202
|
|
212
|
-
add_source(name,
|
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
|
393
|
-
#
|
394
|
-
#
|
395
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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) ||
|
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
|
-
|
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)
|
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
|
-
# @
|
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
|
-
|
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
|
-
[
|
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
|
data/lib/berkshelf/downloader.rb
CHANGED
@@ -60,7 +60,7 @@ module Berkshelf
|
|
60
60
|
@locations.select { |loc| loc[:type] == type && loc[:value] == value }.any?
|
61
61
|
end
|
62
62
|
|
63
|
-
#
|
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
|
-
|
72
|
+
if source.location
|
73
73
|
begin
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
82
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
92
|
-
#
|
93
|
-
#
|
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 [
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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 [
|
133
|
-
def
|
134
|
-
|
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
|