summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToshio Kuratomi <toshio@fedoraproject.org>2015-10-26 14:23:09 -0700
committerToshio Kuratomi <toshio@fedoraproject.org>2015-10-27 12:39:42 -0700
commit4203850d1afe7f4bcfd32963900485f99198da6a (patch)
tree6a6d8dd9e99209365ce0d140386048e25a482ecc
parent57fe0d6aeda17ff93427e02d52c3354adc4f547d (diff)
downloadansible-4203850d1afe7f4bcfd32963900485f99198da6a.tar.gz
Break apart a looped dependency to show a warning when parsing playbooks
Display a warning when a dict key is overwritten by pyyaml Fixes #12888
-rwxr-xr-xhacking/test-module2
-rw-r--r--lib/ansible/cli/adhoc.py2
-rw-r--r--lib/ansible/cli/playbook.py2
-rw-r--r--lib/ansible/cli/vault.py5
-rw-r--r--lib/ansible/constants.py2
-rw-r--r--lib/ansible/executor/task_result.py2
-rw-r--r--lib/ansible/parsing/__init__.py277
-rw-r--r--lib/ansible/parsing/dataloader.py294
-rw-r--r--lib/ansible/parsing/quoting.py30
-rw-r--r--lib/ansible/parsing/splitter.py10
-rw-r--r--lib/ansible/parsing/yaml/constructor.py41
-rw-r--r--lib/ansible/playbook/__init__.py4
-rw-r--r--lib/ansible/playbook/base.py6
-rw-r--r--lib/ansible/vars/__init__.py1
-rw-r--r--samples/multi_queues.py2
-rw-r--r--test/units/mock/loader.py2
-rw-r--r--test/units/parsing/test_data_loader.py2
-rw-r--r--test/units/parsing/test_unquote.py2
18 files changed, 374 insertions, 312 deletions
diff --git a/hacking/test-module b/hacking/test-module
index 543e803b97..412b39452c 100755
--- a/hacking/test-module
+++ b/hacking/test-module
@@ -36,7 +36,7 @@ import subprocess
import traceback
import optparse
import ansible.utils.vars as utils_vars
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
from ansible.parsing.utils.jsonify import jsonify
from ansible.parsing.splitter import parse_kv
import ansible.executor.module_common as module_common
diff --git a/lib/ansible/cli/adhoc.py b/lib/ansible/cli/adhoc.py
index f2d6780c93..7d611bf1f0 100644
--- a/lib/ansible/cli/adhoc.py
+++ b/lib/ansible/cli/adhoc.py
@@ -27,7 +27,7 @@ from ansible.cli import CLI
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.inventory import Inventory
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
from ansible.parsing.splitter import parse_kv
from ansible.playbook.play import Play
from ansible.plugins import get_all_plugin_loaders
diff --git a/lib/ansible/cli/playbook.py b/lib/ansible/cli/playbook.py
index 33414601ed..11c4f796b9 100644
--- a/lib/ansible/cli/playbook.py
+++ b/lib/ansible/cli/playbook.py
@@ -29,7 +29,7 @@ from ansible.cli import CLI
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.inventory import Inventory
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
from ansible.utils.vars import load_extra_vars
from ansible.vars import VariableManager
diff --git a/lib/ansible/cli/vault.py b/lib/ansible/cli/vault.py
index e4909cc255..a4329056f9 100644
--- a/lib/ansible/cli/vault.py
+++ b/lib/ansible/cli/vault.py
@@ -21,14 +21,11 @@ __metaclass__ = type
import os
import sys
-import traceback
-from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleOptionsError
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
from ansible.parsing.vault import VaultEditor
from ansible.cli import CLI
-from ansible.utils.display import Display
class VaultCLI(CLI):
""" Vault command line class """
diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py
index 1a919eed59..8270ef22c0 100644
--- a/lib/ansible/constants.py
+++ b/lib/ansible/constants.py
@@ -25,7 +25,7 @@ from string import ascii_letters, digits
from ansible.compat.six import string_types
from ansible.compat.six.moves import configparser
-from ansible.parsing.splitter import unquote
+from ansible.parsing.quoting import unquote
from ansible.errors import AnsibleOptionsError
# copied from utils, avoid circular reference fun :)
diff --git a/lib/ansible/executor/task_result.py b/lib/ansible/executor/task_result.py
index d87f9413ef..da9ab2a11a 100644
--- a/lib/ansible/executor/task_result.py
+++ b/lib/ansible/executor/task_result.py
@@ -19,7 +19,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
class TaskResult:
'''
diff --git a/lib/ansible/parsing/__init__.py b/lib/ansible/parsing/__init__.py
index 8c1833f037..c5cbe7cf9d 100644
--- a/lib/ansible/parsing/__init__.py
+++ b/lib/ansible/parsing/__init__.py
@@ -1,4 +1,4 @@
-# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2015, Toshio Kuratomi <tkuratomi@ansbile.com>
#
# This file is part of Ansible
#
@@ -18,278 +18,3 @@
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-
-import copy
-import json
-import os
-import stat
-import subprocess
-
-from yaml import load, YAMLError
-from ansible.compat.six import text_type, string_types
-
-from ansible.errors import AnsibleFileNotFound, AnsibleParserError, AnsibleError
-from ansible.errors.yaml_strings import YAML_SYNTAX_ERROR
-from ansible.parsing.vault import VaultLib
-from ansible.parsing.splitter import unquote
-from ansible.parsing.yaml.loader import AnsibleLoader
-from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleUnicode
-from ansible.module_utils.basic import is_executable
-from ansible.utils.path import unfrackpath
-from ansible.utils.unicode import to_unicode
-
-class DataLoader():
-
- '''
- The DataLoader class is used to load and parse YAML or JSON content,
- either from a given file name or from a string that was previously
- read in through other means. A Vault password can be specified, and
- any vault-encrypted files will be decrypted.
-
- Data read from files will also be cached, so the file will never be
- read from disk more than once.
-
- Usage:
-
- dl = DataLoader()
- (or)
- dl = DataLoader(vault_password='foo')
-
- ds = dl.load('...')
- ds = dl.load_from_file('/path/to/file')
- '''
-
- def __init__(self):
- self._basedir = '.'
- self._FILE_CACHE = dict()
-
- # initialize the vault stuff with an empty password
- self.set_vault_password(None)
-
- def set_vault_password(self, vault_password):
- self._vault_password = vault_password
- self._vault = VaultLib(password=vault_password)
-
- def load(self, data, file_name='<string>', show_content=True):
- '''
- Creates a python datastructure from the given data, which can be either
- a JSON or YAML string.
- '''
-
- try:
- # we first try to load this data as JSON
- return json.loads(data)
- except:
- # if loading JSON failed for any reason, we go ahead
- # and try to parse it as YAML instead
-
- if isinstance(data, AnsibleUnicode):
- # The PyYAML's libyaml bindings use PyUnicode_CheckExact so
- # they are unable to cope with our subclass.
- # Unwrap and re-wrap the unicode so we can keep track of line
- # numbers
- new_data = text_type(data)
- else:
- new_data = data
- try:
- new_data = self._safe_load(new_data, file_name=file_name)
- except YAMLError as yaml_exc:
- self._handle_error(yaml_exc, file_name, show_content)
-
- if isinstance(data, AnsibleUnicode):
- new_data = AnsibleUnicode(new_data)
- new_data.ansible_pos = data.ansible_pos
- return new_data
-
- def load_from_file(self, file_name):
- ''' Loads data from a file, which can contain either JSON or YAML. '''
-
- file_name = self.path_dwim(file_name)
-
- # if the file has already been read in and cached, we'll
- # return those results to avoid more file/vault operations
- if file_name in self._FILE_CACHE:
- parsed_data = self._FILE_CACHE[file_name]
- else:
- # read the file contents and load the data structure from them
- (file_data, show_content) = self._get_file_contents(file_name)
- parsed_data = self.load(data=file_data, file_name=file_name, show_content=show_content)
-
- # cache the file contents for next time
- self._FILE_CACHE[file_name] = parsed_data
-
- # return a deep copy here, so the cache is not affected
- return copy.deepcopy(parsed_data)
-
- def path_exists(self, path):
- path = self.path_dwim(path)
- return os.path.exists(path)
-
- def is_file(self, path):
- path = self.path_dwim(path)
- return os.path.isfile(path) or path == os.devnull
-
- def is_directory(self, path):
- path = self.path_dwim(path)
- return os.path.isdir(path)
-
- def list_directory(self, path):
- path = self.path_dwim(path)
- return os.listdir(path)
-
- def is_executable(self, path):
- '''is the given path executable?'''
- path = self.path_dwim(path)
- return is_executable(path)
-
- def _safe_load(self, stream, file_name=None):
- ''' Implements yaml.safe_load(), except using our custom loader class. '''
-
- loader = AnsibleLoader(stream, file_name)
- try:
- return loader.get_single_data()
- finally:
- loader.dispose()
-
- def _get_file_contents(self, file_name):
- '''
- Reads the file contents from the given file name, and will decrypt them
- if they are found to be vault-encrypted.
- '''
- if not file_name or not isinstance(file_name, string_types):
- raise AnsibleParserError("Invalid filename: '%s'" % str(file_name))
-
- if not self.path_exists(file_name) or not self.is_file(file_name):
- raise AnsibleFileNotFound("the file_name '%s' does not exist, or is not readable" % file_name)
-
- show_content = True
- try:
- with open(file_name, 'rb') as f:
- data = f.read()
- if self._vault.is_encrypted(data):
- data = self._vault.decrypt(data)
- show_content = False
-
- data = to_unicode(data, errors='strict')
- return (data, show_content)
-
- except (IOError, OSError) as e:
- raise AnsibleParserError("an error occurred while trying to read the file '%s': %s" % (file_name, str(e)))
-
- def _handle_error(self, yaml_exc, file_name, show_content):
- '''
- Optionally constructs an object (AnsibleBaseYAMLObject) to encapsulate the
- file name/position where a YAML exception occurred, and raises an AnsibleParserError
- to display the syntax exception information.
- '''
-
- # if the YAML exception contains a problem mark, use it to construct
- # an object the error class can use to display the faulty line
- err_obj = None
- if hasattr(yaml_exc, 'problem_mark'):
- err_obj = AnsibleBaseYAMLObject()
- err_obj.ansible_pos = (file_name, yaml_exc.problem_mark.line + 1, yaml_exc.problem_mark.column + 1)
-
- raise AnsibleParserError(YAML_SYNTAX_ERROR, obj=err_obj, show_content=show_content)
-
- def get_basedir(self):
- ''' returns the current basedir '''
- return self._basedir
-
- def set_basedir(self, basedir):
- ''' sets the base directory, used to find files when a relative path is given '''
-
- if basedir is not None:
- self._basedir = to_unicode(basedir)
-
- def path_dwim(self, given):
- '''
- make relative paths work like folks expect.
- '''
-
- given = unquote(given)
-
- if given.startswith("/"):
- return os.path.abspath(given)
- elif given.startswith("~"):
- return os.path.abspath(os.path.expanduser(given))
- else:
- return os.path.abspath(os.path.join(self._basedir, given))
-
- def path_dwim_relative(self, path, dirname, source):
- '''
- find one file in either a role or playbook dir with or without
- explicitly named dirname subdirs
-
- Used in action plugins and lookups to find supplemental files that
- could be in either place.
- '''
-
- search = []
- isrole = False
-
- # I have full path, nothing else needs to be looked at
- if source.startswith('~') or source.startswith('/'):
- search.append(self.path_dwim(source))
- else:
- # base role/play path + templates/files/vars + relative filename
- search.append(os.path.join(path, dirname, source))
-
- basedir = unfrackpath(path)
-
- # is it a role and if so make sure you get correct base path
- if path.endswith('tasks') and os.path.exists(os.path.join(path,'main.yml')) \
- or os.path.exists(os.path.join(path,'tasks/main.yml')):
- isrole = True
- if path.endswith('tasks'):
- basedir = unfrackpath(os.path.dirname(path))
-
- cur_basedir = self._basedir
- self.set_basedir(basedir)
- # resolved base role/play path + templates/files/vars + relative filename
- search.append(self.path_dwim(os.path.join(basedir, dirname, source)))
- self.set_basedir(cur_basedir)
-
- if isrole and not source.endswith(dirname):
- # look in role's tasks dir w/o dirname
- search.append(self.path_dwim(os.path.join(basedir, 'tasks', source)))
-
- # try to create absolute path for loader basedir + templates/files/vars + filename
- search.append(self.path_dwim(os.path.join(dirname,source)))
- search.append(self.path_dwim(os.path.join(basedir, source)))
-
- # try to create absolute path for loader basedir + filename
- search.append(self.path_dwim(source))
-
- for candidate in search:
- if os.path.exists(candidate):
- break
-
- return candidate
-
- def read_vault_password_file(self, vault_password_file):
- """
- Read a vault password from a file or if executable, execute the script and
- retrieve password from STDOUT
- """
-
- this_path = os.path.realpath(os.path.expanduser(vault_password_file))
- if not os.path.exists(this_path):
- raise AnsibleFileNotFound("The vault password file %s was not found" % this_path)
-
- if self.is_executable(this_path):
- try:
- # STDERR not captured to make it easier for users to prompt for input in their scripts
- p = subprocess.Popen(this_path, stdout=subprocess.PIPE)
- except OSError as e:
- raise AnsibleError("Problem running vault password script %s (%s). If this is not a script, remove the executable bit from the file." % (' '.join(this_path), e))
- stdout, stderr = p.communicate()
- self.set_vault_password(stdout.strip('\r\n'))
- else:
- try:
- f = open(this_path, "rb")
- self.set_vault_password(f.read().strip())
- f.close()
- except (OSError, IOError) as e:
- raise AnsibleError("Could not read vault password file %s: %s" % (this_path, e))
-
diff --git a/lib/ansible/parsing/dataloader.py b/lib/ansible/parsing/dataloader.py
new file mode 100644
index 0000000000..aaa878bb5f
--- /dev/null
+++ b/lib/ansible/parsing/dataloader.py
@@ -0,0 +1,294 @@
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import copy
+import json
+import os
+import stat
+import subprocess
+
+from yaml import load, YAMLError
+from ansible.compat.six import text_type, string_types
+
+from ansible.errors import AnsibleFileNotFound, AnsibleParserError, AnsibleError
+from ansible.errors.yaml_strings import YAML_SYNTAX_ERROR
+from ansible.parsing.vault import VaultLib
+from ansible.parsing.quoting import unquote
+from ansible.parsing.yaml.loader import AnsibleLoader
+from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleUnicode
+from ansible.module_utils.basic import is_executable
+from ansible.utils.path import unfrackpath
+from ansible.utils.unicode import to_unicode
+
+class DataLoader():
+
+ '''
+ The DataLoader class is used to load and parse YAML or JSON content,
+ either from a given file name or from a string that was previously
+ read in through other means. A Vault password can be specified, and
+ any vault-encrypted files will be decrypted.
+
+ Data read from files will also be cached, so the file will never be
+ read from disk more than once.
+
+ Usage:
+
+ dl = DataLoader()
+ (or)
+ dl = DataLoader(vault_password='foo')
+
+ ds = dl.load('...')
+ ds = dl.load_from_file('/path/to/file')
+ '''
+
+ def __init__(self):
+ self._basedir = '.'
+ self._FILE_CACHE = dict()
+
+ # initialize the vault stuff with an empty password
+ self.set_vault_password(None)
+
+ def set_vault_password(self, vault_password):
+ self._vault_password = vault_password
+ self._vault = VaultLib(password=vault_password)
+
+ def load(self, data, file_name='<string>', show_content=True):
+ '''
+ Creates a python datastructure from the given data, which can be either
+ a JSON or YAML string.
+ '''
+
+ try:
+ # we first try to load this data as JSON
+ return json.loads(data)
+ except:
+ # if loading JSON failed for any reason, we go ahead
+ # and try to parse it as YAML instead
+
+ if isinstance(data, AnsibleUnicode):
+ # The PyYAML's libyaml bindings use PyUnicode_CheckExact so
+ # they are unable to cope with our subclass.
+ # Unwrap and re-wrap the unicode so we can keep track of line
+ # numbers
+ new_data = text_type(data)
+ else:
+ new_data = data
+ try:
+ new_data = self._safe_load(new_data, file_name=file_name)
+ except YAMLError as yaml_exc:
+ self._handle_error(yaml_exc, file_name, show_content)
+
+ if isinstance(data, AnsibleUnicode):
+ new_data = AnsibleUnicode(new_data)
+ new_data.ansible_pos = data.ansible_pos
+ return new_data
+
+ def load_from_file(self, file_name):
+ ''' Loads data from a file, which can contain either JSON or YAML. '''
+
+ file_name = self.path_dwim(file_name)
+
+ # if the file has already been read in and cached, we'll
+ # return those results to avoid more file/vault operations
+ if file_name in self._FILE_CACHE:
+ parsed_data = self._FILE_CACHE[file_name]
+ else:
+ # read the file contents and load the data structure from them
+ (file_data, show_content) = self._get_file_contents(file_name)
+ parsed_data = self.load(data=file_data, file_name=file_name, show_content=show_content)
+
+ # cache the file contents for next time
+ self._FILE_CACHE[file_name] = parsed_data
+
+ # return a deep copy here, so the cache is not affected
+ return copy.deepcopy(parsed_data)
+
+ def path_exists(self, path):
+ path = self.path_dwim(path)
+ return os.path.exists(path)
+
+ def is_file(self, path):
+ path = self.path_dwim(path)
+ return os.path.isfile(path) or path == os.devnull
+
+ def is_directory(self, path):
+ path = self.path_dwim(path)
+ return os.path.isdir(path)
+
+ def list_directory(self, path):
+ path = self.path_dwim(path)
+ return os.listdir(path)
+
+ def is_executable(self, path):
+ '''is the given path executable?'''
+ path = self.path_dwim(path)
+ return is_executable(path)
+
+ def _safe_load(self, stream, file_name=None):
+ ''' Implements yaml.safe_load(), except using our custom loader class. '''
+
+ loader = AnsibleLoader(stream, file_name)
+ try:
+ return loader.get_single_data()
+ finally:
+ loader.dispose()
+
+ def _get_file_contents(self, file_name):
+ '''
+ Reads the file contents from the given file name, and will decrypt them
+ if they are found to be vault-encrypted.
+ '''
+ if not file_name or not isinstance(file_name, string_types):
+ raise AnsibleParserError("Invalid filename: '%s'" % str(file_name))
+
+ if not self.path_exists(file_name) or not self.is_file(file_name):
+ raise AnsibleFileNotFound("the file_name '%s' does not exist, or is not readable" % file_name)
+
+ show_content = True
+ try:
+ with open(file_name, 'rb') as f:
+ data = f.read()
+ if self._vault.is_encrypted(data):
+ data = self._vault.decrypt(data)
+ show_content = False
+
+ data = to_unicode(data, errors='strict')
+ return (data, show_content)
+
+ except (IOError, OSError) as e:
+ raise AnsibleParserError("an error occurred while trying to read the file '%s': %s" % (file_name, str(e)))
+
+ def _handle_error(self, yaml_exc, file_name, show_content):
+ '''
+ Optionally constructs an object (AnsibleBaseYAMLObject) to encapsulate the
+ file name/position where a YAML exception occurred, and raises an AnsibleParserError
+ to display the syntax exception information.
+ '''
+
+ # if the YAML exception contains a problem mark, use it to construct
+ # an object the error class can use to display the faulty line
+ err_obj = None
+ if hasattr(yaml_exc, 'problem_mark'):
+ err_obj = AnsibleBaseYAMLObject()
+ err_obj.ansible_pos = (file_name, yaml_exc.problem_mark.line + 1, yaml_exc.problem_mark.column + 1)
+
+ raise AnsibleParserError(YAML_SYNTAX_ERROR, obj=err_obj, show_content=show_content)
+
+ def get_basedir(self):
+ ''' returns the current basedir '''
+ return self._basedir
+
+ def set_basedir(self, basedir):
+ ''' sets the base directory, used to find files when a relative path is given '''
+
+ if basedir is not None:
+ self._basedir = to_unicode(basedir)
+
+ def path_dwim(self, given):
+ '''
+ make relative paths work like folks expect.
+ '''
+
+ given = unquote(given)
+
+ if given.startswith("/"):
+ return os.path.abspath(given)
+ elif given.startswith("~"):
+ return os.path.abspath(os.path.expanduser(given))
+ else:
+ return os.path.abspath(os.path.join(self._basedir, given))
+
+ def path_dwim_relative(self, path, dirname, source):
+ '''
+ find one file in either a role or playbook dir with or without
+ explicitly named dirname subdirs
+
+ Used in action plugins and lookups to find supplemental files that
+ could be in either place.
+ '''
+
+ search = []
+ isrole = False
+
+ # I have full path, nothing else needs to be looked at
+ if source.startswith('~') or source.startswith('/'):
+ search.append(self.path_dwim(source))
+ else:
+ # base role/play path + templates/files/vars + relative filename
+ search.append(os.path.join(path, dirname, source))
+
+ basedir = unfrackpath(path)
+
+ # is it a role and if so make sure you get correct base path
+ if path.endswith('tasks') and os.path.exists(os.path.join(path,'main.yml')) \
+ or os.path.exists(os.path.join(path,'tasks/main.yml')):
+ isrole = True
+ if path.endswith('tasks'):
+ basedir = unfrackpath(os.path.dirname(path))
+
+ cur_basedir = self._basedir
+ self.set_basedir(basedir)
+ # resolved base role/play path + templates/files/vars + relative filename
+ search.append(self.path_dwim(os.path.join(basedir, dirname, source)))
+ self.set_basedir(cur_basedir)
+
+ if isrole and not source.endswith(dirname):
+ # look in role's tasks dir w/o dirname
+ search.append(self.path_dwim(os.path.join(basedir, 'tasks', source)))
+
+ # try to create absolute path for loader basedir + templates/files/vars + filename
+ search.append(self.path_dwim(os.path.join(dirname,source)))
+ search.append(self.path_dwim(os.path.join(basedir, source)))
+
+ # try to create absolute path for loader basedir + filename
+ search.append(self.path_dwim(source))
+
+ for candidate in search:
+ if os.path.exists(candidate):
+ break
+
+ return candidate
+
+ def read_vault_password_file(self, vault_password_file):
+ """
+ Read a vault password from a file or if executable, execute the script and
+ retrieve password from STDOUT
+ """
+
+ this_path = os.path.realpath(os.path.expanduser(vault_password_file))
+ if not os.path.exists(this_path):
+ raise AnsibleFileNotFound("The vault password file %s was not found" % this_path)
+
+ if self.is_executable(this_path):
+ try:
+ # STDERR not captured to make it easier for users to prompt for input in their scripts
+ p = subprocess.Popen(this_path, stdout=subprocess.PIPE)
+ except OSError as e:
+ raise AnsibleError("Problem running vault password script %s (%s). If this is not a script, remove the executable bit from the file." % (' '.join(this_path), e))
+ stdout, stderr = p.communicate()
+ self.set_vault_password(stdout.strip('\r\n'))
+ else:
+ try:
+ f = open(this_path, "rb")
+ self.set_vault_password(f.read().strip())
+ f.close()
+ except (OSError, IOError) as e:
+ raise AnsibleError("Could not read vault password file %s: %s" % (this_path, e))
diff --git a/lib/ansible/parsing/quoting.py b/lib/ansible/parsing/quoting.py
new file mode 100644
index 0000000000..7b94f90653
--- /dev/null
+++ b/lib/ansible/parsing/quoting.py
@@ -0,0 +1,30 @@
+# (c) 2014 James Cammarata, <jcammarata@ansible.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+def is_quoted(data):
+ return len(data) > 1 and data[0] == data[-1] and data[0] in ('"', "'") and data[-2] != '\\'
+
+def unquote(data):
+ ''' removes first and last quotes from a string, if the string starts and ends with the same quotes '''
+ if is_quoted(data):
+ return data[1:-1]
+ return data
diff --git a/lib/ansible/parsing/splitter.py b/lib/ansible/parsing/splitter.py
index ef8fc89b00..3bbf6e7e9c 100644
--- a/lib/ansible/parsing/splitter.py
+++ b/lib/ansible/parsing/splitter.py
@@ -23,6 +23,7 @@ import re
import codecs
from ansible.errors import AnsibleError
+from ansible.parsing.quoting import unquote
# Decode escapes adapted from rspeer's answer here:
# http://stackoverflow.com/questions/4020539/process-escape-sequences-in-a-string-in-python
@@ -263,12 +264,3 @@ def split_args(args):
raise Exception("error while splitting arguments, either an unbalanced jinja2 block or quotes")
return params
-
-def is_quoted(data):
- return len(data) > 1 and data[0] == data[-1] and data[0] in ('"', "'") and data[-2] != '\\'
-
-def unquote(data):
- ''' removes first and last quotes from a string, if the string starts and ends with the same quotes '''
- if is_quoted(data):
- return data[1:-1]
- return data
diff --git a/lib/ansible/parsing/yaml/constructor.py b/lib/ansible/parsing/yaml/constructor.py
index d1a2a01bc2..541165a42f 100644
--- a/lib/ansible/parsing/yaml/constructor.py
+++ b/lib/ansible/parsing/yaml/constructor.py
@@ -19,9 +19,17 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from yaml.constructor import Constructor
+from yaml.constructor import Constructor, ConstructorError
+from yaml.nodes import MappingNode
from ansible.parsing.yaml.objects import AnsibleMapping, AnsibleSequence, AnsibleUnicode
+try:
+ from __main__ import display
+except ImportError:
+ from ansible.utils.display import Display
+ display = Display()
+
+
class AnsibleConstructor(Constructor):
def __init__(self, file_name=None):
self._ansible_file_name = file_name
@@ -35,10 +43,33 @@ class AnsibleConstructor(Constructor):
data.ansible_pos = self._node_position_info(node)
def construct_mapping(self, node, deep=False):
- ret = AnsibleMapping(super(Constructor, self).construct_mapping(node, deep))
- ret.ansible_pos = self._node_position_info(node)
-
- return ret
+ # This first part fixes a problem with pyyaml's handling of duplicate
+ # dict keys.
+ # from SafeConstructor
+ if not isinstance(node, MappingNode):
+ raise ConstructorError(None, None,
+ "expected a mapping node, but found %s" % node.id,
+ node.start_mark)
+ self.flatten_mapping(node)
+ mapping = AnsibleMapping()
+ for key_node, value_node in node.value:
+ key = self.construct_object(key_node, deep=deep)
+ try:
+ hash(key)
+ except TypeError as exc:
+ raise ConstructorError("while constructing a mapping", node.start_mark,
+ "found unacceptable key (%s)" % exc, key_node.start_mark)
+
+ if key in mapping:
+ display.warning('While constructing a mapping%s found a duplicate dict key (%s). Using last value only.' % (node.start_mark, key_node.start_mark))
+
+ value = self.construct_object(value_node, deep=deep)
+ mapping[key] = value
+
+ # Add our extra information to the returned value
+ mapping.ansible_pos = self._node_position_info(node)
+
+ return mapping
def construct_yaml_str(self, node):
# Override the default string handling function
diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py
index 37b3ee7503..888299e1d9 100644
--- a/lib/ansible/playbook/__init__.py
+++ b/lib/ansible/playbook/__init__.py
@@ -21,9 +21,7 @@ __metaclass__ = type
import os
-from ansible.errors import AnsibleError, AnsibleParserError
-from ansible.parsing import DataLoader
-from ansible.playbook.attribute import Attribute, FieldAttribute
+from ansible.errors import AnsibleParserError
from ansible.playbook.play import Play
from ansible.playbook.playbook_include import PlaybookInclude
from ansible.plugins import get_all_plugin_loaders
diff --git a/lib/ansible/playbook/base.py b/lib/ansible/playbook/base.py
index e8759c32a2..06681abe76 100644
--- a/lib/ansible/playbook/base.py
+++ b/lib/ansible/playbook/base.py
@@ -26,20 +26,16 @@ import uuid
from functools import partial
from inspect import getmembers
-from io import FileIO
from ansible.compat.six import iteritems, string_types, text_type
from jinja2.exceptions import UndefinedError
from ansible.errors import AnsibleParserError
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
from ansible.playbook.attribute import Attribute, FieldAttribute
-from ansible.template import Templar
from ansible.utils.boolean import boolean
-from ansible.utils.debug import debug
from ansible.utils.vars import combine_vars, isidentifier
-from ansible.template import template
class Base:
diff --git a/lib/ansible/vars/__init__.py b/lib/ansible/vars/__init__.py
index 7ce24f0b0a..a8972293d6 100644
--- a/lib/ansible/vars/__init__.py
+++ b/lib/ansible/vars/__init__.py
@@ -37,7 +37,6 @@ from ansible.cli import CLI
from ansible.compat.six import string_types
from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleFileNotFound
from ansible.inventory.host import Host
-from ansible.parsing import DataLoader
from ansible.plugins import lookup_loader
from ansible.plugins.cache import FactCache
from ansible.template import Templar
diff --git a/samples/multi_queues.py b/samples/multi_queues.py
index 673bb01de6..3b647071c7 100644
--- a/samples/multi_queues.py
+++ b/samples/multi_queues.py
@@ -13,7 +13,7 @@ from ansible.playbook.play_context import PlayContext
from ansible.playbook.task import Task
from ansible.executor.task_executor import TaskExecutor
from ansible.executor.task_result import TaskResult
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
from ansible.vars import VariableManager
from ansible.utils.debug import debug
diff --git a/test/units/mock/loader.py b/test/units/mock/loader.py
index db69b862a7..2027e183c1 100644
--- a/test/units/mock/loader.py
+++ b/test/units/mock/loader.py
@@ -22,7 +22,7 @@ __metaclass__ = type
import os
from ansible.errors import AnsibleParserError
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
class DictDataLoader(DataLoader):
diff --git a/test/units/parsing/test_data_loader.py b/test/units/parsing/test_data_loader.py
index d4d4d9a1d5..19e3107447 100644
--- a/test/units/parsing/test_data_loader.py
+++ b/test/units/parsing/test_data_loader.py
@@ -26,7 +26,7 @@ from ansible.compat.tests import unittest
from ansible.compat.tests.mock import patch, mock_open
from ansible.errors import AnsibleParserError
-from ansible.parsing import DataLoader
+from ansible.parsing.dataloader import DataLoader
from ansible.parsing.yaml.objects import AnsibleMapping
class TestDataLoader(unittest.TestCase):
diff --git a/test/units/parsing/test_unquote.py b/test/units/parsing/test_unquote.py
index afb11d4e23..4e5d632066 100644
--- a/test/units/parsing/test_unquote.py
+++ b/test/units/parsing/test_unquote.py
@@ -23,7 +23,7 @@ __metaclass__ = type
from nose import tools
from ansible.compat.tests import unittest
-from ansible.parsing.splitter import unquote
+from ansible.parsing.quoting import unquote
# Tests using nose's test generators cannot use unittest base class.