Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a5ac1cf
Add Windows support for snapshot action
crohr Nov 20, 2025
ae2e278
build
crohr Nov 20, 2025
ffb83c5
build
crohr Nov 20, 2025
55a27bb
workflow
crohr Nov 20, 2025
faf5381
fix
crohr Nov 20, 2025
620bcc9
fix
crohr Nov 20, 2025
f47b50b
fix
crohr Nov 20, 2025
528fd2d
fix
crohr Nov 20, 2025
e1e4a57
fix
crohr Nov 20, 2025
1a8fd1d
fix
crohr Nov 21, 2025
e41fa3c
fix
crohr Nov 21, 2025
6392069
aws recommendation
crohr Nov 21, 2025
83d364a
fix
crohr Nov 21, 2025
e34e5b5
fix
crohr Nov 21, 2025
c3424cb
fix
crohr Nov 21, 2025
6323e02
fix
crohr Nov 21, 2025
01d9956
fix
crohr Nov 21, 2025
b043f96
fix
crohr Nov 21, 2025
5642c8e
fix
crohr Nov 21, 2025
c2a3944
fix
crohr Nov 21, 2025
2f44086
Allow custom cache key and restore key
crohr Nov 25, 2025
4b13d21
Fix snapshot JSON file name
crohr Nov 25, 2025
798b193
Clean file path
crohr Nov 25, 2025
17a0d4c
buiold
crohr Nov 25, 2025
37d5c85
fix
crohr Nov 25, 2025
c951c28
fix
crohr Nov 25, 2025
36bff5e
Add cache-hit output
crohr Nov 25, 2025
b10e07b
fix
crohr Nov 25, 2025
4688c86
fix
crohr Nov 25, 2025
610821e
Chown mount point
crohr Nov 25, 2025
f8e9d89
fix
crohr Nov 25, 2025
0c0274f
fix
crohr Nov 25, 2025
abdaabb
Add tests + debug env
crohr Nov 26, 2025
bf567be
fix
crohr Nov 26, 2025
5497c52
Store inputs in state so that the action also works in composite acti…
crohr Nov 26, 2025
d24c153
fix
crohr Nov 26, 2025
5137dab
fix
crohr Nov 26, 2025
06c494b
Remove debug
crohr Nov 26, 2025
ed78de5
Bust cache, verify test file is present
crohr Nov 26, 2025
beaf344
fix
crohr Nov 26, 2025
c0eadc9
fix
crohr Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 139 additions & 12 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,32 @@ on:
permissions:
contents: read

env:
CACHE_BUST: v2

jobs:
build-with-snapshot:
name: Build with Snapshot
runs-on: runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=c7a.2xlarge
test-linux-with-save-docker:
name: Test Linux with save (docker)
runs-on: runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=c7a.xlarge
steps:
- uses: actions/checkout@v4
- uses: ./
- uses: actions/checkout@v5
- name: snapshot
id: snapshot
uses: ./
with:
version: ${{ env.CACHE_BUST }}-${{ github.job }}
path: /var/lib/docker
volume_size: 60
- name: Check test file exists if cache hit
if: steps.snapshot.outputs.cache-hit == 'true'
run: |
test -f /var/lib/docker/test-file.txt || (echo "Error: test-file.txt should exist when cache-hit is true" && exit 1)
echo "✓ test-file.txt exists as expected"
- run: df -ah && sudo ls -al /var/lib/docker
- name: Create test file
run: |
echo "Test file created at $(date)" | sudo tee /var/lib/docker/test-file.txt
sudo ls -lh /var/lib/docker/test-file.txt
- uses: docker/setup-buildx-action@v3
with:
name: runs-on
Expand All @@ -32,21 +47,133 @@ jobs:
repository: runs-on-demo/benchmark-posthog
submodules: recursive
path: repo
- name: Determine upstream commit SHA
id: upstream
run: echo "sha=$(cd repo/upstream && git rev-parse HEAD)" >> $GITHUB_OUTPUT
- uses: docker/build-push-action@v4
with:
context: ./repo/upstream
context: https://github.com/PostHog/posthog.git#${{ steps.upstream.outputs.sha }}
build-args: |
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1

