berkshelf 3.0.0.beta1 → 3.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/CONTRIBUTING.md +2 -0
  4. data/LICENSE +1 -1
  5. data/README.md +1 -1
  6. data/Thorfile +2 -2
  7. data/berkshelf.gemspec +3 -3
  8. data/features/install_command.feature +36 -8
  9. data/features/json_formatter.feature +93 -3
  10. data/features/licenses.feature +1 -1
  11. data/features/lockfile.feature +0 -12
  12. data/features/outdated_command.feature +124 -0
  13. data/features/show_command.feature +44 -25
  14. data/features/step_definitions/chef/config_steps.rb +2 -2
  15. data/features/step_definitions/chef_server_steps.rb +9 -1
  16. data/features/step_definitions/config_steps.rb +1 -1
  17. data/features/step_definitions/filesystem_steps.rb +7 -0
  18. data/features/support/env.rb +2 -1
  19. data/features/update_command.feature +11 -21
  20. data/features/upload_command.feature +45 -1
  21. data/features/vendor_command.feature +83 -0
  22. data/lib/berkshelf.rb +5 -4
  23. data/lib/berkshelf/api_client/remote_cookbook.rb +13 -0
  24. data/lib/berkshelf/berksfile.rb +155 -23
  25. data/lib/berkshelf/chef.rb +0 -1
  26. data/lib/berkshelf/cli.rb +40 -31
  27. data/lib/berkshelf/dependency.rb +14 -4
  28. data/lib/berkshelf/errors.rb +74 -3
  29. data/lib/berkshelf/formatters.rb +12 -1
  30. data/lib/berkshelf/formatters/human_readable.rb +44 -5
  31. data/lib/berkshelf/formatters/json.rb +50 -8
  32. data/lib/berkshelf/installer.rb +8 -8
  33. data/lib/berkshelf/location.rb +17 -0
  34. data/lib/berkshelf/locations/git_location.rb +7 -17
  35. data/lib/berkshelf/locations/mercurial_location.rb +112 -0
  36. data/lib/berkshelf/lockfile.rb +1 -1
  37. data/lib/berkshelf/mercurial.rb +146 -0
  38. data/lib/berkshelf/version.rb +1 -1
  39. data/spec/config/knife.rb +2 -4
  40. data/spec/fixtures/lockfiles/default.lock +0 -1
  41. data/spec/support/chef_api.rb +9 -2
  42. data/spec/support/mercurial.rb +122 -0
  43. data/spec/support/path_helpers.rb +2 -2
  44. data/spec/unit/berkshelf/berksfile_spec.rb +34 -8
  45. data/spec/unit/berkshelf/dependency_spec.rb +0 -7
  46. data/spec/unit/berkshelf/formatters/null_spec.rb +1 -1
  47. data/spec/unit/berkshelf/locations/mercurial_location_spec.rb +150 -0
  48. data/spec/unit/berkshelf/lockfile_spec.rb +0 -12
  49. data/spec/unit/berkshelf/mercurial_spec.rb +173 -0
  50. metadata +32 -110
  51. data/lib/berkshelf/chef/config.rb +0 -68
  52. data/lib/berkshelf/mixin/config.rb +0 -172
  53. data/spec/fixtures/cookbooks/example_metadata_name/metadata.rb +0 -2
  54. data/spec/fixtures/cookbooks/example_metadata_no_name/metadata.rb +0 -1
  55. data/spec/fixtures/cookbooks/example_no_metadata/recipes/default.rb +0 -1
  56. data/spec/fixtures/cookbooks/nginx-0.100.5/README.md +0 -77
  57. data/spec/fixtures/cookbooks/nginx-0.100.5/attributes/default.rb +0 -65
  58. data/spec/fixtures/cookbooks/nginx-0.100.5/definitions/nginx_site.rb +0 -35
  59. data/spec/fixtures/cookbooks/nginx-0.100.5/files/default/mime.types +0 -73
  60. data/spec/fixtures/cookbooks/nginx-0.100.5/files/ubuntu/mime.types +0 -73
  61. data/spec/fixtures/cookbooks/nginx-0.100.5/libraries/nginxlib.rb +0 -1
  62. data/spec/fixtures/cookbooks/nginx-0.100.5/metadata.rb +0 -91
  63. data/spec/fixtures/cookbooks/nginx-0.100.5/providers/defprovider.rb +0 -1
  64. data/spec/fixtures/cookbooks/nginx-0.100.5/recipes/default.rb +0 -59
  65. data/spec/fixtures/cookbooks/nginx-0.100.5/resources/defresource.rb +0 -1
  66. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/nginx.pill.erb +0 -15
  67. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/plugins/nginx.rb.erb +0 -66
  68. data/spec/fixtures/lockfile_spec/with_lock/Berksfile +0 -1
  69. data/spec/fixtures/lockfile_spec/without_lock/.gitkeep +0 -0
  70. data/spec/fixtures/reset.pem +0 -27
  71. data/spec/unit/chef/config_spec.rb +0 -81
