berkshelf 1.0.4 → 1.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,3 +69,200 @@ Feature: upload command
69
69
  And the Chef server should have the cookbooks:
70
70
  | artifact | 0.9.8 |
71
71
  And the exit status should be 0
72
+
73
+ @chef_server @slow_process
74
+ Scenario: Running the upload command for a single cookbook
75
+ Given I write to "Berksfile" with:
76
+ """
77
+ cookbook "build-essential", "1.2.0"
78
+ cookbook "mysql", "1.2.4"
79
+ """
80
+ And I successfully run `berks install`
81
+ And the Chef server does not have the cookbooks:
82
+ | mysql | 1.2.4 |
83
+ | openssl | 1.0.0 |
84
+ | build-essential | 1.2.0 |
85
+ When I run `berks upload mysql`
86
+ Then the output should contain "Uploading mysql (1.2.4)"
87
+ And the output should contain "Uploading openssl (1.0.0)"
88
+ And the output should not contain "Uploading build-essential (1.2.0)"
89
+ And the Chef server should have the cookbooks:
90
+ | mysql | 1.2.4 |
91
+ | openssl | 1.0.0 |
92
+ And the Chef server should not have the cookbooks:
93
+ | build-essential | 1.2.0 |
94
+ And the exit status should be 0
95
+
96
+ @chef_server @slow_process
97
+ Scenario: Running the upload command with multiple cookbooks
98
+ Given I write to "Berksfile" with:
99
+ """
100
+ cookbook "build-essential"
101
+ cookbook "chef-client"
102
+ cookbook "database"
103
+ cookbook "editor"
104
+ cookbook "git"
105
+ cookbook "known_host"
106
+ cookbook "networking_basic"
107
+ cookbook "vim"
108
+ """
109
+ And I successfully run `berks install`
110
+ And the Chef server does not have the cookbooks:
111
+ | build-essential |
112
+ | chef-client |
113
+ | database |
114
+ | editor |
115
+ | git |
116
+ | known_host |
117
+ | networking_basic |
118
+ | vim |
119
+ When I run `berks upload build-essential chef-client database`
120
+ Then the output should contain "Uploading build-essential"
121
+ And the output should contain "Uploading chef-client"
122
+ And the output should contain "Uploading database"
123
+ And the output should not contain "Uploading editor"
124
+ And the output should not contain "Uploading git"
125
+ And the output should not contain "Uploading known_host"
126
+ And the output should not contain "Uploading networking_basic"
127
+ And the output should not contain "Uploading vim"
128
+ And the Chef server should have the cookbooks:
129
+ | build_essential |
130
+ | chef-client |
131
+ | database |
132
+ And the Chef server should not have the cookbooks:
133
+ | editor |
134
+ | git |
135
+ | known_host |
136
+ | networking_basic |
137
+ | vim |
138
+ And the exit status should be 0
139
+
140
+ @chef_server @slow_process
141
+ Scenario: Running the upload command with the :only option
142
+ Given I write to "Berksfile" with:
143
+ """
144
+ group :core do
145
+ cookbook "build-essential"
146
+ cookbook "chef-client"
147
+ end
148
+
149
+ group :system do
150
+ cookbook "database"
151
+ cookbook "editor"
152
+ end
153
+ """
154
+ And I successfully run `berks install`
155
+ And the Chef server does not have the cookbooks:
156
+ | build-essential |
157
+ | chef-client |
158
+ | database |
159
+ | editor |
160
+ When I run `berks upload --only core`
161
+ Then the output should contain "Uploading build-essential"
162
+ And the output should contain "Uploading chef-client"
163
+ And the output should not contain "Uploading database"
164
+ And the output should not contain "Uploading editor"
165
+ And the Chef server should have the cookbooks:
166
+ | build_essential |
167
+ | chef-client |
168
+ And the Chef server should not have the cookbooks:
169
+ | database |
170
+ | editor |
171
+ And the exit status should be 0
172
+
173
+ @chef_server @slow_process
174
+ Scenario: Running the upload command with the :only option multiple
175
+ Given I write to "Berksfile" with:
176
+ """
177
+ group :core do
178
+ cookbook "build-essential"
179
+ cookbook "chef-client"
180
+ end
181
+
182
+ group :system do
183
+ cookbook "database"
184
+ cookbook "editor"
185
+ end
186
+ """
187
+ And I successfully run `berks install`
188
+ And the Chef server does not have the cookbooks:
189
+ | build-essential |
190
+ | chef-client |
191
+ | database |
192
+ | editor |
193
+ When I run `berks upload --only core system`
194
+ Then the output should contain "Uploading build-essential"
195
+ And the output should contain "Uploading chef-client"
196
+ And the output should contain "Uploading database"
197
+ And the output should contain "Uploading editor"
198
+ And the Chef server should have the cookbooks:
199
+ | build_essential |
200
+ | chef-client |
201
+ | database |
202
+ | editor |
203
+ And the exit status should be 0
204
+
205
+ @chef_server @slow_process
206
+ Scenario: Running the upload command with the :except option
207
+ Given I write to "Berksfile" with:
208
+ """
209
+ group :core do
210
+ cookbook "build-essential"
211
+ cookbook "chef-client"
212
+ end
213
+
214
+ group :system do
215
+ cookbook "database"
216
+ cookbook "editor"
217
+ end
218
+ """
219
+ And I successfully run `berks install`
220
+ And the Chef server does not have the cookbooks:
221
+ | build-essential |
222
+ | chef-client |
223
+ | database |
224
+ | editor |
225
+ When I run `berks upload --except core`
226
+ Then the output should not contain "Uploading build-essential"
227
+ And the output should not contain "Uploading chef-client"
228
+ And the output should contain "Uploading database"
229
+ And the output should contain "Uploading editor"
230
+ And the Chef server should not have the cookbooks:
231
+ | build_essential |
232
+ | chef-client |
233
+ And the Chef server should have the cookbooks:
234
+ | database |
235
+ | editor |
236
+ And the exit status should be 0
237
+
238
+ @chef_server @slow_process
239
+ Scenario: Running the upload command with the :except option multiple
240
+ Given I write to "Berksfile" with:
241
+ """
242
+ group :core do
243
+ cookbook "build-essential"
244
+ cookbook "chef-client"
245
+ end
246
+
247
+ group :system do
248
+ cookbook "database"
249
+ cookbook "editor"
250
+ end
251
+ """
252
+ And I successfully run `berks install`
253
+ And the Chef server does not have the cookbooks:
254
+ | build-essential |
255
+ | chef-client |
256
+ | database |
257
+ | editor |
258
+ When I run `berks upload --except core system`
259
+ Then the output should not contain "Uploading build-essential"
260
+ And the output should not contain "Uploading chef-client"
261
+ And the output should not contain "Uploading database"
262
+ And the output should not contain "Uploading editor"
263
+ And the Chef server should not have the cookbooks:
264
+ | build_essential |
265
+ | chef-client |
266
+ | database |
267
+ | editor |
268
+ And the exit status should be 0
@@ -239,7 +239,11 @@ module Berkshelf
239
239
  # @return [Array<Berkshelf::CookbookSource]
