knife-winops 2.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.travis.yml +30 -0
- data/CHANGELOG.md +147 -0
- data/DOC_CHANGES.md +22 -0
- data/Gemfile +13 -0
- data/LICENSE +201 -0
- data/README.md +430 -0
- data/RELEASE_NOTES.md +17 -0
- data/Rakefile +21 -0
- data/appveyor.yml +36 -0
- data/ci.gemfile +15 -0
- data/knife-winops.gemspec +26 -0
- data/lib/chef/knife/bootstrap/Chef_bootstrap.erb +44 -0
- data/lib/chef/knife/bootstrap/bootstrap.ps1 +134 -0
- data/lib/chef/knife/bootstrap/tail.cmd +15 -0
- data/lib/chef/knife/bootstrap/windows-chef-client-msi.erb +302 -0
- data/lib/chef/knife/bootstrap_windows_base.rb +473 -0
- data/lib/chef/knife/bootstrap_windows_ssh.rb +115 -0
- data/lib/chef/knife/bootstrap_windows_winrm.rb +102 -0
- data/lib/chef/knife/core/windows_bootstrap_context.rb +356 -0
- data/lib/chef/knife/knife_windows_base.rb +33 -0
- data/lib/chef/knife/windows_cert_generate.rb +155 -0
- data/lib/chef/knife/windows_cert_install.rb +68 -0
- data/lib/chef/knife/windows_helper.rb +36 -0
- data/lib/chef/knife/windows_listener_create.rb +107 -0
- data/lib/chef/knife/winrm.rb +127 -0
- data/lib/chef/knife/winrm_base.rb +128 -0
- data/lib/chef/knife/winrm_knife_base.rb +315 -0
- data/lib/chef/knife/winrm_session.rb +101 -0
- data/lib/chef/knife/winrm_shared_options.rb +54 -0
- data/lib/chef/knife/wsman_endpoint.rb +44 -0
- data/lib/chef/knife/wsman_test.rb +118 -0
- data/lib/knife-winops/path_helper.rb +242 -0
- data/lib/knife-winops/version.rb +6 -0
- data/spec/assets/fake_trusted_certs/excluded.txt +2 -0
- data/spec/assets/fake_trusted_certs/github.pem +42 -0
- data/spec/assets/fake_trusted_certs/google.crt +41 -0
- data/spec/assets/win_fake_trusted_cert_script.txt +89 -0
- data/spec/dummy_winrm_connection.rb +21 -0
- data/spec/functional/bootstrap_download_spec.rb +229 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/unit/knife/bootstrap_options_spec.rb +164 -0
- data/spec/unit/knife/bootstrap_template_spec.rb +98 -0
- data/spec/unit/knife/bootstrap_windows_winrm_spec.rb +410 -0
- data/spec/unit/knife/core/windows_bootstrap_context_spec.rb +292 -0
- data/spec/unit/knife/windows_cert_generate_spec.rb +90 -0
- data/spec/unit/knife/windows_cert_install_spec.rb +51 -0
- data/spec/unit/knife/windows_listener_create_spec.rb +76 -0
- data/spec/unit/knife/winrm_session_spec.rb +101 -0
- data/spec/unit/knife/winrm_spec.rb +494 -0
- data/spec/unit/knife/wsman_test_spec.rb +209 -0
- metadata +157 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
#
|
2
|
+
# Original knife-windows author:: Kartik Null Cating-Subramanian(<ksubramanian@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015-2016 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
|
+
|
21
|
+
describe Chef::Knife::Bootstrap, :chef_gte_12_7_only do
|
22
|
+
before(:all) do
|
23
|
+
Chef::Config.reset
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:bootstrap) { Chef::Knife::Bootstrap.new }
|
27
|
+
let(:win_bootstrap) { nil }
|
28
|
+
let(:opt_map) { {} }
|
29
|
+
let(:ref_ignore) { [] }
|
30
|
+
let(:win_ignore) { [] }
|
31
|
+
|
32
|
+
def compare_property(sym)
|
33
|
+
win_bootstrap.options.each do |win_key, win_val|
|
34
|
+
unless win_ignore.include?(win_key) || opt_map.include?(win_key) then
|
35
|
+
actual = win_val[sym]
|
36
|
+
expected = bootstrap.options[win_key][sym]
|
37
|
+
expect(actual).to eq(expected),
|
38
|
+
"#{win_key} flag's #{sym} property doesn't match
|
39
|
+
|
40
|
+
expected: #{expected}
|
41
|
+
got: #{actual}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
shared_examples 'compare_options' do
|
47
|
+
it 'contains the option flags' do
|
48
|
+
opt_map.default_proc = proc { |map, key| key }
|
49
|
+
filtered_keys = (win_bootstrap.options.keys - win_ignore).map! { |key| opt_map[key] }
|
50
|
+
|
51
|
+
expect(filtered_keys).to match_array(bootstrap.options.keys - ref_ignore)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'uses the same long-name' do
|
55
|
+
compare_property(:long)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'uses the same short-name' do
|
59
|
+
compare_property(:short)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'uses the same description' do
|
63
|
+
compare_property(:description)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'uses the same default value' do
|
67
|
+
compare_property(:default)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when compared to BootstrapWindowsWinrm' do
|
72
|
+
let(:win_bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new }
|
73
|
+
|
74
|
+
# opt_map: Hash of symbols in windows mapping to symbols in core. Name checks are
|
75
|
+
# ignored for these.
|
76
|
+
let(:opt_map) { {
|
77
|
+
:msi_url => :bootstrap_url,
|
78
|
+
:encrypted_data_bag_secret => :secret,
|
79
|
+
:encrypted_data_bag_secret_file => :secret_file,
|
80
|
+
:winrm_user => :ssh_user,
|
81
|
+
:winrm_password => :ssh_password,
|
82
|
+
:winrm_port => :ssh_port,
|
83
|
+
:winrm_ssl_verify_mode => :host_key_verify,
|
84
|
+
:bootstrap_install_command => :bootstrap_install_command,
|
85
|
+
:hint => :hint
|
86
|
+
}}
|
87
|
+
|
88
|
+
# ref_ignore: Options in core that are not implemented here.
|
89
|
+
let(:ref_ignore) { [
|
90
|
+
# These are irrelevant to WinRM.
|
91
|
+
:bootstrap_curl_options,
|
92
|
+
:bootstrap_wget_options,
|
93
|
+
:forward_agent,
|
94
|
+
:preserve_home,
|
95
|
+
:ssh_gateway,
|
96
|
+
:use_sudo,
|
97
|
+
:use_sudo_password,
|
98
|
+
:encrypt, # irrelevant during bootstrap
|
99
|
+
:identity_file,
|
100
|
+
:ssh_identity_file,
|
101
|
+
:ssh_gateway_identity,
|
102
|
+
:bootstrap_proxy_user,
|
103
|
+
:bootstrap_proxy_pass,
|
104
|
+
]}
|
105
|
+
|
106
|
+
# win_ignore: Options in windows that aren't relevant to core.
|
107
|
+
let(:win_ignore) { [
|
108
|
+
:attribute,
|
109
|
+
:auth_timeout,
|
110
|
+
:ca_trust_file,
|
111
|
+
:install_as_service,
|
112
|
+
:kerberos_keytab_file,
|
113
|
+
:kerberos_realm,
|
114
|
+
:kerberos_service,
|
115
|
+
:manual,
|
116
|
+
:session_timeout,
|
117
|
+
:ssl_peer_fingerprint,
|
118
|
+
:winrm_authentication_protocol,
|
119
|
+
:winrm_transport,
|
120
|
+
:winrm_codepage,
|
121
|
+
:concurrency,
|
122
|
+
:winrm_shell,
|
123
|
+
:bootstrap_debug,
|
124
|
+
] }
|
125
|
+
|
126
|
+
include_examples 'compare_options'
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'when compared to BootstrapWindowsSsh' do
|
130
|
+
let(:win_bootstrap) { Chef::Knife::BootstrapWindowsSsh.new }
|
131
|
+
|
132
|
+
# opt_map: Hash of symbols in windows mapping to symbols in core. Name checks are
|
133
|
+
# ignored for these.
|
134
|
+
let(:opt_map) { {
|
135
|
+
:msi_url => :bootstrap_url,
|
136
|
+
:encrypted_data_bag_secret => :secret,
|
137
|
+
:encrypted_data_bag_secret_file => :secret_file,
|
138
|
+
:bootstrap_install_command => :bootstrap_install_command,
|
139
|
+
:hint => :hint
|
140
|
+
}}
|
141
|
+
# ref_ignore: Options in core that are not implemented here.
|
142
|
+
let(:ref_ignore) { [
|
143
|
+
:bootstrap_curl_options,
|
144
|
+
:bootstrap_wget_options,
|
145
|
+
:preserve_home,
|
146
|
+
:use_sudo,
|
147
|
+
:use_sudo_password,
|
148
|
+
:encrypt, # irrelevant during bootstrap
|
149
|
+
:ssh_gateway_identity,
|
150
|
+
:bootstrap_proxy_user,
|
151
|
+
:bootstrap_proxy_pass,
|
152
|
+
]}
|
153
|
+
# win_ignore: Options in windows that aren't relevant to core.
|
154
|
+
let(:win_ignore) { [
|
155
|
+
:auth_timeout,
|
156
|
+
:install_as_service,
|
157
|
+
:host_key_verification, # Deprecated - remove this when the flag is removed.
|
158
|
+
:bootstrap_debug,
|
159
|
+
] }
|
160
|
+
|
161
|
+
include_examples 'compare_options'
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#
|
2
|
+
# Original knife-windows author:: Chirag Jog (<chirag@clogeny.com>)
|
3
|
+
# Copyright:: Copyright (c) 2013 Chirag Jog
|
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
|
+
TEMPLATE_FILE = File.expand_path(File.dirname(__FILE__)) + "/../../../lib/chef/knife/bootstrap/windows-chef-client-msi.erb"
|
20
|
+
|
21
|
+
require 'spec_helper'
|
22
|
+
|
23
|
+
describe Chef::Knife::BootstrapWindowsWinrm do
|
24
|
+
let(:template_file) { TEMPLATE_FILE }
|
25
|
+
let(:options) { [] }
|
26
|
+
let(:rendered_template) do
|
27
|
+
knife.instance_variable_set("@template_file", template_file)
|
28
|
+
knife.parse_options(options)
|
29
|
+
# Avoid referencing a validation keyfile we won't find during #render_template
|
30
|
+
template = IO.read(template_file).chomp
|
31
|
+
knife.render_template(template)
|
32
|
+
end
|
33
|
+
subject(:knife) { described_class.new }
|
34
|
+
|
35
|
+
before(:all) do
|
36
|
+
@original_config = Chef::Config.hash_dup
|
37
|
+
@original_knife_config = Chef::Config[:knife].dup
|
38
|
+
end
|
39
|
+
|
40
|
+
after(:all) do
|
41
|
+
Chef::Config.configuration = @original_config
|
42
|
+
Chef::Config[:knife] = @original_knife_config
|
43
|
+
end
|
44
|
+
|
45
|
+
before(:each) do
|
46
|
+
Chef::Log.logger = Logger.new(StringIO.new)
|
47
|
+
@knife = Chef::Knife::BootstrapWindowsWinrm.new
|
48
|
+
# Merge default settings in.
|
49
|
+
@knife.merge_configs
|
50
|
+
@knife.config[:template_file] = template_file
|
51
|
+
@stdout = StringIO.new
|
52
|
+
allow(@knife.ui).to receive(:stdout).and_return(@stdout)
|
53
|
+
@stderr = StringIO.new
|
54
|
+
allow(@knife.ui).to receive(:stderr).and_return(@stderr)
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "specifying no_proxy with various entries" do
|
58
|
+
let(:options){ ["--bootstrap-proxy", "", "--bootstrap-no-proxy", setting] }
|
59
|
+
|
60
|
+
context "via --bootstrap-no-proxy" do
|
61
|
+
let(:setting) { "api.chef.io" }
|
62
|
+
|
63
|
+
it "renders the client.rb with a single FQDN no_proxy entry" do
|
64
|
+
expect(rendered_template).to match(%r{.*no_proxy\s*\"api.chef.io\".*})
|
65
|
+
end
|
66
|
+
end
|
67
|
+
context "via --bootstrap-no-proxy multiple" do
|
68
|
+
let(:setting) { "api.chef.io,172.16.10.*" }
|
69
|
+
|
70
|
+
it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
|
71
|
+
expect(rendered_template).to match(%r{.*no_proxy\s*"api.chef.io,172.16.10.\*".*})
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "specifying --msi-url" do
|
77
|
+
context "with explicitly provided --msi-url" do
|
78
|
+
let(:options) { ["--msi-url", "file:///something.msi"] }
|
79
|
+
|
80
|
+
it "bootstrap batch file must fetch from provided url" do
|
81
|
+
expect(rendered_template).to match(%r{.*CHEF_REMOTE_SOURCE_MSI_URL \"file:///something\.msi.*})
|
82
|
+
end
|
83
|
+
end
|
84
|
+
context "with no provided --msi-url" do
|
85
|
+
it "bootstrap batch file must fetch from provided url" do
|
86
|
+
expect(rendered_template).to match(%r{.*CHEF_REMOTE_SOURCE_MSI_URL \"https://www\.chef\.io/.*})
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "specifying knife_config[:architecture]" do
|
92
|
+
it "puts the target architecture into the msi_url" do
|
93
|
+
Chef::Config[:knife][:architecture] = :x86_64
|
94
|
+
expect(rendered_template).to match(/MACHINE_ARCH=x86_64/)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,410 @@
|
|
1
|
+
#
|
2
|
+
# Original knife-windows author:: Adam Edwards(<adamed@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2014-2016 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 'winrm/output'
|
21
|
+
|
22
|
+
Chef::Knife::Winrm.load_deps
|
23
|
+
|
24
|
+
describe Chef::Knife::BootstrapWindowsWinrm do
|
25
|
+
before do
|
26
|
+
Chef::Config.reset
|
27
|
+
bootstrap.config[:run_list] = []
|
28
|
+
allow(bootstrap).to receive(:validate_options!).and_return(nil)
|
29
|
+
allow(bootstrap).to receive(:sleep).and_return(10)
|
30
|
+
allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session)
|
31
|
+
allow(File).to receive(:exist?).with(anything).and_call_original
|
32
|
+
allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
allow(bootstrap).to receive(:sleep).and_return(10)
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:session_opts) do
|
40
|
+
{
|
41
|
+
user: "Administrator",
|
42
|
+
password: "testpassword",
|
43
|
+
port: "5986",
|
44
|
+
transport: :ssl,
|
45
|
+
host: "localhost"
|
46
|
+
}
|
47
|
+
end
|
48
|
+
let(:bootstrap) { Chef::Knife::BootstrapWindowsWinrm.new(['winrm', '-d', 'windows-chef-client-msi', '-x', session_opts[:user], '-P', session_opts[:password], session_opts[:host]]) }
|
49
|
+
let(:session) { Chef::Knife::WinrmSession.new(session_opts) }
|
50
|
+
let(:arch_session_result) {
|
51
|
+
o = WinRM::Output.new
|
52
|
+
o << {stdout: "X86\r\n"}
|
53
|
+
o
|
54
|
+
}
|
55
|
+
let(:arch_session_results) { [arch_session_result] }
|
56
|
+
let(:initial_fail_count) { 4 }
|
57
|
+
|
58
|
+
context "knife secret-file && knife secret options are passed" do
|
59
|
+
before do
|
60
|
+
Chef::Config.reset
|
61
|
+
Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
|
62
|
+
Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
|
63
|
+
end
|
64
|
+
it "gives preference to secret key passed under knife's secret-file option" do
|
65
|
+
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
|
66
|
+
Chef::Config[:knife][:encrypted_data_bag_secret_file]).
|
67
|
+
and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
|
68
|
+
expect(bootstrap.load_correct_secret).to eq(
|
69
|
+
"data_bag_secret_key_passed_under_knife_secret_file_option")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "cli secret-file && cli secret options are passed" do
|
74
|
+
before do
|
75
|
+
Chef::Config.reset
|
76
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
|
77
|
+
bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
|
78
|
+
end
|
79
|
+
it "gives preference to secret key passed under cli's secret-file option" do
|
80
|
+
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
|
81
|
+
bootstrap.config[:encrypted_data_bag_secret_file]).
|
82
|
+
and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
|
83
|
+
expect(bootstrap.load_correct_secret).to eq(
|
84
|
+
"data_bag_secret_key_passed_under_cli_secret_file_option")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "knife secret-file, knife secret, cli secret-file && cli secret options are passed" do
|
89
|
+
before do
|
90
|
+
Chef::Config.reset
|
91
|
+
Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/knife_encrypted_data_bag_secret"
|
92
|
+
Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
|
93
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/cli_encrypted_data_bag_secret"
|
94
|
+
bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
|
95
|
+
end
|
96
|
+
it "gives preference to secret key passed under cli's secret-file option" do
|
97
|
+
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
|
98
|
+
Chef::Config[:knife][:encrypted_data_bag_secret_file]).
|
99
|
+
and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
|
100
|
+
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
|
101
|
+
bootstrap.config[:encrypted_data_bag_secret_file]).
|
102
|
+
and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
|
103
|
+
expect(bootstrap.load_correct_secret).to eq(
|
104
|
+
"data_bag_secret_key_passed_under_cli_secret_file_option")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "knife secret-file && cli secret options are passed" do
|
109
|
+
before do
|
110
|
+
Chef::Config.reset
|
111
|
+
Chef::Config[:knife][:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
|
112
|
+
bootstrap.config[:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_cli_secret_option"
|
113
|
+
end
|
114
|
+
it "gives preference to secret key passed under cli's secret option" do
|
115
|
+
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
|
116
|
+
Chef::Config[:knife][:encrypted_data_bag_secret_file]).
|
117
|
+
and_return("data_bag_secret_key_passed_under_knife_secret_file_option")
|
118
|
+
expect(bootstrap.load_correct_secret).to eq(
|
119
|
+
"data_bag_secret_key_passed_under_cli_secret_option")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "knife secret && cli secret-file options are passed" do
|
124
|
+
before do
|
125
|
+
Chef::Config.reset
|
126
|
+
Chef::Config[:knife][:encrypted_data_bag_secret] = "data_bag_secret_key_passed_under_knife_secret_option"
|
127
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
|
128
|
+
end
|
129
|
+
it "gives preference to secret key passed under cli's secret-file option" do
|
130
|
+
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
|
131
|
+
bootstrap.config[:encrypted_data_bag_secret_file]).
|
132
|
+
and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
|
133
|
+
expect(bootstrap.load_correct_secret).to eq(
|
134
|
+
"data_bag_secret_key_passed_under_cli_secret_file_option")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "cli secret-file option is passed" do
|
139
|
+
before do
|
140
|
+
Chef::Config.reset
|
141
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = "/tmp/encrypted_data_bag_secret"
|
142
|
+
end
|
143
|
+
it "takes the secret key passed under cli's secret-file option" do
|
144
|
+
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
|
145
|
+
bootstrap.config[:encrypted_data_bag_secret_file]).
|
146
|
+
and_return("data_bag_secret_key_passed_under_cli_secret_file_option")
|
147
|
+
expect(bootstrap.load_correct_secret).to eq(
|
148
|
+
"data_bag_secret_key_passed_under_cli_secret_file_option")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should pass exit code from failed winrm call' do
|
153
|
+
allow(session).to receive(:exit_code).and_return(500)
|
154
|
+
allow(bootstrap).to receive(:wait_for_remote_response)
|
155
|
+
allow(bootstrap).to receive(:create_bootstrap_bat_command)
|
156
|
+
allow(session).to receive(:relay_command).and_return(arch_session_result)
|
157
|
+
allow(bootstrap.ui).to receive(:info)
|
158
|
+
expect {
|
159
|
+
bootstrap.run_with_pretty_exceptions
|
160
|
+
}.to raise_error(SystemExit) { |e|
|
161
|
+
expect(e.status).to eq(500)
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should retry if a 401 is received from WinRM' do
|
166
|
+
call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '401')}}
|
167
|
+
call_result_sequence.push(0)
|
168
|
+
allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
|
169
|
+
allow(bootstrap).to receive(:print)
|
170
|
+
allow(bootstrap.ui).to receive(:info)
|
171
|
+
|
172
|
+
expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
|
173
|
+
bootstrap.send(:wait_for_remote_response, 2)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should retry if something other than a 401 is received from WinRM' do
|
177
|
+
call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
|
178
|
+
call_result_sequence.push(0)
|
179
|
+
allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
|
180
|
+
allow(bootstrap).to receive(:print)
|
181
|
+
allow(bootstrap.ui).to receive(:info)
|
182
|
+
|
183
|
+
expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
|
184
|
+
bootstrap.send(:wait_for_remote_response, 2)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'should keep retrying at 10s intervals if the timeout in minutes has not elapsed' do
|
188
|
+
call_result_sequence = Array.new(initial_fail_count) {lambda {raise WinRM::WinRMHTTPTransportError.new('', '500')}}
|
189
|
+
call_result_sequence.push(0)
|
190
|
+
allow(bootstrap).to receive(:run_command).and_return(*call_result_sequence)
|
191
|
+
allow(bootstrap).to receive(:print)
|
192
|
+
allow(bootstrap.ui).to receive(:info)
|
193
|
+
|
194
|
+
expect(bootstrap).to receive(:run_command).exactly(call_result_sequence.length).times
|
195
|
+
bootstrap.send(:wait_for_remote_response, 2)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should have a wait timeout of 2 minutes by default' do
|
199
|
+
allow(bootstrap).to receive(:run_command).and_raise(WinRM::WinRMHTTPTransportError.new('','500'))
|
200
|
+
allow(bootstrap).to receive(:create_bootstrap_bat_command).and_raise(SystemExit)
|
201
|
+
expect(bootstrap).to receive(:wait_for_remote_response).with(2)
|
202
|
+
|
203
|
+
allow(bootstrap.ui).to receive(:info)
|
204
|
+
bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
|
205
|
+
expect { bootstrap.bootstrap }.to raise_error(SystemExit)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'should not a wait for timeout on Errno::ECONNREFUSED' do
|
209
|
+
allow(bootstrap).to receive(:run_command).and_raise(Errno::ECONNREFUSED.new)
|
210
|
+
allow(bootstrap.ui).to receive(:info)
|
211
|
+
bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
|
212
|
+
expect(bootstrap.ui).to receive(:error).with("Connection refused connecting to localhost:5985.")
|
213
|
+
|
214
|
+
# wait_for_remote_response is protected method, So define singleton test method to call it.
|
215
|
+
bootstrap.define_singleton_method(:test_wait_for_remote_response){wait_for_remote_response(bootstrap.options[:auth_timeout][:default])}
|
216
|
+
expect { bootstrap.test_wait_for_remote_response }.to raise_error(Errno::ECONNREFUSED)
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'should stop retrying if more than 2 minutes has elapsed' do
|
220
|
+
times = [ Time.new(2014, 4, 1, 22, 25), Time.new(2014, 4, 1, 22, 51), Time.new(2014, 4, 1, 22, 28) ]
|
221
|
+
allow(Time).to receive(:now).and_return(*times)
|
222
|
+
run_command_result = lambda {raise WinRM::WinRMHTTPTransportError, '401'}
|
223
|
+
allow(bootstrap).to receive(:run_command).and_return(run_command_result)
|
224
|
+
allow(bootstrap).to receive(:print)
|
225
|
+
allow(bootstrap.ui).to receive(:info)
|
226
|
+
allow(bootstrap.ui).to receive(:error)
|
227
|
+
expect(bootstrap).to receive(:run_command).exactly(1).times
|
228
|
+
bootstrap.config[:auth_timeout] = bootstrap.options[:auth_timeout][:default]
|
229
|
+
expect { bootstrap.bootstrap }.to raise_error /Command execution failed./
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'successfully bootstraps' do
|
233
|
+
Chef::Config[:knife][:bootstrap_architecture] = :i386
|
234
|
+
allow(bootstrap).to receive(:wait_for_remote_response)
|
235
|
+
allow(bootstrap).to receive(:create_bootstrap_bat_command)
|
236
|
+
allow(bootstrap).to receive(:run_command).and_return(0)
|
237
|
+
expect(bootstrap.bootstrap).to eq(0)
|
238
|
+
expect(Chef::Config[:knife][:architecture]).to eq(:i686)
|
239
|
+
end
|
240
|
+
|
241
|
+
context "when the target node is 64 bit" do
|
242
|
+
it 'successfully bootstraps' do
|
243
|
+
Chef::Config[:knife][:bootstrap_architecture] = :x86_64
|
244
|
+
allow(bootstrap).to receive(:wait_for_remote_response)
|
245
|
+
allow(bootstrap).to receive(:create_bootstrap_bat_command)
|
246
|
+
allow(bootstrap).to receive(:run_command).and_return(0)
|
247
|
+
expect(bootstrap.bootstrap).to eq(0)
|
248
|
+
expect(Chef::Config[:knife][:architecture]).to eq(:x86_64)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'FQDN validation -' do
|
253
|
+
it 'should raise an error if FQDN value is not passed' do
|
254
|
+
bootstrap.instance_variable_set(:@name_args, [])
|
255
|
+
allow(bootstrap.ui).to receive(:error)
|
256
|
+
expect {
|
257
|
+
bootstrap.run
|
258
|
+
}.to raise_error(SystemExit)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'should not raise error if FQDN value is passed' do
|
262
|
+
bootstrap.instance_variable_set(:@name_args, ["fqdn_name"])
|
263
|
+
expect {
|
264
|
+
bootstrap.run
|
265
|
+
}.not_to raise_error(SystemExit)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context "when validation_key is not present" do
|
270
|
+
before do
|
271
|
+
allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
|
272
|
+
bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
|
273
|
+
Chef::Config[:knife] = {:chef_node_name => 'foo.example.com'}
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'raises an exception if winrm_authentication_protocol is basic and transport is plaintext' do
|
277
|
+
Chef::Config[:knife] = {:winrm_authentication_protocol => 'basic', :winrm_transport => 'plaintext', :chef_node_name => 'foo.example.com'}
|
278
|
+
expect(bootstrap.ui).to receive(:error)
|
279
|
+
expect { bootstrap.run }.to raise_error(SystemExit)
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'raises an exception if chef_node_name is not present ' do
|
283
|
+
Chef::Config[:knife] = {:chef_node_name => nil}
|
284
|
+
expect(bootstrap.client_builder).not_to receive(:run)
|
285
|
+
expect(bootstrap.client_builder).not_to receive(:client_path)
|
286
|
+
expect(bootstrap.ui).to receive(:error)
|
287
|
+
expect { bootstrap.bootstrap }.to raise_error(SystemExit)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context "when doing chef vault" do
|
292
|
+
let(:vault_handler) { double('vault_handler', :doing_chef_vault? => true) }
|
293
|
+
let(:node_name) { 'foo.example.com' }
|
294
|
+
before do
|
295
|
+
allow(bootstrap).to receive(:wait_for_remote_response)
|
296
|
+
allow(bootstrap).to receive(:create_bootstrap_bat_command)
|
297
|
+
allow(bootstrap).to receive(:run_command).and_return(0)
|
298
|
+
bootstrap.config[:chef_node_name] = node_name
|
299
|
+
bootstrap.chef_vault_handler = vault_handler
|
300
|
+
end
|
301
|
+
|
302
|
+
context "builder does not respond to client" do
|
303
|
+
before do
|
304
|
+
bootstrap.client_builder = instance_double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil)
|
305
|
+
end
|
306
|
+
|
307
|
+
it "passes a node search query to the handler" do
|
308
|
+
expect(vault_handler).to receive(:run).with(node_name: node_name)
|
309
|
+
bootstrap.bootstrap
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
context "builder responds to client" do
|
314
|
+
let(:client) { Chef::ApiClient.new }
|
315
|
+
|
316
|
+
before do
|
317
|
+
bootstrap.client_builder = double("Chef::Knife::Bootstrap::ClientBuilder", :run => nil, :client_path => nil, :client => client)
|
318
|
+
end
|
319
|
+
|
320
|
+
it "passes a node search query to the handler" do
|
321
|
+
expect(vault_handler).to receive(:run).with(client)
|
322
|
+
bootstrap.bootstrap
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
describe 'first_boot_attributes' do
|
328
|
+
let(:first_boot_attributes) { { 'a1' => 'b1', 'a2' => 'b2', 'source' => 'hash' } }
|
329
|
+
let(:json_file) { 'my_json.json' }
|
330
|
+
let(:first_boot_attributes_from_file) { read_json_file(json_file) }
|
331
|
+
|
332
|
+
before do
|
333
|
+
File.open(json_file,"w+") do |f|
|
334
|
+
f.write <<-EOH
|
335
|
+
{"b2" : "a3", "a4" : "b5", "source" : "file"}
|
336
|
+
EOH
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
context 'when none of the json-attributes options are passed' do
|
341
|
+
it 'returns an empty hash' do
|
342
|
+
response = bootstrap.first_boot_attributes
|
343
|
+
expect(response).to be == {}
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
context 'when only --json-attributes option is passed' do
|
348
|
+
before do
|
349
|
+
bootstrap.config[:first_boot_attributes] = first_boot_attributes
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'returns the hash passed by the user in --json-attributes option' do
|
353
|
+
response = bootstrap.first_boot_attributes
|
354
|
+
expect(response).to be == first_boot_attributes
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'when only --json-attribute-file option is passed' do
|
359
|
+
before do
|
360
|
+
bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'returns the hash passed by the user in --json-attribute-file option' do
|
364
|
+
response = bootstrap.first_boot_attributes
|
365
|
+
expect(response).to be == { 'b2' => 'a3', 'a4' => 'b5', 'source' => 'file' }
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
context 'when both the --json-attributes option and --json-attribute-file options are passed' do
|
370
|
+
before do
|
371
|
+
bootstrap.config[:first_boot_attributes] = first_boot_attributes
|
372
|
+
bootstrap.config[:first_boot_attributes_from_file] = first_boot_attributes_from_file
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'returns the hash passed by the user in --json-attributes option' do
|
376
|
+
response = bootstrap.first_boot_attributes
|
377
|
+
expect(response).to be == first_boot_attributes
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
after do
|
382
|
+
FileUtils.rm_rf json_file
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe 'render_template' do
|
387
|
+
before do
|
388
|
+
allow(bootstrap).to receive(:first_boot_attributes).and_return(
|
389
|
+
{ 'a1' => 'b3', 'a2' => 'b1' }
|
390
|
+
)
|
391
|
+
allow(bootstrap).to receive(:load_correct_secret).and_return(
|
392
|
+
'my_secret'
|
393
|
+
)
|
394
|
+
allow(Erubis::Eruby).to receive_message_chain(:new, :evaluate).and_return(
|
395
|
+
'my_template'
|
396
|
+
)
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'sets correct values into config and returns the correct response' do
|
400
|
+
response = bootstrap.render_template
|
401
|
+
expect(bootstrap.config[:first_boot_attributes]).to be == { 'a1' => 'b3', 'a2' => 'b1' }
|
402
|
+
expect(bootstrap.config[:secret]).to be == 'my_secret'
|
403
|
+
expect(response).to be == 'my_template'
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def read_json_file(file)
|
409
|
+
Chef::JSONCompat.parse(File.read(file))
|
410
|
+
end
|