berkshelf 3.0.0.beta9 → 3.0.0.rc1

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.
@@ -1,5 +1,9 @@
1
- # <%= name %> cookbook CHANGELOG
2
- This file is used to list changes made in each version of the <%= name %> cookbook.
1
+ # 0.1.0
3
2
 
4
- ## v0.1.0 (<%= Time.now.strftime('%Y-%m-%d') %>)
5
- - Initial release of <%= name %>
3
+ Initial release of <%= name %>
4
+
5
+ * Enhancements
6
+ * an enhancement
7
+
8
+ * Bug Fixes
9
+ * a bug fix
@@ -1,12 +1,17 @@
1
- .vagrant
2
- Berksfile.lock
3
1
  *~
4
2
  *#
5
3
  .#*
6
4
  \#*#
7
5
  .*.sw[a-z]
8
6
  *.un~
7
+ pkg/
8
+
9
+ # Berkshelf
10
+ .vagrant
9
11
  /cookbooks
12
+ <% unless options[:pattern] == "environment" -%>
13
+ Berksfile.lock
14
+ <% end -%>
10
15
 
11
16
  # Bundler
12
17
  Gemfile.lock
@@ -3,9 +3,9 @@ maintainer '<%= maintainer %>'
3
3
  maintainer_email '<%= maintainer_email %>'
4
4
  license '<%= license_name %>'
5
5
  description 'Installs/Configures <%= name %>'
6
- long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
6
+ long_description 'Installs/Configures <%= name %>'
7
7
  <% if options[:scmversion] -%>
8
- version IO.read(File.join(File.dirname(__FILE__), 'VERSION')) rescue '0.1.0'
8
+ version IO.read(File.join(File.dirname(__FILE__), 'VERSION'))
9
9
  <% else -%>
10
10
  version '0.1.0'
11
11
  <% end -%>
data/lib/berkshelf.rb CHANGED
@@ -24,6 +24,8 @@ module Berkshelf
24
24
  autoload :Logging, 'berkshelf/mixin/logging'
25
25
  end
26
26
 
27
+ autoload :Uploader, 'berkshelf/uploader'
28
+
27
29
  autoload :BaseFormatter, 'berkshelf/formatters/base'
28
30
  autoload :HumanFormatter, 'berkshelf/formatters/human'
29
31
  autoload :JsonFormatter, 'berkshelf/formatters/json'
@@ -8,12 +8,30 @@ module Berkshelf
8
8
  end
9
9
  end
10
10
 
11
+ # A list of cookbook patterns accepted by generators inheriting from
12
+ # this generator.
13
+ #
14
+ # @return [Array<String>]
15
+ PATTERNS = [
16
+ "environment",
17
+ "application",
18
+ "library",
19
+ "wrapper"
20
+ ].freeze
21
+
11
22
  shell = Berkshelf.ui
12
23
 
13
24
  argument :path,
14
25
  type: :string,
15
26
  required: true
16
27
 
28
+ class_option :pattern,
29
+ type: :string,
30
+ default: "application",
31
+ desc: "Modifies the generated skeleton based on the given pattern.",
32
+ aliases: "-p",
33
+ enum: BaseGenerator::PATTERNS
34
+
17
35
  include Thor::Actions
18
36
 
19
37
  private
@@ -378,18 +378,22 @@ module Berkshelf
378
378
  # @option options [String, Array<String>] :cookbooks
379
379
  # Names of the cookbooks to retrieve dependencies for
380
380
  def update(*names)
381
+ validate_lockfile_present!
381
382
  validate_cookbook_names!(names)
382
383
 
384
+ Berkshelf.log.info "Updating cookbooks"
385
+
383
386
  # Calculate the list of cookbooks to unlock
384
387
  if names.empty?
385
- list = dependencies
388
+ Berkshelf.log.debug " Unlocking all the things!"
389
+ lockfile.unlock_all
386
390
  else
387
- list = dependencies.select { |dependency| names.include?(dependency.name) }
391
+ names.each do |name|
392
+ Berkshelf.log.debug " Unlocking #{name}"
393
+ lockfile.unlock(name, true)
394
+ end
388
395
  end
