Esempio n. 1
0
  def __init__(self, argv=None, *, cmd=None, **kw_options):
    ''' Initialise the command line.
        Raises `GetoptError` for unrecognised options.

        Parameters:
        * `argv`:
          optional command line arguments
          including the main command name if `cmd` is not specified.
          The default is `sys.argv`.
          The contents of `argv` are copied,
          permitting desctructive parsing of `argv`.
        * `options`:
          a optional object for command state and context.
          If not specified a new `SimpleNamespace`
          is allocated for use as `options`,
          and prefilled with `.cmd` set to `cmd`
          and other values as set by `.apply_defaults()`
          if such a method is provided.
        * `cmd`:
          optional command name for context;
          if this is not specified it is taken from `argv.pop(0)`.
        Other keyword arguments are applied to `self.options`
        as attributes.

        The command line arguments are parsed according to
        the optional `GETOPT_SPEC` class attribute (default `''`).
        If `getopt_spec` is not empty
        then `apply_opts(opts)` is called
        to apply the supplied options to the state
        where `opts` is the return from `getopt.getopt(argv,getopt_spec)`.

        After the option parse,
        if the first command line argument *foo*
        has a corresponding method `cmd_`*foo*
        then that argument is removed from the start of `argv`
        and `self.cmd_`*foo*`(argv,options,cmd=`*foo*`)` is called
        and its value returned.
        Otherwise `self.main(argv,options)` is called
        and its value returned.

        If the command implementation requires some setup or teardown
        then this may be provided by the `run_context`
        context manager method,
        called with `cmd=`*subcmd* for subcommands
        and with `cmd=None` for `main`.
    '''
    subcmds = self.subcommands()
    has_subcmds = subcmds and list(subcmds) != ['help']
    options = self.options = self.OPTIONS_CLASS()
    if argv is None:
      argv = list(sys.argv)
      if cmd is not None:
        # consume the first argument anyway
        argv.pop(0)
    else:
      argv = list(argv)
    if cmd is None:
      cmd = basename(argv.pop(0))
    log_level = getattr(options, 'log_level', None)
    loginfo = setup_logging(cmd, level=log_level)
    # post: argv is list of arguments after the command name
    self.cmd = cmd
    self.loginfo = loginfo
    self.apply_defaults()
    # override the default options
    for option, value in kw_options.items():
      setattr(options, option, value)
    self._argv = argv
    self._run = lambda subcmd, command, argv: 2
    self._subcmd = None
    self._printed_usage = False
    # we catch GetoptError from this suite...
    subcmd = None  # default: no subcmd specific usage available
    short_usage = False
    try:
      getopt_spec = getattr(self, 'GETOPT_SPEC', '')
      # catch bare -h or --help if no 'h' in the getopt_spec
      if ('h' not in getopt_spec and len(argv) == 1
          and argv[0] in ('-h', '-help', '--help')):
        argv = ['help']
      else:
        # we do this regardless in order to honour '--'
        try:
          opts, argv = getopt(argv, getopt_spec, '')
        except GetoptError:
          short_usage = True
          raise
        self.apply_opts(opts)
        # we do this regardless so that subclasses can do some presubcommand parsing
        # after any command line options
        argv = self._argv = self.apply_preargv(argv)

      # now prepare self._run, a callable
      if not has_subcmds:
        # no subcommands, just use the main() method
        try:
          main = self.main
        except AttributeError:
          # pylint: disable=raise-missing-from
          raise GetoptError("no main method and no subcommand methods")
        self._run = _MethodSubCommand(None, main)
      else:
        # expect a subcommand on the command line
        if not argv:
          default_argv = getattr(self, 'SUBCOMMAND_ARGV_DEFAULT', None)
          if not default_argv:
            short_usage = True
            raise GetoptError(
                "missing subcommand, expected one of: %s" %
                (', '.join(sorted(subcmds.keys())),)
            )
          argv = (
              [default_argv]
              if isinstance(default_argv, str) else list(default_argv)
          )
        subcmd = argv.pop(0)
        subcmd_ = subcmd.replace('-', '_')
        try:
          subcommand = subcmds[subcmd_]
        except KeyError:
          # pylint: disable=raise-missing-from
          short_usage = True
          bad_subcmd = subcmd
          subcmd = None
          raise GetoptError(
              "%s: unrecognised subcommand, expected one of: %s" % (
                  bad_subcmd,
                  ', '.join(sorted(subcmds.keys())),
              )
          )
        self._run = subcommand
      self._subcmd = subcmd
    except GetoptError as e:
      if self.getopt_error_handler(cmd, self.options, e,
                                   self.usage_text(subcmd=subcmd,
                                                   short=short_usage)):
        self._printed_usage = True
        return
      raise
