chef-apply 0.1.17 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -7
- data/Gemfile.lock +176 -84
- data/chef-apply.gemspec +2 -2
- data/lib/chef_apply/version.rb +1 -1
- data/spec/fixtures/custom_config.toml +2 -0
- data/spec/integration/chef-run_spec.rb +41 -0
- data/spec/integration/fixtures/chef_help.out +69 -0
- data/spec/integration/fixtures/chef_version.out +1 -0
- data/spec/integration/spec_helper.rb +55 -0
- data/spec/spec_helper.rb +114 -0
- data/spec/support/matchers/output_to_terminal.rb +36 -0
- data/spec/unit/action/base_spec.rb +89 -0
- data/spec/unit/action/converge_target_spec.rb +292 -0
- data/spec/unit/action/generate_local_policy_spec.rb +114 -0
- data/spec/unit/action/generate_temp_cookbook_spec.rb +75 -0
- data/spec/unit/action/install_chef/base_spec.rb +234 -0
- data/spec/unit/action/install_chef_spec.rb +69 -0
- data/spec/unit/cli/options_spec.rb +75 -0
- data/spec/unit/cli/validation_spec.rb +78 -0
- data/spec/unit/cli_spec.rb +440 -0
- data/spec/unit/config_spec.rb +70 -0
- data/spec/unit/errors/ccr_failure_mapper_spec.rb +103 -0
- data/spec/unit/file_fetcher_spec.rb +40 -0
- data/spec/unit/fixtures/multi-error.out +2 -0
- data/spec/unit/log_spec.rb +37 -0
- data/spec/unit/recipe_lookup_spec.rb +122 -0
- data/spec/unit/startup_spec.rb +283 -0
- data/spec/unit/target_host_spec.rb +231 -0
- data/spec/unit/target_resolver_spec.rb +380 -0
- data/spec/unit/telemeter/sender_spec.rb +140 -0
- data/spec/unit/telemeter_spec.rb +191 -0
- data/spec/unit/temp_cookbook_spec.rb +199 -0
- data/spec/unit/ui/error_printer_spec.rb +173 -0
- data/spec/unit/ui/terminal_spec.rb +109 -0
- data/spec/unit/version_spec.rb +31 -0
- data/warning.txt +3 -0
- metadata +34 -2
@@ -0,0 +1,231 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2018 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require "spec_helper"
|
19
|
+
require "ostruct"
|
20
|
+
require "chef_apply/target_host"
|
21
|
+
|
22
|
+
RSpec.describe ChefApply::TargetHost do
|
23
|
+
let(:host) { "mock://user@example.com" }
|
24
|
+
let(:sudo) { true }
|
25
|
+
let(:logger) { nil }
|
26
|
+
let(:family) { "windows" }
|
27
|
+
let(:is_linux) { false }
|
28
|
+
let(:platform_mock) { double("platform", linux?: is_linux, family: family, name: "an os") }
|
29
|
+
subject do
|
30
|
+
s = ChefApply::TargetHost.new(host, sudo: sudo, logger: logger)
|
31
|
+
allow(s).to receive(:platform).and_return(platform_mock)
|
32
|
+
s
|
33
|
+
end
|
34
|
+
|
35
|
+
context "#base_os" do
|
36
|
+
context "for a windows os" do
|
37
|
+
it "reports :windows" do
|
38
|
+
expect(subject.base_os).to eq :windows
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "for a linux os" do
|
43
|
+
let(:family) { "debian" }
|
44
|
+
let(:is_linux) { true }
|
45
|
+
it "reports :linux" do
|
46
|
+
expect(subject.base_os).to eq :linux
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "for an unsupported OS" do
|
51
|
+
let(:family) { "other" }
|
52
|
+
let(:is_linux) { false }
|
53
|
+
it "raises UnsupportedTargetOS" do
|
54
|
+
expect { subject.base_os }.to raise_error(ChefApply::TargetHost::UnsupportedTargetOS)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "#installed_chef_version" do
|
60
|
+
let(:manifest) { :not_found }
|
61
|
+
before do
|
62
|
+
allow(subject).to receive(:get_chef_version_manifest).and_return manifest
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when no version manifest is present" do
|
66
|
+
it "raises ChefNotInstalled" do
|
67
|
+
expect { subject.installed_chef_version }.to raise_error(ChefApply::TargetHost::ChefNotInstalled)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when version manifest is present" do
|
72
|
+
let(:manifest) { { "build_version" => "14.0.1" } }
|
73
|
+
it "reports version based on the build_version field" do
|
74
|
+
expect(subject.installed_chef_version).to eq Gem::Version.new("14.0.1")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "connect!" do
|
80
|
+
let(:train_connection_mock) { double("train connection") }
|
81
|
+
before do
|
82
|
+
allow(subject).to receive(:train_connection).and_return(train_connection_mock)
|
83
|
+
end
|
84
|
+
context "when an Train::UserError occurs" do
|
85
|
+
it "raises a ConnectionFailure" do
|
86
|
+
allow(train_connection_mock).to receive(:connection).and_raise Train::UserError
|
87
|
+
expect { subject.connect! }.to raise_error(ChefApply::TargetHost::ConnectionFailure)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
context "when a Train::Error occurs" do
|
91
|
+
it "raises a ConnectionFailure" do
|
92
|
+
allow(train_connection_mock).to receive(:connection).and_raise Train::Error
|
93
|
+
expect { subject.connect! }.to raise_error(ChefApply::TargetHost::ConnectionFailure)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "#user" do
|
99
|
+
before do
|
100
|
+
allow(subject).to receive(:config).and_return(user: user)
|
101
|
+
end
|
102
|
+
context "when a user has been configured" do
|
103
|
+
let(:user) { "testuser" }
|
104
|
+
it "returns that user" do
|
105
|
+
expect(subject.user).to eq user
|
106
|
+
end
|
107
|
+
end
|
108
|
+
context "when no user has been configured" do
|
109
|
+
let(:user) { nil }
|
110
|
+
it "returns the correct default from train" do
|
111
|
+
expect(subject.user).to eq Train::Transports::SSH.default_options[:user][:default]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "#run_command!" do
|
117
|
+
let(:backend) { double("backend") }
|
118
|
+
let(:exit_status) { 0 }
|
119
|
+
let(:result) { RemoteExecResult.new(exit_status, "", "an error occurred") }
|
120
|
+
let(:command) { "cmd" }
|
121
|
+
|
122
|
+
before do
|
123
|
+
allow(subject).to receive(:backend).and_return(backend)
|
124
|
+
allow(backend).to receive(:run_command).with(command).and_return(result)
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when no error occurs" do
|
128
|
+
let(:exit_status) { 0 }
|
129
|
+
it "returns the result" do
|
130
|
+
expect(subject.run_command!(command)).to eq result
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when sudo_as_user is true" do
|
134
|
+
let(:family) { "debian" }
|
135
|
+
let(:is_linux) { true }
|
136
|
+
it "returns the result" do
|
137
|
+
expect(backend).to receive(:run_command).with("-u user #{command}").and_return(result)
|
138
|
+
expect(subject.run_command!(command, true)).to eq result
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when an error occurs" do
|
144
|
+
let(:exit_status) { 1 }
|
145
|
+
it "raises a RemoteExecutionFailed error" do
|
146
|
+
expected_error = ChefApply::TargetHost::RemoteExecutionFailed
|
147
|
+
expect { subject.run_command!(command) }.to raise_error(expected_error)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "#get_chef_version_manifest" do
|
153
|
+
let(:manifest_content) { '{"build_version" : "1.2.3"}' }
|
154
|
+
let(:expected_manifest_path) do
|
155
|
+
{
|
156
|
+
windows: "c:\\opscode\\chef\\version-manifest.json",
|
157
|
+
linux: "/opt/chef/version-manifest.json"
|
158
|
+
}
|
159
|
+
end
|
160
|
+
let(:base_os) { :unknown }
|
161
|
+
before do
|
162
|
+
remote_file_mock = double("remote_file", file?: manifest_exists, content: manifest_content)
|
163
|
+
backend_mock = double("backend")
|
164
|
+
expect(backend_mock).to receive(:file).
|
165
|
+
with(expected_manifest_path[base_os]).
|
166
|
+
and_return(remote_file_mock)
|
167
|
+
allow(subject).to receive(:backend).and_return backend_mock
|
168
|
+
allow(subject).to receive(:base_os).and_return base_os
|
169
|
+
end
|
170
|
+
|
171
|
+
context "when manifest is missing" do
|
172
|
+
let(:manifest_exists) { false }
|
173
|
+
context "on windows" do
|
174
|
+
let(:base_os) { :windows }
|
175
|
+
it "returns :not_found" do
|
176
|
+
expect(subject.get_chef_version_manifest).to eq :not_found
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
context "on linux" do
|
181
|
+
let(:base_os) { :linux }
|
182
|
+
it "returns :not_found" do
|
183
|
+
expect(subject.get_chef_version_manifest).to eq :not_found
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context "when manifest is present" do
|
189
|
+
let(:manifest_exists) { true }
|
190
|
+
context "on windows" do
|
191
|
+
let(:base_os) { :windows }
|
192
|
+
it "should return the parsed manifest" do
|
193
|
+
expect(subject.get_chef_version_manifest).to eq({ "build_version" => "1.2.3" })
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "on linux" do
|
198
|
+
let(:base_os) { :linux }
|
199
|
+
it "should return the parsed manifest" do
|
200
|
+
expect(subject.get_chef_version_manifest).to eq({ "build_version" => "1.2.3" })
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "#apply_ssh_config" do
|
207
|
+
let(:ssh_host_config) { { user: "testuser", port: 1000, proxy: double("Net:SSH::Proxy::Command") } }
|
208
|
+
let(:connection_config) { { user: "user1", port: 8022, proxy: nil } }
|
209
|
+
before do
|
210
|
+
allow(subject).to receive(:ssh_config_for_host).and_return ssh_host_config
|
211
|
+
end
|
212
|
+
|
213
|
+
ChefApply::TargetHost::SSH_CONFIG_OVERRIDE_KEYS.each do |key|
|
214
|
+
context "when a value is not explicitly provided in options" do
|
215
|
+
it "replaces config config[:#{key}] with the ssh config value" do
|
216
|
+
subject.apply_ssh_config(connection_config, key => nil)
|
217
|
+
expect(connection_config[key]).to eq(ssh_host_config[key])
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "when a value is explicitly provided in options" do
|
222
|
+
it "the connection configuration isnot updated with a value from ssh config" do
|
223
|
+
original_config = connection_config.clone
|
224
|
+
subject.apply_ssh_config(connection_config, { key => "testvalue" } )
|
225
|
+
expect(connection_config[key]).to eq original_config[key]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
@@ -0,0 +1,380 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2018 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require "spec_helper"
|
19
|
+
require "chef_apply/target_resolver"
|
20
|
+
|
21
|
+
RSpec.describe ChefApply::TargetResolver do
|
22
|
+
let(:target_string) { "" }
|
23
|
+
let(:default_protocol) { "ssh" }
|
24
|
+
let(:connection_options) { {} }
|
25
|
+
subject { ChefApply::TargetResolver.new(target_string, default_protocol, connection_options) }
|
26
|
+
|
27
|
+
context "#targets" do
|
28
|
+
context "when no target is provided" do
|
29
|
+
let(:target_string) { "" }
|
30
|
+
it "returns an empty array" do
|
31
|
+
expect(subject.targets).to eq []
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when a single target is provided" do
|
36
|
+
let(:target_string) { "ssh://localhost" }
|
37
|
+
it "returns any array with one target" do
|
38
|
+
actual_targets = subject.targets
|
39
|
+
expect(actual_targets[0].config[:host]).to eq "localhost"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when a comma-separated list of targets is provided" do
|
44
|
+
let(:target_string) { "ssh://node1.example.com,winrm://node2.example.com" }
|
45
|
+
it "returns an array with correct TargetHost instances" do
|
46
|
+
actual_targets = subject.targets
|
47
|
+
expect(actual_targets[0].config[:host]).to eq "node1.example.com"
|
48
|
+
expect(actual_targets[1].config[:host]).to eq "node2.example.com"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
context "when a comma-separated list of targets that include ranges is provided" do
|
52
|
+
let(:target_string) { "ssh://node[0:1],ssh://machine[0:1]" }
|
53
|
+
it "returns an array with correct TargetHost instances" do
|
54
|
+
actual_targets = subject.targets
|
55
|
+
|
56
|
+
expect(actual_targets[0].config[:host]).to eq "node0"
|
57
|
+
expect(actual_targets[1].config[:host]).to eq "node1"
|
58
|
+
expect(actual_targets[2].config[:host]).to eq "machine0"
|
59
|
+
expect(actual_targets[3].config[:host]).to eq "machine1"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when a mixed list of targets containing user prefix and not are included" do
|
64
|
+
|
65
|
+
let(:target_string) { "test_user1@node1,node2,test_user2:password@node3" }
|
66
|
+
|
67
|
+
context "and the :user option is provided" do
|
68
|
+
|
69
|
+
let(:connection_options) { { user: "defaultuser" } }
|
70
|
+
it "should default to the given :user only for the host that does not include name" do
|
71
|
+
actual_targets = subject.targets
|
72
|
+
tc = actual_targets[0].config
|
73
|
+
expect(tc[:host]).to eq "node1"
|
74
|
+
expect(tc[:user]).to eq "test_user1"
|
75
|
+
|
76
|
+
tc = actual_targets[1].config
|
77
|
+
expect(tc[:host]).to eq "node2"
|
78
|
+
expect(tc[:user]).to eq "defaultuser"
|
79
|
+
|
80
|
+
tc = actual_targets[2].config
|
81
|
+
expect(tc[:host]).to eq "node3"
|
82
|
+
expect(tc[:user]).to eq "test_user2"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
context "and the :user option is not provided" do
|
86
|
+
let(:opts) { {} }
|
87
|
+
it "should default to no user when user is not included with host" do
|
88
|
+
actual_targets = subject.targets
|
89
|
+
tc = actual_targets[0].config
|
90
|
+
expect(tc[:host]).to eq "node1"
|
91
|
+
expect(tc[:user]).to eq "test_user1"
|
92
|
+
|
93
|
+
tc = actual_targets[1].config
|
94
|
+
expect(tc[:host]).to eq "node2"
|
95
|
+
expect(tc[:user]).to eq nil
|
96
|
+
|
97
|
+
tc = actual_targets[2].config
|
98
|
+
expect(tc[:host]).to eq "node3"
|
99
|
+
expect(tc[:user]).to eq "test_user2"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "#expand_targets" do
|
107
|
+
it "returns a single item when no expansion is required" do
|
108
|
+
expect(subject.expand_targets("one")).to eq ["one"]
|
109
|
+
end
|
110
|
+
|
111
|
+
it "expands single alphabetic range" do
|
112
|
+
expect(subject.expand_targets("host[a:h]")).to eq %w{
|
113
|
+
hosta hostb hostc hostd hoste hostf hostg hosth
|
114
|
+
}
|
115
|
+
end
|
116
|
+
it "expands single alphabetic range even if reverse ordering is given" do
|
117
|
+
expect(subject.expand_targets("host[h:a]")).to eq %w{
|
118
|
+
hosta hostb hostc hostd hoste hostf hostg hosth
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
it "expands a range when the target name is qualified with credentials" do
|
123
|
+
expect(subject.expand_targets("ssh://user:password@host[a:b]")).to eq %w{
|
124
|
+
ssh://user:password@hosta
|
125
|
+
ssh://user:password@hostb
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
it "expands a numeric range correctly when start/stop string values ASCII-sort in reverse" do
|
130
|
+
# eg: ["4", "10"].sort => ["10", "4"]
|
131
|
+
expect(subject.expand_targets("[4:10]")).to eq %w{ 4 5 6 7 8 9 10 }
|
132
|
+
end
|
133
|
+
|
134
|
+
it "expands a numeric range correctly when stop is higher than start" do
|
135
|
+
expect(subject.expand_targets("[10:8]")).to eq %w{ 8 9 10 }
|
136
|
+
end
|
137
|
+
|
138
|
+
it "expands a string range correctly when stop is higher than start" do
|
139
|
+
expect(subject.expand_targets("[z:y]")).to eq %w{ y z }
|
140
|
+
end
|
141
|
+
|
142
|
+
it "expands single numeric range" do
|
143
|
+
expect(subject.expand_targets("host[10:20]")).to eq %w{
|
144
|
+
host10 host11 host12 host13 host14 host15 host16
|
145
|
+
host17 host18 host19 host20
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
it "expands two included ranges" do
|
150
|
+
expect(subject.expand_targets("host[1:4].domain[a:c]").sort).to eq [
|
151
|
+
"host1.domaina", "host1.domainb", "host1.domainc",
|
152
|
+
"host2.domaina", "host2.domainb", "host2.domainc",
|
153
|
+
"host3.domaina", "host3.domainb", "host3.domainc",
|
154
|
+
"host4.domaina", "host4.domainb", "host4.domainc"
|
155
|
+
].sort
|
156
|
+
end
|
157
|
+
|
158
|
+
it "raises InvalidRange if a range mixes alpha and numeric" do
|
159
|
+
expect { subject.expand_targets("host[a:9]") }.to raise_error(ChefApply::TargetResolver::InvalidRange)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "raises TooManyRanges if more than two ranges are included" do
|
163
|
+
expect { subject.expand_targets("[0:1][5:10][10:11]") }.to raise_error(ChefApply::TargetResolver::TooManyRanges)
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when the target resolves to more than #{ChefApply::TargetResolver::MAX_EXPANDED_TARGETS} names" do
|
167
|
+
it "raises TooManyTargets" do
|
168
|
+
expect { subject.expand_targets("[0:99999]") }.to raise_error(ChefApply::TargetResolver::TooManyTargets)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "#make_credentials" do
|
174
|
+
let(:default_user) { nil }
|
175
|
+
let(:default_password) { nil }
|
176
|
+
|
177
|
+
let(:inline_user) { nil }
|
178
|
+
let(:inline_password) { nil }
|
179
|
+
|
180
|
+
subject do
|
181
|
+
opts = {}
|
182
|
+
opts[:user] = default_user unless default_user.nil?
|
183
|
+
opts[:password] = default_password unless default_password.nil?
|
184
|
+
resolver = ChefApply::TargetResolver.new("", default_protocol, opts)
|
185
|
+
Proc.new { resolver.make_credentials(inline_user, inline_password) }
|
186
|
+
end
|
187
|
+
|
188
|
+
context "when no default user or password is given" do
|
189
|
+
let(:default_user) { nil }
|
190
|
+
let(:default_password) { nil }
|
191
|
+
|
192
|
+
context "and only an inline user is provided" do
|
193
|
+
let(:inline_user) { "aninlineuser" }
|
194
|
+
let(:inline_password) { nil }
|
195
|
+
it "returns the decorated inline user with nil password" do
|
196
|
+
expect(subject.call).to eq [inline_user, nil]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context "and only an inline password is provided" do
|
201
|
+
let(:inline_user) { nil }
|
202
|
+
let(:inline_password) { "inlinepassword4u" }
|
203
|
+
it "returns the decorated inline password" do
|
204
|
+
expect(subject.call).to eq [nil, inline_password]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context "and neither inline user nor inline password is given" do
|
209
|
+
let(:inline_user) { nil }
|
210
|
+
let(:inline_password) { nil }
|
211
|
+
it "returns an empty string" do
|
212
|
+
expect(subject.call).to eq [nil, nil]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context "and both inline user and inline password are given" do
|
217
|
+
let(:inline_user) { "adefaultuser" }
|
218
|
+
let(:inline_password) { "inlinepassword4u" }
|
219
|
+
it "returns the decorated inline_user and inline password" do
|
220
|
+
expect(subject.call).to eq [inline_user, inline_password]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context "when only a default user is given" do
|
226
|
+
let(:default_user) { "defaultusername" }
|
227
|
+
let(:default_password) { nil }
|
228
|
+
|
229
|
+
context "and only an inline user is provided" do
|
230
|
+
let(:inline_user) { "aninlineuser" }
|
231
|
+
let(:inline_password) { nil }
|
232
|
+
it "returns the inline user with no password" do
|
233
|
+
expect(subject.call).to eq [inline_user, nil]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context "and only an inline password is provided" do
|
238
|
+
let(:inline_user) { nil }
|
239
|
+
let(:inline_password) { "inlinepassword4u" }
|
240
|
+
it "returns the default user and inline password" do
|
241
|
+
expect(subject.call).to eq [default_user, inline_password]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context "and neither inline user nor inline password is given" do
|
246
|
+
let(:inline_user) { nil }
|
247
|
+
let(:inline_password) { nil }
|
248
|
+
it "returns the default user" do
|
249
|
+
expect(subject.call).to eq [default_user, nil]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context "and both inline user and inline password are given" do
|
254
|
+
let(:inline_user) { "adefaultuser" }
|
255
|
+
let(:inline_password) { "inlinepassword4u" }
|
256
|
+
it "returns the decorated inline_user and inline password" do
|
257
|
+
expect(subject.call).to eq [inline_user, inline_password]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context "when only a default password is given" do
|
263
|
+
let(:default_user) { nil }
|
264
|
+
let(:default_password) { "ihasdefaultpassword" }
|
265
|
+
|
266
|
+
context "and only an inline user is provided" do
|
267
|
+
let(:inline_user) { "aninlineuser" }
|
268
|
+
let(:inline_password) { nil }
|
269
|
+
it "returns the decorated inline user and default password" do
|
270
|
+
expect(subject.call).to eq [inline_user, default_password]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context "and only an inline password is provided" do
|
275
|
+
let(:inline_user) { nil }
|
276
|
+
let(:inline_password) { "inlinepassword4u" }
|
277
|
+
it "returns nil user and inline password" do
|
278
|
+
expect(subject.call).to eq [nil, inline_password]
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
context "and neither inline user nor inline password is given" do
|
283
|
+
let(:inline_user) { nil }
|
284
|
+
let(:inline_password) { nil }
|
285
|
+
it "returns the nil user and default password" do
|
286
|
+
expect(subject.call).to eq [nil, default_password]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context "and both inline user and inline password are given" do
|
291
|
+
let(:inline_user) { "adefaultuser" }
|
292
|
+
let(:inline_password) { "inlinepassword4u" }
|
293
|
+
it "returns the inline_user and inline password" do
|
294
|
+
expect(subject.call).to eq [inline_user, inline_password]
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
context "when defaults for both user and password are given" do
|
300
|
+
let(:default_user) { "adefaultuser" }
|
301
|
+
let(:default_password) { "ihasdefaultpassword" }
|
302
|
+
|
303
|
+
context "and only an inline user is provided" do
|
304
|
+
let(:inline_user) { "aninlineuser" }
|
305
|
+
let(:inline_password) { nil }
|
306
|
+
it "returns the decorated inline user and default password" do
|
307
|
+
expect(subject.call).to eq [inline_user, default_password]
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
context "and only an inline password is provided" do
|
312
|
+
let(:inline_user) { nil }
|
313
|
+
let(:inline_password) { "inlinepassword4u" }
|
314
|
+
it "returns the decorated default user and inline password" do
|
315
|
+
expect(subject.call).to eq [default_user, inline_password]
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
context "and neither inline user nor inline password is given" do
|
320
|
+
let(:inline_user) { nil }
|
321
|
+
let(:inline_password) { nil }
|
322
|
+
it "returns the decorated default user and default password" do
|
323
|
+
expect(subject.call).to eq [default_user, default_password]
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
context "and both inline user and inline password are given" do
|
328
|
+
let(:inline_user) { "adefaultuser" }
|
329
|
+
let(:inline_password) { "inlinepassword4u" }
|
330
|
+
it "returns the decorated inline_user and inline password" do
|
331
|
+
expect(subject.call).to eq [inline_user, inline_password]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
context "#config_for_target" do
|
338
|
+
{ "example.com" => { password: nil, url: "ssh://example.com", user: nil },
|
339
|
+
"ssh://example.com" => { password: nil, url: "ssh://example.com", user: nil },
|
340
|
+
"ssh://user@example.com" => { password: nil, url: "ssh://example.com", user: "user" },
|
341
|
+
"ssh://user:password@example.com" => { password: "password", user: "user", url: "ssh://example.com" },
|
342
|
+
"ssh://user:pas:sw:ord@example.com" => { password: "pas:sw:ord", user: "user", url: "ssh://example.com" },
|
343
|
+
"ssh://user:!@#$%^&*()|\'\";:/?><.,{}[]+=`~@example.com" => { password: "!@#$%^&*()|\'\";:/?><.,{}[]+=`~", user: "user", url: "ssh://example.com" }
|
344
|
+
}.each do |values|
|
345
|
+
it "resolves #{values[0]} to #{values[1]}" do
|
346
|
+
expect(subject.config_for_target(values[0])).to eq values[1]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
it "preserves range specifiers in the host portion while encoding in the password portion" do
|
351
|
+
input = "user:pas[1:2]!^@ho[a:b]s[t:z].com"
|
352
|
+
output = { password: "pas[1:2]!^", url: "ssh://ho[a:b]s[t:z].com", user: "user" }
|
353
|
+
expect(subject.config_for_target(input)).to eq output
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
context "#prefix_from_target" do
|
358
|
+
context "when no protocol is provided" do
|
359
|
+
let(:default_protocol) { "badproto" }
|
360
|
+
it "uses the default from configuration" do
|
361
|
+
expect(subject.prefix_from_target("host.com")).to eq %w{badproto:// host.com}
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
context "when protocol is provided" do
|
366
|
+
context "and it is valid" do
|
367
|
+
it "keeps the protocol" do
|
368
|
+
expect(subject.prefix_from_target("ssh://host.com")).to eq %w{ssh:// host.com}
|
369
|
+
end
|
370
|
+
end
|
371
|
+
context "and it is not valid" do
|
372
|
+
it "raises an error" do
|
373
|
+
expect { subject.prefix_from_target("bad://host.com") }.
|
374
|
+
to raise_error(ChefApply::TargetResolver::UnsupportedProtocol)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|