389
396
 
390
- # Unlock any/all specified cookbooks
391
- list.each { |dependency| lockfile.unlock(dependency) }
392
-
393
397
  # NOTE: We intentionally do NOT pass options to the installer
394
398
  self.install
395
399
  end
@@ -529,24 +533,11 @@ module Berkshelf
529
533
  # @return [Array<CachedCookbook>]
530
534
  # the list of cookbooks that were uploaded to the Chef Server
531
535
  def upload(*args)
532
- options = args.last.is_a?(Hash) ? args.pop : {}
533
- names = args.flatten
534
-
535
536
  validate_lockfile_present!
536
537
  validate_lockfile_trusted!
537
538
  validate_dependencies_installed!
538
- validate_cookbook_names!(names)
539
539
 
540
- # Calculate the list of cookbooks from the given arguments
541
- if names.empty?
542
- list = dependencies
543
- else
544
- list = dependencies.select { |dependency| names.include?(dependency.name) }
545
- end
546
-
547
- cookbooks = cookbooks_for_upload(list)
548
- ridley_upload(cookbooks, options)
549
- cookbooks
540
+ Uploader.new(self, *args).run
550
541
  end
551
542
 
552
543
  # Package the given cookbook for distribution outside of berkshelf. If the
@@ -651,73 +642,6 @@ module Berkshelf
651
642
 
652
643
  private
653
644
 
654
- def ridley_upload(cookbooks, options = {})
655
- options = {
656
- force: false,
657
- freeze: true,
658
- halt_on_frozen: false,
659
- validate: true,
660
- }.merge(options)
661
-
662
- skipped = []
663
-
664
- Berkshelf.ridley_connection(options) do |conn|
665
- cookbooks.each do |cookbook|
666
- Berkshelf.formatter.upload(cookbook, conn)
667
- validate_files!(cookbook)
668
-
669
- begin
670
- conn.cookbook.upload(cookbook.path, {
671
- force: options[:force],
672
- freeze: options[:freeze],
673
- name: cookbook.cookbook_name,
674
- validate: options[:validate]
675
- })
676
- rescue Ridley::Errors::FrozenCookbook => ex
677
- if options[:halt_on_frozen]
678
- raise FrozenCookbook.new(cookbook)
679
- end
680
-
681
- Berkshelf.formatter.skip(cookbook, conn)
682
- skipped << cookbook
683
- end
684
- end
685
- end
686
-
687
- unless skipped.empty?
688
- Berkshelf.formatter.msg "Skipped uploading some cookbooks because they" <<
689
- " already exist on the remote server and are frozen. Re-run with the `--force`" <<
690
- " flag to force overwrite these cookbooks:" <<
691
- "\n\n" <<
692
- " * " << skipped.map { |c| "#{c.cookbook_name} (#{c.version})" }.join("\n * ")
693
- end
694
- end
695
-
696
- # Filter the given dependencies for upload. The dependencies of a cookbook
697
- # will always be included in the filtered results, even if that
698
- # dependency's name is not explicitly provided.
699
- #
700
- # @param [Array<Dependency>] dependencies
701
- # the list of dependencies to filter for upload
702
- #
703
- # @return [Array<CachedCookbook>]
704
- # the cookbook objects for uploading
705
- def cookbooks_for_upload(dependencies)
706
- dependencies.reduce({}) do |hash, dependency|
707
- if hash[dependency.name].nil?
708
- # TODO: make this a configurable option for advanced users to save
709
- # time.
710
- lockfile.graph.find(dependency).dependencies.each do |direct, _|
711
- hash[direct] ||= lockfile.retrieve(direct)
712
- end
713
-
714
- hash[dependency.name] = lockfile.retrieve(dependency)
715
- end
716
-
717
- hash
718
- end.values
719
- end
720
-
721
645
  # Ensure the lockfile is present on disk.
722
646
  #
723
647
  # @raise [LockfileNotFound]
@@ -769,27 +693,11 @@ module Berkshelf
769
693
  # @raise [DependencyNotFound]
770
694
  # if a cookbook name is given that does not exist
