berkshelf 3.0.0.beta9 → 3.0.0.rc1

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