Esempio n. 1
0
def api_view_plugins_project(repo, username=None, namespace=None):
    """
    List project's plugins
    ----------------------
    List installed plugins on a project.

    ::

        GET /api/0/<repo>/settings/plugins
        GET /api/0/<namespace>/<repo>/settings/plugins

    ::

        GET /api/0/fork/<username>/<repo>/settings/plugins
        GET /api/0/fork/<username>/<namespace>/<repo>/settings/plugins

    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
            'plugins':
            [
                {
                    'Mail':
                    {
                        'mail_to': '*****@*****.**'
                    }
                }
            ],
            'total_plugins': 1
        }

    """
    repo = _get_repo(repo, username, namespace)

    plugins = {
        plugin[0]: plugin[1]
        for plugin in plugins_lib.get_enabled_plugins(repo)
    }
    output = {}

    output["plugins"] = []

    for (plugin, dbobj) in plugins.items():
        if dbobj:
            form = plugin.form(obj=dbobj)
            fields = _filter_fields(plugin)
            output["plugins"].append(
                {plugin.name: {field: form[field].data
                               for field in fields}})

    output["total_plugins"] = len(output["plugins"])

    jsonout = flask.jsonify(output)
    return jsonout
Esempio n. 2
0
def run_project_hooks(
    session,
    username,
    project,
    hooktype,
    repotype,
    repodir,
    changes,
    is_internal,
    pull_request,
):
    """ Function to run the hooks on a project

    This will first call all the plugins with a Runner on the project,
    and afterwards, for a non-repoSpanner repo, run all hooks/<hooktype>.*
    scripts in the repo.

    Args:
        session: Database session
        username (string): The user performing a push
        project (model.Project): The project this call is made for
        repotype (string): Value of lib.query.get_repotypes() indicating
            for which repo the currnet call is
        repodir (string): Directory where a clone of the specified repo is
            located. Do note that this might or might not be a writable
            clone.
        hooktype (string): The type of hook to run: pre-receive, update
            or post-receive
        changes (dict): A dict with keys being the ref to update, values being
            a tuple of (from, to).
        is_internal (bool): Whether this push originated from Pagure internally
        pull_request (model.PullRequest or None): The pull request whose merge
            is initiating this hook run.
    """
    debug = pagure_config.get("HOOK_DEBUG", False)

    # First we run dynamic ACLs
    authbackend = get_git_auth_helper()

    if (is_internal and username == "pagure"
            and repotype in ("tickets", "requests")):
        if debug:
            print("This is an internal push, dynamic ACL is pre-approved")
    elif not authbackend.is_dynamic:
        if debug:
            print("Auth backend %s is static-only" % authbackend)
    elif hooktype == "post-receive":
        if debug:
            print("Skipping auth backend during post-receive")
    else:
        if debug:
            print("Checking push request against auth backend %s" %
                  authbackend)
        todeny = []
        for refname in changes:
            change = changes[refname]
            authresult = authbackend.check_acl(
                session,
                project,
                username,
                refname,
                is_update=hooktype == "update",
                revfrom=change[0],
                revto=change[1],
                is_internal=is_internal,
                pull_request=pull_request,
                repotype=repotype,
                repodir=repodir,
            )
            if debug:
                print("Auth result for ref %s: %s" %
                      (refname, "Accepted" if authresult else "Denied"))
            if not authresult:
                print("Denied push for ref '%s' for user '%s'" %
                      (refname, username))
                todeny.append(refname)
        for toremove in todeny:
            del changes[toremove]
        if not changes:
            print("All changes have been rejected")
            sys.exit(1)

    # Now we run the hooks for plugins
    haderrors = False
    for plugin, _ in get_enabled_plugins(project):
        if not plugin.runner:
            if debug:
                print("Hook plugin %s should be ported to Runner" %
                      plugin.name)
        else:
            if debug:
                print("Running plugin %s" % plugin.name)

            try:
                plugin.runner.runhook(
                    session=session,
                    username=username,
                    hooktype=hooktype,
                    project=project,
                    repotype=repotype,
                    repodir=repodir,
                    changes=changes,
                )
            except Exception as e:
                if hooktype != "pre-receive" or debug:
                    traceback.print_exc()
                else:
                    print(str(e))
                haderrors = True

    if project.is_on_repospanner:
        # We are done. We are not doing any legacy hooks for repoSpanner
        return

    hookdir = os.path.join(repodir, "hooks")
    if not os.path.exists(hookdir):
        return

    stdin = ""
    args = []
    if hooktype == "update":
        refname = six.next(six.iterkeys(changes))
        (revfrom, revto) = changes[refname]
        args = [refname, revfrom, revto]
    else:
        stdin = ("\n".join([
            "%s %s %s" % (changes[refname] + (refname, ))
            for refname in changes
        ]) + "\n")
    stdin = stdin.encode("utf-8")

    if debug:
        print("Running legacy hooks (if any) with args: %s, stdin: %s" %
              (args, stdin))

    for hook in os.listdir(hookdir):
        # This is for legacy hooks, which create symlinks in the form of
        # "post-receive.$pluginname"
        if hook.startswith(hooktype + "."):
            hookfile = os.path.join(hookdir, hook)

            # By-pass all the old hooks that pagure may have created before
            # moving to the runner architecture
            if hook in pagure.lib.query.ORIGINAL_PAGURE_HOOK:
                continue

            if hook.endswith(".sample"):
                # Ignore the samples that Git inserts
                continue

            # Execute
            print("Running legacy hook %s. "
                  "Please ask your admin to port this to the new plugin "
                  "format, as the current system will cease functioning "
                  "in a future Pagure release" % hook)

            # Using subprocess.Popen rather than check_call so that stdin
            # can be passed without having to use a temporary file.
            proc = subprocess.Popen([hookfile] + args,
                                    cwd=repodir,
                                    stdin=subprocess.PIPE)
            proc.communicate(stdin)
            ecode = proc.wait()
            if ecode != 0:
                print("Hook %s errored out" % hook)
                haderrors = True

    if haderrors:
        session.close()
        raise SystemExit(1)
