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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/features/berksfile.feature +2 -0
  3. data/features/commands/apply.feature +1 -1
  4. data/features/commands/contingent.feature +5 -3
  5. data/features/commands/install.feature +40 -40
  6. data/features/commands/list.feature +42 -20
  7. data/features/commands/outdated.feature +60 -16
  8. data/features/commands/show.feature +51 -8
  9. data/features/commands/update.feature +43 -15
  10. data/features/commands/upload.feature +4 -1
  11. data/features/commands/vendor.feature +27 -0
  12. data/features/json_formatter.feature +20 -8
  13. data/features/lockfile.feature +192 -71
  14. data/generator_files/CHANGELOG.md.erb +5 -0
  15. data/lib/berkshelf/berksfile.rb +166 -139
  16. data/lib/berkshelf/cli.rb +33 -30
  17. data/lib/berkshelf/cookbook_generator.rb +1 -0
  18. data/lib/berkshelf/dependency.rb +64 -14
  19. data/lib/berkshelf/downloader.rb +7 -10
  20. data/lib/berkshelf/errors.rb +59 -11
  21. data/lib/berkshelf/formatters/human_readable.rb +23 -36
  22. data/lib/berkshelf/formatters/json.rb +25 -29
  23. data/lib/berkshelf/installer.rb +111 -122
  24. data/lib/berkshelf/locations/git_location.rb +22 -9
  25. data/lib/berkshelf/locations/mercurial_location.rb +20 -5
  26. data/lib/berkshelf/locations/path_location.rb +22 -7
  27. data/lib/berkshelf/lockfile.rb +435 -203
  28. data/lib/berkshelf/resolver.rb +4 -2
  29. data/lib/berkshelf/source.rb +10 -1
  30. data/lib/berkshelf/version.rb +1 -1
  31. data/spec/fixtures/cookbooks/example_cookbook/Berksfile.lock +3 -4
  32. data/spec/fixtures/lockfiles/2.0.lock +17 -0
  33. data/spec/fixtures/lockfiles/blank.lock +0 -0
  34. data/spec/fixtures/lockfiles/default.lock +18 -10
  35. data/spec/fixtures/lockfiles/empty.lock +3 -0
  36. data/spec/unit/berkshelf/berksfile_spec.rb +31 -74
  37. data/spec/unit/berkshelf/cookbook_generator_spec.rb +4 -0
  38. data/spec/unit/berkshelf/installer_spec.rb +4 -7
  39. data/spec/unit/berkshelf/lockfile_parser_spec.rb +124 -0
  40. data/spec/unit/berkshelf/lockfile_spec.rb +140 -163
  41. metadata +11 -6
  42. data/features/licenses.feature +0 -79
  43. data/features/step_definitions/lockfile_steps.rb +0 -57
@@ -139,8 +139,8 @@ module Berkshelf
139
139
  exit(1)
140
140
  end
141
141
 
142
- berksfile = Berkshelf::Berksfile.from_file(options[:berksfile])
143
- berksfile.install(options)
142
+ berksfile = Berksfile.from_options(options)
143
+ berksfile.install
144
144
  end
145
145
 
146
146
  method_option :berksfile,
@@ -159,13 +159,8 @@ module Berkshelf
159
159
  aliases: '-o'
160
160
  desc 'update [COOKBOOKS]', 'Update the cookbooks (and dependencies) specified in the Berksfile'
161
161
  def update(*cookbook_names)
162
- berksfile = Berksfile.from_file(options[:berksfile])
163
-
164
- update_options = {
165
- cookbooks: cookbook_names
166
- }.merge(options).symbolize_keys
167
-
168
- berksfile.update(update_options)
162
+ berksfile = Berksfile.from_options(options)
163
+ berksfile.update(*cookbook_names)
169
164
  end
170
165
 
171
166
  method_option :berksfile,
@@ -205,7 +200,7 @@ module Berkshelf
205
200
  desc: 'Halt uploading and exit if the Chef Server has a frozen version of the cookbook(s).'
206
201
  desc 'upload [COOKBOOKS]', 'Upload the cookbook specified in the Berksfile to the Chef Server'
207
202
  def upload(*cookbook_names)
