berkshelf 4.0.1 → 4.1.0

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