diff --git a/.gitea/workflows/checks.yaml b/.gitea/workflows/checks.yaml new file mode 100644 index 0000000..d186a79 --- /dev/null +++ b/.gitea/workflows/checks.yaml @@ -0,0 +1,279 @@ +name: CICD Checks + +on: + workflow_run: + workflows: [ "CICD Main Build" ] + types: [ completed ] + workflow_dispatch: + +env: + GITEA_REGISTRY: kankali.darkhelm.lan:3001 + GITEA_REGISTRY_IP: 10.18.75.2 + GITEA_REGISTRY_HOST: kankali.darkhelm.lan + +concurrency: + group: checks-${{ github.event.workflow_run.head_sha || github.sha }} + cancel-in-progress: true + +jobs: + route: + name: Route Checks Trigger + runs-on: ubuntu-act + outputs: + run_checks: ${{ steps.route.outputs.run_checks }} + head_sha: ${{ steps.route.outputs.head_sha }} + steps: + - name: Decide whether to run checks + id: route + env: + EVENT_NAME: ${{ github.event_name }} + SOURCE_CONCLUSION: ${{ github.event.workflow_run.conclusion }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} + run: | + echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" + if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then + echo "run_checks=true" >> "$GITHUB_OUTPUT" + elif [ "${SOURCE_CONCLUSION}" = "success" ]; then + echo "run_checks=true" >> "$GITHUB_OUTPUT" + else + echo "run_checks=false" >> "$GITHUB_OUTPUT" + fi + + trailing-whitespace: + name: Trailing Whitespace Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - &configure_registry_host_step + name: Configure registry host resolution + run: | + if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then + echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts + fi + - &ensure_cicd_image_step + name: Ensure CICD image is available + run: | + IMAGE="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" + if docker image inspect "${IMAGE}" >/dev/null 2>&1; then + echo "Using cached CICD image: ${IMAGE}" + else + echo "${{ secrets.PACKAGE_ACCESS_TOKEN }}" | docker login "http://${GITEA_REGISTRY}" -u "${{ secrets.REGISTRY_USER || github.actor }}" --password-stdin + docker pull "${IMAGE}" + fi + - name: Check trailing whitespace with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run trailing-whitespace --all-files --show-diff-on-failure + " + + check-yaml: + name: YAML Syntax Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Check YAML files with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run check-yaml --all-files --show-diff-on-failure + " + + end-of-file-fixer: + name: End of File Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Check end of file with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run end-of-file-fixer --all-files --show-diff-on-failure + " + + check-toml: + name: TOML Syntax Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Check TOML files with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run check-toml --all-files --show-diff-on-failure + " + + mixed-line-ending: + name: Mixed Line Ending Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Check line endings with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run mixed-line-ending --all-files --show-diff-on-failure + " + + toml-lint: + name: TOML Formatting Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Check TOML formatting with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run pretty-format-toml --all-files --show-diff-on-failure + " + + ruff-lint: + name: Ruff Linting + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run ruff linting with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run ruff --all-files --show-diff-on-failure + " + + ruff-format: + name: Ruff Format Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Check ruff formatting with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run ruff-format --all-files --show-diff-on-failure + " + + pyright: + name: Pyright Type Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run pyright type checking with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run pyright --all-files --show-diff-on-failure + " + + darglint: + name: Darglint Docstring Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run darglint docstring linting with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run darglint --all-files --show-diff-on-failure + " + + no-docstring-types: + name: No Docstring Types Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run no docstring types check with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run no-docstring-types --all-files --show-diff-on-failure + " + + eslint: + name: ESLint Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run ESLint with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run eslint --all-files --show-diff-on-failure + " + + prettier: + name: Prettier Format Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Check Prettier formatting with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run prettier --all-files --show-diff-on-failure + " + + tsdoc-lint: + name: TSDoc Lint Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run TSDoc linting with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run tsdoc-lint --all-files --show-diff-on-failure + " + + typescript-check: + name: TypeScript Type Check + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_checks == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run TypeScript type checking with pre-commit + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace && + pre-commit run typescript-check --all-files --show-diff-on-failure + " diff --git a/.gitea/workflows/cicd-base.yml b/.gitea/workflows/cicd-base.yml index fb05cf3..1f65cca 100644 --- a/.gitea/workflows/cicd-base.yml +++ b/.gitea/workflows/cicd-base.yml @@ -1,20 +1,9 @@ name: CICD Base Image on: - push: - branches: [ main, develop, feature/* ] - paths: - - Dockerfile.cicd-base - - .dockerignore - - scripts/compute-cicd-base-hash.sh - - .gitea/workflows/cicd-base.yml - pull_request: - branches: [ main, develop ] - paths: - - Dockerfile.cicd-base - - .dockerignore - - scripts/compute-cicd-base-hash.sh - - .gitea/workflows/cicd-base.yml + workflow_run: + workflows: [ "CICD Start" ] + types: [ completed ] workflow_dispatch: inputs: force_rebuild: @@ -22,6 +11,10 @@ on: required: false default: "false" +concurrency: + group: base-${{ github.event.workflow_run.head_sha || github.sha }} + cancel-in-progress: true + env: GITEA_SSH_HOST: kankali.darkhelm.lan GITEA_SSH_PORT: "2222" @@ -34,6 +27,7 @@ jobs: registry-preflight: name: Registry Push Preflight runs-on: ubuntu-act-8gb + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' steps: - &write_registry_push_helpers @@ -112,7 +106,7 @@ jobs: env: PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }} REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }} - GITHUB_SHA: ${{ github.sha }} + GITHUB_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} run: | echo "=== Registry Push Preflight ===" @@ -160,7 +154,7 @@ jobs: - name: Minimal checkout for base inputs env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - GITHUB_SHA: ${{ github.sha }} + GITHUB_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} run: | umask 077 trap 'rm -f ~/.ssh/id_rsa' EXIT diff --git a/.gitea/workflows/cicd-main-build.yaml b/.gitea/workflows/cicd-main-build.yaml new file mode 100644 index 0000000..76e524e --- /dev/null +++ b/.gitea/workflows/cicd-main-build.yaml @@ -0,0 +1,193 @@ +name: CICD Main Build + +on: + workflow_run: + workflows: [ "CICD Start", "CICD Base Image" ] + types: [ completed ] + workflow_dispatch: + +env: + GITEA_SSH_HOST: kankali.darkhelm.lan + GITEA_SSH_PORT: "2222" + GITEA_REPO_SSH_URL: ssh://git@kankali.darkhelm.lan:2222/DarkHelm.org/plex-playlist.git + GITEA_REGISTRY: kankali.darkhelm.lan:3001 + GITEA_REGISTRY_IP: 10.18.75.2 + GITEA_REGISTRY_HOST: kankali.darkhelm.lan + +concurrency: + group: main-build-${{ github.event.workflow_run.head_sha || github.sha }} + cancel-in-progress: true + +jobs: + route: + name: Route Main Build Trigger + runs-on: ubuntu-act-8gb + + outputs: + run_build: ${{ steps.route.outputs.run_build }} + head_sha: ${{ steps.route.outputs.head_sha }} + + steps: + - name: Decide whether to run main build + id: route + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + EVENT_NAME: ${{ github.event_name }} + SOURCE_WORKFLOW: ${{ github.event.workflow_run.name }} + SOURCE_CONCLUSION: ${{ github.event.workflow_run.conclusion }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} + run: | + set -e + echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" + + if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then + echo "run_build=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${SOURCE_CONCLUSION}" != "success" ]; then + echo "run_build=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + umask 077 + trap 'rm -f ~/.ssh/id_rsa' EXIT + + if [ -z "${SSH_PRIVATE_KEY}" ]; then + echo "❌ SSH_PRIVATE_KEY is empty" + exit 1 + fi + + mkdir -p ~/.ssh + echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null + + GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" . + + GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1 || true + + CHANGED_FILES=$(git show --pretty='' --name-only "${HEAD_SHA}" | sed '/^$/d' || true) + + BASE_CHANGED=false + if printf '%s\n' "${CHANGED_FILES}" | grep -Eq '^(Dockerfile\.cicd-base|\.dockerignore|scripts/compute-cicd-base-hash\.sh|\.gitea/workflows/cicd-base\.yml)$'; then + BASE_CHANGED=true + fi + + if [ "${SOURCE_WORKFLOW}" = "CICD Start" ]; then + if [ "${BASE_CHANGED}" = "true" ]; then + echo "run_build=false" >> "$GITHUB_OUTPUT" + else + echo "run_build=true" >> "$GITHUB_OUTPUT" + fi + exit 0 + fi + + if [ "${SOURCE_WORKFLOW}" = "CICD Base Image" ]; then + if [ "${BASE_CHANGED}" = "true" ]; then + echo "run_build=true" >> "$GITHUB_OUTPUT" + else + echo "run_build=false" >> "$GITHUB_OUTPUT" + fi + exit 0 + fi + + echo "run_build=false" >> "$GITHUB_OUTPUT" + + build: + name: Build and Push CICD Complete Image + runs-on: ubuntu-act-8gb + needs: route + if: needs.route.outputs.run_build == 'true' + + steps: + - name: Minimal checkout for build inputs + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + HEAD_SHA: ${{ needs.route.outputs.head_sha }} + run: | + set -e + umask 077 + trap 'rm -f ~/.ssh/id_rsa' EXIT + + if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then + echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts + fi + + mkdir -p ~/.ssh + echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null + + GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" . + + GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git fetch --depth 1 origin "${HEAD_SHA}" >/dev/null 2>&1 || true + + git checkout FETCH_HEAD -- Dockerfile.cicd Dockerfile.cicd-base .dockerignore scripts/compute-cicd-base-hash.sh + chmod +x scripts/compute-cicd-base-hash.sh + + - name: Build and push complete CICD image + env: + PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }} + REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + HEAD_SHA: ${{ needs.route.outputs.head_sha }} + run: | + set -e + umask 077 + trap 'rm -f /tmp/ssh_key' EXIT + + if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then + echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts + fi + + echo "${PACKAGE_ACCESS_TOKEN}" | docker login "http://${GITEA_REGISTRY}" -u "${REGISTRY_USER}" --password-stdin + + BASE_HASH=$(./scripts/compute-cicd-base-hash.sh) + BASE_IMAGE="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd-base:${BASE_HASH}" + + verify_base_image() { + if timeout 30 docker manifest inspect "${BASE_IMAGE}" >/dev/null 2>&1 && \ + timeout 60 docker pull "${BASE_IMAGE}" >/dev/null 2>&1; then + actual_hash=$(docker image inspect "${BASE_IMAGE}" --format '{{ index .Config.Labels "darkhelm.cicd-base.hash" }}' 2>/dev/null || true) + if [ "${actual_hash}" = "${BASE_HASH}" ]; then + return 0 + fi + fi + return 1 + } + + max_attempts=220 + sleep_seconds=30 + for i in $(seq 1 "${max_attempts}"); do + echo "Base availability check ${i}/${max_attempts}..." + if verify_base_image; then + break + fi + + if [ "${i}" -eq "${max_attempts}" ]; then + echo "❌ Required immutable base image is not available or mismatched: ${BASE_IMAGE}" + exit 1 + fi + sleep "${sleep_seconds}" + done + + echo "${SSH_PRIVATE_KEY}" > /tmp/ssh_key + chmod 600 /tmp/ssh_key + export DOCKER_BUILDKIT=1 + + docker build -f Dockerfile.cicd \ + --secret id=ssh_private_key,src=/tmp/ssh_key \ + --build-arg GITHUB_SHA="${HEAD_SHA}" \ + --build-arg CICD_BASE_IMAGE="${BASE_IMAGE}" \ + -t cicd:latest . + + docker tag cicd:latest "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:latest" + docker tag cicd:latest "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}" + + docker push "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:latest" + docker push "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}" diff --git a/.gitea/workflows/cicd.yml b/.gitea/workflows/cicd.yml index cde1c53..3d04620 100644 --- a/.gitea/workflows/cicd.yml +++ b/.gitea/workflows/cicd.yml @@ -1,10 +1,6 @@ name: Tests on: - push: - branches: [ main, develop, feature/* ] - pull_request: - branches: [ main, develop ] workflow_dispatch: concurrency: diff --git a/.gitea/workflows/start.yaml b/.gitea/workflows/start.yaml new file mode 100644 index 0000000..012df4d --- /dev/null +++ b/.gitea/workflows/start.yaml @@ -0,0 +1,104 @@ +name: CICD Start + +on: + push: + branches: [ main, develop, feature/* ] + pull_request: + branches: [ main, develop ] + workflow_dispatch: + +env: + GITEA_SSH_HOST: kankali.darkhelm.lan + GITEA_SSH_PORT: "2222" + GITEA_REPO_SSH_URL: ssh://git@kankali.darkhelm.lan:2222/DarkHelm.org/plex-playlist.git + GITEA_REGISTRY: kankali.darkhelm.lan:3001 + GITEA_REGISTRY_IP: 10.18.75.2 + GITEA_REGISTRY_HOST: kankali.darkhelm.lan + +concurrency: + group: start-${{ github.ref }} + cancel-in-progress: true + +jobs: + sanity-and-routing: + name: Sanity and Base Decision + runs-on: ubuntu-act-8gb + + steps: + - name: Configure registry host resolution + run: | + if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then + echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts + fi + + - name: Registry sanity login + env: + PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }} + REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }} + run: | + echo "${PACKAGE_ACCESS_TOKEN}" | docker login "http://${GITEA_REGISTRY}" -u "${REGISTRY_USER}" --password-stdin + + - name: Registry sanity push and pull + env: + REGISTRY_USER: ${{ secrets.REGISTRY_USER || github.actor }} + run: | + PREFLIGHT_TAG="${GITHUB_SHA:-manual}" + PREFLIGHT_REF="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-ci-preflight:${PREFLIGHT_TAG}" + + cat <<'EOF' > /tmp/Dockerfile.preflight + FROM scratch + LABEL org.opencontainers.image.title="plex-playlist registry preflight" + LABEL org.opencontainers.image.description="Minimal image used to verify registry push/pull access" + EOF + + docker build -f /tmp/Dockerfile.preflight -t registry-preflight:latest /tmp + docker tag registry-preflight:latest "${PREFLIGHT_REF}" + docker push "${PREFLIGHT_REF}" + docker pull "${PREFLIGHT_REF}" + + - name: Compute base hash and base-needed signal + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + run: | + set -e + umask 077 + trap 'rm -f ~/.ssh/id_rsa' EXIT + + if [ -z "${SSH_PRIVATE_KEY}" ]; then + echo "❌ SSH_PRIVATE_KEY is empty" + exit 1 + fi + + mkdir -p ~/.ssh + echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -p "${GITEA_SSH_PORT}" "${GITEA_SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null + + GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git clone --depth 1 --no-checkout "${GITEA_REPO_SSH_URL}" . + + GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \ + git fetch --depth 1 origin "${GITHUB_SHA}" >/dev/null 2>&1 || true + + git checkout FETCH_HEAD -- Dockerfile.cicd-base .dockerignore scripts/compute-cicd-base-hash.sh + chmod +x scripts/compute-cicd-base-hash.sh + + BASE_HASH=$(./scripts/compute-cicd-base-hash.sh) + BASE_REF_HASH="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd-base:${BASE_HASH}" + + echo "Base hash: ${BASE_HASH}" + echo "Base ref: ${BASE_REF_HASH}" + + for i in 1 2 3; do + if timeout 30 docker manifest inspect "${BASE_REF_HASH}" >/dev/null 2>&1 && \ + timeout 60 docker pull "${BASE_REF_HASH}" >/dev/null 2>&1; then + echo "Base rebuild needed: false" + exit 0 + fi + + if [ "${i}" -lt 3 ]; then + sleep 20 + fi + done + + echo "Base rebuild needed: true" diff --git a/.gitea/workflows/tests.yaml b/.gitea/workflows/tests.yaml new file mode 100644 index 0000000..d06df99 --- /dev/null +++ b/.gitea/workflows/tests.yaml @@ -0,0 +1,142 @@ +name: CICD Tests + +on: + workflow_run: + workflows: [ "CICD Checks" ] + types: [ completed ] + workflow_dispatch: + +env: + GITEA_REGISTRY: kankali.darkhelm.lan:3001 + GITEA_REGISTRY_IP: 10.18.75.2 + GITEA_REGISTRY_HOST: kankali.darkhelm.lan + +concurrency: + group: tests-${{ github.event.workflow_run.head_sha || github.sha }} + cancel-in-progress: true + +jobs: + route: + name: Route Tests Trigger + runs-on: ubuntu-act + outputs: + run_tests: ${{ steps.route.outputs.run_tests }} + head_sha: ${{ steps.route.outputs.head_sha }} + steps: + - name: Decide whether to run tests + id: route + env: + EVENT_NAME: ${{ github.event_name }} + SOURCE_CONCLUSION: ${{ github.event.workflow_run.conclusion }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} + run: | + echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" + if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then + echo "run_tests=true" >> "$GITHUB_OUTPUT" + elif [ "${SOURCE_CONCLUSION}" = "success" ]; then + echo "run_tests=true" >> "$GITHUB_OUTPUT" + else + echo "run_tests=false" >> "$GITHUB_OUTPUT" + fi + + backend-tests: + name: Backend Tests + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_tests == 'true' + steps: + - &configure_registry_host_step + name: Configure registry host resolution + run: | + if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then + echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts + fi + - &ensure_cicd_image_step + name: Ensure CICD image is available + run: | + IMAGE="${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" + if docker image inspect "${IMAGE}" >/dev/null 2>&1; then + echo "Using cached CICD image: ${IMAGE}" + else + echo "${{ secrets.PACKAGE_ACCESS_TOKEN }}" | docker login "http://${GITEA_REGISTRY}" -u "${{ secrets.REGISTRY_USER || github.actor }}" --password-stdin + docker pull "${IMAGE}" + fi + - name: Run backend tests with coverage + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace/backend && + source .venv/bin/activate && + uv run pytest -v --tb=short --cov=src --cov-report=term-missing --cov-fail-under=95 + " + + frontend-tests: + name: Frontend Tests + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_tests == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run frontend tests with coverage + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace/frontend && + yarn test:coverage --run --reporter=verbose --coverage.reporter=text --coverage.reporter=text-summary --coverage.thresholds.lines=85 --coverage.thresholds.functions=85 --coverage.thresholds.branches=85 --coverage.thresholds.statements=85 + " + + xdoctest: + name: Backend Doctests + runs-on: ubuntu-act + needs: route + if: needs.route.outputs.run_tests == 'true' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run backend doctests + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace/backend && + source .venv/bin/activate && + uv run xdoctest src/ --quiet + " + + integration-tests: + name: Integration Tests + runs-on: ubuntu-act + needs: backend-tests + if: needs.backend-tests.result == 'success' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run integration tests + run: | + docker run --rm "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace/backend && + source .venv/bin/activate && + if [ -d 'tests/integration' ]; then + uv run pytest tests/integration/ -v --tb=short + else + echo 'ℹ No integration tests found' + fi + " + + e2e-tests: + name: End-to-End Tests + runs-on: ubuntu-act + needs: frontend-tests + if: needs.frontend-tests.result == 'success' + steps: + - *configure_registry_host_step + - *ensure_cicd_image_step + - name: Run E2E tests + run: | + docker run --rm -e CI=true "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${{ needs.route.outputs.head_sha }}" bash -c " + cd /workspace/frontend && + if [ -d 'tests/e2e' ] || grep -q 'playwright' package.json; then + yarn playwright --version && + timeout 300 yarn playwright install chromium && + yarn test:e2e --reporter=list --timeout=90000 + else + echo 'ℹ No E2E tests found' + fi + "