GitHub Action: matrix-extended
GitHub Action that when used together with reusable workflows makes it easier to workaround the limit of 256 jobs in a matrix.
Introduction
GitHub Actions matrix have limit to 256 items There is workaround to extend the limit with reusable workflows This GitHub Action outputs a JSON structure for up to 3 levels deep of nested matrixes. In theory run 256 ^ 3 (i.e., 16 777 216) jobs per workflow run!
Matrix max nested level | Total jobs count limit |
---|---|
1 | 256 |
2 | 65 536 |
3 | 16 777 216 |
If nested-matrices-count
input is 1
, the output matrix
would be JSON formatted string with the following structure
{
"include": [matrix items]
}
If nested-matrices-count
input is 2
output matrix
whould be a JSON formatted string with the following structure
{
"include": [{
"name": "group name",
"items": {
"include": [matrix items]
} ## serialized as string
}]
}
If nested-matrices-count
input is 3
output matrix
would be a JSON formatted string with the following structure
{
"include": [{
"name": "group name",
"items": [{
"name": "chunk 256 range name",
"include": [
"items": {
"include": [matrix items] ## serialized as string
}
]
}] ## serialized as string
} ## serialized as string
}]
}
Make sure you restrict the concurrency of your jobs to avoid DDOS'ing the GitHub Actions API, which might cause restrictions to be applied to your account.
Matrix max nested level | First Matrix Concurrency | Second Matrix Concurrency | Third Matrix Concurrency |
---|---|---|---|
1 | x | - | - |
2 | 1 | x | - |
3 | 1 | 1 | x |
Usage
The action have 3 modes depends of how many nested levels you want. The settings affect to reusable workflows count and usage pattern.
1 Level of nested matrices
.github/workflows/matrices-1.yml
name: Pull Request
on:
pull_request:
branches: [ 'main' ]
types: [opened, synchronize, reopened, closed, labeled, unlabeled]
jobs:
matrix-builder:
runs-on: self-hosted
name: Affected stacks
outputs:
matrix: ${{ steps.extend.outputs.matrix }}
steps:
- id: setup-matrix
uses: druzsan/setup-matrix@v1
with:
matrix: |
os: ubuntu-latest windows-latest macos-latest,
python-version: 3.8 3.9 3.10
arch: arm64 amd64
- uses: cloudposse/github-action-matrix-extended@main
id: extend
with:
matrix: ${{ steps.setup-matrix.outputs.matrix }}
sort-by: '[.python-version, .os, .arch] | join("-")'
group-by: '.arch'
nested-matrices-count: '1'
operation:
if: ${{ needs.matrix-builder.outputs.matrix != '{"include":[]}' }}
needs:
- matrix-builder
strategy:
max-parallel: 10
fail-fast: false # Don't fail fast to avoid locking TF State
matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: Do (${{ matrix.arch }})
runs-on: self-hosted
steps:
- shell: bash
run: |
echo "Do real work - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ matrix.python-version }}"
2 Level of nested matrices
.github/workflows/matrices-1.yml
name: Pull Request
on:
pull_request:
branches: [ 'main' ]
types: [opened, synchronize, reopened, closed, labeled, unlabeled]
jobs:
matrix-builder:
runs-on: self-hosted
name: Affected stacks
outputs:
matrix: ${{ steps.extend.outputs.matrix }}
steps:
- id: setup-matrix
uses: druzsan/setup-matrix@v1
with:
matrix: |
os: ubuntu-latest windows-latest macos-latest,
python-version: 3.8 3.9 3.10
arch: arm64 amd64
- uses: cloudposse/github-action-matrix-extended@main
id: extend
with:
sort-by: '[.python-version, .os, .arch] | join("-")'
group-by: '.arch'
nested-matrices-count: '1'
matrix: ${{ steps.setup-matrix.outputs.matrix }}
operation:
if: ${{ needs.matrix-builder.outputs.matrix != '{"include":[]}' }}
uses: ./.github/workflows/matrices-2.yml
needs:
- matrix-builder
strategy:
max-parallel: 1 # This is important to avoid ddos GHA API
fail-fast: false # Don't fail fast to avoid locking TF State
matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: Group (${{ matrix.name }})
with:
items: ${{ matrix.items }}
.github/workflows/matrices-2.yml
name: Reusable workflow for 2 level of nested matrices
on:
workflow_call:
inputs:
items:
description: "Items"
required: true
type: string
jobs:
operation:
if: ${{ inputs.items != '{"include":[]}' }}
strategy:
max-parallel: 10
fail-fast: false # Don't fail fast to avoid locking TF State
matrix: ${{ fromJson(inputs.items) }}
name: Do (${{ matrix.arch }})
runs-on: self-hosted
steps:
- shell: bash
run: |
echo "Do real work - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ matrix.python-version }}"
3 Level of nested matrices
.github/workflows/matrices-1.yml
name: Pull Request
on:
pull_request:
branches: [ 'main' ]
types: [opened, synchronize, reopened, closed, labeled, unlabeled]
jobs:
matrix-builder:
runs-on: self-hosted
name: Affected stacks
outputs:
matrix: ${{ steps.extend.outputs.matrix }}
steps:
- id: setup-matrix
uses: druzsan/setup-matrix@v1
with:
matrix: |
os: ubuntu-latest windows-latest macos-latest,
python-version: 3.8 3.9 3.10
arch: arm64 amd64
- uses: cloudposse/github-action-matrix-extended@main
id: query
with:
sort-by: '[.python-version, .os, .arch] | join("-")'
group-by: '.arch'
nested-matrices-count: '1'
matrix: ${{ steps.setup-matrix.outputs.matrix }}
operation:
if: ${{ needs.matrix-builder.outputs.matrix != '{"include":[]}' }}
uses: ./.github/workflows/matrices-2.yml
needs:
- matrix-builder
strategy:
max-parallel: 1 # This is important to avoid ddos GHA API
fail-fast: false # Don't fail fast to avoid locking TF State
matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }}
name: Group (${{ matrix.name }})
with:
items: ${{ matrix.items }}
.github/workflows/matrices-2.yml
name: Reusable workflow for 2 level of nested matrices
on:
workflow_call:
inputs:
items:
description: "Items"
required: true
type: string
jobs:
operation:
if: ${{ inputs.items != '{"include":[]}' }}
uses: ./.github/workflows/matrices-3.yml
strategy:
max-parallel: 1 # This is important to avoid ddos GHA API
fail-fast: false # Don't fail fast to avoid locking TF State
matrix: ${{ fromJson(inputs.items) }}
name: Group (${{ matrix.name }})
with:
items: ${{ matrix.items }}
.github/workflows/matrices-3.yml
name: Reusable workflow for 3 level of nested matrices
on:
workflow_call:
inputs:
items:
description: "Items"
required: true
type: string
jobs:
operation:
if: ${{ inputs.items != '{"include":[]}' }}
strategy:
max-parallel: 10
fail-fast: false # Don't fail fast to avoid locking TF State
matrix: ${{ fromJson(inputs.items) }}
name: Do (${{ matrix.arch }})
runs-on: self-hosted
steps:
- shell: bash
run: |
echo "Do real work - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ matrix.python-version }}"
Inputs
Name | Description | Default | Required |
---|---|---|---|
group-by | Group by query | empty | false |
matrix | Matrix inputs (JSON array or object which includes property passed as string or file path) | N/A | true |
nested-matrices-count | Number of nested matrices that should be returned as the output (from 1 to 3) | 1 | false |
sort-by | Sort by query | empty | false |
Outputs
Name | Description |
---|---|
matrix | A matrix suitable for extending matrix size workaround (see README) |