From 0d04a0f790c509c67a9d5e00032cca8010b98edc Mon Sep 17 00:00:00 2001 From: florian Date: Wed, 16 Jul 2025 13:32:21 +0200 Subject: [PATCH] Initial commit --- .gitignore | 2 ++ README.md | 11 +++++++++++ dockerhub_api.py | 29 ++++++++++++++++++++++++++++ github_api.py | 42 +++++++++++++++++++++++++++++++++++++++++ query_and_compare.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 14 ++++++++++++++ send_mail.py | 19 +++++++++++++++++++ webserver.py | 22 ++++++++++++++++++++++ 8 files changed, 184 insertions(+) create mode 100644 dockerhub_api.py create mode 100644 github_api.py create mode 100644 query_and_compare.py create mode 100644 requirements.txt create mode 100644 send_mail.py create mode 100644 webserver.py diff --git a/.gitignore b/.gitignore index 0dbf2f2..70321f7 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +state.json diff --git a/README.md b/README.md index 47987fd..c36483f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ # docker-repository-query + +## Necessary environment variables saved in .env +GITHUB_TOKEN= + +DOCKER_TOKEN= + +DOCKER_USERNAME= + +MAIL_ADDRESS= + +FROM_ADDRESS= \ No newline at end of file diff --git a/dockerhub_api.py b/dockerhub_api.py new file mode 100644 index 0000000..93a0966 --- /dev/null +++ b/dockerhub_api.py @@ -0,0 +1,29 @@ +import requests +from dotenv import load_dotenv +import os + +load_dotenv() + +DOCKER_TOKEN = os.getenv("DOCKER_TOKEN") +DOCKER_USERNAME = os.getenv("DOCKER_USERNAME") + +def login_and_get_token(): + login_url = "https://hub.docker.com/v2/users/login/" + response = requests.post(login_url, + json={"username": DOCKER_USERNAME, "password": DOCKER_TOKEN}) + if response.status_code == 200: + token = response.json()["token"] + return token + else: + print(f"Login failed: {response.status_code} - {response.text}") + +def find_package_version_with_tag(repo, tag): + token = login_and_get_token() + headers = {"Authorization": f"JWT {token}"} + tags_url = f"https://hub.docker.com/v2/repositories/{repo}/tags/{tag}?page_size=1" + tags_response = requests.get(tags_url, headers=headers) + id = tags_response.json()["id"] + return id + +if __name__ == "__main__": + print(find_package_version_with_tag("pihole/pihole", "latest")) \ No newline at end of file diff --git a/github_api.py b/github_api.py new file mode 100644 index 0000000..cfcbd1d --- /dev/null +++ b/github_api.py @@ -0,0 +1,42 @@ +import requests +from dotenv import load_dotenv +import os + +load_dotenv() + +GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") + +def find_package_version_with_tag(org , package, target_tag): + + headers = { + "Authorization": f"Bearer {GITHUB_TOKEN}", + "Accept": "application/vnd.github+json" + } + + page = 1 + per_page = 100 + + while True: + url = f"https://api.github.com/orgs/{org}/packages/container/{package}/versions" + params = {"per_page": per_page, "page": page} + + response = requests.get(url, headers=headers, params=params) + if response.status_code != 200: + print(f"Error {response.status_code}: {response.text}") + return None + + versions = response.json() + if not versions: + print(f"Reached end of pages — tag '{target_tag}' not found.") + return None + + for version in versions: + tags = version.get("metadata", {}).get("container", {}).get("tags", []) + if target_tag in tags: + print(f"Found tag '{target_tag}' on page {page}:\n") + return version["id"] + + page += 1 + +if __name__ == "__main__": + find_package_version_with_tag("Suwayomi", "tachidesk", "stable") \ No newline at end of file diff --git a/query_and_compare.py b/query_and_compare.py new file mode 100644 index 0000000..1344537 --- /dev/null +++ b/query_and_compare.py @@ -0,0 +1,45 @@ +from github_api import find_package_version_with_tag as find_package_version_with_tag_github +from dockerhub_api import find_package_version_with_tag as find_package_version_with_tag_dockerhub +import os +import json + +def retrieve_state (): + default_state = { + "suwayomi_id": "", + "pihole_id":"" + } + if os.path.exists("state.json"): + with open("state.json", "r") as f: + return json.load(f) + else: + state = default_state.copy() + return state + +def save_state(state): + with open("state.json", "w") as f: + json.dump(state, f, indent=2) + +def check_for_new_suwayomi_version(): + latest_online_version = find_package_version_with_tag_github("Suwayomi", "tachidesk", "stable") + local_state = retrieve_state() + if latest_online_version != local_state["suwayomi_id"]: + local_state["suwayomi_id"] = latest_online_version + save_state(local_state) + print("New Suwayomi version has been found") + return True + print("No new Suwayomi version found") + return False + +def check_for_new_pihole_version(): + latest_online_version = find_package_version_with_tag_dockerhub("pihole/pihole", "latest") + local_state = retrieve_state() + if latest_online_version != local_state["pihole_id"]: + local_state["pihole_id"] = latest_online_version + save_state(local_state) + print("New Pi-hole version has been found") + return True + print("No new Pi-hole version found") + return False + +if __name__ == '__main__': + check_for_new_pihole_version() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..90fedef --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +blinker==1.9.0 +certifi==2025.7.14 +charset-normalizer==3.4.2 +click==8.2.1 +dotenv==0.9.9 +Flask==3.1.1 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe==3.0.2 +python-dotenv==1.1.1 +requests==2.32.4 +urllib3==2.5.0 +Werkzeug==3.1.3 diff --git a/send_mail.py b/send_mail.py new file mode 100644 index 0000000..996cdd1 --- /dev/null +++ b/send_mail.py @@ -0,0 +1,19 @@ +import smtplib +from email.message import EmailMessage +from dotenv import load_dotenv +import os + +load_dotenv() + +MAIL_ADDRESS = os.getenv("MAIL_ADDRESS") +FROM_ADDRESS = os.getenv("FROM_ADDRESS", "noreply@localhost") + +def send_mail(subject): + msg = EmailMessage() + msg["From"] = FROM_ADDRESS + msg["To"] = MAIL_ADDRESS + msg["Subject"] = subject + msg.set_content("New Docker image is available") + + with smtplib.SMTP("localhost", 25) as server: + server.send_message(msg) \ No newline at end of file diff --git a/webserver.py b/webserver.py new file mode 100644 index 0000000..7662aed --- /dev/null +++ b/webserver.py @@ -0,0 +1,22 @@ +from query_and_compare import check_for_new_suwayomi_version,check_for_new_pihole_version +from flask import Flask +from send_mail import send_mail + +app = Flask(__name__) + +@app.route('/suwayomi', methods=['GET']) +def handle_suwayomi(): + print("Suwayomi handler invoked") + if check_for_new_suwayomi_version(): + send_mail("New Suwayomi version available") + return '', 200 + +@app.route('/pihole', methods=['GET']) +def handle_pihole(): + print("Pi-hole handler invoked") + if check_for_new_pihole_version(): + send_mail("New Pi-hole version available") + return '', 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000)