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




Немного прошерстив Habr удивился тому, что очень мало опубликовано статей на тему (beta-)фичи GitHub'а — Actions.


Казалось бы, можно объяснить такую недосказанность тем, что функционал еще в тестировании, пусть и "beta". Но именно полезная особенность беты позволяет использовать этот инструмент в приватных репозиториях. Именно про работу с данной технологией я расскажу в этой статье.


Предистория


Если начинать по порядку, то стоит, наверное, упомянуть тот момент, что в процессе поиска быстрого, удобного, легкого и бесплатного варианта хранения персонального сайта "Обо мне" пришлось потратить несколько ночей и прошерстить множество статей.


Кто-то на выбирает хостинг, кто-то облачный сервер, а тем, кому не хочется разбираться в работе, взаимодействии и оплате всего этого — приходится по душе выгрузка статических сайтов в репозиторий, благо сейчас это можно сделать и на GitHub, и на GitLab.


Конечно, это личный выбор каждого.


Мой окончательный выбор был в пользу GitHub Pages.


Про Pages

Кто не в курсе, gh-pages — это такой вариант хранения документации в виде сайта и предоставляется он бесплатно, а кроме документации предлагается хранить также персональные сайты. Этот функционал предоставляется GitHub’ом всем пользователям и доступен в настройках репозитория.


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


Подробнее можно посмотреть в документации, но отмечу лишь то, что GitHub с удивительной щедростью разрешает каждому привязать собственный домен к такому сайту, просто добавив файл CNAME c названием домена и настроив 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.

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


0) Имя можно оставить и “CI”. Тут дело вкусовщины.


1) Далее необходимо выбрать то действие/триггер, которое приведет к запуску скрипта, в нашем случае это обычный пуш нового коммита в репозиторий.


on:
  push

2) Образ на основе которого будет запускаться скрипт также оставим с примера, так как Ubuntu вполне устраивает по необходимому функционалу. Глядя на доступные инструменты становится понятно, что это может быть любой необходимый или просто удобный образ (или докер контейнер на его основе).


  build:
    runs-on: ubuntu-latest

3) В шагах сначала настроим среду для подготовки к основной работе.


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

4) Для того, чтобы работа над сайтом была последовательна, а именно не удаляла предыдущие изменения и можно было без конфликтов добавлять изменения в репозиторий сайта, следующим шагом будем каждый раз клонировать репозиторий сайта:


   - 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.

5) Переходим к генерации наших страниц:


   - name: Generate static pages
      run: pelican content -o output -s publishconf.py

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


Напомню, что у нас в папку output уже склонирован репозиторий сайта.


6) Настроим 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.


7) Сгенерируем сообщение коммита, закоммитим изменения и запушим их в репозиторий. Чтобы коммит не был впустую, и соответственно не выдал ошибку в 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 из под любой системы или созданием файла через web-интерфейс 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

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (12):

  1. vintage
    /#20851852

    Для деплоя на гх-паджес проще всего использовать спец экшен: https://github.com/alex-page/blazing-fast-gh-pages-deploy

    • dmytrohoi
      /#20859200

      Это самописный Action который:
      — работает на JS, а идея была использовать только свой язык программирования и bash(это в том числе описано в моменте где уточнение про выбор генератором — Pelican);
      — речь идет об отправке файлов в репозиторий (то же что наши три команды для чистого git), тогда как это можно сделать не тащя за собой node и npm, и еще некоторые пользовательские пакеты к node.

  2. vintage
    /#20851868

    Если говорить про джекил, то он в гитхабе включён по умолчанию и так и норовит упасть, запоров вам деплой, о чём вы узнаете только письмом счастья на почту. Чтобы этого не происходило нужно добавить в корень ветки пустой .nojekyll файл.

    • dmytrohoi
      /#20859186

      Хорошое уточнение!

      Но учитывая что все файлы в конечном репозитории будут чистыми .html, такое поведение маловероятно.

      • vintage
        /#20859246

        И если в них встретятся двойные фигурные скобочки, то джекил упадёт.

        • dmytrohoi
          /#20859314

          Действительно, touch .nojekyll выглядит как хорошая идея.


          В пакет добавлю этот момент.

  3. vintage
    /#20851882

    Ну и такие инструкции по сборке лучше не в виде статей поставлять, а в виде собственно github action. сделать это совсем не сложно. Например, сборку МАМ проектов я вынес в экшен https://github.com/hyoo-ru/mam_build ввиду чего настройка деплоя очередного проекта крайне упростиласть.

    • zloddey
      /#20852192

      Ну почему же, одно другому не мешает. Статья полезная и внимания привлечёт больше, а ссылку на готовый репозиторий можно в конец добавить

  4. zloddey
    /#20852306

    Интересная и полезная статья, спасибо! Как раз вчера думал на эту тему, а тут — хопа! — и готовое решение подъехало. Строго говоря, через подобную автоматизацию можно наворотить много чего: форматирование кода, разного рода автофиксы, сохранение контекста автотестов и т.п. Механизм во всех случаях один и тот же

    • dmytrohoi
      /#20859212

      Это был рассмотрен наиболее близкий для меня юзкейс.

      Во время написания статьи углубился в эту тему и очень вдохновлен возможностями лежащим на поверхности (про подобные Docker, разные Travis и прочие CI/CD я знаю, но с этим не выходя из GitHub выглядит перспективнее для малых проектов), теперь останется только придумать куда это применять.

  5. ra2003
    /#20859656

    Полезная статья, но как то все очень сложно сделано. Предпочел в разы проще взял lektor + простая публикация на гитлаб (который можно было на гитхаб заменить).