berkshelf 3.0.0.beta6 → 3.0.0.beta7
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.
- checksums.yaml +4 -4
- data/features/berksfile.feature +2 -0
- data/features/commands/apply.feature +1 -1
- data/features/commands/contingent.feature +5 -3
- data/features/commands/install.feature +40 -40
- data/features/commands/list.feature +42 -20
- data/features/commands/outdated.feature +60 -16
- data/features/commands/show.feature +51 -8
- data/features/commands/update.feature +43 -15
- data/features/commands/upload.feature +4 -1
- data/features/commands/vendor.feature +27 -0
- data/features/json_formatter.feature +20 -8
- data/features/lockfile.feature +192 -71
- data/generator_files/CHANGELOG.md.erb +5 -0
- data/lib/berkshelf/berksfile.rb +166 -139
- data/lib/berkshelf/cli.rb +33 -30
- data/lib/berkshelf/cookbook_generator.rb +1 -0
- data/lib/berkshelf/dependency.rb +64 -14
- data/lib/berkshelf/downloader.rb +7 -10
- data/lib/berkshelf/errors.rb +59 -11
- data/lib/berkshelf/formatters/human_readable.rb +23 -36
- data/lib/berkshelf/formatters/json.rb +25 -29
- data/lib/berkshelf/installer.rb +111 -122
- data/lib/berkshelf/locations/git_location.rb +22 -9
- data/lib/berkshelf/locations/mercurial_location.rb +20 -5
- data/lib/berkshelf/locations/path_location.rb +22 -7
- data/lib/berkshelf/lockfile.rb +435 -203
- data/lib/berkshelf/resolver.rb +4 -2
- data/lib/berkshelf/source.rb +10 -1
- data/lib/berkshelf/version.rb +1 -1
- data/spec/fixtures/cookbooks/example_cookbook/Berksfile.lock +3 -4
- data/spec/fixtures/lockfiles/2.0.lock +17 -0
- data/spec/fixtures/lockfiles/blank.lock +0 -0
- data/spec/fixtures/lockfiles/default.lock +18 -10
- data/spec/fixtures/lockfiles/empty.lock +3 -0
- data/spec/unit/berkshelf/berksfile_spec.rb +31 -74
- data/spec/unit/berkshelf/cookbook_generator_spec.rb +4 -0
- data/spec/unit/berkshelf/installer_spec.rb +4 -7
- data/spec/unit/berkshelf/lockfile_parser_spec.rb +124 -0
- data/spec/unit/berkshelf/lockfile_spec.rb +140 -163
- metadata +11 -6
- data/features/licenses.feature +0 -79
- data/features/step_definitions/lockfile_steps.rb +0 -57
data/lib/berkshelf/berksfile.rb
CHANGED
@@ -3,22 +3,23 @@ require_relative "packager"
|
|
3
3
|
module Berkshelf
|
4
4
|
class Berksfile
|
5
5
|
class << self
|
6
|
-
#
|
6
|
+
# Instantiate a Berksfile from the given options. This method is used
|
7
|
+
# heavily by the CLI to reduce duplication.
|
7
8
|
#
|
8
|
-
# @
|
9
|
-
def
|
10
|
-
|
9
|
+
# @param (see Berksfile#initialize)
|
10
|
+
def from_options(options = {})
|
11
|
+
from_file(options[:berksfile], options.slice(:except, :only))
|
11
12
|
end
|
12
13
|
|
13
14
|
# @param [#to_s] file
|
14
15
|
# a path on disk to a Berksfile to instantiate from
|
15
16
|
#
|
16
17
|
# @return [Berksfile]
|
17
|
-
def from_file(file)
|
18
|
+
def from_file(file, options = {})
|
18
19
|
raise BerksfileNotFound.new(file) unless File.exist?(file)
|
19
20
|
|
20
21
|
begin
|
21
|
-
new(file).dsl_eval_file(file)
|
22
|
+
new(file, options).dsl_eval_file(file)
|
22
23
|
rescue => ex
|
23
24
|
raise BerksfileReadError.new(ex)
|
24
25
|
end
|
@@ -38,18 +39,37 @@ module Berkshelf
|
|
38
39
|
expose_method :cookbook
|
39
40
|
expose_method :group
|
40
41
|
|
41
|
-
@@active_group = nil
|
42
|
-
|
43
42
|
# @return [String]
|
44
43
|
# The path on disk to the file representing this instance of Berksfile
|
45
44
|
attr_reader :filepath
|
46
45
|
|
46
|
+
# Create a new Berksfile object.
|
47
|
+
#
|
47
48
|
# @param [String] path
|
48
49
|
# path on disk to the file containing the contents of this Berksfile
|
49
|
-
|
50
|
+
#
|
51
|
+
# @option options [Symbol, Array<String>] :except
|
52
|
+
# Group(s) to exclude which will cause any dependencies marked as a member of the
|
53
|
+
# group to not be installed
|
54
|
+
# @option options [Symbol, Array<String>] :only
|
55
|
+
# Group(s) to include which will cause any dependencies marked as a member of the
|
56
|
+
# group to be installed and all others to be ignored
|
57
|
+
def initialize(path, options = {})
|
50
58
|
@filepath = path
|
51
59
|
@dependencies = Hash.new
|
52
|
-
@sources =
|
60
|
+
@sources = Hash.new
|
61
|
+
|
62
|
+
if options[:except] && options[:only]
|
63
|
+
raise Berkshelf::ArgumentError, 'Cannot specify both :except and :only!'
|
64
|
+
elsif options[:except]
|
65
|
+
except = Array(options[:except]).collect(&:to_sym)
|
66
|
+
@filter = ->(dependency) { (except & dependency.groups).empty? }
|
67
|
+
elsif options[:only]
|
68
|
+
only = Array(options[:only]).collect(&:to_sym)
|
69
|
+
@filter = ->(dependency) { !(only & dependency.groups).empty? }
|
70
|
+
else
|
71
|
+
@filter = ->(dependency) { true }
|
72
|
+
end
|
53
73
|
end
|
54
74
|
|
55
75
|
# Add a cookbook dependency to the Berksfile to be retrieved and have its dependencies recursively retrieved
|
@@ -96,17 +116,17 @@ module Berkshelf
|
|
96
116
|
options[:path] &&= File.expand_path(options[:path], File.dirname(filepath))
|
97
117
|
options[:group] = Array(options[:group])
|
98
118
|
|
99
|
-
if
|
100
|
-
options[:group] +=
|
119
|
+
if @active_group
|
120
|
+
options[:group] += @active_group
|
101
121
|
end
|
102
122
|
|
103
123
|
add_dependency(name, constraint, options)
|
104
124
|
end
|
105
125
|
|
106
126
|
def group(*args)
|
107
|
-
|
127
|
+
@active_group = args
|
108
128
|
yield
|
109
|
-
|
129
|
+
@active_group = nil
|
110
130
|
end
|
111
131
|
|
112
132
|
# Use a Cookbook metadata file to determine additional cookbook dependencies to retrieve. All
|
@@ -143,13 +163,22 @@ module Berkshelf
|
|
143
163
|
#
|
144
164
|
# @return [Array<Berkshelf::Source>]
|
145
165
|
def source(api_url)
|
146
|
-
|
147
|
-
@sources.push(new_source) unless @sources.include?(new_source)
|
166
|
+
@sources[api_url] = Source.new(api_url)
|
148
167
|
end
|
149
168
|
|
150
169
|
# @return [Array<Berkshelf::Source>]
|
151
170
|
def sources
|
152
|
-
@sources.empty?
|
171
|
+
if @sources.empty?
|
172
|
+
raise NoAPISourcesDefined
|
173
|
+
else
|
174
|
+
@sources.values
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# @param [Dependency] dependency
|
179
|
+
# the dependency to find the source for
|
180
|
+
def source_for(name, version)
|
181
|
+
sources.find { |source| source.cookbook(name, version) }
|
153
182
|
end
|
154
183
|
|
155
184
|
# @todo remove in Berkshelf 4.0
|
@@ -232,36 +261,27 @@ module Berkshelf
|
|
232
261
|
@dependencies.has_key?(dependency.to_s)
|
233
262
|
end
|
234
263
|
|
235
|
-
|
236
|
-
# Group(s) to exclude which will cause any dependencies marked as a member of the
|
237
|
-
# group to not be installed
|
238
|
-
# @option options [Symbol, Array] :only
|
239
|
-
# Group(s) to include which will cause any dependencies marked as a member of the
|
240
|
-
# group to be installed and all others to be ignored
|
241
|
-
# @option cookbooks [String, Array] :cookbooks
|
242
|
-
# Names of the cookbooks to retrieve dependencies for
|
243
|
-
#
|
264
|
+
|
244
265
|
# @return [Array<Berkshelf::Dependency>]
|
245
|
-
def dependencies
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
end
|
266
|
+
def dependencies
|
267
|
+
@dependencies.values.sort.select(&@filter)
|
268
|
+
end
|
269
|
+
|
270
|
+
#
|
271
|
+
# Behaves the same as {Berksfile#dependencies}, but this method returns an
|
272
|
+
# array of CachedCookbook objects instead of dependency objects. This method
|
273
|
+
# relies on the {Berksfile#retrieve_locked} method to load the proper
|
274
|
+
# cached cookbook from the Berksfile + lockfile combination.
|
275
|
+
#
|
276
|
+
# @see [Berksfile#dependencies]
|
277
|
+
# for a description of the +options+ hash
|
278
|
+
# @see [Berksfile#retrieve_locked]
|
279
|
+
# for a list of possible exceptions that might be raised and why
|
280
|
+
#
|
281
|
+
# @return [Array<CachedCookbook>]
|
282
|
+
#
|
283
|
+
def cookbooks
|
284
|
+
dependencies.map { |dependency| retrieve_locked(dependency) }
|
265
285
|
end
|
266
286
|
|
267
287
|
# Find a dependency defined in this berksfile by name.
|
@@ -336,36 +356,30 @@ module Berkshelf
|
|
336
356
|
#
|
337
357
|
# 3. Write out a new lockfile.
|
338
358
|
#
|
339
|
-
# @option options [Symbol, Array] :except
|
340
|
-
# Group(s) to exclude which will cause any dependencies marked as a member of the
|
341
|
-
# group to not be installed
|
342
|
-
# @option options [Symbol, Array] :only
|
343
|
-
# Group(s) to include which will cause any dependencies marked as a member of the
|
344
|
-
# group to be installed and all others to be ignored
|
345
|
-
# @option cookbooks [String, Array] :cookbooks
|
346
|
-
# Names of the cookbooks to retrieve dependencies for
|
347
|
-
#
|
348
359
|
# @raise [Berkshelf::OutdatedDependency]
|
349
360
|
# if the lockfile constraints do not satisfy the Berksfile constraints
|
350
361
|
#
|
351
362
|
# @return [Array<Berkshelf::CachedCookbook>]
|
352
|
-
def install
|
353
|
-
Installer.new(self).run
|
363
|
+
def install
|
364
|
+
Installer.new(self).run
|
354
365
|
end
|
355
366
|
|
356
|
-
#
|
357
|
-
#
|
358
|
-
#
|
359
|
-
# @option options [Symbol, Array] :only
|
360
|
-
# Group(s) to include which will cause any dependencies marked as a member of the
|
361
|
-
# group to be installed and all others to be ignored
|
362
|
-
# @option cookbooks [String, Array] :cookbooks
|
367
|
+
# Update the given set of dependencies (or all if no names are given).
|
368
|
+
#
|
369
|
+
# @option options [String, Array<String>] :cookbooks
|
363
370
|
# Names of the cookbooks to retrieve dependencies for
|
364
|
-
def update(
|
365
|
-
validate_cookbook_names!(
|
371
|
+
def update(*names)
|
372
|
+
validate_cookbook_names!(names)
|
373
|
+
|
374
|
+
# Calculate the list of cookbooks to unlock
|
375
|
+
if names.empty?
|
376
|
+
list = dependencies
|
377
|
+
else
|
378
|
+
list = dependencies.select { |dependency| names.include?(dependency.name) }
|
379
|
+
end
|
366
380
|
|
367
381
|
# Unlock any/all specified cookbooks
|
368
|
-
|
382
|
+
list.each { |dependency| lockfile.unlock(dependency) }
|
369
383
|
|
370
384
|
# NOTE: We intentionally do NOT pass options to the installer
|
371
385
|
self.install
|
@@ -386,22 +400,7 @@ module Berkshelf
|
|
386
400
|
# @return [CachedCookbook]
|
387
401
|
# the CachedCookbook that corresponds to the given name parameter
|
388
402
|
def retrieve_locked(dependency)
|
389
|
-
|
390
|
-
|
391
|
-
unless locked
|
392
|
-
raise CookbookNotFound, "Could not find cookbook '#{dependency.to_s}'."\
|
393
|
-
" Try running `berks install` to download and install the missing"\
|
394
|
-
" dependencies."
|
395
|
-
end
|
396
|
-
|
397
|
-
unless locked.downloaded?
|
398
|
-
raise CookbookNotFound, "Could not find cookbook '#{locked.to_s}'."\
|
399
|
-
" Try running `berks install` to download and install the missing"\
|
400
|
-
" dependencies."
|
401
|
-
end
|
402
|
-
|
403
|
-
@dependencies[dependency.name] = locked
|
404
|
-
locked.cached_cookbook
|
403
|
+
lockfile.retrieve(dependency)
|
405
404
|
end
|
406
405
|
|
407
406
|
# The cached cookbooks installed by this Berksfile.
|
@@ -414,52 +413,54 @@ module Berkshelf
|
|
414
413
|
# @return [Hash<Berkshelf::Dependency, Berkshelf::CachedCookbook>]
|
415
414
|
# the list of dependencies as keys and the cached cookbook as the value
|
416
415
|
def list
|
417
|
-
|
416
|
+
validate_lockfile_present!
|
417
|
+
validate_lockfile_trusted!
|
418
|
+
validate_dependencies_installed!
|
419
|
+
|
420
|
+
lockfile.graph.locks.values
|
418
421
|
end
|
419
422
|
|
420
|
-
# List of all the cookbooks which have a newer version found at a source
|
421
|
-
# the constraints of your dependencies
|
422
|
-
#
|
423
|
-
# @option options [Symbol, Array] :except
|
424
|
-
# Group(s) to exclude which will cause any dependencies marked as a member of the
|
425
|
-
# group to not be installed
|
426
|
-
# @option options [Symbol, Array] :only
|
427
|
-
# Group(s) to include which will cause any dependencies marked as a member of the
|
428
|
-
# group to be installed and all others to be ignored
|
429
|
-
# @option cookbooks [String, Array] :cookbooks
|
430
|
-
# Whitelist of cookbooks to to check for updated versions for
|
423
|
+
# List of all the cookbooks which have a newer version found at a source
|
424
|
+
# that satisfies the constraints of your dependencies.
|
431
425
|
#
|
432
426
|
# @return [Hash]
|
433
|
-
# a hash of cached cookbooks and their latest version. An empty hash is
|
434
|
-
# if there are no newer cookbooks for any of your dependencies
|
427
|
+
# a hash of cached cookbooks and their latest version. An empty hash is
|
428
|
+
# returned if there are no newer cookbooks for any of your dependencies
|
435
429
|
#
|
436
430
|
# @example
|
437
431
|
# berksfile.outdated #=> {
|
438
432
|
# #<CachedCookbook name="artifact"> => "0.11.2"
|
439
433
|
# }
|
440
|
-
def outdated(
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
434
|
+
def outdated(*names)
|
435
|
+
validate_lockfile_present!
|
436
|
+
validate_lockfile_trusted!
|
437
|
+
validate_dependencies_installed!
|
438
|
+
validate_cookbook_names!(names)
|
439
|
+
|
440
|
+
# Calculate the list of cookbooks to unlock
|
441
|
+
if names.empty?
|
442
|
+
list = dependencies
|
443
|
+
else
|
444
|
+
list = dependencies.select { |dependency| names.include?(dependency.name) }
|
445
|
+
end
|
447
446
|
|
447
|
+
lockfile.graph.locks.inject({}) do |hash, (name, dependency)|
|
448
448
|
sources.each do |source|
|
449
|
-
cookbooks = source.versions(
|
449
|
+
cookbooks = source.versions(name)
|
450
450
|
|
451
451
|
latest = cookbooks.select do |cookbook|
|
452
452
|
dependency.version_constraint.satisfies?(cookbook.version) &&
|
453
|
-
cookbook.version !=
|
453
|
+
cookbook.version.to_s != dependency.locked_version.to_s
|
454
454
|
end.sort_by { |cookbook| cookbook.version }.last
|
455
455
|
|
456
456
|
unless latest.nil?
|
457
|
-
|
457
|
+
hash[name] ||= {}
|
458
|
+
hash[name][source.uri.to_s] = latest
|
458
459
|
end
|
459
460
|
end
|
460
|
-
end
|
461
461
|
|
462
|
-
|
462
|
+
hash
|
463
|
+
end
|
463
464
|
end
|
464
465
|
|
465
466
|
# Upload the cookbooks installed by this Berksfile
|
@@ -469,12 +470,6 @@ module Berkshelf
|
|
469
470
|
# target Chef Server
|
470
471
|
# @option options [Boolean] :freeze (true)
|
471
472
|
# Freeze the uploaded Cookbook on the Chef Server so that it cannot be overwritten
|
472
|
-
# @option options [Symbol, Array] :except
|
473
|
-
# Group(s) to exclude which will cause any dependencies marked as a member of the
|
474
|
-
# group to not be installed
|
475
|
-
# @option options [Symbol, Array] :only
|
476
|
-
# Group(s) to include which will cause any dependencies marked as a member of the
|
477
|
-
# group to be installed and all others to be ignored
|
478
473
|
# @option options [String, Array] :cookbooks
|
479
474
|
# Names of the cookbooks to retrieve dependencies for
|
480
475
|
# @option options [Hash] :ssl_verify (true)
|
@@ -506,9 +501,9 @@ module Berkshelf
|
|
506
501
|
validate: true
|
507
502
|
}.merge(options)
|
508
503
|
|
509
|
-
validate_cookbook_names!(options)
|
504
|
+
validate_cookbook_names!(options[:cookbooks])
|
510
505
|
|
511
|
-
cached_cookbooks = install
|
506
|
+
cached_cookbooks = install
|
512
507
|
cached_cookbooks = filter_to_upload(cached_cookbooks, options[:cookbooks]) if options[:cookbooks]
|
513
508
|
do_upload(cached_cookbooks, options)
|
514
509
|
end
|
@@ -520,24 +515,17 @@ module Berkshelf
|
|
520
515
|
# @param [String] path
|
521
516
|
# the path where the tarball will be created
|
522
517
|
#
|
523
|
-
# @option options [Symbol, Array] :except
|
524
|
-
# Group(s) to exclude which will cause any dependencies marked as a member of the
|
525
|
-
# group to not be installed
|
526
|
-
# @option options [Symbol, Array] :only
|
527
|
-
# Group(s) to include which will cause any dependencies marked as a member of the
|
528
|
-
# group to be installed and all others to be ignored
|
529
|
-
#
|
530
518
|
# @raise [Berkshelf::PackageError]
|
531
519
|
#
|
532
520
|
# @return [String]
|
533
521
|
# the path to the package
|
534
|
-
def package(path
|
522
|
+
def package(path)
|
535
523
|
packager = Packager.new(path)
|
536
524
|
packager.validate!
|
537
525
|
|
538
526
|
outdir = Dir.mktmpdir do |temp_dir|
|
539
527
|
source = Berkshelf.ui.mute do
|
540
|
-
vendor(File.join(temp_dir,
|
528
|
+
vendor(File.join(temp_dir, 'cookbooks'))
|
541
529
|
end
|
542
530
|
packager.run(source)
|
543
531
|
end
|
@@ -552,16 +540,9 @@ module Berkshelf
|
|
552
540
|
# @param [String] destination
|
553
541
|
# filepath to vendor cookbooks to
|
554
542
|
#
|
555
|
-
# @option options [Symbol, Array] :except
|
556
|
-
# Group(s) to exclude which will cause any dependencies marked as a member of the
|
557
|
-
# group to not be installed
|
558
|
-
# @option options [Symbol, Array] :only
|
559
|
-
# Group(s) to include which will cause any dependencies marked as a member of the
|
560
|
-
# group to be installed and all others to be ignored
|
561
|
-
#
|
562
543
|
# @return [String, nil]
|
563
544
|
# the expanded path cookbooks were vendored to or nil if nothing was vendored
|
564
|
-
def vendor(destination
|
545
|
+
def vendor(destination)
|
565
546
|
destination = File.expand_path(destination)
|
566
547
|
|
567
548
|
if Dir.exist?(destination)
|
@@ -569,9 +550,12 @@ module Berkshelf
|
|
569
550
|
"different filepath."
|
570
551
|
end
|
571
552
|
|
553
|
+
# Ensure the parent directory exists, in case a nested path was given
|
554
|
+
FileUtils.mkdir_p(File.expand_path(File.join(destination, '..')))
|
555
|
+
|
572
556
|
scratch = Berkshelf.mktmpdir
|
573
557
|
chefignore = nil
|
574
|
-
cached_cookbooks = install
|
558
|
+
cached_cookbooks = install
|
575
559
|
|
576
560
|
return nil if cached_cookbooks.empty?
|
577
561
|
|
@@ -686,15 +670,58 @@ module Berkshelf
|
|
686
670
|
cookbooks
|
687
671
|
end
|
688
672
|
|
673
|
+
# Ensure the lockfile is present on disk.
|
674
|
+
#
|
675
|
+
# @raise [LockfileNotFound]
|
676
|
+
# if the lockfile does not exist on disk
|
677
|
+
#
|
678
|
+
# @return [true]
|
679
|
+
def validate_lockfile_present!
|
680
|
+
raise LockfileNotFound unless lockfile.present?
|
681
|
+
true
|
682
|
+
end
|
683
|
+
|
684
|
+
# Ensure that all dependencies defined in the Berksfile exist in this
|
685
|
+
# lockfile.
|
686
|
+
#
|
687
|
+
# @raise [LockfileOutOfSync]
|
688
|
+
# if there are dependencies specified in the Berksfile which do not
|
689
|
+
# exist (or are not satisifed by) the lockfile
|
690
|
+
#
|
691
|
+
# @return [true]
|
692
|
+
def validate_lockfile_trusted!
|
693
|
+
raise LockfileOutOfSync unless lockfile.trusted?
|
694
|
+
true
|
695
|
+
end
|
696
|
+
|
697
|
+
# Ensure that all dependencies in the lockfile are installed on this
|
698
|
+
# system. You should validate that the lockfile can be trusted before
|
699
|
+
# using this method.
|
700
|
+
#
|
701
|
+
# @raise [DependencyNotInstalled]
|
702
|
+
# if the dependency in the lockfile is not in the Berkshelf shelf on
|
703
|
+
# this system
|
704
|
+
#
|
705
|
+
# @return [true]
|
706
|
+
def validate_dependencies_installed!
|
707
|
+
lockfile.graph.locks.each do |_, dependency|
|
708
|
+
unless dependency.downloaded?
|
709
|
+
raise DependencyNotInstalled.new(dependency)
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
true
|
714
|
+
end
|
715
|
+
|
689
716
|
# Determine if any cookbooks were specified that aren't in our shelf.
|
690
717
|
#
|
691
|
-
# @
|
692
|
-
# a list of
|
718
|
+
# @param [Array<String>] names
|
719
|
+
# a list of cookbook names
|
693
720
|
#
|
694
721
|
# @raise [Berkshelf::DependencyNotFound]
|
695
722
|
# if a cookbook name is given that does not exist
|
696
|
-
def validate_cookbook_names!(
|
697
|
-
missing =
|
723
|
+
def validate_cookbook_names!(names)
|
724
|
+
missing = names - dependencies.map(&:name)
|
698
725
|
|
699
726
|
unless missing.empty?
|
700
727
|
raise Berkshelf::DependencyNotFound.new(missing)
|