Learn (Almost) Anything
КаталогСкачать курс

Автоматизация в GitHub

Когда composite action лучше copy-paste steps: metadata, inputs, outputs, shell portability, path handling, limitations и локальное использование внутри repo.

ruЭнциклопедия33 урока6 модулей9
GitHub actionsАвтоматизация в GitHubМодель workflowсостояние и артефактыматрицы и runtimeрелизы и environmentsПереиспользование и собственные actionsgovernance и эксплуатацияgithubactions

Composite actions

Когда composite action лучше copy-paste steps: metadata, inputs, outputs, shell portability, path handling, limitations и локальное использование внутри repo.

Composite actions

Composite action — это custom action, который собирает несколько workflow steps в один uses-step. Он хорошо подходит для повторяющихся кусков YAML: setup языка, проверка lockfile, подготовка toolchain, стандартный набор lint-команд, публикация отчёта в $GITHUB_STEP_SUMMARY. В отличие от Reusable workflows, composite action не описывает целый job или pipeline: у него нет своего runs-on, permissions, environment, services или matrix. Он выполняется внутри job, который уже выбрал runner и выдал токен.

GitHub поддерживает три типа custom actions: composite, JavaScript actions и Docker container actions. Composite — самый лёгкий вариант, когда логика уже выражается как shell steps и вызовы других actions. Если нужна сложная переносимая логика через GitHub API, лучше смотреть в сторону JavaScript action. Если нужно контролируемое Linux-окружение с системными пакетами, это зона Docker action.

flowchart TD A[Workflow job] --> B[Step: uses composite action] B --> C[action.yml] C --> D[Input mapping] C --> E[runs.steps] E --> F[uses: setup action] E --> G[run: bundled script] E --> H[write to GITHUB_OUTPUT] H --> I[Composite output] I --> J[Caller reads steps.<id>.outputs.*] K[Job-level settings] --> A K --> L[runs-on] K --> M[permissions] K --> N[environment] style C fill:#e8f4ff,stroke:#3b82f6 style K fill:#fff7ed,stroke:#f97316
flowchart TD
  A[Workflow job] --> B[Step: uses composite action]
  B --> C[action.yml]
  C --> D[Input mapping]
  C --> E[runs.steps]
  E --> F[uses: setup action]
  E --> G[run: bundled script]
  E --> H[write to GITHUB_OUTPUT]
  H --> I[Composite output]
  I --> J[Caller reads steps.<id>.outputs.*]

  K[Job-level settings] --> A
  K --> L[runs-on]
  K --> M[permissions]
  K --> N[environment]

  style C fill:#e8f4ff,stroke:#3b82f6
  style K fill:#fff7ed,stroke:#f97316
Composite action живёт внутри одного step и наследует job-level настройки от caller workflow.

Базовая структура

Composite action живёт в директории с metadata-файлом action.yml или action.yaml; предпочтительный вариант — action.yml. Для локальных действий внутри repo часто используют .github/actions/<name>/action.yml:

.github/
  workflows/
    ci.yml
  actions/
    setup-node-project/
      action.yml
      scripts/
        validate-lockfile.sh

Минимальный action.yml выглядит так:

name: "Setup Node project"
description: "Install Node.js, dependencies and validate lockfile"

inputs:
  node-version:
    description: "Node.js version"
    required: true
  install-command:
    description: "Dependency install command"
    required: false
    default: "npm ci"

outputs:
  node-version:
    description: "Resolved Node.js version label"
    value: ${{ steps.meta.outputs.node-version }}

runs:
  using: "composite"
  steps:
    - uses: actions/setup-node@v6
      with:
        node-version: ${{ inputs.node-version }}
        cache: npm

    - name: Install dependencies
      run: ${{ inputs.install-command }}
      shell: bash

    - name: Validate lockfile
      run: "$GITHUB_ACTION_PATH/scripts/validate-lockfile.sh"
      shell: bash
      env:
        GITHUB_ACTION_PATH: ${{ github.action_path }}

    - id: meta
      run: echo "node-version=${NODE_VERSION}" >> "$GITHUB_OUTPUT"
      shell: bash
      env:
        NODE_VERSION: ${{ inputs.node-version }}

Здесь runs.using: "composite" говорит GitHub, что внутри будет список steps. Эти steps могут быть run или uses: например, composite action может вызвать actions/setup-node, локальный action или action из Marketplace. Для каждого run-step в composite action нужно явно указать shell. Это полезная дисциплина: сразу видно, завязан ли action на bash, pwsh или другую оболочку.

Как вызывать локальный composite action

Если action лежит в том же репозитории, workflow вызывает его по относительному пути. Для локального action обычно нужен actions/checkout, потому что runner должен получить файлы repo до uses: ./.github/actions/...:

name: ci

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-24.04
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v6

      - id: setup
        uses: ./.github/actions/setup-node-project
        with:
          node-version: "22"
          install-command: "npm ci"

      - run: echo "Composite used Node ${{ steps.setup.outputs.node-version }}"
        shell: bash
      - run: npm test
        shell: bash

Для внешнего repo синтаксис такой же, как у других actions: owner/repo/path@ref. По тем же причинам, что в Marketplace actions и pinning, production-sensitive composite action лучше pin-ить по commit SHA или по контролируемому release tag, а не по default branch.

Inputs, outputs и env

Inputs доступны через ${{ inputs.<name> }}. Они не становятся автоматически shell-переменными с удобными именами, поэтому для shell-кода лучше прокидывать их через env. Это снижает путаницу между expression evaluation и runtime shell evaluation из Contexts и expressions:

- name: Print target
  run: echo "Target: $DEPLOY_TARGET"
  shell: bash
  env:
    DEPLOY_TARGET: ${{ inputs.target }}

Outputs строятся в два шага. Внутренний step пишет значение в $GITHUB_OUTPUT, а metadata-файл мапит его наружу через outputs.<id>.value:

outputs:
  report-path:
    description: "Path to generated report"
    value: ${{ steps.report.outputs.path }}

runs:
  using: "composite"
  steps:
    - id: report
      run: echo "path=reports/junit.xml" >> "$GITHUB_OUTPUT"
      shell: bash

После этого caller читает output как steps.<step_id>.outputs.report-path, то есть через ту же модель, что в Step outputs и job outputs. Ограничения на размер outputs остаются workflow-level ограничениями: не надо передавать большие JSON-отчёты через output, для этого есть Artifacts и reports.

Path handling: где реально лежит script

Самая частая ошибка в composite actions — написать run: ./scripts/foo.sh и ожидать, что путь будет относиться к директории action. На практике команда выполняется в контексте job workspace, то есть обычно в checkout-е caller repo. Если нужен файл, который поставляется вместе с action, используйте ${{ github.action_path }} или переменную GITHUB_ACTION_PATH.

- name: Run bundled script
  run: "$GITHUB_ACTION_PATH/scripts/validate-lockfile.sh"
  shell: bash
  env:
    GITHUB_ACTION_PATH: ${{ github.action_path }}

Если нужен путь в проекте caller-а, используйте рабочую директорию явно:

- name: Build package
  run: npm run build
  shell: bash
  working-directory: ${{ inputs.package-dir }}

Так action не путает «мои собственные файлы» и «код репозитория, который меня вызвал».

Shell portability и границы инструмента

Composite action переносим настолько, насколько переносимы его steps. bash отлично подходит для Ubuntu/macOS runners, но на Windows он означает зависимость от доступного Git Bash. Если action должен честно работать на Windows, macOS и Linux, иногда проще развести шаги по condition:

runs:
  using: "composite"
  steps:
    - name: Unix setup
      if: runner.os != 'Windows'
      run: ./scripts/setup.sh
      shell: bash

    - name: Windows setup
      if: runner.os == 'Windows'
      run: ./scripts/setup.ps1
      shell: pwsh

Если таких развилок становится много, composite action перестаёт быть простым wrapper-ом. Тогда стоит вынести логику в JavaScript actions, где один runtime может скрыть платформенные отличия, или в Docker container actions, если допустим Linux-only execution.

Есть и security-граница. Composite action не объявляет собственный контракт secrets как reusable workflow. Обычные secrets он получает только если caller явно передал их через with или env; secrets context внутри composite action недоступен. При этом GITHUB_TOKEN доступен action через github.token, а его права всё равно задаются в caller workflow/job через Permissions и GITHUB_TOKEN, а не внутри composite action. Не передавайте untrusted данные из PR title, branch name или issue body прямо в run-команды; это уже тема Secure use и threat model и pull_request_target и untrusted PR.

ИнтерактивКак вынести copy-paste steps в composite action#int-1
Шаг 1 из 51. Найдите повторяющийся step-level блок
Берите steps, которые повторяются почти без изменений: setup runtime, установка зависимостей, проверка файлов, генерация summary. Если нужно переиспользовать `runs-on`, `permissions`, matrix или `environment`, это уже кандидат на reusable workflow.
Короткий walkthrough показывает практический порядок рефакторинга без превращения composite action в скрытый pipeline.

Когда composite action лучше copy-paste

Composite action оправдан, когда повторяется один и тот же step-level фрагмент, но workflow-структура остаётся разной. Например, десять repos по-разному запускают тесты, но одинаково ставят Node, валидируют lockfile и печатают summary. Это не повод централизовать весь pipeline через Reusable workflows, но хороший повод убрать copy-paste setup-блока.

Не стоит делать composite action для одного короткого run: npm ci, для job-level политики deploy или для сложного API-клиента. Хороший composite action читается как маленькая команда: setup-node-project, prepare-docker-metadata, publish-test-summary, validate-release-tag. Его inputs описывают варианты поведения, outputs возвращают короткие значения, а все существенные права и deploy-решения остаются видны в caller workflow.

See also

  • Reusable workflows — когда переиспользовать целые jobs и pipelines, а не набор steps.
  • Marketplace actions и pinning — как выбирать и фиксировать actions, которые composite вызывает через uses.
  • JavaScript actions — когда shell-wrapper уже слишком хрупкий и нужна полноценная action-логика.
  • Docker container actions — когда важнее контролировать Linux-окружение, чем запускаться на всех OS.
  • Step outputs и job outputs — базовая модель $GITHUB_OUTPUT и передачи значений.
  • Secure use и threat model — почему inputs и contexts нельзя автоматически считать безопасными.

Внешние справки: GitHub Docs по creating a composite action, metadata syntax, custom actions, contexts и workflow commands.

Быстрое повторение #rc-1

Когда composite action лучше, чем просто копировать один и тот же YAML-блок между workflow?

Показать ответ

Когда повторяется step-level фрагмент, а структура jobs/pipeline в разных repo остаётся разной. Например, общий setup Node + проверка lockfile, но разные команды тестов.

Источники

  1. Creating a composite action - GitHub Docs
  2. Metadata syntax reference - GitHub Docs
  3. About custom actions - GitHub Docs
  4. Contexts reference - GitHub Docs
  5. Workflow commands for GitHub Actions - GitHub Docs