berkshelf 3.0.0.beta9 → 3.0.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.
@@ -11,8 +11,8 @@ module Berkshelf
11
11
  #
12
12
  # @param [Exception] ex
13
13
  def exception(ex)
14
- log.fatal("#{ex.class}: #{ex}")
15
- log.fatal(ex.backtrace.join("\n")) unless ex.backtrace.nil?
14
+ fatal("#{ex.class}: #{ex}")
15
+ fatal(ex.backtrace.join("\n")) unless ex.backtrace.nil?
16
16
  end
17
17
  end
18
18
  end
@@ -57,7 +57,10 @@ module Berkshelf
57
57
  #
58
58
  # @return [Array<String, String>]
59
59
  def demand_array
60
- demands.collect { |demand| [ demand.name, demand.version_constraint ] }
60
+ demands.collect do |demand|
61
+ constraint = demand.locked_version || demand.version_constraint
62
+ [demand.name, constraint]
63
+ end
61
64
  end
62
65
 
63
66
  # Finds a solution for the currently added dependencies and their dependencies and
@@ -0,0 +1,122 @@
1
+ module Berkshelf
2
+ class Uploader
3
+ attr_reader :berksfile
4
+ attr_reader :lockfile
5
+ attr_reader :options
6
+ attr_reader :names
7
+
8
+ def initialize(berksfile, *args)
9
+ @berksfile = berksfile
10
+ @lockfile = berksfile.lockfile
11
+
12
+ @options = {
13
+ force: false,
14
+ freeze: true,
15
+ halt_on_frozen: false,
16
+ validate: true,
17
+ }.merge(args.last.is_a?(Hash) ? args.pop : {})
18
+
19
+ @names = Array(args).flatten
20
+ end
21
+
22
+ def run
23
+ Berkshelf.log.info "Uploading cookbooks"
24
+
25
+ cookbooks = if names.empty?
26
+ Berkshelf.log.debug " No names given, using all cookbooks"
27
+ filtered_cookbooks
28
+ else
29
+ Berkshelf.log.debug " Names given (#{names.join(', ')})"
30
+ names.map { |name| lockfile.retrieve(name) }
31
+ end
32
+
33
+ # Perform all validations first to prevent partially uploaded cookbooks
34
+ cookbooks.each { |cookbook| validate_files!(cookbook) }
35
+
36
+ upload(cookbooks)
37
+ cookbooks
38
+ end
39
+
40
+ private
41
+
42
+ # Upload the list of cookbooks to the Chef Server, with some exception
43
+ # wrapping.
44
+ #
45
+ # @param [Array<String>] cookbooks
46
+ def upload(cookbooks)
47
+ Berkshelf.log.info "Starting upload"
48
+
49
+ Berkshelf.ridley_connection(options) do |connection|
50
+ cookbooks.each do |cookbook|
51
+ Berkshelf.log.debug " Uploading #{cookbook}"
52
+
53
+ begin
54
+ connection.cookbook.upload(cookbook.path,
55
+ name: cookbook.cookbook_name,
56
+ force: options[:force],
57
+ freeze: options[:freeze],
58
+ validate: options[:validate],
59
+ )
60
+
61
+ Berkshelf.formatter.uploaded(cookbook, connection)
62
+ rescue Ridley::Errors::FrozenCookbook
63
+ if options[:halt_on_frozen]
64
+ raise FrozenCookbook.new(cookbook)
65
+ end
66
+
67
+ Berkshelf.formatter.skipping(cookbook, connection)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ # Filter cookbooks based off the list of dependencies in the Berksfile.
74
+ #
75
+ # This method is secretly recursive. It iterates over each dependency in
76
+ # the Berksfile (using {Berksfile#dependencies} to account for filters)
77
+ # and retrieves that cookbook, it's dependencies, and the recusive
78
+ # dependencies, but iteratively.
79
+ #
80
+ # @return [Array<CachedCookbook>]
81
+ #
82
+ def filtered_cookbooks
83
+ # Create a copy of the dependencies. We need to make a copy, or else
84
+ # we would be adding dependencies directly to the Berksfile object, and
85
+ # that would be a bad idea...
86
+ dependencies = berksfile.dependencies.map(&:name)
87
+
88
+ checked = {}
89
+ cookbooks = {}
90
+
91
+ dependencies.each do |dependency|
92
+ next if checked[dependency]
93
+
94
+ lockfile.graph.find(dependency).dependencies.each do |name, _|
95
+ cookbooks[name] ||= lockfile.retrieve(name)
96
+ dependencies << name
97
+ end
98
+
99
+ checked[dependency] = true
100
+ cookbooks[dependency] ||= lockfile.retrieve(dependency)
101
+ end
102
+
103
+ cookbooks.values.sort
104
+ end
105
+
106
+ # Validate that the given cookbook does not have "bad" files. Currently
107
+ # this means including spaces in filenames (such as recipes)
108
+ #
109
+ # @param [CachedCookbook] cookbook
110
+ # the Cookbook to validate
111
+ def validate_files!(cookbook)
112
+ path = cookbook.path.to_s
113
+
114
+ files = Dir.glob(File.join(path, '**', '*.rb')).select do |f|
115
+ parent = Pathname.new(path).dirname.to_s
116
+ f.gsub(parent, '') =~ /[[:space:]]/
117
+ end
118
+
119
+ raise InvalidCookbookFiles.new(cookbook, files) unless files.empty?
120
+ end
121
+ end
122
+ end
@@ -1,3 +1,3 @@
1
1
  module Berkshelf
