示例#1
0
def main(argv=None):
    if argv is None:
        argv = sys.argv
    else:
        argv.insert(0, __file__)

    try:
        node = Node()
    except Exception as exc:
        print(exc, file=sys.stderr)
        return 1

    try:
        _main(node, argv)
        return 0
    except ex.Error as e:
        if str(e):
            print(e, file=sys.stderr)
        return 1
    except KeyboardInterrupt:
        sys.stderr.write("Keybord Interrupt\n")
        return 1
    finally:
        node.close()

    return 0
示例#2
0
def main(argv=None):
    node = Node()

    try:
        return _main(node, argv=argv)
    except ex.Error as exc:
        print(exc, file=sys.stderr)
        return 1
    except ex.Version as exc:
        print(exc)
        return 0
    finally:
        node.close()
示例#3
0
class Mgr(object):
    def __init__(self, parser=None, node=None, selector=None):
        self.parser = parser
        self.node = node
        self.selector = selector
        self.expanded_svcs = None

    @staticmethod
    def get_extra_argv(argv=None):
        """
        Extract oci/docker/podman/... passed-through argv from the command main argv.

        The service management command acts as a wrapper for podman and docker commands,
        setting the service-specific socket if necessary.
        """
        commands = ["oci", "podman", "docker"]
        if argv is None:
            argv = sys.argv[1:]
        if len(argv) < 2:
            return argv, []
        for command in commands:
            try:
                pos = argv.index(command)
                extra_argv = argv[pos + 1:]
                argv = argv[:pos + 1]
                return argv, extra_argv
            except Exception:
                extra_argv = []
        return argv, []

    @staticmethod
    def get_build_kwargs(options, action):
        """
        Return the service build function keyword arguments, deduced from
        parsed command options.
        """
        build_kwargs = {}

        if len(set(["paths", "status"]) & set(build_kwargs.keys())) == 0:
            if hasattr(options, "svcs") and options.svcs is not None:
                build_kwargs["paths"] = options.svcs

        if hasattr(options, "status") and options.status is not None:
            build_kwargs["status"] = [
                core.status.status_value(s) for s in options.status.split(",")
            ]

        build_kwargs["create_instance"] = action in ("create", "pull")

        return build_kwargs

    def do_svcs_action_detached(self, argv=None):
        """
        Executes the services action in detached process mode, so that
        a term/kill signal on the parent process does not abort the action.

        Keyboard interrupts do abort the detached process though.
        """
        ret = 0
        env = {}
        env.update(os.environ)
        env["OSVC_DETACHED"] = "1"
        env["OSVC_PARENT_SESSION_UUID"] = Env.session_uuid
        try:
            import subprocess
            import signal
            kwargs = {}
            try:
                kwargs["preexec_fn"] = os.setsid
            except AttributeError:
                pass
            prog = os.path.dirname(os.path.abspath(__file__))
            prog = os.path.join(prog, "..", "..", "__main__.py")
            prog = os.path.realpath(prog)
            if self.selector:
                executable = [sys.executable, prog, self.selector]
            elif os.environ.get("OSVC_KIND"):
                executable = [
                    sys.executable, prog,
                    os.environ.get("OSVC_KIND")
                ]
            else:
                executable = [sys.executable, prog]

            proc = subprocess.Popen(executable + argv,
                                    stdout=None,
                                    stderr=None,
                                    stdin=None,
                                    close_fds=True,
                                    cwd=os.sep,
                                    env=env,
                                    **kwargs)
            proc.wait()
            ret = proc.returncode
        except KeyboardInterrupt as exc:
            os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
            print("kill detached process")
            ret = 1
        except ex.Signal as exc:
            print("the action, detached as pid %d, "
                  "will continue executing" % proc.pid)
            ret = 1
        except Exception as exc:
            print(exc, file=sys.stderr)
            ret = 1
        return ret

    def do_svcs_action(self, options, action, argv):
        """
        Execute the services action, switching between detached mode for
        stop*/shutdown/unprovision/switch, and inline mode for other actions.
        """
        ret = 0

        if os.environ.get("OSVC_ACTION_ORIGIN") != "daemon" and \
                os.environ.get("OSVC_DETACHED") != "1" and \
                (action in ("stop", "shutdown", "unprovision") or
                 (action == "delete" and options.unprovision is True)):
            ret = self.do_svcs_action_detached(argv)
        else:
            try:
                ret = self.node.do_svcs_action(action, options)
            except ex.Error as exc:
                print(exc, file=sys.stderr)
                ret = 1
        return ret

    def prepare_options(self, options):
        """
        Prepare and return the options Storage() as expected by the Svc::action
        and Node::do_svcs_action methods.
        """
        opts = Storage()
        # preserve parm_svcs, as svcs will be expanded
        opts.parm_svcs = options.parm_svcs
        for key, val in options.__dict__.items():
            opts[key.replace("parm_", "")] = val
        try:
            namespace = options.namespace
        except AttributeError:
            namespace = None
        if namespace:
            opts.namespace = namespace
        elif "OSVC_NAMESPACE" in os.environ:
            opts.namespace = os.environ["OSVC_NAMESPACE"]
        if self.selector:
            opts.parm_svcs = self.selector
            opts.svcs = self.selector
        if opts.eval and not opts.format:
            opts.format = "json"
        return opts

    @staticmethod
    def split_env(arg):
        idx = arg.index("=")
        option = arg[:idx]
        value = arg[idx + 1:]
        return option, value

    def export_env_from_options(self, options):
        if options.get("daemon"):
            os.environ["OSVC_DETACHED"] = "1"
        for arg in options.get("env", []):
            if arg in ("-", "stdin", "/dev/stdin"):
                continue
            option, value = self.split_env(arg)
            option = option.upper()
            os.environ[option] = value

    @staticmethod
    def dispatch_svcs(paths):
        data = {}
        for path in paths:
            try:
                _, _, kind = split_path(path)
            except ValueError:
                continue
            try:
                validate_kind(kind)
            except ValueError as exc:
                raise ex.Error(str(exc))
            try:
                data[kind].append(path)
            except KeyError:
                data[kind] = [path]
        return data

    def get_action(self, argv):
        action = []
        _in = False
        for word in argv:
            if _in and word.startswith("-"):
                break
            if word.startswith("-"):
                continue
            action.append(word)
            _in = True
        return "_".join(action)

    def dispatch(self, argv):
        if self.selector is None:
            yield
        namespace = get_option("--namespace", argv)
        if namespace is None:
            namespace = os.environ.get("OSVC_NAMESPACE")
        action = self.get_action(argv)
        if action in ("create", "deploy"):
            expanded_svcs = self.selector.split(",")
        elif action == "ls":
            from commands.svc.parser import SvcOptParser
            parser = SvcOptParser()
            expanded_svcs = None
            yield parser
        else:
            local = action == "boot"
            expanded_svcs = self.node.svcs_selector(self.selector,
                                                    namespace,
                                                    local=local)
            self.expanded_svcs = expanded_svcs
        if expanded_svcs is not None:
            svc_by_kind = self.dispatch_svcs(expanded_svcs)
            for kind, paths in svc_by_kind.items():
                modname = "commands.{kind}.parser".format(kind=kind)
                mod = importlib.import_module(modname)
                parser = getattr(mod, kind.capitalize() + "OptParser")()
                yield parser

    def parse_args(self, argv):
        if self.parser:
            self.optparser = self.parser()
            return self.optparser.parse_args(argv)
        err = []
        for parser in self.dispatch(argv):
            if not parser:
                continue
            self.optparser = parser
            try:
                options, action = self.optparser.parse_args(argv)
                return options, action
            except Exception as exc:
                # if any parser accepts this argv, don't display errors
                # raised by other parsers. keep them around in case no
                # parser accepts.
                buff = str(exc)
                if not buff.startswith(self.optparser.prog):
                    buff = "%s: %s" % (self.optparser.prog, buff)
                err.append(buff)
                pass
        # no parser matched. display a per-parser errorlog
        raise ex.Error("\n".join(err))

    def _main(self, argv=None):
        """
        Build the service list.
        Execute action-specific codepaths.
        """
        build_err = False
        ret = 0

        argv, extra_argv = self.get_extra_argv(argv)
        try:
            options, action = self.parse_args(argv)
        except ex.Error as exc:
            if str(exc):
                raise
            else:
                raise ex.Error("no match")
        if action == "deploy":
            action = "create"
            options.provision = True
        options = self.prepare_options(options)
        self.export_env_from_options(options)
        options.extra_argv = extra_argv
        utilities.render.color.use_color = options.color
        try:
            self.node.options.format = options.format
            self.node.options.jsonpath_filter = options.jsonpath_filter
        except AttributeError:
            pass
        if action not in (
                "ls", "monitor",
                "create") and options.svcs is None and options.status is None:
            raise ex.Error("no service selected.")
        if action in ("ls", "monitor") and options.svcs is None:
            kind = os.environ.get("OSVC_KIND", "svc")
            options.svcs = "*/%s/*" % kind

        if action == "create":
            if options.svcs:
                options.svcs = options.svcs.split(",")
        else:
            if self.expanded_svcs is None:
                local = action == "boot"
                expanded_svcs = self.node.svcs_selector(options.svcs,
                                                        options.namespace,
                                                        local=local)
            else:
                expanded_svcs = self.expanded_svcs
            if options.svcs in (None, "*") and expanded_svcs == []:
                return
            options.svcs = expanded_svcs

        self.node.set_rlimit()
        build_kwargs = self.get_build_kwargs(options, action)

        if action != "create":
            try:
                self.node.build_services(**build_kwargs)
            except ex.Error as exc:
                if len(str(exc)) > 0:
                    print(exc, file=sys.stderr)
                build_err = True

        if action not in ("ls", "create") and len(options.svcs) == 0:
            if not build_err:
                sys.stderr.write("no match\n")
            return 1

        if action == "create":
            return self.node.create_service(options.svcs, options)

        ret = self.do_svcs_action(options, action, argv=argv)

        try:
            import logging
            logging.shutdown()
        except:
            pass

        return ret

    def __call__(self, argv=None):
        """
        Instanciate a Node object.
        Call the real deal making sure the node is finally freed.
        """
        ret = 0

        if self.node is None:
            try:
                self.node = Node()
            except Exception as exc:
                print(exc, file=sys.stderr)
                return 1

        check_privs()

        try:
            ret = self._main(argv=argv)
        except ex.Error as exc:
            print(exc, file=sys.stderr)
            return 1
        except ex.Version as exc:
            print(exc)
            return 0
        except KeyboardInterrupt:
            return 1
        finally:
            self.node.close()

        if ret is None:
            ret = 0

        return ret