GitHub Actions як CI/CD для сайту на статичному генераторі та GitHub Pages

Habr Технічна тема Мова оригіналу: Російською

Історичний приклад автоматичної генерації сайту Pelican із приватного репозиторію й завантаження готових сторінок у репозиторій GitHub Pages. Автор пояснює кроки workflow та додає сповіщення через Telegram.

Translated by ChatGPT GPT-6.1 Sol

Відомості про першоджерело

GitHub Actions как CI/CD для сайта на статическом генераторе и GitHub Pages

Архівна публікація. Посади, показники й технічні інструкції наведено станом на дату першої публікації.

Трохи переглянувши Habr, здивувався тому, що дуже мало опубліковано статей на тему (beta-)функції GitHub — Actions.

Здавалося б, можна пояснити таку недомовленість тим, що функціональність іще на тестуванні, нехай і “beta”. Але саме корисна особливість бети дає змогу використовувати цей інструмент у приватних репозиторіях. Саме про роботу з цією технологією я розповім у цій статті.

Передісторія

Якщо починати по порядку, то варто, мабуть, згадати той момент, що в процесі пошуку швидкого, зручного, легкого й безкоштовного варіанта зберігання персонального сайту “Про мене” довелося витратити кілька ночей і переглянути безліч статей.

Хтось вибирає хостинг, хтось — хмарний сервер, а тим, кому не хочеться розбиратися в роботі, взаємодії та оплаті всього цього, — припадає до душі вивантаження статичних сайтів у репозиторій, благо зараз це можна зробити і на GitHub, і на GitLab.

Звісно, це особистий вибір кожного.

Мій остаточний вибір був на користь GitHub Pages.

Про Pages

Хто не в курсі, gh-pages — це такий варіант зберігання документації у вигляді сайту, і надається він безкоштовно, а крім документації пропонується зберігати також персональні сайти. Ця функціональність надається GitHub усім користувачам і доступна в налаштуваннях репозиторію.

Для репозиторію проєкту використовується гілка gh-pages, для користувацького сайту — окремий репозиторій із назвою username.github.io з вихідними файлами сайту в гілці master.

Докладніше можна подивитися в документації, але зазначу лише те, що GitHub із дивовижною щедрістю дозволяє кожному прив’язати власний домен до такого сайту, просто додавши файл CNAME із назвою домену й налаштувавши DNS свого домен-провайдера на сервери GitHub.

Упевнений, що статей про те, як розгорнути такий сайт, тут знайдеться безліч, тому далі не про це.

Виникнення проблеми

Проблема полягала в тому, що під час використання статичного генератора є необхідність вигадувати додаткові милиці скрипти й використовувати бібліотеки для спрощення процесу генерації сторінок та їхнього завантаження в репозиторій. Простіше кажучи, якщо зберігати вихідні файли в окремому приватному репозиторії, то щоразу за будь-якої зміни на сайті було необхідно розгортати локальне середовище для подальшої генерації статичних сторінок і публікації в основному репозиторії сайту.

Існує безліч статичних генераторів, і всі вони мають таку саму проблему. Ці дії займають надто багато часу й сил, а зрештою гальмують роботу над сайтом, особливо після кількох міграцій з ОС на ОС або інцидентів із втратою даних на жорстких дисках (так було в моєму випадку).

Буквально нещодавно чи то у спливному сповіщенні на сайті, чи то в розсилці від GitHub було помічено нововбудований CI/CD, який дозволив виконувати ці дії з мінімальними зусиллями.

Про генератори статичних сторінок

Не буду приділяти цьому підпункту особливої уваги, але поділюся парою тез, до яких прийшов за час вибору й використання таких:

  1. вибирати генератор варто під свою мову програмування або такий, який був би максимально зрозумілим. До цієї ідеї я прийшов у той момент, коли самому довелося дописувати деяку функціональність для роботи сайту, ставити милиці для його більшої стійкості й автоматизації. Крім того, це гарний привід самому написати додаткову функціональність у вигляді плагінів;

  2. на якому саме генераторі зупинятися — це особистий вибір, але варто враховувати, що для початкового занурення в роботу функціональності GitHub Pages необхідно спочатку встановити собі Jekyll. Благо, він дає змогу генерувати сайт із вихідних файлів прямо в репозиторії (це я й повторю зі своїм вибором).