Esempio n. 2
0
def main(argv=None):
    ''' Command line main programme.
  '''
    if argv is None:
        argv = sys.argv
    cmd = basename(argv.pop(0))
    usage = USAGE.format(cmd=cmd, TEST_RATE=TEST_RATE, VARRUN=VARRUN)
    setup_logging(cmd)
    badopts = False
    try:
        if not argv:
            raise GetoptError("missing arguments")
        arg0 = argv[0]
        if arg0 == 'disable':
            argv.pop(0)
            for name in argv:
                SvcD([], name=name).disable()
            return 0
        if arg0 == 'enable':
            argv.pop(0)
            for name in argv:
                SvcD([], name=name).enable()
            return 0
        if arg0 == 'restart':
            argv.pop(0)
            for name in argv:
                SvcD([], name=name).restart()
            return 0
        if arg0 == 'stop':
            argv.pop(0)
            for name in argv:
                SvcD([], name=name).stop()
            return 0
        once = False
        use_lock = False
        lock_name = None
        name = None
        svc_pidfile = None  # pid file for the service process
        mypidfile = None  # pid file for the svcd
        quiet = False
        sig_shcmd = None
        test_shcmd = None
        test_rate = TEST_RATE
        uid = os.geteuid()
        username = getpwuid(uid).pw_name
        run_uid = uid
        run_username = username
        test_uid = uid
        test_username = username
        test_flags = {}
        trace = sys.stderr.isatty()
        opts, argv = getopt(argv, '1lF:L:n:p:P:qs:t:T:u:U:x')
        for opt, value in opts:
            with Pfx(opt):
                if opt == '-1':
                    once = True
                elif opt == '-l':
                    use_lock = True
                elif opt == '-F':
                    for flagname in value.split(','):
                        with Pfx(flagname):
                            truthiness = True
                            if flagname.startswith('!'):
                                truthiness = False
                                flagname = flagname[1:]
                            if not flagname:
                                warning("invalid empty flag name")
                                badopts = True
                            else:
                                test_flags[flagname] = truthiness
                elif opt == '-L':
                    use_lock = True
                    lock_name = value
                elif opt == '-n':
                    name = value
                elif opt == '-p':
                    svc_pidfile = value
                elif opt == '-P':
                    mypidfile = value
                elif opt == '-q':
                    quiet = True
                elif opt == '-s':
                    sig_shcmd = value
                elif opt == '-t':
                    test_shcmd = value
                elif opt == '-T':
                    try:
                        test_rate = int(value)
                    except ValueError as e:
                        raise GetoptError(
                            "testrate should be a valid integer: %s" % (e, ))
                elif opt == '-u':
                    run_username = value
                    run_uid = getpwnam(run_username).pw_uid
                elif opt == '-U':
                    test_username = value
                    test_uid = getpwnam(test_username).pw_uid
                elif opt == '-x':
                    trace = True
                else:
                    raise RuntimeError("unhandled option")
        if use_lock and name is None:
            raise GetoptError("-l (lock) requires a name (-n)")
        if not argv:
            raise GetoptError("missing command")
    except GetoptError as e:
        warning("%s", e)
        badopts = True
    if badopts:
        print(usage, file=sys.stderr)
        return 2
    if sig_shcmd is None:
        sig_func = None
    else:

        def sig_func():
            argv = ['sh', ('-xc' if trace else '-c'), sig_shcmd]
            if test_uid != uid:
                su_shcmd = 'exec ' + quotecmd(argv)
                if trace:
                    su_shcmd = 'set -x; ' + su_shcmd
                argv = ['su', test_username, '-c', su_shcmd]
            P = LockedPopen(argv, stdin=DEVNULL, stdout=PIPE)
            sig_text = P.stdout.read()
            returncode = P.wait()
            if returncode != 0:
                warning("returncode %s from %r", returncode, sig_shcmd)
                sig_text = None
            return sig_text

    if test_shcmd is None:
        test_func = None
    else:

        def test_func():
            with Pfx("main.test_func: shcmd=%r", test_shcmd):
                argv = ['sh', '-c', test_shcmd]
                if test_uid != uid:
                    argv = ['su', test_username, 'exec ' + quotecmd(argv)]
                shcmd_ok = callproc(argv, stdin=DEVNULL) == 0
                if not quiet:
                    info("exit status != 0")
                return shcmd_ok

    if run_uid != uid:
        argv = ['su', run_username, 'exec ' + quotecmd(argv)]
    if use_lock:
        argv = ['lock', '--', 'svcd-' + name] + argv
    S = SvcD(argv,
             name=name,
             pidfile=svc_pidfile,
             sig_func=sig_func,
             test_flags=test_flags,
             test_func=test_func,
             test_rate=test_rate,
             once=once,
             quiet=quiet,
             trace=trace)

    def signal_handler(*_):
        S.stop()
        S.wait()
        S.flag_stop = False
        sys.exit(1)

    signal(SIGHUP, signal_handler)
    signal(SIGINT, signal_handler)
    signal(SIGTERM, signal_handler)
    if S.pidfile or mypidfile:
        if mypidfile is None:
            pidfile_base, pidfile_ext = splitext(S.pidfile)
            mypidfile = pidfile_base + '-svcd' + pidfile_ext
        with PidFileManager(mypidfile):
            S.start()
            S.wait()
    else:
        S.start()
        S.wait()