Для приготовления github shell action нам понадобится github
В гитхабе действия можно написать тремя способами:
на JavaScript
в docker контейнере
в интерпретаторе shell
Я выбрал последний и самый непопулярный способ, потому, что скрипты в интерпретаторе shell можно использовать не только в действиях гитхаба, но и нетрудно преобразовать, например, в сборочные линии гитлаба.
К сожалению, полномочий встроенного ключа github_token не всегда хватает для выполнения необходимых действий, поэтому в первую очередь надо создать персональный ключ доступа с ограниченным скоупом public_repo и добавить его в секреты (скажем, под именем PUBLIC_REPO_ACCESS_TOKEN
) в каждом своём репозитории.
Гитхаб действия будем запускать в ubuntu виртуалке или контейнере, что не столь важно, а важно то, что там уже установлены все необходимые программы. Т.к. при запуске действия гитхаб не клонирует репозиторий с текущим действием, то первым действием логично было бы сделать как раз это клонирование. Конечно, в гитхаб действиях уже есть стандартный инструмент для этого, но он написан на JavaScripte и выполняется в node.js, но мы-то делаем shell-действия! Итак,
1. Действие клонирования репозитория
Для передачи параметров в действия обычно используют inputs, но т.к. мы будем делать shell-действия, то в них параметры передаются в переменных окружения, поэтому вместо inputs и преобразования их в переменные окружения, сразу будем использовать только переменные окружения.
action.yml
name: git clone shell action # название действия
description: git clone shell action # описание действия
author: RekGRpth # автор действия
runs: # запуски
using: composite # используя композитные действия
steps: # шаги
- run: "${GITHUB_ACTION_PATH}/action.sh" # запустить скрипт
shell: sh # в интерпретаторе sh
action.sh
#!/bin/sh
# показываем, что будем запускать
# а также, выходим при ошибках
# а также, считаем ошибкой отсутствие заданных переменных окружения
# (что эмулирует обязательность параметров действия)
set -eux
# клонируем с прогрессом только одну ветку
git clone --progress --single-branch \
# которую можно задать (необязательным) параметром-переменной
# (по-умолчанию - текущая ветка при выполнении действия)
--branch "${INPUTS_BRANCH:-${GITHUB_REF##*/}}" \
# указываем пользователя репозитория
# а в качестве пароля - гитхаб токен
# и в конце собственно сам репозиторий
"https://${GITHUB_REPOSITORY_OWNER}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" .
Используем действие так:
.github/workflows/action.yml
...
on: # когда запускать действие
...
jobs: # работы
job: # работа
env: # через переменные окружения
# передаём секрет в качестве токена
GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}
runs-on: ubuntu-latest # запускаем в ubuntu
steps: # шаги
# клонируем текущий репозиторий
- uses: rekgrpth/git-clone-shell-action@v1
...
Но, чтобы использовать действие - его сначала надо опубликовать на маркетплейсе гитхаба. К сожалению, гитхаб не предоставляет инструмента для автоматизации этого, поэтому публиковать в маркетплейсе приходится каждый раз вручную. Зато, можно автоматизировать публикацию релиза:
2. Действие публикации релиза
Или github publish action shell action
action.yml точно такой-же, только название и описание - другие, а вот action.sh
#!/bin/sh
set -eux
# с помощью программки гитхаб API (которая уже установлена в виртуалке/контейнере ubuntu)
# сначала удаляем старый тег релиза репозитория с заданным тегом
# (по-умолчанию - это текущая ветка)
hub api --method DELETE "repos/${GITHUB_REPOSITORY}/git/refs/tags/${INPUTS_TAG:-${GITHUB_REF##*/}}" | jq .
# затем вычисляем идентификатор старого релиза
RELEASE_ID="$(hub api --method GET "repos/${GITHUB_REPOSITORY}/releases/tags/${INPUTS_TAG:-${GITHUB_REF##*/}}" | jq .id)"
# и удаляем старый релиз
hub api --method DELETE "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" | jq .
# и, наконец, публикуем новый релиз
hub api --method POST "repos/${GITHUB_REPOSITORY}/releases" --field "tag_name=${INPUTS_TAG:-${GITHUB_REF##*/}}" --field "target_commitish=${INPUTS_TAG:-${GITHUB_REF##*/}}" | jq .
Используем действие так:
.github/workflows/action.yml
...
on:
...
jobs:
job:
runs-on: ubuntu-latest
steps:
- env:
# передаём токен
GITHUB_TOKEN: ${{ github.token }}
# публикуем релиз
uses: rekgrpth/github-publish-action-shell-action@v1
3. Действие ежегодного обновления лицензии
Или update license year shell action
action.sh
#!/bin/sh
set -eux
# заменяем последний год (или добавляем его если нет)
sed -Ei "s|^([Cc]opyright.+) ([0-9]{4})([-]?)([0-9]{4}?) (.+)$|\1 \2-$(date "+%Y") \5|g" \
"${INPUTS_LICENSE:-LICENSE}" # в лицензионном файле
# задаём почту
git config --local user.email "${INPUTS_EMAIL:-actions@github.com}"
# и имя
git config --local user.name "${INPUTS_NAME:-update license year}"
# комиттим
git commit --message "update license year" --all
# и пушим
git push --progress
Используем действие так:
.github/workflows/action.yml
...
on:
schedule: # запускаем по крону
- cron: '0 0 1 1 *' # каждый год
jobs:
job:
env:
GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}
runs-on: ubuntu-latest
steps:
# сначала клонируем репозиторий
- uses: rekgrpth/git-clone-shell-action@v1
# а потом обновляем в нём лицензию
- uses: rekgrpth/update-license-year-shell-action@v1
4. Действие обновления клонированного репозитория
Или git fetch upstream merge push shell action
action.sh
#!/bin/sh
set -eux
# добавляем родительский репозиторий
# (по-умолчанию с дефолтной веткой)
git remote add --fetch --track "${INPUTS_BRANCH:-${GITHUB_REF##*/}}" \
# (по-умолчанию - с гитхаба, но можно задать и с гитлаба или ещё откуда)
upstream "${INPUTS_URL:-${GITHUB_SERVER_URL}}/${INPUTS_REPOSITORY}.git"
# задаём почту
git config --local user.email "${INPUTS_EMAIL:-actions@github.com}"
# и имя
git config --local user.name "${INPUTS_NAME:-git merge upstream}"
# мержим
git merge --allow-unrelated-histories "upstream/${INPUTS_BRANCH:-${GITHUB_REF##*/}}"
# и пушим
git push --progress
Используем действие так:
.github/workflows/action.yml
...
on:
schedule:
- cron: '0 0 * * *' # каждый день
jobs:
job:
env:
GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}
runs-on: ubuntu-latest
steps:
# клонируем репозиторий
- uses: rekgrpth/git-clone-shell-action@v1
- env:
# задаём родительский репозиторий
INPUTS_REPOSITORY: gawkextlib/code
# и откуда его брать
INPUTS_URL: git://git.code.sf.net/p
# обновляем наш репозиторий
uses: rekgrpth/git-fetch-upstream-merge-push-shell-action@v1
Тут надо сказать, что если в репозитории ничего не обновляется 60 дней, то гитхаб отключает такие действия по крону. Можно, наверное, написать действие по включению действий, а может, и нет...
5. Действие запуска другого действия в другом репозитории
Или github repository dispatch shell action
action.sh
#!/bin/sh
set -eux
# с помощью гитхаб API запускаем другое действие в другом репозитории
# (по-умолчанию, тип события - это текущая ветка)
echo "{\"event_type\":\"${INPUTS_EVENT_TYPE:-${GITHUB_REF##*/}}\",\"client_payload\":${INPUTS_CLIENT_PAYLOAD}}" \
| hub api "repos/${INPUTS_REPOSITORY:-${GITHUB_REPOSITORY}}/dispatches" --input -
Используем действие так:
.github/workflows/action.yml
...
on:
...
jobs:
jon:
runs-on: ubuntu-latest
steps:
- env:
GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}
# задаём сообщение
INPUTS_CLIENT_PAYLOAD: '{"repository":${{ toJson(github.event.repository.name) }}}'
# тип события
INPUTS_EVENT_TYPE: latest
# и репозиторий
INPUTS_REPOSITORY: ${{ github.repository_owner }}/${{ matrix.repo }}
# запускаем другое действие в другом репозитории
uses: rekgrpth/github-repository-dispatch-shell-action@v1
strategy: # с помощью стратегии
matrix: # одновременно запускаем
repo: # на несколкьих репозиториях:
- repo1
- repo2
- repo3
6. Действие сборки и публикации докер-образа
Или docker login build push shell action
action.sh
#!/bin/sh
set -eux
export DOCKER_BUILDKIT=1 # используем новый сборщик
# генерируем название образа
REGISTRY_USERNAME_IMAGE_TAG="$(echo "${INPUTS_REGISTRY:-ghcr.io}/${INPUTS_USERNAME:-${GITHUB_REPOSITORY_OWNER}}/${INPUTS_IMAGE:-${GITHUB_REPOSITORY##*/}}:${INPUTS_TAG:-${GITHUB_REF##*/}}" | tr '[:upper:]' '[:lower:]')"
# логигимся в докер
echo "${INPUTS_PASSWORD:-${GITHUB_TOKEN}}" | docker login --username "${INPUTS_USERNAME:-${GITHUB_REPOSITORY_OWNER}}" --password-stdin "${INPUTS_REGISTRY:-ghcr.io}"
# собираем образ
docker build --progress=plain --tag "${REGISTRY_USERNAME_IMAGE_TAG}" .
# и публикуем
docker push "${REGISTRY_USERNAME_IMAGE_TAG}"
Используем действие так:
.github/workflows/action.yml
...
on:
...
jobs:
job:
concurrency: # если одновременно запущено несколько дейсвий
cancel-in-progress: true # отменяем все старые
group: ${{ github.ref }} # группируя по ветке
env:
GITHUB_TOKEN: ${{ github.token }}
# задаём название работы
name: ${{ github.event.client_payload.repository }} ${{ github.event_name }} ${{ github.event.branch }}
runs-on: ubuntu-latest
steps:
# клонируем репозиторий
- uses: rekgrpth/git-clone-shell-action@v1
# собираем и публикуем образ
- uses: rekgrpth/docker-login-build-push-shell-action@v1
# релизим
- uses: rekgrpth/github-publish-action-shell-action@v1
Комментарии (5)
nin-jin
08.10.2021 08:43Ключ можно прописать в настройках организации - тогда не надо будет его прописывать в каждом репозитории. Жаль с личными репозиториями так нельзя.
А запуск экшенов из экшенов разве не противоречит правилам гитхаба? Где-то читал, что так делать нельзя.
RekGRpth Автор
08.10.2021 09:39А запуск экшенов из экшенов
ну у меня не запуск экшенов из экшенов, а просто запуск триггера repository_dispatch (наверное, неправильно сформулировал в статье)
chemtech
08.10.2021 09:46Чтобы обмнениваться опытом по Github Action создал telegram чат - https://t.me/github_action_ru
amarao
Публиковать не обязательно. Свои actions можно иметь в репозитории. Надо сначала репозиторий checkout, после этого можно написать так:
Внутри каталога должен быть actions.yaml.
Использование PAT в репозиториях - сомнительное удовольствие. Я эту проблему решаю с помощью создания github app (сущности в github), заведения ему публичного ключа и "install" операции в репозитории, после чего можно использовать приватный ключ "приложения" для создания jwt-токена, с помощью которого можно access token получить. Бонусом будет то, что все действия от этого токена будут показываться в UI с шильдиком "bot".
Вообще, меня убивает кривизна github'а. actions не могут работать с композитными объектами (только to_json/from_json), на репозиторий нельзя создать repo-scoped токен с набором прав...
mark_ablov
`access token` приложения лимитирован. В частности столкнулся с тем, что через этот токен нельзя работать с github packages. Самое забавное, что дефолтный
GITHUB_TOKEN
позволяет это, а вот через токен приложения - ни-ни.