jenkins: Add Jenkins webhook
Using the [Generic Event Plugin][0], we can receive a notification from Jenkins when builds start and finish. We'll relay these to *ntfy* on a unique topic that I will subscribe to on my desktop. That way, I can get desktop notifications about jobs while I am working, which will be particularly useful while developing and troubleshooting pipelines. [0]: https://plugins.jenkins.io/generic-event/master
parent
55df6f61a7
commit
9cf2345aa9
|
@ -1,3 +1,4 @@
|
||||||
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
import logging
|
import logging
|
||||||
|
@ -44,6 +45,7 @@ MAX_DOCUMENT_SIZE = int(
|
||||||
50 * 2**20,
|
50 * 2**20,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
NTFY_URL = os.environ.get('NTFY_URL', 'http://ntfy.ntfy:2586')
|
||||||
PAPERLESS_URL = os.environ.get(
|
PAPERLESS_URL = os.environ.get(
|
||||||
'PAPERLESS_URL',
|
'PAPERLESS_URL',
|
||||||
'http://paperless-ngx',
|
'http://paperless-ngx',
|
||||||
|
@ -308,6 +310,55 @@ def response_filename(response: httpx.Response) -> str:
|
||||||
return response.url.path.rstrip('/').rsplit('/', 1)[-1]
|
return response.url.path.rstrip('/').rsplit('/', 1)[-1]
|
||||||
|
|
||||||
|
|
||||||
|
async def ntfy(
|
||||||
|
message: Optional[str],
|
||||||
|
topic: str,
|
||||||
|
title: Optional[str] = None,
|
||||||
|
tags: Optional[str] = None,
|
||||||
|
attach: Optional[bytes] = None,
|
||||||
|
filename: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
assert message or attach
|
||||||
|
headers = {
|
||||||
|
}
|
||||||
|
if title:
|
||||||
|
headers['Title'] = title
|
||||||
|
if tags:
|
||||||
|
headers['Tags'] = tags
|
||||||
|
url = f'{NTFY_URL}/{topic}'
|
||||||
|
client = httpx.AsyncClient()
|
||||||
|
if attach:
|
||||||
|
if filename:
|
||||||
|
headers['Filename'] = filename
|
||||||
|
if message:
|
||||||
|
try:
|
||||||
|
message.encode("ascii")
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
message = rfc2047_base64encode(message)
|
||||||
|
else:
|
||||||
|
message = message.replace('\n', '\\n')
|
||||||
|
headers['Message'] = message
|
||||||
|
r = await client.put(
|
||||||
|
url,
|
||||||
|
headers=headers,
|
||||||
|
content=attach,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
r = await client.post(
|
||||||
|
url,
|
||||||
|
headers=headers,
|
||||||
|
content=message,
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
|
||||||
|
def rfc2047_base64encode(
|
||||||
|
message: str,
|
||||||
|
) -> str:
|
||||||
|
encoded = base64.b64encode(message.encode("utf-8")).decode("ascii")
|
||||||
|
return f"=?UTF-8?B?{encoded}?="
|
||||||
|
|
||||||
|
|
||||||
app = fastapi.FastAPI(
|
app = fastapi.FastAPI(
|
||||||
name=DIST['Name'],
|
name=DIST['Name'],
|
||||||
version=DIST['Version'],
|
version=DIST['Version'],
|
||||||
|
@ -331,3 +382,46 @@ def status() -> str:
|
||||||
@app.post('/hooks/firefly-iii/create')
|
@app.post('/hooks/firefly-iii/create')
|
||||||
async def firefly_iii_create(hook: FireflyIIIWebhook) -> None:
|
async def firefly_iii_create(hook: FireflyIIIWebhook) -> None:
|
||||||
await handle_firefly_transaction(hook.content)
|
await handle_firefly_transaction(hook.content)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/hooks/jenkins')
|
||||||
|
async def jenkins_notify(request: fastapi.Request) -> None:
|
||||||
|
body = await request.json()
|
||||||
|
data = body.get('data', {})
|
||||||
|
if body['type'] == 'run.started':
|
||||||
|
title = 'Build started'
|
||||||
|
tag = 'building_construction'
|
||||||
|
build_cause = None
|
||||||
|
for action in data.get('actions', []):
|
||||||
|
for cause in action.get('causes', []):
|
||||||
|
if build_cause := cause.get('shortDescription'):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
message = f'Build started: {data["fullDisplayName"]}'
|
||||||
|
if build_cause:
|
||||||
|
message = f'{message} ({build_cause})'
|
||||||
|
elif body['type'] == 'run.finalized':
|
||||||
|
message = f'{data["fullDisplayName"]} {data["result"]}'
|
||||||
|
title = 'Build finished'
|
||||||
|
match data['result']:
|
||||||
|
case 'FAILURE':
|
||||||
|
tag = 'red_circle'
|
||||||
|
case 'SUCCESS':
|
||||||
|
tag = 'green_circle'
|
||||||
|
case 'UNSTABLE':
|
||||||
|
tag = 'yellow_circle'
|
||||||
|
case 'NOT_BUILT':
|
||||||
|
tag = 'large_blue_circle'
|
||||||
|
case 'ABORTED':
|
||||||
|
tag = 'black_circle'
|
||||||
|
case _:
|
||||||
|
tag = 'white_circle'
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ntfy(message, 'jenkins', title, tag)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
log.exception('Failed to send notification:')
|
||||||
|
|
Loading…
Reference in New Issue