berkshelf 4.0.1 → 4.1.0

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.travis.yml +41 -6
  4. data/CHANGELOG.md +15 -0
  5. data/CONTRIBUTING.md +10 -0
  6. data/Gemfile.lock +332 -0
  7. data/berkshelf.gemspec +8 -6
  8. data/features/commands/install.feature +105 -8
  9. data/features/json_formatter.feature +2 -2
  10. data/features/step_definitions/chef/config_steps.rb +1 -1
  11. data/features/step_definitions/chef_server_steps.rb +2 -2
  12. data/features/step_definitions/cli_steps.rb +1 -1
  13. data/features/step_definitions/config_steps.rb +1 -1
  14. data/features/step_definitions/environment_steps.rb +2 -2
  15. data/features/step_definitions/filesystem_steps.rb +3 -5
  16. data/features/step_definitions/json_steps.rb +1 -1
  17. data/features/support/env.rb +8 -8
  18. data/generator_files/Gemfile.erb +0 -14
  19. data/lib/berkshelf/berksfile.rb +47 -0
  20. data/lib/berkshelf/downloader.rb +7 -6
  21. data/lib/berkshelf/lockfile.rb +22 -11
  22. data/lib/berkshelf/resolver.rb +27 -0
  23. data/lib/berkshelf/thor_ext/hash_with_indifferent_access.rb +5 -1
  24. data/lib/berkshelf/version.rb +1 -1
  25. data/spec/fixtures/berksfiles/default +4 -0
  26. data/spec/fixtures/cookbook-path/jenkins-config/metadata.rb +4 -0
  27. data/spec/fixtures/cookbook-store/jenkins-2.0.3/metadata.rb +6 -0
  28. data/spec/fixtures/cookbook-store/jenkins-2.0.4/metadata.rb +5 -0
  29. data/spec/fixtures/lockfiles/default.lock +5 -0
  30. data/spec/fixtures/lockfiles/orphans.lock +21 -0
  31. data/spec/support/chef_server.rb +3 -3
  32. data/spec/unit/berkshelf/berksfile_spec.rb +27 -1
  33. data/spec/unit/berkshelf/init_generator_spec.rb +2 -4
  34. data/spec/unit/berkshelf/lockfile_spec.rb +37 -0
  35. data/spec/unit/berkshelf/resolver_spec.rb +28 -1
  36. metadata +54 -15
@@ -41,19 +41,21 @@ Gem::Specification.new do |s|
41
41
  s.add_dependency 'minitar', '~> 0.5.4'
42
42
  s.add_dependency 'retryable', '~> 2.0'
43
43
  s.add_dependency 'ridley', '~> 4.3'
44
- s.add_dependency 'solve', '~> 1.1'
44
+ s.add_dependency 'solve', '~> 2.0'
45
45
  s.add_dependency 'thor', '~> 0.19'
46
- s.add_dependency 'octokit', '~> 3.0'
46
+ s.add_dependency 'octokit', '~> 4.0'
47
47
  s.add_dependency 'celluloid', '= 0.16.0'
48
48
  s.add_dependency 'celluloid-io', '~> 0.16.1'
49
49
 
50
- s.add_development_dependency 'aruba', '~> 0.6'
51
- s.add_development_dependency 'chef-zero', '~> 1.5.0'
52
- s.add_development_dependency 'fuubar', '~> 1.1'
53
- s.add_development_dependency 'rake', '~> 0.9'
50
+ s.add_development_dependency 'aruba', '~> 0.10.0' # Lock this here to avoid problems with public API changes
51
+ s.add_development_dependency 'chef-zero', '~> 4.0'
52
+ s.add_development_dependency 'dep_selector', '~> 1.0'
53
+ s.add_development_dependency 'fuubar', '~> 2.0'
54
+ s.add_development_dependency 'rake', '~> 10.1'
54
55
  s.add_development_dependency 'rspec', '~> 3.0'
55
56
  s.add_development_dependency 'spork', '~> 0.9'
56
57
  s.add_development_dependency 'test-kitchen', '~> 1.2'
57
58
  s.add_development_dependency 'webmock', '~> 1.11'
58
59
  s.add_development_dependency 'yard', '~> 0.8'