771
695
  def validate_cookbook_names!(names)
772
- missing = names - dependencies.map(&:name)
696
+ missing = names - lockfile.graph.locks.keys
773
697
 
774
698
  unless missing.empty?
775
699
  raise DependencyNotFound.new(missing)
776
700
  end
777
701
  end
778
-
779
- # Validate that the given cookbook does not have "bad" files. Currently
780
- # this means including spaces in filenames (such as recipes)
781
- #
782
- # @param [CachedCookbook] cookbook
783
- # the Cookbook to validate
784
- def validate_files!(cookbook)
785
- path = cookbook.path.to_s
786
-
787
- files = Dir.glob(File.join(path, '**', '*.rb')).select do |f|
788
- parent = Pathname.new(path).dirname.to_s
789
- f.gsub(parent, '') =~ /[[:space:]]/
790
- end
791
-
792
- raise InvalidCookbookFiles.new(cookbook, files) unless files.empty?
793
- end
794
702
  end
795
703
  end
data/lib/berkshelf/cli.rb CHANGED
@@ -197,7 +197,7 @@ module Berkshelf
197
197
  method_option :halt_on_frozen,
198
198
  type: :boolean,
199
199
  default: false,
200
- desc: 'Halt uploading and exit if the Chef Server has a frozen version of the cookbook(s).'
200
+ desc: 'Exit with a non zero exit code if the Chef Server already has the version of the cookbook(s).'
201
201
  desc 'upload [COOKBOOKS]', 'Upload the cookbook specified in the Berksfile to the Chef Server'
202
202
  def upload(*names)
203
203
  berksfile = Berksfile.from_options(options)
@@ -279,6 +279,7 @@ module Berkshelf
279
279
 
280
280
  Berkshelf.formatter.msg 'Successfully initialized'
281
281
  end
282
+ tasks['init'].options = Berkshelf::InitGenerator.class_options
282
283
 
283
284
  method_option :berksfile,
284
285
  type: :string,
@@ -385,12 +386,13 @@ module Berkshelf
385
386
  Berkshelf.formatter.version
386
387
  end
387
388
 
388
- desc 'cookbook NAME', 'Create a skeleton for a new cookbook'
389
- def cookbook(name)
389
+ desc 'cookbook NAME [PATH]', 'Create a skeleton for a new cookbook'
390
+ def cookbook(name, path = nil)
391
+ path = File.join(Dir.pwd, name) if path.nil?
390
392
  Berkshelf.formatter.deprecation '--git is now the default' if options[:git]
391
393
  Berkshelf.formatter.deprecation '--vagrant is now the default' if options[:vagrant]
392
394
 
393
- Berkshelf::CookbookGenerator.new([File.join(Dir.pwd, name), name], options).invoke_all
395
+ Berkshelf::CookbookGenerator.new([path, name], options).invoke_all
394
396
  end
395
397
  tasks['cookbook'].options = Berkshelf::CookbookGenerator.class_options
396
398
 
@@ -48,15 +48,26 @@ module Berkshelf
48
48
  default: Berkshelf.config.cookbook.email
49
49
 
50
50
  def generate
51
- empty_directory target.join('files/default')
52
- empty_directory target.join('templates/default')
53
- empty_directory target.join('attributes')
54
- empty_directory target.join('libraries')
55
- empty_directory target.join('providers')
56
- empty_directory target.join('recipes')
57
- empty_directory target.join('resources')
58
-
59
- template 'default_recipe.erb', target.join('recipes/default.rb')
51
+ case options[:pattern]
52
+ when "library"
53
+ empty_directory target.join("libraries")
54
+ empty_directory target.join("providers")
55
+ empty_directory target.join("resources")
56
+ when "wrapper"
57
+ empty_directory target.join("attributes")
58
+ empty_directory target.join("recipes")
59
+ template "default_recipe.erb", target.join("recipes/default.rb")
60
+ when "environment", "application"
61
+ empty_directory target.join("files/default")
62
+ empty_directory target.join("templates/default")
63
+ empty_directory target.join("attributes")
64
+ empty_directory target.join("libraries")
65
+ empty_directory target.join("providers")
66
+ empty_directory target.join("recipes")
67
+ empty_directory target.join("resources")
68
+ template "default_recipe.erb", target.join("recipes/default.rb")
69
+ end
70
+
60
71
  template 'metadata.rb.erb', target.join('metadata.rb')
