Для приготовления github shell action нам понадобится github

В гитхабе действия можно написать тремя способами:

  1. на JavaScript

  2. в docker контейнере

  3. в интерпретаторе shell

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

К сожалению, полномочий встроенного ключа github_token не всегда хватает для выполнения необходимых действий, поэтому в первую очередь надо создать персональный ключ доступа с ограниченным скоупом public_repo и добавить его в секреты (скажем, под именем PUBLIC_REPO_ACCESS_TOKEN) в каждом своём репозитории.

Гитхаб действия будем запускать в ubuntu виртуалке или контейнере, что не столь важно, а важно то, что там уже установлены все необходимые программы. Т.к. при запуске действия гитхаб не клонирует репозиторий с текущим действием, то первым действием логично было бы сделать как раз это клонирование. Конечно, в гитхаб действиях уже есть стандартный инструмент для этого, но он написан на JavaScripte и выполняется в node.js, но мы-то делаем shell-действия! Итак,

1. Действие клонирования репозитория

Или git clone shell action

Для передачи параметров в действия обычно используют 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)


  1. amarao
    08.10.2021 08:34
    +1

    Публиковать не обязательно. Свои actions можно иметь в репозитории. Надо сначала репозиторий checkout, после этого можно написать так:

    uses: ./.github/actions/myaction

    Внутри каталога должен быть actions.yaml.

    Использование PAT в репозиториях - сомнительное удовольствие. Я эту проблему решаю с помощью создания github app (сущности в github), заведения ему публичного ключа и "install" операции в репозитории, после чего можно использовать приватный ключ "приложения" для создания jwt-токена, с помощью которого можно access token получить. Бонусом будет то, что все действия от этого токена будут показываться в UI с шильдиком "bot".

    Вообще, меня убивает кривизна github'а. actions не могут работать с композитными объектами (только to_json/from_json), на репозиторий нельзя создать repo-scoped токен с набором прав...


    1. mark_ablov
      08.10.2021 10:53

      `access token` приложения лимитирован. В частности столкнулся с тем, что через этот токен нельзя работать с github packages. Самое забавное, что дефолтный GITHUB_TOKEN позволяет это, а вот через токен приложения - ни-ни.


  1. nin-jin
    08.10.2021 08:43

    Ключ можно прописать в настройках организации - тогда не надо будет его прописывать в каждом репозитории. Жаль с личными репозиториями так нельзя.

    А запуск экшенов из экшенов разве не противоречит правилам гитхаба? Где-то читал, что так делать нельзя.


    1. RekGRpth Автор
      08.10.2021 09:39

      А запуск экшенов из экшенов

      ну у меня не запуск экшенов из экшенов, а просто запуск триггера repository_dispatch (наверное, неправильно сформулировал в статье)


  1. chemtech
    08.10.2021 09:46

    Чтобы обмнениваться опытом по Github Action создал telegram чат - https://t.me/github_action_ru