~sircmpwn/dispatch.sr.ht

ref: 35faf0f4a3d948ae8b2780337b6a49fadc8f85e2 dispatch.sr.ht/dispatchsrht/tasks/github/github_pr_to_build.py -rw-r--r-- 7.2 KiB View raw
35faf0f4Drew DeVault Generalize build submission 7 months 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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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 import session
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_github_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')
        private = sa.Column(sa.Boolean, nullable=False, server_default='f')
        secrets = 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)
        auth = GitHubAuthorization.query.filter(
            GitHubAuthorization.user_id == current_user.id
        ).first()
        github = Github(auth.oauth_token)
        repo = github.get_repo(record.repo)
        if repo.private != record.private:
            record.private = repo.private
            if not repo.private:
                record.secrets = False
            db.session.commit()
        saved = session.pop("saved", False)
        return render_template("github/edit.html",
                task=task, record=record, saved=saved)

    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)
        secrets = valid.optional("secrets", cls=bool, default=False)
        record.automerge = bool(automerge)
        record.secrets = bool(secrets)
        if not record.private:
            record.secrets = False
        db.session.commit()
        session["saved"] = True
        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
            )
        secrets = hook.secrets
        if not base_repo["private"]:
            secrets = False
        return submit_github_build(hook, head_repo, head, base_repo,
                secrets=secrets, 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
        record.private = repo.private
        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))