1
0
mirror of https://github.com/MaxenceG2M/github-release-notifier.git synced 2025-12-08 22:03:24 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
8dba474e23 Validate configuration + readable error
Change docker-compose version - use Just to handle version
2023-11-25 01:57:44 +01:00
07dccee235 Just
(cherry picked from commit 5d67747b3a31177010e32ce9cb6a668cd1b5737c)
2023-11-25 01:57:44 +01:00
fd7e6b73d1 Overload config with environment variables
Improves mail configuration management
2023-11-10 00:52:19 +01:00
0ee46a5d9e Fix Pylint problems 2023-11-10 00:51:32 +01:00
9709947c28 Apply pre-commit + keep black commit sha 2023-11-10 00:15:01 +01:00
89e88c8a1b Black formatting 2023-11-10 00:14:00 +01:00
148c647ac7 Install pre-commit 2023-11-10 00:13:24 +01:00
02fad7767c Cron with Ofelia
https://github.com/mcuadros/ofelia
2023-11-09 20:09:40 +01:00
8 changed files with 190 additions and 83 deletions

2
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,2 @@
# Black format commit
89e88c8a1b26e2c6ca242d0bba6cdba8da35c3ae

28
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,28 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 23.11.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort
- repo: https://github.com/PyCQA/autoflake
rev: v2.1.1
hooks:
- id: autoflake
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.11.0
hooks:
- id: pretty-format-yaml
args: [--autofix, --indent, '2', --offset, '2']

View File

@@ -1,17 +1,57 @@
# https://github.com/casey/just
up: build
vbin := "./venv/bin"
pip := vbin / "pip"
python := vbin / "python"
last_commit_sha1 := `git rev-parse --short HEAD`
remote_image_name := "gitea.gdemontauzan.fr/maxenceg2m/github-release-notifier"
remote_build_image := remote_image_name + ":" + last_commit_sha1
# Run the script
run: init
{{ python }} notifier.py
# Init python virtual env
init:
python3 -m venv venv
{{ pip }} install --requirement requirements.txt
# Clean workspace - remove venv - and init
reinit: hclean init
# Remove virtual env (venv)
hclean:
rm -fr venv
# Run docker compose then show logs
dup: dbuild
docker compose up -d
docker compose logs
build:
# Build with docker compose
dbuild:
docker compose build
rebuild: down
docker compose build
# Down docker compose then build
drebuild: ddown dbuild
down:
# Down docker compose
ddown:
docker compose down
force-build:
# Docker build without cache
dforce-build:
docker compose build --no-cache
# Push a working images on registry, tagged with commit-sha1
dpush: dbuild
docker tag github-release-notifier {{ remote_build_image }}
docker push {{ remote_build_image }}
echo "To push a tagged version, do 'just release <version>'"
# Release a version: create a tagged images, push it and create a git tag
release version: dbuild
docker tag github-release-notifier {{ remote_image_name }}:{{ version }}
docker push {{ remote_image_name }}:{{ version }}
git tag -a v{{ version }} -m ""

View File

