~sircmpwn/dispatch.sr.ht

ref: e4b9f89a68a4cda43fb2c239a8b0ebc5b8eee0c3 dispatch.sr.ht/dispatchsrht/tasks/github/github_pr_to_build.py -rw-r--r-- 6.2 KiB View raw
e4b9f89a — √Čloi Rivard Define 'make' program via an environment variable 1 year, 11 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import sqlalchemy as sa
import sqlalchemy_utils as sau
from github import Github
from flask import Blueprint, redirect, request, render_template, url_for, abort
from flask_login import current_user
from jinja2 import Markup
from uuid import UUID, uuid4
from srht.database import Base, db
from srht.config import cfg
from srht.flask import icon, csrf_bypass
from srht.validation import Validation
from dispatchsrht.tasks import TaskDef
from dispatchsrht.tasks.github.auth import GitHubAuthorization
from dispatchsrht.tasks.github.auth import githubloginrequired
from dispatchsrht.tasks.github.auth import submit_build
from dispatchsrht.types import Task

_root = cfg("dispatch.sr.ht", "origin")
_builds_sr_ht = cfg("builds.sr.ht", "origin", default=None)
_github_client_id = cfg("dispatch.sr.ht::github",
        "oauth-client-id", default=None)
_github_client_secret = cfg("dispatch.sr.ht::github",
        "oauth-client-secret", default=None)

class GitHubPRToBuild(TaskDef):
    name = "github_pr_to_build"
    enabled = bool(_github_client_id
            and _github_client_secret
            and _builds_sr_ht)

    def description():
        return (icon("github") + Markup(" GitHub pull requests ") +
            icon("caret-right") + Markup(" builds.sr.ht jobs"))

    class _GitHubPRToBuildRecord(Base):
        __tablename__ = "github_pr_to_build"
        id = sa.Column(sau.UUIDType, primary_key=True)
        created = sa.Column(sa.DateTime, nullable=False)
        updated = sa.Column(sa.DateTime, nullable=False)
        user_id = sa.Column(sa.Integer,
                sa.ForeignKey("user.id", ondelete="CASCADE"))
        user = sa.orm.relationship("User")
        task_id = sa.Column(sa.Integer,
                sa.ForeignKey("task.id", ondelete="CASCADE"))
        task = sa.orm.relationship("Task")
        repo = sa.Column(sa.Unicode(1024), nullable=False)
        github_webhook_id = sa.Column(sa.Integer, nullable=False)
        automerge = sa.Column(sa.Boolean, nullable=False, server_default='f')

    blueprint = Blueprint("github_pr_to_build",
            __name__, template_folder="github_pr_to_build")

    def edit_GET(task):
        record = GitHubPRToBuild._GitHubPRToBuildRecord.query.filter(
            GitHubPRToBuild._GitHubPRToBuildRecord.task_id == task.id
        ).one_or_none()
        if not record:
            abort(404)
        return render_template("github/edit.html", task=task, record=record)

    def edit_POST(task):
        record = GitHubPRToBuild._GitHubPRToBuildRecord.query.filter(
            GitHubPRToBuild._GitHubPRToBuildRecord.task_id == task.id
        ).one_or_none()
        valid = Validation(request)
        automerge = valid.optional("automerge", cls=bool, default=False)
        record.automerge = bool(automerge)
        db.session.commit()
        return redirect(url_for("html.edit_task", task_id=task.id))

    @csrf_bypass
    @blueprint.route("/webhook/<record_id>", methods=["POST"])
    def _webhook(record_id):
        record_id = UUID(record_id)
        hook = GitHubPRToBuild._GitHubPRToBuildRecord.query.filter(
                GitHubPRToBuild._GitHubPRToBuildRecord.id == record_id
            ).first()
        if not hook:
            return "Unknown hook " + str(record_id), 404
        valid = Validation(request)
        pr = valid.require("pull_request")
        action = valid.require("action")
        if not valid.ok:
            return "Got request, but it has no commits"
        if action not in ["opened", "synchronize"]:
            return "Got update, but there are no new commits"
        head = pr["head"]
        base = pr["base"]
        base_repo = base["repo"]
        head_repo = head["repo"]
        auth = GitHubAuthorization.query.filter(
            GitHubAuthorization.user_id == hook.user_id).first()
        if not auth:
            return (
                "You have not authorized us to access your GitHub account", 401
            )
        return submit_build(hook, head_repo, head, base_repo,
                secrets=False, extras={
                    "automerge": hook.automerge, 
                    "pr": pr["number"]
                }, env={
                    "GITHUB_DELIVERY": request.headers.get("X-GitHub-Delivery"),
                    "GITHUB_EVENT": request.headers.get("X-GitHub-Event"),
                    "GITHUB_PR_NUMBER": str(pr["number"]),
                    "GITHUB_PR_TITLE": pr["title"],
                    "GITHUB_BASE_REPO": base_repo["full_name"],
                    "GITHUB_HEAD_REPO": head_repo["full_name"],
                })

    @blueprint.route("/configure")
    @githubloginrequired
    def configure(github):
        repos = github.get_user().get_repos(sort="updated")
        repos = filter(lambda r: r.permissions.admin and not r.fork, repos)
        existing = GitHubPRToBuild._GitHubPRToBuildRecord.query.filter(
                GitHubPRToBuild._GitHubPRToBuildRecord.user_id ==
                current_user.id).all()
        existing = [e.repo for e in existing]
        return render_template("github/select-repo.html",
                repos=repos, existing=existing)

    @blueprint.route("/configure", methods=["POST"])
    @githubloginrequired
    def _configure_POST(github):
        valid = Validation(request)
        repo = valid.require("repo")
        if not valid.ok:
            return "quit yo hackin bullshit"
        repo = github.get_repo(repo)
        if not repo:
            return "quit yo hackin bullshit"
        task = Task()
        task.name = "{}::github_pr_to_build".format(repo.full_name)
        task.user_id = current_user.id
        task._taskdef = "github_pr_to_build"
        db.session.add(task)
        db.session.flush()
        record = GitHubPRToBuild._GitHubPRToBuildRecord()
        record.id = uuid4()
        record.user_id = current_user.id
        record.task_id = task.id
        record.github_webhook_id = -1
        record.repo = repo.full_name
        db.session.add(record)
        db.session.flush()
        hook = repo.create_hook("web", {
            "url": _root + url_for("github_pr_to_build._webhook",
                record_id=record.id),
            "content_type": "json",
        }, ["pull_request"], active=True)
        record.github_webhook_id = hook.id
        db.session.commit()
        return redirect(url_for("html.edit_task", task_id=task.id))