mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-08 22:17:34 +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__)
|
bp = Blueprint("github", __name__)
|
||||||
|
|
||||||
from flask import redirect, url_for, request, flash, abort
|
from flask import redirect, url_for, request, flash, abort, render_template, jsonify
|
||||||
from flask_user import current_user
|
from flask_user import current_user, login_required
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from flask_github import GitHub
|
from flask_github import GitHub
|
||||||
from app import github, csrf
|
from app import github, csrf
|
||||||
from app.models import db, User, APIToken, Package
|
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
|
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/")
|
@bp.route("/github/start/")
|
||||||
def start():
|
def start():
|
||||||
return github.authorize("")
|
return github.authorize("", redirect_uri=url_for("github.callback"))
|
||||||
|
|
||||||
@bp.route("/github/callback/")
|
@bp.route("/github/callback/")
|
||||||
@github.authorized_handler
|
@github.authorized_handler
|
||||||
@ -40,8 +43,6 @@ def callback(oauth_token):
|
|||||||
flash("Authorization failed [err=gh-oauth-login-failed]", "danger")
|
flash("Authorization failed [err=gh-oauth-login-failed]", "danger")
|
||||||
return redirect(url_for("user.login"))
|
return redirect(url_for("user.login"))
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Get Github username
|
# Get Github username
|
||||||
url = "https://api.github.com/user"
|
url = "https://api.github.com/user"
|
||||||
r = requests.get(url, headers={"Authorization": "token " + oauth_token})
|
r = requests.get(url, headers={"Authorization": "token " + oauth_token})
|
||||||
@ -121,11 +122,100 @@ def webhook():
|
|||||||
if event == "push":
|
if event == "push":
|
||||||
title = json["head_commit"]["message"].partition("\n")[0]
|
title = json["head_commit"]["message"].partition("\n")[0]
|
||||||
ref = json["after"]
|
ref = json["after"]
|
||||||
|
elif event == "ping":
|
||||||
|
return jsonify({ "success": True, "message": "Ping successful" })
|
||||||
else:
|
else:
|
||||||
return error(400, "Unknown event, expected 'push'")
|
return error(400, "Unsupported event. Only 'push' and 'ping' are supported.")
|
||||||
|
|
||||||
#
|
#
|
||||||
# Perform release
|
# Perform release
|
||||||
#
|
#
|
||||||
|
|
||||||
return handleCreateRelease(actual_token, package, title, ref)
|
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)
|
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)
|
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
|
# User email information
|
||||||
email = db.Column(db.String(255), nullable=True, unique=True)
|
email = db.Column(db.String(255), nullable=True, unique=True)
|
||||||
email_confirmed_at = db.Column(db.DateTime())
|
email_confirmed_at = db.Column(db.DateTime())
|
||||||
@ -461,6 +464,31 @@ class Package(db.Model):
|
|||||||
def getIsFOSS(self):
|
def getIsFOSS(self):
|
||||||
return self.license.is_foss and self.media_license.is_foss
|
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):
|
def getSortedDependencies(self, is_hard=None):
|
||||||
query = self.dependencies
|
query = self.dependencies
|
||||||
if is_hard is not None:
|
if is_hard is not None:
|
||||||
|
@ -111,7 +111,7 @@
|
|||||||
<li class="alert alert-{{category}} container">
|
<li class="alert alert-{{category}} container">
|
||||||
<span class="icon_message"></span>
|
<span class="icon_message"></span>
|
||||||
|
|
||||||
{{ message|safe }}
|
{{ message }}
|
||||||
|
|
||||||
<div style="clear: both;"></div>
|
<div style="clear: both;"></div>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</div>
|
</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 my-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
|
{% 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