diff --git a/cdist/config.py b/cdist/config.py index bba9bfcb..f5e62ce1 100644 --- a/cdist/config.py +++ b/cdist/config.py @@ -26,6 +26,7 @@ import shutil import sys import time import pprint +import itertools import cdist @@ -53,6 +54,27 @@ class Config(object): self.local.create_files_dirs() self.remote.create_files_dirs() + @staticmethod + def hosts(source): + """Yield hosts from source. + Source can be a sequence or filename (stdin if \'-\'). + In case of filename each line represents one host. + """ + if isinstance(source, str): + import fileinput + try: + for host in fileinput.input(files=(source)): + # remove leading and trailing whitespace + yield host.strip() + except (IOError, OSError) as e: + raise cdist.Error("Error reading hosts from \'{}\'".format( + source)) + else: + if source: + for host in source: + yield host + + @classmethod def commandline(cls, args): """Configure remote system""" @@ -60,6 +82,13 @@ class Config(object): # FIXME: Refactor relict - remove later log = logging.getLogger("cdist") + + if args.manifest == '-' and args.hostfile == '-': + raise cdist.Error(("Cannot read both, manifest and host file, " + "from stdin")) + # if no host source is specified then read hosts from stdin + if not (args.hostfile or args.host): + args.hostfile = '-' initial_manifest_tempfile = None if args.manifest == '-': @@ -79,8 +108,11 @@ class Config(object): process = {} failed_hosts = [] time_start = time.time() - - for host in args.host: + + hostcnt = 0 + for host in itertools.chain(cls.hosts(args.host), + cls.hosts(args.hostfile)): + hostcnt += 1 if args.parallel: log.debug("Creating child process for %s", host) process[host] = multiprocessing.Process(target=cls.onehost, args=(host, args, True)) @@ -101,7 +133,7 @@ class Config(object): failed_hosts.append(host) time_end = time.time() - log.info("Total processing time for %s host(s): %s", len(args.host), + log.info("Total processing time for %s host(s): %s", hostcnt, (time_end - time_start)) if len(failed_hosts) > 0: diff --git a/docs/changelog b/docs/changelog index 3a644b6f..e7dc26ed 100644 --- a/docs/changelog +++ b/docs/changelog @@ -1,6 +1,9 @@ Changelog --------- +next: + * Core: Add -f option to read additional hosts from file/stdin (Darko Poljak) + 4.0.0: 2016-05-04 * Core: Fix bug with parallel hosts operation when output path is specifed (Darko Poljak) * Type __package_pip: Add support for running as specified user (useful for pip in venv) (Darko Poljak) @@ -25,7 +28,6 @@ Changelog * Type __consul: Add new consul versions (Nico Schottelius) * Type __apt_ppa: Do not install legacy package python-software-properties (Steven Armstrong) - 3.1.13: 2015-05-16 * Type __block: Fix support for non stdin blocks (Dominique Roux) * Type __consul: Install package unzip (Nico Schottelius) diff --git a/docs/man/man1/cdist.text b/docs/man/man1/cdist.text index e29ae3ae..1dc1e87f 100644 --- a/docs/man/man1/cdist.text +++ b/docs/man/man1/cdist.text @@ -14,7 +14,7 @@ cdist [-h] [-d] [-v] [-V] {banner,config,shell} ... cdist banner [-h] [-d] [-v] -cdist config [-h] [-d] [-V] [-c CONF_DIR] [-i MANIFEST] [-p] [-s] host [host ...] +cdist config [-h] [-d] [-V] [-c CONF_DIR] [-f HOSTFILE] [-i MANIFEST] [-p] [-s] [host [host ...]] cdist shell [-h] [-d] [-v] [-s SHELL] @@ -63,6 +63,12 @@ Configure one or more hosts --conf-dir argument have higher precedence over those set through the environment variable. +-f HOSTFILE, --file HOSTFILE:: + Read additional hosts to operate on from specified file + or from stdin if '-' (each host on separate line). + If no host or host file is specified then, by default, + read hosts from stdin. + -i MANIFEST, --initial-manifest MANIFEST:: Path to a cdist manifest or - to read from stdin @@ -105,6 +111,9 @@ EXAMPLES --remote-copy /path/to/my/remote/copy \ -p ikq02.ethz.ch ikq03.ethz.ch ikq04.ethz.ch +# Configure hosts read from file loadbalancers +% cdist config -f loadbalancers + # Display banner cdist banner diff --git a/scripts/cdist b/scripts/cdist index 8e22aacb..6baa28f3 100755 --- a/scripts/cdist +++ b/scripts/cdist @@ -67,12 +67,14 @@ def commandline(): action='store_true', default=False) # Main subcommand parser - parser['main'] = argparse.ArgumentParser(description='cdist ' + cdist.VERSION, + parser['main'] = argparse.ArgumentParser(description='cdist ' + + cdist.VERSION, parents=[parser['loglevel']]) parser['main'].add_argument('-V', '--version', help='Show version', action='version', version='%(prog)s ' + cdist.VERSION) - parser['sub'] = parser['main'].add_subparsers(title="Commands") + parser['sub'] = parser['main'].add_subparsers(title="Commands", + dest="command") # Banner parser['banner'] = parser['sub'].add_parser('banner', @@ -82,11 +84,17 @@ def commandline(): # Config parser['config'] = parser['sub'].add_parser('config', parents=[parser['loglevel']]) - parser['config'].add_argument('host', nargs='+', - help='one or more hosts to operate on') + parser['config'].add_argument('host', nargs='*', + help='host(s) to operate on') parser['config'].add_argument('-c', '--conf-dir', - help='Add configuration directory (can be repeated, last one wins)', - action='append') + help=('Add configuration directory (can be repeated, ' + 'last one wins)'), action='append') + parser['config'].add_argument('-f', '--file', + help=('Read additional hosts to operate on from specified file ' + 'or from stdin if \'-\' (each host on separate line). ' + 'If no host or host file is specified then, by default, ' + 'read hosts from stdin.'), + dest='hostfile', required=False) parser['config'].add_argument('-i', '--initial-manifest', help='Path to a cdist manifest or \'-\' to read from stdin.', dest='manifest', required=False) @@ -108,7 +116,8 @@ def commandline(): action='store', dest='remote_copy', default=os.environ.get('CDIST_REMOTE_COPY')) parser['config'].add_argument('--remote-exec', - help='Command to use for remote execution (should behave like ssh)', + help=('Command to use for remote execution ' + '(should behave like ssh)'), action='store', dest='remote_exec', default=os.environ.get('CDIST_REMOTE_EXEC')) parser['config'].set_defaults(func=cdist.config.Config.commandline) @@ -147,6 +156,11 @@ def commandline(): if args_dict['remote_copy'] is None: args.remote_copy = cdist.REMOTE_COPY + mux_opts + if args.command == 'config': + if args.manifest == '-' and args.hostfile == '-': + print('cdist config: error: cannot read both, manifest and host file, from stdin') + sys.exit(1) + log.debug(args) log.info("version %s" % cdist.VERSION)