Skip to content
Draft
48 changes: 24 additions & 24 deletions .github/actions/cache-go-dependencies/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ inputs:
description: Whether this job saves caches on pushes to the default branch.
required: false
default: 'true'
key-suffix:
description: Extra suffix for cache keys (use to distinguish matrix entries that share github.job).
required: false
default: ''
runs:
using: composite
steps:
Expand All @@ -15,42 +19,38 @@ runs:
echo "GOMODCACHE=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
echo "GOARCH=$(go env GOARCH)" >> "$GITHUB_OUTPUT"
echo "TAG=${{ github.sha }}" >> "$GITHUB_OUTPUT"
suffix="${{ inputs.key-suffix }}"
echo "KEY_SUFFIX=${suffix:+-$suffix}" >> "$GITHUB_OUTPUT"
shell: bash

# Save caches only on pushes to the default branch.
# All other events (PRs, etc.) restore only.
- name: Cache Go Dependencies (save)
if: inputs.save == 'true' && (github.event_name == 'push' && github.ref_name == github.event.repository.default_branch)
# Temporarily always save for testing cache effectiveness.
- name: Cache Go Dependencies
uses: actions/cache@v5
with:
path: ${{ steps.cache-paths.outputs.GOMODCACHE }}
key: go-mod-v1-${{ hashFiles('**/go.sum') }}
restore-keys: go-mod-v1-

- name: Cache Go Dependencies (restore)
if: ${{ !(inputs.save == 'true' && (github.event_name == 'push' && github.ref_name == github.event.repository.default_branch)) }}
uses: actions/cache/restore@v5
with:
path: ${{ steps.cache-paths.outputs.GOMODCACHE }}
key: go-mod-v1-${{ hashFiles('**/go.sum') }}
restore-keys: go-mod-v1-

- name: Cache Go Build (save)
if: inputs.save == 'true' && (github.event_name == 'push' && github.ref_name == github.event.repository.default_branch)
- name: Cache Go Build
uses: actions/cache@v5
with:
path: ${{ steps.cache-paths.outputs.GOCACHE }}
key: go-build-v1-${{ github.job }}-${{ steps.cache-paths.outputs.GOARCH }}-${{ steps.cache-paths.outputs.TAG }}
restore-keys: go-build-v1-${{ github.job }}-${{ steps.cache-paths.outputs.GOARCH }}-

- name: Cache Go Build (restore)
if: ${{ !(inputs.save == 'true' && (github.event_name == 'push' && github.ref_name == github.event.repository.default_branch)) }}
uses: actions/cache/restore@v5
with:
path: ${{ steps.cache-paths.outputs.GOCACHE }}
key: go-build-v1-${{ github.job }}-${{ steps.cache-paths.outputs.GOARCH }}-${{ steps.cache-paths.outputs.TAG }}
restore-keys: go-build-v1-${{ github.job }}-${{ steps.cache-paths.outputs.GOARCH }}-
key: go-build-v1-${{ github.job }}${{ steps.cache-paths.outputs.KEY_SUFFIX }}-${{ steps.cache-paths.outputs.GOARCH }}-${{ steps.cache-paths.outputs.TAG }}
restore-keys: go-build-v1-${{ github.job }}${{ steps.cache-paths.outputs.KEY_SUFFIX }}-${{ steps.cache-paths.outputs.GOARCH }}-

- name: Download Go modules
run: make deps --always-make
shell: bash

- name: Stabilize file mtimes for Go test cache
run: |
# Go's test cache validates inputs by (path, size, mtime) — NOT content.
# git checkout sets all mtimes to "now", which differs between CI runs,
# causing every test to miss the cache. Setting a fixed mtime makes the
# test cache hit across runs when file content hasn't changed.
#
# Risk: if a non-.go testdata file changes content but not size between
# commits, the test cache could return a stale result. This is extremely
# rare and Go's own test cache has the same inherent limitation.
git ls-files -z | xargs -0 touch -t 200101010000
shell: bash
10 changes: 10 additions & 0 deletions .github/workflows/unit-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:

- name: Cache Go dependencies
uses: ./.github/actions/cache-go-dependencies
with:
key-suffix: ${{ matrix.gotags }}

- name: Go Unit Tests
run: ${{ matrix.gotags }} make go-unit-tests
Expand Down Expand Up @@ -122,6 +124,8 @@ jobs:

- name: Cache Go dependencies
uses: ./.github/actions/cache-go-dependencies
with:
key-suffix: ${{ matrix.gotags }}

- name: Is Postgres ready
run: pg_isready -h 127.0.0.1
Expand Down Expand Up @@ -182,6 +186,8 @@ jobs:

- name: Cache Go dependencies
uses: ./.github/actions/cache-go-dependencies
with:
key-suffix: ${{ matrix.gotags }}

- name: Is Postgres ready
run: pg_isready -h 127.0.0.1
Expand Down Expand Up @@ -303,6 +309,8 @@ jobs:

- name: Cache Go dependencies
uses: ./.github/actions/cache-go-dependencies
with:
key-suffix: ${{ matrix.gotags }}

- uses: ./.github/actions/handle-tagged-build

Expand Down Expand Up @@ -403,6 +411,8 @@ jobs:

- name: Cache Go dependencies
uses: ./.github/actions/cache-go-dependencies
with:
key-suffix: ${{ matrix.gotags }}

- name: Login to Quay.io
uses: docker/login-action@v4
Expand Down
8 changes: 8 additions & 0 deletions pkg/mathutil/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@ func Mod(a, b int) int {
}
return r
}

// MinInt returns the smaller of a and b.
func MinInt(a, b int) int {
if a < b {
return a
}
return b
}
54 changes: 45 additions & 9 deletions scripts/go-tool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ die() {
}

RACE="${RACE:-false}"
REPO_ROOT="${SCRIPT_DIR}/.."

x_defs=()
x_def_errors=()
Expand All @@ -26,7 +27,7 @@ while read -r line || [[ -n "$line" ]]; do
else
die "Malformed status.sh output line ${line}"
fi
done < <(cd "${SCRIPT_DIR}/.."; ./status.sh)
done < <(cd "${REPO_ROOT}"; ./status.sh)

while read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*$ ]]; then
Expand All @@ -41,33 +42,68 @@ while read -r line || [[ -n "$line" ]]; do
[[ -n "${!varname}" ]] || x_def_errors+=(
"Variable ${go_var} defined in ${go_file}:${go_line} references status var ${status_var} that is not part of the status.sh output"
)
go_package="$(cd "${SCRIPT_DIR}/.."; go list -e "./$(dirname "$go_file")")"
go_package="$(cd "${REPO_ROOT}"; go list -e "./$(dirname "$go_file")")"

x_defs+=(-X "\"${go_package}.${go_var}=${!varname}\"")
fi
done < <(git -C "${SCRIPT_DIR}/.." grep -n '//XDef:' -- '*.go')
done < <(git -C "${REPO_ROOT}" grep -n '//XDef:' -- '*.go')
if [[ "${#x_def_errors[@]}" -gt 0 ]]; then
printf >&2 "%s\n" "${x_def_errors[@]}"
exit 1
fi

ldflags=("${x_defs[@]}")
# Build ldflags: full version for builds, stable base tag for tests.
# Go includes -X ldflags in the link ActionID (exec.go:1404), so per-commit
# values (MainVersion, GitShortSha) invalidate the test result cache.
# Using a stable base tag for tests keeps test ActionIDs constant across commits.
build_ldflags=("${x_defs[@]}")

BASE_VERSION="$(cd "${REPO_ROOT}"; git describe --tags --abbrev=0 --exclude '*-nightly-*' 2>/dev/null || echo "")"
if [[ -n "$BASE_VERSION" ]]; then
version_pkg="$(cd "${REPO_ROOT}"; go list -e ./pkg/version/internal)"
test_ldflags=()
# x_defs contains pairs: -X "pkg.Var=value". Iterate in steps of 2.
for (( i=0; i<${#x_defs[@]}; i+=2 )); do
xflag="${x_defs[i]}" # -X
xval="${x_defs[i+1]}" # "pkg.Var=value"
if [[ "$xval" == *"MainVersion="* ]]; then
test_ldflags+=(-X "\"${version_pkg}.MainVersion=${BASE_VERSION}\"")
elif [[ "$xval" == *"GitShortSha="* ]]; then
# Skip GitShortSha for tests — not needed and changes per commit.
:
else
test_ldflags+=("$xflag" "$xval")
fi
done
else
# No git tags available — fall back to full ldflags for tests too.
test_ldflags=("${x_defs[@]}")
fi

if [[ "$DEBUG_BUILD" != "yes" ]]; then
ldflags+=(-s -w)
build_ldflags+=(-s -w)
test_ldflags+=(-s -w)
fi

if [[ "${CGO_ENABLED}" != 0 ]]; then
echo >&2 "CGO_ENABLED is not 0. Compiling with -linkmode=external"
ldflags+=('-linkmode=external')
build_ldflags+=('-linkmode=external')
test_ldflags+=('-linkmode=external')
fi

function invoke_go() {
tool="$1"
local tool="$1"
shift
local flags
if [[ "$tool" == "test" ]]; then
flags="${test_ldflags[*]}"
else
flags="${build_ldflags[*]}"
fi
if [[ "$RACE" == "true" ]]; then
CGO_ENABLED=1 go "$tool" -race -ldflags="${ldflags[*]}" -tags "$(tr , ' ' <<<"$GOTAGS")" "$@"
CGO_ENABLED=1 go "$tool" -race -buildvcs=false -ldflags="${flags}" -tags "$(tr , ' ' <<<"$GOTAGS")" "$@"
else
go "$tool" -ldflags="${ldflags[*]}" -tags "$(tr , ' ' <<<"$GOTAGS")" "$@"
go "$tool" -buildvcs=false -ldflags="${flags}" -tags "$(tr , ' ' <<<"$GOTAGS")" "$@"
fi
}

Expand Down
23 changes: 17 additions & 6 deletions status.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
#!/bin/sh

# Note: This requires .git directory in the build context (e.g. builder container)
echo "STABLE_MAIN_VERSION $(make --quiet --no-print-directory tag)"
echo "STABLE_COLLECTOR_VERSION $(make --quiet --no-print-directory collector-tag)"
echo "STABLE_FACT_VERSION $(make --quiet --no-print-directory fact-tag)"
echo "STABLE_SCANNER_VERSION $(make --quiet --no-print-directory scanner-tag)"
echo "STABLE_GIT_SHORT_SHA $(make --quiet --no-print-directory shortcommit)"
# Outputs version variables for go-tool.sh's XDef ldflags injection.
# Uses direct git/cat commands instead of 'make' to avoid ~15s of
# Makefile parsing overhead per invocation (5 make calls × ~3s each).

REPO_ROOT="$(cd "$(dirname "$0")" && pwd)"

main_version="${BUILD_TAG:-$(git describe --tags --abbrev=10 --dirty --long --exclude '*-nightly-*' 2>/dev/null || cat "${REPO_ROOT}/VERSION" 2>/dev/null || echo '0.0.0')}"
collector_version="$(cat "${REPO_ROOT}/COLLECTOR_VERSION" 2>/dev/null || echo '0.0.0')"
fact_version="$(cat "${REPO_ROOT}/FACT_VERSION" 2>/dev/null || echo '0.0.0')"
scanner_version="$(cat "${REPO_ROOT}/SCANNER_VERSION" 2>/dev/null || echo '0.0.0')"
git_short_sha="${SHORTCOMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')}"

echo "STABLE_MAIN_VERSION ${main_version}"
echo "STABLE_COLLECTOR_VERSION ${collector_version}"
echo "STABLE_FACT_VERSION ${fact_version}"
echo "STABLE_SCANNER_VERSION ${scanner_version}"
echo "STABLE_GIT_SHORT_SHA ${git_short_sha}"
Loading