208
- berksfile = Berkshelf::Berksfile.from_file(options[:berksfile])
203
+ berksfile = Berksfile.from_options(options)
209
204
 
210
205
  options[:cookbooks] = cookbook_names
211
206
  options[:freeze] = !options[:no_freeze]
@@ -230,7 +225,7 @@ module Berkshelf
230
225
  raise LockfileNotFound, "No lockfile found at #{options[:lockfile]}"
231
226
  end
232
227
 
233
- lockfile = Berkshelf::Lockfile.from_file(options[:lockfile])
228
+ lockfile = Lockfile.from_file(options[:lockfile])
234
229
  lock_options = Hash[options].symbolize_keys
235
230
 
236
231
  lockfile.apply(environment_name, lock_options)
@@ -251,10 +246,9 @@ module Berkshelf
251
246
  desc: 'Only cookbooks that are in these groups.',
252
247
  aliases: '-o'
253
248
  desc 'outdated [COOKBOOKS]', 'List dependencies that have new versions available that satisfy their constraints'
254
- def outdated(*cookbook_names)
255
- berksfile = Berkshelf::Berksfile.from_file(options[:berksfile])
256
- options[:cookbooks] = cookbook_names
257
- outdated = berksfile.outdated(options.symbolize_keys)
249
+ def outdated(*names)
250
+ berksfile = Berksfile.from_options(options)
251
+ outdated = berksfile.outdated(*names)
258
252
 
259
253
  if outdated.empty?
260
254
  Berkshelf.formatter.msg "All cookbooks up to date!"
@@ -281,9 +275,17 @@ module Berkshelf
281
275
  desc: 'Path to a Berksfile to operate off of.',
282
276
  aliases: '-b',
283
277
  banner: 'PATH'
284
- desc 'list', 'List all cookbooks and their dependencies specified by your Berksfile'
278
+ method_option :except,
279
+ type: :array,
280
+ desc: 'Exclude cookbooks that are in these groups.',
281
+ aliases: '-e'
282
+ method_option :only,
283
+ type: :array,
284
+ desc: 'Only cookbooks that are in these groups.',
285
+ aliases: '-o'
286
+ desc 'list', 'List cookbooks and their dependencies specified by your Berksfile'
285
287
  def list
286
- berksfile = Berksfile.from_file(options[:berksfile])
288
+ berksfile = Berksfile.from_options(options)
287
289
  Berkshelf.formatter.list(berksfile.list)
288
290
  end
289
291
 
@@ -295,8 +297,8 @@ module Berkshelf
295
297
  banner: "PATH"
296
298
  desc "show [COOKBOOK]", "Display name, author, copyright, and dependency information about a cookbook"
297
299
  def show(name)
298
- berksfile = Berksfile.from_file(options[:berksfile])
299
- cookbook = berksfile.retrieve_locked(berksfile.find!(name))
300
+ berksfile = Berksfile.from_options(options)
301
+ cookbook = berksfile.retrieve_locked(name)
300
302
  Berkshelf.formatter.show(cookbook)
301
303
  end
302
304
 
@@ -306,16 +308,17 @@ module Berkshelf
306
308
  desc: 'Path to a Berksfile to operate off of.',
307
309
  aliases: '-b',
308
310
  banner: 'PATH'
309
- desc 'contingent COOKBOOK', 'List all cookbooks that depend on the given cookbook'
311
+ desc 'contingent COOKBOOK', 'List all cookbooks that depend on the given cookbook in your Berksfile'
310
312
  def contingent(name)
311
- berksfile = Berksfile.from_file(options[:berksfile])
312
- dependencies = Berkshelf.ui.mute { berksfile.install }.sort
313
- dependencies = dependencies.select { |cookbook| cookbook.dependencies.include?(name) }
313
+ berksfile = Berksfile.from_options(options)
314
+ dependencies = berksfile.cookbooks.select do |cookbook|
315
+ cookbook.dependencies.include?(name)
316
+ end
314
317
 
315
318
  if dependencies.empty?
316
- Berkshelf.formatter.msg "There are no cookbooks contingent upon '#{name}' defined in this Berksfile"
319
+ Berkshelf.formatter.msg "There are no cookbooks in this Berksfile contingent upon '#{name}'."
317
320
  else
