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.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef/api_client/registration.rb +46 -9
  3. data/lib/chef/application.rb +1 -0
  4. data/lib/chef/application/client.rb +25 -24
  5. data/lib/chef/client.rb +34 -0
  6. data/lib/chef/config.rb +11 -0
  7. data/lib/chef/cookbook/chefignore.rb +10 -2
  8. data/lib/chef/cookbook/metadata.rb +31 -3
  9. data/lib/chef/cookbook/synchronizer.rb +2 -2
  10. data/lib/chef/cookbook/syntax_check.rb +4 -4
  11. data/lib/chef/encrypted_data_bag_item.rb +37 -1
  12. data/lib/chef/exceptions.rb +1 -0
  13. data/lib/chef/guard_interpreter/default_guard_interpreter.rb +42 -0
  14. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +122 -0
  15. data/lib/chef/http.rb +0 -1
  16. data/lib/chef/http/decompressor.rb +7 -4
  17. data/lib/chef/http/simple.rb +5 -0
  18. data/lib/chef/http/validate_content_length.rb +28 -12
  19. data/lib/chef/knife.rb +1 -0
  20. data/lib/chef/knife/client_bulk_delete.rb +48 -9
  21. data/lib/chef/knife/client_delete.rb +4 -4
  22. data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
  23. data/lib/chef/knife/cookbook_upload.rb +17 -7
  24. data/lib/chef/knife/core/bootstrap_context.rb +1 -1
  25. data/lib/chef/knife/core/ui.rb +42 -5
  26. data/lib/chef/knife/node_run_list_add.rb +31 -2
  27. data/lib/chef/knife/ssh.rb +44 -31
  28. data/lib/chef/knife/ssl_check.rb +213 -0
  29. data/lib/chef/knife/ssl_fetch.rb +145 -0
  30. data/lib/chef/mixin/deep_merge.rb +13 -5
  31. data/lib/chef/mixin/shell_out.rb +9 -3
  32. data/lib/chef/node.rb +23 -4
  33. data/lib/chef/node/immutable_collections.rb +32 -0
  34. data/lib/chef/platform/provider_mapping.rb +21 -18
  35. data/lib/chef/platform/query_helpers.rb +10 -2
  36. data/lib/chef/policy_builder/expand_node_object.rb +3 -6
  37. data/lib/chef/provider/cron.rb +25 -3
  38. data/lib/chef/provider/mount/mount.rb +1 -1
  39. data/lib/chef/provider/package/dpkg.rb +2 -1
  40. data/lib/chef/provider/package/windows.rb +80 -0
  41. data/lib/chef/provider/package/windows/msi.rb +69 -0
  42. data/lib/chef/provider/powershell_script.rb +19 -6
  43. data/lib/chef/provider/service/solaris.rb +11 -7
  44. data/lib/chef/resource.rb +18 -5
  45. data/lib/chef/resource/conditional.rb +20 -7
  46. data/lib/chef/resource/cron.rb +18 -2
  47. data/lib/chef/resource/execute.rb +0 -2
  48. data/lib/chef/resource/powershell_script.rb +23 -1
  49. data/lib/chef/resource/script.rb +25 -0
  50. data/lib/chef/resource/subversion.rb +4 -0
  51. data/lib/chef/resource/windows_package.rb +79 -0
  52. data/lib/chef/resource/windows_script.rb +0 -5
  53. data/lib/chef/resources.rb +1 -0
  54. data/lib/chef/rest.rb +6 -1
  55. data/lib/chef/run_context.rb +22 -2
  56. data/lib/chef/run_context/cookbook_compiler.rb +12 -0
  57. data/lib/chef/util/editor.rb +92 -0
  58. data/lib/chef/util/file_edit.rb +22 -54
  59. data/lib/chef/version.rb +2 -2
  60. data/lib/chef/win32/api/installer.rb +166 -0
  61. data/lib/chef/win32/version.rb +8 -0
  62. data/spec/data/standalone_cookbook/Gemfile +1 -0
  63. data/spec/data/standalone_cookbook/chefignore +9 -0
  64. data/spec/data/standalone_cookbook/recipes/default.rb +3 -0
  65. data/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/multi_json.rb +1 -0
  66. data/spec/functional/resource/powershell_spec.rb +262 -1
  67. data/spec/functional/win32/versions_spec.rb +3 -3
  68. data/spec/integration/knife/chefignore_spec.rb +1 -2
  69. data/spec/integration/knife/raw_spec.rb +8 -13
  70. data/spec/integration/knife/redirection_spec.rb +6 -14
  71. data/spec/integration/solo/solo_spec.rb +19 -0
  72. data/spec/support/shared/functional/windows_script.rb +1 -1
  73. data/spec/support/shared/integration/app_server_support.rb +42 -0
  74. data/spec/support/shared/integration/integration_helper.rb +1 -0
  75. data/spec/support/shared/unit/script_resource.rb +38 -0
  76. data/spec/unit/api_client/registration_spec.rb +109 -38
  77. data/spec/unit/application/client_spec.rb +48 -1
  78. data/spec/unit/cookbook/chefignore_spec.rb +10 -0
  79. data/spec/unit/cookbook/metadata_spec.rb +45 -1
  80. data/spec/unit/cookbook/syntax_check_spec.rb +28 -0
  81. data/spec/unit/cookbook_spec.rb +0 -10
  82. data/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb +56 -0
  83. data/spec/unit/http/simple_spec.rb +32 -0
  84. data/spec/unit/http/validate_content_length_spec.rb +187 -0
  85. data/spec/unit/knife/bootstrap_spec.rb +13 -4
  86. data/spec/unit/knife/client_bulk_delete_spec.rb +123 -38
  87. data/spec/unit/knife/client_delete_spec.rb +4 -4
  88. data/spec/unit/knife/cookbook_upload_spec.rb +181 -88
  89. data/spec/unit/knife/core/bootstrap_context_spec.rb +11 -1
  90. data/spec/unit/knife/core/ui_spec.rb +109 -38
  91. data/spec/unit/knife/node_run_list_add_spec.rb +24 -1
  92. data/spec/unit/knife/ssh_spec.rb +17 -6
  93. data/spec/unit/knife/ssl_check_spec.rb +187 -0
  94. data/spec/unit/knife/ssl_fetch_spec.rb +151 -0
  95. data/spec/unit/mixin/deep_merge_spec.rb +17 -0
  96. data/spec/unit/node/immutable_collections_spec.rb +55 -0
  97. data/spec/unit/node_spec.rb +9 -0
  98. data/spec/unit/platform/query_helpers_spec.rb +32 -0
  99. data/spec/unit/platform_spec.rb +193 -175
  100. data/spec/unit/policy_builder/expand_node_object_spec.rb +1 -1
  101. data/spec/unit/provider/cron_spec.rb +175 -1
  102. data/spec/unit/provider/mount/mount_spec.rb +33 -3
  103. data/spec/unit/provider/package/dpkg_spec.rb +4 -0
  104. data/spec/unit/provider/package/windows/msi_spec.rb +60 -0
  105. data/spec/unit/provider/package/windows_spec.rb +80 -0
  106. data/spec/unit/provider/service/macosx_spec.rb +3 -3
  107. data/spec/unit/provider/service/solaris_smf_service_spec.rb +35 -10
  108. data/spec/unit/pure_application_spec.rb +32 -0
  109. data/spec/unit/recipe_spec.rb +4 -0
  110. data/spec/unit/resource/conditional_spec.rb +13 -12
  111. data/spec/unit/resource/cron_spec.rb +7 -2
  112. data/spec/unit/resource/powershell_spec.rb +85 -2
  113. data/spec/unit/resource/subversion_spec.rb +5 -0
  114. data/spec/unit/resource/windows_package_spec.rb +74 -0
  115. data/spec/unit/resource_spec.rb +23 -1
  116. data/spec/unit/rest_spec.rb +15 -0
  117. data/spec/unit/run_context/cookbook_compiler_spec.rb +12 -0
  118. data/spec/unit/run_context_spec.rb +7 -0
  119. data/spec/unit/util/editor_spec.rb +152 -0
  120. data/spec/unit/util/file_edit_spec.rb +37 -1
  121. 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
