diff --git a/cdist/__init__.py b/cdist/__init__.py index 279fd502..5cfb2be3 100644 --- a/cdist/__init__.py +++ b/cdist/__init__.py @@ -26,6 +26,7 @@ import hashlib import cdist.log import cdist.version + VERSION = cdist.version.VERSION BANNER = """ @@ -51,6 +52,9 @@ ORDER_DEP_STATE_NAME = 'order_dep_state' TYPEORDER_DEP_NAME = 'typeorder_dep' +MIN_SUPPORTED_PYTHON_VERSION = '3.5' + + class Error(Exception): """Base exception class for this project""" pass diff --git a/cdist/argparse.py b/cdist/argparse.py index ece7ff10..6473957a 100644 --- a/cdist/argparse.py +++ b/cdist/argparse.py @@ -6,6 +6,7 @@ import collections import functools import cdist.configuration import cdist.trigger +import cdist.log import cdist.preos import cdist.info @@ -126,6 +127,14 @@ def get_parsers(): 'value.'), action='count', default=None) + parser['colored_output'] = argparse.ArgumentParser(add_help=False) + parser['colored_output'].add_argument( + '--colors', metavar='WHEN', + help="Colorize cdist's output based on log level; " + "WHEN is 'always', 'never', or 'auto'.", + action='store', dest='colored_output', required=False, + choices=cdist.configuration.ColoredOutputOption.CHOICES) + parser['beta'] = argparse.ArgumentParser(add_help=False) parser['beta'].add_argument( '-b', '--beta', @@ -198,6 +207,13 @@ def get_parsers(): 'supported. Without argument CPU count is used by default. '), action='store', dest='jobs', const=multiprocessing.cpu_count()) + parser['config_main'].add_argument( + '--log-server', + action='store_true', + help=('Start a log server for sub processes to use. ' + 'This is mainly useful when running cdist nested ' + 'from a code-local script. Log server is alwasy ' + 'implicitly started for \'install\' command.')) parser['config_main'].add_argument( '-n', '--dry-run', help='Do not execute code.', action='store_true') @@ -284,6 +300,7 @@ def get_parsers(): 'host', nargs='*', help='Host(s) to operate on.') parser['config'] = parser['sub'].add_parser( 'config', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['config_main'], parser['inventory_common'], @@ -302,6 +319,7 @@ def get_parsers(): parser['add-host'] = parser['invsub'].add_parser( 'add-host', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['add-host'].add_argument( @@ -316,6 +334,7 @@ def get_parsers(): parser['add-tag'] = parser['invsub'].add_parser( 'add-tag', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['add-tag'].add_argument( @@ -347,6 +366,7 @@ def get_parsers(): parser['del-host'] = parser['invsub'].add_parser( 'del-host', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['del-host'].add_argument( @@ -364,6 +384,7 @@ def get_parsers(): parser['del-tag'] = parser['invsub'].add_parser( 'del-tag', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['del-tag'].add_argument( @@ -399,6 +420,7 @@ def get_parsers(): parser['list'] = parser['invsub'].add_parser( 'list', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['list'].add_argument( @@ -431,7 +453,7 @@ def get_parsers(): # Shell parser['shell'] = parser['sub'].add_parser( - 'shell', parents=[parser['loglevel']]) + 'shell', parents=[parser['loglevel'], parser['colored_output']]) parser['shell'].add_argument( '-s', '--shell', help=('Select shell to use, defaults to current shell. Used shell' @@ -501,7 +523,12 @@ def handle_loglevel(args): if hasattr(args, 'quiet') and args.quiet: args.verbose = _verbosity_level_off - logging.root.setLevel(_verbosity_level[args.verbose]) + logging.getLogger().setLevel(_verbosity_level[args.verbose]) + + +def handle_log_colors(args): + if cdist.configuration.ColoredOutputOption.translate(args.colored_output): + cdist.log.CdistFormatter.USE_COLORS = True def parse_and_configure(argv, singleton=True): @@ -515,6 +542,7 @@ def parse_and_configure(argv, singleton=True): raise cdist.Error(str(e)) # Loglevels are handled globally in here handle_loglevel(args) + handle_log_colors(args) log = logging.getLogger("cdist") diff --git a/cdist/conf/explorer/os b/cdist/conf/explorer/os index 563fa4cf..2d2aede6 100755 --- a/cdist/conf/explorer/os +++ b/cdist/conf/explorer/os @@ -143,6 +143,11 @@ case "$uname_s" in esac if [ -f /etc/os-release ]; then + # after sles15, suse don't provide an /etc/SuSE-release anymore, but there is almost no difference between sles and opensuse leap, so call it suse + if grep -q ^ID_LIKE=\"suse\" /etc/os-release 2>/dev/null; then + echo suse + exit 0 + fi # already lowercase, according to: # https://www.freedesktop.org/software/systemd/man/os-release.html awk -F= '/^ID=/ { if ($2 ~ /^'"'"'(.*)'"'"'$/ || $2 ~ /^"(.*)"$/) { print substr($2, 2, length($2) - 2) } else { print $2 } }' /etc/os-release diff --git a/cdist/conf/type/__clean_path/explorer/list b/cdist/conf/type/__clean_path/explorer/list index 07d38127..2bdc63a5 100755 --- a/cdist/conf/type/__clean_path/explorer/list +++ b/cdist/conf/type/__clean_path/explorer/list @@ -18,7 +18,12 @@ # along with cdist. If not, see . # -path="/$__object_id" +if [ -f "$__object/parameter/path" ] +then + path="$( cat "$__object/parameter/path" )" +else + path="/$__object_id" +fi [ ! -d "$path" ] && exit 0 diff --git a/cdist/conf/type/__clean_path/gencode-remote b/cdist/conf/type/__clean_path/gencode-remote index 998a70d8..2899c4a5 100755 --- a/cdist/conf/type/__clean_path/gencode-remote +++ b/cdist/conf/type/__clean_path/gencode-remote @@ -20,7 +20,12 @@ [ ! -s "$__object/explorer/list" ] && exit 0 -path="/$__object_id" +if [ -f "$__object/parameter/path" ] +then + path="$( cat "$__object/parameter/path" )" +else + path="/$__object_id" +fi pattern="$( cat "$__object/parameter/pattern" )" diff --git a/cdist/conf/type/__clean_path/man.rst b/cdist/conf/type/__clean_path/man.rst index 826f4589..31d90701 100644 --- a/cdist/conf/type/__clean_path/man.rst +++ b/cdist/conf/type/__clean_path/man.rst @@ -10,7 +10,7 @@ DESCRIPTION ----------- Remove files and directories which match the pattern. -Provided path (as __object_id) must be a directory. +Provided path must be a directory. Patterns are passed to ``find``'s ``-regex`` - see ``find(1)`` for more details. @@ -29,6 +29,9 @@ pattern OPTIONAL PARAMETERS ------------------- +path + Path which will be cleaned. Defaults to ``$__object_id``. + exclude Pattern of files which are excluded from removal. @@ -46,6 +49,11 @@ EXAMPLES --exclude '.+\(charset\.conf\|security\.conf\)' \ --onchange 'service apache2 restart' + __clean_path apache2-conf-enabled \ + --path /etc/apache2/conf-enabled \ + --pattern '.+' \ + --exclude '.+\(charset\.conf\|security\.conf\)' \ + --onchange 'service apache2 restart' AUTHORS ------- diff --git a/cdist/conf/type/__clean_path/parameter/optional b/cdist/conf/type/__clean_path/parameter/optional index 6f313474..3b97f71c 100644 --- a/cdist/conf/type/__clean_path/parameter/optional +++ b/cdist/conf/type/__clean_path/parameter/optional @@ -1,2 +1,3 @@ exclude onchange +path diff --git a/cdist/conf/type/__ssh_authorized_keys/explorer/keys b/cdist/conf/type/__ssh_authorized_keys/explorer/keys new file mode 100755 index 00000000..cec25746 --- /dev/null +++ b/cdist/conf/type/__ssh_authorized_keys/explorer/keys @@ -0,0 +1,9 @@ +#!/bin/sh -e + +# shellcheck disable=SC1090 +file="$( . "$__type_explorer/file" )" + +if [ -f "$file" ] +then + cat "$file" +fi diff --git a/cdist/conf/type/__ssh_authorized_keys/man.rst b/cdist/conf/type/__ssh_authorized_keys/man.rst index 93357b1d..dac6adeb 100644 --- a/cdist/conf/type/__ssh_authorized_keys/man.rst +++ b/cdist/conf/type/__ssh_authorized_keys/man.rst @@ -60,6 +60,9 @@ nofile Don't manage existence, ownership and permissions of the the authorized_keys file. +remove-unknown + Remove undefined keys. + EXAMPLES -------- @@ -70,6 +73,12 @@ EXAMPLES __ssh_authorized_keys root \ --key "$(cat ~/.ssh/id_rsa.pub)" + # same as above, but make sure your key is only key in + # root's authorized_keys file + __ssh_authorized_keys root \ + --key "$(cat ~/.ssh/id_rsa.pub)" \ + --remove-unknown + # allow key to login as user-name __ssh_authorized_keys user-name \ --key "ssh-rsa AXYZAAB3NzaC1yc2..." diff --git a/cdist/conf/type/__ssh_authorized_keys/manifest b/cdist/conf/type/__ssh_authorized_keys/manifest index b9f0582e..b319316b 100755 --- a/cdist/conf/type/__ssh_authorized_keys/manifest +++ b/cdist/conf/type/__ssh_authorized_keys/manifest @@ -55,8 +55,12 @@ _cksum() { echo "$1" | cksum | cut -d' ' -f 1 } +_type_and_key() { + echo "$1" | tr ' ' '\n' | awk '/^(ssh|ecdsa)-[^ ]+/ { printf $1" "; getline; printf $1 }' +} + while read -r key; do - type_and_key="$(echo "$key" | tr ' ' '\n' | awk '/^(ssh|ecdsa)-[^ ]+/ { printf $1" "; getline; printf $1 }')" + type_and_key="$( _type_and_key "$key" )" object_id="$(_cksum "$file")-$(_cksum "$type_and_key")" set -- "$object_id" set -- "$@" --file "$file" @@ -72,3 +76,24 @@ while read -r key; do # Ensure __ssh_authorized_key does not read stdin __ssh_authorized_key "$@" < /dev/null done < "$__object/parameter/key" + +if [ -f "$__object/parameter/remove-unknown" ] && + [ -s "$__object/explorer/keys" ] +then + while read -r key + do + type_and_key="$( _type_and_key "$key" )" + + if grep -Fq "$type_and_key" "$__object/parameter/key" + then + continue + fi + + __ssh_authorized_key "remove-$( _cksum "$file$key" )" \ + --file "$file" \ + --key "$key" \ + --state absent \ + < /dev/null + done \ + < "$__object/explorer/keys" +fi diff --git a/cdist/conf/type/__ssh_authorized_keys/parameter/boolean b/cdist/conf/type/__ssh_authorized_keys/parameter/boolean index 4bb126fe..7388fed5 100644 --- a/cdist/conf/type/__ssh_authorized_keys/parameter/boolean +++ b/cdist/conf/type/__ssh_authorized_keys/parameter/boolean @@ -1,2 +1,3 @@ noparent nofile +remove-unknown diff --git a/cdist/config.py b/cdist/config.py index 74cd1ae2..048b1082 100644 --- a/cdist/config.py +++ b/cdist/config.py @@ -29,10 +29,14 @@ import time import itertools import tempfile import multiprocessing -from cdist.mputil import mp_pool_run, mp_sig_handler import atexit import shutil import socket + +from cdist.mputil import mp_pool_run, mp_sig_handler +from cdist import core, inventory +from cdist.util.remoteutil import inspect_ssh_mux_opts + import cdist import cdist.hostsource import cdist.exec.local @@ -40,8 +44,6 @@ import cdist.exec.remote import cdist.util.ipaddr as ipaddr import cdist.util.python_type_util as pytype_util import cdist.configuration -from cdist import core, inventory -from cdist.util.remoteutil import inspect_ssh_mux_opts def graph_check_cycle(graph): @@ -198,7 +200,6 @@ class Config(object): @classmethod def commandline(cls, args): """Configure remote system""" - if (args.parallel and args.parallel != 1) or args.jobs: if args.timestamp: cdist.log.setupTimestampingParallelLogging() @@ -206,6 +207,7 @@ class Config(object): cdist.log.setupParallelLogging() elif args.timestamp: cdist.log.setupTimestampingLogging() + log = logging.getLogger("config") # No new child process if only one host at a time. @@ -384,10 +386,16 @@ class Config(object): If operating in parallel then return tuple (host, True|False, ) so that main process knows for which host function was successful. """ - log = logging.getLogger(host) try: + if args.log_server: + # Start a log server so that nested `cdist config` runs + # have a place to send their logs to. + log_server_socket_dir = tempfile.mkdtemp() + cls._register_path_for_removal(log_server_socket_dir) + cdist.log.setupLogServer(log_server_socket_dir, log) + remote_exec, remote_copy, cleanup_cmd = cls._resolve_remote_cmds( args) log.debug("remote_exec for host \"{}\": {}".format( diff --git a/cdist/configuration.py b/cdist/configuration.py index 1011a382..4ba43a7a 100644 --- a/cdist/configuration.py +++ b/cdist/configuration.py @@ -27,6 +27,7 @@ import cdist.argparse import re import multiprocessing import logging +import sys class Singleton(type): @@ -246,9 +247,33 @@ class LogLevelOption(OptionBase): return VerbosityOption().translate(val) +class ColoredOutputOption(BooleanOption): + CHOICES = ('always', 'never', 'auto') + DEFAULT = 'auto' + + def get_converter(self): + return self.translate + + @staticmethod + def translate(val): + if isinstance(val, bool): + return val + elif val == 'always': + return True + elif val == 'never': + return False + elif val == 'auto': + return 'NO_COLOR' not in os.environ and sys.stdout.isatty() + + +ColoredOutputOption.DEFAULT = ColoredOutputOption.translate( + ColoredOutputOption.DEFAULT) + + _ARG_OPTION_MAPPING = { 'beta': 'beta', 'cache_path_pattern': 'cache_path_pattern', + 'colored_output': 'colored_output', 'conf_dir': 'conf_dir', 'manifest': 'init_manifest', 'out_path': 'out_path', @@ -294,6 +319,7 @@ class Configuration(metaclass=Singleton): 'remote_shell': StringOption('remote_shell'), 'cache_path_pattern': StringOption('cache_path_pattern'), 'conf_dir': ConfDirOption(), + 'colored_output': ColoredOutputOption('colored_output'), 'init_manifest': StringOption('init_manifest'), 'out_path': StringOption('out_path'), 'remote_out_path': StringOption('remote_out_path'), @@ -319,6 +345,7 @@ class Configuration(metaclass=Singleton): 'CDIST_REMOTE_COPY': 'remote_copy', 'CDIST_INVENTORY_DIR': 'inventory_dir', 'CDIST_CACHE_PATH_PATTERN': 'cache_path_pattern', + 'CDIST_COLORED_OUTPUT': 'colored_output', '__cdist_log_level': 'verbosity', } ENV_VAR_BOOLEAN_OPTIONS = ('CDIST_BETA', ) @@ -327,11 +354,10 @@ class Configuration(metaclass=Singleton): } ARG_OPTION_MAPPING = _ARG_OPTION_MAPPING - ADJUST_ARG_OPTION_MAPPING = { - _ARG_OPTION_MAPPING[key]: key for key in _ARG_OPTION_MAPPING - } + ADJUST_ARG_OPTION_MAPPING = {v: k for k, v in _ARG_OPTION_MAPPING.items()} REQUIRED_DEFAULT_CONFIG_VALUES = { 'GLOBAL': { + 'colored_output': 'auto', 'verbosity': 0, }, } @@ -484,8 +510,7 @@ class Configuration(metaclass=Singleton): newconfig = self._read_config_file(config_file) self._update_config_dict(config, newconfig) # command line config file - if (self.args and 'config_file' in self.args and - self.args['config_file']): + if (self.args and self.args.get('config_file', None)): newconfig = self._read_config_file(self.args['config_file']) self._update_config_dict(config, newconfig) # command line diff --git a/cdist/core/code.py b/cdist/core/code.py index ae1fddf2..3b5be9f8 100644 --- a/cdist/core/code.py +++ b/cdist/core/code.py @@ -118,6 +118,10 @@ class Code(object): if dry_run: self.env['__cdist_dry_run'] = '1' + if '__cdist_log_server_socket_export' in os.environ: + self.env['__cdist_log_server_socket'] = os.environ[ + '__cdist_log_server_socket_export'] + def run_py(self, cdist_object): cdist_type = cdist_object.cdist_type type_class = get_pytype_class(cdist_type) diff --git a/cdist/core/manifest.py b/cdist/core/manifest.py index 84bc813f..413d363c 100644 --- a/cdist/core/manifest.py +++ b/cdist/core/manifest.py @@ -119,6 +119,8 @@ class Manifest(object): '__cdist_log_level': util.log_level_env_var_val(self.log), '__cdist_log_level_name': util.log_level_name_env_var_val( self.log), + '__cdist_colored_log': str( + cdist.log.CdistFormatter.USE_COLORS).lower(), } if dry_run: diff --git a/cdist/emulator.py b/cdist/emulator.py index 00060a10..a8f5b97d 100644 --- a/cdist/emulator.py +++ b/cdist/emulator.py @@ -152,6 +152,9 @@ class Emulator(object): # if invalid __cdist_log_level value logging.root.setLevel(logging.WARNING) + colored_log = self.env.get('__cdist_colored_log', 'false') + cdist.log.CdistFormatter.USE_COLORS = colored_log == 'true' + self.log = logging.getLogger(self.target_host[0]) def get_args_parser(self): diff --git a/cdist/flock.py b/cdist/flock.py index d8bac916..3afacfd9 100644 --- a/cdist/flock.py +++ b/cdist/flock.py @@ -22,6 +22,7 @@ import fcntl import logging import os +import cdist.log log = logging.getLogger('cdist-flock') diff --git a/cdist/install.py b/cdist/install.py index b88ad016..561b2fa6 100644 --- a/cdist/install.py +++ b/cdist/install.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # -# 2013 Steven Armstrong (steven-cdist at armstrong.cc) +# 2013-2019 Steven Armstrong (steven-cdist at armstrong.cc) # # This file is part of cdist. # @@ -22,9 +22,21 @@ import cdist.config import cdist.core +import cdist.log class Install(cdist.config.Config): + + @classmethod + def onehost(cls, host, host_tags, host_base_path, host_dir_name, args, + parallel, configuration, remove_remote_files_dirs=False): + # Always start log server during cdist install so that nested + # `cdist config` runs have a place to send their logs to. + args.log_server = True + + super().onehost(host, host_tags, host_base_path, host_dir_name, args, + parallel, configuration, remove_remote_files_dirs) + def object_list(self): """Short name for object list retrieval. In install mode, we only care about install objects. diff --git a/cdist/log.py b/cdist/log.py index 2d0bef0b..bee99fac 100644 --- a/cdist/log.py +++ b/cdist/log.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # # 2010-2013 Nico Schottelius (nico-cdist at schottelius.org) +# 2019-2020 Steven Armstrong # # This file is part of cdist. # @@ -20,9 +21,16 @@ # # -import logging -import sys import datetime +import logging +import logging.handlers +import sys +import os +import asyncio +import contextlib +import pickle +import struct +import threading # Define additional cdist logging levels. @@ -50,9 +58,32 @@ def _trace(msg, *args, **kwargs): logging.trace = _trace -class DefaultLog(logging.Logger): +class CdistFormatter(logging.Formatter): + USE_COLORS = False + RESET = '\033[0m' + COLOR_MAP = { + 'ERROR': '\033[0;31m', + 'WARNING': '\033[0;33m', + 'INFO': '\033[0;94m', + 'VERBOSE': '\033[0;34m', + 'DEBUG': '\033[0;90m', + 'TRACE': '\033[0;37m', + } - FORMAT = '%(levelname)s: %(message)s' + def __init__(self, fmt): + super().__init__(fmt=fmt) + + def format(self, record): + msg = super().format(record) + if self.USE_COLORS: + color = self.COLOR_MAP.get(record.levelname) + if color: + msg = color + msg + self.RESET + return msg + + +class DefaultLog(logging.Logger): + FORMAT = '%(levelname)s: %(name)s: %(message)s' class StdoutFilter(logging.Filter): def filter(self, rec): @@ -66,29 +97,26 @@ class DefaultLog(logging.Logger): super().__init__(name) self.propagate = False - formatter = logging.Formatter(self.FORMAT) + if '__cdist_log_server_socket' in os.environ: + log_server_socket = os.environ['__cdist_log_server_socket'] + socket_handler = logging.handlers.SocketHandler(log_server_socket, + None) + self.addHandler(socket_handler) + else: + formatter = CdistFormatter(self.FORMAT) - self.addFilter(self) + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.addFilter(self.StdoutFilter()) + stdout_handler.setLevel(logging.TRACE) + stdout_handler.setFormatter(formatter) - stdout_handler = logging.StreamHandler(sys.stdout) - stdout_handler.addFilter(self.StdoutFilter()) - stdout_handler.setLevel(logging.TRACE) - stdout_handler.setFormatter(formatter) + stderr_handler = logging.StreamHandler(sys.stderr) + stderr_handler.addFilter(self.StderrFilter()) + stderr_handler.setLevel(logging.ERROR) + stderr_handler.setFormatter(formatter) - stderr_handler = logging.StreamHandler(sys.stderr) - stderr_handler.addFilter(self.StderrFilter()) - stderr_handler.setLevel(logging.ERROR) - stderr_handler.setFormatter(formatter) - - self.addHandler(stdout_handler) - self.addHandler(stderr_handler) - - def filter(self, record): - """Prefix messages with logger name""" - - record.msg = self.name + ": " + str(record.msg) - - return True + self.addHandler(stdout_handler) + self.addHandler(stderr_handler) def verbose(self, msg, *args, **kwargs): self.log(logging.VERBOSE, msg, *args, **kwargs) @@ -111,7 +139,7 @@ class TimestampingLog(DefaultLog): class ParallelLog(DefaultLog): - FORMAT = '%(levelname)s: [%(process)d]: %(message)s' + FORMAT = '%(levelname)s: [%(process)d]: %(name)s: %(message)s' class TimestampingParallelLog(TimestampingLog, ParallelLog): @@ -138,4 +166,42 @@ def setupParallelLogging(): logging.setLoggerClass(ParallelLog) +async def handle_log_client(reader, writer): + while True: + chunk = await reader.read(4) + if len(chunk) < 4: + return + + data_size = struct.unpack('>L', chunk)[0] + data = await reader.read(data_size) + + obj = pickle.loads(data) + record = logging.makeLogRecord(obj) + logger = logging.getLogger(record.name) + logger.handle(record) + + +def run_log_server(server_address): + # Get a new loop inside the current thread to run the log server. + loop = asyncio.new_event_loop() + loop.create_task(asyncio.start_unix_server(handle_log_client, + server_address)) + loop.run_forever() + + +def setupLogServer(socket_dir, log=logging.getLogger(__name__)): + """Run a asyncio based unix socket log server in a background thread. + """ + log_server_socket = os.path.join(socket_dir, 'log-server') + log.debug('Starting logging server on: %s', log_server_socket) + os.environ['__cdist_log_server_socket_export'] = log_server_socket + with contextlib.suppress(FileNotFoundError): + os.remove(log_server_socket) + t = threading.Thread(target=run_log_server, args=(log_server_socket,)) + # Deamonizing the thread means we don't have to care about stoping it. + # It will die together with the main process. + t.daemon = True + t.start() + + setupDefaultLogging() diff --git a/cdist/sphinxext/manpage.py b/cdist/sphinxext/manpage.py index 135fe22e..1f8ac4f6 100644 --- a/cdist/sphinxext/manpage.py +++ b/cdist/sphinxext/manpage.py @@ -7,6 +7,7 @@ from docutils.io import FileOutput from os import path from sphinx.util.nodes import inline_all_toctrees from sphinx import addnodes +from sphinx.util import logging """ Extension based on sphinx builtin manpage. @@ -15,6 +16,9 @@ from sphinx import addnodes """ +logger = logging.getLogger(__name__) + + class ManualPageTranslator(sphinx.writers.manpage.ManualPageTranslator): def header(self): @@ -28,7 +32,7 @@ class ManualPageWriter(sphinx.writers.manpage.ManualPageWriter): def __init__(self, builder): super().__init__(builder) self.translator_class = ( - self.builder.translator_class or ManualPageTranslator) + self.builder.get_translator_class() or ManualPageTranslator) class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): @@ -43,7 +47,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): components=(docwriter,), read_config_files=True).get_default_values() - self.info(bold('writing... '), nonl=True) + logger.info(bold('writing... '), nonl=True) for info in self.config.man_pages: docname, name, description, authors, section = info @@ -54,7 +58,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): authors = [] targetname = '%s.%s' % (name, section) - self.info(darkgreen(targetname) + ' { ', nonl=True) + logger.info(darkgreen(targetname) + ' { ', nonl=True) destination = FileOutput( destination_path=path.join(self.outdir, targetname), encoding='utf-8') @@ -63,7 +67,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): docnames = set() largetree = inline_all_toctrees(self, docnames, docname, tree, darkgreen, [docname]) - self.info('} ', nonl=True) + logger.info('} ', nonl=True) self.env.resolve_references(largetree, docname, self) # remove pending_xref nodes for pendingnode in largetree.traverse(addnodes.pending_xref): @@ -76,7 +80,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): largetree.settings.section = section docwriter.write(largetree, destination) - self.info() + logger.info("") def setup(app): diff --git a/cdist/test/configuration/__init__.py b/cdist/test/configuration/__init__.py index 182868a6..3fd24ca5 100644 --- a/cdist/test/configuration/__init__.py +++ b/cdist/test/configuration/__init__.py @@ -28,10 +28,12 @@ import argparse from cdist import test import cdist.argparse as cap import logging +import sys my_dir = op.abspath(op.dirname(__file__)) fixtures = op.join(my_dir, 'fixtures') interpolation_config_file = op.join(fixtures, "interpolation-test.cfg") +colored_output_default = 'auto' def newConfigParser(): @@ -153,6 +155,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': '', 'cache_path_pattern': '', + 'colored_output': colored_output_default, 'conf_dir': '', 'init_manifest': '', 'out_path': '', @@ -184,6 +187,8 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': None, 'cache_path_pattern': None, + 'colored_output': cc.ColoredOutputOption.translate( + colored_output_default), 'conf_dir': None, 'init_manifest': None, 'out_path': None, @@ -390,6 +395,7 @@ class ConfigurationTestCase(test.CdistTestCase): args = argparse.Namespace() expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'verbosity': 0, }, } @@ -440,6 +446,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': None, 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': None, 'init_manifest': None, 'out_path': None, @@ -515,6 +522,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': ['/opt/cdist', ], 'init_manifest': None, 'out_path': None, @@ -556,6 +564,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': '', 'cache_path_pattern': '', + 'colored_output': colored_output_default, 'conf_dir': '', 'init_manifest': '', 'out_path': '', @@ -579,6 +588,8 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': None, 'cache_path_pattern': None, + 'colored_output': cc.ColoredOutputOption.translate( + colored_output_default), 'conf_dir': [ '/opt/cdist/conf', '/usr/local/share/cdist/conf', @@ -623,6 +634,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': '', 'cache_path_pattern': '', + 'colored_output': colored_output_default, 'conf_dir': '', 'init_manifest': '', 'out_path': '', @@ -645,6 +657,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'local_shell': '/usr/bin/sh', 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', + 'colored_output': colored_output_default, 'conf_dir': '/opt/cdist', 'remote_copy': 'myscp', 'remote_exec': 'myexec', @@ -663,6 +676,8 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': cc.ColoredOutputOption.translate( + colored_output_default), 'conf_dir': [ '/opt/cdist/conf', '/usr/local/share/cdist/conf', @@ -694,6 +709,7 @@ class ConfigurationTestCase(test.CdistTestCase): } expected_config = { 'GLOBAL': { + 'colored_output': colored_output_default, 'verbosity': 0, }, } @@ -767,6 +783,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/opt/sysadmin/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/cdist/conf', '/usr/local/share/cdist/conf', @@ -865,6 +882,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -964,6 +982,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -1063,6 +1082,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -1095,6 +1115,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'beta': True, 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -1125,6 +1146,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { 'inventory_dir': None, + 'colored_output': colored_output_default, 'conf_dir': None, 'verbosity': 0, }, @@ -1148,6 +1170,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'verbosity': cap.VERBOSE_DEBUG, }, } @@ -1185,6 +1208,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': True, 'verbosity': 0, }, @@ -1213,6 +1237,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': False, 'verbosity': 0, }, @@ -1241,6 +1266,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': False, 'verbosity': 0, }, @@ -1269,6 +1295,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': False, 'verbosity': 0, }, @@ -1308,6 +1335,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': True, 'verbosity': 0, }, @@ -1336,6 +1364,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': True, 'verbosity': 0, }, @@ -1364,6 +1393,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': False, 'verbosity': 0, }, @@ -1392,6 +1422,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': False, 'verbosity': 0, }, diff --git a/configuration/cdist.cfg.skeleton b/configuration/cdist.cfg.skeleton index 91c5ab02..14861592 100644 --- a/configuration/cdist.cfg.skeleton +++ b/configuration/cdist.cfg.skeleton @@ -13,6 +13,14 @@ # Specify cache path pattern. # cache_path_pattern = %h # +# colored_output +# Colorize cdist's output. If enabled, cdist will use different colors for +# different log levels. +# Recognized values are 'always', 'never', and 'auto'. +# If the value is 'auto', colors are enabled if stdout is a TTY unless +# the NO_COLOR (https://no-color.org/) environment variable is defined. +# colored_output = auto +# # conf_dir # List of configuration directories separated with the character conventionally # used by the operating system to separate search path components (as in PATH), @@ -45,7 +53,7 @@ # # out_path # Directory to save cdist output in. -# out_path = +# out_path = # # parallel # Process hosts in parallel. If -1 then the default, number of CPU's in @@ -71,6 +79,6 @@ # remote_shell = /bin/sh # # verbosity -# Set verbosity level. Valid values are: +# Set verbosity level. Valid values are: # ERROR, WARNING, INFO, VERBOSE, DEBUG, TRACE and OFF. # verbosity = INFO diff --git a/docs/changelog b/docs/changelog index cb34fc2a..6ee68d6f 100644 --- a/docs/changelog +++ b/docs/changelog @@ -5,6 +5,17 @@ next: * Core: Add trigger functionality (Nico Schottelius, Darko Poljak) * Core: Implement core support for python types (Darko Poljak) +6.6.0: 2020-06-17 + * Type __ssh_authorized_keys: Add option for removing undefined keys (Ander Punnar) + * Core: Support colored log output (Evil Ham) + * Core: Tune colored log output and respect NO_COLOR env var (Dennis Camera) + * Documentation: Fix failing man pages build with newer sphinx versions (Darko Poljak) + * Documentation: Fix trivial grammatical mistakes (Jaak Ristioja) + * Explorer os: Fix for sles15 (Daniel Heule) + * Type __clean_path: Add --path parameter (Ander Punnar) + * Core: Increase minimal supported Python version to 3.5 (Darko Poljak) + * Core: Add log server for nested logging (Steven Armstrong) + 6.5.6: 2020-05-25 * Type __pyvenv: Switch to python3 -m venv for Ubuntu (Nico Schottelius) * Type __letsencrypt_cert: Whitelist Ubuntu (Nico Schottelius) diff --git a/docs/src/cdist-bootstrap.rst b/docs/src/cdist-bootstrap.rst index c9972a99..10f86c5a 100644 --- a/docs/src/cdist-bootstrap.rst +++ b/docs/src/cdist-bootstrap.rst @@ -25,7 +25,7 @@ people, have a look at `cdist best practice `_. Setup working directory and branch ---------------------------------- I assume you have a fresh copy of the cdist tree in ~/cdist, cloned from -one of the official urls (see `cdist quickstart `_ if you don't). +one of the official URLs (see `cdist quickstart `_ if you don't). Entering the command "git branch" should show you "* master", which indicates you are on the **master** branch. diff --git a/docs/src/cdist-cache.rst b/docs/src/cdist-cache.rst index 0e5361ee..d2d2d56c 100644 --- a/docs/src/cdist-cache.rst +++ b/docs/src/cdist-cache.rst @@ -59,7 +59,7 @@ typeorder Object cache overview ~~~~~~~~~~~~~~~~~~~~~ -Each object under :strong:`object` directory has its own structurue. +Each object under :strong:`object` directory has its own structure. code-local code generated from gencode-local, present only if something is diff --git a/docs/src/cdist-configuration.rst b/docs/src/cdist-configuration.rst index 706ed761..a2024584 100644 --- a/docs/src/cdist-configuration.rst +++ b/docs/src/cdist-configuration.rst @@ -21,7 +21,7 @@ precedence. Configuration option value read from source with higher precedence will overwrite option value, if exists, read from source with lower precedence. That means that command-line option wins them all. -Users can decide on the local conifguration file location. It can be either +Users can decide on the local configuration file location. It can be either ~/.cdist.cfg or $XDG_CONFIG_HOME/cdist/cdist.cfg. Note that, if both exist, then ~/.cdist.cfg is used. diff --git a/docs/src/cdist-install.rst b/docs/src/cdist-install.rst index 880b9f8e..6f4f14d7 100644 --- a/docs/src/cdist-install.rst +++ b/docs/src/cdist-install.rst @@ -9,15 +9,15 @@ Source Host This is the machine from which you will configure target hosts. - * /bin/sh: A posix like shell (for instance bash, dash, zsh) - * Python >= 3.2 + * /bin/sh: A POSIX like shell (for instance bash, dash, zsh) + * Python >= 3.5 * SSH client * sphinx (for building html docs and/or the man pages) Target Hosts ~~~~~~~~~~~~ - * /bin/sh: A posix like shell (for instance bash, dash, zsh) + * /bin/sh: A POSIX like shell (for instance bash, dash, zsh) * SSH server Install cdist diff --git a/docs/src/cdist-real-world.rst b/docs/src/cdist-real-world.rst index ba118d63..973dcd6d 100644 --- a/docs/src/cdist-real-world.rst +++ b/docs/src/cdist-real-world.rst @@ -198,7 +198,7 @@ We require package uWSGI present in order to create **/etc/uwsgi/apps-enabled/$u Installation of uWSGI also creates configuration layout: **/etc/uwsgi/apps-enabled**. If this directory does not exist then **__file** type would error. We also use stdin as file content source. For details see `Input from stdin `_. -For feading stdin we use here-document (**<<** operator). It allows redirection of subsequent +For feeding stdin we use here-document (**<<** operator). It allows redirection of subsequent lines read by the shell to the input of a command until a line containing only the delimiter and a newline, with no blank characters in between (EOF in our case). @@ -546,7 +546,7 @@ we have changed our **wsgi.py** file uWSGI reloads the application. Our application selects and lists items from **items** table. -Openning application +Opening application ~~~~~~~~~~~~~~~~~~~~ Finally try the application:: diff --git a/docs/src/cdist-reference.rst.sh b/docs/src/cdist-reference.rst.sh index e77d98f6..c0ac2c3e 100755 --- a/docs/src/cdist-reference.rst.sh +++ b/docs/src/cdist-reference.rst.sh @@ -34,7 +34,7 @@ dest="$__cdist_abs_mydir/$filename" cd "$__cdist_abs_mydir" exec > "$dest" -cat << eof +cat << eof Reference ========= Variable, path and type reference for cdist @@ -51,7 +51,7 @@ eof done ) -cat << eof +cat << eof Paths ----- @@ -187,13 +187,13 @@ usable within a object directory: files This directory is reserved for user data and will not be used - by cdist at any time. It can be used freely by the type + by cdist at any time. It can be used freely by the type (for instance to store template results). changed This empty file exists in an object directory, if the object has code to be executed (either remote or local). stdin - This file exists and contains data, if data was provided on stdin + This file exists and contains data, if data was provided on stdin when the type was called. @@ -222,65 +222,89 @@ __cdist_log_level, __cdist_log_level_name | TRACE | 5 | +----------------+-----------------+ + Available for: initial manifest, explorer, type manifest, type explorer, + type gencode. +__cdist_colored_log + whether or not cdist's log has colors enabled. + Is set to the string ``true`` if cdist's output is using colors, + otherwise the variable contains the string ``false``. + Available for: initial manifest, explorer, type manifest, type explorer, type gencode. __cdist_dry_run Is set only when doing dry run (``-n`` flag). + Available for: initial manifest, explorer, type manifest, type explorer, type gencode. __explorer Directory that contains all global explorers. + Available for: initial manifest, explorer, type explorer, shell. __files Directory that contains content from the "files" subdirectories from the configuration directories. + Available for: initial manifest, type manifest, type gencode, shell. __manifest Directory that contains the initial manifest. + Available for: initial manifest, type manifest, shell. __global Directory that contains generic output like explorer. + Available for: initial manifest, type manifest, type gencode, shell. __messages_in File to read messages from. + Available for: initial manifest, type manifest, type gencode. __messages_out File to write messages. + Available for: initial manifest, type manifest, type gencode. __object Directory that contains the current object. + Available for: type manifest, type explorer, type gencode and code scripts. __object_id The type unique object id. + Available for: type manifest, type explorer, type gencode and code scripts. - Note: The leading and the trailing "/" will always be stripped (caused by - the filesystem database and ensured by the core). - Note: Double slashes ("//") will not be fixed and result in an error. + + | Note: The leading and the trailing "/" will always be stripped (caused by + the filesystem database and ensured by the core). + | Note: Double slashes ("//") will not be fixed and result in an error. __object_name The full qualified name of the current object. + Available for: type manifest, type explorer, type gencode. __target_host The host we are deploying to. This is primary variable. It's content is literally the one user passed in. + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __target_hostname The hostname of host we are deploying to. This variable is derived from **__target_host** (using **socket.getaddrinfo(__target_host)** and then **socket.gethostbyaddr()**). + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __target_fqdn The fully qualified domain name of the host we are deploying to. This variable is derived from **__target_host** (using **socket.getfqdn()**). + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __target_host_tags Comma separated list of target host tags. + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __type Path to the current type. + Available for: type manifest, type gencode. __type_explorer Directory that contains the type explorers. + Available for: type explorer. Environment variables (for writing) @@ -344,6 +368,11 @@ CDIST_INVENTORY_DIR CDIST_BETA Enable beta functionalities. +CDIST_COLORED_OUTPUT + Colorize cdist's output. If enabled, cdist will use different colors for + different log levels. + Recognized values are 'always', 'never', and 'auto' (the default). + CDIST_CACHE_PATH_PATTERN Custom cache path pattern. eof diff --git a/docs/src/cdist-upgrade.rst b/docs/src/cdist-upgrade.rst index 67fd4934..f745b212 100644 --- a/docs/src/cdist-upgrade.rst +++ b/docs/src/cdist-upgrade.rst @@ -14,7 +14,7 @@ To upgrade cdist in the current branch use make man export MANPATH=$MANPATH:$(pwd -P)/doc/man -If you stay on a version branche (i.e. 1.0, 1.1., ...), nothing should break. +If you stay on a version branch (i.e. 1.0, 1.1., ...), nothing should break. The master branch on the other hand is the development branch and may not be working, break your setup or eat the tree in your garden. @@ -61,7 +61,7 @@ After that, you can go back and continue the upgrade: Update the python package ------------------------- -To upgrade to the lastet version do +To upgrade to the latest version do .. code-block:: sh @@ -158,7 +158,7 @@ Updating from 1.5 to 1.6 * If you used **\_\_package_apt --preseed**, you need to use the new type **\_\_debconf_set_selections** instead. * The **\_\_package** types accepted either --state deinstalled or - --state uninstaaled. Starting with 1.6, it was made consistently + --state uninstalled. Starting with 1.6, it was made consistently to --state removed. Updating from 1.3 to 1.5 diff --git a/docs/src/cdist-why.rst b/docs/src/cdist-why.rst index 1123a1de..0e2cd34d 100644 --- a/docs/src/cdist-why.rst +++ b/docs/src/cdist-why.rst @@ -21,7 +21,7 @@ Not only is shell scripting widely known by system engineers, but it is also a very powerful language. Here are some features which make daily work easy: - * Configuration can react dynamicly on explored values + * Configuration can react dynamically on explored values * High level string manipulation (using sed, awk, grep) * Conditional support (**if, case**) * Loop support (**for, while**) @@ -44,7 +44,7 @@ Cdist requires very little on a target system. Even better, in almost all cases all dependencies are usually fulfilled. Cdist does not require an agent or high level programming languages on the target host: it will run on any host that -has a **ssh server running** and a posix compatible shell +has a **ssh server running** and a POSIX compatible shell (**/bin/sh**). Compared to other configuration management systems, it does not require to open up an additional port. diff --git a/docs/src/man1/cdist.rst b/docs/src/man1/cdist.rst index 01e25c4e..dd522041 100644 --- a/docs/src/man1/cdist.rst +++ b/docs/src/man1/cdist.rst @@ -15,52 +15,51 @@ SYNOPSIS cdist banner [-h] [-l LOGLEVEL] [-q] [-v] - cdist config [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-g CONFIG_FILE] [-4] - [-6] [-C CACHE_PATH_PATTERN] [-c CONF_DIR] [-i MANIFEST] - [-j [JOBS]] [-n] [-o OUT_PATH] [-P] - [-R [{tar,tgz,tbz2,txz}]] [-r REMOTE_OUT_PATH] - [--remote-copy REMOTE_COPY] [--remote-exec REMOTE_EXEC] - [-S] [-I INVENTORY_DIR] [-A] [-a] [-f HOSTFILE] - [-p [HOST_MAX]] [-s] [-t] + cdist config [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-4] [-6] [-C CACHE_PATH_PATTERN] + [-c CONF_DIR] [-i MANIFEST] [-j [JOBS]] [--log-server] + [-n] [-o OUT_PATH] [-P] [-R [{tar,tgz,tbz2,txz}]] + [-r REMOTE_OUT_PATH] [--remote-copy REMOTE_COPY] + [--remote-exec REMOTE_EXEC] [-S] [-I INVENTORY_DIR] [-A] + [-a] [-f HOSTFILE] [-p [HOST_MAX]] [-s] [-t] [host [host ...]] - cdist install [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-g CONFIG_FILE] [-4] - [-6] [-C CACHE_PATH_PATTERN] [-c CONF_DIR] [-i MANIFEST] - [-j [JOBS]] [-n] [-o OUT_PATH] [-P] - [-R [{tar,tgz,tbz2,txz}]] [-r REMOTE_OUT_PATH] - [--remote-copy REMOTE_COPY] [--remote-exec REMOTE_EXEC] - [-S] [-I INVENTORY_DIR] [-A] [-a] [-f HOSTFILE] - [-p [HOST_MAX]] [-s] [-t] + cdist install [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-4] [-6] [-C CACHE_PATH_PATTERN] + [-c CONF_DIR] [-i MANIFEST] [-j [JOBS]] [--log-server] + [-n] [-o OUT_PATH] [-P] [-R [{tar,tgz,tbz2,txz}]] + [-r REMOTE_OUT_PATH] [--remote-copy REMOTE_COPY] + [--remote-exec REMOTE_EXEC] [-S] [-I INVENTORY_DIR] [-A] + [-a] [-f HOSTFILE] [-p [HOST_MAX]] [-s] [-t] [host [host ...]] cdist inventory [-h] {add-host,add-tag,del-host,del-tag,list} ... - cdist inventory add-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] - [-g CONFIG_FILE] [-I INVENTORY_DIR] - [-f HOSTFILE] + cdist inventory add-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-I INVENTORY_DIR] [-f HOSTFILE] [host [host ...]] - cdist inventory add-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] - [-g CONFIG_FILE] [-I INVENTORY_DIR] - [-f HOSTFILE] [-T TAGFILE] [-t TAGLIST] + cdist inventory add-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-I INVENTORY_DIR] [-f HOSTFILE] + [-T TAGFILE] [-t TAGLIST] [host [host ...]] - cdist inventory del-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] + cdist inventory del-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] [-g CONFIG_FILE] [-I INVENTORY_DIR] [-a] [-f HOSTFILE] [host [host ...]] - cdist inventory del-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] + cdist inventory del-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] [-g CONFIG_FILE] [-I INVENTORY_DIR] [-a] [-f HOSTFILE] [-T TAGFILE] [-t TAGLIST] [host [host ...]] - cdist inventory list [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-g CONFIG_FILE] - [-I INVENTORY_DIR] [-a] [-f HOSTFILE] [-H] [-t] + cdist inventory list [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-I INVENTORY_DIR] [-a] [-f HOSTFILE] + [-H] [-t] [host [host ...]] - cdist preos [-h] [-l LOGLEVEL] [-q] [-v] [-c CONF_DIR] [-g CONFIG_FILE] - [-L] + cdist preos [-h] [-l LOGLEVEL] [-q] [-v] [-c CONF_DIR] [-g CONFIG_FILE] [-L] [preos] ... cdist preos [preos-options] debian [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-a ARCH] [-B] @@ -87,9 +86,10 @@ SYNOPSIS [-y REMOTE_COPY] target_dir - cdist shell [-h] [-l LOGLEVEL] [-q] [-v] [-s SHELL] + cdist shell [-h] [-l LOGLEVEL] [-q] [-v] [--colors WHEN] [-s SHELL] - cdist info [-h] [-a] [-c CONF_DIR] [-e] [-F] [-f] [-g CONFIG_FILE] [-t] [pattern] + cdist info [-h] [-a] [-c CONF_DIR] [-e] [-F] [-f] [-g CONFIG_FILE] [-t] + [pattern] cdist trigger [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-g CONFIG_FILE] [-4] [-6] [-C CACHE_PATH_PATTERN] [-c CONF_DIR] [-i MANIFEST] @@ -105,7 +105,7 @@ cdist is the frontend executable to the cdist configuration management. It supports different subcommands as explained below. It is written in Python so it requires :strong:`python`\ (1) to be installed. -It requires a minimal Python version 3.2. +It requires a minimal Python version 3.5. GENERAL ------- @@ -114,6 +114,14 @@ All commands accept the following options: **-h, --help** Show the help screen. +**--colors WHEN** + Colorize cdist's output. If enabled, cdist will use different colors for + different log levels. + WHEN recognizes the values 'always', 'never', and 'auto' (the default). + + If the value is 'auto', colored output is enabled if stdout is a TTY + unless the NO_COLOR (https://no-color.org/) environment variable is defined. + **-l LOGLEVEL, --log-level LOGLEVEL** Set the specified verbosity level. The levels, in order from the lowest to the highest, are: ERROR (-1), @@ -166,7 +174,7 @@ Install command is currently in beta. **-b, --beta** Enable beta functionality. - + **-C CACHE_PATH_PATTERN, --cache-path-pattern CACHE_PATH_PATTERN** Specify custom cache path pattern. If it is not set then default hostdir is used. For more info on format see @@ -189,7 +197,7 @@ Install command is currently in beta. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -204,6 +212,12 @@ Install command is currently in beta. are supported. Without argument CPU count is used by default. +**--log-server** + Start a log server for sub processes to use. This is + mainly useful when running cdist nested from a code- + local script. Log server is always implicitly started + for 'install' command. + **-n, --dry-run** Do not execute code. @@ -304,7 +318,7 @@ Add host(s) to inventory database. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -334,7 +348,7 @@ Add tag(s) to inventory database. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -377,7 +391,7 @@ Delete host(s) from inventory database. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -411,7 +425,7 @@ Delete tag(s) from inventory database. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -458,7 +472,7 @@ List inventory database. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -780,6 +794,9 @@ The possible keywords and their meanings are as follows: :strong:`cache_path_pattern` Specify cache path pattern. +:strong:`colored_output` + Colorize cdist's output. cf. the :code:`--colors` option. + :strong:`conf_dir` List of configuration directories separated with the character conventionally used by the operating system to separate search path components (as in PATH), @@ -833,7 +850,7 @@ The possible keywords and their meanings are as follows: in the format: YYYYMMDDHHMMSS.us. :strong:`verbosity` - Set verbosity level. Valid values are: + Set verbosity level. Valid values are: 'ERROR', 'WARNING', 'INFO', 'VERBOSE', 'DEBUG', 'TRACE' and 'OFF'. @@ -865,7 +882,7 @@ cdist/preos NOTES ----- cdist detects if host is specified by IPv6 address. If so then remote_copy -command is executed with host address enclosed in square brackets +command is executed with host address enclosed in square brackets (see :strong:`scp`\ (1)). EXAMPLES @@ -1008,6 +1025,9 @@ CDIST_BETA CDIST_CACHE_PATH_PATTERN Custom cache path pattern. +CDIST_COLORED_OUTPUT + Colorize cdist's output. cf. the :code:`--colors` option. + CDIST_CONFIG_FILE Custom configuration file. @@ -1070,5 +1090,5 @@ such case and display a warning message. An example of such a case: COPYING ------- -Copyright \(C) 2011-2019 Nico Schottelius. Free use of this software is +Copyright \(C) 2011-2020 Nico Schottelius. Free use of this software is granted under the terms of the GNU General Public License v3 or later (GPLv3+). diff --git a/scripts/cdist b/scripts/cdist index 664504a0..b1d782ab 100755 --- a/scripts/cdist +++ b/scripts/cdist @@ -60,10 +60,9 @@ def commandline(): if __name__ == "__main__": - cdistpythonversion = '3.5' - if sys.version < cdistpythonversion: + if sys.version < cdist.MIN_SUPPORTED_PYTHON_VERSION: print('Python >= {} is required on the source host.'.format( - cdistpythonversion), file=sys.stderr) + cdist.MIN_SUPPORTED_PYTHON_VERSIO), file=sys.stderr) sys.exit(1) exit_code = 0