2
- VERSION = '3.0.0.beta9'
2
+ VERSION = '3.0.0.rc1'
3
3
  end
@@ -320,187 +320,36 @@ describe Berkshelf::Berksfile do
320
320
  end
321
321
 
322
322
  describe '#upload' do
323
- let(:graph) { double(locks: []) }
324
- let(:lockfile) { double(present?: true, trusted?: true, graph: graph) }
323
+ let(:uploader) { double(Berkshelf::Uploader, run: nil) }
325
324
 
326
325
  before do
327
- Berkshelf.stub(:config).and_return(berkshelf_config)
328
- subject.stub(:lockfile).and_return(lockfile)
329
- end
330
-
331
- let(:options) { Hash.new }
332
- let(:chef_config) do
333
- double('chef-config',
334
- node_name: 'fake-client',
335
- client_key: 'client-key',
336
- chef_server_url: 'http://configured-chef-server/',
337
- validation_client_name: 'validator',
338
- validation_key: 'validator.pem',
339
- cookbook_copyright: 'user',
340
- cookbook_email: 'user@example.com',
341
- cookbook_license: 'apachev2',
342
- )
343
- end
344
- let(:berkshelf_config) { double('berkshelf-config', ssl: double(verify: true), chef: chef_config) }
345
- let(:default_ridley_options) do
346
- {
347
- client_name: 'fake-client',
348
- client_key: 'client-key',
349
- ssl: {
350
- verify: true
351
- }
352
- }
353
- end
354
-
355
- let(:upload) { subject.upload(options) }
356
-
357
- context 'when there is no value for :chef_server_url' do
358
- before { chef_config.stub(chef_server_url: nil) }
359
- let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.server_url' }
360
-
361
- it 'raises an error' do
362
- expect { upload }.to raise_error(Berkshelf::ChefConnectionError, message)
363
- end
364
- end
365
-
366
- context 'when there is no value for :client_name' do
367
- before { chef_config.stub(node_name: nil) }
368
- let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.node_name' }
369
-
370
- it 'raises an error' do
371
- expect { upload }.to raise_error(Berkshelf::ChefConnectionError, message)
372
- end
373
- end
374
-
375
- context 'when there is no value for :client_key' do
376
- before { chef_config.stub(client_key: nil) }
377
- let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.client_key' }
378
-
379
- it 'raises an error' do
380
- expect {
381
- upload
382
- }.to raise_error(Berkshelf::ChefConnectionError, message)
383
- end
384
- end
385
-
386
- context 'when a Chef Server url is not passed as an option' do
387
- let(:ridley_options) do
388
- { server_url: 'http://configured-chef-server/' }.merge(default_ridley_options)
389
- end
390
-
391
- it 'uses Berkshelf::Config configured server_url' do
392
- Ridley.should_receive(:open).with(ridley_options)
393
- upload
394
- end
395
- end
396
-
397
- context 'when a Chef Server url is passed as an option' do
398
- let(:options) do
399
- {
400
- server_url: 'http://fake-chef-server.com/'
401
- }
402
- end
403
- let(:ridley_options) do
404
- { server_url: 'http://fake-chef-server.com/'}.merge(default_ridley_options)
405
- end
406
-
407
- it 'uses the passed in :server_url' do
408
- Ridley.should_receive(:open).with(ridley_options)
409
- upload
410
- end
411
- end
412
-
413
- context 'when a client name is passed as an option' do
414
- let(:options) do
415
- {
416
- client_name: 'passed-in-client-name'
417
- }
418
- end
419
- let(:ridley_options) do
420
- default_ridley_options.merge(
421
- { server_url: 'http://configured-chef-server/', client_name: 'passed-in-client-name'})
422
- end
423
-
424
- it 'uses the passed in :client_name' do
425
- Ridley.should_receive(:open).with(ridley_options)
426
- upload
427
- end
428
- end
429
-
430
- context 'when a client key is passed as an option' do
431
- let(:options) do
432
- {
433
- client_key: 'passed-in-client-key'
434
- }
435
- end
436
- let(:ridley_options) do
437
- default_ridley_options.merge(
438
- { server_url: 'http://configured-chef-server/', client_key: 'passed-in-client-key'})
439
- end
326
+ subject.stub(:validate_lockfile_present!)
327
+ subject.stub(:validate_lockfile_trusted!)
328
+ subject.stub(:validate_dependencies_installed!)
440
329
 
