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

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

Как передавать значения между steps и jobs через `GITHUB_OUTPUT`, `steps.<id>.outputs`, `jobs.<id>.outputs` и `needs`; когда outputs лучше artifacts или cache.

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

Step outputs и job outputs

Как передавать значения между steps и jobs через `GITHUB_OUTPUT`, `steps.<id>.outputs`, `jobs.<id>.outputs` и `needs`; когда outputs лучше artifacts или cache.

Step outputs и job outputs

Step outputs и job outputs — это короткий канал передачи вычисленных значений внутри одного workflow run. Он нужен не для файлов и не для кеша зависимостей, а для небольших строковых результатов: версии релиза, имени Docker image tag, флага «надо ли публиковать», пути к сгенерированному manifest, id deployment или JSON-кусочка конфигурации.

На практике outputs закрывают промежуток между тем, что уже было в Variables, env и secrets, и тем, что будет в Artifacts и reports. env удобно передаёт значения в shell внутри job, artifacts передают файлы между jobs, cache ускоряет повторные runs. Outputs же передают маленькое вычисленное значение в expression-модель GitHub Actions: steps.<id>.outputs.* внутри job и needs.<job_id>.outputs.* между jobs.

flowchart TD A[Step выполняет shell-команду] --> B[Запись name=value в GITHUB_OUTPUT] B --> C[steps.<id>.outputs.name] C --> D{Нужно значение в другом job?} D -- Нет --> E[Следующий step того же job читает output] D -- Да --> F[jobs.<job_id>.outputs мапит step output] F --> G[Downstream job объявляет needs] G --> H[needs.<job_id>.outputs.name]
flowchart TD
  A[Step выполняет shell-команду] --> B[Запись name=value в GITHUB_OUTPUT]
  B --> C[steps.<id>.outputs.name]
  C --> D{Нужно значение в другом job?}
  D -- Нет --> E[Следующий step того же job читает output]
  D -- Да --> F[jobs.<job_id>.outputs мапит step output]
  F --> G[Downstream job объявляет needs]
  G --> H[needs.<job_id>.outputs.name]
Как значение поднимается от step output к job output и затем читается через `needs`.

Step output: значение из одного step в следующие steps

Step output создаётся записью в environment file GITHUB_OUTPUT. Step, который задаёт output, должен иметь id, иначе к нему нельзя обратиться через steps.<id>.outputs.

name: Version metadata

on:
  workflow_dispatch:

jobs:
  meta:
    runs-on: ubuntu-latest
    steps:
      - name: Compute version
        id: version
        run: |
          echo "tag=1.4.${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT"
          echo "channel=preview" >> "$GITHUB_OUTPUT"

      - name: Use step outputs
        run: |
          echo "Tag: $RELEASE_TAG"
          echo "Channel: $RELEASE_CHANNEL"
        env:
          RELEASE_TAG: ${{ steps.version.outputs.tag }}
          RELEASE_CHANNEL: ${{ steps.version.outputs.channel }}

Здесь важны две стадии. Команда echo "tag=..." >> "$GITHUB_OUTPUT" выполняется на runner. После step GitHub Actions разбирает файл и добавляет tag в outputs этого step. Следующий step читает значение через expression ${{ steps.version.outputs.tag }} и, при желании, перекладывает его в обычную shell-переменную RELEASE_TAG.

Это не то же самое, что GITHUB_ENV. GITHUB_ENV делает environment variable доступной следующим steps того же job. GITHUB_OUTPUT делает именованный результат доступным через expression context. Если значение должно участвовать в if, with, name, env, job outputs или reusable workflow outputs, обычно удобнее output.

Job output: значение из job в другой job

Jobs изолированы: environment variables, созданные в одном job, не появляются в другом. Чтобы передать маленькое значение дальше, step output надо «поднять» на уровень job через jobs.<job_id>.outputs, а downstream job должен явно зависеть от него через needs.

name: Build and publish

on:
  push:
    branches: [main]

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.meta.outputs.image_tag }}
      should_publish: ${{ steps.meta.outputs.should_publish }}
    steps:
      - uses: actions/checkout@v4

      - name: Compute build metadata
        id: meta
        run: |
          short_sha="${GITHUB_SHA::7}"
          echo "image_tag=ghcr.io/acme/api:${short_sha}" >> "$GITHUB_OUTPUT"
          echo "should_publish=true" >> "$GITHUB_OUTPUT"

  build:
    needs: prepare
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t "$IMAGE_TAG" .
        env:
          IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}

  publish:
    needs: [prepare, build]
    if: ${{ needs.prepare.outputs.should_publish == 'true' }}
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Publish image
        run: echo "Publishing $IMAGE_TAG"
        env:
          IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}

Схема чтения такая: prepare вычисляет step output steps.meta.outputs.image_tag; job prepare объявляет публичный job output image_tag; jobs build и publish читают его через needs.prepare.outputs.image_tag. Без needs: prepare context needs.prepare не существует.

Есть тонкость с транзитивными зависимостями. Если deploy зависит только от publish, а publish зависит от prepare, то deploy не получает автоматически needs.prepare.outputs.*. Для чтения output job должен быть прямой зависимостью, либо промежуточный job должен переэкспортировать нужное значение.