240
240
  def add_source(name, constraint = nil, options = {})
241
241
  if has_source?(name)
242
- raise DuplicateSourceDefined, "Berksfile contains two sources named '#{name}'. Remove one and try again."
242
+ # Only raise an exception if the source is a true duplicate
243
+ groups = (options[:group].nil? || options[:group].empty?) ? [:default] : options[:group]
244
+ if !(@sources[name].groups & groups).empty?
245
+ raise DuplicateSourceDefined, "Berksfile contains multiple sources named '#{name}'. Use only one, or put them in different groups."
246
+ end
243
247
  end
244
248
 
245
249
  options[:constraint] = constraint
@@ -269,6 +273,8 @@ module Berkshelf
269
273
  # @option options [Symbol, Array] :only
270
274
  # Group(s) to include which will cause any sources marked as a member of the
271
275
  # group to be installed and all others to be ignored
276
+ # @option cookbooks [String, Array] :cookbooks
277
+ # Names of the cookbooks to retrieve sources for
272
278
  #
273
279
  # @raise [Berkshelf::ArgumentError] if a value for both :except and :only is provided
274
280
  #
@@ -276,12 +282,18 @@ module Berkshelf
276
282
  def sources(options = {})
277
283
  l_sources = @sources.collect { |name, source| source }.flatten
278
284
 
279
- except = Array(options.fetch(:except, nil)).collect(&:to_sym)
280
- only = Array(options.fetch(:only, nil)).collect(&:to_sym)
285
+ cookbooks = Array(options.fetch(:cookbooks, nil))
286
+ except = Array(options.fetch(:except, nil)).collect(&:to_sym)
287
+ only = Array(options.fetch(:only, nil)).collect(&:to_sym)
281
288
 
282
289
  case
