berkshelf 3.0.0.beta6 → 3.0.0.beta7

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