@@ -47,5 +47,3 @@ Hey boy, what is the `pit.db` file?
Oh, just for fun, and because I love this project, I use [pit by michaeldv](https://github.com/michaeldv/pit) to follow my task etc.
It makes me think I should push my python version of this project on occasion when I will take the time to do...

View File

@@ -3,7 +3,7 @@ version: '3'
services:
notifier:
build: .
image: github-release-notifier:1
image: github-release-notifier
container_name: github-release-notifier
volumes:
- ./conf.ini:/app/conf.ini
@@ -13,3 +13,12 @@ services:
ports:
- "8025:8025"
- "1025:1025"
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- notifier
command: daemon --config=/opt/config.ini
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./ofelia.ini:/opt/config.ini

View File

@@ -1,113 +1,129 @@
import os
from configparser import ConfigParser
import json
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import configparser
import datetime
import json
import os
import smtplib
import sys
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import requests
SMTP_PORT = 0
SMTP_SERVER = 'null'
SENDER_EMAIL = 'a@b.c'
RECEIVER_EMAIL = 'd@e.f'
class EnvInjection(configparser.Interpolation):
"""
Derived interpolation to take env variable before file variable.
Permit to keep the ini file for local / traditionnal use
And use env variable to overload configuration in a Docker usage.
"""
def before_get(self, parser, section, option, value, defaults):
file_value = super().before_get(parser, section, option, value, defaults)
if section != parser.default_section:
return file_value
env_value = os.getenv(option.upper())
return env_value if env_value else file_value
def main():
global SMTP_PORT, SMTP_SERVER, SENDER_EMAIL, RECEIVER_EMAIL
script_dir = os.path.dirname(__file__)
conf_file = os.path.join(script_dir, 'conf.ini')
template_file = os.path.join(script_dir, 'template.html')
conf_file = os.path.join(script_dir, "conf.ini")
template_file = os.path.join(script_dir, "template.html")
parser = ConfigParser()
parser = configparser.ConfigParser(
default_section="config", interpolation=EnvInjection()
)
parser.read(conf_file)
default_config = parser["config"]
SMTP_PORT = parser.get('config', 'smtp_port')
SMTP_SERVER = parser.get('config', 'smtp_server')
SENDER_EMAIL = parser.get('config', 'sender_email')
RECEIVER_EMAIL = parser.get('config', 'receiver_email')
try:
projects = json.loads(parser.get("projects", "projects"))
except json.decoder.JSONDecodeError as jse:
print("ERROR: config file is not correctly JSON formatted!", end="\n\t")
print(jse)
sys.exit(1)
projects = json.loads(parser.get('projects', 'projects'))
new_releases = []
new_projects = []
if not parser.has_section('release'):
parser.add_section('release')
if not parser.has_section("release"):
parser.add_section("release")
for project in projects:
last_release = get_last_release(project)
if not parser.has_option('release', project):
if not parser.has_option("release", project):
new_projects.append(last_release)
else:
last_config_tag = parser.get('release', project)
if last_config_tag != last_release['release_tag']:
last_release['preview_tag'] = last_config_tag
last_config_tag = parser.get("release", project)
if last_config_tag != last_release["release_tag"]:
last_release["previous_tag"] = last_config_tag
new_releases.append(last_release)
parser.set('release', project, last_release['release_tag'])
parser.set("release", project, last_release["release_tag"])
if not new_releases and not new_projects:
print('No new projets or new release detected. Bye!')
print("No new projets or new release detected. Bye!")
return
content = ""
for new_r in new_releases:
content += """
<li><a href="{}" target="_blank">{}</a>: new release <a href="{}" target="_blank">{}</a> available (old: {}).
(published {})</li>
""".format(
new_r['release_url'],
new_r['project_name'],
new_r['release_url'],
new_r['release_tag'],
new_r['preview_tag'],
convert_date(new_r['published_date']))
content += f"""
<li><a href="{new_r["release_url"]}" target="_blank">{new_r["project_name"]}</a>
: new release
<a href="{new_r["release_url"]}" target="_blank">{new_r["release_tag"]}</a>
available (old: {new_r["previous_tag"]}).
(published {convert_date(new_r["published_date"])})</li>"""
for new_p in new_projects:
content += """
<li><a href="{}" target="_blank">{}</a> was added to your configuration. Last release: <a href="{}" target="_blank">{}</a>
(published {})</li>""".format(
new_p['release_url'],
new_p['project_name'],
new_p['release_url'],
new_p['release_tag'],
convert_date(new_p['published_date']))
content += f"""
<li><a href="{new_p["release_url"]}" target="_blank">{new_p["project_name"]}</a>
was added to your configuration.
Last release:
<a href="{new_p["release_url"]}" target="_blank">{new_p["release_tag"]}</a>
(published {convert_date(new_p["published_date"])})</li>"""
# print(content)
with open(template_file, 'r') as f_template:
with open(template_file, "r", encoding="utf-8") as f_template:
template = f_template.read()
send_mail(template.replace('{{content}}', content))
send_mail(template.replace("{{content}}", content), default_config)
with open('conf.ini', 'w') as configfile:
with open("conf.ini", "w", encoding="utf-8") as configfile:
parser.write(configfile)
def get_last_release(project):
url = 'https://api.github.com/repos/{}/releases/latest'.format(project)
result = requests.get(url)
url = f"https://api.github.com/repos/{project}/releases/latest"
result = requests.get(url, timeout=10)
print(project)
print(url)
release = result.json()
release_tag = release['tag_name']
published_date = release['published_at']
release_tag = release["tag_name"]
published_date = release["published_at"]
# body = release['body']
release_url = release['html_url']
release_url = release["html_url"]
return {'release_tag': release_tag,
'published_date': published_date,
return {
"release_tag": release_tag,
"published_date": published_date,
# 'body': body,
'project_name': project,
'release_url': release_url}
"project_name": project,
"release_url": release_url,
}
def send_mail(content, config):
smtp_port = config.get("smtp_port")
smtp_server = config.get("smtp_server")
sender_email = config.get("sender_email")
receiver_email = config.get("receiver_email")
def send_mail(content):
message = MIMEMultipart("alternative")
message["Subject"] = "New Github releases"
message["From"] = SENDER_EMAIL
message["To"] = RECEIVER_EMAIL
message["From"] = sender_email
message["To"] = receiver_email
# part1 = MIMEText(text, "plain")
part2 = MIMEText(content, "html")
@@ -115,11 +131,13 @@ def send_mail(content):
# message.attach(part1)
message.attach(part2)
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.sendmail(SENDER_EMAIL, RECEIVER_EMAIL, message.as_string())
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.sendmail(sender_email, receiver_email, message.as_string())
def convert_date(date: str, dest_format="%d %b %Y at %H:%M") -> str:
return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ").strftime(dest_format)
def convert_date(date: str, format='%d %b %Y at %H:%M') -> str:
return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ").strftime(format)
if __name__ == "__main__":
main()

9
ofelia.ini Normal file
View File

@@ -0,0 +1,9 @@
[global]
smtp-host = mailhog
smtp-port = 1025
email-to = max@ence.fr
email-from = ofelia@container.sh
[job-run "notifier"]
schedule = @every 10s
container = github-release-notifier

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
requests
pre-commit
black