Реализация GitHub Action в контейнере Docker

В предыдущей статье я подробно рассказала о сути GitHub Actions, архитектуре этого инструмента, о реализации потока событий через GitHub и создании собственного экшена с нуля при помощи JavaScript.

Речь шла о двух их видах: JS-экшенах и экшенах Docker. Многие из встречавшихся мне открытых версий построены именно с помощью JavaScript. Предполагаю, что по следующим причинам:

  1. Они могут задействовать удобный набор инструментов GitHub, предоставляющий библиотекам доступ к вводам экшена и клиенту GitHub, который можно настроить с использованием токена.
  2. Все GitHub Runners (исполнители) по умолчанию поддерживают Node 12, что существенно упрощает написание экшенов на основе этой версии Node, не требуя дополнительной настройки. Чтобы узнать, какое ПО поддерживается в средах GitHub Runners, загляните в документацию.

Когда же используются экшены на базе Docker?

Этот вид экшенов хорошо проявляет себя в двух общих случаях.

Когда использование JavaScript неуместно

  • Возможно, ваша команда знакома с другими языками или фреймворками.
  • Возможно, вы предпочитаете поддерживать согласованность инструментов. Например, пишите их все на Go и не хотите применять другой язык. 
  • Возможно, вы хотите использовать утилиту или библиотеку, которая не доступна в Node. Например, уже написанный командой скрипт Python для изменения или парсинга данных, который вы не хотите портировать в JavaScript.

Когда неуместно использование конкретной версии Node (12)

Быть может вам нужно построить экшен на другой версии Node.

Даже если вы задействуете поддерживаемый Node 12, есть пара причин, по которым все равно может оказаться удобным использовать именно экшен Docker:

  1. Вам больше не потребуется включать каталог node_modules непосредственно в репозиторий экшена. Можно использовать файл package.json, где указаны зависимости, и откуда Docker контейнер при выполнении экшена будет эти зависимости извлекать.
  2. Связывание необходимой для выполнения экшена среды с самим экшеном исключит вероятность внесения нарушающих работоспособность изменений при обновлении ПО в среде исполнителя на GitHub.

Создание экшена в контейнере Docker

Находясь в размышлениях о том, какой бы интересный экшен создать, я наткнулась на этот незавершенный вариант в организации StoryBook.

Их экшен задуман для сборки и развертывания сайта StoryBook либо на GitHub Pages, либо в корзине AWS S3. Так что я расскажу о том, как создать экшен в контейнере Docker именно для этого случая. 

Ознакомительный материал по данной теме можете найти в документации GitHub.

Определение экшена

Сначала мы определяем интерфейс экшена  —  его вводы, выводы и среду  —  в файле action.yml, расположенном в корне репозитория:

name: 'Storybook Publisher'
description: 'Publish a Storybook Site to GitHub Pages'
inputs:
  access-token:  
    description: 'A GitHub personal access token used to commit to a branch on your behalf.'
    required: true
  branch:  
    description: 'The branch to publish your Storybook site to.'
    required: true
    default: 'gh-pages'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.access-token }}
    - ${{ inputs.branch }}

Вводы экшена

Мы указываем для экшена следующие вводные параметры:

  1. access-token  —  персональный токен доступа на GitHub, необходимый для отправки данных в конкретную ветку репозитория.
  2. branch  —  целевая ветка, в которую нужно развертывать сайт StoryBook.

Образ Docker для экшена

В экшенах Docker указывается образ, используемый для запуска контейнера, в котором выполняется код экшена. В данном случае мы указываем запуск экшена с помощью docker. image можно обозначить одним из двух способов:

1. Используя Dockerfile в репозитории экшена:

runs: 
  using: 'docker'
  image: 'Dockerfile'

Этот вариант мы и задействовали в нашем примере.

2. Используя образ из открытого реестра Docker:

runs: 
  using: 'docker'
  image: 'docker://debian:stretch-slim'

Передача вводных параметров в контейнер Docker

В идеале я бы предпочла, чтобы определенные в экшене вводы при запуске передавались в контейнер Docker автоматически. Тем не менее здесь необходим один дополнительный шаг. args указывает аргументы, которые нужно передать в контейнер при запуске экшена исполнителем.

args:
   - ${{ inputs.access-token }}
   - ${{ inputs.branch }}

Исполнитель передает аргументы в ENTRYPOINT контейнера при его запуске.

Создание Dockerfile

Мы следуем стандартному синтаксису и принципам написания Dockerfile с несколькими характерными для GitHub недочетами, о которых сказано в документации.

FROM alpine:3.10

RUN apk add --update nodejs nodejs-npm
RUN apk add git
 
COPY entrypoint.sh /entrypoint.sh
 
ENTRYPOINT ["/entrypoint.sh"]

Здесь Dockerfile использует в качестве основы для образа alpine Linux, добавляет Node и Git, а также инструктирует Docker запускать entrypoint.sh при старте контейнера с использованием этого образа.

Исполнитель создаст образ из Dockerfile, запустит контейнер, используя этот образ, после чего выполнит код в entrypoint.sh.

