From 1c6b3a80fc4016f158ef0ee78c69a256bf70cf32 Mon Sep 17 00:00:00 2001 From: Barry Morrison <689591+esacteksab@users.noreply.github.com> Date: Wed, 10 Jun 2026 04:51:25 -0600 Subject: [PATCH 1/2] chore: golang 1.25.11 --- .golangci.yaml | 2 +- .mise.toml | 2 +- Dockerfile | 2 +- Makefile | 4 ++-- go.mod | 2 +- go.tool.mod | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index fd80958..9c163d3 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,7 +3,7 @@ version: "2" run: timeout: 5m tests: true - go: 1.25.10 + go: 1.25.11 linters: enable: - dupl diff --git a/.mise.toml b/.mise.toml index d24c2c9..7d7efc5 100644 --- a/.mise.toml +++ b/.mise.toml @@ -1,5 +1,5 @@ [tools] - go = "1.25.10" + go = "1.25.11" node = "22.22.0" pnpm = "11.4.0" python = "3.12.12" diff --git a/Dockerfile b/Dockerfile index c2d0965..e591ac5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM esacteksab/go:1.25.10-2026-05-29@sha256:804a4bcb657d118ae7845e9b9cd54517ce0a350274c7eb2f5d073f14508f5908 AS builder +FROM esacteksab/go:1.25.11-2026-06-05@sha256:5014f4c7f045759141b9152dcaacdd50a0466f6402bfba06a76fe15ae4a73550 AS builder ENV GOMODCACHE=/go/pkg/mod diff --git a/Makefile b/Makefile index 335dfc0..1185ff7 100644 --- a/Makefile +++ b/Makefile @@ -78,8 +78,8 @@ modernize: .PHONY: update-go-version update-go-version: @if [ -z "$(or $(GO_VERSION),$(version))" ]; then \ - echo "Usage: make update-go-version GO_VERSION=1.25.10"; \ - echo " or: make update-go-version version=1.25.10"; \ + echo "Usage: make update-go-version GO_VERSION=1.25.11"; \ + echo " or: make update-go-version version=1.25.11"; \ exit 1; \ fi ./scripts/update-go-version.sh "$(or $(GO_VERSION),$(version))" diff --git a/go.mod b/go.mod index 8fcc31d..fa6e4bd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/esacteksab/gh-actlock -go 1.25.10 +go 1.25.11 require ( github.com/charmbracelet/lipgloss v1.1.0 diff --git a/go.tool.mod b/go.tool.mod index 8473afe..9b0a74b 100644 --- a/go.tool.mod +++ b/go.tool.mod @@ -1,7 +1,7 @@ // How to use https://www.alexedwards.net/blog/how-to-manage-tool-dependencies-in-go-1.24-plus module github.com/esacteksab/gh-actlock -go 1.25.10 +go 1.25.11 tool ( github.com/segmentio/golines From 78a25a1fa2ef087a8e17296eefed8429400d9732 Mon Sep 17 00:00:00 2001 From: Barry Morrison <689591+esacteksab@users.noreply.github.com> Date: Sun, 14 Jun 2026 06:49:32 -0600 Subject: [PATCH 2/2] chore: golang 1.25.11 --- .golangci.yaml | 2 +- .typos.toml | 2 + Dockerfile | 2 +- scripts/update-go-version.sh | 198 ++++++++++++++++++++++++++++++----- 4 files changed, 174 insertions(+), 30 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 9c163d3..7180c41 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,7 +3,7 @@ version: "2" run: timeout: 5m tests: true - go: 1.25.11 + go: "1.25.11" linters: enable: - dupl diff --git a/.typos.toml b/.typos.toml index 8464559..f35ba5b 100644 --- a/.typos.toml +++ b/.typos.toml @@ -3,6 +3,8 @@ [default.extend-words] ERRO = "ERRO" Hashi = "Hashi" + fter = "fter" + etry = "etry" [files] extend-exclude = [".goreleaser.yaml"] diff --git a/Dockerfile b/Dockerfile index e591ac5..d006ad2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM esacteksab/go:1.25.11-2026-06-05@sha256:5014f4c7f045759141b9152dcaacdd50a0466f6402bfba06a76fe15ae4a73550 AS builder +FROM esacteksab/go:1.25.11-2026-06-12@sha256:76db7b11120372912b1e2c92d6339cf9b5f82ea43f554955d3954fb67a142c8b AS builder ENV GOMODCACHE=/go/pkg/mod diff --git a/scripts/update-go-version.sh b/scripts/update-go-version.sh index 3d35132..5155088 100755 --- a/scripts/update-go-version.sh +++ b/scripts/update-go-version.sh @@ -5,8 +5,21 @@ set -euo pipefail readonly VERSION_INPUT="${1:-}" readonly DOCKERFILE="Dockerfile" +# Global initialization (Token managed internally; no longer leaks via set -x) +HUB_TOKEN="" + +# Manage temporary files robustly, even if the script crashes or dies early. +declare -a CLEANUP_FILES=() +cleanup() { + if [[ ${#CLEANUP_FILES[@]} -gt 0 ]]; then + rm -f "${CLEANUP_FILES[@]}" + fi +} +trap cleanup EXIT + +# FIX: Redirect to stderr (>&2) so command substitutions $(...) don't capture log output log() { - echo "[update-go-version] $*" + echo "[update-go-version] $*" >&2 } die() { @@ -37,12 +50,95 @@ replace_or_fail() { sed -E -i "$sed_expr" "$file" } +# --------------------------------------------------------------------------- +# Docker Hub API helpers +# --------------------------------------------------------------------------- + +hub_authenticate() { + if [[ -n "${DOCKERHUB_USERNAME:-}" && -n "${DOCKERHUB_TOKEN:-}" ]]; then + log "Authenticating to Docker Hub as ${DOCKERHUB_USERNAME}" + local resp + + # Send credentials safely, suppressing stdout. + # If it fails, `|| true` catches it, and jq handles the empty/invalid output safely. + resp="$(curl -sS \ + -H 'Content-Type: application/json' \ + -d "{\"username\": \"${DOCKERHUB_USERNAME}\", \"password\": \"${DOCKERHUB_TOKEN}\"}" \ + "https://hub.docker.com/v2/users/login" 2>/dev/null || true)" + + HUB_TOKEN="$(printf '%s' "$resp" | jq -r '.token // empty')" + [[ -n "$HUB_TOKEN" ]] || die "Docker Hub authentication failed (check credentials)." + log "Docker Hub authentication succeeded" + else + log "No DOCKERHUB_USERNAME/DOCKERHUB_TOKEN set; using anonymous Hub API access" + fi +} + +hub_get() { + local url="$1" + local attempt=1 + local max_attempts="${HUB_MAX_ATTEMPTS:-6}" + local delay=2 + + while :; do + local body_file + body_file="$(mktemp)" + CLEANUP_FILES+=("$body_file") + + local headers_file + headers_file="$(mktemp)" + CLEANUP_FILES+=("$headers_file") + + # Use arrays to prevent Authorization tokens from leaking into `ps aux` process tables + local curl_args=( + -sS + -o "$body_file" + -D "$headers_file" + -w '%{http_code}' + -H 'Accept: application/json' + ) + + if [[ -n "${HUB_TOKEN}" ]]; then + curl_args+=( -H "Authorization: Bearer ${HUB_TOKEN}" ) + fi + + local http + http="$(curl "${curl_args[@]}" "$url" 2>/dev/null || echo "000")" + + if [[ "$http" == "200" ]]; then + cat "$body_file" + return 0 + fi + + if { [[ "$http" == "429" ]] || [[ "$http" == 5* ]] || [[ "$http" == "000" ]]; } && (( attempt < max_attempts )); then + local retry_after + retry_after="$(grep -i '^Retry-After:' "$headers_file" 2>/dev/null | tail -n1 \ + | sed -E 's/^[Rr]etry-[Aa]fter:[[:space:]]*([0-9]+).*/\1/' | tr -d '\r')" + + local sleep_for + if [[ "$retry_after" =~ ^[0-9]+$ ]]; then + sleep_for="$retry_after" + else + sleep_for="$delay" + fi + + log "Docker Hub API returned HTTP ${http}; retry ${attempt}/${max_attempts} after ${sleep_for}s" + sleep "$sleep_for" + (( delay = delay * 2 > 60 ? 60 : delay * 2 )) + (( attempt++ )) + continue + fi + + die "Docker Hub request failed (HTTP ${http}): ${url}" + done +} + update_known_version_files() { log "Updating Go version references in known files" replace_or_fail "go.mod" '^go [0-9]+\.[0-9]+\.[0-9]+$' "s/^go [0-9]+\.[0-9]+\.[0-9]+$/go ${VERSION_INPUT}/" replace_or_fail "go.tool.mod" '^go [0-9]+\.[0-9]+\.[0-9]+$' "s/^go [0-9]+\.[0-9]+\.[0-9]+$/go ${VERSION_INPUT}/" - replace_or_fail ".golangci.yaml" '^[[:space:]]*go:[[:space:]]*[0-9]+\.[0-9]+(\.[0-9]+)?[[:space:]]*$' "s/^([[:space:]]*go:[[:space:]]*).*/\\1${VERSION_INPUT}/" + replace_or_fail ".golangci.yaml" '^[[:space:]]*go:[[:space:]]*"?[0-9]+\.[0-9]+(\.[0-9]+)?"?[[:space:]]*$' "s/^([[:space:]]*go:[[:space:]]*).*/\\1\"${VERSION_INPUT}\"/" require_file ".mise.toml" awk -v version="$VERSION_INPUT" ' @@ -104,22 +200,28 @@ find_latest_dated_tag() { local namespace="${ns_repo%/*}" local repository="${ns_repo#*/}" - local api_url="https://hub.docker.com/v2/namespaces/${namespace}/repositories/${repository}/tags?page_size=100" + + local api_url="https://hub.docker.com/v2/namespaces/${namespace}/repositories/${repository}/tags?page_size=100&name=${VERSION_INPUT}-" local matches="" + # Ensure regex literal dots are escaped for jq's test() + local safe_version="${VERSION_INPUT//./\\.}" + while [[ -n "$api_url" && "$api_url" != "null" ]]; do local body - body="$(curl -fsSL "$api_url")" + body="$(hub_get "$api_url")" local page - page="$(printf '%s\n' "$body" | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' | sed -E 's/^"name"[[:space:]]*:[[:space:]]*"([^"]+)"$/\1/' | grep -E "^${VERSION_INPUT}-[0-9]{4}-[0-9]{2}-[0-9]{2}$" || true)" + page="$(printf '%s\n' "$body" | jq -r --arg regex "^${safe_version}-[0-9]{4}-[0-9]{2}-[0-9]{2}$" ' + .results[].name | select(. != null and test($regex)) + ')" + if [[ -n "$page" ]]; then matches+=$'\n' matches+="$page" fi - api_url="$(printf '%s\n' "$body" | tr -d '\n' | sed -nE 's/.*"next"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p')" - [[ -n "$api_url" ]] || api_url="null" + api_url="$(printf '%s\n' "$body" | jq -r '.next // empty')" done local latest @@ -132,18 +234,31 @@ resolve_digest_with_docker() { local image_ref="$1" local digest="" + local err_file + err_file="$(mktemp)" + CLEANUP_FILES+=("$err_file") + + # Enforce Docker Content Trust to mitigate image spoofing/tampering. + export DOCKER_CONTENT_TRUST="${DOCKER_CONTENT_TRUST:-1}" + local inspect_output - inspect_output="$(docker buildx imagetools inspect "$image_ref" 2>/dev/null || true)" - if [[ "$inspect_output" =~ Digest:[[:space:]]*(sha256:[a-f0-9]{64}) ]]; then - digest="${BASH_REMATCH[1]}" + if inspect_output="$(docker buildx imagetools inspect "$image_ref" 2>"$err_file")"; then + if [[ "$inspect_output" =~ Digest:[[:space:]]*(sha256:[a-f0-9]{64}) ]]; then + digest="${BASH_REMATCH[1]}" + fi + else + log "Notice: imagetools inspect failed (stderr: $(cat "$err_file")). Falling back to docker pull." fi if [[ -z "$digest" ]]; then - docker pull "$image_ref" >/dev/null 2>&1 || true - local repo_digest - repo_digest="$(docker image inspect --format '{{index .RepoDigests 0}}' "$image_ref" 2>/dev/null || true)" - if [[ "$repo_digest" == *@sha256:* ]]; then - digest="${repo_digest##*@}" + if docker pull "$image_ref" >/dev/null 2>"$err_file"; then + local repo_digest + repo_digest="$(docker image inspect --format '{{index .RepoDigests 0}}' "$image_ref" 2>/dev/null || true)" + if [[ "$repo_digest" == *@sha256:* ]]; then + digest="${repo_digest##*@}" + fi + else + log "Notice: docker pull failed (stderr: $(cat "$err_file"))." fi fi @@ -151,26 +266,48 @@ resolve_digest_with_docker() { printf '%s\n' "$digest" } -update_dockerfile_base_image() { +# Constructs and returns the fully assembled replacement string, +# preventing the need for global state variables. +resolve_base_image() { local parsed parsed="$(extract_repo_and_stage_from_dockerfile)" + local repo="${parsed%%$'\t'*}" local stage_suffix="${parsed#*$'\t'}" local tag tag="$(find_latest_dated_tag "$repo")" + local image_ref="${repo}:${tag}" log "Resolving digest for $image_ref" + local digest digest="$(resolve_digest_with_docker "$image_ref")" - local replacement="FROM ${image_ref}@${digest}${stage_suffix}" - awk -v line="$replacement" 'BEGIN { done = 0 } { if (!done && $0 ~ /^FROM[[:space:]]+/) { print line; done = 1 } else { print } } END { if (!done) { exit 1 } }' "$DOCKERFILE" > "$DOCKERFILE.tmp" && mv "$DOCKERFILE.tmp" "$DOCKERFILE" + printf 'FROM %s@%s%s\n' "$image_ref" "$digest" "$stage_suffix" +} + +update_dockerfile_base_image() { + local replacement_line="$1" + + awk -v line="$replacement_line" ' + BEGIN { done = 0 } + { + if (!done && $0 ~ /^FROM[[:space:]]+/) { + print line + done = 1 + } else { + print + } + } + END { + if (!done) { exit 1 } + }' "$DOCKERFILE" > "$DOCKERFILE.tmp" && mv "$DOCKERFILE.tmp" "$DOCKERFILE" } show_detected_go_version_refs() { log "Scanning tracked files for Go version keys" - grep -HnE '(^go [0-9]+\.[0-9]+\.[0-9]+$|^[[:space:]]*go:[[:space:]]*[0-9]+\.[0-9]+(\.[0-9]+)?[[:space:]]*$|^[[:space:]]*(go|golang)[[:space:]]*=[[:space:]]*"[^"]+"[[:space:]]*$)' go.mod go.tool.mod .golangci.yaml .mise.toml 2>/dev/null || true + grep -HnE '(^go [0-9]+\.[0-9]+\.[0-9]+$|^[[:space:]]*go:[[:space:]]*"?[0-9]+\.[0-9]+(\.[0-9]+)?"?[[:space:]]*$|^[[:space:]]*(go|golang)[[:space:]]*=[[:space:]]*"[^"]+"[[:space:]]*$)' go.mod go.tool.mod .golangci.yaml .mise.toml 2>/dev/null || true if [[ -f "mise.toml" ]]; then grep -HnE '^[[:space:]]*(go|golang)[[:space:]]*=[[:space:]]*"[^"]+"[[:space:]]*$' mise.toml 2>/dev/null || true fi @@ -178,29 +315,34 @@ show_detected_go_version_refs() { main() { validate_version + + # Ensure file availability require_file "go.mod" require_file "go.tool.mod" require_file ".golangci.yaml" require_file ".mise.toml" require_file "$DOCKERFILE" + + # Ensure tool availability require_cmd grep require_cmd sed require_cmd awk require_cmd curl require_cmd docker + require_cmd jq + require_cmd mktemp - # Preflight Docker resolution before mutating files. - local preflight - preflight="$(extract_repo_and_stage_from_dockerfile)" - local preflight_repo="${preflight%%$'\t'*}" - local preflight_tag - preflight_tag="$(find_latest_dated_tag "$preflight_repo")" - resolve_digest_with_docker "${preflight_repo}:${preflight_tag}" >/dev/null + hub_authenticate + + # Generate the final valid line directly (avoids global variables) + local dockerfile_replacement_line + dockerfile_replacement_line="$(resolve_base_image)" show_detected_go_version_refs update_known_version_files - update_dockerfile_base_image + update_dockerfile_base_image "$dockerfile_replacement_line" + log "Done. Updated Go references to $VERSION_INPUT" } -main +main "$@"