[Перевод] Жизнь действия GitHub Action
Мне показалось, будет прикольно запилить пост под названием «Жизнь действия GitHub Action». На вводном обучении в Google тебя проводят через «Жизнь запроса», и у меня это был один из самых любимых элементов. Поэтому я использую аналогичный подход для GitHub Action.
Для тех, кто в танке, Actions — это функция GitHub, запущенная на конференции Universe в прошлом году. Хотите в бета-тестеры? Гоу сюда.
Общая идея — это GitHub с поддержкой сценариев, но страдать фигней и разливаться в объяснениях я не стану. Лучше проведу вас через события, которые происходят при запуске действия.
Проблема
Вот типичный рабочий процесс:
- Создаю пулл-реквест в репозиторий.
- Пулл-реквест поглощается.
- Ветка сохраняется до скончания времен, съедая мне ту часть мозга, которая любит чистоту и порядок.
Остающиеся ветви — моя боль, сосредоточимся на ней. Хотя проблема-то общая, поэтому давайте создадим действие для удаления ветвей после поглощения пулл-реквеста.
Многабукаф? Весь код для действия — здесь.
Файл рабочего процесса
Хотите — создавайте действия через пользовательский интерфейс, а хотите — пишите файл рабочего процесса ручками. В этой статье я просто использую файл.
Вот как это выглядит, и я объясню, что все это означает, в комментариях к файлу. Он находится в .github/main.workflow
в вашем репозитории.
workflow "on pull request merge, delete the branch" {
## On pull_request defines that whenever a pull request event is fired this
## workflow will be run.
on = "pull_request"
## What is the ending action (or set of actions) that we are running.
## Since we can set what actions "need" in our definition of an action,
## we only care about the last actions run here.
resolves = ["branch cleanup"]
}
## This is our action, you can have more than one but we just have this one for
## our example.
## I named it branch cleanup, and since it is our last action run it matches
## the name in the resolves section above.
action "branch cleanup" {
## Uses defines what we are running, you can point to a repository like below
## OR you can define a docker image.
uses = "jessfraz/branch-cleanup-action@master"
## We need a github token so that when we call the github api from our
## scripts in the above repository we can authenticate and have permission
## to delete a branch.
secrets = ["GITHUB_TOKEN"]
}
Событие
Итак, поскольку статья называется «Жизнь действия», то начнем с того, что за мракобесие творится. Все действия запускаются через событие GitHub. Список поддерживаемых событий — здесь.
Выше мы выбрали событие pull_request
. Оно запускается при назначении пулл-реквеста, отмене назначения, пометке, снятии метки, открытии, редактировании, закрытии, повторном открытии, синхронизации, запросе на ревью пулл-реквеста или удалении пулл-реквеста.
Ладно, запустили мы это событие, и…
С пулл-реквестом «что-то» не так…
И тут GitHub такой: «Блинский блин, с пулл-реквестом что-то не так! Фигану-ка я из всех орудий по неполадкам!»
Глядя же на файл рабочего процесса (см.выше), GitHub говорит: «Я сейчас запущу рабочий процесс по поглощению пулл-реквеста, а ветвь удалю».
К чему это ведет? О, «очистка ветви». Давайте я упорядочу действия, необходимые для очистки ветви (в данном случае таковых нет) и запущу их по порядку/параллельно, чтобы прийти к «очистке ветви».
Действие
Здесь GitHub заявляет: «Йоу, народ, мне тут «очистку ветви» надо запустить. Дайте-ка разобраться».
Это возвращает нас к разделу uses
нашего файла. Указываем на репозиторий: jessfraz/branch-cleanup-action@master
.
В этом репозитории находится Dockerfile. Он определяет среду, в которой будет выполняться наше действие.
Dockerfile
Взглянем на него, а я постараюсь объяснить все объяснить в комментариях.
## FROM defines what Docker image we are starting at. A docker image is a bunch
## of files combined in a tarball.
## This image is all the files we need for an Alpine OS environment.
FROM alpine:latest
## This label defines our action name, we could have named it butts but
## I decided to be an adult.
LABEL "com.github.actions.name"="Branch Cleanup"
## This label defines the description for our action.
LABEL "com.github.actions.description"="Delete the branch after a pull request has been merged"
## We can pick from a variety of icons for our action.
## The list of icons is here: https://developer.github.com/actions/creating-github-actions/creating-a-docker-container/#supported-feather-icons
LABEL "com.github.actions.icon"="activity"
## This is the color for the action icon that shows up in the UI when it's run.
LABEL "com.github.actions.color"="red"
## These are the packages we are installing. Since I just wrote a shitty bash
## script for our Action we don't really need all that much. We need bash,
## CA certificates and curl so we can send a request to the GitHub API
## and jq so I can easily muck with JSON from bash.
RUN apk add --no-cache \
bash \
ca-certificates \
curl \
jq
## Now I am going to copy my shitty bash script into the image.
COPY cleanup-pr-branch /usr/bin/cleanup-pr-branch
## The cmd for the container defines what arguments should be executed when
## it is run.
## We are just going to call back to my shitty script.
CMD ["cleanup-pr-branch"]
Сценарий
Ниже приведено содержимое пробного сценария, который я выполняю.
#!/bin/bash
set -e
set -o pipefail
# This is populated by our secret from the Workflow file.
if [[ -z "$GITHUB_TOKEN" ]]; then
echo "Set the GITHUB_TOKEN env variable."
exit 1
fi
# This one is populated by GitHub for free :)
if [[ -z "$GITHUB_REPOSITORY" ]]; then
echo "Set the GITHUB_REPOSITORY env variable."
exit 1
fi
URI=https://api.github.com
API_VERSION=v3
API_HEADER="Accept: application/vnd.github.${API_VERSION}+json"
AUTH_HEADER="Authorization: token ${GITHUB_TOKEN}"
main(){
# In every runtime environment for an Action you have the GITHUB_EVENT_PATH
# populated. This file holds the JSON data for the event that was triggered.
# From that we can get the status of the pull request and if it was merged.
# In this case we only care if it was closed and it was merged.
action=$(jq --raw-output .action "$GITHUB_EVENT_PATH")
merged=$(jq --raw-output .pull_request.merged "$GITHUB_EVENT_PATH")
echo "DEBUG -> action: $action merged: $merged"
if [[ "$action" == "closed" ]] && [[ "$merged" == "true" ]]; then
# We only care about the closed event and if it was merged.
# If so, delete the branch.
ref=$(jq --raw-output .pull_request.head.ref "$GITHUB_EVENT_PATH")
owner=$(jq --raw-output .pull_request.head.repo.owner.login "$GITHUB_EVENT_PATH")
repo=$(jq --raw-output .pull_request.head.repo.name "$GITHUB_EVENT_PATH")
default_branch=$(
curl -XGET -sSL \
-H "${AUTH_HEADER}" \
-H "${API_HEADER}" \
"${URI}/repos/${owner}/${repo}" | jq .default_branch
)
if [[ "$ref" == "$default_branch" ]]; then
# Never delete the default branch.
echo "Will not delete default branch (${default_branch}) for ${owner}/${repo}, exiting."
exit 0
fi
echo "Deleting branch ref $ref for owner ${owner}/${repo}..."
curl -XDELETE -sSL \
-H "${AUTH_HEADER}" \
-H "${API_HEADER}" \
"${URI}/repos/${owner}/${repo}/git/refs/heads/${ref}"
echo "Branch delete success!"
fi
}
main "$@"
Таким образом, на данный момент GitHub выполнил наш сценарий в нашей среде выполнения.
GitHub сообщит статус действия в пользовательский интерфейс, и Вы сможете увидеть его во вкладке «Actions».
Надеюсь, это внесло некоторую ясность о том, как осуществляются процессы в GitHub Actions. Жду не дождусь посмотреть, что выйдет у вас.