441
- it 'uses the passed in :client_key' do
442
- Ridley.should_receive(:open).with(ridley_options)
443
- upload
444
- end
330
+ Berkshelf::Uploader.stub(:new).and_return(uploader)
445
331
  end
446
332
 
447
- context 'when validate is passed' do
448
- let(:mysql_dependency) { double(name: 'mysql', metadata?: false, dependencies: []) }
449
- let(:mysql_cookbook) { double(cookbook_name: 'mysql', path: 'path') }
450
-
451
- let(:options) do
452
- {
453
- force: false,
454
- freeze: true,
455
- validate: false,
456
- name: mysql_cookbook.cookbook_name
457
- }
458
- end
459
- let(:ridley_options) do
460
- default_ridley_options.merge({ server_url: 'http://configured-chef-server/'})
461
- end
462
- let(:conn) { double('conn') }
463
-
464
- before do
465
- subject.stub(:dependencies).and_return([mysql_dependency])
466
- graph.stub(:find).with(mysql_dependency).and_return(mysql_dependency)
467
- lockfile.stub(:retrieve).with(mysql_dependency).and_return(mysql_cookbook)
468
- end
469
-
470
- it 'uses the passed in :validate' do
471
- expect(Ridley).to receive(:open).and_yield(conn)
472
- expect(conn).to receive(:cookbook).and_return(mysql_cookbook)
473
- expect(mysql_cookbook).to receive(:upload).with('path', options)
474
- subject.upload('mysql', options)
475
- end
333
+ it 'validates the lockfile is present' do
334
+ expect(subject).to receive(:validate_lockfile_present!).once
335
+ subject.upload
476
336
  end
477
- end
478
-
479
- describe '#validate_files!' do
480
- before { described_class.send(:public, :validate_files!) }
481
- let(:cookbook) { double('cookbook', cookbook_name: 'cookbook', path: 'path') }
482
337
 
483
- it 'raises an error when the cookbook has spaces in the files' do
484
- Dir.stub(:glob).and_return(['/there are/spaces/in this/recipes/default.rb'])
485
- expect {
486
- subject.validate_files!(cookbook)
487
- }.to raise_error
338
+ it 'validates the lockfile is trusted' do
339
+ expect(subject).to receive(:validate_lockfile_trusted!).once
340
+ subject.upload
488
341
  end
489
342
 
490
- it 'does not raise an error when the cookbook is valid' do
491
- Dir.stub(:glob).and_return(['/there-are/no-spaces/in-this/recipes/default.rb'])
492
- expect {
493
- subject.validate_files!(cookbook)
494
- }.to_not raise_error
343
+ it 'validates the dependencies are installed' do
344
+ expect(subject).to receive(:validate_dependencies_installed!).once
345
+ subject.upload
495
346
  end
496
347
 
497
- it 'does not raise an exception with spaces in the path' do
498
- Dir.stub(:glob).and_return(['/there are/spaces/in this/recipes/default.rb'])
499
- Pathname.any_instance.stub(:dirname).and_return('/there are/spaces/in this')
348
+ it 'creates a new Uploader' do
349
+ expect(Berkshelf::Uploader).to receive(:new).with(subject)
350
+ expect(uploader).to receive(:run)
500
351
 
501
- expect {
502
- subject.validate_files!(cookbook)
503
- }.to_not raise_error
352
+ subject.upload
504
353
  end
505
354
  end
506
355
  end
@@ -47,8 +47,8 @@ describe Berkshelf::CookbookGenerator do
47
47
  contains 'Author:: YOUR_NAME (<YOUR_EMAIL>)'
48
48
  end
49
49
  file 'CHANGELOG.md' do
50
- contains '# sparkle_motion cookbook CHANGELOG'
51
- contains "## v0.1.0 (#{Time.now.strftime('%Y-%m-%d')})"
50
+ contains '# 0.1.0'
51
+ contains "Initial release of sparkle_motion"
52
52
  end
