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

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

Справочник по `${{ }}`: contexts `github`, `env`, `vars`, `secrets`, `matrix`, `needs`, `inputs`, операторы, функции, `fromJSON`, статусные функции и различие между expression-time и shell runtime.

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

Contexts и expressions

Справочник по `${{ }}`: contexts `github`, `env`, `vars`, `secrets`, `matrix`, `needs`, `inputs`, операторы, функции, `fromJSON`, статусные функции и различие между expression-time и shell runtime.

Contexts и expressions

Expression в GitHub Actions — это небольшой язык вычислений внутри YAML. Всё, что написано в форме ${{ ... }}, обрабатывается движком GitHub Actions: он подставляет значения из contexts, сравнивает строки, вызывает функции, решает if, строит matrix, формирует runs-on, env, with и другие поля workflow. Это не Bash, не PowerShell и не JavaScript, хотя синтаксис местами похож.

Главная практическая граница такая: expression-time происходит на стороне GitHub Actions при подготовке workflow/job/step, а shell runtime происходит уже на runner, когда выполняется run. В Jobs, dependencies и conditions это проявлялось в if: GitHub может решить не отправлять job на runner вообще. Внутри run уже работает shell, видит $GITHUB_REF, $NODE_VERSION, $MY_VAR и не понимает ${{ }} — к этому моменту выражения уже подставлены как текст.

flowchart TD A[Workflow YAML] --> B[GitHub Actions engine] B --> C{Expressions ${{ ... }} } C --> D[Conditions: if, needs, strategy, runs-on] C --> E[Text substituted into run blocks] D --> F[Job selected and sent to runner] E --> F F --> G[Shell runtime: bash, pwsh, cmd] G --> H[$GITHUB_ENV and $GITHUB_OUTPUT] H --> I[Later steps/jobs read env, steps outputs, needs outputs]
flowchart TD
  A[Workflow YAML] --> B[GitHub Actions engine]
  B --> C{Expressions ${{ ... }} }
  C --> D[Conditions: if, needs, strategy, runs-on]
  C --> E[Text substituted into run blocks]
  D --> F[Job selected and sent to runner]
  E --> F
  F --> G[Shell runtime: bash, pwsh, cmd]
  G --> H[$GITHUB_ENV and $GITHUB_OUTPUT]
  H --> I[Later steps/jobs read env, steps outputs, needs outputs]
Граница между вычислением expression на стороне GitHub Actions и выполнением shell-команд на runner.

Expression-time против shell runtime

Один и тот же факт часто доступен двумя способами: через context и через environment variable. Например, ветку можно прочитать как ${{ github.ref }} во время обработки workflow или как $GITHUB_REF внутри shell-скрипта на runner.

name: Expression demo

on: push

jobs:
  inspect:
    if: ${{ startsWith(github.ref, 'refs/heads/') }}
    runs-on: ubuntu-latest
    steps:
      - name: Compare GitHub expression and shell variable
        run: |
          echo "Expression value: ${{ github.ref }}"
          echo "Shell value: $GITHUB_REF"

Первый echo опасно читать как «Bash сам знает github.ref». Нет: GitHub заранее заменит ${{ github.ref }} на строку, а потом отдаст готовый скрипт runner. Поэтому для внешних данных из event payload — названия PR, body issue, имени branch от форка — безопаснее передавать значение через env, а в shell обращаться к переменной в кавычках.

steps:
  - name: Print PR title safely
    env:
      PR_TITLE: ${{ github.event.pull_request.title }}
    run: |
      printf 'PR title: %s\n' "$PR_TITLE"

Это не делает данные «доверенными», но убирает класс ошибок, где непредсказуемый текст прямо вшивается в shell-команду. Подробная security-модель рядом с этим живёт в Secure use и threat model и pull_request_target и untrusted PR.

Основные contexts

Context — это объект данных workflow. К нему обращаются через точку (github.sha) или индекс (github['sha']). Если свойства нет, результат обычно становится пустой строкой, что удобно для простых проверок, но опасно для опечаток: ${{ github.ref_namee }} не упадёт как строгий язык.

Чаще всего в обычном CI/CD встречаются такие contexts:

  • github — сведения о run, event, ref, sha, actor, repository и payload события. Это центральный context для условий по branch, tag, event action и PR.
  • env — переменные, заданные в YAML на уровне workflow, job или step. Это не то же самое, что все environment variables runner.
  • vars — configuration variables, заданные на уровне organization, repository или environment. Их удобно использовать для не секретных настроек: имя registry, регион cloud, флаг включения feature.
  • secrets — секреты, доступные текущему run. Если secret не задан, обращение вроде ${{ secrets.DEPLOY_TOKEN }} даёт пустую строку.
  • matrix — текущая комбинация matrix job: например, matrix.node или matrix.os. Полная тема — Matrix strategy.
  • needs — статусы и outputs jobs, от которых текущий job зависит через needs. Это мост между графом jobs и выражениями.
  • inputs — параметры workflow_dispatch или workflow_call. Особенно важен для ручных запусков и Reusable workflows.
  • steps — outputs и outcome/conclusion уже выполненных steps в текущем job.
  • runner — сведения о текущем runner: OS, architecture, temp paths и окружение выполнения.