283
290
  when !except.empty? && !only.empty?
284
291
  raise Berkshelf::ArgumentError, "Cannot specify both :except and :only"
292
+ when !cookbooks.empty?
293
+ if !except.empty? && !only.empty?
294
+ Berkshelf.ui.warn "Cookbooks were specified, ignoring :except and :only"
295
+ end
296
+ l_sources.select { |source| options[:cookbooks].include?(source.name) }
285
297
  when !except.empty?
286
298
  l_sources.select { |source| (except & source.groups).empty? }
287
299
  when !only.empty?
@@ -352,6 +364,75 @@ module Berkshelf
352
364
  self.cached_cookbooks
353
365
  end
354
366
 
367
+ # @option options [Symbol, Array] :except
368
+ # Group(s) to exclude which will cause any sources marked as a member of the
369
+ # group to not be installed
370
+ # @option options [Symbol, Array] :only
371
+ # Group(s) to include which will cause any sources marked as a member of the
372
+ # group to be installed and all others to be ignored
373
+ # @option cookbooks [String, Array] :cookbooks
374
+ # Names of the cookbooks to retrieve sources for
375
+ def update(options = {})
376
+ resolver = Resolver.new(
377
+ self.downloader,
378
+ sources: sources(options)
379
+ )
380
+
381
+ cookbooks = resolver.resolve
382
+ sources = resolver.sources
383
+ missing_cookbooks = (options[:cookbooks] - cookbooks.map(&:cookbook_name))
384
+
385
+ unless missing_cookbooks.empty?
386
+ raise Berkshelf::CookbookNotFound, "Could not find cookbooks #{missing_cookbooks.collect{|cookbook| "'#{cookbook}'"}.join(', ')} in any of the sources. #{missing_cookbooks.size == 1 ? 'Is it' : 'Are they' } in your Berksfile?"
387
+ end
388
+
389
+ update_lockfile(sources)
390
+
391
+ if options[:path]
392
+ self.class.vendor(cookbooks, options[:path])
393
+ end
394
+
395
+ cookbooks
396
+ end
397
+
398
+ # Get a list of all the cookbooks which have newer versions found on the community
399
+ # site versus what your current constraints allow
400
+ #
401
+ # @option options [Symbol, Array] :except
402
+ # Group(s) to exclude which will cause any sources marked as a member of the
403
+ # group to not be installed
404
+ # @option options [Symbol, Array] :only
405
+ # Group(s) to include which will cause any sources marked as a member of the
406
+ # group to be installed and all others to be ignored
407
+ # @option cookbooks [String, Array] :cookbooks
408
+ # Names of the cookbooks to retrieve sources for
409
+ #
410
+ # @return [Hash]
411
+ # a hash of cached cookbooks and their latest version. An empty hash is returned
412
+ # if there are no newer cookbooks for any of your sources
413
+ #
414
+ # @example
415
+ # berksfile.outdated => {
416
+ # <#CachedCookbook name="artifact"> => "0.11.2"
417
+ # }
418
+ def outdated(options = {})
419
+ outdated = Hash.new
420
+
421
+ sources(options).each do |cookbook|
422
+ location = cookbook.location || Location.init(cookbook.name, cookbook.version_constraint)
423
+
424
+ if location.is_a?(SiteLocation)
425
+ latest_version = SiteLocation.new(cookbook.name, cookbook.version_constraint).latest_version[0]
426
+
427
+ unless cookbook.version_constraint.satisfies?(latest_version)
428
+ outdated[cookbook] = latest_version
429
+ end
430
+ end
431
+ end
432
+
433
+ outdated
434
+ end
435
+
355
436
  # @option options [String] :server_url
356
437
  # URL to the Chef API
357
438
  # @option options [String] :client_name
@@ -372,6 +453,8 @@ module Berkshelf
372
453
  # @option options [Symbol, Array] :only
373
454
  # Group(s) to include which will cause any sources marked as a member of the
374
455
  # group to be installed and all others to be ignored
456
+ # @option cookbooks [String, Array] :cookbooks
457
+ # Names of the cookbooks to retrieve sources for
375
458
  # @option options [Integer] :thread_count
376
459
  # @option options [Hash] :params
377
460
  # URI query unencoded key/value pairs
@@ -383,6 +466,8 @@ module Berkshelf
383
466
  # SSL options
384
467
  # @option options [URI, String, Hash] :proxy
385
468
  # URI, String, or Hash of HTTP proxy options