@@ -12,6 +12,16 @@ Feature: Displaying information about a cookbook defined by a Berksfile
12
12
 
13
13
  cookbook 'fake', '1.0.0'
14
14
  """
15
+ And I write to "Berksfile.lock" with:
16
+ """
17
+ {
18
+ "dependencies": {
19
+ "fake": {
20
+ "locked_version": "1.0.0"
21
+ }
22
+ }
23
+ }
24
+ """
15
25
  When I successfully run `berks show fake`
16
26
  Then the output should contain:
17
27
  """
@@ -23,43 +33,52 @@ Feature: Displaying information about a cookbook defined by a Berksfile
23
33
  License: none
24
34
  """
25
35
 
26
- Scenario: When JSON is requested
27
- Given the cookbook store has the cookbooks:
28
- | fake | 1.0.0 |
36
+ Scenario: When the cookbook is not in the Berksfile
37
+ Given I write to "Berksfile" with:
38
+ """
39
+ source "http://localhost:26210"
40
+ """
41
+ When I run `berks show fake`
42
+ Then the output should contain:
43
+ """
44
+ Could not find cookbook(s) 'fake' in any of the configured dependencies. Is it in your Berksfile?
45
+ """
46
+ And the exit status should be "DependencyNotFound"
47
+
48
+ Scenario: When there is no lockfile present
29
49
  And I write to "Berksfile" with:
30
50
  """
31
51
  source "http://localhost:26210"
32
52
 
33
53
  cookbook 'fake', '1.0.0'
34
54
  """
35
- When I successfully run `berks show fake --format json`
55
+ When I run `berks show fake`
36
56
  Then the output should contain:
37
57
  """
38
- {
39
- "cookbooks": [
40
- {
41
- "name": "fake",
42
- "version": "1.0.0",
43
- "description": "A fabulous new cookbook",
44
- "author": "YOUR_COMPANY_NAME",
45
- "email": "YOUR_EMAIL",
46
- "license": "none"
47
- }
48
- ],
49
- "errors": [
50
-
51
- ],
52
- "messages": [
53
- "building universe..."
54
- ]
55
- }
58
+ Could not find cookbook 'fake (>= 0.0.0)'. Try running `berks install` to download and install the missing dependencies.
56
59
  """
60
+ And the exit status should be "LockfileNotFound"
57
61
 
58
- Scenario: When the cookbook is not in the Berksfile
59
- Given I write to "Berksfile" with:
62
+ Scenario: When the cookbook is not installed
63
+ And I write to "Berksfile" with:
60
64
  """
61
65
  source "http://localhost:26210"
