2011-09-26 09:18:36 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
2013-08-28 13:39:17 +00:00
|
|
|
# 2011-2013 Nico Schottelius (nico-cdist at schottelius.org)
|
2012-06-04 12:11:34 +00:00
|
|
|
# 2012 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
|
2011-09-26 09:21:04 +00:00
|
|
|
|
2013-08-19 11:34:29 +00:00
|
|
|
class MissingRequiredEnvironmentVariableError(cdist.Error):
|
|
|
|
def __init__(self, name):
|
|
|
|
self.name = name
|
|
|
|
self.message = "Emulator requires the environment variable %s to be setup" % self.name
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
@see https://groups.google.com/forum/#!msg/comp.lang.python/sAUvkJEDpRc/RnRymrzJVDYJ
|
|
|
|
"""
|
|
|
|
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):
|
2011-10-15 00:31:40 +00:00
|
|
|
self.argv = argv
|
2012-11-07 08:58:47 +00:00
|
|
|
self.stdin = stdin
|
2012-11-07 09:49:11 +00:00
|
|
|
self.env = env
|
|
|
|
|
2013-07-10 14:31:58 +00:00
|
|
|
self.object_id = ''
|
2011-10-15 00:31:40 +00:00
|
|
|
|
2013-08-19 10:03:25 +00:00
|
|
|
try:
|
|
|
|
self.global_path = self.env['__global']
|
|
|
|
self.target_host = self.env['__target_host']
|
|
|
|
|
|
|
|
# Internally only
|
|
|
|
self.object_source = self.env['__cdist_manifest']
|
|
|
|
self.type_base_path = self.env['__cdist_type_base_path']
|
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")
|
|
|
|
|
|
|
|
self.type_name = os.path.basename(argv[0])
|
2012-02-12 00:27:25 +00:00
|
|
|
self.cdist_type = core.CdistType(self.type_base_path, self.type_name)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
|
|
|
self.__init_log()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""Emulate type commands (i.e. __file and co)"""
|
|
|
|
|
|
|
|
self.commandline()
|
|
|
|
self.setup_object()
|
2012-06-04 12:11:34 +00:00
|
|
|
self.save_stdin()
|
2011-10-15 00:31:40 +00:00
|
|
|
self.record_requirements()
|
2011-11-02 22:58:18 +00:00
|
|
|
self.record_auto_requirements()
|
2011-10-15 00:31:40 +00:00
|
|
|
self.log.debug("Finished %s %s" % (self.cdist_object.path, self.parameters))
|
|
|
|
|
|
|
|
def __init_log(self):
|
|
|
|
"""Setup logging facility"""
|
|
|
|
|
2012-11-07 09:25:47 +00:00
|
|
|
if '__cdist_debug' in self.env:
|
2011-10-15 00:31:40 +00:00
|
|
|
logging.root.setLevel(logging.DEBUG)
|
|
|
|
else:
|
|
|
|
logging.root.setLevel(logging.INFO)
|
|
|
|
|
2013-09-02 08:49:11 +00:00
|
|
|
self.log = logging.getLogger(self.target_host)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
|
|
|
def commandline(self):
|
|
|
|
"""Parse command line"""
|
|
|
|
|
2012-11-19 14:17:46 +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
|
|
|
|
parser.add_argument(argument, dest=parameter, action='store', required=True)
|
|
|
|
for parameter in self.cdist_type.required_multiple_parameters:
|
|
|
|
argument = "--" + parameter
|
|
|
|
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
|
2013-09-04 20:11:42 +00:00
|
|
|
parser.add_argument(argument, dest=parameter, action='store', required=False,
|
|
|
|
default=self.cdist_type.parameter_defaults.get(parameter, None))
|
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
|
2013-09-04 20:11:42 +00:00
|
|
|
parser.add_argument(argument, dest=parameter, action='append', required=False,
|
2013-12-19 22:33:43 +00:00
|
|
|
default=DefaultList.create(self.cdist_type.parameter_defaults.get(parameter, None)))
|
2012-02-15 13:44:16 +00:00
|
|
|
for parameter in self.cdist_type.boolean_parameters:
|
|
|
|
argument = "--" + parameter
|
|
|
|
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:])
|
2011-10-15 00:36:33 +00:00
|
|
|
self.log.debug('Args: %s' % self.args)
|
2011-10-15 00:31:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
def setup_object(self):
|
2012-02-13 06:45:10 +00:00
|
|
|
# Setup object_id - FIXME: unset / do not setup anymore!
|
2013-07-10 14:31:58 +00:00
|
|
|
if not self.cdist_type.is_singleton:
|
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
|
2012-02-11 23:08:18 +00:00
|
|
|
self.cdist_object = core.CdistObject(self.cdist_type, self.object_base_path, self.object_id)
|
2011-10-14 13:52:53 +00:00
|
|
|
|
2011-10-15 00:31:40 +00:00
|
|
|
# Create object with given parameters
|
|
|
|
self.parameters = {}
|
|
|
|
for key,value in vars(self.args).items():
|
|
|
|
if value is not None:
|
|
|
|
self.parameters[key] = value
|
2011-10-18 11:32:36 +00:00
|
|
|
|
2014-02-05 20:09:24 +00:00
|
|
|
if self.cdist_object.exists and not 'CDIST_ALLOW_OVERRIDE' in os.environ:
|
2011-10-16 16:38:22 +00:00
|
|
|
if self.cdist_object.parameters != self.parameters:
|
2011-10-15 00:31:40 +00:00
|
|
|
raise cdist.Error("Object %s already exists with conflicting parameters:\n%s: %s\n%s: %s"
|
2012-02-15 08:27:11 +00:00
|
|
|
% (self.cdist_object.name, " ".join(self.cdist_object.source), self.cdist_object.parameters, self.object_source, self.parameters)
|
2011-10-11 10:27:08 +00:00
|
|
|
)
|
2011-10-15 00:31:40 +00:00
|
|
|
else:
|
2014-01-27 15:19:01 +00:00
|
|
|
if self.cdist_object.exists:
|
2014-02-05 20:09:24 +00:00
|
|
|
self.log.debug('Object %s override forced with CDIST_ALLOW_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
|
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
|
2011-10-15 00:31:40 +00:00
|
|
|
def record_requirements(self):
|
|
|
|
"""record requirements"""
|
|
|
|
|
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
|
2012-02-13 07:44:09 +00:00
|
|
|
if len(requirement) == 0: continue
|
2011-10-15 21:23:57 +00:00
|
|
|
|
2012-02-13 07:44:09 +00:00
|
|
|
# Raises an error, if object cannot be created
|
2013-05-27 14:36:20 +00:00
|
|
|
try:
|
|
|
|
cdist_object = self.cdist_object.object_from_name(requirement)
|
2013-09-02 09:30:22 +00:00
|
|
|
except core.cdist_type.NoSuchTypeError as e:
|
2014-01-19 22:28:45 +00:00
|
|
|
self.log.error("%s requires object %s, but type %s does not exist. Defined at %s" % (self.cdist_object.name, requirement, e.name, self.object_source))
|
2013-05-27 14:36:20 +00:00
|
|
|
raise
|
2014-01-19 22:22:48 +00:00
|
|
|
except core.cdist_object.MissingObjectIdError as e:
|
2014-01-19 22:28:45 +00:00
|
|
|
self.log.error("%s requires object %s without object id. Defined at %s" % (self.cdist_object.name, requirement, self.object_source))
|
2014-01-19 22:22:48 +00:00
|
|
|
raise
|
2012-02-13 09:47:40 +00:00
|
|
|
|
2012-01-19 06:51:02 +00:00
|
|
|
self.log.debug("Recording requirement: " + requirement)
|
2012-11-19 14:17:46 +00:00
|
|
|
|
2012-02-14 10:12:17 +00:00
|
|
|
# Save the sanitised version, not the user supplied one
|
|
|
|
# (__file//bar => __file/bar)
|
|
|
|
# This ensures pattern matching is done against sanitised list
|
2012-11-19 14:17:46 +00:00
|
|
|
self.cdist_object.requirements.append(cdist_object.name)
|
2011-09-26 09:18:36 +00:00
|
|
|
|
2011-11-02 22:58:18 +00:00
|
|
|
def record_auto_requirements(self):
|
|
|
|
"""An object shall automatically depend on all objects that it defined in it's type manifest.
|
|
|
|
"""
|
2012-04-25 15:18:16 +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
|
|
|
|
# As parent defined current_object it shall automatically depend on it.
|
|
|
|
# But only if the user hasn't said otherwise.
|
|
|
|
# Must prevent circular dependencies.
|
|
|
|
if not parent.name in current_object.requirements:
|
2012-05-03 08:16:08 +00:00
|
|
|
parent.autorequire.append(current_object.name)
|