chef-apply 0.1.2
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/Gemfile +26 -0
- data/Gemfile.lock +423 -0
- data/LICENSE +201 -0
- data/README.md +41 -0
- data/Rakefile +32 -0
- data/bin/chef-run +23 -0
- data/chef-apply.gemspec +67 -0
- data/i18n/en.yml +513 -0
- data/lib/chef_apply.rb +20 -0
- data/lib/chef_apply/action/base.rb +158 -0
- data/lib/chef_apply/action/converge_target.rb +173 -0
- data/lib/chef_apply/action/install_chef.rb +30 -0
- data/lib/chef_apply/action/install_chef/base.rb +137 -0
- data/lib/chef_apply/action/install_chef/linux.rb +38 -0
- data/lib/chef_apply/action/install_chef/windows.rb +54 -0
- data/lib/chef_apply/action/reporter.rb +39 -0
- data/lib/chef_apply/cli.rb +470 -0
- data/lib/chef_apply/cli_options.rb +145 -0
- data/lib/chef_apply/config.rb +150 -0
- data/lib/chef_apply/error.rb +108 -0
- data/lib/chef_apply/errors/ccr_failure_mapper.rb +93 -0
- data/lib/chef_apply/file_fetcher.rb +70 -0
- data/lib/chef_apply/log.rb +42 -0
- data/lib/chef_apply/recipe_lookup.rb +117 -0
- data/lib/chef_apply/startup.rb +162 -0
- data/lib/chef_apply/status_reporter.rb +42 -0
- data/lib/chef_apply/target_host.rb +233 -0
- data/lib/chef_apply/target_resolver.rb +202 -0
- data/lib/chef_apply/telemeter.rb +162 -0
- data/lib/chef_apply/telemeter/patch.rb +32 -0
- data/lib/chef_apply/telemeter/sender.rb +121 -0
- data/lib/chef_apply/temp_cookbook.rb +159 -0
- data/lib/chef_apply/text.rb +77 -0
- data/lib/chef_apply/ui/error_printer.rb +261 -0
- data/lib/chef_apply/ui/plain_text_element.rb +75 -0
- data/lib/chef_apply/ui/terminal.rb +94 -0
- data/lib/chef_apply/version.rb +20 -0
- metadata +376 -0
@@ -0,0 +1,145 @@
|
|
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 "chef_apply/text"
|
19
|
+
require "chef_apply/action/install_chef"
|
20
|
+
|
21
|
+
# Moving the options into here so the cli.rb file is smaller and easier to read
|
22
|
+
# For options that need to be merged back into the global ChefApply::Config object
|
23
|
+
# we do that with a proc in the option itself. We decided to do that because it is
|
24
|
+
# an easy, straight forward way to merge those options when they do not directly
|
25
|
+
# map back to keys in the Config global. IE, we cannot just do
|
26
|
+
# `ChefApply::Config.merge!(options)` because the keys do not line up, and we do
|
27
|
+
# not want all CLI params merged back into the global config object.
|
28
|
+
# We know that the config is already loaded from the file (or program defaults)
|
29
|
+
# because the `Startup` class was invoked to start the program.
|
30
|
+
module ChefApply
|
31
|
+
module CLIOptions
|
32
|
+
|
33
|
+
T = ChefApply::Text.cli
|
34
|
+
TS = ChefApply::Text.status
|
35
|
+
|
36
|
+
def self.included(klass)
|
37
|
+
klass.banner T.description + "\n" + T.usage_full
|
38
|
+
|
39
|
+
klass.option :version,
|
40
|
+
short: "-v",
|
41
|
+
long: "--version",
|
42
|
+
description: T.version.description,
|
43
|
+
boolean: true
|
44
|
+
|
45
|
+
klass.option :help,
|
46
|
+
short: "-h",
|
47
|
+
long: "--help",
|
48
|
+
description: T.help.description,
|
49
|
+
boolean: true
|
50
|
+
|
51
|
+
# Special note:
|
52
|
+
# config_path is pre-processed in startup.rb, and is shown here only
|
53
|
+
# for purpoess of rendering help text.
|
54
|
+
klass.option :config_path,
|
55
|
+
short: "-c PATH",
|
56
|
+
long: "--config PATH",
|
57
|
+
description: T.default_config_location(ChefApply::Config.default_location),
|
58
|
+
default: ChefApply::Config.default_location,
|
59
|
+
proc: Proc.new { |path| ChefApply::Config.custom_location(path) }
|
60
|
+
|
61
|
+
klass.option :identity_file,
|
62
|
+
long: "--identity-file PATH",
|
63
|
+
short: "-i PATH",
|
64
|
+
description: T.identity_file,
|
65
|
+
proc: (Proc.new do |paths|
|
66
|
+
path = paths
|
67
|
+
unless File.exist?(path)
|
68
|
+
raise OptionValidationError.new("CHEFVAL001", self, path)
|
69
|
+
end
|
70
|
+
path
|
71
|
+
end)
|
72
|
+
|
73
|
+
klass.option :ssl,
|
74
|
+
long: "--[no-]ssl",
|
75
|
+
description: T.ssl.desc(ChefApply::Config.connection.winrm.ssl),
|
76
|
+
boolean: true,
|
77
|
+
default: ChefApply::Config.connection.winrm.ssl,
|
78
|
+
proc: Proc.new { |val| ChefApply::Config.connection.winrm.ssl(val) }
|
79
|
+
|
80
|
+
klass.option :ssl_verify,
|
81
|
+
long: "--[no-]ssl-verify",
|
82
|
+
description: T.ssl.verify_desc(ChefApply::Config.connection.winrm.ssl_verify),
|
83
|
+
boolean: true,
|
84
|
+
default: ChefApply::Config.connection.winrm.ssl_verify,
|
85
|
+
proc: Proc.new { |val| ChefApply::Config.connection.winrm.ssl_verify(val) }
|
86
|
+
|
87
|
+
klass.option :protocol,
|
88
|
+
long: "--protocol <PROTOCOL>",
|
89
|
+
short: "-p",
|
90
|
+
description: T.protocol_description(ChefApply::Config::SUPPORTED_PROTOCOLS.join(" "),
|
91
|
+
ChefApply::Config.connection.default_protocol),
|
92
|
+
default: ChefApply::Config.connection.default_protocol,
|
93
|
+
proc: Proc.new { |val| ChefApply::Config.connection.default_protocol(val) }
|
94
|
+
|
95
|
+
klass.option :user,
|
96
|
+
long: "--user <USER>",
|
97
|
+
description: T.user_description
|
98
|
+
|
99
|
+
klass.option :password,
|
100
|
+
long: "--password <PASSWORD>",
|
101
|
+
description: T.password_description
|
102
|
+
|
103
|
+
klass.option :cookbook_repo_paths,
|
104
|
+
long: "--cookbook-repo-paths PATH",
|
105
|
+
description: T.cookbook_repo_paths,
|
106
|
+
default: ChefApply::Config.chef.cookbook_repo_paths,
|
107
|
+
proc: (Proc.new do |paths|
|
108
|
+
paths = paths.split(",")
|
109
|
+
ChefApply::Config.chef.cookbook_repo_paths(paths)
|
110
|
+
paths
|
111
|
+
end)
|
112
|
+
|
113
|
+
klass.option :install,
|
114
|
+
long: "--[no-]install",
|
115
|
+
default: true,
|
116
|
+
boolean: true,
|
117
|
+
description: T.install_description(Action::InstallChef::Base::MIN_CHEF_VERSION)
|
118
|
+
|
119
|
+
klass.option :sudo,
|
120
|
+
long: "--[no-]sudo",
|
121
|
+
description: T.sudo.flag_description.sudo,
|
122
|
+
boolean: true,
|
123
|
+
default: true
|
124
|
+
|
125
|
+
klass.option :sudo_command,
|
126
|
+
long: "--sudo-command <COMMAND>",
|
127
|
+
default: "sudo",
|
128
|
+
description: T.sudo.flag_description.command
|
129
|
+
|
130
|
+
klass.option :sudo_password,
|
131
|
+
long: "--sudo-password <PASSWORD>",
|
132
|
+
description: T.sudo.flag_description.password
|
133
|
+
|
134
|
+
klass.option :sudo_options,
|
135
|
+
long: "--sudo-options 'OPTIONS...'",
|
136
|
+
description: T.sudo.flag_description.options
|
137
|
+
end
|
138
|
+
|
139
|
+
# I really don't like that mixlib-cli refers to the parsed command line flags in
|
140
|
+
# a hash accesed via the `config` method. Thats just such an overloaded word.
|
141
|
+
def parsed_options
|
142
|
+
config
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,150 @@
|
|
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 "chef_apply/log"
|
19
|
+
require "mixlib/config"
|
20
|
+
require "fileutils"
|
21
|
+
require "pathname"
|
22
|
+
require "chef-config/config"
|
23
|
+
require "chef-config/workstation_config_loader"
|
24
|
+
|
25
|
+
module ChefApply
|
26
|
+
class Config
|
27
|
+
WS_BASE_PATH = File.join(Dir.home, ".chef-workstation/")
|
28
|
+
SUPPORTED_PROTOCOLS = %w{ssh winrm}
|
29
|
+
|
30
|
+
class << self
|
31
|
+
@custom_location = nil
|
32
|
+
|
33
|
+
# Ensure when we extend Mixlib::Config that we load
|
34
|
+
# up the workstation config since we will need that
|
35
|
+
# to converge later
|
36
|
+
def initialize_mixlib_config
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
def custom_location(path)
|
41
|
+
@custom_location = path
|
42
|
+
raise "No config file located at #{path}" unless exist?
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_location
|
46
|
+
File.join(WS_BASE_PATH, "config.toml")
|
47
|
+
end
|
48
|
+
|
49
|
+
def telemetry_path
|
50
|
+
File.join(WS_BASE_PATH, "telemetry")
|
51
|
+
end
|
52
|
+
|
53
|
+
def telemetry_session_file
|
54
|
+
File.join(telemetry_path, "TELEMETRY_SESSION_ID")
|
55
|
+
end
|
56
|
+
|
57
|
+
def telemetry_installation_identifier_file
|
58
|
+
File.join(WS_BASE_PATH, "installation_id")
|
59
|
+
end
|
60
|
+
|
61
|
+
def base_log_directory
|
62
|
+
File.dirname(log.location)
|
63
|
+
end
|
64
|
+
|
65
|
+
# These paths are relative to the log output path, which is user-configurable.
|
66
|
+
def error_output_path
|
67
|
+
File.join(base_log_directory, "errors.txt")
|
68
|
+
end
|
69
|
+
|
70
|
+
def stack_trace_path
|
71
|
+
File.join(base_log_directory, "stack-trace.log")
|
72
|
+
end
|
73
|
+
|
74
|
+
def using_default_location?
|
75
|
+
@custom_location.nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def location
|
79
|
+
using_default_location? ? default_location : @custom_location
|
80
|
+
end
|
81
|
+
|
82
|
+
def load
|
83
|
+
if exist?
|
84
|
+
from_file(location)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def exist?
|
89
|
+
File.exist? location
|
90
|
+
end
|
91
|
+
|
92
|
+
def reset
|
93
|
+
@custom_location = nil
|
94
|
+
super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
extend Mixlib::Config
|
99
|
+
|
100
|
+
config_strict_mode true
|
101
|
+
|
102
|
+
# When working on Chef Apply itself,
|
103
|
+
# developers should set telemetry.dev to true
|
104
|
+
# in their local configuration to ensure that dev usage
|
105
|
+
# doesn't skew customer telemetry.
|
106
|
+
config_context :telemetry do
|
107
|
+
default(:dev, false)
|
108
|
+
default(:enable, true)
|
109
|
+
end
|
110
|
+
|
111
|
+
config_context :log do
|
112
|
+
default(:level, "warn")
|
113
|
+
default(:location, File.join(WS_BASE_PATH, "logs/default.log"))
|
114
|
+
end
|
115
|
+
|
116
|
+
config_context :cache do
|
117
|
+
default(:path, File.join(WS_BASE_PATH, "cache"))
|
118
|
+
end
|
119
|
+
|
120
|
+
config_context :connection do
|
121
|
+
default(:default_protocol, "ssh")
|
122
|
+
default(:default_user, nil)
|
123
|
+
|
124
|
+
config_context :winrm do
|
125
|
+
default(:ssl, false)
|
126
|
+
default(:ssl_verify, true)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
config_context :dev do
|
131
|
+
default(:spinner, "TTY::Spinner")
|
132
|
+
end
|
133
|
+
|
134
|
+
config_context :chef do
|
135
|
+
# We want to use any configured chef repo paths or trusted certs in
|
136
|
+
# ~/.chef/knife.rb on the user's workstation. But because they could have
|
137
|
+
# config that could mess up our Policyfile creation later we reset the
|
138
|
+
# ChefConfig back to default after loading that.
|
139
|
+
ChefConfig::WorkstationConfigLoader.new(nil, ChefApply::Log).load
|
140
|
+
default(:cookbook_repo_paths, [ChefConfig::Config[:cookbook_path]].flatten)
|
141
|
+
default(:trusted_certs_dir, ChefConfig::Config[:trusted_certs_dir])
|
142
|
+
ChefConfig::Config.reset
|
143
|
+
end
|
144
|
+
|
145
|
+
config_context :data_collector do
|
146
|
+
default :url, nil
|
147
|
+
default :token, nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,108 @@
|
|
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
|
+
module ChefApply
|
19
|
+
class Error < StandardError
|
20
|
+
attr_reader :id, :params
|
21
|
+
attr_accessor :show_stack, :show_log, :decorate
|
22
|
+
def initialize(id, *params)
|
23
|
+
@id = id
|
24
|
+
@params = params || []
|
25
|
+
@show_log = true
|
26
|
+
@show_stack = true
|
27
|
+
@decorate = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ErrorNoLogs < Error
|
32
|
+
def initialize(id, *params)
|
33
|
+
super
|
34
|
+
@show_log = false
|
35
|
+
@show_stack = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ErrorNoStack < Error
|
40
|
+
def initialize(id, *params)
|
41
|
+
super
|
42
|
+
@show_log = true
|
43
|
+
@show_stack = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class WrappedError < StandardError
|
48
|
+
attr_accessor :target_host, :contained_exception
|
49
|
+
def initialize(e, target_host)
|
50
|
+
super(e.message)
|
51
|
+
@contained_exception = e
|
52
|
+
@target_host = target_host
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class MultiJobFailure < ChefApply::ErrorNoLogs
|
57
|
+
attr_reader :jobs
|
58
|
+
def initialize(jobs)
|
59
|
+
super("CHEFMULTI001")
|
60
|
+
@jobs = jobs
|
61
|
+
@decorate = false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Provides mappings of common errors that we don't explicitly
|
66
|
+
# handle, but can offer expanded help text around.
|
67
|
+
class StandardErrorResolver
|
68
|
+
|
69
|
+
def self.resolve_exception(exception)
|
70
|
+
deps
|
71
|
+
show_log = true
|
72
|
+
show_stack = true
|
73
|
+
case exception
|
74
|
+
when OpenSSL::SSL::SSLError
|
75
|
+
if exception.message =~ /SSL.*verify failed.*/
|
76
|
+
id = "CHEFNET002"
|
77
|
+
show_log = false
|
78
|
+
show_stack = false
|
79
|
+
end
|
80
|
+
when SocketError then id = "CHEFNET001"; show_log = false; show_stack = false
|
81
|
+
end
|
82
|
+
if id.nil?
|
83
|
+
exception
|
84
|
+
else
|
85
|
+
e = ChefApply::Error.new(id, exception.message)
|
86
|
+
e.show_log = show_log
|
87
|
+
e.show_stack = show_stack
|
88
|
+
e
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.wrap_exception(original, target_host = nil)
|
93
|
+
resolved_exception = resolve_exception(original)
|
94
|
+
WrappedError.new(resolved_exception, target_host)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.unwrap_exception(wrapper)
|
98
|
+
resolve_exception(wrapper.contained_exception)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.deps
|
102
|
+
# Avoid loading additional includes until they're needed
|
103
|
+
require "socket"
|
104
|
+
require "openssl"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -0,0 +1,93 @@
|
|
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 "chef_apply/error"
|
19
|
+
|
20
|
+
module ChefApply::Errors
|
21
|
+
class CCRFailureMapper
|
22
|
+
attr_reader :params
|
23
|
+
|
24
|
+
def initialize(exception, params)
|
25
|
+
@params = params
|
26
|
+
@cause_line = exception
|
27
|
+
end
|
28
|
+
|
29
|
+
def raise_mapped_exception!
|
30
|
+
if @cause_line.nil?
|
31
|
+
raise RemoteChefRunFailedToResolveError.new(params[:stdout], params[:stderr])
|
32
|
+
else
|
33
|
+
errid, *args = exception_args_from_cause()
|
34
|
+
if errid.nil?
|
35
|
+
raise RemoteChefClientRunFailedUnknownReason.new()
|
36
|
+
else
|
37
|
+
raise RemoteChefClientRunFailed.new(errid, *args)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Ideally we will write a custom handler to package up data we care
|
44
|
+
# about and present it more directly https://docs.chef.io/handlers.html
|
45
|
+
# For now, we'll just match the most common failures based on their
|
46
|
+
# messages.
|
47
|
+
def exception_args_from_cause
|
48
|
+
# Ordering is important below. Some earlier tests are more detailed
|
49
|
+
# cases of things that will match more general tests further down.
|
50
|
+
case @cause_line
|
51
|
+
when /.*had an error:(.*:)\s+(.*$)/
|
52
|
+
# Some invalid property value cases, among others.
|
53
|
+
["CHEFCCR002", $2]
|
54
|
+
when /.*Chef::Exceptions::ValidationFailed:\s+Option action must be equal to one of:\s+(.*)!\s+You passed :(.*)\./
|
55
|
+
# Invalid action - specialization of invalid property value, below
|
56
|
+
["CHEFCCR003", $2, $1]
|
57
|
+
when /.*Chef::Exceptions::ValidationFailed:\s+(.*)/
|
58
|
+
# Invalid resource property value
|
59
|
+
["CHEFCCR004", $1]
|
60
|
+
when /.*NameError: undefined local variable or method `(.+)' for cookbook.+/
|
61
|
+
# Invalid resource type in most cases
|
62
|
+
["CHEFCCR005", $1]
|
63
|
+
when /.*NoMethodError: undefined method `(.+)' for cookbook.+/
|
64
|
+
# Invalid resource type in most cases
|
65
|
+
["CHEFCCR005", $1]
|
66
|
+
when /.*undefined method `(.*)' for (.+)/
|
67
|
+
# Unknown resource property
|
68
|
+
["CHEFCCR006", $1, $2]
|
69
|
+
|
70
|
+
# Below would catch the general form of most errors, but the
|
71
|
+
# message itself in those lines is not generally aligned
|
72
|
+
# with the UX we want to provide.
|
73
|
+
# when /.*Exception|Error.*:\s+(.*)/
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class RemoteChefClientRunFailed < ChefApply::ErrorNoLogs
|
80
|
+
def initialize(id, *args); super(id, *args); end
|
81
|
+
end
|
82
|
+
|
83
|
+
class RemoteChefClientRunFailedUnknownReason < ChefApply::ErrorNoStack
|
84
|
+
def initialize(); super("CHEFCCR099"); end
|
85
|
+
end
|
86
|
+
|
87
|
+
class RemoteChefRunFailedToResolveError < ChefApply::ErrorNoStack
|
88
|
+
def initialize(stdout, stderr); super("CHEFCCR001", stdout, stderr); end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|