318
- Berkshelf.formatter.msg "Cookbooks in this Berksfile contingent upon #{name}:"
321
+ Berkshelf.formatter.msg "Cookbooks in this Berksfile contingent upon '#{name}':"
319
322
  print_list(dependencies)
320
323
  end
321
324
  end
@@ -342,8 +345,8 @@ module Berkshelf
342
345
  path = File.expand_path(path)
343
346
  end
344
347
 
345
- berksfile = Berkshelf::Berksfile.from_file(options[:berksfile])
346
- berksfile.package(path, options)
348
+ berksfile = Berksfile.from_options(options)
349
+ berksfile.package(path)
347
350
  end
348
351
 
349
352
  method_option :except,
@@ -362,8 +365,8 @@ module Berkshelf
362
365
  banner: 'PATH'
363
366
  desc "vendor [PATH]", "Vendor the cookbooks specified by the Berksfile into a directory"
364
367
  def vendor(path = File.join(Dir.pwd, "berks-cookbooks"))
365
- berksfile = Berkshelf::Berksfile.from_file(options[:berksfile])
366
- berksfile.vendor(path, options)
368
+ berksfile = Berkshelf::Berksfile.from_options(options)
369
+ berksfile.vendor(path)
367
370
  end
368
371
 
369
372
  desc 'version', 'Display version and copyright information'
@@ -387,7 +390,7 @@ module Berkshelf
387
390
  # @param [Array<CachedCookbook>] cookbooks
388
391
  #
389
392
  def print_list(cookbooks)
390
- Array(cookbooks).each do |cookbook|
393
+ Array(cookbooks).sort.each do |cookbook|
391
394
  Berkshelf.formatter.msg " * #{cookbook.cookbook_name} (#{cookbook.version})"
392
395
  end
393
396
  end
@@ -68,6 +68,7 @@ module Berkshelf
68
68
  template 'metadata.rb.erb', target.join('metadata.rb')
69
69
  template license_file, target.join('LICENSE')
70
70
  template 'README.md.erb', target.join('README.md')
71
+ template 'CHANGELOG.md.erb', target.join('CHANGELOG.md')
71
72
 
72
73
  Berkshelf::InitGenerator.new([target], options.merge(default_options)).invoke_all
73
74
  end
@@ -63,6 +63,21 @@ module Berkshelf
63
63
 
64
64
  true
65
65
  end
66
+
67
+ # Returns the name of this cookbook (because it's the key in hash tables).
68
+ #
69
+ # @param [Dependency, #to_s] dependency
70
+ # the dependency to find the name from
71
+ #
72
+ # @return [String]
73
+ # the name of the cookbook
74
+ def name(dependency)
75
+ if dependency.is_a?(Dependency)
76
+ dependency.name.to_s
77
+ else
78
+ dependency.to_s
79
+ end
80
+ end
66
81
  end
67
82
 
68
83
  DEFAULT_CONSTRAINT = '>= 0.0.0'.freeze
@@ -77,9 +92,11 @@ module Berkshelf
77
92
  # @return [Berkshelf::Location]
78
93
  attr_reader :location
79
94
  # @return [Solve::Version]
80
- attr_accessor :locked_version
95
+ attr_reader :locked_version
81
96
  # @return [Solve::Constraint]
82
- attr_accessor :version_constraint
97
+ attr_reader :version_constraint
98
+ # @return [Source]
99
+ attr_accessor :source
83
100
  # @return [Berkshelf::CachedCookbook]
84
101
  attr_accessor :cached_cookbook
85
102
 
@@ -127,6 +144,22 @@ module Berkshelf
127
144
  !!@metadata
128
145
  end
129
146
 
147
+ # Set this dependency's locked version.
148
+ #
149
+ # @param [#to_s] version
150
+ # the version to set
151
+ def locked_version=(version)
152
+ @locked_version = Solve::Version.new(version.to_s)
153
+ end
154
+
155
+ # Set this dependency's constraint(s).
156
+ #
157
+ # @param [#to_s] constraint
158
+ # the constraint to set
159
+ def version_constraint=(constraint)
160
+ @version_constraint = Solve::Constraint.new(constraint.to_s)
161
+ end
162
+
130
163
  def add_group(*local_groups)