restore-only:
name: "save: false"
runs-on: runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=c7a.2xlarge
test-linux-with-save-simple:
name: Test Linux with save (simple)
runs-on: runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=c7a.large
steps:
- uses: actions/checkout@v4
- uses: ./
- uses: actions/checkout@v5
- name: snapshot
id: snapshot
uses: ./
with:
version: ${{ env.CACHE_BUST }}-${{ github.job }}
path: /path/to/volume
volume_throughput: 125
volume_size: 5
- name: Check test file exists if cache hit
if: steps.snapshot.outputs.cache-hit == 'true'
run: |
test -f /path/to/volume/test-file.txt || (echo "Error: test-file.txt should exist when cache-hit is true" && exit 1)
echo "✓ test-file.txt exists as expected"
- name: Create test file
run: |
echo "Test file created at $(date)" | sudo tee /path/to/volume/test-file.txt
sudo ls -lh /path/to/volume/test-file.txt

test-linux-no-save:
name: Test Linux without save
runs-on: runs-on=${{ github.run_id }}/image=ubuntu24-full-x64/family=c7a.large
steps:
- uses: actions/checkout@v5
- name: snapshot
id: snapshot
uses: ./
with:
version: ${{ env.CACHE_BUST }}-${{ github.job }}-v2
path: /path/to/volume
volume_size: 10
save: false
- run: |
sudo chown -R runner:runner /path/to/volume
dd if=/dev/zero of=/path/to/volume/test-file-1gb bs=1M count=1024
ls -lh /path/to/volume/

test-windows-with-save:
name: Test Windows with save
runs-on: runs-on=${{ github.run_id }}/image=windows25-full-x64/family=m7a.large
steps:
- uses: actions/checkout@v5
- name: snapshot
id: snapshot
uses: ./
with:
version: ${{ env.CACHE_BUST }}-${{ github.job }}
path: C:\test-volume
volume_size: 10
save: true
- name: Check test file exists if cache hit
if: steps.snapshot.outputs.cache-hit == 'true'
shell: pwsh
run: |
$testFile = "C:\test-volume\test-file.txt"
if (Test-Path $testFile) {
Write-Host "✓ test-file.txt exists as expected"
Get-Item $testFile | Format-List FullName, Length, LastWriteTime
Write-Host "File contents:"
Get-Content $testFile
} else {
Write-Error "Error: test-file.txt should exist when cache-hit is true"
exit 1
}
- name: Display all disks and mountpoints
shell: pwsh
run: |
Write-Host "=== All Disks ==="
Get-Disk | Format-Table Number, PartitionStyle, OperationalStatus, Size, FriendlyName -AutoSize
Write-Host "`n=== All Partitions ==="
Get-Partition | Format-Table DiskNumber, PartitionNumber, DriveLetter, AccessPaths, Size -AutoSize
Write-Host "`n=== All Volumes ==="
Get-Volume | Format-Table DriveLetter, FileSystemLabel, FileSystem, Size, SizeRemaining -AutoSize
Write-Host "`n=== Mount Points ==="
Get-Partition | Where-Object { $_.AccessPaths } | ForEach-Object {
Write-Host "Disk $($_.DiskNumber), Partition $($_.PartitionNumber):"
$_.AccessPaths | ForEach-Object { Write-Host " $_" }
}
- name: Test write to mounted volume
shell: pwsh
run: |
$testFile = "C:\test-volume\test-file.txt"
$testContent = "Test file written at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host "Writing test file to $testFile"
Set-Content -Path $testFile -Value $testContent
Write-Host "File written successfully"
Write-Host "`n=== Verifying file ==="
Get-Item $testFile | Format-List FullName, Length, LastWriteTime
Write-Host "File contents:"
Get-Content $testFile
Write-Host "`n=== Directory listing ==="
Get-ChildItem C:\test-volume | Format-Table Name, Length, LastWriteTime -AutoSize