66
+
67
+ cookbook 'fake', '1.0.0'
68
+ """
69
+ And I write to "Berksfile.lock" with:
70
+ """
71
+ {
72
+ "dependencies": {
73
+ "fake": {
74
+ "locked_version": "1.0.0"
75
+ }
76
+ }
77
+ }
62
78
  """
63
79
  When I run `berks show fake`
64
- Then the output should contain "Cookbook 'fake' is not installed by your Berksfile"
80
+ Then the output should contain:
81
+ """
82
+ Could not find cookbook 'fake (= 1.0.0)'. Try running `berks install` to download and install the missing dependencies.
83
+ """
65
84
  And the exit status should be "CookbookNotFound"
@@ -4,8 +4,8 @@ end
4
4
 
5
5
  Given /^I do not have a Chef config$/ do
6
6
  path = tmp_path.join('knife.rb').to_s
7
- Berkshelf.chef_config = Berkshelf::Chef::Config.new(path)
8
- Berkshelf::Chef::Config.instance.save
7
+ Berkshelf.chef_config = Ridley::Chef::Config.new(path)
8
+ Berkshelf.chef_config.save
9
9
 
10
10
  ENV['BERKSHELF_CHEF_CONFIG'] = path
11
11
  set_env 'BERKSHELF_CHEF_CONFIG', path
@@ -8,10 +8,18 @@ Given /^the Chef Server has cookbooks:$/ do |cookbooks|
8
8
  cookbooks.raw.each do |name, version|
9
9
  purge_cookbook(name, version)
10
10
  cb_path = generate_cookbook(tmp_path, name, version)
11
- upload_cookbook(cb_path)
11
+ upload_cookbook(cb_path, freeze: false, force: true)
12
12
  end
13
13
  end
14
14
 
15
+ Given /^the Chef Server has frozen cookbooks:$/ do |cookbooks|
16
+ cookbooks.raw.each do |name, version|
17
+ purge_cookbook(name, version)
18
+ cb_path = generate_cookbook(tmp_path, name, version)
19
+ upload_cookbook(cb_path, freeze: true, force: true)
20
+ end
21
+ end
22
+
15
23
  Then /^the Chef Server should have the cookbooks:$/ do |cookbooks|
16
24
  cookbooks.raw.each do |name, version|
17
25
  expect(server_has_cookbook?(name, version)).to be_true
@@ -34,7 +34,7 @@ end
34
34
  Then /^a Berkshelf config file should exist at "(.+)" and contain:$/ do |path, table|
35
35
  check_file_presence([path], true)
36
36
 
37
- path = File.expand_path(File.join('tmp', 'aruba', path))
37
+ path = File.join(@dirs.first, path)
38
38
  Berkshelf.config = Berkshelf::Config.from_file(path)
39
39
 
40
40
  table.raw.each do |key, value|
@@ -250,3 +250,10 @@ Then /^the file "(.*?)" in the directory "(.*?)" should not contain:$/ do |file_
250
250
  end
251
251
  }
252
252
  end
253
+
254
+ Then(/^the directory "(.*?)" should contain version "(.*?)" of the "(.*?)" cookbook$/) do |path, version, name|
255
+ cookbook_path = File.join(current_dir, path)
256
+ cookbook = Berkshelf::CachedCookbook.from_path(cookbook_path)
257
+ expect(cookbook.version).to eql(version)
258
+ expect(cookbook.cookbook_name).to eql(name)
259
+ end
@@ -29,6 +29,7 @@ Spork.prefork do
29
29
 
30
30
  Aruba::InProcess.main_class = Berkshelf::Cli::Runner
31
31
  Aruba.process = Aruba::InProcess
32
+ @dirs = ["spec/tmp/aruba"] # set aruba's temporary directory
32
33
 
33
34
  stub_kitchen!
34
35
  clean_tmp_path
@@ -42,7 +43,7 @@ Spork.prefork do
42
43
  options: {
43
44
  url: "http://localhost:#{CHEF_SERVER_PORT}",
44
45
  client_name: "reset",
45
- client_key: fixtures_path.join("reset.pem")
46
+ client_key: File.expand_path("spec/config/berkshelf.pem")
46
47
  }
47
48
  }
48
49
  ]
@@ -23,8 +23,7 @@ Feature: Updating a cookbook defined by a Berksfile
23
23
  {
24
24
  "dependencies":{
25
25
  "berkshelf-cookbook-fixture":{
26
- "locked_version":"0.1.0",
27
- "constraint":"~> 0.1"
26
+ "locked_version":"0.1.0"
28
27
  }
29
28
  }
30
29
  }
@@ -47,12 +46,10 @@ Feature: Updating a cookbook defined by a Berksfile
47
46
  {
48
47
  "dependencies":{
49
48
  "berkshelf-cookbook-fixture":{
50
- "locked_version":"0.1.0",
51
- "constraint":"~> 0.1"
49
+ "locked_version":"0.1.0"
52
50
  },
53
51
  "hostsfile":{
54
- "locked_version":"1.0.1",
55
- "constraint":"= 1.0.1"
52
+ "locked_version":"1.0.1"
56
53
  }
57
54
  }
58
55
  }
@@ -63,12 +60,10 @@ Feature: Updating a cookbook defined by a Berksfile
63
60
  {
64
61
  "dependencies":{
65
62
  "berkshelf-cookbook-fixture":{
66
- "locked_version":"0.2.0",
67
- "constraint":"~> 0.1"
63
+ "locked_version":"0.2.0"
68
64
  },
69
65
  "hostsfile":{
70
- "locked_version":"1.0.1",
71
- "constraint":"~> 1.0.0"
66
+ "locked_version":"1.0.1"
72
67
  }
73
68
  }
74
69
  }
@@ -91,12 +86,10 @@ Feature: Updating a cookbook defined by a Berksfile
91
86
  {
92
87
  "dependencies":{
93
88
  "berkshelf-cookbook-fixture":{
94
- "locked_version":"0.1.0",
95
- "constraint":"~> 0.1"
89
+ "locked_version":"0.1.0"
96
90
  },
97
91
  "hostsfile":{
98
- "locked_version":"1.0.0",
99
- "constraint":"~> 1.0.0"
92
+ "locked_version":"1.0.1"
100
93
  }
101
94
  }
102
95
  }
@@ -107,12 +100,10 @@ Feature: Updating a cookbook defined by a Berksfile
107
100
  {
108
101
  "dependencies":{
109
102
  "berkshelf-cookbook-fixture":{
110
- "locked_version":"0.2.0",
111
- "constraint":"~> 0.1"
103
+ "locked_version":"0.2.0"
112
104
  },
113
105
  "hostsfile":{
114
- "locked_version":"1.0.0",
115
- "constraint":"~> 1.0.0"
106
+ "locked_version":"1.0.1"
116
107
  }
117
108
  }
118
109
  }
@@ -132,8 +123,7 @@ Feature: Updating a cookbook defined by a Berksfile
132
123
  {
133
124
  "dependencies":{
134
125
  "berkshelf-cookbook-fixture":{
135
- "locked_version":"0.1.0",
136
- "constraint":"~> 0.1"
126
+ "locked_version":"0.1.0"
137
127
  }
138
128
  }
139
129
  }
@@ -143,4 +133,4 @@ Feature: Updating a cookbook defined by a Berksfile
143
133
  """