131
164
  local_groups = local_groups.first if local_groups.first.is_a?(Array)
132
165
 
@@ -136,31 +169,37 @@ module Berkshelf
136
169
  end
137
170
  end
138
171
 
139
- # @return [Berkshelf::CachedCookbook]
140
- def cached_cookbook
141
- @cached_cookbook ||= if location
142
- download
172
+ # The cached (downloaded) cookbook for this dependency.
173
+ #
174
+ # @return [CachedCookbook, nil]
175
+ def download
176
+ @cached_cookbook = if location
177
+ location.download
143
178
  else
144
179
  if locked_version
145
180
  CookbookStore.instance.cookbook(name, locked_version)
146
181
  else
147
- Berkshelf::CookbookStore.instance.satisfy(name, version_constraint)
182
+ CookbookStore.instance.satisfy(name, version_constraint)
148
183
  end
149
184
  end
150
- end
151
-
152
- # @return [Berkshelf::CachedCookbook]
153
- def download
154
- @cached_cookbook = location.download
155
185
 
156
186
  if scm_location? || path_location?
157
- @locked_version = Solve::Version.new(@cached_cookbook.version)
158
- @version_constraint = Solve::Constraint.new(@cached_cookbook.version)
187
+ self.locked_version = @cached_cookbook.version
188
+ self.version_constraint = @cached_cookbook.version
159
189
  end
160
190
 
161
191
  @cached_cookbook
162
192
  end
163
193
 
194
+ # Download the dependency. If this dependency is an SCM location or Path
195
+ # location, the constraints are also updated to correspond to the cookbook
196
+ # on disk.
197
+ #
198
+ # @return [CachedCookbook, nil]
199
+ def cached_cookbook
200
+ @cached_cookbook ||= download
201
+ end
202
+
164
203
  # Returns true if the dependency has already been downloaded. A dependency is downloaded when a
165
204
  # cached cookbook is present.
166
205
  #
@@ -227,6 +266,17 @@ module Berkshelf
227
266
  ].join(', ')
228
267
  end
229
268
 
269
+ def to_lock
270
+ out = if path_location? || scm_location? || version_constraint.to_s == '>= 0.0.0'
271
+ " #{name}\n"
272
+ else
273
+ " #{name} (#{version_constraint})\n"
274
+ end
275
+
276
+ out << location.to_lock if location
277
+ out
278
+ end
279
+
230
280
  def to_hash
231
281
  {}.tap do |h|
232
282
  h[:locked_version] = locked_version.to_s
@@ -1,7 +1,6 @@
1
1
  require 'net/http'
2
2
  require 'zlib'
3
3
  require 'archive/tar/minitar'
4
- require 'octokit'
5
4
 
6
5
  module Berkshelf
7
6
  class Downloader
@@ -30,17 +29,13 @@ module Berkshelf
30
29
  options = args.last.is_a?(Hash) ? args.pop : Hash.new
31
30
  dependency, version = args
32
31
 
33
- if dependency.is_a?(Berkshelf::Dependency)
34
- dependency.download
35
- else
36
- sources.each do |source|
37
- if result = try_download(source, dependency, version)
38
- return result
39
- end
32
+ sources.each do |source|
33
+ if result = try_download(source, dependency, version)
34
+ return result
40
35
  end
41
-
42
- raise CookbookNotFound, "#{dependency} (#{version}) not found in any sources"
43
36
  end
37
+
38
+ raise CookbookNotFound, "#{dependency} (#{version}) not found in any sources"
44
39
  end
45
40
 
46
41
  # @param [Berkshelf::Source] source
@@ -71,6 +66,8 @@ module Berkshelf
71
66
  Celluloid.logger = nil unless ENV["DEBUG_CELLULOID"]
72
67
  Ridley.open(credentials) { |r| r.cookbook.download(name, version) }
73
68
  when :github
69
+ require 'octokit' unless defined?(Octokit)
70
+
74
71
  tmp_dir = Dir.mktmpdir
75
72
  archive_path = File.join(tmp_dir, "#{name}-#{version}.tar.gz")
76
73
  unpack_dir = File.join(tmp_dir, "#{name}-#{version}")
@@ -272,9 +272,17 @@ module Berkshelf
272
272
  end
273
273
 
274
274
  def to_s