61
72
  template license_file, target.join('LICENSE')
62
73
  template 'README.md.erb', target.join('README.md')
@@ -30,8 +30,8 @@ module Berkshelf
30
30
  formatter_method :package
31
31
  formatter_method :search
32
32
  formatter_method :show
33
- formatter_method :skip
34
- formatter_method :upload
33
+ formatter_method :skipping
34
+ formatter_method :uploaded
35
35
  formatter_method :use
36
36
  formatter_method :vendor
37
37
  formatter_method :version
@@ -40,15 +40,15 @@ module Berkshelf
40
40
  #
41
41
  # @param [Berkshelf::CachedCookbook] cookbook
42
42
  # @param [Ridley::Connection] conn
43
- def upload(cookbook, conn)
44
- Berkshelf.ui.info "Uploading #{cookbook.cookbook_name} (#{cookbook.version}) to: '#{conn.server_url}'"
43
+ def uploaded(cookbook, conn)
44
+ Berkshelf.ui.info "Uploaded #{cookbook.cookbook_name} (#{cookbook.version}) to: '#{conn.server_url}'"
45
45
  end
46
46
 
47
47
  # Output a Cookbook skip message using {Berkshelf.ui}
48
48
  #
49
49
  # @param [Berkshelf::CachedCookbook] cookbook
50
50
  # @param [Ridley::Connection] conn
51
- def skip(cookbook, conn)
51
+ def skipping(cookbook, conn)
52
52
  Berkshelf.ui.info "Skipping #{cookbook.cookbook_name} (#{cookbook.version}) (frozen)"
53
53
  end
54
54
 
@@ -66,7 +66,7 @@ module Berkshelf
66
66
  #
67
67
  # @param [Berkshelf::CachedCookbook] cookbook
68
68
  # @param [Ridley::Connection] conn
69
- def upload(cookbook, conn)
69
+ def uploaded(cookbook, conn)
70
70
  name = cookbook.cookbook_name
71
71
  cookbooks[name] ||= {}
72
72
  cookbooks[name][:version] = cookbook.version
@@ -77,7 +77,7 @@ module Berkshelf
77
77
  #
78
78
  # @param [Berkshelf::CachedCookbook] cookbook
79
79
  # @param [Ridley::Connection] conn
80
- def skip(cookbook, conn)
80
+ def skipping(cookbook, conn)
81
81
  name = cookbook.cookbook_name
82
82
  cookbooks[name] ||= {}
83
83
  cookbooks[name][:version] = cookbook.version
@@ -96,13 +96,7 @@ module Berkshelf
96
96
 
97
97
  if defined?(Kitchen::Generator::Init)
98
98
  unless options[:skip_test_kitchen]
99
- # Temporarily use Dir.chdir to ensure the destination_root of test kitchen's generator
100
- # is where we expect until this bug can be addressed:
101
- # https://github.com/opscode/test-kitchen/pull/140
102
- Dir.chdir target do
103
- # Kitchen::Generator::Init.new([], {}, destination_root: target).invoke_all
104
- Kitchen::Generator::Init.new([], {}).invoke_all
105
- end
99
+ Kitchen::Generator::Init.new([], {}, destination_root: target).invoke_all
106
100
  end
107
101
  end
108
102
 
@@ -146,7 +146,8 @@ module Berkshelf
146
146
  # Add any explicit dependencies for already-downloaded cookbooks (like
147
147
  # path locations)
148
148
  dependencies.each do |dependency|
149
- if cookbook = dependency.cached_cookbook
149
+ if dependency.location
150
+ cookbook = dependency.cached_cookbook
150
151
  Berkshelf.log.debug " Adding explicit dependency on #{cookbook}"
151
152
  resolver.add_explicit_dependencies(cookbook)
152
153
  end
