berkshelf 1.0.4 → 1.1.0.rc1

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