275
- list = @cookbooks.collect {|c| "'#{c}'" }
276
- "Could not find cookbook(s) #{list.join(', ')} in any of the configured" <<
277
- " dependencies. #{list.size == 1 ? 'Is it' : 'Are they' } in your Berksfile?"
275
+ list = @cookbooks.collect { |cookbook| "'#{cookbook}'" }.join(', ')
276
+
277
+ if @cookbooks.size == 1
278
+ "Could not find cookbook #{list}. Make sure it is in your " \
279
+ "Berksfile, then run `berks install` to download and install the " \
280
+ "missing dependencies."
281
+ else
282
+ "Could not find cookbooks #{list}. Make sure they are in your " \
283
+ "Berksfile, then run `berks install` to download and install the " \
284
+ "missing dependencies."
285
+ end
278
286
  end
279
287
  end
280
288
 
@@ -325,9 +333,9 @@ module Berkshelf
325
333
  # the locked dependency
326
334
  # @param [Berkshelf::Dependency] dependency
327
335
  # the dependency that is outdated
328
- def initialize(locked_dependency, dependency)
329
- @locked_dependency = locked_dependency
330
- @dependency = dependency
336
+ def initialize(locked, dependency)
337
+ @locked = locked
338
+ @dependency = dependency
331
339
  end
332
340
 
333
341
  def to_s
@@ -335,7 +343,7 @@ module Berkshelf
335
343
  " In Berksfile:\n" +
336
344
  " #{@dependency.name} (#{@dependency.version_constraint})\n\n" +
337
345
  " In Berksfile.lock:\n" +
338
- " #{@locked_dependency.name} (#{@locked_dependency.locked_version})\n\n" +
346
+ " #{@locked.name} (#{@locked.version})\n\n" +
339
347
  "Try running `berks update #{@dependency.name}`, which will try to find '#{@dependency.name}' matching " +
340
348
  "'#{@dependency.version_constraint}'."
341
349
  end
@@ -459,13 +467,13 @@ module Berkshelf
459
467
  # the path to the Lockfile
460
468
  # @param [~Exception] original
461
469
  # the original exception class
462
- def initialize(lockfile, original)
463
- @lockfile = Pathname.new(lockfile.to_s).basename.to_s
470
+ def initialize(original)
464
471
  @original = original
465
472
  end
466
473
 
467
474
  def to_s
468
- "Error reading the Berkshelf lockfile `#{@lockfile}` (#{@original.class})"
475
+ "Error reading the Berkshelf lockfile:\n\n" \
476
+ " #{@original.class}: #{@original.message}"
469
477
  end
470
478
  end
471
479
 
@@ -487,7 +495,13 @@ module Berkshelf
487
495
 
488
496
  class DuplicateDemand < BerkshelfError; status_code(138); end
489
497
  class VendorError < BerkshelfError; status_code(139); end
490
- class LockfileNotFound < BerkshelfError; status_code(140); end
498
+ class LockfileNotFound < BerkshelfError
499
+ status_code(140)
500
+
501
+ def to_s
502
+ 'Lockfile not found! Run `berks install` to create the lockfile.'
503
+ end
504
+ end
491
505
 
492
506
  class NotACookbook < BerkshelfError
493
507
  status_code(141)
@@ -505,4 +519,38 @@ module Berkshelf
505
519
 
506
520
  class InvalidLockFile < BerkshelfError; status_code(142); end
507
521
  class PackageError < BerkshelfError; status_code(143); end
522
+
523
+ class LockfileOutOfSync < BerkshelfError
524
+ status_code(144)
525
+
526
+ def to_s
527
+ 'The lockfile is out of sync! Run `berks install` to sync the lockfile.'
528
+ end
529
+ end
530
+
531
+ class DependencyNotInstalled < BerkshelfError
532
+ status_code(145)
533
+
534
+ def initialize(dependency)
535
+ name = dependency.name
536
+ version = dependency.locked_version
537
+
538
+ super "The cookbook '#{name} (#{version})' is not installed. Please " \
539
+ "run `berks install` to download and install the missing " \
540
+ "dependency."
541
+ end
542
+ end
543
+
544
+ class NoAPISourcesDefined < BerkshelfError
545
+ status_code(146)
546
+
547
+ def initialize
548
+ super "Your Berksfile does not define any API sources! You must define " \
549
+ "at least one source in order to download cookbooks. To add the " \
550
+ "default Berkshelf API server, add the following code to the top of " \
551
+ "your Berksfile:" \
552
+ "\n\n" \
553
+ " source 'https://api.berkshelf.com'"
554
+ end
555
+ end
508
556
  end