53
53
  file 'metadata.rb' do
54
54
  contains "name 'sparkle_motion'"
@@ -16,7 +16,7 @@ module Berkshelf
16
16
  }.to raise_error(AbstractFunction)
17
17
 
18
18
  expect {
19
- subject.upload('my_coobook','1.2.3','http://chef_server')
19
+ subject.uploaded('my_coobook','1.2.3','http://chef_server')
20
20
  }.to raise_error(AbstractFunction)
21
21
 
22
22
  expect {
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Thor::Base.shell do
4
- let(:stdout) { double('stdout') }
4
+ let(:stdout) { double('stdout', tty?: true) }
5
5
  let(:stderr) { double('stderr') }
6
6
 
7
7
  before do
@@ -30,7 +30,7 @@ describe Thor::Base.shell do
30
30
  end
31
31
 
32
32
  it 'does not output anything', :not_supported_on_windows do
33
- stdout.should_not_receive(:puts)
33
+ stdout.should_not_receive(:print)
34
34
  subject.say 'message'
35
35
  end
36
36
  end
@@ -41,7 +41,7 @@ describe Thor::Base.shell do
41
41
  end
42
42
 
43
43
  it 'prints to stdout' do
44
- stdout.should_receive(:puts).with('message').once
44
+ stdout.should_receive(:print).once
45
45
  stdout.should_receive(:flush).with(no_args())
46
46
  subject.say 'message'
47
47
  end
@@ -66,7 +66,7 @@ describe Thor::Base.shell do
66
66
  end
67
67
 
68
68
  it 'prints to stdout' do
69
- stdout.should_receive(:puts).with(windows? ? " 5 message" : "\e[1m\e[32m 5\e[0m message")
69
+ stdout.should_receive(:print).once
70
70
  stdout.should_receive(:flush).with(no_args())
71
71
  subject.say_status 5, 'message'
72
72
  end
@@ -80,7 +80,7 @@ describe Thor::Base.shell do
80
80
  end
81
81
 
82
82
  it 'does not output anything' do
83
- stdout.should_not_receive(:puts)
83
+ stdout.should_not_receive(:print)
84
84
  subject.warn 'warning'
85
85
  end
86
86
  end
@@ -91,8 +91,7 @@ describe Thor::Base.shell do
91
91
  end
92
92
 
93
93
  it 'calls #say with yellow coloring' do
94
- stdout.stub :tty?
95
- stdout.should_receive(:puts).with("warning")
94
+ stdout.should_receive(:print)
96
95
  stdout.should_receive(:flush).with(no_args())
97
96
  subject.warn 'warning'
98
97
  end
@@ -0,0 +1,190 @@
1
+ require 'spec_helper'
2
+
3
+ module Berkshelf
4
+ describe Uploader do
5
+ let(:berksfile) do
6
+ double(Berksfile,
7
+ lockfile: lockfile,
8
+ dependencies: [],
9
+ )
10
+ end
11
+
12
+ let(:lockfile) do
13
+ double(Lockfile,
14
+ graph: graph
15
+ )
16
+ end
17
+
18
+ let(:graph) { double(Lockfile::Graph, locks: {}) }
19
+
20
+ subject { Uploader.new(berksfile) }
21
+
22
+ describe '#initialize' do
23
+ it 'saves the berksfile' do
24
+ instance = Uploader.new(berksfile)
25
+ expect(instance.berksfile).to be(berksfile)
26
+ end
27
+
28
+ it 'saves the lockfile' do
29
+ instance = Uploader.new(berksfile)
30
+ expect(instance.lockfile).to be(lockfile)
31
+ end
32
+
33
+ it 'saves the options' do
34
+ instance = Uploader.new(berksfile, force: true, validate: false)
35
+ options = instance.options
36
+ expect(options[:force]).to be_true
37
+ expect(options[:validate]).to be_false
38
+ end
39
+
40
+ it 'saves the names' do
41
+ instance = Uploader.new(berksfile, 'cookbook_1', 'cookbook_2')
42
+ expect(instance.names).to eq(['cookbook_1', 'cookbook_2'])
43
+ end
44
+ end
45
+
46
+ describe '#validate_files!' do
47
+ before { Uploader.send(:public, :validate_files!) }
48
+
49
+ let(:cookbook) { double('cookbook', cookbook_name: 'cookbook', path: 'path') }
50
+
51
+ it 'raises an error when the cookbook has spaces in the files' do
52
+ Dir.stub(:glob).and_return(['/there are/spaces/in this/recipes/default.rb'])
53
+ expect {
54
+ subject.validate_files!(cookbook)
55
+ }.to raise_error
56
+ end
57
+
58
+ it 'does not raise an error when the cookbook is valid' do
59
+ Dir.stub(:glob).and_return(['/there-are/no-spaces/in-this/recipes/default.rb'])
60
+ expect {
61
+ subject.validate_files!(cookbook)
62
+ }.to_not raise_error
63
+ end
64
+
65
+ it 'does not raise an exception with spaces in the path' do
66
+ Dir.stub(:glob).and_return(['/there are/spaces/in this/recipes/default.rb'])
67
+ Pathname.any_instance.stub(:dirname).and_return('/there are/spaces/in this')
68
+
69
+ expect {
70
+ subject.validate_files!(cookbook)
71
+ }.to_not raise_error
72
+ end
73
+ end
74
+
75
+ describe '#run' do
76
+ let(:options) { Hash.new }
77
+
78
+ let(:chef_config) do
79
+ double(Ridley::Chef::Config,
80
+ node_name: 'fake-client',
81
+ client_key: 'client-key',
82
+ chef_server_url: 'http://configured-chef-server/',
83
+ validation_client_name: 'validator',
84
+ validation_key: 'validator.pem',
85
+ cookbook_copyright: 'user',
86
+ cookbook_email: 'user@example.com',
87
+ cookbook_license: 'apachev2',
88
+ )
89
+ end
90
+
91
+ let(:berkshelf_config) do
92
+ double(Config,
93
+ ssl: double(verify: true),
94
+ chef: chef_config,
95
+ )
96
+ end
97
+
98
+ let(:default_ridley_options) do
99
+ {
100
+ client_name: 'fake-client',
101
+ client_key: 'client-key',
102
+ ssl: {
103
+ verify: true
104
+ }
105
+ }
106
+ end
107
+
108
+ before do
109
+ Berkshelf.stub(:config).and_return(berkshelf_config)
110
+ end
111
+
112
+ context 'when there is no value for :chef_server_url' do
113
+ before { chef_config.stub(chef_server_url: nil) }
114
+ let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.server_url' }
115
+
116
+ it 'raises an error' do
117
+ expect { subject.run }.to raise_error(Berkshelf::ChefConnectionError, message)
118
+ end
119
+ end
120
+
121
+ context 'when there is no value for :client_name' do
122
+ before { chef_config.stub(node_name: nil) }
123
+ let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.node_name' }
124
+
125
+ it 'raises an error' do
126
+ expect { subject.run }.to raise_error(Berkshelf::ChefConnectionError, message)
127
+ end
128
+ end
129
+
130
+ context 'when there is no value for :client_key' do
131
+ before { chef_config.stub(client_key: nil) }
132
+ let(:message) { 'Missing required attribute in your Berkshelf configuration: chef.client_key' }
133
+
134
+ it 'raises an error' do
135
+ expect {
136
+ subject.run
137
+ }.to raise_error(Berkshelf::ChefConnectionError, message)
138
+ end
139
+ end
140
+
141
+ context 'when no options are given' do
142
+ let(:ridley_options) do
143
+ { server_url: 'http://configured-chef-server/' }.merge(default_ridley_options)
144
+ end
145
+
146
+ it 'uses the Berkshelf::Config options' do
147
+ expect(Ridley).to receive(:open).with(
148
+ server_url: chef_config.chef_server_url,
149
+ client_name: chef_config.node_name,
150
+ client_key: chef_config.client_key,
151
+ ssl: {
152
+ verify: berkshelf_config.ssl.verify
153
+ }
154
+ )
155
+ subject.run
156
+ end
157
+ end
158
+
159
+ context 'when a Chef Server url is passed as an option' do
160
+ subject { Uploader.new(berksfile, server_url: 'http://custom') }
161
+
162
+ it 'uses the passed in :server_url' do
163
+ expect(Ridley).to receive(:open)
164
+ .with(include(server_url: 'http://custom'))
165
+ subject.run
166
+ end
167
+ end
168
+
169
+ context 'when a client name is passed as an option' do
170
+ subject { Uploader.new(berksfile, client_name: 'custom') }
171
+
172
+ it 'uses the passed in :client_name' do
173
+ expect(Ridley).to receive(:open)
174
+ .with(include(client_name: 'custom'))
175
+ subject.run
176
+ end
177
+ end
178
+
179
+ context 'when a client key is passed as an option' do
180
+ subject { Uploader.new(berksfile, client_key: 'custom') }
181
+
182
+ it 'uses the passed in :client_key' do
183
+ expect(Ridley).to receive(:open)
184
+ .with(include(client_key: 'custom'))
185
+ subject.run
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end