@@ -98,8 +98,6 @@ module Berkshelf
98
98
  berksfile.dependencies.each do |dependency|
99
99
  Berkshelf.log.debug "Checking #{dependency}"
100
100
 
101
- checked[dependency.name] = true
102
-
103
101
  locked = find(dependency)
104
102
  if locked.nil?
105
103
  Berkshelf.log.debug " Not in lockfile - cannot be trusted!"
@@ -152,17 +150,36 @@ module Berkshelf
152
150
  # the list of already checked dependencies
153
151
  #
154
152
  # @return [Boolean]
155
- def satisfies_transitive?(graph_item, checked)
156
- graph_item.dependencies.all? do |name, constraint|
157
- return true if checked[name]
153
+ def satisfies_transitive?(graph_item, checked, level = 0)
154
+ indent = ' '*(level + 2)
158
155
 
159
- checked[name] = true
156
+ Berkshelf.log.debug "#{indent}Checking transitive dependencies for #{graph_item}"
157
+
158
+ if checked[graph_item.name]
159
+ Berkshelf.log.debug "#{indent} Already checked - skipping"
160
+ return true
161
+ end
162
+
163
+ graph_item.dependencies.each do |name, constraint|
164
+ Berkshelf.log.debug "#{indent} Checking #{name} (#{constraint})"
160
165
 
161
166
  graphed = graph.find(name)
162
- return false if graphed.nil?
167
+ if graphed.nil?
168
+ Berkshelf.log.debug "#{indent} Not graphed - cannot be satisifed"
169
+ return false
170
+ end
171
+
172
+ unless Semverse::Constraint.new(constraint).satisfies?(graphed.version)
173
+ Berkshelf.log.debug "#{indent} Version constraint is not satisfied"
174
+ return false
175
+ end
163
176
 
164
- Semverse::Constraint.new(constraint).satisfies?(graphed.version) &&
165
- satisfies_transitive?(graphed, checked)
177
+ unless satisfies_transitive?(graphed, checked, level + 2)
178
+ Berkshelf.log.debug "#{indent} Transitive are not satisifed"
179
+ return false
180
+ end
181
+
182
+ checked[name] = true
166
183
  end
167
184
  end
168
185
 
@@ -288,14 +305,22 @@ module Berkshelf
288
305
  # dependencies. Then it uses a recursive algorithm to safely remove any
289
306
  # other dependencies from the graph that are no longer needed.
290
307
  #
291
- # @raise [CookbookNotFound]
292
- # if the provided dependency does not exist
293
- #
294
308
  # @param [String] dependency
295
309
  # the name of the cookbook to remove
296
- def unlock(dependency)
310
+ def unlock(dependency, force = false)
297
311
  @dependencies.delete(Dependency.name(dependency))
298
- graph.remove(dependency)
312
+
313
+ if force
314
+ graph.remove(dependency, ignore: graph.locks.keys)
315
+ else
316
+ graph.remove(dependency)
317
+ end
318
+ end
319
+
320
+ # Completely remove all dependencies from the lockfile and underlying graph.
321
+ def unlock_all
322
+ @dependencies = {}
323
+ @graph = Graph.new(self)
299
324
  end
300
325
 
301
326
  # Iterate over each top-level dependency defined in the lockfile and
@@ -317,58 +342,72 @@ module Berkshelf
317
342
  #
318
343
  # @return [Array<Dependency>]
319
344
  def reduce!
320
- # Store a list of cookbooks to ungraph
321
- to_ungraph = {}
322
- to_ignore = {}
345
+ Berkshelf.log.info "Reducing lockfile"
346
+
347
+ Berkshelf.log.debug "Current lockfile:"
348
+ Berkshelf.log.debug ""
349
+ to_lock.each_line do |line|
350
+ Berkshelf.log.debug " #{line.chomp}"
351
+ end
352
+ Berkshelf.log.debug ""
353
+
323
354
 
324
355
  # Unlock any locked dependencies that are no longer in the Berksfile
356
+ Berkshelf.log.debug "Unlocking dependencies no longer in the Berksfile"
357
+
325
358
  dependencies.each do |dependency|
