#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # 2010-2011 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 . # # def commandline(): """Parse command line""" import argparse import cdist.banner import cdist.config import cdist.install # Construct parser others can reuse parser = {} # Options _all_ parsers have in common parser['loglevel'] = argparse.ArgumentParser(add_help=False) parser['loglevel'].add_argument('-d', '--debug', help='Set log level to debug', action='store_true', default=False) parser['loglevel'].add_argument('-v', '--verbose', help='Set log level to info, be more verbose', action='store_true', default=False) # Main subcommand parser 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") # Banner parser['banner'] = parser['sub'].add_parser('banner', parents=[parser['loglevel']]) parser['banner'].set_defaults(func=cdist.banner.banner) # Config and install (common stuff) parser['configinstall'] = argparse.ArgumentParser(add_help=False) parser['configinstall'].add_argument('host', nargs='+', help='one or more hosts to operate on') parser['configinstall'].add_argument('-c', '--cdist-home', help='Change cdist home (default: .. from bin directory)', action='store') parser['configinstall'].add_argument('-i', '--initial-manifest', help='Path to a cdist manifest', dest='manifest', required=False) parser['configinstall'].add_argument('-p', '--parallel', help='Operate on multiple hosts in parallel', action='store_true', dest='parallel') parser['configinstall'].add_argument('-s', '--sequential', help='Operate on multiple hosts sequentially (default)', action='store_false', dest='parallel') # Config parser['config'] = parser['sub'].add_parser('config', parents=[parser['loglevel'], parser['configinstall']]) parser['config'].set_defaults(func=config) # Install parser['install'] = parser['sub'].add_parser('install', parents=[parser['loglevel'], parser['configinstall']]) parser['install'].set_defaults(func=install) for p in parser: parser[p].epilog = "Get cdist at http://www.nico.schottelius.org/software/cdist/" args = parser['main'].parse_args(sys.argv[1:]) # Loglevels are handled globally in here and debug wins over verbose if args.verbose: logging.root.setLevel(logging.INFO) if args.debug: logging.root.setLevel(logging.DEBUG) log.debug(args) args.func(args) def config(args): configinstall(args, mode=cdist.config.Config) def install(args): configinstall(args, mode=cdist.install.Install) def configinstall(args, mode): """Configure or install remote system""" import multiprocessing import time try: process = {} failed_hosts = [] time_start = time.time() for host in args.host: if args.parallel: log.debug("Creating child process for %s", host) process[host] = multiprocessing.Process(target=configinstall_onehost, args=(host, args, mode, True)) process[host].start() else: if not configinstall_onehost(host, args, mode, parallel=False): failed_hosts.append(host) if args.parallel: for p in process.keys(): log.debug("Joining process %s", p) process[p].join() if not process[p].exitcode == 0: failed_hosts.append(p) if len(failed_hosts) > 0: log.warn("Failed to deploy to the following hosts: " + " ".join(failed_hosts)) time_end = time.time() log.info("Total processing time for %s host(s): %s", len(args.host), (time_end - time_start)) except KeyboardInterrupt: if args.parallel: for p in process.keys(): # NOT needed: KeyBoardInterrupet (aka SIGINT) # is forwarded to processes spawned by multiprocess! # pid = process[p].pid.__str__() #log.warn("Terminating deploy " + p + " (" + pid + ")") # process[p].terminate() pass sys.exit(0) def configinstall_onehost(host, args, mode, parallel): """Configure or install ONE remote system""" try: import cdist.context context = cdist.context.Context( target_host=host, initial_manifest=args.manifest, base_path=args.cdist_home, exec_path=sys.argv[0], debug=args.debug) c = mode(context) c.deploy_and_cleanup() context.cleanup() except cdist.Error as e: log.error(e) return False except KeyboardInterrupt: # Do not care in sequential mode, catch in parallel mode if not parallel: raise else: # Catch here, above does not need to know about our errors return False return True def emulator(): """Prepare and run emulator""" try: import cdist.emulator emulator = cdist.emulator.Emulator(sys.argv) emulator.run() except cdist.Error as e: log.error(e) sys.exit(1) if __name__ == "__main__": try: import logging import os import re import sys import cdist log = logging.getLogger("cdist") # Ensure our /lib/ is included into PYTHON_PATH sys.path.insert(0, os.path.abspath( os.path.join(os.path.dirname(os.path.realpath(__file__)), '../lib'))) logging.basicConfig(format='%(levelname)s: %(message)s') if re.match("__", os.path.basename(sys.argv[0])): emulator() else: commandline() except KeyboardInterrupt: sys.exit(0) sys.exit(0)