Github actions: пишем свой
Ура, мы умеем подключать готовые 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 очень милая, позволяет создать логотип экшена из готового набора иконок и цветов. Потом можно запариться и добавить к нему настоящий логотип, но этим займемся позже.
Дальше создадим файл 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.
Возможностей, конечно же, гораздо больше, нужно только понять техническое задание и сформулировать запрос ко вселенной(зачеркнуто) к замечательной документации гитхаба или к гуглу.
Или можно прямо тут задать вопрос/написать мне сообщение.
Ура!