Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/ansible_core-2.17.0.dev0-py3.8.egg/ansible/errors/__init__.py: 43%
168 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-30 06:38 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-30 06:38 +0000
1# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
2#
3# This file is part of Ansible
4#
5# Ansible is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansible is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
18from __future__ import annotations
20import re
21import traceback
23from collections.abc import Sequence
25from ansible.errors.yaml_strings import (
26 YAML_COMMON_DICT_ERROR,
27 YAML_COMMON_LEADING_TAB_ERROR,
28 YAML_COMMON_PARTIALLY_QUOTED_LINE_ERROR,
29 YAML_COMMON_UNBALANCED_QUOTES_ERROR,
30 YAML_COMMON_UNQUOTED_COLON_ERROR,
31 YAML_COMMON_UNQUOTED_VARIABLE_ERROR,
32 YAML_POSITION_DETAILS,
33 YAML_AND_SHORTHAND_ERROR,
34)
35from ansible.module_utils.common.text.converters import to_native, to_text
38class AnsibleError(Exception):
39 '''
40 This is the base class for all errors raised from Ansible code,
41 and can be instantiated with two optional parameters beyond the
42 error message to control whether detailed information is displayed
43 when the error occurred while parsing a data file of some kind.
45 Usage:
47 raise AnsibleError('some message here', obj=obj, show_content=True)
49 Where "obj" is some subclass of ansible.parsing.yaml.objects.AnsibleBaseYAMLObject,
50 which should be returned by the DataLoader() class.
51 '''
53 def __init__(self, message="", obj=None, show_content=True, suppress_extended_error=False, orig_exc=None):
54 super(AnsibleError, self).__init__(message)
56 self._show_content = show_content
57 self._suppress_extended_error = suppress_extended_error
58 self._message = to_native(message)
59 self.obj = obj
60 self.orig_exc = orig_exc
62 @property
63 def message(self):
64 # we import this here to prevent an import loop problem,
65 # since the objects code also imports ansible.errors
66 from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject
68 message = [self._message]
69 if isinstance(self.obj, AnsibleBaseYAMLObject):
70 extended_error = self._get_extended_error()
71 if extended_error and not self._suppress_extended_error:
72 message.append(
73 '\n\n%s' % to_native(extended_error)
74 )
75 elif self.orig_exc:
76 message.append('. %s' % to_native(self.orig_exc))
78 return ''.join(message)
80 @message.setter
81 def message(self, val):
82 self._message = val
84 def __str__(self):
85 return self.message
87 def __repr__(self):
88 return self.message
90 def _get_error_lines_from_file(self, file_name, line_number):
91 '''
92 Returns the line in the file which corresponds to the reported error
93 location, as well as the line preceding it (if the error did not
94 occur on the first line), to provide context to the error.
95 '''
97 target_line = ''
98 prev_line = ''
100 with open(file_name, 'r') as f:
101 lines = f.readlines()
103 # In case of a YAML loading error, PyYAML will report the very last line
104 # as the location of the error. Avoid an index error here in order to
105 # return a helpful message.
106 file_length = len(lines)
107 if line_number >= file_length:
108 line_number = file_length - 1
110 # If target_line contains only whitespace, move backwards until
111 # actual code is found. If there are several empty lines after target_line,
112 # the error lines would just be blank, which is not very helpful.
113 target_line = lines[line_number]
114 while not target_line.strip():
115 line_number -= 1
116 target_line = lines[line_number]
118 if line_number > 0:
119 prev_line = lines[line_number - 1]
121 return (target_line, prev_line)
123 def _get_extended_error(self):
124 '''
125 Given an object reporting the location of the exception in a file, return
126 detailed information regarding it including:
128 * the line which caused the error as well as the one preceding it
129 * causes and suggested remedies for common syntax errors
131 If this error was created with show_content=False, the reporting of content
132 is suppressed, as the file contents may be sensitive (ie. vault data).
133 '''
135 error_message = ''
137 try:
138 (src_file, line_number, col_number) = self.obj.ansible_pos
139 error_message += YAML_POSITION_DETAILS % (src_file, line_number, col_number)
140 if src_file not in ('<string>', '<unicode>') and self._show_content:
141 (target_line, prev_line) = self._get_error_lines_from_file(src_file, line_number - 1)
142 target_line = to_text(target_line)
143 prev_line = to_text(prev_line)
144 if target_line:
145 stripped_line = target_line.replace(" ", "")
147 # Check for k=v syntax in addition to YAML syntax and set the appropriate error position,
148 # arrow index
149 if re.search(r'\w+(\s+)?=(\s+)?[\w/-]+', prev_line):
150 error_position = prev_line.rstrip().find('=')
151 arrow_line = (" " * error_position) + "^ here"
152 error_message = YAML_POSITION_DETAILS % (src_file, line_number - 1, error_position + 1)
153 error_message += "\nThe offending line appears to be:\n\n%s\n%s\n\n" % (prev_line.rstrip(), arrow_line)
154 error_message += YAML_AND_SHORTHAND_ERROR
155 else:
156 arrow_line = (" " * (col_number - 1)) + "^ here"
157 error_message += "\nThe offending line appears to be:\n\n%s\n%s\n%s\n" % (prev_line.rstrip(), target_line.rstrip(), arrow_line)
159 # TODO: There may be cases where there is a valid tab in a line that has other errors.
160 if '\t' in target_line:
161 error_message += YAML_COMMON_LEADING_TAB_ERROR
162 # common error/remediation checking here:
163 # check for unquoted vars starting lines
164 if ('{{' in target_line and '}}' in target_line) and ('"{{' not in target_line or "'{{" not in target_line):
165 error_message += YAML_COMMON_UNQUOTED_VARIABLE_ERROR
166 # check for common dictionary mistakes
167 elif ":{{" in stripped_line and "}}" in stripped_line:
168 error_message += YAML_COMMON_DICT_ERROR
169 # check for common unquoted colon mistakes
170 elif (len(target_line) and
171 len(target_line) > 1 and
172 len(target_line) > col_number and
173 target_line[col_number] == ":" and
174 target_line.count(':') > 1):
175 error_message += YAML_COMMON_UNQUOTED_COLON_ERROR
176 # otherwise, check for some common quoting mistakes
177 else:
178 # FIXME: This needs to split on the first ':' to account for modules like lineinfile
179 # that may have lines that contain legitimate colons, e.g., line: 'i ALL= (ALL) NOPASSWD: ALL'
180 # and throw off the quote matching logic.
181 parts = target_line.split(":")
182 if len(parts) > 1:
183 middle = parts[1].strip()
184 match = False
185 unbalanced = False
187 if middle.startswith("'") and not middle.endswith("'"):
188 match = True
189 elif middle.startswith('"') and not middle.endswith('"'):
190 match = True
192 if (len(middle) > 0 and
193 middle[0] in ['"', "'"] and
194 middle[-1] in ['"', "'"] and
195 target_line.count("'") > 2 or
196 target_line.count('"') > 2):
197 unbalanced = True
199 if match:
200 error_message += YAML_COMMON_PARTIALLY_QUOTED_LINE_ERROR
201 if unbalanced:
202 error_message += YAML_COMMON_UNBALANCED_QUOTES_ERROR
204 except (IOError, TypeError):
205 error_message += '\n(could not open file to display line)'
206 except IndexError:
207 error_message += '\n(specified line no longer in file, maybe it changed?)'
209 return error_message
212class AnsiblePromptInterrupt(AnsibleError):
213 '''User interrupt'''
216class AnsiblePromptNoninteractive(AnsibleError):
217 '''Unable to get user input'''
220class AnsibleAssertionError(AnsibleError, AssertionError):
221 '''Invalid assertion'''
222 pass
225class AnsibleOptionsError(AnsibleError):
226 ''' bad or incomplete options passed '''
227 pass
230class AnsibleParserError(AnsibleError):
231 ''' something was detected early that is wrong about a playbook or data file '''
232 pass
235class AnsibleInternalError(AnsibleError):
236 ''' internal safeguards tripped, something happened in the code that should never happen '''
237 pass
240class AnsibleRuntimeError(AnsibleError):
241 ''' ansible had a problem while running a playbook '''
242 pass
245class AnsibleModuleError(AnsibleRuntimeError):
246 ''' a module failed somehow '''
247 pass
250class AnsibleConnectionFailure(AnsibleRuntimeError):
251 ''' the transport / connection_plugin had a fatal error '''
252 pass
255class AnsibleAuthenticationFailure(AnsibleConnectionFailure):
256 '''invalid username/password/key'''
257 pass
260class AnsibleCallbackError(AnsibleRuntimeError):
261 ''' a callback failure '''
262 pass
265class AnsibleTemplateError(AnsibleRuntimeError):
266 '''A template related error'''
267 pass
270class AnsibleFilterError(AnsibleTemplateError):
271 ''' a templating failure '''
272 pass
275class AnsibleLookupError(AnsibleTemplateError):
276 ''' a lookup failure '''
277 pass
280class AnsibleUndefinedVariable(AnsibleTemplateError):
281 ''' a templating failure '''
282 pass
285class AnsibleFileNotFound(AnsibleRuntimeError):
286 ''' a file missing failure '''
288 def __init__(self, message="", obj=None, show_content=True, suppress_extended_error=False, orig_exc=None, paths=None, file_name=None):
290 self.file_name = file_name
291 self.paths = paths
293 if message:
294 message += "\n"
295 if self.file_name:
296 message += "Could not find or access '%s'" % to_text(self.file_name)
297 else:
298 message += "Could not find file"
300 if self.paths and isinstance(self.paths, Sequence):
301 searched = to_text('\n\t'.join(self.paths))
302 if message:
303 message += "\n"
304 message += "Searched in:\n\t%s" % searched
306 message += " on the Ansible Controller.\nIf you are using a module and expect the file to exist on the remote, see the remote_src option"
308 super(AnsibleFileNotFound, self).__init__(message=message, obj=obj, show_content=show_content,
309 suppress_extended_error=suppress_extended_error, orig_exc=orig_exc)
312# These Exceptions are temporary, using them as flow control until we can get a better solution.
313# DO NOT USE as they will probably be removed soon.
314# We will port the action modules in our tree to use a context manager instead.
315class AnsibleAction(AnsibleRuntimeError):
316 ''' Base Exception for Action plugin flow control '''
318 def __init__(self, message="", obj=None, show_content=True, suppress_extended_error=False, orig_exc=None, result=None):
320 super(AnsibleAction, self).__init__(message=message, obj=obj, show_content=show_content,
321 suppress_extended_error=suppress_extended_error, orig_exc=orig_exc)
322 if result is None:
323 self.result = {}
324 else:
325 self.result = result
328class AnsibleActionSkip(AnsibleAction):
329 ''' an action runtime skip'''
331 def __init__(self, message="", obj=None, show_content=True, suppress_extended_error=False, orig_exc=None, result=None):
332 super(AnsibleActionSkip, self).__init__(message=message, obj=obj, show_content=show_content,
333 suppress_extended_error=suppress_extended_error, orig_exc=orig_exc, result=result)
334 self.result.update({'skipped': True, 'msg': message})
337class AnsibleActionFail(AnsibleAction):
338 ''' an action runtime failure'''
339 def __init__(self, message="", obj=None, show_content=True, suppress_extended_error=False, orig_exc=None, result=None):
340 super(AnsibleActionFail, self).__init__(message=message, obj=obj, show_content=show_content,
341 suppress_extended_error=suppress_extended_error, orig_exc=orig_exc, result=result)
342 self.result.update({'failed': True, 'msg': message, 'exception': traceback.format_exc()})
345class _AnsibleActionDone(AnsibleAction):
346 ''' an action runtime early exit'''
347 pass
350class AnsiblePluginError(AnsibleError):
351 ''' base class for Ansible plugin-related errors that do not need AnsibleError contextual data '''
352 def __init__(self, message=None, plugin_load_context=None):
353 super(AnsiblePluginError, self).__init__(message)
354 self.plugin_load_context = plugin_load_context
357class AnsiblePluginRemovedError(AnsiblePluginError):
358 ''' a requested plugin has been removed '''
359 pass
362class AnsiblePluginCircularRedirect(AnsiblePluginError):
363 '''a cycle was detected in plugin redirection'''
364 pass
367class AnsibleCollectionUnsupportedVersionError(AnsiblePluginError):
368 '''a collection is not supported by this version of Ansible'''
369 pass
372class AnsibleFilterTypeError(AnsibleTemplateError, TypeError):
373 ''' a Jinja filter templating failure due to bad type'''
374 pass
377class AnsiblePluginNotFound(AnsiblePluginError):
378 ''' Indicates we did not find an Ansible plugin '''
379 pass