# -*- coding: utf-8 -*- # # 2011-2013 Steven Armstrong (steven-cdist at armstrong.cc) # 2011-2013 Nico Schottelius (nico-cdist at schottelius.org) # # 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 . # # import logging import os import importlib.util import inspect import cdist import cdist.emulator from . import util from cdist.core.pytypes import PythonType, Command ''' common: runs only locally, does not need remote env: PATH: prepend directory with type emulator symlinks == local.bin_path __target_host: the target host we are working on __target_hostname: the target hostname provided from __target_host __target_fqdn: the target's fully qualified domain name provided from __target_host __global: full qualified path to the global output dir == local.out_path __cdist_manifest: full qualified path of the manifest == script __cdist_type_base_path: full qualified path to the directory where types are defined for use in type emulator == local.type_path __files: full qualified path to the files dir __target_host_tags: comma spearated list of host tags initial manifest is: script: full qualified path to the initial manifest env: __manifest: path to .../conf/manifest/ == local.manifest_path creates: new objects through type emulator type manifeste is: script: full qualified path to the type manifest env: __object: full qualified path to the object's dir __object_id: the objects id __object_fq: full qualified object id, iow: $type.name + / + object_id __type: full qualified path to the type's dir creates: new objects through type emulator ''' class NoInitialManifestError(cdist.Error): """ Display missing initial manifest: - Display path if user given - try to resolve link if it is a link - Omit path if default (is a linked path in temp directory without much help) """ def __init__(self, manifest_path, user_supplied): msg_header = "Initial manifest missing" if user_supplied: if os.path.islink(manifest_path): self.message = "%s: %s -> %s" % ( msg_header, manifest_path, os.path.realpath(manifest_path)) else: self.message = "%s: %s" % (msg_header, manifest_path) else: self.message = "%s" % (msg_header) def __str__(self): return repr(self.message) class Manifest(object): """Executes cdist manifests. """ def __init__(self, target_host, local, dry_run=False): self.target_host = target_host self.local = local self._open_logger() self.env = { 'PATH': "%s:%s" % (self.local.bin_path, os.environ['PATH']), # for use in type emulator '__cdist_type_base_path': self.local.type_path, '__global': self.local.base_path, '__target_host': self.target_host[0], '__target_hostname': self.target_host[1], '__target_fqdn': self.target_host[2], '__files': self.local.files_path, '__target_host_tags': self.local.target_host_tags, '__cdist_log_level': util.log_level_env_var_val(self.log), '__cdist_log_level_name': util.log_level_name_env_var_val( self.log), } if dry_run: self.env['__cdist_dry_run'] = '1' def _open_logger(self): self.log = logging.getLogger(self.target_host[0]) # logger is not pickable, so remove it when we pickle def __getstate__(self): state = self.__dict__.copy() if 'log' in state: del state['log'] return state # recreate logger when we unpickle def __setstate__(self, state): self.__dict__.update(state) self._open_logger() def env_initial_manifest(self, initial_manifest): env = os.environ.copy() env.update(self.env) env['__cdist_manifest'] = initial_manifest env['__manifest'] = self.local.manifest_path env['__explorer'] = self.local.global_explorer_out_path return env def run_initial_manifest(self, initial_manifest=None): if not initial_manifest: initial_manifest = self.local.initial_manifest user_supplied = False else: user_supplied = True if not os.path.isfile(initial_manifest): raise NoInitialManifestError(initial_manifest, user_supplied) message_prefix = "initialmanifest" self.log.verbose("Running initial manifest " + initial_manifest) which = "init" if self.local.save_output_streams: stderr_path = os.path.join(self.local.stderr_base_path, which) stdout_path = os.path.join(self.local.stdout_base_path, which) with open(stderr_path, 'ba+') as stderr, \ open(stdout_path, 'ba+') as stdout: self.local.run_script( initial_manifest, env=self.env_initial_manifest(initial_manifest), message_prefix=message_prefix, stdout=stdout, stderr=stderr) else: self.local.run_script( initial_manifest, env=self.env_initial_manifest(initial_manifest), message_prefix=message_prefix) def env_type_manifest(self, cdist_object): type_manifest = os.path.join(self.local.type_path, cdist_object.cdist_type.manifest_path) env = os.environ.copy() env.update(self.env) env.update({ '__cdist_manifest': type_manifest, '__manifest': self.local.manifest_path, '__object': cdist_object.absolute_path, '__object_id': cdist_object.object_id, '__object_name': cdist_object.name, '__type': cdist_object.cdist_type.absolute_path, }) return env def run_type_manifest(self, cdist_object): type_manifest = os.path.join(self.local.type_path, cdist_object.cdist_type.manifest_path) message_prefix = cdist_object.name which = 'manifest' if os.path.isfile(type_manifest): self.log.verbose("Running type manifest %s for object %s", type_manifest, cdist_object.name) if self.local.save_output_streams: stderr_path = os.path.join(cdist_object.stderr_path, which) stdout_path = os.path.join(cdist_object.stdout_path, which) with open(stderr_path, 'ba+') as stderr, \ open(stdout_path, 'ba+') as stdout: self.local.run_script( type_manifest, env=self.env_type_manifest(cdist_object), message_prefix=message_prefix, stdout=stdout, stderr=stderr) else: self.local.run_script( type_manifest, env=self.env_type_manifest(cdist_object), message_prefix=message_prefix) def env_py_type_manifest(self, cdist_object): env = os.environ.copy() env.update(self.env) env.update({ '__cdist_object_marker': self.local.object_marker_name, '__cdist_manifest': cdist_object.cdist_type, '__manifest': self.local.manifest_path, '__object': cdist_object.absolute_path, '__object_id': cdist_object.object_id, '__object_name': cdist_object.name, '__type': cdist_object.cdist_type.absolute_path, }) return env def run_py_type_manifest(self, cdist_object): cdist_type = cdist_object.cdist_type module_name = cdist_type.name file_path = os.path.join(cdist_type.absolute_path, '__init__.py') message_prefix = cdist_object.name if os.path.isfile(file_path): self.log.verbose("Running python type manifest for object %s", cdist_object.name) spec = importlib.util.spec_from_file_location(module_name, file_path) m = importlib.util.module_from_spec(spec) spec.loader.exec_module(m) classes = inspect.getmembers(m, inspect.isclass) type_class = None for _, cl in classes: if cl != PythonType and issubclass(cl, PythonType): if type_class: raise cdist.Error("Only one python type class is " "supported, but at least two " "found: {}".format((type_class, cl, ))) else: type_class = cl env = self.env_py_type_manifest(cdist_object) type_obj = type_class(env=env, cdist_object=cdist_object, local=self.local, remote=None, message_prefix=message_prefix) if self.local.save_output_streams: which = 'manifest' stderr_path = os.path.join(cdist_object.stderr_path, which) stdout_path = os.path.join(cdist_object.stdout_path, which) with open(stderr_path, 'a+') as stderr, \ open(stdout_path, 'a+') as stdout: self._process_py_type_manifest_entries( type_obj, env, stdout=stdout, stderr=stderr) else: self._process_py_type_manifest_entries(type_obj, env) def _process_py_type_manifest_entries(self, type_obj, env, stdout=None, stderr=None): if hasattr(type_obj, 'manifest') and \ inspect.ismethod(type_obj.manifest): for cmd in type_obj.manifest(stdout=stdout, stderr=stderr): if not isinstance(cmd, Command): raise TypeError("Manifest command must be of type Command") kwargs = { 'argv': cmd.cmd_line(), 'env': env, } if cmd.stdin: kwargs['stdin'] = cmd.stdin emulator = cdist.emulator.Emulator(**kwargs) emulator.run()