Мій вибір генератора ґрунтується на першому пункті. Pelican, написаний на Python, із легкістю замінив чужий для мене Jekyll (користувався майже рік). У результаті навіть створення й редагування статей, робота над сайтом дають додатковий досвід у цікавій для мене мові.

__

Постановка завдання

Головним завданням буде написати такий скрипт (насправді файл конфігурації), який дозволив би автоматично генерувати статичні сторінки з приватного репозиторію. У рішенні буде задіяно функціональність віртуального середовища. Скрипт сам додаватиме готові сторінки в публічний репозиторій.

Інструменти для розв’язання

Інструменти, які використовуватимемо для розв’язання завдання:

  • GitHub Actions;

  • Python 3.7;

  • Pelican;

  • Git;

  • GitHub Pages.

Розв’язання проблеми

Отже, трохи ознайомившись із документацією й розібравшись, як пишуться скрипти для Actions, стало зрозуміло, що цей механізм повністю розв’яже проблему, яка виникла. На момент написання статті для використання цієї функціональності необхідно підписатися на бета-тестування!

Опис нової функціональності самим GitHub

Написання Actions-скрипту починається зі створення іменованого файлу в папці .github та її підпапці workflows. Зробити це можна як вручну, так і з редактора у вкладці Actions на сторінці репозиторію.

Приклад порожнього бланка скрипту

Коротко прокоментую бланк

name: CI    # название скрипта: будет отображаться во вкладке Actions

on: [push]  # действие, по которому запускается данный скрипт

jobs:       # роботы, которые будут выполняться
  build:    # сборка, которая..

    runs-on: ubuntu-latest      # ..будет запущена на основе этого образа

    steps:              # шаги которые будут проделаны после запуска образа
    - uses: actions/checkout@v1     # переход в самую актуальную ветку
    - name: Run a one-line script   # имя работы номер 1
      run: echo Hello, world!       # суть работы номер 1 (bash-команда записана в одну строку)
    - name: Run a multi-line script   # имя работы номер 2
      run: |                    # суть работы номер 2 (многострочная)
        echo Add other actions to build,
        echo test, and deploy your project.

Напишемо свій на основі шаблону:

  1. Ім’я можна залишити й “CI”. Тут справа смаку.

  2. Далі необхідно вибрати ту дію/тригер, яка призведе до запуску скрипту, у нашому випадку це звичайний push нового коміту в репозиторій.

on:
  push
  1. Образ, на основі якого запускатиметься скрипт, також залишимо з прикладу, оскільки Ubuntu цілком влаштовує за необхідною функціональністю. Дивлячись на доступні інструменти, стає зрозуміло, що це може бути будь-який потрібний або просто зручний образ (чи Docker-контейнер на його основі).
  build:
    runs-on: ubuntu-latest
  1. У кроках спочатку налаштуємо середовище для підготовки до основної роботи.

3.1) переходимо в потрібну нам гілку (стандартний крок checkout):

- uses: actions/checkout@v1

3.2) встановлюємо Python:

    - name: Set up Python
      uses: actions/setup-python@v1
      with:
        python-version: 3.7

3.3) встановлюємо залежності нашого генератора:

    - name: Install dependencies
      run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

3.4) створюємо директорію, у яку генеруватимуться сторінки сайту:

   - name: Make output folder
      run: mkdir output
  1. Щоб робота над сайтом була послідовною, а саме не видаляла попередні зміни й можна було без конфліктів додавати зміни в репозиторій сайту, наступним кроком будемо щоразу клонувати репозиторій сайту:
   - name: Clone master branch
      run: git clone "https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git" --branch master --single-branch ./output

Цей крок звертається до системних змінних:

  • змінну GITHUB_ACTOR GitHub установлює сам, і це ім’я користувача, з вини якого запустився цей скрипт;

  • змінна secrets.ACCESS_TOKEN — це згенерований токен для керування GitHub, його у вигляді змінної середовища ми можемо передати, встановивши у вкладці Secrets налаштувань нашого репозиторію. Прошу зауважити: під час генерації токен буде надано нам лише раз, більше доступу до нього не буде. Так само, як і до значень пунктів Secrets.

  1. Переходимо до генерації наших сторінок:
   - name: Generate static pages
      run: pelican content -o output -s publishconf.py

Параметри, передані генератору, відповідають за директорію, куди буде надіслано згенеровані файли (-o output), і конфігураційний файл, який використовуємо для генерації (-s publishconf.py; про підхід до поділу локального конфігу та конфігу для публікації можна почитати в документації Pelican).

Нагадаю, що в нас у папку output уже склоновано репозиторій сайту.

  1. Налаштуємо git і проіндексуємо наші змінені файли:
    - name: Set git config and add changes
      run: |
          git config --global user.email "${GITHUB_ACTOR}@https://users.noreply.github.com/"
          git config --global user.name "${GITHUB_ACTOR}"
          git add --all
      working-directory: ./output

У цьому пункті використовується вже відома змінна й указується робоча директорія, у якій відбуватиметься запуск команд із цього кроку. Команда переходу в робочу директорію інакше виглядала б як — cd output.

  1. Згенеруємо повідомлення коміту, закомітимо зміни й відправимо їх у репозиторій. Щоб коміт не був порожнім і, відповідно, не видав помилку в bash (результат на виході не 0), — спочатку перевіримо, чи взагалі потрібно щось комітити й відправляти. Для цього використаємо команду git diff-index --quiet --cached HEAD --, яка на виході в термінал видасть 0, якщо немає змін відносно попередньої версії сайту, і 1, якщо такі зміни є. Після чого обробляємо результат цієї команди. Таким чином ми в інформації про виконання скрипту запишемо корисну інформацію про стан сайту на цьому етапі замість автоматичного падіння й надсилання нам звіту про падіння скрипту.

Ці дії також проводимо в нашій директорії з готовими сторінками.

   - name: Push and send notification
      run: |
          COMMIT_MESSAGE="Update pages on $(date +'%Y-%m-%d %H:%M:%S')"
          git diff-index --quiet --cached HEAD -- && echo "No changes!" && exit 0 || echo $COMMIT_MESSAGE
          # Only if repo have changes
          git commit -m "${COMMIT_MESSAGE}"
          git push https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git master
      working-directory: ./output

Результат

У підсумку такий скрипт дає змогу не думати про створення статичних сторінок. Додаючи зміни безпосередньо в приватний репозиторій — чи то роботою з git з-під будь-якої системи, чи то створенням файлу через вебінтерфейс GitHub, — Actions зроблять усе самі. У разі несподіваного падіння скрипту на пошту прийде сповіщення.

Повний код

Залишу свій робочий варіант, у ньому в останній крок додано надсилання сповіщення про те, що коміт було відправлено в основний репозиторій.

Використовуються описані вище Secrets, куди додано токен бота й ідентифікатор користувача, якому потрібно надіслати повідомлення.

name: Push content to the user's GitHub pages repository

on:
  push

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1
    - name: Set up Python
      uses: actions/setup-python@v1
      with:
        python-version: 3.7
    - name: Install dependencies
      run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
    - name: Make output folder
      run: mkdir output
    - name: Clone master branch
      run: git clone "https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git" --branch master --single-branch ./output
    - name: Generate static pages
      run: pelican content -o output -s publishconf.py
    - name: Set git config and add changes
      run: |
          git config --global user.email "${GITHUB_ACTOR}@https://users.noreply.github.com/"
          git config --global user.name "${GITHUB_ACTOR}"
          git add --all
      working-directory: ./output
    - name: Push and send notification
      run: |
          COMMIT_MESSAGE="Update pages on $(date +'%Y-%m-%d %H:%M:%S')"
          git diff-index --quiet --cached HEAD -- && echo "No changes!" && exit 0 || echo $COMMIT_MESSAGE
          git commit -m "${COMMIT_MESSAGE}"
          git push https://${{ secrets.ACCESS_TOKEN }}@github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io.git master
          curl "https://api.telegram.org/bot${{ secrets.BOT_TOKEN }}/sendMessage?text=$COMMIT_MESSAGE %0ALook at ${GITHUB_ACTOR}.github.io %0ARepository%3A github.com/${GITHUB_ACTOR}/${GITHUB_ACTOR}.github.io&chat_id=${{ secrets.ADMIN_ID }}"
      working-directory: ./output

Скриншоти

Результат одного із запусків, відображений у вкладці Actions репозиторію з вихідними файлами

Повідомлення від бота про завершення роботи скрипту

Корисні посилання

Загальні відомості про Actions
Синтаксис Actions
Список тригерів
Варіанти віртуальних середовищ
Github Pages
Static Generator list