Esempio n. 3
0
def api_remove_plugin(repo, plugin, username=None, namespace=None):
    """
    Remove plugin
    --------------
    Remove a plugin from repository.

    ::

        POST /api/0/<repo>/settings/<plugin>/remove
        POST /api/0/<namespace>/<repo>/settings/<plugin>/remove

    ::

        POST /api/0/fork/<username>/<repo>/settings/<plugin>/remove
        POST /api/0/fork/<username>/<namespace>/<repo>/settings/<plugin>
             /remove

    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
          "plugin": {
            "mail_to": "*****@*****.**"
          },
          "message": "Hook 'Mail' deactivated"
        }

    """
    output = {}
    repo = _get_repo(repo, username, namespace)
    _check_token(repo, project_token=False)
    plugin = _check_plugin(repo, plugin)

    dbobj = plugin.db_object()

    enabled_plugins = {
        plugin[0]: plugin[1]
        for plugin in plugins_lib.get_enabled_plugins(repo)
    }

    # If the plugin is not installed raise error
    if plugin not in enabled_plugins.keys():
        raise pagure.exceptions.APIError(
            400, error_code=APIERROR.EPLUGINNOTINSTALLED)

    if enabled_plugins[plugin]:
        dbobj = enabled_plugins[plugin]

    form = plugin.form(obj=dbobj)
    form.active.data = False

    try:
        plugin.remove(repo)
    except pagure.exceptions.FileNotFoundException as err:
        flask.g.session.rollback()
        _log.exception(err)
        raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)

    try:
        flask.g.session.commit()
        output["message"] = "Hook '%s' deactivated" % plugin.name
        output["plugin"] = {
            field: form[field].data
            for field in _filter_fields(plugin)
        }
    except SQLAlchemyError as err:  # pragma: no cover
        flask.g.session.rollback()
        _log.exception(err)
        raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)

    jsonout = flask.jsonify(output)
    return jsonout
