chef-core 0.0.1
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/LICENSE +201 -0
- data/i18n/errors/en.yml +394 -0
- data/lib/chef_core/cliux/status_reporter.rb +43 -0
- data/lib/chef_core/cliux/ui/error_printer.rb +266 -0
- data/lib/chef_core/cliux/ui/plain_text_element.rb +80 -0
- data/lib/chef_core/cliux/ui/plain_text_header.rb +48 -0
- data/lib/chef_core/cliux/ui/terminal/job.rb +41 -0
- data/lib/chef_core/cliux/ui/terminal.rb +103 -0
- data/lib/chef_core/cliux.rb +7 -0
- data/lib/chef_core/error.rb +49 -0
- data/lib/chef_core/errors/standard_error_resolver.rb +38 -0
- data/lib/chef_core/file_fetcher.rb +67 -0
- data/lib/chef_core/log.rb +42 -0
- data/lib/chef_core/target_host/linux.rb +63 -0
- data/lib/chef_core/target_host/windows.rb +62 -0
- data/lib/chef_core/target_host.rb +351 -0
- data/lib/chef_core/target_resolver.rb +221 -0
- data/lib/chef_core/telemeter/patch.rb +32 -0
- data/lib/chef_core/telemeter/sender.rb +123 -0
- data/lib/chef_core/telemeter.rb +157 -0
- data/lib/chef_core/text/error_translation.rb +82 -0
- data/lib/chef_core/text/text_wrapper.rb +87 -0
- data/lib/chef_core/text.rb +79 -0
- data/lib/chef_core/version.rb +20 -0
- data/lib/chef_core.rb +3 -0
- data/resources/chef_run_reporter.rb +40 -0
- metadata +307 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2017 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 "mixlib/log"
|
19
|
+
|
20
|
+
module ChefCore
|
21
|
+
class Log
|
22
|
+
extend Mixlib::Log
|
23
|
+
|
24
|
+
def self.setup(location, log_level)
|
25
|
+
if location.is_a?(String)
|
26
|
+
if location.casecmp("stdout") == 0
|
27
|
+
location = $stdout
|
28
|
+
else
|
29
|
+
location = File.open(location, "w+")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@location = location
|
33
|
+
init(location)
|
34
|
+
Log.level = log_level
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.location
|
38
|
+
@location
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module ChefCore
|
4
|
+
class TargetHost
|
5
|
+
module Linux
|
6
|
+
def omnibus_manifest_path
|
7
|
+
# TODO - if habitat install on target, this won't work
|
8
|
+
# Note that we can't use File::Join, because that will render for the
|
9
|
+
# CURRENT platform - not the platform of the target.
|
10
|
+
"/opt/chef/version-manifest.json"
|
11
|
+
end
|
12
|
+
|
13
|
+
def mkdir(path)
|
14
|
+
run_command!("mkdir -p #{path}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def chown(path, owner)
|
18
|
+
owner ||= user()
|
19
|
+
run_command!("chown #{owner} '#{path}'")
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def make_temp_dir
|
24
|
+
# We will cache this so that we only
|
25
|
+
@tempdir ||= begin
|
26
|
+
res = run_command!("bash -c '#{MKTEMP_COMMAND}'")
|
27
|
+
res.stdout.chomp.strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def install_package(target_package_path)
|
32
|
+
install_cmd = case File.extname(target_package_path)
|
33
|
+
when ".rpm"
|
34
|
+
"rpm -Uvh #{target_package_path}"
|
35
|
+
when ".deb"
|
36
|
+
"dpkg -i #{target_package_path}"
|
37
|
+
end
|
38
|
+
run_command!(install_cmd)
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def del_file(path)
|
43
|
+
run_command!("rm -rf #{path}")
|
44
|
+
end
|
45
|
+
|
46
|
+
def del_dir(path)
|
47
|
+
del_file(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
def ws_cache_path
|
51
|
+
"/var/chef-workstation"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Nothing to escape in a linux-based path
|
55
|
+
def normalize_path(path)
|
56
|
+
path
|
57
|
+
end
|
58
|
+
|
59
|
+
MKTEMP_COMMAND = "d=$(mktemp -d -p${TMPDIR:-/tmp} chef_XXXXXX); echo $d".freeze
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
module ChefCore
|
3
|
+
class TargetHost
|
4
|
+
module Windows
|
5
|
+
def omnibus_manifest_path
|
6
|
+
# TODO - use a proper method to query the win installation path -
|
7
|
+
# currently we're assuming the default, but this can be customized
|
8
|
+
# at install time.
|
9
|
+
# A working approach is below - but it runs very slowly (~10s) in testing
|
10
|
+
# on a virtualbox windows vm:
|
11
|
+
# (over winrm) Get-WmiObject Win32_Product | Where {$_.Name -match 'Chef Client'}
|
12
|
+
# TODO - if habitat install on target, this won't work
|
13
|
+
"c:\\opscode\\chef\\version-manifest.json"
|
14
|
+
end
|
15
|
+
|
16
|
+
def mkdir(path)
|
17
|
+
run_command!("New-Item -ItemType Directory -Force -Path #{path}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def chown(path, owner)
|
21
|
+
# This implementation left intentionally blank.
|
22
|
+
# To date, we have not needed chown functionality on windows;
|
23
|
+
# when/if that changes we'll need to implement it here.
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_temp_dir
|
28
|
+
@tmpdir ||= begin
|
29
|
+
res = run_command!(MKTEMP_COMMAND)
|
30
|
+
res.stdout.chomp.strip
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def install_package(target_package_path)
|
35
|
+
# While powershell does not mind the mixed path separators \ and /,
|
36
|
+
# 'cmd.exe' definitely does - so we'll make the path cmd-friendly
|
37
|
+
# before running the command
|
38
|
+
cmd = "cmd /c msiexec /package #{target_package_path.tr("/", "\\")} /quiet"
|
39
|
+
run_command!(cmd)
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def del_file(path)
|
44
|
+
run_command!("If (Test-Path #{path}) { Remove-Item -Force -Path #{path} }")
|
45
|
+
end
|
46
|
+
|
47
|
+
def del_dir(path)
|
48
|
+
run_command!("Remove-Item -Recurse -Force –Path #{path}")
|
49
|
+
end
|
50
|
+
|
51
|
+
def ws_cache_path
|
52
|
+
'#{ENV[\'APPDATA\']}/chef-workstation'
|
53
|
+
end
|
54
|
+
|
55
|
+
MKTEMP_COMMAND = "$parent = [System.IO.Path]::GetTempPath();" +
|
56
|
+
"[string] $name = [System.Guid]::NewGuid();" +
|
57
|
+
"$tmp = New-Item -ItemType Directory -Path " +
|
58
|
+
"(Join-Path $parent $name);" +
|
59
|
+
"$tmp.FullName".freeze
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2017-2019 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 "chef_core/log"
|
19
|
+
require "chef_core/error"
|
20
|
+
require "train"
|
21
|
+
|
22
|
+
module ChefCore
|
23
|
+
class TargetHost
|
24
|
+
attr_reader :config, :reporter, :backend, :transport_type
|
25
|
+
# These values may exist in .ssh/config but will be ignored by train
|
26
|
+
# in favor of its defaults unless we specify them explicitly.
|
27
|
+
# See #apply_ssh_config
|
28
|
+
SSH_CONFIG_OVERRIDE_KEYS = [:user, :port, :proxy].freeze
|
29
|
+
|
30
|
+
# We're borrowing a page from train here - because setting up a
|
31
|
+
# reliable connection for testing is a multi-step process,
|
32
|
+
# we'll provide this method which instantiates a TargetHost connected
|
33
|
+
# to a train mock backend. If the family/name provided resolves to a suported
|
34
|
+
# OS, this instance will mix-in the supporting methods for the given platform;
|
35
|
+
# otherwise those methods will raise NotImplementedError.
|
36
|
+
def self.mock_instance(url, family: "unknown", name: "unknown",
|
37
|
+
release: "unknown", arch: "x86_64")
|
38
|
+
# Specifying sudo: false ensures that attempted operations
|
39
|
+
# don't fail because the mock platform doesn't support sudo
|
40
|
+
target_host = TargetHost.new(url, { sudo: false })
|
41
|
+
|
42
|
+
# Don't pull in the platform-specific mixins automatically during connect
|
43
|
+
# Otherwise, it will raise since it can't resolve the OS without the mock.
|
44
|
+
target_host.instance_variable_set(:@mocked_connection, true)
|
45
|
+
target_host.connect!
|
46
|
+
|
47
|
+
# We need to provide this mock before invoking mix_in_target_platform,
|
48
|
+
# otherwise it will fail with an unknown OS (since we don't have a real connection).
|
49
|
+
target_host.backend.mock_os(
|
50
|
+
family: family,
|
51
|
+
name: name,
|
52
|
+
release: release,
|
53
|
+
arch: arch
|
54
|
+
)
|
55
|
+
|
56
|
+
# Only mix-in if we can identify the platform. This
|
57
|
+
# prevents mix_in_target_platform! from raising on unknown platform during
|
58
|
+
# tests that validate unsupported platform behaviors.
|
59
|
+
if target_host.base_os != :other
|
60
|
+
target_host.mix_in_target_platform!
|
61
|
+
end
|
62
|
+
|
63
|
+
target_host
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(host_url, opts = {}, logger = nil)
|
67
|
+
@config = connection_config(host_url, opts, logger)
|
68
|
+
@transport_type = Train.validate_backend(@config)
|
69
|
+
apply_ssh_config(@config, opts) if @transport_type == "ssh"
|
70
|
+
@train_connection = Train.create(@transport_type, config)
|
71
|
+
end
|
72
|
+
|
73
|
+
def connection_config(host_url, opts_in, logger)
|
74
|
+
connection_opts = { target: host_url,
|
75
|
+
sudo: opts_in[:sudo] === false ? false : true,
|
76
|
+
www_form_encoded_password: true,
|
77
|
+
key_files: opts_in[:identity_file] || opts_in[:key_files],
|
78
|
+
non_interactive: true, # Prevent password prompts
|
79
|
+
connection_retries: 2,
|
80
|
+
connection_retry_sleep: 1,
|
81
|
+
logger: opts_in[:logger] || ChefCore::Log }
|
82
|
+
|
83
|
+
target_opts = Train.unpack_target_from_uri(host_url)
|
84
|
+
if opts_in.key?(:ssl) && opts_in[:ssl]
|
85
|
+
connection_opts[:ssl] = opts_in[:ssl]
|
86
|
+
connection_opts[:self_signed] = opts_in[:self_signed] || (opts_in[:ssl_verify] === false ? true : false)
|
87
|
+
end
|
88
|
+
|
89
|
+
target_opts[:host] = host_url if target_opts[:host].nil?
|
90
|
+
target_opts[:backend] = "ssh" if target_opts[:backend].nil?
|
91
|
+
connection_opts = connection_opts.merge(target_opts)
|
92
|
+
|
93
|
+
# From WinRM gem: It is recommended that you :disable_sspi => true if you are using the plaintext or ssl transport.
|
94
|
+
# See note here: https://github.com/mwrock/WinRM#example
|
95
|
+
if ["ssl", "plaintext"].include?(target_opts[:winrm_transport])
|
96
|
+
target_opts[:winrm_disable_sspi] = true
|
97
|
+
end
|
98
|
+
|
99
|
+
connection_opts = connection_opts.merge(target_opts)
|
100
|
+
|
101
|
+
# Anything we haven't explicitly set already, pass through to train.
|
102
|
+
Train.options(target_opts[:backend]).keys.each do |key|
|
103
|
+
if opts_in.key?(key) && !connection_opts.key?(key)
|
104
|
+
connection_opts[key] = opts_in[key]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
Train.target_config(connection_opts)
|
109
|
+
end
|
110
|
+
|
111
|
+
def apply_ssh_config(config, opts_in)
|
112
|
+
# If we don't provide certain options, they will be defaulted
|
113
|
+
# within train - in the case of ssh, this will prevent the .ssh/config
|
114
|
+
# values from being picked up.
|
115
|
+
# Here we'll modify the returned @config to specify
|
116
|
+
# values that we get out of .ssh/config if present and if they haven't
|
117
|
+
# been explicitly given.
|
118
|
+
host_cfg = ssh_config_for_host(config[:host])
|
119
|
+
SSH_CONFIG_OVERRIDE_KEYS.each do |key|
|
120
|
+
if host_cfg.key?(key) && opts_in[key].nil?
|
121
|
+
config[key] = host_cfg[key]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Establish connection to configured target.
|
127
|
+
#
|
128
|
+
def connect!
|
129
|
+
# Keep existing connections
|
130
|
+
return unless @backend.nil?
|
131
|
+
@backend = train_connection.connection
|
132
|
+
@backend.wait_until_ready
|
133
|
+
|
134
|
+
# When the testing function `mock_instance` is used, it will set
|
135
|
+
# this instance variable to false and handle this function call
|
136
|
+
# of mixin functions based on the mocked platform.
|
137
|
+
mix_in_target_platform! unless @mocked_connection
|
138
|
+
rescue Train::UserError => e
|
139
|
+
raise ConnectionFailure.new(e, config)
|
140
|
+
rescue Train::Error => e
|
141
|
+
# These are typically wrapper errors for other problems,
|
142
|
+
# so we'll prefer to use e.cause over e if available.
|
143
|
+
raise ConnectionFailure.new(e.cause || e, config)
|
144
|
+
end
|
145
|
+
|
146
|
+
def mix_in_target_platform!
|
147
|
+
case base_os
|
148
|
+
when :linux
|
149
|
+
require "chef_core/target_host/linux"
|
150
|
+
class << self; include ChefCore::TargetHost::Linux; end
|
151
|
+
when :windows
|
152
|
+
require "chef_core/target_host/windows"
|
153
|
+
class << self; include ChefCore::TargetHost::Windows; end
|
154
|
+
when :other
|
155
|
+
raise ChefCore::TargetHost::UnsupportedTargetOS.new(platform.name)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the user being used to connect. Defaults to train's default user if not specified
|
160
|
+
def user
|
161
|
+
return config[:user] unless config[:user].nil?
|
162
|
+
require "train/transports/ssh"
|
163
|
+
# TODO - this should use the right transport, not default to SSH
|
164
|
+
Train::Transports::SSH.default_options[:user][:default]
|
165
|
+
end
|
166
|
+
|
167
|
+
def hostname
|
168
|
+
config[:host]
|
169
|
+
end
|
170
|
+
|
171
|
+
def architecture
|
172
|
+
platform.arch
|
173
|
+
end
|
174
|
+
|
175
|
+
def version
|
176
|
+
platform.release
|
177
|
+
end
|
178
|
+
|
179
|
+
def base_os
|
180
|
+
if platform.windows?
|
181
|
+
:windows
|
182
|
+
elsif platform.linux?
|
183
|
+
:linux
|
184
|
+
else
|
185
|
+
:other
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# TODO 2019-01-29 not expose this, it's internal implemenation. Same with #backend.
|
190
|
+
def platform
|
191
|
+
backend.platform
|
192
|
+
end
|
193
|
+
|
194
|
+
def run_command!(command, &data_handler)
|
195
|
+
result = run_command(command, &data_handler)
|
196
|
+
if result.exit_status != 0
|
197
|
+
raise RemoteExecutionFailed.new(@config[:host], command, result)
|
198
|
+
end
|
199
|
+
result
|
200
|
+
end
|
201
|
+
|
202
|
+
def run_command(command, &data_handler)
|
203
|
+
backend.run_command command, &data_handler
|
204
|
+
end
|
205
|
+
|
206
|
+
# TODO spec
|
207
|
+
def save_as_remote_file(content, remote_path)
|
208
|
+
t = Tempfile.new("chef-content")
|
209
|
+
t << content
|
210
|
+
t.close
|
211
|
+
upload_file(t.path, remote_path)
|
212
|
+
ensure
|
213
|
+
t.close
|
214
|
+
t.unlink
|
215
|
+
end
|
216
|
+
|
217
|
+
def upload_file(local_path, remote_path)
|
218
|
+
backend.upload(local_path, remote_path)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Retrieve the contents of a remote file. Returns nil
|
222
|
+
# if the file didn't exist or couldn't be read.
|
223
|
+
def fetch_file_contents(remote_path)
|
224
|
+
result = backend.file(remote_path)
|
225
|
+
if result.exist? && result.file?
|
226
|
+
result.content
|
227
|
+
else
|
228
|
+
nil
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns the installed chef version as a Gem::Version,
|
233
|
+
# or raised ChefNotInstalled if chef client version manifest can't
|
234
|
+
# be found.
|
235
|
+
def installed_chef_version
|
236
|
+
return @installed_chef_version if @installed_chef_version
|
237
|
+
# Note: In the case of a very old version of chef (that has no manifest - pre 12.0?)
|
238
|
+
# this will report as not installed.
|
239
|
+
manifest = read_chef_version_manifest()
|
240
|
+
|
241
|
+
# We split the version here because unstable builds install from)
|
242
|
+
# are in the form "Major.Minor.Build+HASH" which is not a valid
|
243
|
+
# version string.
|
244
|
+
@installed_chef_version = Gem::Version.new(manifest["build_version"].split("+")[0])
|
245
|
+
end
|
246
|
+
|
247
|
+
def read_chef_version_manifest
|
248
|
+
manifest = fetch_file_contents(omnibus_manifest_path)
|
249
|
+
raise ChefNotInstalled.new if manifest.nil?
|
250
|
+
JSON.parse(manifest)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Creates and caches location of temporary directory on the remote host
|
254
|
+
# using platform-specific implementations of make_temp_dir
|
255
|
+
# This will also set ownership to the connecting user instead of default of
|
256
|
+
# root when sudo'd, so that the dir can be used to upload files using scp
|
257
|
+
# as the connecting user.
|
258
|
+
#
|
259
|
+
# The base temp dir is cached and will only be created once per connection lifetime.
|
260
|
+
def temp_dir
|
261
|
+
dir = make_temp_dir()
|
262
|
+
chown(dir, user)
|
263
|
+
dir
|
264
|
+
end
|
265
|
+
|
266
|
+
# create a directory. because we run all commands as root, this will also set group:owner
|
267
|
+
# to the connecting user if host isn't windows so that scp -- which uses the connecting user --
|
268
|
+
# will have permissions to upload into it.
|
269
|
+
def make_directory(path)
|
270
|
+
mkdir(path)
|
271
|
+
chown(path, user)
|
272
|
+
path
|
273
|
+
end
|
274
|
+
|
275
|
+
# normalizes path across OS's
|
276
|
+
def normalize_path(p) # NOTE BOOTSTRAP: was action::base::escape_windows_path
|
277
|
+
p.tr("\\", "/")
|
278
|
+
end
|
279
|
+
|
280
|
+
# Simplified chown - just sets user, defaults to connection user. Does not touch
|
281
|
+
# group. Only has effect on non-windows targets
|
282
|
+
def chown(path, owner); raise NotImplementedError; end
|
283
|
+
|
284
|
+
# Platform-specific installation of packages
|
285
|
+
def install_package(target_package_path); raise NotImplementedError; end
|
286
|
+
|
287
|
+
def ws_cache_path; raise NotImplementedError; end
|
288
|
+
|
289
|
+
# Recursively delete directory
|
290
|
+
def del_dir(path); raise NotImplementedError; end
|
291
|
+
|
292
|
+
def del_file(path); raise NotImplementedError; end
|
293
|
+
|
294
|
+
def omnibus_manifest_path(); raise NotImplementedError; end
|
295
|
+
|
296
|
+
private
|
297
|
+
|
298
|
+
def train_connection
|
299
|
+
@train_connection
|
300
|
+
end
|
301
|
+
|
302
|
+
def ssh_config_for_host(host)
|
303
|
+
require "net/ssh"
|
304
|
+
Net::SSH::Config.for(host)
|
305
|
+
end
|
306
|
+
|
307
|
+
class RemoteExecutionFailed < ChefCore::Error
|
308
|
+
attr_reader :stdout, :stderr
|
309
|
+
def initialize(host, command, result)
|
310
|
+
super("CHEFRMT001",
|
311
|
+
command,
|
312
|
+
result.exit_status,
|
313
|
+
host,
|
314
|
+
result.stderr.empty? ? result.stdout : result.stderr)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
class ConnectionFailure < ChefCore::Error
|
319
|
+
# TODO: Currently this only handles sudo-related errors;
|
320
|
+
# we should also look at e.cause for underlying connection errors
|
321
|
+
# which are presently only visible in log files.
|
322
|
+
def initialize(original_exception, connection_opts)
|
323
|
+
init_params =
|
324
|
+
# Comments below show the original_exception.reason values to check for instead of strings,
|
325
|
+
# after train 1.4.12 is consumable.
|
326
|
+
case original_exception.message # original_exception.reason
|
327
|
+
when /Sudo requires a password/ # :sudo_password_required
|
328
|
+
"CHEFTRN003"
|
329
|
+
when /Wrong sudo password/ #:bad_sudo_password
|
330
|
+
"CHEFTRN004"
|
331
|
+
when /Can't find sudo command/, /No such file/, /command not found/ # :sudo_command_not_found
|
332
|
+
# NOTE: In the /No such file/ case, reason will be nil - we still have
|
333
|
+
# to check message text. (Or PR to train to handle this case)
|
334
|
+
sudo_command = connection_opts[:sudo_command]
|
335
|
+
["CHEFTRN005", sudo_command] # :sudo_command_not_found
|
336
|
+
when /Sudo requires a TTY.*/ # :sudo_no_tty
|
337
|
+
"CHEFTRN006"
|
338
|
+
when /has no keys added/
|
339
|
+
"CHEFTRN007"
|
340
|
+
else
|
341
|
+
["CHEFTRN999", original_exception.message]
|
342
|
+
end
|
343
|
+
super(*(Array(init_params).flatten))
|
344
|
+
end
|
345
|
+
end
|
346
|
+
class ChefNotInstalled < StandardError; end
|
347
|
+
class UnsupportedTargetOS < ChefCore::Error
|
348
|
+
def initialize(os_name); super("CHEFTARG001", os_name); end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|