144
134
  Could not find cookbook(s) 'non-existent-cookbook' in any of the configured dependencies. Is it in your Berksfile?
145
135
  """
146
- And the exit status should be "CookbookNotFound"
136
+ And the exit status should be "DependencyNotFound"
@@ -100,7 +100,7 @@ Feature: Uploading cookbooks to a Chef Server
100
100
  When I run `berks upload reset`
101
101
  Then the output should contain:
102
102
  """
103
- Failed to upload cookbook 'reset'. Not defined in Berksfile.
103
+ Could not find cookbook(s) 'reset' in any of the configured dependencies. Is it in your Berksfile?
104
104
  """
105
105
  And the exit status should be "DependencyNotFound"
106
106
 
@@ -281,3 +281,47 @@ Feature: Uploading cookbooks to a Chef Server
281
281
  """
282
282
  Uploading fake (0.0.0)
283
283
  """
284
+
285
+ Scenario: When the cookbook already exist
286
+ Given the cookbook store has the cookbooks:
287
+ | fake | 1.0.0 |
288
+ And the Chef Server has frozen cookbooks:
289
+ | fake | 1.0.0 |
290
+ And I write to "Berksfile" with:
291
+ """
292
+ cookbook 'fake', '1.0.0'
293
+ """
294
+ When I successfully run `berks upload`
295
+ Then the output should contain:
296
+ """
297
+ Skipping fake (1.0.0) (already uploaded)
298
+ """
299
+ And the output should contain:
300
+ """
301
+ Skipped uploading some cookbooks because they already existed on the remote server. Re-run with the `--force` flag to force overwrite these cookbooks:
302
+
303
+ * fake (1.0.0)
304
+ """
305
+ And the exit status should be 0
306
+
307
+ Scenario: When the cookbook already exist and is a metadata location
308
+ Given a cookbook named "fake"
309
+ And the cookbook "fake" has the file "Berksfile" with:
310
+ """
311
+ metadata
312
+ """
313
+ When I cd to "fake"
314
+ And the Chef Server has frozen cookbooks:
315
+ | fake | 0.0.0 |
316
+ When I run `berks upload`
317
+ Then the output should contain:
318
+ """
319
+ building universe...
320
+ Using fake (0.0.0) path: '/home/travis/build/RiotGames/berkshelf/spec/tmp/aruba/fake'
321
+ Uploading fake (0.0.0) to: 'http://localhost:26310/'
322
+ Skipping fake (0.0.0) (already uploaded)
323
+ Skipped uploading some cookbooks because they already existed on the remote server. Re-run with the `--force` flag to force overwrite these cookbooks:
324
+
325
+ * fake (0.0.0)
326
+ """
327
+ And the exit status should be 0
@@ -0,0 +1,83 @@
1
+ Feature: Vendoring cookbooks to a directory
2
+ As a CLI user
3
+ I want a command to vendor cookbooks into a directory
4
+ So they are structured similar to a Chef Repository
5
+
6
+ Background:
7
+ Given the Berkshelf API server's cache is empty
8
+ And the Chef Server is empty
9
+
10
+ Scenario: successfully vendoring a Berksfile with multiple cookbook demands
11
+ Given I write to "Berksfile" with:
12
+ """
13
+ source "http://localhost:26210"
14
+
15
+ cookbook 'berkshelf'
16
+ cookbook 'elixir'
17
+ """
18
+ And the Chef Server has cookbooks:
19
+ | berkshelf | 1.0.0 |
20
+ | elixir | 1.0.0 |
21
+ And the Berkshelf API server's cache is up to date
22
+ When I successfully run `berks vendor cukebooks`
23
+ Then the output should contain:
24
+ """
25
+ Vendoring berkshelf (1.0.0) to
26
+ """
27
+ And the output should contain:
28
+ """
29
+ Vendoring elixir (1.0.0) to
30
+ """
31
+ And a directory named "cukebooks/berkshelf" should exist
32
+ And a directory named "cukebooks/elixir" should exist
33
+ And the directory "cukebooks/berkshelf" should contain version "1.0.0" of the "berkshelf" cookbook
34
+ And the directory "cukebooks/elixir" should contain version "1.0.0" of the "elixir" cookbook
35
+
36
+ Scenario: attempting to vendor when no Berksfile is present
37
+ When I run `berks vendor cukebooks`
38
+ Then the exit status should be "BerksfileNotFound"
39
+
40
+ Scenario: vendoring a Berksfile with a metadata demand
41
+ Given a cookbook named "sparkle-motion"
42
+ And the cookbook "sparkle-motion" has the file "Berksfile" with:
43
+ """
44
+ source "http://localhost:26210"
45
+
46
+ metadata
47
+ """
48
+ When I cd to "sparkle-motion"
49
+ And I successfully run `berks vendor cukebooks`
50
+ Then the output should contain:
51
+ """
52
+ Vendoring sparkle-motion (0.0.0) to
53
+ """
54
+ And a directory named "cukebooks/sparkle-motion" should exist
55
+ And the directory "cukebooks/sparkle-motion" should contain version "0.0.0" of the "sparkle-motion" cookbook
56
+
57
+ Scenario: vendoring without an explicit path to vendor into
58
+ Given I write to "Berksfile" with:
59
+ """
60
+ source "http://localhost:26210"
61
+
62
+ cookbook 'berkshelf'
63
+ """
64
+ And the Chef Server has cookbooks:
65
+ | berkshelf | 1.0.0 |
66
+ And the Berkshelf API server's cache is up to date
67
+ When I successfully run `berks vendor`
68
+ And a directory named "berks-cookbooks/berkshelf" should exist
69
+ And the directory "berks-cookbooks/berkshelf" should contain version "1.0.0" of the "berkshelf" cookbook
70
+
71
+ Scenario: vendoring to a directory that already exists
72
+ Given I write to "Berksfile" with:
73
+ """
74
+ source "http://localhost:26210"
75
+
76
+ cookbook 'berkshelf'
77
+ """
78
+ And the Chef Server has cookbooks:
79
+ | berkshelf | 1.0.0 |
80
+ And the Berkshelf API server's cache is up to date
81
+ And a directory named "cukebooks"
82
+ When I run `berks vendor cukebooks`
83
+ And the exit status should be "VendorError"
@@ -67,14 +67,14 @@ module Berkshelf
67
67
 
68
68
  # The Chef configuration file.
69
69
  #
70
- # @return [Berkshelf::Chef::Config]
70
+ # @return [Ridley::Chef::Config]
71
71
  def chef_config
72
- Berkshelf::Chef::Config.instance
72
+ @chef_config ||= Ridley::Chef::Config.new(ENV['BERKSHELF_CHEF_CONFIG'])
73
73
  end
74
74
 
75
- # @param [Berkshelf::Chef::Config]
75
+ # @param [Ridley::Chef::Config]
76
76
  def chef_config=(config)
77
- Berkshelf::Chef::Config.set_config(config)
77
+ @chef_config = config
78
78
  end
79
79
 
80
80
  # Initialize the filepath for the Berkshelf path..
@@ -153,6 +153,7 @@ require_relative 'berkshelf/dependency'
153
153
  require_relative 'berkshelf/downloader'
154
154
  require_relative 'berkshelf/formatters'
155
155
  require_relative 'berkshelf/git'
156
+ require_relative 'berkshelf/mercurial'
156
157
  require_relative 'berkshelf/init_generator'
157
158
  require_relative 'berkshelf/installer'
158
159
  require_relative 'berkshelf/location'
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+
1
3
  module Berkshelf
2
4
  class APIClient
3
5
  # A representation of cookbook metadata indexed by a Berkshelf API Server. Returned
@@ -37,6 +39,17 @@ module Berkshelf
37
39
  def location_path
38
40
  @attributes[:location_path]
39
41
  end
42
+
43
+ def to_hash
44
+ {
45
+ name: name,
46
+ version: version
47
+ }
48
+ end
49
+
50
+ def to_json(options = {})
51
+ ::JSON.pretty_generate(to_hash, options)
52
+ end
40
53
  end
41
54
  end
42
55
  end
@@ -13,11 +13,13 @@ module Berkshelf
13
13
  #
14
14
  # @return [Berksfile]
15
15
  def from_file(file)
16
- new(file).dsl_eval_file(file)
17
- rescue Errno::ENOENT => ex
18
- raise BerksfileNotFound, "No Berksfile or Berksfile.lock found at: #{file}"
19
- rescue => ex
20
- raise BerksfileReadError.new(ex)
16
+ raise BerksfileNotFound.new(file) unless File.exist?(file)
17
+
18
+ begin
19
+ new(file).dsl_eval_file(file)
20
+ rescue => ex
21
+ raise BerksfileReadError.new(ex)
22
+ end
21
23
  end
22
24
  end
23
25
 
@@ -365,6 +367,40 @@ module Berkshelf
365
367
  self.install
366
368
  end
367
369
 
370
+ # Retrieve information about a given cookbook that is installed by this Berksfile.
371
+ # Unlike {#find}, which returns a dependency, this method returns the corresponding
372
+ # CachedCookbook for the given name.
373
+ #
374
+ # @raise [LockfileNotFound]
375
+ # if there is no lockfile containing that cookbook
376
+ # @raise [CookbookNotFound]
377
+ # if there is a lockfile with a cookbook, but the cookbook is not downloaded
378
+ #
379
+ # @param [String] name
380
+ # the name of the cookbook to find
381
+ #
382
+ # @return [CachedCookbook]
383
+ # the CachedCookbook that corresponds to the given name parameter
384
+ def retrieve_locked(name)
385
+ validate_cookbook_names!(cookbooks: name)
386
+
387
+ locked = lockfile.find(name)
388
+ unless locked
389
+ raise LockfileNotFound, "Could not find cookbook '#{name} (>= 0.0.0)'."\
390
+ " Try running `berks install` to download and install the missing"\
391
+ " dependencies."
392
+ end
393
+
394
+ cookbook = locked.cached_cookbook
395
+ unless cookbook
396
+ raise CookbookNotFound, "Could not find cookbook '#{name} (= #{locked.locked_version})'."\
397
+ " Try running `berks install` to download and install the missing"\
398
+ " dependencies."
399
+ end
400
+
401
+ cookbook
402
+ end
403
+
368
404
  # List of all the cookbooks which have a newer version found at a source that satisfies
369
405
  # the constraints of your dependencies
370
406
  #
@@ -386,7 +422,28 @@ module Berkshelf
386
422
  # #<CachedCookbook name="artifact"> => "0.11.2"
387
423
  # }
388
424
  def outdated(options = {})
389
- raise RuntimeError, "not yet implemented"
425
+ validate_cookbook_names!(options)
426
+
427
+ outdated = {}
428
+ dependencies(options).each do |dependency|
429
+ locked = retrieve_locked(dependency.name)
430
+ outdated[dependency.name] = {}
431
+
432
+ sources.each do |source|
433
+ cookbooks = source.versions(dependency.name)
434
+
435
+ latest = cookbooks.select do |cookbook|
436
+ dependency.version_constraint.satisfies?(cookbook.version) &&
437
+ cookbook.version != locked.version
438
+ end.sort_by { |cookbook| cookbook.version }.last
439
+
440
+ unless latest.nil?
441
+ outdated[dependency.name][source.uri.to_s] = latest
442
+ end
443
+ end
444
+ end
445
+
446
+ outdated.reject { |name, newer| newer.empty? }
390
447
  end
391
448
 
392
449
  # Upload the cookbooks installed by this Berksfile
@@ -417,16 +474,19 @@ module Berkshelf
417
474
  # @raise [Berkshelf::DependencyNotFound]
418
475
  # if one of the given cookbooks is not a dependency defined in the Berksfile
419
476
  # @raise [Berkshelf::FrozenCookbook]
420
- # if an attempt to upload a cookbook which has been frozen on the target server is made
421
- # and the :halt_on_frozen option was true
477
+ # if the cookbook being uploaded is a {metadata} cookbook and is already
478
+ # frozen on the remote Chef Server; indirect dependencies or non-metadata
479
+ # dependencies are just skipped
422
480
  def upload(options = {})
423
- options = options.reverse_merge(force: false, freeze: true, halt_on_frozen: false, cookbooks: [])
481
+ options = {
482
+ force: false,
483
+ freeze: true,
484
+ halt_on_frozen: false,
485
+ skip_dependencies: false,
486
+ cookbooks: [],
487
+ }.merge(options)
424
488
 
425
- options[:cookbooks].each do |cookbook|
426
- unless dependency = find(cookbook)
427
- raise DependencyNotFound, "Failed to upload cookbook '#{cookbook}'. Not defined in Berksfile."
428
- end
429
- end
489
+ validate_cookbook_names!(options)
430
490
 
431
491
  cached_cookbooks = install(options)
432
492
  cached_cookbooks = filter_to_upload(cached_cookbooks, options[:cookbooks]) if options[:cookbooks]
@@ -495,9 +555,12 @@ module Berkshelf
495
555
  cached_cookbooks.each { |cookbook| validate_files!(cookbook) }
496
556
 
497
557
  Dir.mktmpdir do |tmp|
558
+ cookbooks_dir = File.join(tmp, 'cookbooks')
559
+ FileUtils.mkdir_p(cookbooks_dir)
560
+
498
561
  cached_cookbooks.each do |cookbook|
499
562
  path = cookbook.path.to_s
500
- destination = File.join(tmp, cookbook.cookbook_name)
563
+ destination = File.join(cookbooks_dir, cookbook.cookbook_name)
501
564
 
502
565
  FileUtils.cp_r(path, destination)
503
566
 
@@ -522,6 +585,61 @@ module Berkshelf
522
585
  output
523
586
  end
524
587
 
588
+ # Install the Berksfile or Berksfile.lock and then copy the cached cookbooks into
589
+ # directories within the given destination matching their name.
590
+ #
591
+ # @param [String] destination
592
+ # filepath to vendor cookbooks to
593
+ #
594
+ # @option options [Symbol, Array] :except
595
+ # Group(s) to exclude which will cause any dependencies marked as a member of the
596
+ # group to not be installed
597
+ # @option options [Symbol, Array] :only
598
+ # Group(s) to include which will cause any dependencies marked as a member of the
599
+ # group to be installed and all others to be ignored
600
+ #
601
+ # @return [String, nil]
602
+ # the expanded path cookbooks were vendored to or nil if nothing was vendored
603
+ def vendor(destination, options = {})
604
+ destination = File.expand_path(destination)
605
+
606
+ if Dir.exist?(destination)
607
+ raise VendorError, "destination already exists #{destination}. Delete it and try again or use a " +
608
+ "different filepath."
609
+ end
610
+
611
+ scratch = Berkshelf.mktmpdir
612
+ chefignore = nil
613
+ cached_cookbooks = install(options.slice(:except, :only))
614
+
615
+ if cached_cookbooks.empty?
616
+ return nil
617
+ end
618
+
619
+ if ignore_file = Berkshelf::Chef::Cookbook::Chefignore.find_relative_to(Dir.pwd)
620
+ chefignore = Berkshelf::Chef::Cookbook::Chefignore.new(ignore_file)
621
+ end
622
+
623
+ cached_cookbooks.each do |cookbook|
624
+ Berkshelf.formatter.vendor(cookbook, destination)
625
+ cookbook_destination = File.join(scratch, cookbook.cookbook_name, '/')
626
+ FileUtils.mkdir_p(cookbook_destination)
627
+
628
+ # Dir.glob does not support backslash as a File separator
629
+ src = cookbook.path.to_s.gsub('\\', '/')
630
+ files = Dir.glob(File.join(src, '*'))
631
+
632
+ if chefignore
633
+ files = chefignore.remove_ignores_from(files)
634
+ end
635
+
636
+ FileUtils.cp_r(files, cookbook_destination)
637
+ end
638
+
639
+ FileUtils.mv(scratch, destination)
640
+ destination
641
+ end
642
+
525
643
  # Get the lockfile corresponding to this Berksfile. This is necessary because
526
644
  # the user can specify a different path to the Berksfile. So assuming the lockfile
527
645
  # is named "Berksfile.lock" is a poor assumption.
@@ -536,22 +654,37 @@ module Berkshelf
536
654
  private
537
655
 
538
656
  def do_upload(cookbooks, options = {})
539
- upload_opts = options.slice(:force, :freeze)
657
+ @skipped = []
540
658
 
541
659
  ridley_connection(options) do |conn|
542
660
  cookbooks.each do |cookbook|
543
- Berkshelf.formatter.upload(cookbook.cookbook_name, cookbook.version, conn.server_url)
661
+ Berkshelf.formatter.upload(cookbook, conn)
544
662
  validate_files!(cookbook)
545
663
 
546
664
  begin
547
- conn.cookbook.upload(cookbook.path, upload_opts.merge(name: cookbook.cookbook_name))
665
+ conn.cookbook.upload(cookbook.path, {
666
+ force: options[:force],
667
+ freeze: options[:freeze],
668
+ name: cookbook.cookbook_name,
669
+ })
548
670
  rescue Ridley::Errors::FrozenCookbook => ex
549
671
  if options[:halt_on_frozen]
550
- raise Berkshelf::FrozenCookbook, ex
672
+ raise Berkshelf::FrozenCookbook.new(cookbook)
551
673
  end
674
+
675
+ Berkshelf.formatter.skip(cookbook, conn)
676
+ @skipped << cookbook
552
677
  end
553
678
  end
554
679
  end
680
+
681
+ unless @skipped.empty?
682
+ Berkshelf.formatter.msg "Skipped uploading some cookbooks because they" <<
683
+ " already existed on the remote server. Re-run with the `--force`" <<
684
+ " flag to force overwrite these cookbooks:" <<
685
+ "\n\n" <<
686
+ " * " << @skipped.map { |c| "#{c.cookbook_name} (#{c.version})" }.join("\n * ")
687
+ end
555
688
  end
556
689
 
557
690
  # Filter the cookbooks to upload based on a set of given names. The dependencies of a cookbook
@@ -608,14 +741,13 @@ module Berkshelf
608
741
  # @option options [Array<String>] :cookbooks
609
742
  # a list of strings of cookbook names
610
743
  #
611
- # @raise [Berkshelf::CookbookNotFound]
744
+ # @raise [Berkshelf::DependencyNotFound]
612
745
  # if a cookbook name is given that does not exist
613
746
  def validate_cookbook_names!(options = {})
614
747
  missing = (Array(options[:cookbooks]) - dependencies.map(&:name))
748
+
615
749
  unless missing.empty?
616
- raise Berkshelf::CookbookNotFound,
617
- "Could not find cookbook(s) #{missing.collect{ |c| "'#{c}'" }.join(', ')} " +
618
- "in any of the configured dependencies. #{missing.size == 1 ? 'Is it' : 'Are they' } in your Berksfile?"
750
+ raise Berkshelf::DependencyNotFound.new(missing)
619
751
  end
620
752
  end
621
753