- before(:each) do
410
- @question = "monkeys rule"
411
- @stdout = StringIO.new
412
- @ui.stub(:stdout).and_return(@stdout)
413
- @ui.stdin.stub(:readline).and_return("y")
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
- it "should return true if you answer Y" do
417
- @ui.stdin.stub(:readline).and_return("Y")
418
- @ui.confirm(@question).should == true
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
- it "should return true if you answer y" do
422
- @ui.stdin.stub(:readline).and_return("y")
423
- @ui.confirm(@question).should == true
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
- it "should exit 3 if you answer N" do
427
- @ui.stdin.stub(:readline).and_return("N")
428
- lambda {
429
- @ui.confirm(@question)
430
- }.should raise_error(SystemExit) { |e| e.status.should == 3 }
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
- it "should exit 3 if you answer n" do
434
- @ui.stdin.stub(:readline).and_return("n")
435
- lambda {
436
- @ui.confirm(@question)
437
- }.should raise_error(SystemExit) { |e| e.status.should == 3 }
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 --y or --yes passed" do
441
- it "should return true" do
442
- @ui.config[:yes] = true
443
- @ui.confirm(@question).should == true
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
- describe "when asking for free-form user input" do
448
- it "asks a question and returns the answer provided by the user" do
449
- out = StringIO.new
450
- @ui.stub(:stdout).and_return(out)
451
- @ui.stub(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
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
- it "suggests a default setting and returns the default when the user's response only contains whitespace" do
457
- out = StringIO.new
458
- @ui.stub(:stdout).and_return(out)
459
- @ui.stub(:stdin).and_return(StringIO.new(" \n"))
460
- @ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000"
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 comas" do
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]"
@@ -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(['foo.example.org', 'bar.example.org'])
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(['ec2-10-0-0-1.compute-1.amazonaws.com', 'ec2-10-0-0-2.compute-1.amazonaws.com'])
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
+