@@ -17,38 +17,27 @@ module Berkshelf
17
17
 
18
18
  # Output a Cookbook installation message using {Berkshelf.ui}
19
19
  #
20
- # @param [String] cookbook
21
- # @param [String] version
22
- # @option options [#to_s] :api_source
23
- # the berkshelf-api source url
24
- # @option options [#to_s] :location_path
25
- # endpoint location for the remote cookbook provided by the api server
26
- # @option options [#to_s] :location_type
27
- # type of the location provided by the api server for the remote cookbook
28
- def install(cookbook, version, options = {})
29
- info_message = "Installing #{cookbook} (#{version})"
30
-
31
- if options.has_key?(:api_source) && options.has_key?(:location_path) && options.has_key?(:location_type)
32
- api_source = options[:api_source].to_s
33
- location_path = options[:location_path].to_s
34
- location_type = options[:location_type].to_s
35
-
36
- unless api_source == Berkshelf::Berksfile::DEFAULT_API_URL
37
- info_message << " from [api: #{URI(api_source)}] ([#{location_type}] #{location_path})"
38
- end
20
+ # @param [Source] source
21
+ # the source the dependency is being downloaded from
22
+ # @param [RemoteCookbook] cookbook
23
+ # the cookbook to be downloaded
24
+ def install(source, cookbook)
25
+ message = "Installing #{cookbook.name} (#{cookbook.version})"
26
+
27
+ unless source.default?
28
+ message << " from #{source}"
29
+ message << " ([#{cookbook.location_type}] #{cookbook.location_path})"
39
30
  end
40
31
 
41
- Berkshelf.ui.info info_message
32
+ Berkshelf.ui.info(message)
42
33
  end
43
34
 
44
35
  # Output a Cookbook use message using {Berkshelf.ui}
45
36
  #
46
- # @param [String] cookbook
47
- # @param [String] version
48
- # @param [~Location] location
49
- def use(cookbook, version, location = nil)
50
- message = "Using #{cookbook} (#{version})"
51
- message += " #{location}" if location
37
+ # @param [Dependency] dependency
38
+ def use(dependency)
39
+ message = "Using #{dependency.name} (#{dependency.locked_version})"
40
+ message << " from #{dependency.location}" if dependency.location
52
41
  Berkshelf.ui.info message
53
42
  end
54
43
 
@@ -78,7 +67,7 @@ module Berkshelf
78
67
  hash.keys.each do |name|
79
68
  hash[name].each do |source, newest|
80
69
  string = " * #{newest.name} (#{newest.version})"
81
- unless Berksfile.default_sources.map { |s| s.uri.to_s }.include?(source)
70
+ unless source == Berksfile::DEFAULT_API_URL
82
71
  string << " [#{source}]"
83
72
  end
84
73
  Berkshelf.ui.info string
@@ -95,15 +84,13 @@ module Berkshelf
95
84
 
96
85
  # Output a list of cookbooks using {Berkshelf.ui}
97
86
  #
98
- # @param [Hash<Dependency, CachedCookbook>] list
99
- def list(list)
100
- if list.empty?
101
- Berkshelf.ui.info "There are no cookbooks installed by your Berksfile"
102
- else
103
- Berkshelf.ui.info "Cookbooks installed by your Berksfile:"
104
- list.each do |dependency, cookbook|
105
- Berkshelf.ui.info(" * #{cookbook.cookbook_name} (#{cookbook.version})")
106
- end
87
+ # @param [Array<Dependency>] list
88
+ def list(dependencies)
89
+ Berkshelf.ui.info "Cookbooks installed by your Berksfile:"
90
+ dependencies.each do |dependency|
91
+ out = " * #{dependency}"
92
+ out << " from #{dependency.location}" if dependency.location
93
+ Berkshelf.ui.info(out)
107
94
  end
108
95
  end
109
96