60
+ s.add_development_dependency 'http', '~> 0.9.8'
59
61
  end
@@ -133,19 +133,19 @@ Feature: berks install
133
133
  Scenario: installing a demand from a path location
134
134
  Given I have a Berksfile pointing at the local Berkshelf API with:
135
135
  """
136
- cookbook 'example_cookbook', path: '../../fixtures/cookbooks/example_cookbook-0.5.0'
136
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
137
137
  """
138
138
  And the Berkshelf API server's cache is up to date
139
139
  When I successfully run `berks install`
140
140
  Then the output should contain:
141
141
  """
142
- Using example_cookbook (0.5.0) from source at ../../fixtures/cookbooks/example_cookbook-0.5.0
142
+ Using example_cookbook (0.5.0) from source at ../../spec/fixtures/cookbooks/example_cookbook-0.5.0
143
143
  """
144
144
 
145
145
  Scenario: installing a demand from a path location with a conflicting constraint
146
146
  Given I have a Berksfile pointing at the local Berkshelf API with:
147
147
  """
148
- cookbook 'example_cookbook', '~> 1.0.0', path: '../../fixtures/cookbooks/example_cookbook-0.5.0'
148
+ cookbook 'example_cookbook', '~> 1.0.0', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
149
149
  """
150
150
  When I run `berks install`
151
151
  Then the output should contain:
@@ -158,13 +158,13 @@ Feature: berks install
158
158
  | example_cookbook | 0.5.0 | missing_cookbook >= 1.0.0 |
159
159
  And I have a Berksfile pointing at the local Berkshelf API with:
160
160
  """
161
- cookbook 'example_cookbook', path: '../../fixtures/cookbooks/example_cookbook-0.5.0'
161
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
162
162
  """
163
163
  And the Berkshelf API server's cache is up to date
164
164
  When I successfully run `berks install`
165
165
  Then the output should contain:
166
166
  """
167
- Using example_cookbook (0.5.0) from source at ../../fixtures/cookbooks/example_cookbook-0.5.0
167
+ Using example_cookbook (0.5.0) from source at ../../spec/fixtures/cookbooks/example_cookbook-0.5.0
168
168
  """
169
169
 
170
170
  Scenario: installing a demand from a path location locks the graph to that version
@@ -172,7 +172,7 @@ Feature: berks install
172
172
  | other_cookbook | 1.0.0 | example_cookbook ~> 1.0.0 |
173
173
  And I have a Berksfile pointing at the local Berkshelf API with:
174
174
  """
175
- cookbook 'example_cookbook', path: '../../fixtures/cookbooks/example_cookbook-0.5.0'
175
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
176
176
  cookbook 'other_cookbook'
177
177
  """
178
178
  And the Berkshelf API server's cache is up to date
@@ -185,12 +185,12 @@ Feature: berks install
185
185
  Scenario: installing a Berksfile from a remote directory that contains a path location
186
186
  Given I have a Berksfile at "subdirectory" pointing at the local Berkshelf API with:
187
187
  """
188
- cookbook 'example_cookbook', path: '../../../fixtures/cookbooks/example_cookbook-0.5.0'
188
+ cookbook 'example_cookbook', path: '../../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
189
189
  """
190
190
  When I successfully run `berks install -b subdirectory/Berksfile`
191
191
  Then the output should contain:
192
192
  """
193
- Using example_cookbook (0.5.0) from source at ../../../fixtures/cookbooks/example_cookbook-0.5.0
193
+ Using example_cookbook (0.5.0) from source at ../../../spec/fixtures/cookbooks/example_cookbook-0.5.0
194
194
  """
195
195
 
196
196
  Scenario: installing a demand from a Git location
@@ -541,3 +541,100 @@ Feature: berks install
541
541
  """
542
542
  Using bacon (0.2.0)
543
543
  """