Outputs — это строки

В expressions outputs практически всегда надо воспринимать как строки. Даже если step пишет should_publish=true, downstream expression получает строку "true", поэтому безопасный вариант — сравнивать со строкой:

if: ${{ needs.prepare.outputs.should_publish == 'true' }}

Если вы передаёте JSON, держите его маленьким и явно парсьте там, где это нужно:

jobs:
  plan:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.value }}
    steps:
      - id: matrix
        run: |
          echo 'value={"node":[20,22],"os":["ubuntu-latest"]}' >> "$GITHUB_OUTPUT"

  test:
    needs: plan
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
    steps:
      - run: node --version

Такой приём полезен для динамической Matrix strategy: один job решает, какие варианты тестировать, второй строит matrix из JSON. Но это не повод тащить в output большой manifest. У job outputs есть лимиты размера; большие данные лучше передавать как artifact.

Multiline outputs и quoting

Для простых значений достаточно строки name=value. Для multiline-значения используется heredoc-форма, похожая на multiline GITHUB_ENV:

- name: Produce Markdown notes
  id: notes
  run: |
    {
      echo 'body<<EOF'
      echo '### Release notes'
      echo ''
      echo '- Fixed auth redirect'
      echo '- Updated Docker base image'
      echo 'EOF'
    } >> "$GITHUB_OUTPUT"

Но multiline outputs надо использовать аккуратно. Если значение содержит произвольный пользовательский текст, случайный delimiter вроде EOF теоретически может встретиться внутри значения. Для недоверенного или длинного текста лучше записать файл и передать его через Artifacts и reports, а не через output.

Когда output лучше artifact или cache

Output лучше artifact, когда downstream job нужен не файл, а короткое решение: какой tag собрать, какой environment выбрать, какой id передать в API, запускать ли deploy. Output виден прямо в expressions, поэтому им удобно управлять if, strategy.matrix, with и env.

Artifact лучше output, когда нужно передать файл или набор файлов: build bundle, coverage, junit XML, generated docs, SBOM, screenshots, logs. Artifact сохраняется после job и может быть скачан другим job или человеком из workflow run.

Cache — вообще другой инструмент. Он не передаёт «результат сборки как контракт» между jobs, а ускоряет повторные runs за счёт сохранения зависимостей или build outputs по ключу. Если job B должен гарантированно получить ровно файл, созданный job A в этом run, это artifact. Если job B должен узнать image_tag, это output. Если следующий workflow run хочет быстрее установить npm-зависимости, это Dependency cache.

Практические ограничения и ошибки

Не используйте outputs как канал для secrets. Job outputs срабатывают в orchestration-слое workflow, могут попадать в expressions и передаваться дальше большему числу steps, чем вы изначально планировали. Кроме того, GitHub редактирует outputs, которые выглядят как secrets, и может не отправить такой output дальше. Для credentials лучше использовать secrets, environment secrets, Permissions и GITHUB_TOKEN или OIDC-схемы из OIDC и секреты облаков.

Не забывайте id у step. name нужен человеку в UI, id нужен YAML-выражениям. Это разные поля.

Не рассчитывайте на порядок matrix jobs. Если matrix job пишет outputs с одинаковыми именами, последний завершившийся вариант может перезаписать значение, а порядок GitHub Actions не гарантирует. Делайте имена уникальными, например output_${{ matrix.version }}, или складывайте результаты в artifacts и агрегируйте отдельным job.

Не превращайте outputs в глобальное состояние workflow. Хороший output читается как маленький контракт: image_tag, release_version, deploy_url, should_publish, coverage_percent. Если output называется data и содержит большой JSON со всем подряд, workflow быстро становится хрупким.

See also

  • Variables, env и secrets — где заканчиваются обычные переменные и начинается передача результатов.
  • Workflow commands и summaries — environment files GITHUB_OUTPUT, GITHUB_ENV, GITHUB_STEP_SUMMARY и команды логов.
  • Artifacts и reports — передача файлов, отчётов и build outputs между jobs.
  • Dependency cache — ускорение повторных runs через cache keys и restore keys.
  • Jobs, dependencies и conditions — как needs задаёт граф выполнения jobs.
  • Contexts и expressions — почему ${{ needs.* }} и shell variables живут на разных стадиях.
  • Matrix strategy — динамические matrix-сценарии через JSON outputs.
  • Reusable workflows — как outputs поднимаются ещё выше, до outputs reusable workflow.
Быстрое повторение #rc-1

В workflow один step вычислил Docker tag. Как правильно сделать это значение доступным следующим steps того же job через expression context?

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

У step должен быть id, а значение надо записать в $GITHUB_OUTPUT, например echo "image_tag=ghcr.io/acme/api:${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT". Потом читать как ${{ steps.<id>.outputs.image_tag }}.

Источники

  1. Passing information between jobs - GitHub Docs
  2. Workflow commands for GitHub Actions - GitHub Docs
  3. Contexts reference - GitHub Docs
  4. Workflow syntax for GitHub Actions - GitHub Docs
  5. Store and share data with workflow artifacts - GitHub Docs
  6. Dependency caching reference - GitHub Docs