chef-config 17.10.0 → 17.10.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,19 +1,19 @@
1
- # Copyright:: Copyright (c) Chef Software Inc.
2
- # License:: Apache License, Version 2.0
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
-
16
- module ChefConfig
17
- CHEFCONFIG_ROOT = File.expand_path("..", __dir__)
18
- VERSION = "17.10.0".freeze
19
- end
1
+ # Copyright:: Copyright (c) Chef Software Inc.
2
+ # License:: Apache License, Version 2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module ChefConfig
17
+ CHEFCONFIG_ROOT = File.expand_path("..", __dir__)
18
+ VERSION = "17.10.19".freeze
19
+ end
@@ -1,24 +1,24 @@
1
- #
2
- # Copyright:: Copyright (c) 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-utils" unless defined?(ChefUtils::CANARY)
19
-
20
- module ChefConfig
21
- def self.windows?
22
- ChefUtils.windows?
23
- end
24
- end
1
+ #
2
+ # Copyright:: Copyright (c) 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-utils" unless defined?(ChefUtils::CANARY)
19
+
20
+ module ChefConfig
21
+ def self.windows?
22
+ ChefUtils.windows?
23
+ end
24
+ end
@@ -1,281 +1,281 @@
1
- #
2
- # Author:: Daniel DeLeo (<dan@chef.io>)
3
- # Copyright:: Copyright (c) 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 "chef-utils" unless defined?(ChefUtils::CANARY)
20
- require_relative "config"
21
- require_relative "exceptions"
22
- require_relative "logger"
23
- require_relative "path_helper"
24
- require_relative "windows"
25
- require_relative "mixin/dot_d"
26
- require_relative "mixin/credentials"
27
-
28
- module ChefConfig
29
- class WorkstationConfigLoader
30
- include ChefConfig::Mixin::DotD
31
- include ChefConfig::Mixin::Credentials
32
-
33
- # Path to a config file requested by user, (e.g., via command line option). Can be nil
34
- attr_accessor :explicit_config_file
35
- # The name of a credentials profile. Can be nil
36
- attr_accessor :profile
37
- attr_reader :credentials_found
38
-
39
- # TODO: initialize this with a logger for Chef and Knife
40
- def initialize(explicit_config_file, logger = nil, profile: nil)
41
- @explicit_config_file = explicit_config_file
42
- @chef_config_dir = nil
43
- @config_location = nil
44
- @profile = profile
45
- @logger = logger || NullLogger.new
46
- @credentials_found = false
47
- end
48
-
49
- def no_config_found?
50
- config_location.nil? && !credentials_found
51
- end
52
-
53
- def config_location
54
- @config_location ||= (explicit_config_file || locate_local_config)
55
- end
56
-
57
- def chef_config_dir
58
- if @chef_config_dir.nil?
59
- @chef_config_dir = false
60
- full_path = working_directory.split(File::SEPARATOR)
61
- (full_path.length - 1).downto(0) do |i|
62
- candidate_directory = File.join(full_path[0..i] + [ChefUtils::Dist::Infra::USER_CONF_DIR])
63
- if File.exist?(candidate_directory) && File.directory?(candidate_directory)
64
- @chef_config_dir = candidate_directory
65
- break
66
- end
67
- end
68
- end
69
- @chef_config_dir
70
- end
71
-
72
- def load
73
- load_credentials(profile)
74
- # Ignore it if there's no explicit_config_file and can't find one at a
75
- # default path.
76
- unless config_location.nil?
77
- if explicit_config_file && !path_exists?(config_location)
78
- raise ChefConfig::ConfigurationError, "Specified config file #{config_location} does not exist"
79
- end
80
-
81
- # Have to set Config.config_file b/c other config is derived from it.
82
- Config.config_file = config_location
83
- apply_config(IO.read(config_location), config_location)
84
- end
85
-
86
- load_dot_d(Config[:config_d_dir]) if Config[:config_d_dir]
87
-
88
- apply_defaults
89
- end
90
-
91
- # (Private API, public for test purposes)
92
- def env
93
- ENV
94
- end
95
-
96
- # (Private API, public for test purposes)
97
- def path_exists?(path)
98
- Pathname.new(path).expand_path.exist?
99
- end
100
-
101
- private
102
-
103
- def have_config?(path)
104
- if path_exists?(path)
105
- logger.info("Using config at #{path}")
106
- true
107
- else
108
- logger.debug("Config not found at #{path}, trying next option")
109
- false
110
- end
111
- end
112
-
113
- def locate_local_config
114
- candidate_configs = []
115
-
116
- # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
117
- if env["KNIFE_HOME"]
118
- candidate_configs << File.join(env["KNIFE_HOME"], "config.rb")
119
- candidate_configs << File.join(env["KNIFE_HOME"], "knife.rb")
120
- end
121
- # Look for $PWD/knife.rb
122
- if Dir.pwd
123
- candidate_configs << File.join(Dir.pwd, "config.rb")
124
- candidate_configs << File.join(Dir.pwd, "knife.rb")
125
- end
126
- # Look for $UPWARD/.chef/knife.rb
127
- if chef_config_dir
128
- candidate_configs << File.join(chef_config_dir, "config.rb")
129
- candidate_configs << File.join(chef_config_dir, "knife.rb")
130
- end
131
- # Look for $HOME/.chef/knife.rb
132
- PathHelper.home(ChefUtils::Dist::Infra::USER_CONF_DIR) do |dot_chef_dir|
133
- candidate_configs << File.join(dot_chef_dir, "config.rb")
134
- candidate_configs << File.join(dot_chef_dir, "knife.rb")
135
- end
136
-
137
- candidate_configs.find do |candidate_config|
138
- have_config?(candidate_config)
139
- end
140
- end
141
-
142
- def working_directory
143
- if ChefUtils.windows?
144
- env["CD"]
145
- else
146
- env["PWD"]
147
- end || Dir.pwd
148
- end
149
-
150
- def apply_credentials(creds, profile)
151
- # Store the profile used in case other things want it.
152
- Config.profile ||= profile
153
- # Validate the credentials data.
154
- if creds.key?("node_name") && creds.key?("client_name")
155
- raise ChefConfig::ConfigurationError, "Do not specify both node_name and client_name. You should prefer client_name."
156
- end
157
-
158
- # Load credentials data into the Chef configuration.
159
- creds.each do |key, value|
160
- case key.to_s
161
- when "client_name"
162
- # Special case because it's weird to set your username via `node_name`.
163
- Config.node_name = value
164
- when "validation_key", "validator_key"
165
- extract_key(value, :validation_key, :validation_key_contents)
166
- when "client_key"
167
- extract_key(value, :client_key, :client_key_contents)
168
- when "knife"
169
- Config.knife.merge!(value.transform_keys(&:to_sym))
170
- else
171
- Config[key.to_sym] = value
172
- end
173
- end
174
- @credentials_found = true
175
- end
176
-
177
- def extract_key(key_value, config_path, config_contents)
178
- if key_value.start_with?("-----BEGIN RSA PRIVATE KEY-----")
179
- Config.send(config_contents, key_value)
180
- else
181
- abs_path = Pathname.new(key_value).expand_path(home_chef_dir)
182
- Config.send(config_path, abs_path)
183
- end
184
- end
185
-
186
- def home_chef_dir
187
- @home_chef_dir ||= PathHelper.home(ChefUtils::Dist::Infra::USER_CONF_DIR)
188
- end
189
-
190
- def apply_config(config_content, config_file_path)
191
- Config.from_string(config_content, config_file_path)
192
- rescue SignalException
193
- raise
194
- rescue SyntaxError => e
195
- message = ""
196
- message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
197
- message << "#{e.class.name}: #{e.message}\n"
198
- if file_line = e.message[/#{Regexp.escape(config_file_path)}:\d+/]
199
- line = file_line[/:(\d+)$/, 1].to_i
200
- message << highlight_config_error(config_file_path, line)
201
- end
202
- raise ChefConfig::ConfigurationError, message
203
- rescue Exception => e
204
- message = "You have an error in your config file #{config_file_path}\n\n"
205
- message << "#{e.class.name}: #{e.message}\n"
206
- filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
207
- filtered_trace.each { |bt_line| message << " " << bt_line << "\n" }
208
- unless filtered_trace.empty?
209
- line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:(\d+)/, 1]
210
- message << highlight_config_error(config_file_path, line_nr.to_i)
211
- end
212
- raise ChefConfig::ConfigurationError, message
213
- end
214
-
215
- # Apply default configuration values for workstation-style tools.
216
- #
217
- # Global defaults should go in {ChefConfig::Config} instead, this is only
218
- # for things like `knife` and `chef`.
219
- #
220
- # @api private
221
- # @since 14.3
222
- # @return [void]
223
- def apply_defaults
224
- # If we don't have a better guess use the username.
225
- Config[:node_name] ||= Etc.getlogin
226
- # If we don't have a key (path or inline) check user.pem and $node_name.pem.
227
- unless Config.key?(:client_key) || Config.key?(:client_key_contents)
228
- key_path = find_default_key(["#{Config[:node_name]}.pem", "user.pem"])
229
- Config[:client_key] = key_path if key_path
230
- end
231
- # Similarly look for a validation key file, though this should be less
232
- # common these days.
233
- unless Config.key?(:validation_key) || Config.key?(:validation_key_contents)
234
- key_path = find_default_key(["#{Config[:validation_client_name]}.pem", "validator.pem", "validation.pem"])
235
- Config[:validation_key] = key_path if key_path
236
- end
237
- end
238
-
239
- # Look for a default key file.
240
- #
241
- # This searches for any of a list of possible default keys, checking both
242
- # the local `.chef/` folder and the home directory `~/.chef/`. Returns `nil`
243
- # if no matching file is found.
244
- #
245
- # @api private
246
- # @since 14.3
247
- # @param key_names [Array<String>] A list of possible filenames to check for.
248
- # The first one found will be returned.
249
- # @return [String, nil]
250
- def find_default_key(key_names)
251
- key_names.each do |filename|
252
- path = Pathname.new(filename)
253
- # If we have a config location (like ./.chef/), look there first.
254
- if config_location
255
- local_path = path.expand_path(File.dirname(config_location))
256
- return local_path.to_s if local_path.exist?
257
- end
258
- # Then check ~/.chef.
259
- home_path = path.expand_path(home_chef_dir)
260
- return home_path.to_s if home_path.exist?
261
- end
262
- nil
263
- end
264
-
265
- def highlight_config_error(file, line)
266
- config_file_lines = []
267
- IO.readlines(file).each_with_index { |l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}" }
268
- if line == 1
269
- lines = config_file_lines[0..3]
270
- else
271
- lines = config_file_lines[Range.new(line - 2, line)]
272
- end
273
- "Relevant file content:\n" + lines.join("\n") + "\n"
274
- end
275
-
276
- def logger
277
- @logger
278
- end
279
-
280
- end
281
- end
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@chef.io>)
3
+ # Copyright:: Copyright (c) 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 "chef-utils" unless defined?(ChefUtils::CANARY)
20
+ require_relative "config"
21
+ require_relative "exceptions"
22
+ require_relative "logger"
23
+ require_relative "path_helper"
24
+ require_relative "windows"
25
+ require_relative "mixin/dot_d"
26
+ require_relative "mixin/credentials"
27
+
28
+ module ChefConfig
29
+ class WorkstationConfigLoader
30
+ include ChefConfig::Mixin::DotD
31
+ include ChefConfig::Mixin::Credentials
32
+
33
+ # Path to a config file requested by user, (e.g., via command line option). Can be nil
34
+ attr_accessor :explicit_config_file
35
+ # The name of a credentials profile. Can be nil
36
+ attr_accessor :profile
37
+ attr_reader :credentials_found
38
+
39
+ # TODO: initialize this with a logger for Chef and Knife
40
+ def initialize(explicit_config_file, logger = nil, profile: nil)
41
+ @explicit_config_file = explicit_config_file
42
+ @chef_config_dir = nil
43
+ @config_location = nil
44
+ @profile = profile
45
+ @logger = logger || NullLogger.new
46
+ @credentials_found = false
47
+ end
48
+
49
+ def no_config_found?
50
+ config_location.nil? && !credentials_found
51
+ end
52
+
53
+ def config_location
54
+ @config_location ||= (explicit_config_file || locate_local_config)
55
+ end
56
+
57
+ def chef_config_dir
58
+ if @chef_config_dir.nil?
59
+ @chef_config_dir = false
60
+ full_path = working_directory.split(File::SEPARATOR)
61
+ (full_path.length - 1).downto(0) do |i|
62
+ candidate_directory = File.join(full_path[0..i] + [ChefUtils::Dist::Infra::USER_CONF_DIR])
63
+ if File.exist?(candidate_directory) && File.directory?(candidate_directory)
64
+ @chef_config_dir = candidate_directory
65
+ break
66
+ end
67
+ end
68
+ end
69
+ @chef_config_dir
70
+ end
71
+
72
+ def load
73
+ load_credentials(profile)
74
+ # Ignore it if there's no explicit_config_file and can't find one at a
75
+ # default path.
76
+ unless config_location.nil?
77
+ if explicit_config_file && !path_exists?(config_location)
78
+ raise ChefConfig::ConfigurationError, "Specified config file #{config_location} does not exist"
79
+ end
80
+
81
+ # Have to set Config.config_file b/c other config is derived from it.
82
+ Config.config_file = config_location
83
+ apply_config(IO.read(config_location), config_location)
84
+ end
85
+
86
+ load_dot_d(Config[:config_d_dir]) if Config[:config_d_dir]
87
+
88
+ apply_defaults
89
+ end
90
+
91
+ # (Private API, public for test purposes)
92
+ def env
93
+ ENV
94
+ end
95
+
96
+ # (Private API, public for test purposes)
97
+ def path_exists?(path)
98
+ Pathname.new(path).expand_path.exist?
99
+ end
100
+
101
+ private
102
+
103
+ def have_config?(path)
104
+ if path_exists?(path)
105
+ logger.info("Using config at #{path}")
106
+ true
107
+ else
108
+ logger.debug("Config not found at #{path}, trying next option")
109
+ false
110
+ end
111
+ end
112
+
113
+ def locate_local_config
114
+ candidate_configs = []
115
+
116
+ # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
117
+ if env["KNIFE_HOME"]
118
+ candidate_configs << File.join(env["KNIFE_HOME"], "config.rb")
119
+ candidate_configs << File.join(env["KNIFE_HOME"], "knife.rb")
120
+ end
121
+ # Look for $PWD/knife.rb
122
+ if Dir.pwd
123
+ candidate_configs << File.join(Dir.pwd, "config.rb")
124
+ candidate_configs << File.join(Dir.pwd, "knife.rb")
125
+ end
126
+ # Look for $UPWARD/.chef/knife.rb
127
+ if chef_config_dir
128
+ candidate_configs << File.join(chef_config_dir, "config.rb")
129
+ candidate_configs << File.join(chef_config_dir, "knife.rb")
130
+ end
131
+ # Look for $HOME/.chef/knife.rb
132
+ PathHelper.home(ChefUtils::Dist::Infra::USER_CONF_DIR) do |dot_chef_dir|
133
+ candidate_configs << File.join(dot_chef_dir, "config.rb")
134
+ candidate_configs << File.join(dot_chef_dir, "knife.rb")
135
+ end
136
+
137
+ candidate_configs.find do |candidate_config|
138
+ have_config?(candidate_config)
139
+ end
140
+ end
141
+
142
+ def working_directory
143
+ if ChefUtils.windows?
144
+ env["CD"]
145
+ else
146
+ env["PWD"]
147
+ end || Dir.pwd
148
+ end
149
+
150
+ def apply_credentials(creds, profile)
151
+ # Store the profile used in case other things want it.
152
+ Config.profile ||= profile
153
+ # Validate the credentials data.
154
+ if creds.key?("node_name") && creds.key?("client_name")
155
+ raise ChefConfig::ConfigurationError, "Do not specify both node_name and client_name. You should prefer client_name."
156
+ end
157
+
158
+ # Load credentials data into the Chef configuration.
159
+ creds.each do |key, value|
160
+ case key.to_s
161
+ when "client_name"
162
+ # Special case because it's weird to set your username via `node_name`.
163
+ Config.node_name = value
164
+ when "validation_key", "validator_key"
165
+ extract_key(value, :validation_key, :validation_key_contents)
166
+ when "client_key"
167
+ extract_key(value, :client_key, :client_key_contents)
168
+ when "knife"
169
+ Config.knife.merge!(value.transform_keys(&:to_sym))
170
+ else
171
+ Config[key.to_sym] = value
172
+ end
173
+ end
174
+ @credentials_found = true
175
+ end
176
+
177
+ def extract_key(key_value, config_path, config_contents)
178
+ if key_value.start_with?("-----BEGIN RSA PRIVATE KEY-----")
179
+ Config.send(config_contents, key_value)
180
+ else
181
+ abs_path = Pathname.new(key_value).expand_path(home_chef_dir)
182
+ Config.send(config_path, abs_path)
183
+ end
184
+ end
185
+
186
+ def home_chef_dir
187
+ @home_chef_dir ||= PathHelper.home(ChefUtils::Dist::Infra::USER_CONF_DIR)
188
+ end
189
+
190
+ def apply_config(config_content, config_file_path)
191
+ Config.from_string(config_content, config_file_path)
192
+ rescue SignalException
193
+ raise
194
+ rescue SyntaxError => e
195
+ message = ""
196
+ message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
197
+ message << "#{e.class.name}: #{e.message}\n"
198
+ if file_line = e.message[/#{Regexp.escape(config_file_path)}:\d+/]
199
+ line = file_line[/:(\d+)$/, 1].to_i
200
+ message << highlight_config_error(config_file_path, line)
201
+ end
202
+ raise ChefConfig::ConfigurationError, message
203
+ rescue Exception => e
204
+ message = "You have an error in your config file #{config_file_path}\n\n"
205
+ message << "#{e.class.name}: #{e.message}\n"
206
+ filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
207
+ filtered_trace.each { |bt_line| message << " " << bt_line << "\n" }
208
+ unless filtered_trace.empty?
209
+ line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:(\d+)/, 1]
210
+ message << highlight_config_error(config_file_path, line_nr.to_i)
211
+ end
212
+ raise ChefConfig::ConfigurationError, message
213
+ end
214
+
215
+ # Apply default configuration values for workstation-style tools.
216
+ #
217
+ # Global defaults should go in {ChefConfig::Config} instead, this is only
218
+ # for things like `knife` and `chef`.
219
+ #
220
+ # @api private
221
+ # @since 14.3
222
+ # @return [void]
223
+ def apply_defaults
224
+ # If we don't have a better guess use the username.
225
+ Config[:node_name] ||= Etc.getlogin
226
+ # If we don't have a key (path or inline) check user.pem and $node_name.pem.
227
+ unless Config.key?(:client_key) || Config.key?(:client_key_contents)
228
+ key_path = find_default_key(["#{Config[:node_name]}.pem", "user.pem"])
229
+ Config[:client_key] = key_path if key_path
230
+ end
231
+ # Similarly look for a validation key file, though this should be less
232
+ # common these days.
233
+ unless Config.key?(:validation_key) || Config.key?(:validation_key_contents)
234
+ key_path = find_default_key(["#{Config[:validation_client_name]}.pem", "validator.pem", "validation.pem"])
235
+ Config[:validation_key] = key_path if key_path
236
+ end
237
+ end
238
+
239
+ # Look for a default key file.
240
+ #
241
+ # This searches for any of a list of possible default keys, checking both
242
+ # the local `.chef/` folder and the home directory `~/.chef/`. Returns `nil`
243
+ # if no matching file is found.
244
+ #
245
+ # @api private
246
+ # @since 14.3
247
+ # @param key_names [Array<String>] A list of possible filenames to check for.
248
+ # The first one found will be returned.
249
+ # @return [String, nil]
250
+ def find_default_key(key_names)
251
+ key_names.each do |filename|
252
+ path = Pathname.new(filename)
253
+ # If we have a config location (like ./.chef/), look there first.
254
+ if config_location
255
+ local_path = path.expand_path(File.dirname(config_location))
256
+ return local_path.to_s if local_path.exist?
257
+ end
258
+ # Then check ~/.chef.
259
+ home_path = path.expand_path(home_chef_dir)
260
+ return home_path.to_s if home_path.exist?
261
+ end
262
+ nil
263
+ end
264
+
265
+ def highlight_config_error(file, line)
266
+ config_file_lines = []
267
+ IO.readlines(file).each_with_index { |l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}" }
268
+ if line == 1
269
+ lines = config_file_lines[0..3]
270
+ else
271
+ lines = config_file_lines[Range.new(line - 2, line)]
272
+ end
273
+ "Relevant file content:\n" + lines.join("\n") + "\n"
274
+ end
275
+
276
+ def logger
277
+ @logger
278
+ end
279
+
280
+ end
281
+ end