id: github-release-freshness-monitor
namespace: company.team
description: |
Check the latest GitHub release for a repository, compare its age with an
allowed freshness window, persist release metadata, and alert Slack when stale.
triggers:
- id: daily
type: io.kestra.plugin.core.trigger.Schedule
cron: "0 7 * * *"
disabled: true
inputs:
- id: repository
type: STRING
description: GitHub repository in owner/name format.
defaults: kestra-io/kestra
required: true
- id: max_age_days
type: INT
description: Maximum acceptable age of the latest non-draft release.
defaults: 90
required: true
- id: notify_slack
type: BOOL
description: Alert Slack when no fresh release exists.
defaults: true
tasks:
- id: check_release
type: io.kestra.plugin.scripts.python.Script
description: Read GitHub releases and calculate the age of the latest published release.
taskRunner:
type: io.kestra.plugin.core.runner.Process
env:
REPOSITORY: "{{ inputs.repository }}"
MAX_AGE: "{{ inputs.max_age_days }}"
TOKEN: "{{ secret('GITHUB_TOKEN') }}"
script: |
import datetime as dt, json, os, urllib.request
repo, max_age = os.environ["REPOSITORY"], int(os.environ["MAX_AGE"])
headers = {"Accept": "application/vnd.github+json", "User-Agent": "kestra-release-freshness-monitor"}
if os.environ.get("TOKEN"):
headers["Authorization"] = "Bearer " + os.environ["TOKEN"]
request = urllib.request.Request(f"https://api.github.com/repos/{repo}/releases?per_page=20", headers=headers)
with urllib.request.urlopen(request, timeout=20) as response:
releases = json.loads(response.read())
published = [item for item in releases if not item.get("draft") and item.get("published_at")]
if not published:
report = {"repository": repo, "error": "no published release found", "passed": False}
else:
latest = published[0]
published_at = dt.datetime.fromisoformat(latest["published_at"].replace("Z", "+00:00"))
age_days = (dt.datetime.now(dt.timezone.utc) - published_at).total_seconds() / 86400
report = {"repository": repo, "tag": latest.get("tag_name"), "name": latest.get("name"), "published_at": latest["published_at"], "age_days": round(age_days, 1), "max_age_days": max_age, "passed": age_days <= max_age}
print("::" + json.dumps({"outputs": {"report": report, "failed_count": int(not report["passed"])}}) + "::")
- id: write_report
type: io.kestra.plugin.core.storage.Write
description: Save release freshness evidence.
content: "{{ outputs.check_release.vars.report | toJson }}"
extension: ".json"
- id: alert_if_stale
type: io.kestra.plugin.core.flow.If
description: Notify when a repository has no fresh published release.
condition: "{{ outputs.check_release.vars.failed_count > 0 and inputs.notify_slack }}"
then:
- id: alert_slack
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
messageText: "GitHub release freshness failed for {{ inputs.repository }}: {{
outputs.check_release.vars.report | toJson }}. Evidence: {{
outputs.write_report.uri }}"
outputs:
- id: report
type: URI
value: "{{ outputs.write_report.uri }}"
- id: passed
type: BOOLEAN
value: "{{ outputs.check_release.vars.report.passed }}"