Github Actions Matrix 101

Матрицы в github action - штука позволяющая описывать эдакие “динамические” workflow, на деле оно не так что бы сложное, но из-за того что оно нужно натурально раз в пятилетку - каждый раз приходиться заново вспоминать с какого боку к нему подходить

Цель заметки: после просмотра ее по диагонали должно будет быть понятным как скрафтить простенький динамичный workflow за пять минут

Как это работает

Предположим наша джоба это сборка чего либо или еще лучше, для примера просто вызов команды echo

Выполнять это дело мы хотим для динамического кол-ва объектов

Соотв наша джоба будет иметь следующий вид (псевдокод):

jobs:
  matrix:
    run: prepare items array
  build:
    matrix: use results of matrix step # <- aka: foreach(item in items) {
    run: build ${{ matrix.item }}

Самый гемморой в этом деле это:

  • не запутаться в названиях - там натурально в 5 местах разные переменные и если хоть одну перепутать - все пойдет на перекосяк - потому за для удобства и не долго думая в примере обзываю все matrix
  • shape матрицы - это объект с 1+ пропертей, содержащей массив примитивов или объектов - это как раз ниже наглядно будет видно в примерах

Сама матрица при этом - as easy as

bash

echo 'matrix={"user":[{"username":"mac2000","email":"alexandrm@rabota.ua"},{"username":"vadymkutsenko","email":"vadimk@rabota.ua"},{"username":"TamaraGalaktionova","email":"tamaraga@rabota.ua"}]}' >> "$GITHUB_OUTPUT"

powershell

$matrix = ConvertTo-Json -Compress -InputObject @{
  user = @(
    @{
      username = "mac2000"
      email = "alexandrm@rabota.ua"
    }
  )
}
"matrix=$matrix" >> $env:GITHUB_OUTPUT

github script (javascript)

return JSON.stringify({
  user: [{ username: "mac2000", email: "alexandrm@rabota.ua" }],
});

примечание: последний пример не проверялся и требуед доработки напильником, дока

matrix101

матрица на минималках - массив строк

name: matrix101

on:
  workflow_dispatch:

jobs:
  matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.matrix }}
    steps:
      - id: matrix
        run: echo 'matrix={"letter":["A","B","C"]}' >> "$GITHUB_OUTPUT"
        # in simplest case, matrix is an object, with single property holding array of items

  build:
    needs: matrix
    if: ${{ needs.matrix.outputs.matrix != '{"letter":[]}' }}
    strategy:
      matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - run: echo '${{ toJson(matrix) }}' # {"letter": "A"} - array item is inlined
      - run: echo ${{ matrix.letter }} # A

Тут наглядно будет виден общий подход

Сама матрица - объект с хотя бы одной пропертей содержащей массив элементов

Элеметны при этом могут быть как примитивами так и объектами (см след демку)

В основном степе build, пока итерируемся, имеем объект matrix у которого в эту пропертю подставлен конретный элемент массива

Псевдо код как это в принципе работает:

function prepare() {
  return { letter: ["A", "B", "C"] };
}

function build(matrix) {
  console.log(JSON.stringify(matrix)); // {"letter":"A"}
  console.log(matrix.letter); // "A"
}

function main() {
  var matrix = prepare();
  for (var letter of matrix.letter) {
    build({ letter: letter });
  }
}

matrix102

то же самое что и предыдущая демка, но на этот раз пользуем объекты за вместо примитивов

name: matrix102

on:
  workflow_dispatch:

jobs:
  matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.matrix }}
    steps:
      - id: matrix
        run: echo 'matrix={"user":[{"username":"mac2000","email":"alexandrm@rabota.ua"},{"username":"vadymkutsenko","email":"vadimk@rabota.ua"},{"username":"TamaraGalaktionova","email":"tamaraga@rabota.ua"}]}' >> "$GITHUB_OUTPUT"
        # once again, matrix is an object with at least one property, holding array of items, which can by objects of any shape

  utilize:
    needs: matrix
    if: ${{ needs.matrix.outputs.matrix != '{"user":[]}' }}
    strategy:
      matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - run: echo '${{ toJson(matrix) }}' # {"user": {"username":"mac2000","email":"alexandrm@rabota.ua"}}
      - run: echo ${{ matrix.user.username }} # mac2000
      - run: echo ${{ matrix.user.email }} # alexandrm@rabota.ua

Тут механика происходящего один в один такая же как и в предыдущей демке

Специально на два куска разделено что бы было легче понять\вспомнить

matrix103

Комбинаторика - если в матрице есть две проперти - будут пользоваться все комбинации их элементов

name: matrix103

on:
  workflow_dispatch:

jobs:
  matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.matrix.outputs.matrix }}
    steps:
      - id: matrix
        run: echo 'matrix={"env":["dev","test","prod"],"domain":["rabota.ua","robota.ua"]}' >> "$GITHUB_OUTPUT"
        # matrix can hold multiple properties, in this case - all possible combinations of two arrays will be used

  utilize:
    needs: matrix
    # if: ${{ needs.matrix.outputs.matrix != 'TODO' }}
    strategy:
      matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - run: echo '${{ toJson(matrix) }}' # {"env":"test","domain":"robota.ua"}
      - run: echo ${{ matrix.env }} # test
      - run: echo ${{ matrix.domain }} # robota.ua

Тут любопытный пример имеет две проперти env и domain, каждая содержит массив примитивов

На выходе наша матрица будет содержать все возможные комбинации env + domain