Мне показалось, будет прикольно запилить пост под названием «Жизнь действия 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. Жду не дождусь по��мотреть, что выйдет у вас.
