chef 11.12.0.alpha.1 → 11.12.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/chef/api_client/registration.rb +46 -9
- data/lib/chef/application.rb +1 -0
- data/lib/chef/application/client.rb +25 -24
- data/lib/chef/client.rb +34 -0
- data/lib/chef/config.rb +11 -0
- data/lib/chef/cookbook/chefignore.rb +10 -2
- data/lib/chef/cookbook/metadata.rb +31 -3
- data/lib/chef/cookbook/synchronizer.rb +2 -2
- data/lib/chef/cookbook/syntax_check.rb +4 -4
- data/lib/chef/encrypted_data_bag_item.rb +37 -1
- data/lib/chef/exceptions.rb +1 -0
- data/lib/chef/guard_interpreter/default_guard_interpreter.rb +42 -0
- data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +122 -0
- data/lib/chef/http.rb +0 -1
- data/lib/chef/http/decompressor.rb +7 -4
- data/lib/chef/http/simple.rb +5 -0
- data/lib/chef/http/validate_content_length.rb +28 -12
- data/lib/chef/knife.rb +1 -0
- data/lib/chef/knife/client_bulk_delete.rb +48 -9
- data/lib/chef/knife/client_delete.rb +4 -4
- data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
- data/lib/chef/knife/cookbook_upload.rb +17 -7
- data/lib/chef/knife/core/bootstrap_context.rb +1 -1
- data/lib/chef/knife/core/ui.rb +42 -5
- data/lib/chef/knife/node_run_list_add.rb +31 -2
- data/lib/chef/knife/ssh.rb +44 -31
- data/lib/chef/knife/ssl_check.rb +213 -0
- data/lib/chef/knife/ssl_fetch.rb +145 -0
- data/lib/chef/mixin/deep_merge.rb +13 -5
- data/lib/chef/mixin/shell_out.rb +9 -3
- data/lib/chef/node.rb +23 -4
- data/lib/chef/node/immutable_collections.rb +32 -0
- data/lib/chef/platform/provider_mapping.rb +21 -18
- data/lib/chef/platform/query_helpers.rb +10 -2
- data/lib/chef/policy_builder/expand_node_object.rb +3 -6
- data/lib/chef/provider/cron.rb +25 -3
- data/lib/chef/provider/mount/mount.rb +1 -1
- data/lib/chef/provider/package/dpkg.rb +2 -1
- data/lib/chef/provider/package/windows.rb +80 -0
- data/lib/chef/provider/package/windows/msi.rb +69 -0
- data/lib/chef/provider/powershell_script.rb +19 -6
- data/lib/chef/provider/service/solaris.rb +11 -7
- data/lib/chef/resource.rb +18 -5
- data/lib/chef/resource/conditional.rb +20 -7
- data/lib/chef/resource/cron.rb +18 -2
- data/lib/chef/resource/execute.rb +0 -2
- data/lib/chef/resource/powershell_script.rb +23 -1
- data/lib/chef/resource/script.rb +25 -0
- data/lib/chef/resource/subversion.rb +4 -0
- data/lib/chef/resource/windows_package.rb +79 -0
- data/lib/chef/resource/windows_script.rb +0 -5
- data/lib/chef/resources.rb +1 -0
- data/lib/chef/rest.rb +6 -1
- data/lib/chef/run_context.rb +22 -2
- data/lib/chef/run_context/cookbook_compiler.rb +12 -0
- data/lib/chef/util/editor.rb +92 -0
- data/lib/chef/util/file_edit.rb +22 -54
- data/lib/chef/version.rb +2 -2
- data/lib/chef/win32/api/installer.rb +166 -0
- data/lib/chef/win32/version.rb +8 -0
- data/spec/data/standalone_cookbook/Gemfile +1 -0
- data/spec/data/standalone_cookbook/chefignore +9 -0
- data/spec/data/standalone_cookbook/recipes/default.rb +3 -0
- data/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb +1 -0
- data/spec/functional/resource/powershell_spec.rb +262 -1
- data/spec/functional/win32/versions_spec.rb +3 -3
- data/spec/integration/knife/chefignore_spec.rb +1 -2
- data/spec/integration/knife/raw_spec.rb +8 -13
- data/spec/integration/knife/redirection_spec.rb +6 -14
- data/spec/integration/solo/solo_spec.rb +19 -0
- data/spec/support/shared/functional/windows_script.rb +1 -1
- data/spec/support/shared/integration/app_server_support.rb +42 -0
- data/spec/support/shared/integration/integration_helper.rb +1 -0
- data/spec/support/shared/unit/script_resource.rb +38 -0
- data/spec/unit/api_client/registration_spec.rb +109 -38
- data/spec/unit/application/client_spec.rb +48 -1
- data/spec/unit/cookbook/chefignore_spec.rb +10 -0
- data/spec/unit/cookbook/metadata_spec.rb +45 -1
- data/spec/unit/cookbook/syntax_check_spec.rb +28 -0
- data/spec/unit/cookbook_spec.rb +0 -10
- data/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb +56 -0
- data/spec/unit/http/simple_spec.rb +32 -0
- data/spec/unit/http/validate_content_length_spec.rb +187 -0
- data/spec/unit/knife/bootstrap_spec.rb +13 -4
- data/spec/unit/knife/client_bulk_delete_spec.rb +123 -38
- data/spec/unit/knife/client_delete_spec.rb +4 -4
- data/spec/unit/knife/cookbook_upload_spec.rb +181 -88
- data/spec/unit/knife/core/bootstrap_context_spec.rb +11 -1
- data/spec/unit/knife/core/ui_spec.rb +109 -38
- data/spec/unit/knife/node_run_list_add_spec.rb +24 -1
- data/spec/unit/knife/ssh_spec.rb +17 -6
- data/spec/unit/knife/ssl_check_spec.rb +187 -0
- data/spec/unit/knife/ssl_fetch_spec.rb +151 -0
- data/spec/unit/mixin/deep_merge_spec.rb +17 -0
- data/spec/unit/node/immutable_collections_spec.rb +55 -0
- data/spec/unit/node_spec.rb +9 -0
- data/spec/unit/platform/query_helpers_spec.rb +32 -0
- data/spec/unit/platform_spec.rb +193 -175
- data/spec/unit/policy_builder/expand_node_object_spec.rb +1 -1
- data/spec/unit/provider/cron_spec.rb +175 -1
- data/spec/unit/provider/mount/mount_spec.rb +33 -3
- data/spec/unit/provider/package/dpkg_spec.rb +4 -0
- data/spec/unit/provider/package/windows/msi_spec.rb +60 -0
- data/spec/unit/provider/package/windows_spec.rb +80 -0
- data/spec/unit/provider/service/macosx_spec.rb +3 -3
- data/spec/unit/provider/service/solaris_smf_service_spec.rb +35 -10
- data/spec/unit/pure_application_spec.rb +32 -0
- data/spec/unit/recipe_spec.rb +4 -0
- data/spec/unit/resource/conditional_spec.rb +13 -12
- data/spec/unit/resource/cron_spec.rb +7 -2
- data/spec/unit/resource/powershell_spec.rb +85 -2
- data/spec/unit/resource/subversion_spec.rb +5 -0
- data/spec/unit/resource/windows_package_spec.rb +74 -0
- data/spec/unit/resource_spec.rb +23 -1
- data/spec/unit/rest_spec.rb +15 -0
- data/spec/unit/run_context/cookbook_compiler_spec.rb +12 -0
- data/spec/unit/run_context_spec.rb +7 -0
- data/spec/unit/util/editor_spec.rb +152 -0
- data/spec/unit/util/file_edit_spec.rb +37 -1
- metadata +41 -30
@@ -41,13 +41,19 @@ describe Chef::Knife::Core::BootstrapContext do
|
|
41
41
|
bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default"
|
42
42
|
end
|
43
43
|
|
44
|
+
describe "when in verbosity mode" do
|
45
|
+
let(:config) { {:verbosity => 2} }
|
46
|
+
it "adds '-l debug' when verbosity is >= 2" do
|
47
|
+
bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -l debug -E _default"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
44
51
|
it "reads the validation key" do
|
45
52
|
bootstrap_context.validation_key.should eq IO.read(File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem'))
|
46
53
|
end
|
47
54
|
|
48
55
|
it "generates the config file data" do
|
49
56
|
expected=<<-EXPECTED
|
50
|
-
log_level :auto
|
51
57
|
log_location STDOUT
|
52
58
|
chef_server_url "http://chef.example.com:4444"
|
53
59
|
validation_client_name "chef-validator-testing"
|
@@ -56,6 +62,10 @@ EXPECTED
|
|
56
62
|
bootstrap_context.config_content.should eq expected
|
57
63
|
end
|
58
64
|
|
65
|
+
it "does not set a default log_level" do
|
66
|
+
expect(bootstrap_context.config_content).not_to match(/log_level/)
|
67
|
+
end
|
68
|
+
|
59
69
|
describe "alternate chef-client path" do
|
60
70
|
let(:chef_config){ {:chef_client_path => '/usr/local/bin/chef-client'} }
|
61
71
|
it "runs chef-client from another path when specified" do
|
@@ -406,61 +406,132 @@ EOM
|
|
406
406
|
end
|
407
407
|
|
408
408
|
describe "confirm" do
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
409
|
+
let(:stdout) {StringIO.new}
|
410
|
+
let(:output) {stdout.string}
|
411
|
+
|
412
|
+
let(:question) { "monkeys rule" }
|
413
|
+
let(:answer) { 'y' }
|
414
|
+
|
415
|
+
let(:default_choice) { nil }
|
416
|
+
let(:append_instructions) { true }
|
417
|
+
|
418
|
+
def run_confirm
|
419
|
+
@ui.stub(:stdout).and_return(stdout)
|
420
|
+
@ui.stdin.stub(:readline).and_return(answer)
|
421
|
+
@ui.confirm(question, append_instructions, default_choice)
|
414
422
|
end
|
415
423
|
|
416
|
-
|
417
|
-
@ui.
|
418
|
-
@ui.
|
424
|
+
def run_confirm_without_exit
|
425
|
+
@ui.stub(:stdout).and_return(stdout)
|
426
|
+
@ui.stdin.stub(:readline).and_return(answer)
|
427
|
+
@ui.confirm_without_exit(question, append_instructions, default_choice)
|
419
428
|
end
|
420
429
|
|
421
|
-
|
422
|
-
|
423
|
-
|
430
|
+
shared_examples_for "confirm with positive answer" do
|
431
|
+
it "confirm should return true" do
|
432
|
+
run_confirm.should be_true
|
433
|
+
end
|
434
|
+
|
435
|
+
it "confirm_without_exit should return true" do
|
436
|
+
run_confirm_without_exit.should be_true
|
437
|
+
end
|
424
438
|
end
|
425
439
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
440
|
+
shared_examples_for "confirm with negative answer" do
|
441
|
+
it "confirm should exit 3" do
|
442
|
+
lambda {
|
443
|
+
run_confirm
|
444
|
+
}.should raise_error(SystemExit) { |e| e.status.should == 3 }
|
445
|
+
end
|
446
|
+
|
447
|
+
it "confirm_without_exit should return false" do
|
448
|
+
run_confirm_without_exit.should be_false
|
449
|
+
end
|
431
450
|
end
|
432
451
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
452
|
+
describe "with default choice set to true" do
|
453
|
+
let(:default_choice) { true }
|
454
|
+
|
455
|
+
it "should show 'Y/n' in the instructions" do
|
456
|
+
run_confirm
|
457
|
+
output.should include("Y/n")
|
458
|
+
end
|
459
|
+
|
460
|
+
describe "with empty answer" do
|
461
|
+
let(:answer) { "" }
|
462
|
+
|
463
|
+
it_behaves_like "confirm with positive answer"
|
464
|
+
end
|
465
|
+
|
466
|
+
describe "with answer N " do
|
467
|
+
let(:answer) { "N" }
|
468
|
+
|
469
|
+
it_behaves_like "confirm with negative answer"
|
470
|
+
end
|
438
471
|
end
|
439
472
|
|
440
|
-
describe "with
|
441
|
-
|
442
|
-
|
443
|
-
|
473
|
+
describe "with default choice set to false" do
|
474
|
+
let(:default_choice) { false }
|
475
|
+
|
476
|
+
it "should show 'y/N' in the instructions" do
|
477
|
+
run_confirm
|
478
|
+
output.should include("y/N")
|
479
|
+
end
|
480
|
+
|
481
|
+
describe "with empty answer" do
|
482
|
+
let(:answer) { "" }
|
483
|
+
|
484
|
+
it_behaves_like "confirm with negative answer"
|
485
|
+
end
|
486
|
+
|
487
|
+
describe "with answer N " do
|
488
|
+
let(:answer) { "Y" }
|
489
|
+
|
490
|
+
it_behaves_like "confirm with positive answer"
|
444
491
|
end
|
445
492
|
end
|
446
493
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
@ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com"
|
453
|
-
out.string.should == "your chef server URL?"
|
494
|
+
["Y", "y"].each do |answer|
|
495
|
+
describe "with answer #{answer}" do
|
496
|
+
let(:answer) { answer }
|
497
|
+
|
498
|
+
it_behaves_like "confirm with positive answer"
|
454
499
|
end
|
500
|
+
end
|
455
501
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
out.string.should == "your chef server URL? [http://localhost:4000] "
|
502
|
+
["N", "n"].each do |answer|
|
503
|
+
describe "with answer #{answer}" do
|
504
|
+
let(:answer) { answer }
|
505
|
+
|
506
|
+
it_behaves_like "confirm with negative answer"
|
462
507
|
end
|
463
508
|
end
|
464
509
|
|
510
|
+
describe "with --y or --yes passed" do
|
511
|
+
it "should return true" do
|
512
|
+
@ui.config[:yes] = true
|
513
|
+
run_confirm.should be_true
|
514
|
+
output.should eq("")
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
describe "when asking for free-form user input" do
|
520
|
+
it "asks a question and returns the answer provided by the user" do
|
521
|
+
out = StringIO.new
|
522
|
+
@ui.stub(:stdout).and_return(out)
|
523
|
+
@ui.stub(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
|
524
|
+
@ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com"
|
525
|
+
out.string.should == "your chef server URL?"
|
526
|
+
end
|
527
|
+
|
528
|
+
it "suggests a default setting and returns the default when the user's response only contains whitespace" do
|
529
|
+
out = StringIO.new
|
530
|
+
@ui.stub(:stdout).and_return(out)
|
531
|
+
@ui.stub(:stdin).and_return(StringIO.new(" \n"))
|
532
|
+
@ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000"
|
533
|
+
out.string.should == "your chef server URL? [http://localhost:4000] "
|
534
|
+
end
|
465
535
|
end
|
536
|
+
|
466
537
|
end
|
@@ -65,6 +65,29 @@ describe Chef::Knife::NodeRunListAdd do
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
describe "with -b or --before specified" do
|
69
|
+
it "should add to the run list before the specified entry" do
|
70
|
+
@node.run_list << "role[acorns]"
|
71
|
+
@node.run_list << "role[barn]"
|
72
|
+
@knife.config[:before] = "role[acorns]"
|
73
|
+
@knife.run
|
74
|
+
@node.run_list[0].should == "role[monkey]"
|
75
|
+
@node.run_list[1].should == "role[acorns]"
|
76
|
+
@node.run_list[2].should == "role[barn]"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "with both --after and --before specified" do
|
81
|
+
it "exits with an error" do
|
82
|
+
@node.run_list << "role[acorns]"
|
83
|
+
@node.run_list << "role[barn]"
|
84
|
+
@knife.config[:before] = "role[acorns]"
|
85
|
+
@knife.config[:after] = "role[acorns]"
|
86
|
+
@knife.ui.should_receive(:fatal)
|
87
|
+
lambda { @knife.run }.should raise_error(SystemExit)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
68
91
|
describe "with more than one role or recipe" do
|
69
92
|
it "should add to the run list all the entries" do
|
70
93
|
@knife.name_args = [ "adam", "role[monkey],role[duck]" ]
|
@@ -98,7 +121,7 @@ describe Chef::Knife::NodeRunListAdd do
|
|
98
121
|
end
|
99
122
|
end
|
100
123
|
|
101
|
-
describe "with more than one role or recipe as different arguments and list separated by
|
124
|
+
describe "with more than one role or recipe as different arguments and list separated by commas" do
|
102
125
|
it "should add to the run list all the entries" do
|
103
126
|
@knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
|
104
127
|
@node.run_list << "role[acorns]"
|
data/spec/unit/knife/ssh_spec.rb
CHANGED
@@ -54,7 +54,7 @@ describe Chef::Knife::Ssh do
|
|
54
54
|
@knife.config[:attribute] = "ipaddress"
|
55
55
|
@knife.config[:override_attribute] = "ipaddress"
|
56
56
|
configure_query([@node_foo, @node_bar])
|
57
|
-
@knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
|
57
|
+
@knife.should_receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
|
58
58
|
@knife.configure_session
|
59
59
|
end
|
60
60
|
|
@@ -62,14 +62,17 @@ describe Chef::Knife::Ssh do
|
|
62
62
|
@knife.config[:attribute] = "config_file" # this value will be the config file
|
63
63
|
@knife.config[:override_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
|
64
64
|
configure_query([@node_foo, @node_bar])
|
65
|
-
@knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
|
65
|
+
@knife.should_receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
|
66
66
|
@knife.configure_session
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
70
|
it "searchs for and returns an array of fqdns" do
|
71
71
|
configure_query([@node_foo, @node_bar])
|
72
|
-
@knife.should_receive(:session_from_list).with([
|
72
|
+
@knife.should_receive(:session_from_list).with([
|
73
|
+
['foo.example.org', nil],
|
74
|
+
['bar.example.org', nil]
|
75
|
+
])
|
73
76
|
@knife.configure_session
|
74
77
|
end
|
75
78
|
|
@@ -83,7 +86,10 @@ describe Chef::Knife::Ssh do
|
|
83
86
|
|
84
87
|
it "returns an array of cloud public hostnames" do
|
85
88
|
configure_query([@node_foo, @node_bar])
|
86
|
-
@knife.should_receive(:session_from_list).with([
|
89
|
+
@knife.should_receive(:session_from_list).with([
|
90
|
+
['ec2-10-0-0-1.compute-1.amazonaws.com', nil],
|
91
|
+
['ec2-10-0-0-2.compute-1.amazonaws.com', nil]
|
92
|
+
])
|
87
93
|
@knife.configure_session
|
88
94
|
end
|
89
95
|
|
@@ -179,12 +185,17 @@ describe Chef::Knife::Ssh do
|
|
179
185
|
end
|
180
186
|
|
181
187
|
it "uses the port from an ssh config file" do
|
182
|
-
@knife.session_from_list(['the.b.org'])
|
188
|
+
@knife.session_from_list([['the.b.org', nil]])
|
183
189
|
@knife.session.servers[0].port.should == 23
|
184
190
|
end
|
185
191
|
|
192
|
+
it "uses the port from a cloud attr" do
|
193
|
+
@knife.session_from_list([['the.b.org', 123]])
|
194
|
+
@knife.session.servers[0].port.should == 123
|
195
|
+
end
|
196
|
+
|
186
197
|
it "uses the user from an ssh config file" do
|
187
|
-
@knife.session_from_list(['the.b.org'])
|
198
|
+
@knife.session_from_list([['the.b.org', 123]])
|
188
199
|
@knife.session.servers[0].user.should == "locutus"
|
189
200
|
end
|
190
201
|
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Daniel DeLeo (<dan@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require "spec_helper"
|
20
|
+
require 'stringio'
|
21
|
+
|
22
|
+
describe Chef::Knife::SslCheck do
|
23
|
+
|
24
|
+
let(:name_args) { [] }
|
25
|
+
let(:stdout_io) { StringIO.new }
|
26
|
+
let(:stderr_io) { StringIO.new }
|
27
|
+
|
28
|
+
def stderr
|
29
|
+
stderr_io.string
|
30
|
+
end
|
31
|
+
|
32
|
+
def stdout
|
33
|
+
stdout_io.string
|
34
|
+
end
|
35
|
+
|
36
|
+
subject(:ssl_check) do
|
37
|
+
s = Chef::Knife::SslCheck.new
|
38
|
+
s.ui.stub(:stdout).and_return(stdout_io)
|
39
|
+
s.ui.stub(:stderr).and_return(stderr_io)
|
40
|
+
s.name_args = name_args
|
41
|
+
s
|
42
|
+
end
|
43
|
+
|
44
|
+
before do
|
45
|
+
Chef::Config.chef_server_url = "https://example.com:8443/chef-server"
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when no arguments are given" do
|
49
|
+
it "uses the chef_server_url as the host to check" do
|
50
|
+
expect(ssl_check.host).to eq("example.com")
|
51
|
+
expect(ssl_check.port).to eq(8443)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when a specific URI is given" do
|
56
|
+
let(:name_args) { %w{https://example.test:10443/foo} }
|
57
|
+
|
58
|
+
it "checks the SSL configuration against the given host" do
|
59
|
+
expect(ssl_check.host).to eq("example.test")
|
60
|
+
expect(ssl_check.port).to eq(10443)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when an invalid URI is given" do
|
65
|
+
|
66
|
+
let(:name_args) { %w{foo.test} }
|
67
|
+
|
68
|
+
it "prints an error and exits" do
|
69
|
+
expect { ssl_check.run }.to raise_error(SystemExit)
|
70
|
+
expected_stdout=<<-E
|
71
|
+
USAGE: knife ssl check [URL] (options)
|
72
|
+
E
|
73
|
+
expected_stderr=<<-E
|
74
|
+
ERROR: Given URI: `foo.test' is invalid
|
75
|
+
E
|
76
|
+
expect(stdout_io.string).to eq(expected_stdout)
|
77
|
+
expect(stderr_io.string).to eq(expected_stderr)
|
78
|
+
end
|
79
|
+
|
80
|
+
context "and its malformed enough to make URI.parse barf" do
|
81
|
+
|
82
|
+
let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} }
|
83
|
+
|
84
|
+
it "prints an error and exits" do
|
85
|
+
expect { ssl_check.run }.to raise_error(SystemExit)
|
86
|
+
expected_stdout=<<-E
|
87
|
+
USAGE: knife ssl check [URL] (options)
|
88
|
+
E
|
89
|
+
expected_stderr=<<-E
|
90
|
+
ERROR: Given URI: `#{name_args[0]}' is invalid
|
91
|
+
E
|
92
|
+
expect(stdout_io.string).to eq(expected_stdout)
|
93
|
+
expect(stderr_io.string).to eq(expected_stderr)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "verifying the remote certificate" do
|
99
|
+
let(:name_args) { %w{https://foo.example.com:8443} }
|
100
|
+
|
101
|
+
let(:tcp_socket) { double(TCPSocket) }
|
102
|
+
let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) }
|
103
|
+
|
104
|
+
before do
|
105
|
+
TCPSocket.should_receive(:new).with("foo.example.com", 8443).and_return(tcp_socket)
|
106
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket)
|
107
|
+
end
|
108
|
+
|
109
|
+
def run
|
110
|
+
ssl_check.run
|
111
|
+
rescue Exception
|
112
|
+
#puts "OUT: #{stdout_io.string}"
|
113
|
+
#puts "ERR: #{stderr_io.string}"
|
114
|
+
raise
|
115
|
+
end
|
116
|
+
|
117
|
+
context "when the remote host's certificate is valid" do
|
118
|
+
|
119
|
+
before do
|
120
|
+
ssl_socket.should_receive(:connect) # no error
|
121
|
+
ssl_socket.should_receive(:post_connection_check).with("foo.example.com") # no error
|
122
|
+
end
|
123
|
+
|
124
|
+
it "prints a success message" do
|
125
|
+
ssl_check.run
|
126
|
+
expect(stdout_io.string).to include("Successfully verified certificates from `foo.example.com'")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "and the certificate is not valid" do
|
131
|
+
|
132
|
+
let(:tcp_socket_for_debug) { double(TCPSocket) }
|
133
|
+
let(:ssl_socket_for_debug) { double(OpenSSL::SSL::SSLSocket) }
|
134
|
+
|
135
|
+
let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
|
136
|
+
let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
|
137
|
+
|
138
|
+
before do
|
139
|
+
trap(:INT, "DEFAULT")
|
140
|
+
|
141
|
+
TCPSocket.should_receive(:new).
|
142
|
+
with("foo.example.com", 8443).
|
143
|
+
and_return(tcp_socket_for_debug)
|
144
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).
|
145
|
+
with(tcp_socket_for_debug, ssl_check.noverify_peer_ssl_context).
|
146
|
+
and_return(ssl_socket_for_debug)
|
147
|
+
end
|
148
|
+
|
149
|
+
context "when the certificate's CN does not match the hostname" do
|
150
|
+
before do
|
151
|
+
ssl_socket.should_receive(:connect) # no error
|
152
|
+
ssl_socket.should_receive(:post_connection_check).
|
153
|
+
with("foo.example.com").
|
154
|
+
and_raise(OpenSSL::SSL::SSLError)
|
155
|
+
ssl_socket_for_debug.should_receive(:connect)
|
156
|
+
ssl_socket_for_debug.should_receive(:peer_cert).and_return(self_signed_crt)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "shows the CN used by the certificate and prints an error" do
|
160
|
+
expect { run }.to raise_error(SystemExit)
|
161
|
+
expect(stderr).to include("The SSL cert is signed by a trusted authority but is not valid for the given hostname")
|
162
|
+
expect(stderr).to include("You are attempting to connect to: 'foo.example.com'")
|
163
|
+
expect(stderr).to include("The server's certificate belongs to 'example.local'")
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
context "when the cert is not signed by any trusted authority" do
|
169
|
+
before do
|
170
|
+
ssl_socket.should_receive(:connect).
|
171
|
+
and_raise(OpenSSL::SSL::SSLError)
|
172
|
+
ssl_socket_for_debug.should_receive(:connect)
|
173
|
+
ssl_socket_for_debug.should_receive(:peer_cert).and_return(self_signed_crt)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "shows the CN used by the certificate and prints an error" do
|
177
|
+
expect { run }.to raise_error(SystemExit)
|
178
|
+
expect(stderr).to include("The SSL certificate of foo.example.com could not be verified")
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|