diff --git a/lib/ansible/errors/__init__.py b/lib/ansible/errors/__init__.py index 2186e6bc744..b2bf10dad28 100644 --- a/lib/ansible/errors/__init__.py +++ b/lib/ansible/errors/__init__.py @@ -24,7 +24,8 @@ from ansible.errors.yaml_strings import ( YAML_POSITION_DETAILS, YAML_COMMON_DICT_ERROR, YAML_COMMON_UNQUOTED_COLON_ERROR, YAML_COMMON_PARTIALLY_QUOTED_LINE_ERROR, - YAML_COMMON_UNBALANCED_QUOTES_ERROR ) + YAML_COMMON_UNBALANCED_QUOTES_ERROR, + YAML_COMMON_LEADING_TAB_ERROR) from ansible.module_utils._text import to_native, to_text @@ -111,6 +112,9 @@ class AnsibleError(Exception): #header_line = ("=" * 73) error_message += "\nThe offending line appears to be:\n\n%s\n%s\n%s\n" % (prev_line.rstrip(), target_line.rstrip(), arrow_line) + # TODO: There may be cases where there is a valid tab in a line that has other errors. + if '\t' in target_line: + error_message += YAML_COMMON_LEADING_TAB_ERROR # common error/remediation checking here: # check for unquoted vars starting lines if ('{{' in target_line and '}}' in target_line) and ('"{{' not in target_line or "'{{" not in target_line): diff --git a/lib/ansible/errors/yaml_strings.py b/lib/ansible/errors/yaml_strings.py index dcd6ffd79fc..599729fda4f 100644 --- a/lib/ansible/errors/yaml_strings.py +++ b/lib/ansible/errors/yaml_strings.py @@ -116,3 +116,20 @@ Could be written as: foo: '"bad" "wolf"' """ +YAML_COMMON_LEADING_TAB_ERROR = """\ +There appears to be a tab character at the start of the line. + +YAML does not use tabs for formatting. Tabs should be replaced with spaces. + +For example: + - name: update tooling + vars: + version: 1.2.3 +# ^--- there is a tab there. + +Should be written as: + - name: update tooling + vars: + version: 1.2.3 +# ^--- all spaces here. +""" diff --git a/test/units/parsing/test_data_loader.py b/test/units/parsing/test_data_loader.py index b6ec8247e5b..6b2fe23c454 100644 --- a/test/units/parsing/test_data_loader.py +++ b/test/units/parsing/test_data_loader.py @@ -24,6 +24,7 @@ from six import PY3 from ansible.compat.tests import unittest from ansible.compat.tests.mock import patch, mock_open from ansible.errors import AnsibleParserError +from ansible.errors import yaml_strings from ansible.parsing.dataloader import DataLoader @@ -60,6 +61,17 @@ class TestDataLoader(unittest.TestCase): """, True) self.assertRaises(AnsibleParserError, self._loader.load_from_file, 'dummy_yaml_bad.txt') + @patch('ansible.errors.AnsibleError._get_error_lines_from_file') + @patch.object(DataLoader, '_get_file_contents') + def test_tab_error(self, mock_def, mock_get_error_lines): + mock_def.return_value = (u"""---\nhosts: localhost\nvars:\n foo: bar\n\tblip: baz""", True) + mock_get_error_lines.return_value = ('''\tblip: baz''', '''..foo: bar''') + with self.assertRaises(AnsibleParserError) as cm: + self._loader.load_from_file('dummy_yaml_text.txt') + self.assertIn(yaml_strings.YAML_COMMON_LEADING_TAB_ERROR, str(cm.exception)) + self.assertIn('foo: bar', str(cm.exception)) + + class TestDataLoaderWithVault(unittest.TestCase): def setUp(self): diff --git a/test/units/parsing/yaml/test_loader.py b/test/units/parsing/yaml/test_loader.py index 2b166f2bd4c..7c09437b728 100644 --- a/test/units/parsing/yaml/test_loader.py +++ b/test/units/parsing/yaml/test_loader.py @@ -37,8 +37,10 @@ from units.mock.yaml_helper import YamlTestUtils try: from _yaml import ParserError + from _yaml import ScannerError except ImportError: from yaml.parser import ParserError + from yaml.scanner import ScannerError class NameStringIO(StringIO): @@ -145,6 +147,11 @@ class TestAnsibleLoaderBasic(unittest.TestCase): loader = AnsibleLoader(stream, 'myfile.yml') self.assertRaises(ParserError, loader.get_single_data) + def test_tab_error(self): + stream = StringIO(u"""---\nhosts: localhost\nvars:\n foo: bar\n\tblip: baz""") + loader = AnsibleLoader(stream, 'myfile.yml') + self.assertRaises(ScannerError, loader.get_single_data) + def test_front_matter(self): stream = StringIO(u"""---\nfoo: bar""") loader = AnsibleLoader(stream, 'myfile.yml')