326
- unless berksfile.has_dependency?(dependency.name)
327
- unlock(dependency)
328
-
329
- # Keep a record. We know longer trust these dependencies, but simply
330
- # unlocking them does not guarantee their removal from the graph.
331
- # Instead, we keep a record of the dependency to unlock it later (in
332
- # case it is actually removable because it's parent requirer is also
333
- # being removed in this reduction). It's a form of science. Don't
334
- # question it too much.
335
- to_ungraph[dependency.name] = true
336
- to_ignore[dependency.name] = true
359
+ Berkshelf.log.debug " Checking #{dependency}"
360
+
361
+ if berksfile.has_dependency?(dependency.name)
362
+ Berkshelf.log.debug " Skipping unlock for #{dependency.name} (exists in the Berksfile)"
363
+ else
364
+ Berkshelf.log.debug " Unlocking #{dependency.name}"
365
+ unlock(dependency, true)
337
366
  end
338
367
  end
339
368
 
340
369
  # Remove any transitive dependencies
370
+ Berkshelf.log.debug "Removing transitive dependencies"
371
+
341
372
  berksfile.dependencies.each do |dependency|
373
+ Berkshelf.log.debug " Checking #{dependency}"
374
+
342
375
  graphed = graph.find(dependency)
343
- next if graphed.nil?
376
+
377
+ if graphed.nil?
378
+ Berkshelf.log.debug " Skipping (not graphed)"
379
+ next
380
+ end
344
381
 
345
382
  unless dependency.version_constraint.satisfies?(graphed.version)
383
+ Berkshelf.log.debug " Constraints are not satisfied!"
346
384
  raise OutdatedDependency.new(graphed, dependency)
347
385
  end
348
386
 
349
387
  if cookbook = dependency.cached_cookbook
388
+ Berkshelf.log.debug " Cached cookbook exists"
389
+ Berkshelf.log.debug " Checking dependencies on the cached cookbook"
390
+
350
391
  graphed.dependencies.each do |name, constraint|
392
+ Berkshelf.log.debug " Checking #{name} (#{constraint})"
393
+
351
394
  # Unless the cookbook still depends on this key, we want to queue it
352
395
  # for unlocking. This is the magic that prevents transitive
353
396
  # dependency leaking.
354
397
  unless cookbook.dependencies.has_key?(name)
355
- to_ungraph[name] = true
356
-
357
- # We also want to ignore the top-level dependency. We can no
358
- # longer trust the graph that we have been given for that
359
- # dependency and therefore need to reduce it.
360
- to_ignore[dependency.name] = true
398
+ Berkshelf.log.debug " Not found!"
399
+ unlock(name, true)
361
400
  end
362
401
  end
363
402
  end
364
403
  end
365
404
 
366
- # Now remove all the unlockable items
367
- ignore = to_ungraph.merge(to_ignore).keys
368
-
369
- to_ungraph.each do |name, _|
370
- graph.remove(name, ignore: ignore)
405
+ Berkshelf.log.debug "New lockfile:"
406
+ Berkshelf.log.debug ""
407
+ to_lock.each_line do |line|
408
+ Berkshelf.log.debug " #{line.chomp}"
371
409
  end
410
+ Berkshelf.log.debug ""
372
411
  end
373
412
 
374
413
 
@@ -566,6 +605,10 @@ module Berkshelf
566
605
  dependency = @lockfile.find(name) ||
567
606
  @berksfile && @berksfile.find(name) ||
568
607
  Dependency.new(@berksfile, name)
608
+
609
+ # We need to make a copy of the dependency, or else we could be
610
+ # modifying an existing object that other processes depend on!
611
+ dependency = dependency.dup
569
612
  dependency.locked_version = item.version
570
613
 
571
614
  hash[item.name] = dependency
@@ -748,6 +791,11 @@ module Berkshelf
748
791
  def add_dependency(name, constraint)
749
792
  @dependencies[name.to_s] = constraint.to_s
750
793
  end
794
+
795
+ # @private
796
+ def to_s
797
+ "#{name} (#{version})"
798
+ end
751
799
  end
752
800
  end
753
801
  end