test-windows-no-save:
name: Test Windows without save
runs-on: runs-on=${{ github.run_id }}/image=windows25-full-x64/family=m7a.large
steps:
- uses: actions/checkout@v5
- uses: ./
with:
version: ${{ env.CACHE_BUST }}-${{ github.job }}
path: C:\test-volume
volume_size: 10
save: false
- name: Display all disks and mountpoints
shell: pwsh
run: |
Write-Host "=== All Disks ==="
Get-Disk | Format-Table Number, PartitionStyle, OperationalStatus, Size, FriendlyName -AutoSize
Write-Host "`n=== All Partitions ==="
Get-Partition | Format-Table DiskNumber, PartitionNumber, DriveLetter, AccessPaths, Size -AutoSize
Write-Host "`n=== All Volumes ==="
Get-Volume | Format-Table DriveLetter, FileSystemLabel, FileSystem, Size, SizeRemaining -AutoSize
Write-Host "`n=== Mount Points ==="
Get-Partition | Where-Object { $_.AccessPaths } | ForEach-Object {
Write-Host "Disk $($_.DiskNumber), Partition $($_.PartitionNumber):"
$_.AccessPaths | ForEach-Object { Write-Host " $_" }
}
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ help:
@echo ' make js Generate `index.js` and `post.js` files'
@echo ' make main-linux-amd64 Build static binary for linux/amd64'
@echo ' make main-linux-arm64 Build static binary for linux/arm64'
@echo ' make main-windows-amd64 Build static binary for windows/amd64'
@echo ' make build Build all static binaries + `index.js` and `post.js`'
@echo ' make test Run all tests'
@echo ''

UPX_BIN := $(shell command -v upx 2> /dev/null)
Expand All @@ -31,8 +33,14 @@ main-linux-arm64: _require-upx
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -installsuffix static -o "main-linux-arm64" $(COMMAND)
upx -q -9 "main-linux-arm64"

.PHONY: main-windows-amd64
main-windows-amd64: _require-upx
rm -f main-windows-amd64.exe
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -installsuffix static -o "main-windows-amd64.exe" $(COMMAND)
upx -q -9 "main-windows-amd64.exe"

.PHONY: build
build: main-linux-amd64 main-linux-arm64 js
build: main-linux-amd64 main-linux-arm64 main-windows-amd64 js

.PHONY: _require-upx
_require-upx:
Expand All @@ -54,4 +62,8 @@ release: tag
gh release create $(TAG) --generate-notes

upgrade:
mise exec -- go get -u -t ./...
mise exec -- go get -u -t ./...

.PHONY: test
test:
mise exec -- go test ./...
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,32 @@ jobs:
| volume_initialization_rate | Initialization rate to use for the volume. Useful for very large volumes. 100 MB/s - 200 MB/s: $0.00240/GB, 201 MB/s - 300 MB/s $0.00360/GB | No | 0 |
| wait_for_completion | Wait for snapshot completion before exiting. Note that the first snapshot will always be waited for | No | false |
| save | Save the volume in the post step. When false, the volume is not saved, only restored | No | true |
| key | Custom snapshot key used to segregate snapshots. Defaults to `${{ github.ref_name }}-${{ github.ref }}` | No | auto |
| restore-keys | Multi-line list of fallback snapshot key prefixes, evaluated top-to-bottom. Defaults to `${{ github.ref_name }}-` and `${{ github.default_branch }}-` | No | auto |

## Snapshot selection

When restoring a snapshot, the most recent snapshot for the current branch is fetched. If none is found, the most recent snapshot for the repository default branch will be taken. If none found, a new empty volume is used instead.
Snapshot lookup now mirrors GitHub's cache action semantics:

1. Try the exact `key`.
2. Fall back through each `restore-keys` prefix (latest snapshot whose key starts with the prefix).
3. As a compatibility fallback, use the latest snapshot for the current branch, then the repository default branch.
4. If there's still no match, create a new empty volume.

This keeps the legacy branch-based behavior working while enabling deterministic cache keys for large directories.

### Example: isolation with custom keys

```yaml
- name: Unity Library Snapshot
uses: runs-on/snapshot@v1
with:
path: ${{ github.workspace }}/Library
key: unity-library-${{ hashFiles('Packages/manifest.json') }}
restore-keys: |
unity-library-${{ github.ref_name }}-
unity-library-
```

## Snapshot cleanup

Expand Down
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,15 @@ inputs:
description: 'Save the volume in the post step. When false, the volume is not saved.'
required: false
default: 'true'
key:
description: 'Custom snapshot key. Defaults to github.ref_name-github.ref when unset.'
required: false
default: ''
restore-keys:
description: 'Multiline list of fallback snapshot key prefixes (one per line). Defaults to github.ref_name- and github.default_branch- prefixes.'
required: false
default: ''

outputs:
cache-hit:
description: 'Whether the volume was restored from an existing snapshot (true) or created as a new volume (false).'
14 changes: 6 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const childProcess = require('child_process')
const os = require('os')
const process = require('process')
const path = require('path')

const ARGS = ''.split(',')
const ARGS = ''.split(',').filter(arg => arg !== '')
const WINDOWS = 'win32'
const LINUX = 'linux'
const AMD64 = 'x64'
Expand All @@ -19,21 +20,18 @@ function chooseBinary() {
return `main-linux-arm64`
}
if (platform === WINDOWS && arch === AMD64) {
return `main-windows-amd64`
return `main-windows-amd64.exe`
}

console.error(`Unsupported platform (${platform}) and architecture (${arch})`)
process.exit(1)
process.exit(0)
}

function main() {
const binary = chooseBinary()
const mainScript = `${__dirname}/${binary}`
const mainScript = path.join(__dirname, binary)
if (os.platform() === WINDOWS) {
childProcess.execFileSync('powershell', [
'-Command',
`Start-Process -FilePath "${mainScript}" -ArgumentList "${ARGS.join(' ')}" -Verb RunAs -WindowStyle Hidden -Wait`
], { stdio: 'inherit' })
childProcess.execFileSync(mainScript, ARGS, { stdio: 'inherit' })
} else {
childProcess.execFileSync('sudo', ['-n', '-E', mainScript, ...ARGS], { stdio: 'inherit' })
}
Expand Down
14 changes: 6 additions & 8 deletions index.template.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const childProcess = require('child_process')
const os = require('os')
const process = require('process')
const path = require('path')

const ARGS = '{{ .Args }}'.split(',')
const ARGS = '{{ .Args }}'.split(',').filter(arg => arg !== '')
const WINDOWS = 'win32'
const LINUX = 'linux'
const AMD64 = 'x64'
Expand All @@ -19,21 +20,18 @@ function chooseBinary() {
return `main-linux-arm64`
}
if (platform === WINDOWS && arch === AMD64) {
return `main-windows-amd64`
return `main-windows-amd64.exe`
}

console.error(`Unsupported platform (${platform}) and architecture (${arch})`)
process.exit(1)
process.exit(0)
}

function main() {
const binary = chooseBinary()
const mainScript = `${__dirname}/${binary}`
const mainScript = path.join(__dirname, binary)
if (os.platform() === WINDOWS) {
childProcess.execFileSync('powershell', [
'-Command',
`Start-Process -FilePath "${mainScript}" -ArgumentList "${ARGS.join(' ')}" -Verb RunAs -WindowStyle Hidden -Wait`
], { stdio: 'inherit' })
childProcess.execFileSync(mainScript, ARGS, { stdio: 'inherit' })
} else {
childProcess.execFileSync('sudo', ['-n', '-E', mainScript, ...ARGS], { stdio: 'inherit' })
}
Expand Down
Loading
Loading