469
+ #
470
+ # @raise [UploadFailure] if you are uploading cookbooks with an invalid or not-specified client key
386
471
  def upload(options = {})
387
472
  uploader = Uploader.new(options)
388
473
  solution = resolve(options)
@@ -391,6 +476,11 @@ module Berkshelf
391
476
  Berkshelf.formatter.upload cb.cookbook_name, cb.version, options[:server_url]
392
477
  uploader.upload(cb, options)
393
478
  end
479
+
480
+ rescue Ridley::Errors::ClientKeyFileNotFound => e
481
+ msg = "Could not upload cookbooks: Missing Chef client key: '#{Berkshelf::Config.instance.chef.client_key}'."
482
+ msg << " Generate or update your Berkshelf configuration that contains a valid path to a Chef client key."
483
+ raise UploadFailure, msg
394
484
  end
395
485
 
396
486
  # Finds a solution for the Berksfile and returns an array of CachedCookbooks.
@@ -401,6 +491,8 @@ module Berkshelf
401
491
  # @option options [Symbol, Array] :only
402
492
  # Group(s) to include which will cause any sources marked as a member of the
403
493
  # group to be installed and all others to be ignored
494
+ # @option cookbooks [String, Array] :cookbooks
495
+ # Names of the cookbooks to retrieve sources for
404
496
  #
405
497
  # @return [Array<Berkshelf::CachedCookbooks]
406
498
  def resolve(options = {})
@@ -441,5 +533,9 @@ module Berkshelf
441
533
  def write_lockfile(sources)
442
534
  Berkshelf::Lockfile.new(sources).write
443
535
  end
536
+
537
+ def update_lockfile(sources)
538
+ Berkshelf::Lockfile.update!(sources)
539
+ end
444
540
  end
445
541
  end
@@ -213,6 +213,10 @@ module Berkshelf
213
213
  "#{cookbook_name} (#{version}) '#{path}'"
214
214
  end
215
215
 
216
+ def <=>(other_cookbook)
217
+ [self.cookbook_name, self.version] <=> [other_cookbook.cookbook_name, other_cookbook.version]
218
+ end
219
+
216
220
  private
217
221
 
218
222
  attr_reader :files
@@ -31,6 +31,7 @@ module Berkshelf
31
31
  map 'in' => :install
32
32
  map 'up' => :upload
33
33
  map 'ud' => :update
34
+ map 'ls' => :list
34
35
  map 'ver' => :version
35
36
  map 'book' => :cookbook
36
37
 
@@ -130,10 +131,15 @@ module Berkshelf
130
131
  type: :array,
131
132
  desc: "Only cookbooks that are in these groups.",
132
133
  aliases: "-o"
133
- desc "update", "Update all Cookbooks and their dependencies specified by a Berksfile to their latest versions"
134
- def update
135
- Lockfile.remove!
136
- invoke :install
134
+ desc "update [COOKBOOKS]", "Update all Cookbooks and their dependencies specified by a Berksfile to their latest versions"
135
+ def update(*cookbook_names)
136
+ berksfile = Berksfile.from_file(options[:berksfile])
137
+
138
+ update_options = {
139
+ cookbooks: cookbook_names
140
+ }.merge(options).symbolize_keys
141
+
142
+ berksfile.update(update_options)
137
143
  end
138
144
 
139
145
  method_option :berksfile,
@@ -157,13 +163,13 @@ module Berkshelf
157
163
  option :force,
158
164
  type: :boolean,
159
165
  default: false,
160
- desc: "Upload all cookbooks even if a frozen one exists on the target Chef Server"
166
+ desc: "Upload cookbook(s) even if a frozen one exists on the target Chef Server"
161
167
  option :ssl_verify,
162
168
  type: :boolean,
163
169
  default: nil,
164
170
  desc: "Disable/Enable SSL verification when uploading cookbooks"
165
- desc "upload", "Upload the Cookbooks specified by a Berksfile or a Berksfile.lock to a Chef Server"
166
- def upload
171
+ desc "upload [COOKBOOKS]", "Upload cookbook(s) specified by a Berksfile to the configured Chef Server."
172
+ def upload(*cookbook_names)
167
173
  berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
168
174
 
169
175
  unless Berkshelf::Config.instance.chef.chef_server_url.present?
@@ -178,18 +184,54 @@ module Berkshelf
178
184
  raise UploadFailure, msg
179
185
  end
180
186
 