544
+
545
+ Scenario: when asking for the :ruby solver engine without precedence, no error is raised
546
+ Given I have a Berksfile pointing at the local Berkshelf API with:
547
+ """
548
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
549
+ solver :ruby
550
+ """
551
+ When I successfully run `berks install`
552
+ Then the output should contain:
553
+ """
554
+ Resolving cookbook dependencies...
555
+ """
556
+
557
+ Scenario: when asking for the :gecode solver engine without precedence, no error is raised
558
+ Given I have a Berksfile pointing at the local Berkshelf API with:
559
+ """
560
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
561
+ solver :gecode
562
+ """
563
+ When I successfully run `berks install`
564
+ Then the output should contain:
565
+ """
566
+ Resolving cookbook dependencies...
567
+ """
568
+
569
+ Scenario: when asking for an unknown solver engine without precedence, no error is raised
570
+ Given I have a Berksfile pointing at the local Berkshelf API with:
571
+ """
572
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
573
+ solver :unknown
574
+ """
575
+ When I successfully run `berks install`
576
+ Then the output should contain:
577
+ """
578
+ Resolving cookbook dependencies...
579
+ """
580
+
581
+ Scenario: when preferring the :gecode solver engine, no error is raised
582
+ Given I have a Berksfile pointing at the local Berkshelf API with:
583
+ """
584
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
585
+ solver :gecode, :preferred
586
+ """
587
+ When I successfully run `berks install`
588
+ Then the output should contain:
589
+ """
590
+ Resolving cookbook dependencies...
591
+ """
592
+
593
+ Scenario: when preferring an unknown solver engine, no error is raised
594
+ Given I have a Berksfile pointing at the local Berkshelf API with:
595
+ """
596
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
597
+ solver :unknown, :preferred
598
+ """
599
+ When I successfully run `berks install`
600
+ Then the output should contain:
601
+ """
602
+ Resolving cookbook dependencies...
603
+ """
604
+
605
+ Scenario: when requiring an unknown solver engine, an error is raised
606
+ Given I have a Berksfile pointing at the local Berkshelf API with:
607
+ """
608
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
609
+ solver :unknown, :required
610
+ """
611
+ When I run `berks install`
612
+ Then the output should contain:
613
+ """
614
+ Engine `unknown` is not supported.
615
+ """
616
+ And the exit status should be "ArgumentError"
617
+
618
+ Scenario: when requiring the :ruby solver engine, no error is raised
619
+ Given I have a Berksfile pointing at the local Berkshelf API with:
620
+ """
621
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
622
+ solver :ruby, :required
623
+ """
624
+ When I successfully run `berks install`
625
+ Then the output should contain:
626
+ """
627
+ Resolving cookbook dependencies...
628
+ """
629
+
630
+ Scenario: when requiring the :gecode solver engine, no error is raised
631
+ Given I have a Berksfile pointing at the local Berkshelf API with:
632
+ """
633
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
634
+ solver :gecode, :required
635
+ """
636
+ When I successfully run `berks install`
637
+ Then the output should contain:
638
+ """
639
+ Resolving cookbook dependencies...
640
+ """
@@ -93,13 +93,13 @@ Feature: --format json
93
93
  Scenario: JSON output when running the upload command
94
94
  Given I have a Berksfile pointing at the local Berkshelf API with:
95
95
  """
96
- cookbook 'example_cookbook', path: '../../fixtures/cookbooks/example_cookbook-0.5.0'
96
+ cookbook 'example_cookbook', path: '../../spec/fixtures/cookbooks/example_cookbook-0.5.0'
97
97
  """
98
98
  And I write to "Berksfile.lock" with:
99
99
  """
100
100
  DEPENDENCIES
101
101
  example_cookbook
102
- path: ../../fixtures/cookbooks/example_cookbook-0.5.0
102
+ path: ../../spec/fixtures/cookbooks/example_cookbook-0.5.0
103
103
 
104
104
  GRAPH
105
105
  example_cookbook (0.5.0)
@@ -8,5 +8,5 @@ Given /^I do not have a Chef config$/ do
8
8
  Berkshelf.chef_config.save
9
9
 
10
10
  ENV['BERKSHELF_CHEF_CONFIG'] = path
11
- set_env 'BERKSHELF_CHEF_CONFIG', path
11
+ set_environment_variable 'BERKSHELF_CHEF_CONFIG', path
12
12
  end
@@ -28,8 +28,8 @@ Given(/^the Chef Server has an environment named "(.*?)"$/) do |name|
28
28
  end
29
29
 
30
30
  Given(/^the Chef Server does not have an environment named "(.*?)"$/) do |name|
31
- if chef_server.data_store.exists?(['environments', name])
32
- chef_server.data_store.delete(['environments', name])
31
+ if chef_server.data_store.exists?(['organizations', 'chef', 'environments', name])
32
+ chef_server.data_store.delete(['organizations', 'chef', 'environments', name])
33
33
  end
34
34
  end
35
35
 
@@ -1,4 +1,4 @@
1
1
  Then /^the exit status should be "(.+)"$/ do |name|
2
2
  error = name.split('::').reduce(Berkshelf) { |klass, id| klass.const_get(id) }
3
- assert_exit_status(error.status_code)
3
+ expect(last_command_started).to have_exit_status(error.status_code)
4
4
  end
@@ -8,7 +8,7 @@ Given /^I already have a Berkshelf config file$/ do
8
8
  Berkshelf.config = config
9
9
 
10
10
  ENV['BERKSHELF_CONFIG'] = path
11
- set_env 'BERKSHELF_CONFIG', path
11
+ set_environment_variable 'BERKSHELF_CONFIG', path
12
12
  end
13
13
 
14
14
  Given /^I have a Berkshelf config file containing:$/ do |contents|
@@ -1,7 +1,7 @@
1
1
  Given /^the environment variable (.+) is nil$/ do |variable|
2
- set_env(variable, nil)
2
+ set_environment_variable(variable, nil)
3
3
  end
4
4
 
5
5
  Given /^the environment variable (.+) is "(.+)"$/ do |variable, value|
6
- set_env(variable, value)
6
+ set_environment_variable(variable, value)
7
7
  end
@@ -5,7 +5,7 @@ World(Berkshelf::RSpec::ChefAPI)
5
5
  World(Berkshelf::RSpec::FileSystemMatchers)
6
6
 
7
7
  Given /^a cookbook named "(.*?)"$/ do |name|
8
- create_dir(name)
8
+ create_directory(name)
9
9
  write_file(File.join(name, "metadata.rb"), "name '#{name}'")
10
10
  end
11
11
 
@@ -24,7 +24,7 @@ Given /^the cookbook store has the git cookbooks:$/ do |cookbooks|
24
24
  folder = "#{name}-#{sha}"
25
25
  metadata = File.join(folder, 'metadata.rb')
26
26
 
27
- create_dir(folder)
27
+ create_directory(folder)
28
28
  write_file(metadata, [
29
29
  "name '#{name}'",
30
30
  "version '#{version}'"
@@ -93,9 +93,7 @@ Then /^I should have a new cookbook skeleton "(.*?)"$/ do |name|
93
93
  file "Berksfile" do
94
94
  contains "metadata"
95
95
  end
96
- file "Gemfile" do
97
- contains "gem 'berkshelf'"
98
- end
96
+ file "Gemfile"
99
97
  file "metadata.rb"
100
98
  file "README.md"
101
99
  file "Vagrantfile" do
@@ -17,7 +17,7 @@ end
17
17
  Then /^the output should contain JSON:$/ do |data|
18
18
  parsed = ERB.new(data).result
19
19
  target = JSON.pretty_generate(JSON.parse(parsed).sort_by_key)
20
- actual = JSON.pretty_generate(JSON.parse(all_output).sort_by_key)
20
+ actual = JSON.pretty_generate(JSON.parse(all_commands.map { |c| c.output }.join("\n")).sort_by_key)
21
21
 
22
22
  expect(actual).to eq(target)
23
23
  end
@@ -31,8 +31,8 @@ Spork.prefork do
31
31
  ENV['BERKSHELF_CONFIG'] = Berkshelf.config.path.to_s
32
32
  ENV['BERKSHELF_CHEF_CONFIG'] = chef_config_path.to_s
33
33
 
34
- Aruba::InProcess.main_class = Berkshelf::Cli::Runner
35
- Aruba.process = Aruba::InProcess
34
+ aruba.config.command_launcher = :in_process
35
+ aruba.config.main_class = Berkshelf::Cli::Runner
36
36
  @dirs = ["spec/tmp/aruba"] # set aruba's temporary directory
37
37
 
38
38
  stub_kitchen!
@@ -56,20 +56,20 @@ Spork.prefork do
56
56
  Berkshelf::RSpec::ChefServer.start(port: CHEF_SERVER_PORT)
57
57
  Berkshelf::API::RSpec::Server.start(port: BERKS_API_PORT, endpoints: endpoints) unless windows?
58
58
 
59
- @aruba_io_wait_seconds = Cucumber::JRUBY ? 7 : 5
59
+ aruba.config.io_wait_timeout = Cucumber::JRUBY ? 7 : 5
60
60
  @aruba_timeout_seconds = Cucumber::JRUBY ? 35 : 15
61
61
  end
62
62
 
63
63
  Before('@spawn') do
64
- Aruba.process = Aruba::SpawnProcess
64
+ aruba.config.command_launcher = :spawn
65
65
 
66
- set_env('BERKSHELF_PATH', berkshelf_path.to_s)
67
- set_env('BERKSHELF_CONFIG', Berkshelf.config.path.to_s)
68
- set_env('BERKSHELF_CHEF_CONFIG', chef_config_path.to_s)
66
+ set_environment_variable('BERKSHELF_PATH', berkshelf_path.to_s)
67
+ set_environment_variable('BERKSHELF_CONFIG', Berkshelf.config.path.to_s)
68
+ set_environment_variable('BERKSHELF_CHEF_CONFIG', chef_config_path.to_s)
69
69
  end
70
70
 
71
71
  Before('@slow_process') do
72
- @aruba_io_wait_seconds = Cucumber::JRUBY ? 70 : 30
72
+ aruba.config.io_wait_timeout = Cucumber::JRUBY ? 70 : 30
73
73
  @aruba_timeout_seconds = Cucumber::JRUBY ? 140 : 60
74
74
  end
75
75
  end
@@ -1,19 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'berkshelf'
4
-
5
- # Uncomment these lines if you want to live on the Edge:
6
- #
7
- # group :development do
8
- # gem "berkshelf", github: "berkshelf/berkshelf"
9
- # gem "vagrant", github: "mitchellh/vagrant", tag: "v1.6.3"
10
- # end
11
- #
12
- # group :plugins do
13
- # gem "vagrant-berkshelf", github: "berkshelf/vagrant-berkshelf"
14
- # gem "vagrant-omnibus", github: "schisamo/vagrant-omnibus"
15
- # end
16
-
17
3
  <% if options[:foodcritic] -%>
18
4
  gem 'thor-foodcritic'
19
5
  <% end -%>
@@ -41,6 +41,14 @@ module Berkshelf
41
41
  # The path on disk to the file representing this instance of Berksfile
42
42
  attr_reader :filepath
43
43
 
44
+ # @return [Symbol]
45
+ # The solver engine required by this instance of Berksfile
46
+ attr_reader :required_solver
47
+
48
+ # @return [Symbol]
49
+ # The solver engine preferred by this instance of Berksfile
50
+ attr_reader :preferred_solver
51
+
44
52
  # Create a new Berksfile object.
45
53
  #
46
54
  # @param [String] path
@@ -57,6 +65,10 @@ module Berkshelf
57
65
  @dependencies = Hash.new
58
66
  @sources = Hash.new
59
67
 
68
+ # defaults for what solvers to use
69
+ @required_solver = nil
70
+ @preferred_solver = :gecode
71
+
60
72
  if options[:except] && options[:only]
61
73
  raise ArgumentError, 'Cannot specify both :except and :only!'
62
74
  elsif options[:except]
@@ -186,6 +198,41 @@ module Berkshelf
186
198
  end
187
199
  expose :source
188
200
 
201
+ # Configure a specific engine for the 'solve' gem to use when computing dependencies. You may
202
+ # optionally specify how strong a requirement this is. If omitted, the default precedence is
203
+ # :preferred.
204
+ #
205
+ # If :required is specified and cannot be loaded, Resolver#resolve will raise an ArgumentError.
206
+ # If :preferred is specified and cannot be loaded, Resolver#resolve silently catch any errors and
207
+ # use whatever default method the 'solve' gem provides (as of 2.0.1, solve defaults to :ruby).
208
+ #
209
+ # @example
210
+ # solver :gecode
211
+ # solver :gecode, :preferred
212
+ # solver :gecode, :required
213
+ # solver :ruby
214
+ # solver :ruby, :preferred
215
+ # solver :ruby, :required
216
+ #
217
+ # @param [Symbol] name
218
+ # name of engine for solver gem to use for depsolving
219
+ #
220
+ # @param [Symbol] precedence
221
+ # how strong a requirement using this solver is
222
+ # valid values are :required, :preferred
223
+ #
224
+ # @raise [ArgumentError]
225
+ def solver(name, precedence = :preferred)
226
+ if name && precedence == :required
227
+ @required_solver = name
228
+ elsif name && precedence == :preferred
229
+ @preferred_solver = name
230
+ else
231
+ raise ArgumentError, "Invalid solver precedence ':#{precedence}'"
232
+ end
233
+ end
234
+ expose :solver
235
+
189
236
  # @return [Array<Source>]
190
237
  def sources
191
238
  if @sources.empty?
@@ -101,12 +101,13 @@ module Berkshelf
101
101
  return nil
102
102
  end
103
103
 
104
- Net::HTTP.start(url.host, use_ssl: url.scheme == "https",
105
- verify_mode: (options[:ssl_verify].nil? || options[:ssl_verify]) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE) do |http|
106
- resp = http.get(url.request_uri)
107
- return nil unless resp.is_a?(Net::HTTPSuccess)
108
- open(archive_path, "wb") { |file| file.write(resp.body) }
109
- end
104
+ # We use Net::HTTP.new and then get here, because Net::HTTP.get does not support proxy settings.
105
+ http = Net::HTTP.new(url.host,
106
+ use_ssl: url.scheme == "https",
107
+ verify_mode: (options[:ssl_verify].nil? || options[:ssl_verify]) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE)
108
+ resp = http.get(url.request_uri)
109
+ return nil unless resp.is_a?(Net::HTTPSuccess)
110
+ open(archive_path, "wb") { |file| file.write(resp.body) }
110
111
 
111
112
  tgz = Zlib::GzipReader.new(File.open(archive_path, "rb"))
112
113
  Archive::Tar::Minitar.unpack(tgz, unpack_dir)
@@ -396,20 +396,27 @@ module Berkshelf
396
396
  raise OutdatedDependency.new(graphed, dependency)
397
397
  end
398
398
 
399
+ # Locking dependency version to the graphed version if
400
+ # constraints are satisfied by it.
401
+ dependency.locked_version = graphed.version
402
+
399
403
  if cookbook = dependency.cached_cookbook
400
404
  Berkshelf.log.debug " Cached cookbook exists"
401
- Berkshelf.log.debug " Checking dependencies on the cached cookbook"
402
-
403
- graphed.dependencies.each do |name, constraint|
404
- Berkshelf.log.debug " Checking #{name} (#{constraint})"
405
+ Berkshelf.log.debug " Updating cookbook dependencies if required"
406
+ graphed.set_dependencies(cookbook.dependencies)
407
+ end
408
+ end
405
409
 
406
- # Unless the cookbook still depends on this key, we want to queue it
407
- # for unlocking. This is the magic that prevents transitive
408
- # dependency leaking.
409
- unless cookbook.dependencies.has_key?(name)
410
- Berkshelf.log.debug " Not found!"
411
- unlock(name, true)
412
- end
410
+ # Iteratively remove orphan dependencies
411
+ orphans = true
412
+ while orphans do
413
+ orphans = false
414
+ graph.each do |cookbook|
415
+ name = cookbook.name
416
+ unless dependency?(name) or graph.dependency?(name)
417
+ Berkshelf.log.debug "#{cookbook} identified as orphan; removing it"
418
+ unlock(name)
419
+ orphans = true
413
420
  end
414
421
  end
415
422
  end
@@ -811,6 +818,10 @@ module Berkshelf
811
818
  @dependencies[name.to_s] = constraint.to_s
812
819
  end
813
820
 
821
+ def set_dependencies(dependencies)
822
+ @dependencies = dependencies.to_hash
823
+ end
824
+
814
825
  # @private
815
826
  def to_s
816
827
  "#{name} (#{version})"