Don't waste any time on extra work. Use Standard Action to automatically detect CI targets that need re-doing; implemented on top of familiar GH Actions.
- Evaluate once and distribute final build instructions to workers
- Once configured,
discoverypicks up new targets automatically - Optional
provisoscript can detect if work needs to be done
Note on
proviso: one example is the oci block type which checks if the image is already in the registry and only schedules a build if its missing. Ifprovisoqueries private remote state then thediscoveryenvironment must provide all authentication prior to running the discovery step.
Minimumn nix version v2.16.1
Tip! Since GitHub CI doesn't support yaml anchors, explode your file with:
yq '. | explode(.)' ci.raw.yaml > ci.yaml
{
/* ... */
outputs = {std, ...}@inputs: std.growOn {
/* ... */
cellBlocks = with std.blockTypes; [
(installables "packages" {ci.build = true;})
(containers "oci-images" {ci.publish = true;})
(kubectl "deployments" {ci.apply = true;})
];
/* ... */
};
}# yq '. | explode(.)' this.yml > .github/workflows/std.yml
name: CI/CD
on:
pull_request:
branches:
- main
push:
branches:
- main
permissions:
id-token: write
contents: read
concurrency:
group: std-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
discover:
outputs:
hits: ${{ steps.discovery.outputs.hits }}
runs-on: ubuntu-latest
steps:
# Important: use v25 or above as it also detects flake configuration
- uses: nixbuild/nix-quick-install-action@master
# if you want to use nixbuild
- uses: nixbuild/nixbuild-action@v17
with:
nixbuild_ssh_key: ${{ secrets.SSH_PRIVATE_KEY }}
generate_summary_for: job
# significantly speeds up things in small projects
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: divnix/std-action/discover@main
id: discovery
build: &job
needs: discover
name: ${{ matrix.target.jobName }}
runs-on: ubuntu-latest
if: fromJSON(needs.discover.outputs.hits).packages.build != '{}'
strategy:
matrix:
target: ${{ fromJSON(needs.discover.outputs.hits).packages.build }}
steps:
# Important: use v25 or above as it also detects flake configuration
- uses: nixbuild/nix-quick-install-action@master
# if you want to use nixbuild
- uses: nixbuild/nixbuild-action@v17
with:
nixbuild_ssh_key: ${{ secrets.SSH_PRIVATE_KEY }}
generate_summary_for: job
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: divnix/std-action/run@main
images:
<<: *job
needs: [discover, build]
if: fromJSON(needs.discover.outputs.hits).oci-images.publish != '{}'
strategy:
matrix:
target: ${{ fromJSON(needs.discover.outputs.hits).oci-images.publish }}
deploy:
<<: *job
needs: [discover, images]
environment:
name: development
url: https://my.dev.example.com
if: fromJSON(needs.discover.outputs.hits).deployments.apply != '{}'
strategy:
matrix:
target: ${{ fromJSON(needs.discover.outputs.hits).deployments.apply }}nix>= v2.16.1zstd- (gnu)
parallel jqbase64bash> v5
The persistent host must also implement the nixConfig detection capabilities
implemented by this script.
{
/* ... */
outputs = {std, ...}@inputs: std.growOn {
/* ... */
cellBlocks = with std.blockTypes; [
(devshells "envs" {ci.build = true;})
(containers "oci-images" {ci.publish = true;})
];
/* ... */
};
}# yq '. | explode(.)' this.yml > .github/workflows/std.yml
name: CI/CD
on:
pull_request:
branches:
- main
push:
branches:
- main
env:
DISCOVERY_USER_NAME: gha-runner
DISCOVERY_KNOWN_HOSTS_ENTRY: "10.10.10.10 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEOVVDZydvD+diYa6A3EtA3WGw5NfN0wv7ckQxa/fX1O"
permissions:
id-token: write
contents: read
concurrency:
group: ${{ github.sha }}
cancel-in-progress: true
jobs:
discover:
outputs:
hits: ${{ steps.discovery.outputs.hits }}
runs-on: [self-hosted, discovery]
steps:
- name: Standard Discovery
uses: divnix/std-action/discover@main
id: discovery
# avoids transporting derivations via GH Cache
with: { ffBuildInstructions: true }
image: &run-job
needs: discover
strategy:
fail-fast: false
matrix:
target: ${{ fromJSON(needs.discover.outputs.hits).oci-images.publish }}
if: fromJSON(needs.discover.outputs.hits).oci-images.publish != '{}'
name: ${{ matrix.target.jobName }}
runs-on: ubuntu-latest
steps:
# sets up ssh credentials for `ssh discovery ...`
- uses: divnix/std-action/setup-discovery-ssh@main
with:
ssh_key: ${{ secrets.SSH_PRIVATE_KEY_CI }}
user_name: ${{ env.DISCOVERY_USER_NAME }}
ssh_known_hosts_entry: ${{ env.DISCOVERY_KNOWN_HOSTS_ENTRY }}
- uses: divnix/std-action/run@main
# avoids retreiving derivations via GH Cache and uses `ssh discovery ...` instead
with: { ffBuildInstructions: true }
build:
<<: *run-job
strategy:
matrix:
target: ${{ fromJSON(needs.discover.outputs.hits).envs.build }}
if: fromJSON(needs.discover.outputs.hits).envs.build != '{}'Hits from the discovery phase are namespaced by Block and Action.
That means:
- In:
target: ${{ fromJSON(needs.discover.outputs.hits).packages.build }}packagesis the name of a Standard Blockbuildis the name of an Action of that Block
Watch out for base64-encoded blobs in the logs, you can inspect the
working data of that context by doing: base64 -d <<< copy-blob-here | jq.