Esempio n. 4
0
def run_project_hooks(
    session,
    username,
    project,
    hooktype,
    repotype,
    repodir,
    changes,
    is_internal,
    pull_request,
):
    """ Function to run the hooks on a project

    This will first call all the plugins with a Runner on the project,
    and afterwards, for a non-repoSpanner repo, run all hooks/<hooktype>.*
    scripts in the repo.

    Args:
        session: Database session
        username (string): The user performing a push
        project (model.Project): The project this call is made for
        repotype (string): Value of lib.query.get_repotypes() indicating
            for which repo the currnet call is
        repodir (string): Directory where a clone of the specified repo is
            located. Do note that this might or might not be a writable
            clone.
        hooktype (string): The type of hook to run: pre-receive, update
            or post-receive
        changes (dict): A dict with keys being the ref to update, values being
            a tuple of (from, to).
        is_internal (bool): Whether this push originated from Pagure internally
        pull_request (model.PullRequest or None): The pull request whose merge
            is initiating this hook run.
    """
    debug = pagure_config.get("HOOK_DEBUG", False)

    # First we run dynamic ACLs
    authbackend = get_git_auth_helper()

    if (
        is_internal
        and username == "pagure"
        and repotype in ("tickets", "requests")
    ):
        if debug:
            print("This is an internal push, dynamic ACL is pre-approved")
    elif not authbackend.is_dynamic:
        if debug:
            print("Auth backend %s is static-only" % authbackend)
    elif hooktype == "post-receive":
        if debug:
            print("Skipping auth backend during post-receive")
    else:
        if debug:
            print(
                "Checking push request against auth backend %s" % authbackend
            )
        todeny = []
        for refname in changes:
            change = changes[refname]
            authresult = authbackend.check_acl(
                session,
                project,
                username,
                refname,
                is_update=hooktype == "update",
                revfrom=change[0],
                revto=change[1],
                is_internal=is_internal,
                pull_request=pull_request,
                repotype=repotype,
                repodir=repodir,
            )
            if debug:
                print(
                    "Auth result for ref %s: %s"
                    % (refname, "Accepted" if authresult else "Denied")
                )
            if not authresult:
                print(
                    "Denied push for ref '%s' for user '%s'"
                    % (refname, username)
                )
                todeny.append(refname)
        for toremove in todeny:
            del changes[toremove]
        if not changes:
            print("All changes have been rejected")
            sys.exit(1)

    # Now we run the hooks for plugins
    haderrors = False
    for plugin, _ in get_enabled_plugins(project):
        if not plugin.runner:
            if debug:
                print(
                    "Hook plugin %s should be ported to Runner" % plugin.name
                )
        else:
            if debug:
                print("Running plugin %s" % plugin.name)

            try:
                plugin.runner.runhook(
                    session=session,
                    username=username,
                    hooktype=hooktype,
                    project=project,
                    repotype=repotype,
                    repodir=repodir,
                    changes=changes,
                )
            except Exception as e:
                if hooktype != "pre-receive" or debug:
                    traceback.print_exc()
                else:
                    print(str(e))
                haderrors = True

    if project.is_on_repospanner:
        # We are done. We are not doing any legacy hooks for repoSpanner
        return

    hookdir = os.path.join(repodir, "hooks")
    if not os.path.exists(hookdir):
        return

    stdin = ""
    args = []
    if hooktype == "update":
        refname = six.next(six.iterkeys(changes))
        (revfrom, revto) = changes[refname]
        args = [refname, revfrom, revto]
    else:
        stdin = (
            "\n".join(
                [
                    "%s %s %s" % (changes[refname] + (refname,))
                    for refname in changes
                ]
            )
            + "\n"
        )
    stdin = stdin.encode("utf-8")

    if debug:
        print(
            "Running legacy hooks (if any) with args: %s, stdin: %s"
            % (args, stdin)
        )

    for hook in os.listdir(hookdir):
        # This is for legacy hooks, which create symlinks in the form of
        # "post-receive.$pluginname"
        if hook.startswith(hooktype + "."):
            hookfile = os.path.join(hookdir, hook)

            # By-pass all the old hooks that pagure may have created before
            # moving to the runner architecture
            if hook in pagure.lib.query.ORIGINAL_PAGURE_HOOK:
                continue

            if hook.endswith(".sample"):
                # Ignore the samples that Git inserts
                continue

            # Execute
            print(
                "Running legacy hook %s. "
                "Please ask your admin to port this to the new plugin "
                "format, as the current system will cease functioning "
                "in a future Pagure release" % hook
            )

            # Using subprocess.Popen rather than check_call so that stdin
            # can be passed without having to use a temporary file.
            proc = subprocess.Popen(
                [hookfile] + args, cwd=repodir, stdin=subprocess.PIPE
            )
            proc.communicate(stdin)
            ecode = proc.wait()
            if ecode != 0:
                print("Hook %s errored out" % hook)
                haderrors = True

    if haderrors:
        session.close()
        raise SystemExit(1)