cdist/cdist/core/manifest.py
Darko Poljak da274e5ef3 Redefine/reimplement CDIST_ORDER_DEPENDENCY
CDIST_ORDER_DEPENDENCY now defines type order dependency context.
cdist (emulator) maintains global state variables, as files,
order_dep_state and typeorder_dep, and per object state variable,
as file, typeorder_dep.

If order_dep_state exists then this defines that order dependency is
turned on.
If order_dep_state does not exist then order dependency is turned off.

If order dependency is on then objects created after it is turned on are
recorded into:
    * global typeorder_dep, in case of init manifest
    * object's typeorder_dep, in case of type's manifest.

If order dependency is on then requirement is injected, where object
created before current, is read from:
    * global typeorder_dep, in case of init manifest
    * object's typeorder_dep, in case of type's manifest.

Every time order dependency is turned off, typeorder_dep files are
removed, which means that type order list is cleared, context is
cleaned.

In the end cdist cleans after itself, i.e. mentioned files are removed.

When running type manifest is finished typeorder_dep file is removed.
When running config finishes global typeorder_dep and order_dep_state
files are removed.

Global type order recording is untouched.
Furthermore, for completeness, type order is now recorded for each object
too.
2019-11-27 15:04:47 +01:00

228 lines
8.1 KiB
Python

# -*- 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 <http://www.gnu.org/licenses/>.
#
#
import logging
import os
import cdist
from . import util
'''
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.
"""
ORDER_DEP_STATE_NAME = 'order_dep_state'
TYPEORDER_DEP_NAME = 'typeorder_dep'
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 cleanup(self):
def _rm_file(fname):
try:
self.log.trace("[ORDER_DEP] Removing %s", fname)
os.remove(os.path.join(self.local.base_path, fname))
except FileNotFoundError:
pass
_rm_file(Manifest.ORDER_DEP_STATE_NAME)
_rm_file(Manifest.TYPEORDER_DEP_NAME)