diff --git a/.github/actions/cache-go-dependencies/action.yaml b/.github/actions/cache-go-dependencies/action.yaml index 7ea1be17ccc9d..f26cec5d3f89b 100644 --- a/.github/actions/cache-go-dependencies/action.yaml +++ b/.github/actions/cache-go-dependencies/action.yaml @@ -54,3 +54,16 @@ runs: - 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 diff --git a/.gitignore b/.gitignore index 73713a2db78c8..32a41051640e3 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ shellcheck-reports # These files need to be ignored in order for `make tag` return a clean version string. repository-to-cpe.json container-name-repos-map.json +pkg/version/internal/zversion.go diff --git a/VERSION b/VERSION new file mode 100644 index 0000000000000..291822296cba0 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +4.11.x diff --git a/make/env.mk b/make/env.mk index b5afbc24bbef7..9271d52d7fd5a 100644 --- a/make/env.mk +++ b/make/env.mk @@ -60,7 +60,7 @@ TAG := $(BUILD_TAG) endif ifeq ($(TAG),) -TAG=$(shell git describe --tags --abbrev=10 --dirty --long --exclude '*-nightly-*') +TAG=$(shell git describe --tags --abbrev=10 --dirty --long --exclude '*-nightly-*' 2>/dev/null || cat VERSION 2>/dev/null || echo "0.0.0") endif # Set expiration on Quay.io for non-release tags. diff --git a/operator/Dockerfile b/operator/Dockerfile index 0ebaa95528713..3576b624a56e8 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -11,6 +11,11 @@ ARG roxpath WORKDIR ${roxpath}/ ENV GOPATH=/workspace +# Run the build stage as root so COPY'd files are writable (e.g. for +# generating zversion.go). This is safe because only the final binary +# is copied to the runtime image, which runs as non-root (uid 65534). +USER 0 + # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum @@ -31,27 +36,23 @@ COPY go.sum go.sum ENV GOPROXY=https://proxy.golang.org|https://goproxy.io|direct RUN go mod download || go mod download || go mod download -# Copy operator source +# Copy source files. The go-toolset image runs as uid 1001, so use --chown +# to ensure the build user can write generated files (e.g. zversion.go). COPY operator/ operator/ - -# Copy common packages from repo root COPY pkg/ pkg/ COPY image/ image/ - -# Copy generated files from repo root. -# Not generating them during this docker build because they anyway need to be generated by the orchestrating makefiles. COPY generated/ generated/ - -# Copy scripts/go-build.sh and dependencies. COPY scripts/ scripts/ -COPY operator/build/status.sh status.sh - -# This creates a git repo in workdir so that `git grep` command in build.sh can succeed and actually find //XDef-s. -RUN git init && git add . +COPY COLLECTOR_VERSION SCANNER_VERSION FACT_VERSION VERSION ./ # We've been historically building operator without CGO both upstream and downstream. ENV CGO_ENABLED=0 +# BUILD_TAG is set by the smuggled-status-sh mechanism or Konflux pipeline. +# go-tool.sh uses it for the version string when .git is unavailable. +ARG BUILD_TAG +ENV BUILD_TAG=${BUILD_TAG} + # Build the operator binary. RUN GOOS=linux GOARCH=${TARGET_ARCH} scripts/go-build-file.sh operator/cmd/main.go stackrox-operator diff --git a/pkg/version/internal/version_data.go b/pkg/version/internal/version_data.go index 47b7af84355c7..f0d8dceb2a3ba 100644 --- a/pkg/version/internal/version_data.go +++ b/pkg/version/internal/version_data.go @@ -1,14 +1,17 @@ package internal +// Version variables with fallback defaults for ad-hoc builds (e.g. `go build` +// without the build infrastructure). When building via go-tool.sh, these are +// overridden by the generated zversion.go init() function. var ( // MainVersion is the Rox version. - MainVersion string //XDef:STABLE_MAIN_VERSION + MainVersion string // CollectorVersion is the collector version to be used by default. - CollectorVersion string //XDef:STABLE_COLLECTOR_VERSION + CollectorVersion string // FactVersion is the fact version to be used by default. - FactVersion string //XDef:STABLE_FACT_VERSION + FactVersion string // ScannerVersion is the scanner version to be used with this Rox version. - ScannerVersion string //XDef:STABLE_SCANNER_VERSION - // GitShortSha is the (short) Git SHA that was built. - GitShortSha string //XDef:STABLE_GIT_SHORT_SHA + ScannerVersion string + // GitShortSha is the short git commit SHA. + GitShortSha string ) diff --git a/scripts/go-tool.sh b/scripts/go-tool.sh index 4d96666e4a775..52c8af8944609 100755 --- a/scripts/go-tool.sh +++ b/scripts/go-tool.sh @@ -12,46 +12,61 @@ die() { } RACE="${RACE:-false}" - -x_defs=() -x_def_errors=() - -while read -r line || [[ -n "$line" ]]; do - if [[ "$line" =~ ^[[:space:]]*$ ]]; then - continue - elif [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+(.*)[[:space:]]*$ ]]; then - var="${BASH_REMATCH[1]}" - def="${BASH_REMATCH[2]}" - eval "status_${var}=$(printf '%q' "$def")" +REPO_ROOT="${SCRIPT_DIR}/.." + +# Read version data from committed files. +COLLECTOR_VERSION="$(cat "${REPO_ROOT}/COLLECTOR_VERSION")" || die "Missing COLLECTOR_VERSION" +SCANNER_VERSION="$(cat "${REPO_ROOT}/SCANNER_VERSION")" || die "Missing SCANNER_VERSION" +FACT_VERSION="$(cat "${REPO_ROOT}/FACT_VERSION")" || die "Missing FACT_VERSION" +BASE_VERSION="$(cat "${REPO_ROOT}/VERSION")" || die "Missing VERSION" + +# Generate version data file. Tests use only the base tag (stable across +# commits) to keep ActionIDs stable for test result caching. Builds get +# the full git-describe version with commit count and SHA. +generate_version_file() { + local target="${REPO_ROOT}/pkg/version/internal/zversion.go" + local main_version git_short_sha + + if [[ "$TOOL" == "test" ]]; then + # Base tag only (e.g. "4.11.x") — stable across commits. + main_version="${BASE_VERSION}" + git_short_sha="" + elif [[ -n "${BUILD_TAG:-}" ]]; then + # Konflux/release builds set BUILD_TAG to the full version string. + # Use it directly (the Docker build context has no .git directory). + main_version="${BUILD_TAG}" + git_short_sha="$(echo "$BUILD_TAG" | grep -oP 'g\K[0-9a-f]+$' || echo "")" else - die "Malformed status.sh output line ${line}" + # Full format matching git describe (e.g. "4.11.x-193-g7257553280"). + local commit_count + git_short_sha="$(cd "${REPO_ROOT}"; git rev-parse --short HEAD 2>/dev/null || echo "")" + commit_count="$(cd "${REPO_ROOT}"; git rev-list --count "${BASE_VERSION}..HEAD" 2>/dev/null || echo "0")" + main_version="${BASE_VERSION}-${commit_count}-g${git_short_sha}" fi -done < <(cd "${SCRIPT_DIR}/.."; ./status.sh) - -while read -r line || [[ -n "$line" ]]; do - if [[ "$line" =~ ^[[:space:]]*$ ]]; then - continue - elif [[ "$line" =~ ^([^:]+):([[:digit:]]+):[[:space:]]*(var[[:space:]]+)?([^[:space:]]+)[[:space:]].*//XDef:([^[:space:]]+)[[:space:]]*$ ]]; then - go_file="${BASH_REMATCH[1]}" - go_line="${BASH_REMATCH[2]}" - go_var="${BASH_REMATCH[4]}" - status_var="${BASH_REMATCH[5]}" - - varname="status_${status_var}" - [[ -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")")" - - x_defs+=(-X "\"${go_package}.${go_var}=${!varname}\"") + + local new_content + new_content="// Code generated by go-tool.sh; DO NOT EDIT. + +package internal + +func init() { + MainVersion = \"${main_version}\" + CollectorVersion = \"${COLLECTOR_VERSION}\" + FactVersion = \"${FACT_VERSION}\" + ScannerVersion = \"${SCANNER_VERSION}\" + GitShortSha = \"${git_short_sha}\" +}" + if [[ -f "$target" ]] && [[ "$(cat "$target")" == "$new_content" ]]; then + # Pin mtime to fixed past date for test cache stability. + touch -t 200101010000 "$target" + return fi -done < <(git -C "${SCRIPT_DIR}/.." grep -n '//XDef:' -- '*.go') -if [[ "${#x_def_errors[@]}" -gt 0 ]]; then - printf >&2 "%s\n" "${x_def_errors[@]}" - exit 1 -fi + echo "$new_content" > "$target" + touch -t 200101010000 "$target" +} +generate_version_file -ldflags=("${x_defs[@]}") +ldflags=() if [[ "$DEBUG_BUILD" != "yes" ]]; then ldflags+=(-s -w) fi @@ -88,7 +103,18 @@ function go_build() ( mkdir -p "$output" echo >&2 "Compiling Go source in ${dirs[*]} to ${output}" - invoke_go_build -o "$output" "${dirs[@]}" + local total + total=$(go list -deps "${dirs[@]}" 2>/dev/null | wc -l | tr -d ' ') + local compiled_count=/tmp/go-build-compiled-count + invoke_go_build -o "$output" "${dirs[@]}" 2> >(tee >(wc -l | tr -d ' ' > "$compiled_count") >&2) + wait + local compiled + compiled=$(cat "$compiled_count" 2>/dev/null | tr -d ' ') + local cached=$((total - compiled)) + echo >&2 "Build cache: ${cached}/${total} deps cached ($compiled compiled)" + if [[ "$total" -gt 0 ]] && [[ $((cached * 100 / total)) -lt 50 ]]; then + echo >&2 "WARNING: Build cache hit rate below 50% (${cached}/${total}). Significant code changes invalidate the cached build — expect slower compilation." + fi ) function go_build_file() { @@ -102,7 +128,7 @@ function invoke_go_build() { if [[ "$DEBUG_BUILD" == "yes" ]]; then gcflags+=(-gcflags "all=-N -l") fi - invoke_go build -trimpath "${gcflags[@]}" "$@" + invoke_go build -v -trimpath -buildvcs=false "${gcflags[@]}" "$@" } function go_run() ( @@ -111,7 +137,7 @@ function go_run() ( function go_test() ( unset GOOS - invoke_go test "$@" + invoke_go test -buildvcs=false "$@" ) case "$TOOL" in diff --git a/tests/roxctl/bats-tests/helpers.bash b/tests/roxctl/bats-tests/helpers.bash index 2f86cbb2aef4b..eee94b7adfdb4 100644 --- a/tests/roxctl/bats-tests/helpers.bash +++ b/tests/roxctl/bats-tests/helpers.bash @@ -29,7 +29,7 @@ any_version='[0-9]+\.[0-9]+\.' delete-outdated-binaries() { local roxctl_ver="${1}" - current_tag="$(git describe --tags --abbrev=10 --dirty --long --exclude '*-nightly-*')" + current_tag="$(git describe --tags --abbrev=10 --dirty --long --exclude '*-nightly-*' 2>/dev/null || cat VERSION 2>/dev/null || echo "0.0.0")" echo "Roxctl version='${roxctl_ver}'" >&3 echo "Current tag ='${current_tag}'" >&3 if [[ "${current_tag}" != "${roxctl_ver}" ]]; then