diff --git a/.forgejo/workflows/duplicates.yml b/.forgejo/workflows/duplicates.yml new file mode 100644 index 0000000000..43f8a12ba0 --- /dev/null +++ b/.forgejo/workflows/duplicates.yml @@ -0,0 +1,54 @@ +name: duplicates + +on: + schedule: + - cron: '0 12 * * *' + push: + branches: [dev] + pull_request: + branches: [dev] + +env: + REPO_OWNER: "gentoo" + REPO_NAME: "guru" + REPO_WORKFLOW: "duplicates.yml" + IRC_NICK: "guru-ci" + IRC_HOST: "irc.libera.chat" + IRC_PORT: "6697" + IRC_CHANNEL: "#gentoo-guru" + +jobs: + build: + runs-on: codeberg-tiny-lazy + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Prepare job + run: | + LAST_STATUS=$(python ./scripts/last_status.py) + IRC_NOTIFY=${{ forgejo.repository == 'gentoo/guru' && forgejo.event_name != 'pull_request' }} + echo "last_status=${LAST_STATUS}" >> "${FORGEJO_ENV}" + echo "irc_notify=${IRC_NOTIFY}" >> "${FORGEJO_ENV}" + + - name: Setup master gentoo repository + run: | + ./scripts/setup-master-gentoo.sh + + - name: Check for duplicates + run: | + ./scripts/check-duplicates.sh + + - name: Inform failure on IRC + if: ${{ failure() && env.last_status == 'success' && fromJSON(env.irc_notify) }} + run: | + python ./scripts/irc_notify.py + env: + IRC_MESSAGE: CI failure detected on job ${{ forgejo.workflow }} - ${{ forgejo.server_url }}/${{ forgejo.repository }}/actions/runs/${{ forgejo.run_number }} + + - name: Inform recovery on IRC + if: ${{ success() && env.last_status != 'success' && fromJSON(env.irc_notify) }} + run: | + python ./scripts/irc_notify.py + env: + IRC_MESSAGE: CI on job ${{ forgejo.workflow }} is green again. Thanks! diff --git a/.forgejo/workflows/emails.yml b/.forgejo/workflows/emails.yml new file mode 100644 index 0000000000..115f70de52 --- /dev/null +++ b/.forgejo/workflows/emails.yml @@ -0,0 +1,60 @@ +name: emails + +on: + push: + branches: [dev] + pull_request: + branches: [dev] + +defaults: + run: + shell: bash + +env: + REPO_OWNER: "gentoo" + REPO_NAME: "guru" + REPO_WORKFLOW: "emails.yml" + IRC_NICK: "guru-ci" + IRC_HOST: "irc.libera.chat" + IRC_PORT: "6697" + IRC_CHANNEL: "#gentoo-guru" + +jobs: + bugzilla: + runs-on: codeberg-tiny-lazy + steps: + - name: Checkout repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Get compare commit + run: | + COMMIT=$(python ./scripts/last_successful_commit.py) + echo "last_successful_commit=${COMMIT}" >> "${FORGEJO_ENV}" + echo "Last successful commit: ${COMMIT}" + if git rev-parse --verify "${COMMIT}^{commit}" > /dev/null; then + echo -e "\033[92m\u2713 ${COMMIT:0:7} points to a valid commit" + VALID=true + else + echo -e "\033[91m\u2717 ${COMMIT:0:7} doesn't point to a valid commit" + VALID=false + fi + echo "is_commit_valid=${VALID}" >> "${FORGEJO_ENV}" + + - name: Get changed files + if: ${{ fromJSON(env.is_commit_valid) }} + run: | + git diff --diff-filter d --name-only ${last_successful_commit} -- '*/metadata.xml' | tr '\n' ' ' | tee all_changed_files.txt + + - name: Check emails against Bugzilla + if: ${{ fromJSON(env.is_commit_valid) }} + run: | + python ./scripts/email-checker.py < all_changed_files.txt + + - name: Inform on IRC + if: ${{ failure() && forgejo.repository == 'gentoo/guru' && forgejo.event_name == 'push' }} + run: | + python ./scripts/irc_notify.py + env: + IRC_MESSAGE: CI failure detected on job ${{ forgejo.workflow }} - ${{ forgejo.server_url }}/${{ forgejo.repository }}/actions/runs/${{ forgejo.run_number }} diff --git a/.forgejo/workflows/pkgcheck.yml b/.forgejo/workflows/pkgcheck.yml new file mode 100644 index 0000000000..cbc3f910bf --- /dev/null +++ b/.forgejo/workflows/pkgcheck.yml @@ -0,0 +1,66 @@ +name: pkgcheck + +on: + schedule: + - cron: '0 12 * * *' + push: + branches: [dev] + pull_request: + branches: [dev] + +env: + REPO_OWNER: "gentoo" + REPO_NAME: "guru" + REPO_WORKFLOW: "pkgcheck.yml" + IRC_NICK: "guru-ci" + IRC_HOST: "irc.libera.chat" + IRC_PORT: "6697" + IRC_CHANNEL: "#gentoo-guru" + +jobs: + build: + # codeberg-tiny has a max runtime of 2min, but this usually isn't + # enough time for pkgcheck to do its thing, so use -medium (10min). + runs-on: codeberg-medium-lazy + container: + # At time of writing, the default image for codeberg-tiny-lazy is + # ghcr.io/catthehacker/ubuntu, which has bash 5.2.21. However, + # EAPI 9 requires bash >= 5.3. pkgcheck has an official image + # though! (albeit on github...) + image: ghcr.io/pkgcore/pkgcheck + + steps: + # You need nodejs for actions/checkout. Yes, really. + - name: Install necessary packages + run: | + apk add nodejs + + - name: Check out repository + uses: actions/checkout@v6 + + - name: Prepare job + run: | + LAST_STATUS=$(python ./scripts/last_status.py) + IRC_NOTIFY=${{ forgejo.repository == 'gentoo/guru' && forgejo.event_name != 'pull_request' }} + echo "last_status=${LAST_STATUS}" >> "${FORGEJO_ENV}" + echo "irc_notify=${IRC_NOTIFY}" >> "${FORGEJO_ENV}" + + - name: Run pkgcheck + run: | + ./scripts/pkgcheck.sh + env: + PKGCHECK_ARGS: "--keywords=-RedundantVersion,-NonsolvableDepsInDev" + + - name: Inform failure on IRC + if: ${{ failure() && env.last_status == 'success' && fromJSON(env.irc_notify) }} + run: | + python ./scripts/irc_notify.py + env: + IRC_MESSAGE: CI failure detected on job ${{ forgejo.workflow }} - ${{ forgejo.server_url }}/${{ forgejo.repository }}/actions/runs/${{ forgejo.run_number }} + + - name: Inform recovery on IRC + if: ${{ success() && env.last_status != 'success' && fromJSON(env.irc_notify) }} + run: | + python ./scripts/irc_notify.py + env: + IRC_MESSAGE: CI on job ${{ forgejo.workflow }} is green again. Thanks! diff --git a/scripts/irc_notify.py b/scripts/irc_notify.py new file mode 100755 index 0000000000..6dfc2b37d2 --- /dev/null +++ b/scripts/irc_notify.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# A quick and dirty Python script to send a message to an IRC channel. + +import os +import socket +import ssl + +nick = bytes(os.getenv("IRC_NICK", "notguru-cicd"), "ascii") +host = os.getenv("IRC_HOST", "irc.libera.chat") +port = int(os.getenv("IRC_PORT", "6697")) +channel = bytes(os.getenv("IRC_CHANNEL", "##notgentoo-guru"), "ascii") +message = bytes(os.getenv("IRC_MESSAGE", "Howdy!"), "ascii") +context = ssl.create_default_context() + +def main(): + print("Connecting to IRC network...") + sock = socket.create_connection((host, port)) + ssock = context.wrap_socket(sock, server_hostname=host) + + # Let the server speak up first before we send anything + buf = ssock.recv() + if len(buf) == 0: + raise Exception("Unexpected disconnect") + + # Identify ourselves to the server, then join a channel to send a message. + # We send VERSION last since the response (351) is predictable and used as + # an indicator that everything was received. + print("Sending commands...") + command = ( + b"NICK " + nick + b"\n" + + b"USER " + nick + b" 0 0 :" + nick + b"\n" + + b"JOIN " + channel + b"\n" + + b"PRIVMSG " + channel + b" :" + message + b"\n" + + b"VERSION\n" + ) + ssock.sendall(command) + + print("Waiting for response...") + while True: + buf = ssock.recv() + if len(buf) == 0: + raise Exception("Unexpected disconnect") + + lines = buf.split(b"\n") + for line in lines: + parts = line.split() + if len(parts) > 1 and parts[1] == b"351": + print("Message sent!") + return + +main() diff --git a/scripts/last_status.py b/scripts/last_status.py new file mode 100755 index 0000000000..5be7afb0b0 --- /dev/null +++ b/scripts/last_status.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +# Quick and dirty Python script to replace the behavior of a `gh` +# command that got the last CI/CD status of a given workflow. + +import http.client +import json +import os + +host = "codeberg.org" +owner = os.getenv("REPO_OWNER") +name = os.getenv("REPO_NAME") +workflow = os.getenv("REPO_WORKFLOW") +path = ( + f"/api/v1/repos/{owner}/{name}/actions/runs/?workflow_id={workflow}" + "&page=1&limit=1" +) + +def main(): + conn = http.client.HTTPSConnection(host) + conn.request("GET", path) + response = conn.getresponse() + if response.status != 200: + print(f"Didn't get 200 OK! Got {response.status} instead.") + print( + "The response body may be helpful: " + f"{response.read().decode('utf-8')}" + ) + exit(1) + j = json.loads(response.read()) + + # The first workflow run should be the most recent one + print(j["workflow_runs"][0]["status"]) + +main() diff --git a/scripts/last_successful_commit.py b/scripts/last_successful_commit.py new file mode 100755 index 0000000000..4952db199a --- /dev/null +++ b/scripts/last_successful_commit.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +# Similar to last_status.py, this is just a script to replace the `gh` +# cli behavior with the forgejo/codeberg equivalent. Gets the last +# commit that had a successful run for the given job. + +import http.client +import json +import os + +host = "codeberg.org" +owner = os.getenv("REPO_OWNER") +name = os.getenv("REPO_NAME") +workflow = os.getenv("REPO_WORKFLOW") +path = ( + f"/api/v1/repos/{owner}/{name}/actions/runs/?workflow_id={workflow}" + f"&status=success&page=1&limit=1" +) + +def main(): + conn = http.client.HTTPSConnection(host) + conn.request("GET", path) + response = conn.getresponse() + if response.status != 200: + print(f"Didn't get 200 OK! Got {response.status} instead.") + print( + "The response body may be helpful: " + f"{response.read().decode('utf-8')}" + ) + exit(1) + j = json.loads(response.read()) + + # If run has literally never succeeded before, assume failure + if j["total_count"] == 0: + print("none") # this'll cause VALID=false later? + else: + # The first workflow run should be the most recent one + print(j["workflow_runs"][0]["commit_sha"]) + +main() diff --git a/scripts/pkgcheck.sh b/scripts/pkgcheck.sh new file mode 100755 index 0000000000..8fa6c78e04 --- /dev/null +++ b/scripts/pkgcheck.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# All of this is lifted from pkgcore/pkgcheck-action; please see [1] for +# the real brains behind this work. All I've done is moved the logic +# into a standalone bash script so that we're as forge-agnostic as +# possible. +# [1]: https://github.com/pkgcore/pkgcheck-action/graphs/contributors?all=1 + +# ::(end)group:: is used to mark sections of collapsible CI/CD logs. +runner() { + echo "::group::${1}" + shift 1 + "$@" + local exit=$? + echo + echo "::endgroup::" + return $exit +} + +runner "Sync gentoo repo" \ + pmaint sync gentoo +runner "Update repo metadata" \ + pmaint regen --dir ~/.cache/pkgcheck/repos . +runner "Marking workspace safe for git" \ + git config --global --add safe.directory '*' +runner "Run pkgcheck" \ + pkgcheck --color y ci --failures failures.json --exit GentooCI \ + "${PKGCHECK_ARGS}" +scan_exit_status=$? +pkgcheck replay --color y failures.json +exit $scan_exit_status