mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 21:00:22 -07:00
Creating playbook executor and dependent classes
This commit is contained in:
parent
b6c3670f8a
commit
62d79568be
158 changed files with 22486 additions and 2353 deletions
|
@ -19,3 +19,203 @@
|
|||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from yaml import load, YAMLError
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
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
|
||||
|
||||
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, vault_password=None):
|
||||
self._basedir = '.'
|
||||
self._vault_password = vault_password
|
||||
self._FILE_CACHE = dict()
|
||||
|
||||
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:
|
||||
try:
|
||||
# if loading JSON failed for any reason, we go ahead
|
||||
# and try to parse it as YAML instead
|
||||
return self._safe_load(data, file_name=file_name)
|
||||
except YAMLError as yaml_exc:
|
||||
self._handle_error(yaml_exc, file_name, show_content)
|
||||
|
||||
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:
|
||||
return self._FILE_CACHE[file_name]
|
||||
|
||||
# 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 parsed_data
|
||||
|
||||
def path_exists(self, path):
|
||||
return os.path.exists(path)
|
||||
|
||||
def is_directory(self, path):
|
||||
return os.path.isdir(path)
|
||||
|
||||
def is_file(self, path):
|
||||
return os.path.isfile(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 self.path_exists(file_name) or not self.is_file(file_name):
|
||||
raise AnsibleParserError("the file_name '%s' does not exist, or is not readable" % file_name)
|
||||
|
||||
show_content = True
|
||||
try:
|
||||
with open(file_name, 'r') as f:
|
||||
data = f.read()
|
||||
if self._vault.is_encrypted(data):
|
||||
data = self._vault.decrypt(data)
|
||||
show_content = False
|
||||
return (data, show_content)
|
||||
except (IOError, OSError) as e:
|
||||
raise AnsibleParserError("an error occured 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 occured, 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.set_position_info(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 = 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, role_path, dirname, source):
|
||||
''' find one file in a directory one level up in a dir named dirname relative to current '''
|
||||
|
||||
basedir = os.path.dirname(role_path)
|
||||
if os.path.islink(basedir):
|
||||
# FIXME:
|
||||
#basedir = unfrackpath(basedir)
|
||||
template2 = os.path.join(basedir, dirname, source)
|
||||
else:
|
||||
template2 = os.path.join(basedir, '..', dirname, source)
|
||||
|
||||
source1 = os.path.join(role_path, dirname, source)
|
||||
if os.path.exists(source1):
|
||||
return source1
|
||||
|
||||
cur_basedir = self._basedir
|
||||
self.set_basedir(basedir)
|
||||
source2 = self.path_dwim(template2)
|
||||
if os.path.exists(source2):
|
||||
self.set_basedir(cur_basedir)
|
||||
return source2
|
||||
|
||||
obvious_local_path = self.path_dwim(source)
|
||||
if os.path.exists(obvious_local_path):
|
||||
self.set_basedir(cur_basedir)
|
||||
return obvious_local_path
|
||||
|
||||
self.set_basedir(cur_basedir)
|
||||
return source2 # which does not exist
|
||||
|
||||
#def __getstate__(self):
|
||||
# data = dict(
|
||||
# basedir = self._basedir,
|
||||
# vault_password = self._vault_password,
|
||||
# FILE_CACHE = self._FILE_CACHE,
|
||||
# )
|
||||
# return data
|
||||
|
||||
#def __setstate__(self, data):
|
||||
# self._basedir = data.get('basedir', '.')
|
||||
# self._FILE_CACHE = data.get('FILE_CACHE', dict())
|
||||
# self._vault_password = data.get('vault_password', '')
|
||||
#
|
||||
# self._vault = VaultLib(password=self._vault_password)
|
||||
|
||||
|
|
|
@ -20,9 +20,10 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
from six import iteritems, string_types
|
||||
from types import NoneType
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
from ansible.plugins import module_finder
|
||||
from ansible.plugins import module_loader
|
||||
from ansible.parsing.splitter import parse_kv
|
||||
|
||||
class ModuleArgsParser:
|
||||
|
@ -120,7 +121,7 @@ class ModuleArgsParser:
|
|||
(action, args) = self._normalize_new_style_args(thing)
|
||||
|
||||
# this can occasionally happen, simplify
|
||||
if 'args' in args:
|
||||
if args and 'args' in args:
|
||||
args = args['args']
|
||||
|
||||
return (action, args)
|
||||
|
@ -144,8 +145,11 @@ class ModuleArgsParser:
|
|||
elif isinstance(thing, string_types):
|
||||
# form is like: local_action: copy src=a dest=b ... pretty common
|
||||
args = parse_kv(thing)
|
||||
elif isinstance(thing, NoneType):
|
||||
# this can happen with modules which take no params, like ping:
|
||||
args = None
|
||||
else:
|
||||
raise AnsibleParsingError("unexpected parameter type in action: %s" % type(thing), obj=self._task_ds)
|
||||
raise AnsibleParserError("unexpected parameter type in action: %s" % type(thing), obj=self._task_ds)
|
||||
return args
|
||||
|
||||
def _normalize_new_style_args(self, thing):
|
||||
|
@ -180,7 +184,7 @@ class ModuleArgsParser:
|
|||
|
||||
else:
|
||||
# need a dict or a string, so giving up
|
||||
raise AnsibleParsingError("unexpected parameter type in action: %s" % type(thing), obj=self._task_ds)
|
||||
raise AnsibleParserError("unexpected parameter type in action: %s" % type(thing), obj=self._task_ds)
|
||||
|
||||
return (action, args)
|
||||
|
||||
|
@ -224,7 +228,7 @@ class ModuleArgsParser:
|
|||
|
||||
# walk the input dictionary to see we recognize a module name
|
||||
for (item, value) in iteritems(self._task_ds):
|
||||
if item in module_finder:
|
||||
if item in module_loader:
|
||||
# finding more than one module name is a problem
|
||||
if action is not None:
|
||||
raise AnsibleParserError("conflicting action statements", obj=self._task_ds)
|
||||
|
|
21
v2/ansible/parsing/utils/__init__.py
Normal file
21
v2/ansible/parsing/utils/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# (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
|
||||
|
26
v2/ansible/parsing/utils/jsonify.py
Normal file
26
v2/ansible/parsing/utils/jsonify.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# FIXME: header
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
def jsonify(result, format=False):
|
||||
''' format JSON output (uncompressed or uncompressed) '''
|
||||
|
||||
if result is None:
|
||||
return "{}"
|
||||
result2 = result.copy()
|
||||
for key, value in result2.items():
|
||||
if type(value) is str:
|
||||
result2[key] = value.decode('utf-8', 'ignore')
|
||||
|
||||
indent = None
|
||||
if format:
|
||||
indent = 4
|
||||
|
||||
try:
|
||||
return json.dumps(result2, sort_keys=True, indent=indent, ensure_ascii=False)
|
||||
except UnicodeDecodeError:
|
||||
return json.dumps(result2, sort_keys=True, indent=indent)
|
||||
|
|
@ -19,156 +19,3 @@
|
|||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from yaml import load, YAMLError
|
||||
|
||||
from ansible.errors import AnsibleParserError
|
||||
|
||||
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
|
||||
from ansible.parsing.yaml.strings import YAML_SYNTAX_ERROR
|
||||
|
||||
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')
|
||||
'''
|
||||
|
||||
_FILE_CACHE = dict()
|
||||
|
||||
def __init__(self, vault_password=None):
|
||||
self._basedir = '.'
|
||||
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:
|
||||
try:
|
||||
# if loading JSON failed for any reason, we go ahead
|
||||
# and try to parse it as YAML instead
|
||||
return self._safe_load(data, file_name=file_name)
|
||||
except YAMLError as yaml_exc:
|
||||
self._handle_error(yaml_exc, file_name, show_content)
|
||||
|
||||
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:
|
||||
return self._FILE_CACHE[file_name]
|
||||
|
||||
# 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 parsed_data
|
||||
|
||||
def path_exists(self, path):
|
||||
return os.path.exists(path)
|
||||
|
||||
def is_directory(self, path):
|
||||
return os.path.isdir(path)
|
||||
|
||||
def is_file(self, path):
|
||||
return os.path.isfile(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 self.path_exists(file_name) or not self.is_file(file_name):
|
||||
raise AnsibleParserError("the file_name '%s' does not exist, or is not readable" % file_name)
|
||||
|
||||
show_content = True
|
||||
try:
|
||||
with open(file_name, 'r') as f:
|
||||
data = f.read()
|
||||
if self._vault.is_encrypted(data):
|
||||
data = self._vault.decrypt(data)
|
||||
show_content = False
|
||||
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.set_position_info(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 = 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))
|
||||
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
# (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
|
||||
|
||||
__all__ = [
|
||||
'YAML_SYNTAX_ERROR',
|
||||
'YAML_POSITION_DETAILS',
|
||||
'YAML_COMMON_DICT_ERROR',
|
||||
'YAML_COMMON_UNQUOTED_VARIABLE_ERROR',
|
||||
'YAML_COMMON_UNQUOTED_COLON_ERROR',
|
||||
'YAML_COMMON_PARTIALLY_QUOTED_LINE_ERROR',
|
||||
'YAML_COMMON_UNBALANCED_QUOTES_ERROR',
|
||||
]
|
||||
|
||||
YAML_SYNTAX_ERROR = """\
|
||||
Syntax Error while loading YAML.
|
||||
"""
|
||||
|
||||
YAML_POSITION_DETAILS = """\
|
||||
The error appears to have been in '%s': line %s, column %s, but may
|
||||
be elsewhere in the file depending on the exact syntax problem.
|
||||
"""
|
||||
|
||||
YAML_COMMON_DICT_ERROR = """\
|
||||
This one looks easy to fix. YAML thought it was looking for the start of a
|
||||
hash/dictionary and was confused to see a second "{". Most likely this was
|
||||
meant to be an ansible template evaluation instead, so we have to give the
|
||||
parser a small hint that we wanted a string instead. The solution here is to
|
||||
just quote the entire value.
|
||||
|
||||
For instance, if the original line was:
|
||||
|
||||
app_path: {{ base_path }}/foo
|
||||
|
||||
It should be written as:
|
||||
|
||||
app_path: "{{ base_path }}/foo"
|
||||
"""
|
||||
|
||||
YAML_COMMON_UNQUOTED_VARIABLE_ERROR = """\
|
||||
We could be wrong, but this one looks like it might be an issue with
|
||||
missing quotes. Always quote template expression brackets when they
|
||||
start a value. For instance:
|
||||
|
||||
with_items:
|
||||
- {{ foo }}
|
||||
|
||||
Should be written as:
|
||||
|
||||
with_items:
|
||||
- "{{ foo }}"
|
||||
"""
|
||||
|
||||
YAML_COMMON_UNQUOTED_COLON_ERROR = """\
|
||||
This one looks easy to fix. There seems to be an extra unquoted colon in the line
|
||||
and this is confusing the parser. It was only expecting to find one free
|
||||
colon. The solution is just add some quotes around the colon, or quote the
|
||||
entire line after the first colon.
|
||||
|
||||
For instance, if the original line was:
|
||||
|
||||
copy: src=file.txt dest=/path/filename:with_colon.txt
|
||||
|
||||
It can be written as:
|
||||
|
||||
copy: src=file.txt dest='/path/filename:with_colon.txt'
|
||||
|
||||
Or:
|
||||
|
||||
copy: 'src=file.txt dest=/path/filename:with_colon.txt'
|
||||
"""
|
||||
|
||||
YAML_COMMON_PARTIALLY_QUOTED_LINE_ERROR = """\
|
||||
This one looks easy to fix. It seems that there is a value started
|
||||
with a quote, and the YAML parser is expecting to see the line ended
|
||||
with the same kind of quote. For instance:
|
||||
|
||||
when: "ok" in result.stdout
|
||||
|
||||
Could be written as:
|
||||
|
||||
when: '"ok" in result.stdout'
|
||||
|
||||
Or equivalently:
|
||||
|
||||
when: "'ok' in result.stdout"
|
||||
"""
|
||||
|
||||
YAML_COMMON_UNBALANCED_QUOTES_ERROR = """\
|
||||
We could be wrong, but this one looks like it might be an issue with
|
||||
unbalanced quotes. If starting a value with a quote, make sure the
|
||||
line ends with the same set of quotes. For instance this arbitrary
|
||||
example:
|
||||
|
||||
foo: "bad" "wolf"
|
||||
|
||||
Could be written as:
|
||||
|
||||
foo: '"bad" "wolf"'
|
||||
"""
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue