Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
144 changes: 131 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ permissions:
contents: read

jobs:
formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Check formatting
shell: bash
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "The following files need gofmt:"
echo "$unformatted"
exit 1
fi

test:
strategy:
fail-fast: false
Expand All @@ -20,10 +37,20 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go test -count=1 ./...
- run: go vet ./...
- name: Unit tests
run: go test -count=1 ./...

vet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Vet
run: go vet ./...

quality:
race:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -32,11 +59,27 @@ jobs:
go-version-file: go.mod
- name: Race detector
run: go test -race -count=1 ./...

coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Coverage report
run: |
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -func=coverage.out | awk '/^total:/ { gsub("%", "", $3); if ($3 + 0 < 80) { print "coverage below 80%: " $3 "%"; exit 1 } }'

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Installer syntax
run: sh -n install.sh
- name: Cross-compile supported targets
Expand All @@ -50,19 +93,94 @@ jobs:
CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -trimpath -o "/tmp/remote-${os}-${arch}${extension}" ./cmd/remote
done

windows-installer:
install-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Build release fixture
shell: bash
run: |
asset_dir="$RUNNER_TEMP/releases/latest/download"
mkdir -p "$asset_dir"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-trimpath -ldflags "-s -w -X main.version=ci-smoke" \
-o "$asset_dir/remote-linux-amd64" ./cmd/remote
(cd "$asset_dir" && sha256sum remote-linux-amd64 > checksums.txt)
- name: Run installer and installed CLI
shell: bash
run: |
python3 -m http.server 8765 --bind 127.0.0.1 \
--directory "$RUNNER_TEMP/releases" >/tmp/remote-installer-http.log 2>&1 &
server_pid=$!
trap 'kill "$server_pid"' EXIT

for attempt in 1 2 3 4 5; do
curl -fsS http://127.0.0.1:8765/latest/download/checksums.txt >/dev/null && break
if [ "$attempt" -eq 5 ]; then
cat /tmp/remote-installer-http.log
exit 1
fi
sleep 1
done

REMOTE_RELEASE_BASE_URL=http://127.0.0.1:8765 \
REMOTE_INSTALL_DIR="$RUNNER_TEMP/remote-bin" \
sh ./install.sh
test -x "$RUNNER_TEMP/remote-bin/remote"
test "$("$RUNNER_TEMP/remote-bin/remote" version)" = "ci-smoke"

install-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Parse PowerShell installer
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Build release fixture
shell: pwsh
run: |
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path "./install.ps1"), [ref]$tokens, [ref]$errors
) > $null
if ($errors.Count) {
$errors | ForEach-Object { Write-Error $_ }
exit 1
$AssetDir = Join-Path $env:RUNNER_TEMP "releases\latest\download"
New-Item -ItemType Directory -Force -Path $AssetDir | Out-Null
$Asset = "remote-windows-amd64.exe"
go build -trimpath -ldflags "-s -w -X main.version=ci-smoke" `
-o (Join-Path $AssetDir $Asset) ./cmd/remote
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$Hash = (Get-FileHash -Algorithm SHA256 (Join-Path $AssetDir $Asset)).Hash.ToLowerInvariant()
"$Hash $Asset" | Set-Content -Encoding ascii (Join-Path $AssetDir "checksums.txt")
- name: Run installer and installed CLI
shell: pwsh
run: |
$ReleaseRoot = Join-Path $env:RUNNER_TEMP "releases"
$Log = Join-Path $env:RUNNER_TEMP "remote-installer-http.log"
$Server = Start-Process python -ArgumentList @(
"-m", "http.server", "8765", "--bind", "127.0.0.1", "--directory", $ReleaseRoot
) -RedirectStandardOutput $Log -RedirectStandardError "$Log.err" -PassThru
try {
$Ready = $false
foreach ($Attempt in 1..5) {
try {
Invoke-WebRequest -UseBasicParsing `
http://127.0.0.1:8765/latest/download/checksums.txt | Out-Null
$Ready = $true
break
} catch {
Start-Sleep -Seconds 1
}
}
if (-not $Ready) { throw "release fixture server did not start" }

$env:REMOTE_RELEASE_BASE_URL = "http://127.0.0.1:8765"
$env:REMOTE_INSTALL_DIR = Join-Path $env:RUNNER_TEMP "remote-bin"
& ./install.ps1
$Installed = Join-Path $env:REMOTE_INSTALL_DIR "remote.exe"
if (-not (Test-Path $Installed)) { throw "installer did not create $Installed" }
$InstalledVersion = & $Installed version
if ($LASTEXITCODE -ne 0 -or $InstalledVersion -ne "ci-smoke") {
throw "installed CLI failed: expected ci-smoke, got '$InstalledVersion'"
}
} finally {
Stop-Process -Id $Server.Id -Force -ErrorAction SilentlyContinue
}
116 changes: 101 additions & 15 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
contents: write

jobs:
release:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -22,20 +22,6 @@ jobs:
go test ./...
go vet ./...
sh -n install.sh
- name: Validate Windows installer
shell: pwsh
run: |
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path "./install.ps1"),
[ref]$tokens,
[ref]$errors
) > $null
if ($errors.Count) {
$errors | ForEach-Object { Write-Error $_ }
exit 1
}
- name: Build release binaries
run: |
mkdir -p dist
Expand All @@ -52,6 +38,106 @@ jobs:
done
cd dist
sha256sum remote-* > checksums.txt
- name: Store release assets
uses: actions/upload-artifact@v4
with:
name: release-assets
path: dist/
if-no-files-found: error

install-smoke:
needs: build
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Download release assets
uses: actions/download-artifact@v4
with:
name: release-assets
path: dist
- name: Install and execute release on Linux
if: runner.os == 'Linux'
shell: bash
run: |
fixture="$RUNNER_TEMP/releases/download/$GITHUB_REF_NAME"
install_dir="$RUNNER_TEMP/remote-bin"
mkdir -p "$fixture"
cp dist/* "$fixture/"

python3 -m http.server 8765 --bind 127.0.0.1 \
--directory "$RUNNER_TEMP/releases" >/tmp/remote-release-http.log 2>&1 &
server_pid=$!
trap 'kill "$server_pid"' EXIT
for attempt in 1 2 3 4 5; do
curl -fsS "http://127.0.0.1:8765/download/$GITHUB_REF_NAME/checksums.txt" >/dev/null && break
if [ "$attempt" -eq 5 ]; then
cat /tmp/remote-release-http.log
exit 1
fi
sleep 1
done

REMOTE_VERSION="$GITHUB_REF_NAME" \
REMOTE_RELEASE_BASE_URL=http://127.0.0.1:8765 \
REMOTE_INSTALL_DIR="$install_dir" \
sh ./install.sh
test -x "$install_dir/remote"
test "$("$install_dir/remote" version)" = "$GITHUB_REF_NAME"
- name: Install and execute release on Windows
if: runner.os == 'Windows'
shell: pwsh
run: |
$Fixture = Join-Path $env:RUNNER_TEMP "releases\download\$env:GITHUB_REF_NAME"
New-Item -ItemType Directory -Force -Path $Fixture | Out-Null
Copy-Item "dist\*" $Fixture

$ReleaseRoot = Join-Path $env:RUNNER_TEMP "releases"
$Log = Join-Path $env:RUNNER_TEMP "remote-release-http.log"
$Server = Start-Process python -ArgumentList @(
"-m", "http.server", "8765", "--bind", "127.0.0.1", "--directory", $ReleaseRoot
) -RedirectStandardOutput $Log -RedirectStandardError "$Log.err" -PassThru
try {
$Ready = $false
foreach ($Attempt in 1..5) {
try {
Invoke-WebRequest -UseBasicParsing `
"http://127.0.0.1:8765/download/$env:GITHUB_REF_NAME/checksums.txt" | Out-Null
$Ready = $true
break
} catch {
Start-Sleep -Seconds 1
}
}
if (-not $Ready) { throw "release fixture server did not start" }

$env:REMOTE_VERSION = $env:GITHUB_REF_NAME
$env:REMOTE_RELEASE_BASE_URL = "http://127.0.0.1:8765"
$env:REMOTE_INSTALL_DIR = Join-Path $env:RUNNER_TEMP "remote-bin"
& ./install.ps1
$Installed = Join-Path $env:REMOTE_INSTALL_DIR "remote.exe"
if (-not (Test-Path $Installed)) { throw "installer did not create $Installed" }
$InstalledVersion = & $Installed version
if ($LASTEXITCODE -ne 0 -or $InstalledVersion -ne $env:GITHUB_REF_NAME) {
throw "installed CLI failed: expected $env:GITHUB_REF_NAME, got '$InstalledVersion'"
}
} finally {
Stop-Process -Id $Server.Id -Force -ErrorAction SilentlyContinue
}

publish:
needs: [build, install-smoke]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download tested release assets
uses: actions/download-artifact@v4
with:
name: release-assets
path: dist
- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ make coverage
go vet ./...
```

CI runs tests and vet on Linux, macOS, and Windows, validates both installers,
runs the race detector, cross-compiles every supported OS/architecture pair,
and rejects total statement coverage below 80%.
CI runs tests on Linux, macOS, and Windows. Independent jobs run formatting,
vet, the race detector, coverage, cross-compilation, and real installer smoke
tests in parallel. The installer jobs download checksummed fixture releases,
install them, and execute the resulting Linux or Windows binary. CI rejects
total statement coverage below 80%.

## Testing changes

Expand Down
Loading