181
- berksfile.upload(
187
+ upload_options = {
182
188
  server_url: Berkshelf::Config.instance.chef.chef_server_url,
183
189
  client_name: Berkshelf::Config.instance.chef.node_name,
184
190
  client_key: Berkshelf::Config.instance.chef.client_key,
185
191
  ssl: {
186
192
  verify: (options[:ssl_verify].nil? ? Berkshelf::Config.instance.ssl.verify : options[:ssl_verify])
187
- }
188
- )
189
- rescue Ridley::Errors::ClientKeyFileNotFound => e
190
- msg = "Could not upload cookbooks: Missing Chef client key: '#{Berkshelf::Config.instance.chef.client_key}'."
191
- msg << " Generate or update your Berkshelf configuration that contains a valid path to a Chef client key."
192
- raise UploadFailure, msg
193
+ },
194
+ cookbooks: cookbook_names
195
+ }.merge(options).symbolize_keys
196
+
197
+ berksfile.upload(upload_options)
198
+ end
199
+
200
+ method_option :berksfile,
201
+ type: :string,
202
+ default: File.join(Dir.pwd, Berkshelf::DEFAULT_FILENAME),
203
+ desc: "Path to a Berksfile to operate off of.",
204
+ aliases: "-b",
205
+ banner: "PATH"
206
+ method_option :except,
207
+ type: :array,
208
+ desc: "Exclude cookbooks that are in these groups.",
209
+ aliases: "-e"
210
+ method_option :only,
211
+ type: :array,
212
+ desc: "Only cookbooks that are in these groups.",
213
+ aliases: "-o"
214
+ desc "outdated [COOKBOOKS]", "Show all outdated cookbooks (exclusively from the community site)"
215
+ def outdated(*cookbook_names)
216
+ berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
217
+ Berkshelf.formatter.msg "Listing outdated cookbooks with newer versions available..."
218
+ Berkshelf.formatter.msg "BETA: this feature will only pull differences from the community site and will"
219
+ Berkshelf.formatter.msg "BETA: ignore all other sources you may have defined"
220
+ Berkshelf.formatter.msg ""
221
+
222
+ outdated_options = {
223
+ cookbooks: cookbook_names
224
+ }.merge(options).symbolize_keys
225
+
226
+ outdated = berksfile.outdated(outdated_options)
227
+
228
+ if outdated.empty?
229
+ Berkshelf.formatter.msg "All cookbooks up to date"
230
+ else
231
+ outdated.each do |cookbook, latest_version|
232
+ Berkshelf.formatter.msg "Cookbook '#{cookbook.name} (#{cookbook.version_constraint})' is outdated (#{latest_version})"
233
+ end
234
+ end
193
235
  end
194
236
 
195
237
  method_option :foodcritic,
@@ -228,6 +270,39 @@ module Berkshelf
228
270
  ::Berkshelf.formatter.msg "Successfully initialized"
229
271
  end
230
272
 
273
+ method_option :berksfile,
274
+ type: :string,
275
+ default: File.join(Dir.pwd, Berkshelf::DEFAULT_FILENAME),
276
+ desc: "Path to a Berksfile to operate off of.",
277
+ aliases: "-b",
278
+ banner: "PATH"
279
+ desc "list", "Show all of the cookbooks in the current Berkshelf"
280
+ def list
281
+ berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
282
+
283
+ Berkshelf.ui.say "Cookbooks installed by your Berksfile:"
284
+ Berkshelf.ui.mute { berksfile.resolve }.sort.each do |cookbook|
285
+ Berkshelf.ui.say " * #{cookbook.cookbook_name} (#{cookbook.version})"
286
+ end
287
+ end
288
+
289
+ method_option :berksfile,
290
+ type: :string,
291
+ default: File.join(Dir.pwd, Berkshelf::DEFAULT_FILENAME),
292
+ desc: "Path to a Berksfile to operate off of.",
293
+ aliases: "-b",
294
+ banner: "PATH"
295
+ desc "show [COOKBOOK]", "Display the source path on the local file system for the given cookbook"
296
+ def show(name = nil)
297
+ return list if name.nil?
298
+
299
+ berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
300
+ cookbook = Berkshelf.ui.mute { berksfile.resolve }.find{ |cookbook| cookbook.cookbook_name == name }
301
+
302
+ raise CookbookNotFound, "Cookbook '#{name}' was not installed by your Berksfile" unless cookbook
303
+ Berkshelf.ui.say(cookbook.path)
304
+ end
305
+
231
306
  desc "version", "Display version and copyright information"
232
307
  def version
233
308
  Berkshelf.formatter.msg version_header