mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-22 22:12:24 +01:00
Add automatic GitHub webhook creation
This commit is contained in:
parent
d4936e18ee
commit
e12aec4ccd
@ -18,19 +18,22 @@ from flask import Blueprint
|
||||
|
||||
bp = Blueprint("github", __name__)
|
||||
|
||||
from flask import redirect, url_for, request, flash, abort
|
||||
from flask_user import current_user
|
||||
from flask import redirect, url_for, request, flash, abort, render_template, jsonify
|
||||
from flask_user import current_user, login_required
|
||||
from sqlalchemy import func
|
||||
from flask_github import GitHub
|
||||
from app import github, csrf
|
||||
from app.models import db, User, APIToken, Package
|
||||
from app.utils import loginUser
|
||||
from app.utils import loginUser, randomString
|
||||
from app.blueprints.api.support import error, handleCreateRelease
|
||||
import hmac
|
||||
import hmac, requests, json
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import SelectField, SubmitField
|
||||
|
||||
@bp.route("/github/start/")
|
||||
def start():
|
||||
return github.authorize("")
|
||||
return github.authorize("", redirect_uri=url_for("github.callback"))
|
||||
|
||||
@bp.route("/github/callback/")
|
||||
@github.authorized_handler
|
||||
@ -40,8 +43,6 @@ def callback(oauth_token):
|
||||
flash("Authorization failed [err=gh-oauth-login-failed]", "danger")
|
||||
return redirect(url_for("user.login"))
|
||||
|
||||
import requests
|
||||
|
||||
# Get Github username
|
||||
url = "https://api.github.com/user"
|
||||
r = requests.get(url, headers={"Authorization": "token " + oauth_token})
|
||||
@ -121,11 +122,100 @@ def webhook():
|
||||
if event == "push":
|
||||
title = json["head_commit"]["message"].partition("\n")[0]
|
||||
ref = json["after"]
|
||||
elif event == "ping":
|
||||
return jsonify({ "success": True, "message": "Ping successful" })
|
||||
else:
|
||||
return error(400, "Unknown event, expected 'push'")
|
||||
return error(400, "Unsupported event. Only 'push' and 'ping' are supported.")
|
||||
|
||||
#
|
||||
# Perform release
|
||||
#
|
||||
|
||||
return handleCreateRelease(actual_token, package, title, ref)
|
||||
|
||||
|
||||
class SetupWebhookForm(FlaskForm):
|
||||
event = SelectField("Event Type", choices=[('push', 'Push'), ('tag', 'New tag')])
|
||||
submit = SubmitField("Save")
|
||||
|
||||
|
||||
@bp.route("/github/callback/webhook/")
|
||||
@github.authorized_handler
|
||||
def callback_webhook(oauth_token=None):
|
||||
pid = request.args.get("pid")
|
||||
if pid is None:
|
||||
abort(404)
|
||||
|
||||
current_user.github_access_token = oauth_token
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for("github.setup_webhook", pid=pid))
|
||||
|
||||
|
||||
@bp.route("/github/webhook/new/", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def setup_webhook():
|
||||
pid = request.args.get("pid")
|
||||
if pid is None:
|
||||
abort(404)
|
||||
|
||||
package = Package.query.get(pid)
|
||||
if package is None:
|
||||
abort(404)
|
||||
|
||||
gh_user, gh_repo = package.getGitHubFullName()
|
||||
if gh_user is None or gh_repo is None:
|
||||
flash("Unable to get Github full name from repo address", "danger")
|
||||
return redirect(package.getDetailsURL())
|
||||
|
||||
if current_user.github_access_token is None:
|
||||
return github.authorize("write:repo_hook", \
|
||||
redirect_uri=url_for("github.callback_webhook", pid=pid, _external=True))
|
||||
|
||||
form = SetupWebhookForm(formdata=request.form)
|
||||
if request.method == "POST" and form.validate():
|
||||
token = APIToken()
|
||||
token.name = "Github Webhook for " + package.title
|
||||
token.owner = current_user
|
||||
token.access_token = randomString(32)
|
||||
token.package = package
|
||||
|
||||
event = form.event.data
|
||||
if event != "push" and event != "tag":
|
||||
abort(500)
|
||||
|
||||
# Create webhook
|
||||
url = "https://api.github.com/repos/{}/{}/hooks".format(gh_user, gh_repo)
|
||||
data = {
|
||||
"name": "web",
|
||||
"active": True,
|
||||
"events": [event],
|
||||
"config": {
|
||||
"url": url_for("github.webhook", _external=True),
|
||||
"content_type": "json",
|
||||
"secret": token.access_token
|
||||
},
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": "token " + current_user.github_access_token
|
||||
}
|
||||
|
||||
r = requests.post(url, headers=headers, data=json.dumps(data))
|
||||
if r.status_code == 201:
|
||||
db.session.add(token)
|
||||
db.session.commit()
|
||||
|
||||
return redirect(package.getDetailsURL())
|
||||
elif r.status_code == 403:
|
||||
current_user.github_access_token = None
|
||||
db.session.commit()
|
||||
|
||||
return github.authorize("write:repo_hook", \
|
||||
redirect_uri=url_for("github.callback_webhook", pid=pid, _external=True))
|
||||
else:
|
||||
flash("Failed to create webhook, received response from Github: " +
|
||||
str(r.json().get("message") or r.status_code), "danger")
|
||||
|
||||
return render_template("github/setup_webhook.html", \
|
||||
form=form, package=package)
|
||||
|
@ -126,6 +126,9 @@ class User(db.Model, UserMixin):
|
||||
github_username = db.Column(db.String(50, collation="NOCASE"), nullable=True, unique=True)
|
||||
forums_username = db.Column(db.String(50, collation="NOCASE"), nullable=True, unique=True)
|
||||
|
||||
# Access token for webhook setup
|
||||
github_access_token = db.Column(db.String(50), nullable=True, server_default=None)
|
||||
|
||||
# User email information
|
||||
email = db.Column(db.String(255), nullable=True, unique=True)
|
||||
email_confirmed_at = db.Column(db.DateTime())
|
||||
@ -461,6 +464,31 @@ class Package(db.Model):
|
||||
def getIsFOSS(self):
|
||||
return self.license.is_foss and self.media_license.is_foss
|
||||
|
||||
def getIsOnGitHub(self):
|
||||
if self.repo is None:
|
||||
return False
|
||||
|
||||
url = urlparse(self.repo)
|
||||
return url.netloc == "github.com"
|
||||
|
||||
def getGitHubFullName(self):
|
||||
if self.repo is None:
|
||||
return None
|
||||
|
||||
url = urlparse(self.repo)
|
||||
if url.netloc != "github.com":
|
||||
return None
|
||||
|
||||
import re
|
||||
m = re.search(r"^\/([^\/]+)\/([^\/]+)\/?$", url.path)
|
||||
if m is None:
|
||||
return
|
||||
|
||||
user = m.group(1)
|
||||
repo = m.group(2).replace(".git", "")
|
||||
|
||||
return (user,repo)
|
||||
|
||||
def getSortedDependencies(self, is_hard=None):
|
||||
query = self.dependencies
|
||||
if is_hard is not None:
|
||||
|
@ -111,7 +111,7 @@
|
||||
<li class="alert alert-{{category}} container">
|
||||
<span class="icon_message"></span>
|
||||
|
||||
{{ message|safe }}
|
||||
{{ message }}
|
||||
|
||||
<div style="clear: both;"></div>
|
||||
</li>
|
||||
|
23
app/templates/github/setup_webhook.html
Normal file
23
app/templates/github/setup_webhook.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ _("Setup GitHub webhook") }}
|
||||
{% endblock %}
|
||||
|
||||
{% from "macros/forms.html" import render_field, render_submit_field, render_radio_field %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="mt-0">{{ self.title() }}</h1>
|
||||
|
||||
<div class="alert alert-info">
|
||||
{{ _("You can delete the webhook at any time by going into Settings > Webhooks on the repository.") }}
|
||||
</div>
|
||||
|
||||
<form method="POST" action="" enctype="multipart/form-data">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{{ render_field(form.event) }}
|
||||
|
||||
{{ render_submit_field(form.submit) }}
|
||||
</form>
|
||||
{% endblock %}
|
@ -364,6 +364,15 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if package.getIsOnGitHub() %}
|
||||
<p class="small text-centered">
|
||||
<a href="{{ url_for('github.setup_webhook', pid=package.id) }}">
|
||||
Set up a webhook
|
||||
</a>
|
||||
to create releases automatically.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="card my-4">
|
||||
<div class="card-header">
|
||||
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
|
||||
|
24
migrations/versions/7a48dbd05780_.py
Normal file
24
migrations/versions/7a48dbd05780_.py
Normal file
@ -0,0 +1,24 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 7a48dbd05780
|
||||
Revises: df66c78e6791
|
||||
Create Date: 2020-01-24 21:52:49.744404
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7a48dbd05780'
|
||||
down_revision = 'df66c78e6791'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('user', sa.Column('github_access_token', sa.String(length=50), nullable=True, server_default=None))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('user', 'github_access_token')
|
Loading…
Reference in New Issue
Block a user