2011-09-26 09:18:36 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
2015-03-05 14:02:26 +00:00
|
|
|
# 2011-2015 Nico Schottelius (nico-cdist at schottelius.org)
|
2013-08-29 20:32:26 +00:00
|
|
|
# 2012-2013 Steven Armstrong (steven-cdist at armstrong.cc)
|
2014-01-27 15:19:01 +00:00
|
|
|
# 2014 Daniel Heule (hda at sfs.biz)
|
2011-09-26 09:18:36 +00:00
|
|
|
#
|
|
|
|
# This file is part of cdist.
|
|
|
|
#
|
|
|
|
# cdist 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.
|
|
|
|
#
|
|
|
|
# cdist 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 cdist. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
#
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import logging
|
|
|
|
import os
|
2012-06-04 12:11:34 +00:00
|
|
|
import sys
|
2011-09-26 09:18:36 +00:00
|
|
|
|
2011-09-26 22:49:12 +00:00
|
|
|
import cdist
|
2011-10-11 10:27:08 +00:00
|
|
|
from cdist import core
|
2017-08-11 13:04:26 +00:00
|
|
|
from cdist import flock
|
2011-09-26 09:21:04 +00:00
|
|
|
|
2016-07-05 18:44:24 +00:00
|
|
|
|
2013-08-19 11:34:29 +00:00
|
|
|
class MissingRequiredEnvironmentVariableError(cdist.Error):
|
|
|
|
def __init__(self, name):
|
|
|
|
self.name = name
|
2016-07-05 18:44:24 +00:00
|
|
|
self.message = ("Emulator requires the environment variable %s to be "
|
|
|
|
"setup" % self.name)
|
2013-08-19 11:34:29 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.message
|
|
|
|
|
2013-08-19 10:03:25 +00:00
|
|
|
|
2013-12-19 22:33:43 +00:00
|
|
|
class DefaultList(list):
|
|
|
|
"""Helper class to allow default values for optional_multiple parameters.
|
|
|
|
|
2016-07-05 18:44:24 +00:00
|
|
|
@see https://groups.google.com/forum/#!msg/comp.lang.python/sAUvkJEDpRc/RnRymrzJVDYJ
|
2013-12-19 22:33:43 +00:00
|
|
|
"""
|
|
|
|
def __copy__(self):
|
|
|
|
return []
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def create(cls, initial=None):
|
|
|
|
if initial:
|
2013-12-20 09:56:46 +00:00
|
|
|
return cls(initial.split('\n'))
|
2013-12-19 22:33:43 +00:00
|
|
|
|
|
|
|
|
2011-10-15 00:31:40 +00:00
|
|
|
class Emulator(object):
|
2012-11-19 11:04:07 +00:00
|
|
|
def __init__(self, argv, stdin=sys.stdin.buffer, env=os.environ):
|
2016-07-05 18:44:24 +00:00
|
|
|
self.argv = argv
|
|
|
|
self.stdin = stdin
|
|
|
|
self.env = env
|
2012-11-07 09:49:11 +00:00
|
|
|
|
2016-07-05 18:44:24 +00:00
|
|
|
self.object_id = ''
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2013-08-19 10:03:25 +00:00
|
|
|
try:
|
2016-07-05 18:44:24 +00:00
|
|
|
self.global_path = self.env['__global']
|
2016-08-14 20:09:36 +00:00
|
|
|
self.target_host = (
|
|
|
|
self.env['__target_host'],
|
|
|
|
self.env['__target_hostname'],
|
|
|
|
self.env['__target_fqdn']
|
|
|
|
)
|
2013-08-19 10:03:25 +00:00
|
|
|
|
2015-03-05 14:02:26 +00:00
|
|
|
# Internal variables
|
2016-07-05 18:44:24 +00:00
|
|
|
self.object_source = self.env['__cdist_manifest']
|
2013-08-19 10:03:25 +00:00
|
|
|
self.type_base_path = self.env['__cdist_type_base_path']
|
2016-07-05 18:44:24 +00:00
|
|
|
self.object_marker = self.env['__cdist_object_marker']
|
2012-02-12 00:27:25 +00:00
|
|
|
|
2013-08-19 11:34:29 +00:00
|
|
|
except KeyError as e:
|
|
|
|
raise MissingRequiredEnvironmentVariableError(e.args[0])
|
2011-10-18 11:32:36 +00:00
|
|
|
|
2011-10-15 00:31:40 +00:00
|
|
|
self.object_base_path = os.path.join(self.global_path, "object")
|
2014-01-17 22:35:02 +00:00
|
|
|
self.typeorder_path = os.path.join(self.global_path, "typeorder")
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2016-07-05 18:44:24 +00:00
|
|
|
self.type_name = os.path.basename(argv[0])
|
|
|
|
self.cdist_type = core.CdistType(self.type_base_path, self.type_name)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2016-10-08 09:40:32 +00:00
|
|
|
# If set then object alreay exists and this var holds existing
|
|
|
|
# requirements.
|
|
|
|
self._existing_reqs = None
|
|
|
|
|
2011-10-15 00:31:40 +00:00
|
|
|
self.__init_log()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""Emulate type commands (i.e. __file and co)"""
|
|
|
|
|
|
|
|
self.commandline()
|
2017-08-11 13:04:26 +00:00
|
|
|
self.init_object()
|
|
|
|
|
|
|
|
# locking for parallel execution
|
|
|
|
with flock.Flock(self.flock_path) as lock:
|
|
|
|
self.setup_object()
|
|
|
|
self.save_stdin()
|
|
|
|
self.record_requirements()
|
|
|
|
self.record_auto_requirements()
|
|
|
|
self.log.trace("Finished %s %s" % (
|
|
|
|
self.cdist_object.path, self.parameters))
|
2011-10-15 00:31:40 +00:00
|
|
|
|
|
|
|
def __init_log(self):
|
|
|
|
"""Setup logging facility"""
|
|
|
|
|
2017-08-11 13:04:26 +00:00
|
|
|
if '__cdist_loglevel' in self.env:
|
2017-08-12 21:02:31 +00:00
|
|
|
try:
|
|
|
|
level = int(self.env['__cdist_loglevel'])
|
|
|
|
except:
|
|
|
|
level = logging.WARNING
|
2011-10-15 00:31:40 +00:00
|
|
|
else:
|
2017-08-12 21:02:31 +00:00
|
|
|
level = logging.WARNING
|
2017-08-11 13:04:26 +00:00
|
|
|
logging.root.setLevel(level)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2016-08-10 21:56:56 +00:00
|
|
|
self.log = logging.getLogger(self.target_host[0])
|
2011-10-15 00:31:40 +00:00
|
|
|
|
|
|
|
def commandline(self):
|
|
|
|
"""Parse command line"""
|
|
|
|
|
2016-07-05 18:44:24 +00:00
|
|
|
parser = argparse.ArgumentParser(add_help=False,
|
|
|
|
argument_default=argparse.SUPPRESS)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2012-06-04 20:01:32 +00:00
|
|
|
for parameter in self.cdist_type.required_parameters:
|
|
|
|
argument = "--" + parameter
|
2016-07-05 18:44:24 +00:00
|
|
|
parser.add_argument(argument, dest=parameter, action='store',
|
|
|
|
required=True)
|
2012-06-04 20:01:32 +00:00
|
|
|
for parameter in self.cdist_type.required_multiple_parameters:
|
|
|
|
argument = "--" + parameter
|
2016-07-05 18:44:24 +00:00
|
|
|
parser.add_argument(argument, dest=parameter, action='append',
|
|
|
|
required=True)
|
2011-10-15 00:31:40 +00:00
|
|
|
for parameter in self.cdist_type.optional_parameters:
|
|
|
|
argument = "--" + parameter
|
2016-07-05 18:44:24 +00:00
|
|
|
default = self.cdist_type.parameter_defaults.get(parameter, None)
|
|
|
|
parser.add_argument(argument, dest=parameter, action='store',
|
|
|
|
required=False, default=default)
|
2012-06-04 20:01:32 +00:00
|
|
|
for parameter in self.cdist_type.optional_multiple_parameters:
|
2011-10-15 00:31:40 +00:00
|
|
|
argument = "--" + parameter
|
2016-07-05 18:44:24 +00:00
|
|
|
default = DefaultList.create(
|
|
|
|
self.cdist_type.parameter_defaults.get(
|
|
|
|
parameter, None))
|
|
|
|
parser.add_argument(argument, dest=parameter, action='append',
|
|
|
|
required=False, default=default)
|
2012-02-15 13:44:16 +00:00
|
|
|
for parameter in self.cdist_type.boolean_parameters:
|
|
|
|
argument = "--" + parameter
|
2016-07-05 18:44:24 +00:00
|
|
|
parser.add_argument(argument, dest=parameter,
|
|
|
|
action='store_const', const='')
|
2011-10-15 00:31:40 +00:00
|
|
|
|
|
|
|
# If not singleton support one positional parameter
|
|
|
|
if not self.cdist_type.is_singleton:
|
|
|
|
parser.add_argument("object_id", nargs=1)
|
|
|
|
|
|
|
|
# And finally parse/verify parameter
|
|
|
|
self.args = parser.parse_args(self.argv[1:])
|
2017-06-28 21:36:42 +00:00
|
|
|
self.log.trace('Args: %s' % self.args)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2017-08-11 13:04:26 +00:00
|
|
|
def init_object(self):
|
|
|
|
# Initialize object - and ensure it is not in args
|
2015-03-05 14:02:26 +00:00
|
|
|
if self.cdist_type.is_singleton:
|
2015-03-05 14:23:53 +00:00
|
|
|
self.object_id = ''
|
2015-03-05 14:02:26 +00:00
|
|
|
else:
|
2011-10-15 00:31:40 +00:00
|
|
|
self.object_id = self.args.object_id[0]
|
|
|
|
del self.args.object_id
|
2011-10-14 13:52:53 +00:00
|
|
|
|
2011-10-15 00:31:40 +00:00
|
|
|
# Instantiate the cdist object we are defining
|
2016-07-05 18:44:24 +00:00
|
|
|
self.cdist_object = core.CdistObject(
|
|
|
|
self.cdist_type, self.object_base_path, self.object_marker,
|
|
|
|
self.object_id)
|
2017-08-11 13:04:26 +00:00
|
|
|
lockfname = ('.' + self.cdist_type.name +
|
|
|
|
self.object_id + '_' +
|
|
|
|
self.object_marker + '.lock')
|
|
|
|
lockfname = lockfname.replace(os.sep, '_')
|
|
|
|
self.flock_path = os.path.join(self.object_base_path, lockfname)
|
2011-10-14 13:52:53 +00:00
|
|
|
|
2017-08-11 13:04:26 +00:00
|
|
|
def setup_object(self):
|
2011-10-15 00:31:40 +00:00
|
|
|
# Create object with given parameters
|
|
|
|
self.parameters = {}
|
2016-07-05 18:44:24 +00:00
|
|
|
for key, value in vars(self.args).items():
|
2011-10-15 00:31:40 +00:00
|
|
|
if value is not None:
|
|
|
|
self.parameters[key] = value
|
2011-10-18 11:32:36 +00:00
|
|
|
|
2016-07-05 18:44:24 +00:00
|
|
|
if self.cdist_object.exists and 'CDIST_OVERRIDE' not in self.env:
|
2016-10-08 09:40:32 +00:00
|
|
|
# Make existing requirements a set so that we can compare it
|
|
|
|
# later with new requirements.
|
|
|
|
self._existing_reqs = set(self.cdist_object.requirements)
|
2011-10-16 16:38:22 +00:00
|
|
|
if self.cdist_object.parameters != self.parameters:
|
2016-06-10 05:50:07 +00:00
|
|
|
errmsg = ("Object %s already exists with conflicting "
|
2016-07-05 18:44:24 +00:00
|
|
|
"parameters:\n%s: %s\n%s: %s" % (
|
|
|
|
self.cdist_object.name,
|
|
|
|
" ".join(self.cdist_object.source),
|
|
|
|
self.cdist_object.parameters,
|
|
|
|
self.object_source,
|
|
|
|
self.parameters))
|
2016-06-10 05:50:07 +00:00
|
|
|
self.log.error(errmsg)
|
|
|
|
raise cdist.Error(errmsg)
|
2011-10-15 00:31:40 +00:00
|
|
|
else:
|
2014-01-27 15:19:01 +00:00
|
|
|
if self.cdist_object.exists:
|
2016-07-05 18:44:24 +00:00
|
|
|
self.log.debug(('Object %s override forced with '
|
|
|
|
'CDIST_OVERRIDE'), self.cdist_object.name)
|
2014-01-31 16:56:55 +00:00
|
|
|
self.cdist_object.create(True)
|
2014-01-31 16:59:56 +00:00
|
|
|
else:
|
2014-01-31 16:56:55 +00:00
|
|
|
self.cdist_object.create()
|
2011-10-15 00:31:40 +00:00
|
|
|
self.cdist_object.parameters = self.parameters
|
2014-01-17 22:35:02 +00:00
|
|
|
# record the created object in typeorder file
|
2014-01-18 18:23:21 +00:00
|
|
|
with open(self.typeorder_path, 'a') as typeorderfile:
|
|
|
|
print(self.cdist_object.name, file=typeorderfile)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2012-02-13 06:47:33 +00:00
|
|
|
# Record / Append source
|
|
|
|
self.cdist_object.source.append(self.object_source)
|
|
|
|
|
2012-11-19 11:04:07 +00:00
|
|
|
chunk_size = 65536
|
2016-07-05 18:44:24 +00:00
|
|
|
|
2012-11-19 11:04:07 +00:00
|
|
|
def _read_stdin(self):
|
|
|
|
return self.stdin.read(self.chunk_size)
|
|
|
|
|
2012-06-04 12:11:34 +00:00
|
|
|
def save_stdin(self):
|
|
|
|
"""If something is written to stdin, save it in the object as
|
|
|
|
$__object/stdin so it can be accessed in manifest and gencode-*
|
|
|
|
scripts.
|
|
|
|
"""
|
2012-11-07 08:58:47 +00:00
|
|
|
if not self.stdin.isatty():
|
2012-06-04 12:11:34 +00:00
|
|
|
try:
|
|
|
|
# go directly to file instead of using CdistObject's api
|
|
|
|
# as that does not support streaming
|
|
|
|
path = os.path.join(self.cdist_object.absolute_path, 'stdin')
|
2012-11-19 11:04:07 +00:00
|
|
|
with open(path, 'wb') as fd:
|
|
|
|
chunk = self._read_stdin()
|
|
|
|
while chunk:
|
|
|
|
fd.write(chunk)
|
|
|
|
chunk = self._read_stdin()
|
2012-06-04 12:11:34 +00:00
|
|
|
except EnvironmentError as e:
|
|
|
|
raise cdist.Error('Failed to read from stdin: %s' % e)
|
|
|
|
|
2016-06-30 18:31:01 +00:00
|
|
|
def record_requirement(self, requirement):
|
|
|
|
"""record requirement and return recorded requirement"""
|
|
|
|
|
|
|
|
# Raises an error, if object cannot be created
|
|
|
|
try:
|
|
|
|
cdist_object = self.cdist_object.object_from_name(requirement)
|
|
|
|
except core.cdist_type.NoSuchTypeError as e:
|
|
|
|
self.log.error(("%s requires object %s, but type %s does not"
|
2016-07-05 18:44:24 +00:00
|
|
|
" exist. Defined at %s" % (
|
|
|
|
self.cdist_object.name,
|
|
|
|
requirement, e.name, self.object_source)))
|
2016-06-30 18:31:01 +00:00
|
|
|
raise
|
|
|
|
except core.cdist_object.MissingObjectIdError as e:
|
|
|
|
self.log.error(("%s requires object %s without object id."
|
2016-07-05 18:44:24 +00:00
|
|
|
" Defined at %s" % (self.cdist_object.name,
|
|
|
|
requirement,
|
|
|
|
self.object_source)))
|
2016-06-30 18:31:01 +00:00
|
|
|
raise
|
|
|
|
|
2017-06-28 21:43:39 +00:00
|
|
|
self.log.debug("Recording requirement: %s", requirement)
|
2016-06-30 18:31:01 +00:00
|
|
|
|
|
|
|
# Save the sanitised version, not the user supplied one
|
|
|
|
# (__file//bar => __file/bar)
|
|
|
|
# This ensures pattern matching is done against sanitised list
|
|
|
|
self.cdist_object.requirements.append(cdist_object.name)
|
|
|
|
|
|
|
|
return cdist_object.name
|
|
|
|
|
2011-10-15 00:31:40 +00:00
|
|
|
def record_requirements(self):
|
2016-10-08 09:40:32 +00:00
|
|
|
"""Record requirements."""
|
2014-01-18 18:23:21 +00:00
|
|
|
|
2016-07-05 18:44:24 +00:00
|
|
|
# Inject the predecessor, but not if its an override
|
|
|
|
# (this would leed to an circular dependency)
|
|
|
|
if ("CDIST_ORDER_DEPENDENCY" in self.env and
|
|
|
|
'CDIST_OVERRIDE' not in self.env):
|
2017-06-29 07:22:59 +00:00
|
|
|
# load object name created befor this one from typeorder file ...
|
2014-01-18 18:23:21 +00:00
|
|
|
with open(self.typeorder_path, 'r') as typecreationfile:
|
|
|
|
typecreationorder = typecreationfile.readlines()
|
2017-06-29 07:22:59 +00:00
|
|
|
# get the type created before this one ...
|
2014-01-18 18:23:21 +00:00
|
|
|
try:
|
|
|
|
lastcreatedtype = typecreationorder[-2].strip()
|
|
|
|
if 'require' in self.env:
|
|
|
|
self.env['require'] += " " + lastcreatedtype
|
|
|
|
else:
|
|
|
|
self.env['require'] = lastcreatedtype
|
2016-07-05 18:44:24 +00:00
|
|
|
self.log.debug(("Injecting require for "
|
|
|
|
"CDIST_ORDER_DEPENDENCY: %s for %s"),
|
|
|
|
lastcreatedtype, self.cdist_object.name)
|
2014-01-18 18:23:21 +00:00
|
|
|
except IndexError:
|
2016-07-05 18:44:24 +00:00
|
|
|
# if no second last line, we are on the first type,
|
|
|
|
# so do not set a requirement
|
2014-01-18 18:23:21 +00:00
|
|
|
pass
|
|
|
|
|
2016-10-08 09:40:32 +00:00
|
|
|
reqs = set()
|
2012-11-07 09:25:47 +00:00
|
|
|
if "require" in self.env:
|
|
|
|
requirements = self.env['require']
|
2012-11-19 14:17:46 +00:00
|
|
|
self.log.debug("reqs = " + requirements)
|
2011-10-15 00:31:40 +00:00
|
|
|
for requirement in requirements.split(" "):
|
2011-10-15 21:23:57 +00:00
|
|
|
# Ignore empty fields - probably the only field anyway
|
2016-07-05 18:44:24 +00:00
|
|
|
if len(requirement) == 0:
|
|
|
|
continue
|
2016-10-08 09:40:32 +00:00
|
|
|
object_name = self.record_requirement(requirement)
|
|
|
|
reqs.add(object_name)
|
|
|
|
if self._existing_reqs is not None:
|
|
|
|
# If object exists then compare existing and new requirements.
|
|
|
|
if self._existing_reqs != reqs:
|
|
|
|
warnmsg = ("Object {} already exists with requirements:\n"
|
|
|
|
"{}: {}\n"
|
|
|
|
"{}: {}\n"
|
|
|
|
"Dependency resolver could not handle dependencies "
|
|
|
|
"as expected.".format(
|
|
|
|
self.cdist_object.name,
|
|
|
|
" ".join(self.cdist_object.source),
|
|
|
|
self._existing_reqs,
|
|
|
|
self.object_source,
|
|
|
|
reqs
|
|
|
|
))
|
|
|
|
self.log.warning(warnmsg)
|
2016-06-10 05:50:07 +00:00
|
|
|
|
2011-11-02 22:58:18 +00:00
|
|
|
def record_auto_requirements(self):
|
2016-07-05 18:44:24 +00:00
|
|
|
"""An object shall automatically depend on all objects that it
|
|
|
|
defined in it's type manifest.
|
2011-11-02 22:58:18 +00:00
|
|
|
"""
|
2016-07-05 18:44:24 +00:00
|
|
|
# __object_name is the name of the object whose type manifest is
|
|
|
|
# currently executed
|
2012-11-07 09:25:47 +00:00
|
|
|
__object_name = self.env.get('__object_name', None)
|
2011-11-02 22:58:18 +00:00
|
|
|
if __object_name:
|
2012-04-25 15:18:16 +00:00
|
|
|
# The object whose type manifest is currently run
|
|
|
|
parent = self.cdist_object.object_from_name(__object_name)
|
|
|
|
# The object currently being defined
|
|
|
|
current_object = self.cdist_object
|
2016-07-05 18:44:24 +00:00
|
|
|
# As parent defined current_object it shall automatically
|
|
|
|
# depend on it.
|
2012-04-25 15:18:16 +00:00
|
|
|
# But only if the user hasn't said otherwise.
|
|
|
|
# Must prevent circular dependencies.
|
2016-07-05 18:44:24 +00:00
|
|
|
if parent.name not in current_object.requirements:
|
2012-05-03 08:16:08 +00:00
|
|
|
parent.autorequire.append(current_object.name)
|