Запуск контейнера происходит посредством следующей команды, которая передает ему все необходимые параметры:

Вот некоторые из наиболее важных:

  1. --workdir /github/workspace устанавливает директорию контейнера на рабочую область исполнителя (куда в данном случае должен быть скопирован репозиторий). Эта директория также передается как переменная среды GITHUB_WORKSPACE.
  2. В качестве последних аргументов передаются args, указанные в action.yml:
  • Deborah-Digges:***  —  обфусцированный токен GitHub.
  • gh-pages  —  ветка репозитория, куда нужно выполнить отправку.

Выполнение кода в контейнере

Мы определили ENTRYPOINT как скрипт bash, при этом можно было выполнить скрипт Node, модуль Python, да все, что угодно.

В нашем случае вместо написания кода для сборки и отправки сайта Storybook на GitHub Pages, мы используем storybook-deployer. Извиняюсь за некоторый подвох. Он не завершен, потому что, как видите, предполагает много элементов, относящихся к самому проекту. Например, в нем используется npm, а не yarn.

#!/bin/sh -l

cd $GITHUB_WORKSPACE

# Установка deployer
npm install --save-dev @storybook/storybook-deployer

# Установка других зависимостей
npm install

export GH_TOKEN=${1}
export BRANCH=${2}

# Развертывание страниц GitHub
npx storybook-to-ghpages --host-token-env-variable=GH_TOKEN --branch=$BRANCH --ci

Развертывание сайта Storybook происходит следующими этапами:

  1. Установка зависимости storybook-deployer.
  2. Выполнение storybook-deployer с правильными аргументами ветки и токена.

Весь код готового экшена находится на GitHub.

Использование экшена в рабочем потоке GitHub

Рассмотрим применение этого экшена в репозитории, где Storybook используется для сборки и развертывания сайта в ветке gh-pages при каждой передаче в мастер-ветку.

В репозитории, расположенном в .github/workflows, нужно создать файл рабочего потока:

name: Deploy to GitHub Pages

on:
 push:
   branches:
     master

jobs:
 build:
   runs-on: ubuntu-latest
   steps:
     - uses: actions/[email protected]
     - name: Install dependencies
       run: npm install
     - name: Deploy storybook to Github Pages
       uses: deborah-digges/[email protected]
       with:
         access-token: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}

Мы указываем, что данный рабочий поток должен выполняться при каждой push в ветку master. У него одна задача  —  build, которая разделена на три шага:

  1. Переключение на репозиторий при помощи экшена actions/[email protected].
  2. Установка зависимостей через выполнение скрипта.
  3. Развертывание сайта Storybook на GitHub Pages, используя только что созданный экшен.

Мы ссылаемся на экшен при помощи инструкции deborah-digges/[email protected], которая включает:

  1. Владельца или название организации;
  2. Название репозитория;
  3. Версию, которая может быть тегом или ID коммита в репозитории.

Чтобы увидеть этот рабочий поток в действии, загляните в данный репозиторий, который с помощью созданного экшена развертывает сайт Storybook на GitHub Pages.

Есть ли смысл создавать экшен?

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

Чтобы лучше это понять, важно помнить, что шаг в рабочем процессе может быть:

  1. Экшеном, инкапсулирующим логику, активируемую при помощи вводных инструкций.
  2. Командой bash, выполняемой самим рабочим процессом.

В данном случае количество переиспользуемой логики в созданном нами экшене невелико, и он представляет собой просто тонкую обертку вокруг уже существующего инструмента deploy-storybook. Этот инструмент можно было бы просто выполнить прямо внутри рабочего потока.

При изменении файла рабочего процесса для выполнения кода экшена мы получаем тот же результат без необходимости создавать и обслуживать новый экшен в репозитории. 

name: Deploy to GitHub Pages

on:
 push:
   branches:
     master
 
jobs:
 build:
   runs-on: ubuntu-latest
   steps:
     - uses: actions/[email protected]
     - name: Install dependencies
       run: npm install
     - name: Deploy storybook to Github Pages
       run: npm install --save-dev @storybook/storybook-deployer && npx storybook-to-ghpages --branch=$BRANCH --ci
       env:
         GH_TOKEN: ${{ secrets.GH_TOKEN }}

Прежде чем писать отдельный экшен, нужно ответить на следующий вопрос: “Пригодится ли эта абстракция другим пользователям?”

Если ответом будет “Нет”, то, скорее всего, и создавать его не стоит.

Выводы

В этой статье мы изучили, когда стоит использовать именно экшен Docker, как его написать, а также когда есть смысл создавать отдельный экшен, а когда лучше выполнять встроенные шаги в самом рабочем потоке.

Более подробно о том, что такое GitHub Actions, в чем их польза, и как написать такой экшен на JavaScript, можете прочесть в одной из предыдущих статей.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Deborah Digges: Delving Into Docker Container Actions

Предыдущая статьяClean Architecture в Android для начинающих
Следующая статья80 практических вопросов по Python для собеседования