[enh] uses uuid in job urls instead

This commit is contained in:
Laurent Peuch 2021-01-21 23:37:19 +01:00
parent f1dad89c83
commit 79cc2cad2f
6 changed files with 41 additions and 25 deletions

View file

@ -22,6 +22,7 @@ class Repo(peewee.Model):
class Job(peewee.Model):
name = peewee.CharField()
uuid = peewee.CharField(unique=True)
url_or_path = peewee.CharField()
state = peewee.CharField(choices=(

50
run.py
View file

@ -4,6 +4,7 @@
import os
import sys
import argh
import string
import random
import logging
import asyncio
@ -106,6 +107,10 @@ subscriptions = defaultdict(list)
jobs_in_memory_state = {}
def generate_random_uuid():
return ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for n in range(10))
@asyncio.coroutine
def wait_closed(self):
"""
@ -134,7 +139,7 @@ def reset_busy_workers():
def merge_jobs_on_startup():
task_logger.info(f"looks for jobs to merge on startup")
task_logger.info("looks for jobs to merge on startup")
query = Job.select().where(Job.state == "scheduled").order_by(Job.name, -Job.id)
@ -174,6 +179,7 @@ async def create_job(app_id, repo_url, job_comment=""):
job = Job.create(
name=job_name,
uuid=generate_random_uuid(),
url_or_path=repo_url,
state="scheduled",
)
@ -525,6 +531,7 @@ async def ws_index(request, websocket):
selected_fields = (
Job.id,
Job.name,
Job.uuid,
Job.url_or_path,
Job.state,
Job.created_time,
@ -558,6 +565,8 @@ async def ws_index(request, websocket):
first_chunck = next(data)
print(first_chunck)
await websocket.send(ujson.dumps({
"action": "init_jobs",
"data": first_chunck, # send first chunk
@ -572,10 +581,10 @@ async def ws_index(request, websocket):
await websocket.wait_closed()
@app.websocket('/job-<job_id>-ws')
@app.websocket('/job-<job_uuid>-ws')
@clean_websocket
async def ws_job(request, websocket, job_id):
job = Job.select().where(Job.id == job_id)
async def ws_job(request, websocket, job_uuid):
job = Job.select().where(Job.uuid == job_uuid)
if job.count == 0:
raise NotFound()
@ -611,6 +620,7 @@ async def ws_apps(request, websocket):
"random_job_day",
"job_id",
"job_name",
"job_uuid",
"job_state",
"created_time",
"started_time",
@ -621,6 +631,7 @@ async def ws_apps(request, websocket):
SELECT
"t1"."id" as "job_id",
"t1"."name" as "job_name",
"t1"."uuid" as "job_uuid",
"t1"."url_or_path",
"t1"."state" as "job_state",
"t1"."created_time",
@ -659,6 +670,7 @@ async def ws_apps(request, websocket):
"random_job_day": x.random_job_day,
"job_id": x.job_id,
"job_name": x.job_name,
"job_uuid": x.job_uuid,
"job_state": x.job_state,
"created_time": datetime.strptime(x.created_time.split(".")[0], '%Y-%m-%d %H:%M:%S') if x.created_time else None,
"started_time": datetime.strptime(x.started_time.split(".")[0], '%Y-%m-%d %H:%M:%S') if x.started_time else None,
@ -679,6 +691,7 @@ async def ws_apps(request, websocket):
"random_job_day": repo.random_job_day,
"job_id": None,
"job_name": None,
"job_uuid": None,
"job_state": None,
"created_time": None,
"started_time": None,
@ -736,9 +749,9 @@ def require_token():
token = request.headers["X-Token"].strip()
if token not in tokens:
api_logger.warning(f"someone tried to access the API using "
"the {token} but it's not a valid token in "
"the 'tokens' file")
api_logger.warning(f"someone tried to access the API using \
the {token} but it's not a valid token in \
the 'tokens' file")
return response.json({'status': 'invalide token'}, 403)
result = await f(request, *args, **kwargs)
@ -752,11 +765,12 @@ def require_token():
async def api_new_job(request):
job = Job.create(
name=request.json["name"],
uuid=generate_random_uuid(),
url_or_path=request.json["url_or_path"],
created_time=datetime.now(),
)
api_logger.info(f"Request to add new job '{job.name}' [{job.id}]")
api_logger.info(f"Request to add new job '{job.name}' [{job.id}/{job.uuid}]")
await broadcast({
"action": "new_job",
@ -795,7 +809,7 @@ async def api_delete_job(request, job_id):
# no need to check if job exist, api_stop_job will do it for us
job = Job.select().where(Job.id == job_id)[0]
api_logger.info(f"Request to delete job '{job.name}' [{job.id}]")
api_logger.info(f"Request to delete job '{job.name}' [{job.id}/{job.uuid}]")
data = model_to_dict(job)
job.delete_instance()
@ -910,10 +924,10 @@ async def api_badge_job(request, job_id):
})
@app.route('/job/<job_id>')
@app.route('/job/<job_uuid>')
@jinja.template('job.html')
async def html_job(request, job_id):
job = Job.select().where(Job.id == job_id)
async def html_job(request, job_uuid):
job = Job.select().where(Job.uuid == job_uuid)
if job.count == 0:
raise NotFound()
@ -989,7 +1003,7 @@ async def github(request):
# (which also allows to only enable this feature if
# we define the webhook secret)
if not os.path.exists("./github_webhook_secret") or not os.path.exists("./github_bot_token"):
api_logger.info(f"Received a webhook but no ./github_webhook_secret or ./github_bot_token file exists ... ignoring")
api_logger.info("Received a webhook but no ./github_webhook_secret or ./github_bot_token file exists ... ignoring")
abort(403)
# Only SHA1 is supported
@ -1008,7 +1022,7 @@ async def github(request):
mac = hmac.new(secret.encode(), msg=request.body, digestmod=hashlib.sha1)
if not hmac.compare_digest(str(mac.hexdigest()), str(signature)):
api_logger.info(f"Received a webhook but signature authentication failed (is the secret properly configured?)")
api_logger.info("Received a webhook but signature authentication failed (is the secret properly configured?)")
abort(403, "Bad signature ?!")
hook_type = request.headers.get("X-Github-Event")
@ -1018,9 +1032,9 @@ async def github(request):
# - *New* comments
# - On issue/PRs which are still open
if hook_type != "issue_comment" \
or hook_infos["action"] != "created" \
or hook_infos["issue"]["state"] != "open" \
or "pull_request" not in hook_infos["issue"]:
or hook_infos["action"] != "created" \
or hook_infos["issue"]["state"] != "open" \
or "pull_request" not in hook_infos["issue"]:
# Nothing to do but success anyway (204 = No content)
abort(204, "Nothing to do")
@ -1071,7 +1085,7 @@ async def github(request):
async with session.post(comments_url, data=ujson.dumps({"body": body})) as resp:
api_logger.info("Added comment %s" % resp.json()["html_url"])
catchphrases = ["Alrighty!", "Fingers crossed!", "May the CI gods be with you!", ":carousel_horse:", ":rocket:", ":sunflower:", "Meow :cat2:", ":v:", ":stuck_out_tongue_winking_eye:" ]
catchphrases = ["Alrighty!", "Fingers crossed!", "May the CI gods be with you!", ":carousel_horse:", ":rocket:", ":sunflower:", "Meow :cat2:", ":v:", ":stuck_out_tongue_winking_eye:"]
catchphrase = random.choice(catchphrases)
# Dirty hack with base_url passed from cmd argument because we can't use request.url_for because Sanic < 20.x
job_url = app.config.base_url + app.url_for("html_job", job_id=job.id)

View file

@ -13,8 +13,8 @@
<th>Started time</th>
<th>End time</th>
</thead>
<tr v-for="(job, index) in jobs" :id="job.id" v-bind:class="[{deleted: job.deleted}, job.state + 'Job']">
<td><a v-if="!job.deleted" v-bind:href="'<{ relative_path_to_root }>job/' + job.id">#{{job.id}}</a><span v-if="job.deleted">#{{job.id}} (deleted)</span></td>
<tr v-for="(job, index) in jobs" :id="job.uuid" v-bind:class="[{deleted: job.deleted}, job.state + 'Job']">
<td><a v-if="!job.deleted" v-bind:href="'<{ relative_path_to_root }>job/' + job.uuid">#{{job.uuid}}</a><span v-if="job.deleted">#{{job.id}} (deleted)</span></td>
<td>{{job.state}}</td>
<td>{{timestampToDate(job.created_time)}}</td>
<td>{{timestampToDate(job.started_time)}}</td>

View file

@ -20,7 +20,7 @@
<template v-if="apps.length > 0">
<tr v-for="(app, index) in apps" :id="app.id" v-bind:class="[app.job_state + 'Job']">
<td><a v-bind:href="app.name + '/'">{{app.name}}</a></td>
<td><a v-bind:href="'<{ relative_path_to_root }>job/' + app.job_id">#{{app.job_id}}</a> <span v-bind:class="['tag', jobStateTagClass(app.job_state)]">{{app.job_state}}</span></td>
<td><a v-bind:href="'<{ relative_path_to_root }>job/' + app.job_uuid">#{{app.job_uuid}}</a> <span v-bind:class="['tag', jobStateTagClass(app.job_state)]">{{app.job_state}}</span></td>
<td>{{timestampToDate(app.created_time)}}</td>
<td>{{timestampToDate(app.started_time)}}</td>
<td>{{timestampToDate(app.end_time)}}</td>
@ -140,6 +140,7 @@
if (app.apps[i].url == data.url_or_path) {
Vue.set(app.apps[i], 'job_id', data.id);
Vue.set(app.apps[i], 'job_name', data.name);
Vue.set(app.apps[i], 'job_uuid', data.uuid);
Vue.set(app.apps[i], 'job_state', data.state);
Vue.set(app.apps[i], 'created_time', data.created_time);
Vue.set(app.apps[i], 'started_time', data.started_time);

View file

@ -15,8 +15,8 @@
<th>End time</th>
</thead>
<template v-if="jobs.length > 0">
<tr v-for="(job, index) in jobs" :id="job.id" v-bind:class="[{deleted: job.deleted}, job.state + 'Job']">
<td><a v-if="!job.deleted" v-bind:href="'job/' + job.id">{{job.name}}</a><span v-if="job.deleted">{{job.name}} (deleted)</span> <small title="job's id">#{{job.id}} </small></td>
<tr v-for="(job, index) in jobs" :id="job.uuid" v-bind:class="[{deleted: job.deleted}, job.state + 'Job']">
<td><a v-if="!job.deleted" v-bind:href="'job/' + job.uuid">{{job.name}}</a><span v-if="job.deleted">{{job.name}} (deleted)</span> <small title="job's uuid">#{{job.uuid}} </small></td>
<td>{{job.state}}</td>
<td>{{timestampToDate(job.created_time)}}</td>
<td>{{timestampToDate(job.started_time)}}</td>

View file

@ -5,7 +5,7 @@
<div class="container" v-bind:class="{deleted: job.deleted}">
<h1 class="title">
<span v-if="job.deleted">(DELETED)</span>
Job #{{job.id}}
Job #{{job.uuid}}
<% if app %><a href="<{ relative_path_to_root }>apps/<{ app.name }>/">{{ job.name }} </a>
<% else %>{{ job.name }}
<% endif %>
@ -63,7 +63,7 @@
}
})
ws = new ReconnectingWebSocket(websocketPrefix() + '://' + document.domain + ':' + location.port + websocketRelativePath('<{ path }>') + '/job-<{ job.id }>-ws');
ws = new ReconnectingWebSocket(websocketPrefix() + '://' + document.domain + ':' + location.port + websocketRelativePath('<{ path }>') + '/job-<{ job.uuid }>-ws');
ws.onmessage = function (event) {
var message = JSON.parse(event.data);