Не все contexts доступны везде. Например, jobs.<job_id>.if вычисляется до отправки job на runner, поэтому там нет runner и steps. steps.if знает больше: уже есть runner, предыдущие steps и hashFiles. secrets нельзя напрямую использовать в if; типичный обход — переложить secret в job-level env и проверять env.<name>, если это действительно нужно.

jobs:
  publish:
    runs-on: ubuntu-latest
    env:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    steps:
      - name: Publish only when token exists
        if: ${{ env.NPM_TOKEN != '' }}
        run: npm publish

Операторы и типы

Expressions поддерживают группировку ( ), доступ к полям ., индекс [ ], отрицание !, сравнения < <= > >= == !=, логические && и ||. Строки внутри ${{ }} пишутся в одинарных кавычках: ${{ 'production' }}. Двойные кавычки внутри expression для string literal использовать нельзя.

Сравнения в Actions не строгие. Если типы отличаются, GitHub делает loose coercion: null превращается в 0, true — в 1, пустая строка — в 0, нечисловая строка — в NaN. Строковые сравнения регистронезависимы. На практике это значит: для важных числовых сравнений не полагайтесь на «само как-нибудь сравнит», а приводите строку через fromJSON().

env:
  TIMEOUT_MINUTES: '15'

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - name: Run e2e with numeric timeout
        timeout-minutes: ${{ fromJSON(env.TIMEOUT_MINUTES) }}
        run: npm run test:e2e

Для ветвления сейчас удобно использовать case(): оно читабельнее, чем трюк через && и ||, когда нужно выбрать строку по нескольким условиям.

env:
  DEPLOY_ENV: ${{ case(
    github.ref == 'refs/heads/main', 'production',
    github.ref == 'refs/heads/staging', 'staging',
    'preview'
  ) }}

Функции: contains, fromJSON, toJSON, hashFiles

contains(), startsWith() и endsWith() закрывают большинство условий по branch, tag, label и event name. Для набора допустимых событий часто чище написать массив через fromJSON, чем цепочку ||.

if: ${{ contains(fromJSON('["push", "pull_request"]'), github.event_name) }}

toJSON() полезен для диагностики contexts, но не печатайте весь github или secrets context бездумно. GitHub маскирует секреты в логах, но это не лицензия на dumping всего состояния: некоторые поля чувствительны, а преобразованные значения могут маскироваться не так, как вы ожидаете. Для диагностики лучше выводить узкие фрагменты.

steps:
  - name: Debug event shape
    env:
      EVENT_ACTION: ${{ github.event.action }}
      REF_NAME: ${{ github.ref_name }}
    run: |
      printf 'action=%s ref=%s\n' "$EVENT_ACTION" "$REF_NAME"

hashFiles() считает SHA-256 по файлам внутри GITHUB_WORKSPACE; его часто используют в key для Dependency cache. Если glob ничего не нашёл, функция возвращает пустую строку.

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}

Outputs, needs и динамическая matrix

Expression становится особенно полезным, когда один job принимает решение, а другой использует его. Маленькие значения передают через step outputs и job outputs; файлы и отчёты — через Artifacts и reports.

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

  test:
    needs: plan
    runs-on: ${{ matrix.os }}
    strategy:
      matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci
      - run: npm test

Здесь plan пишет JSON-строку в $GITHUB_OUTPUT; test читает её как needs.plan.outputs.matrix; fromJSON() превращает строку в настоящий object для strategy.matrix. Это хороший пример границы между runtime и expression-time: shell создал строку, GitHub использовал её для построения следующих jobs.

Status functions

В if по умолчанию действует success(), если вы явно не указали status function. Поэтому «запусти диагностику после падения» почти всегда должно включать failure().

steps:
  - name: Build
    id: build
    run: npm run build

  - name: Upload logs after failure
    if: ${{ failure() && steps.build.conclusion == 'failure' }}
    uses: actions/upload-artifact@v4
    with:
      name: build-logs
      path: logs/

success() означает, что предыдущие steps успешны. failure() срабатывает после failure, в цепочке зависимых jobs учитывает и предков. cancelled() проверяет отмену workflow. always() запускает step даже после failure/cancel, но для критичных операций вроде checkout лучше не использовать его без причины; часто безопаснее if: ${{ !cancelled() }}. Диагностика skipped/pending статусов подробнее относится к Monitoring и troubleshooting.

See also

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

В workflow есть строка echo "${{ github.ref }}" внутри run. Кто вычисляет github.ref: shell на runner или GitHub Actions до запуска шага?

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

${{ github.ref }} подставляет движок GitHub Actions до передачи скрипта runner. Shell получает уже готовый текст и сам не понимает contexts GitHub Actions.

Источники

  1. Contexts reference - GitHub Docs
  2. Evaluate expressions in workflows and actions - GitHub Docs
  3. Workflow syntax for GitHub Actions - GitHub Docs
  4. Variables reference - GitHub Docs
  5. Workflow commands for GitHub Actions - GitHub Docs