diff --git a/cdist/config.py b/cdist/config.py index bba9bfcb..fd08d460 100644 --- a/cdist/config.py +++ b/cdist/config.py @@ -53,6 +53,21 @@ 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 + for host in fileinput.input(files=(source)): + yield host.strip() # remove leading and trailing whitespace + else: + for host in source: + yield host + + @classmethod def commandline(cls, args): """Configure remote system""" @@ -60,6 +75,12 @@ 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 +100,15 @@ class Config(object): process = {} failed_hosts = [] time_start = time.time() + + hostcnt = 0 + if args.host: + host_source = args.host + else: + host_source = args.hostfile - for host in args.host: + for host in cls.hosts(host_source): + 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 +129,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/man/man1/cdist.text b/docs/man/man1/cdist.text index e29ae3ae..ed2ce805 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,11 @@ Configure one or more hosts --conf-dir argument have higher precedence over those set through the environment variable. +-f HOSTFILE, --file HOSTFILE:: + Read 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 +110,9 @@ EXAMPLES --remote-copy /path/to/my/remote/copy \ -p ikq02.ethz.ch ikq03.ethz.ch ikq04.ethz.ch +# Configure hosts in parallel by reading hosts from file loadbalancers +% cdist config -f loadbalancers + # Display banner cdist banner diff --git a/scripts/cdist b/scripts/cdist index 8e22aacb..0cfd01d6 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,16 @@ def commandline(): # Config parser['config'] = parser['sub'].add_parser('config', parents=[parser['loglevel']]) - parser['config'].add_argument('host', nargs='+', + parser['config'].add_argument('host', nargs='*', help='one or more hosts 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 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 +115,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 +155,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)