Github actions: пишем свой

Katya Pavlenko
6 min readJul 16, 2020

Ура, мы умеем подключать готовые actions и собирать из них пайплайны(если еще нет, вот статья об этом)

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

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

Тогда самое время написать свой, и мы, js-разработчики, тут на коне, потому что можно просто взять и написать код на javascript!
На другом языке придется заворачивать все в докеры, а тут просто бери и пиши.

Создадим отдельный репозиторий, инициализируем там nodejs-приложение.

Создадим в корне файл action.yml c информацией про наш будущий экшен. Этот файл потом, конечно же, можно будет менять, он необходим только на стороне гитхаба

name: 'Fancy name of the action'
description: 'Short description'
author: 'name I want to become famous with'
inputs: # если нужны какие-то внешние параметры
github-token:
description: 'This is a token to access github'
required: true
flag:
description: 'This is flag that you can set to true or false'
default: false
required: false
runs:
using: 'node12' # необходимая версия ноды – важно
main: 'lib/index.js' # итоговый файл со всем кодом
branding: # как будет выглядеть иконка экшена в маркетплейсе
icon: 'terminal'
color: 'blue'

Важно: если не указать какие-то параметры в поле inputs, то невозможно будет запросить их в js-коде при запуске на github, даже если они есть в списке with.
Однако указать здесь инпут как required, но не передать его, ничего не случится, и даже не появится warning/error. Защититься от этого можно уже в js-коде, об этом ниже.

иконки, сгенерированные из опций branding

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

Дальше создадим файл lib/index.js и напишем там

console.log('Hello, I am an action!')

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

Добавляем в .github/workflows файл test-itself.yaml

name: "test itself"
on:
push:
jobs:
test:
runs-on: ubuntu-16.04
steps:
# 1 шаг необходим, чтобы использовать экшен из соседней папки
- name: Check out repository
uses: actions/checkout@v2
- uses: ./
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

Теперь запушим и увидим на гихабе, как все работает:

Можно в конфиге указать не только относительный путь, но и абсолютный:
— uses: ./<username>/repo-name@<branch-name> (тут имя репозитория, в который мы пушим наш экшен), тогда тестировать можно будет в любом другом репозитории и не будет необходимости в первом шаге с checkout. Выбор того, как тестировать — за вами.

Если разработка ведется в приватном репозитории, то способ лишь один — тестировать локально. На самом деле уже придумали хак, если существует такая необходимость.

Супер, мы создали самый простой в мире github action, уже, в целом, можно начинать его распространять в опенсорсе. Вы смеетесь, а в маркетплейсе уже есть такое 😄 Ниша занята, поэтому придется придумать что-то посложнее

Добавляем логику

Логика ограничена только вашей фантазией, так как по сути это просто nodejs-приложение.

В любом случае вам понадобятся пакеты из actions/toolkit.

Два самых базовых на мой взгляд:
@actions/core если ваш экшен будет принимать инпуты или планирует падать с вербозной ошибкой

@actions/github если ваше экшен должен будет получать информацию о репозитории/пулл-реквесте/другой информации, которой владеет гитхаб

Есть еще другие: @actions/exec,@actions/io ,@actions/glob ,@actions/artifact и еще, подробнее о них в actions/toolkit

Как получить переданные инпуты?

import * as core from '@actions/core';// {required: true} выкинет ошибку, если input не передан
const TOKEN = core.getInput('github-token', {required: true});

Важно помнить, что все инпуты приходят в виде строк, поэтому если вы рассчитываете на number или bool, надо привести типы.

Как запаковать все в один файл?

В итоге github будет запускать один js-файл, который будет выполнен в среде nodejs и путь к которому будет указан в action.yaml

Никто не будет каждый раз перед запуском делать npm i или собирать заново командой npm build, необходимо предоставить единую точку входа, которая будет обеспечивать работу экшена без дополнительных действий. Ну, как бандл собрать для html.

Можно указать как точку входа свой главный файл src/main.js и запушить node_modules целиком, чтобы у кода был доступ к зависимостям.
А можно вместо этого слегка варварского подхода просто собрать бандл. Например, пакетом ncc который рекомендует сам github. Он, кстати, и typescript поддерживает ❤️

ncc build src/main.js -o lib -m

Этот скрипт собирает все в файл lib/index.js, беря за точку входа src/main.js

Как собирать бандл автоматически?

Легко забыть о том, что надо собрать бандл, если это не делается автоматически. Как можно автоматизировать? Совершать это каждый раз на препуш и прекоммит, например. Но это будет ужааааасно медленно и сильно раздражать, и в итоге будет отключено.
Тут вступают в игру github actions! Так я реализовала билд для своего экшена:

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

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

Как получить информацию о репозитории/пулл-реквесте?

import { context, GitHub } from '@actions/github/lib/github';
import * as core from '@actions/core';
const TOKEN = core.getInput('github-token', {required: true});// client может использовать весть REST api от octokit https://octokit.github.io/rest.js/v18
const client = new GitHub(TOKEN);
// context содержит всю информацию
const {
payload: {
repository,
organization: { login: owner },
pull_request: pullRequest,
},
} = context;
const isPRMerged = await octokit.pulls.checkIfMerged({
owner,
repo: repository.name,
pull_number: pullRequest.number
});

Как остановить выполнение экшена с успехом/ошибкой?

// успех
process.exit(0);
// ошибка
core.setFailed('Hi, log failed, I failed and this is why:');

Если вы ищете пример готового работающего кода, могу предложить посмотреть на экшен, который написала я для рабочих нужд

Точно так же можно найти любой другой готовый код и подглядеть там какие-то практики.

Как опубликоваться в маркетплейс?

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

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

Для начала нужно написать хороший подробный README, иначе никто не поймет, как пользоваться экшеном. К тому же без README.md опубликоваться в маркетплейсе не получится.

Идем в раздел releases(доступен в правой панели), и нажимаем там “Draft new release”. Если у вас существует корректный action.yaml , увидите вот такую панель сверху:

Вот эта голубая галочка и сделает всю магию. Создаем новый релиз-тег, указываем правильную версию по semver(подсказка будет справа), кликаем Publish release. Все, вы великолепны, отправляйтесь искать себя в маркетплейсе!
Если тэг уже создан и запушен, можно из вкладки tags выпустить к нему релиз.

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

Теперь вы готовы сделать первый шаг в написании github actions.

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

Или можно прямо тут задать вопрос/написать мне сообщение.

Ура!

--

--

Katya Pavlenko

frontend developer, love instant noodles and super simple explanations of complex things Github: https://github.com/cakeinpanic