From 1b2361bc52ef913314197f08242f29880954fc91 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Mon, 15 Jun 2026 08:32:49 +0800 Subject: [PATCH 1/4] fix: bundle install scripts in release archives (#504) Release .tgz archives built by goreleaser only shipped plugin.yaml, the binary, README and LICENSE, but plugin.yaml defines install/update hooks that call install-binary.sh / install-binary.ps1. Extracting a release .tgz and running 'helm plugin install ./diff' therefore failed with 'install-binary.sh: not found'. - .goreleaser.yml: bundle install-binary.sh and install-binary.ps1 in every archive. - install-binary.sh / install-binary.ps1: skip the (now redundant) binary download when the platform binary is already staged in HELM_PLUGIN_DIR during install. Update mode always re-downloads. - release workflow: snapshot-only smoke test asserting every archive bundles plugin.yaml, both install scripts, and the binary. - README: note that extracting a release .tgz and running helm plugin install ./diff now works directly. Fixes https://github.com/databus23/helm-diff/issues/504 Signed-off-by: yxxhero --- .github/workflows/release.yaml | 21 +++++++++++++++++++++ .goreleaser.yml | 2 ++ README.md | 11 +++++++++++ install-binary.ps1 | 11 +++++++++++ install-binary.sh | 18 ++++++++++++++++++ 5 files changed, 63 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d1c8009e..75bb0a3d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -66,6 +66,27 @@ jobs: args: release --clean ${{ env.flags }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - + name: Verify archives bundle plugin files (snapshot only) + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: | + set -e + missing=0 + for f in dist/helm-diff-*.tgz; do + echo "== $f ==" + tar tzf "$f" + for member in diff/plugin.yaml diff/install-binary.sh diff/install-binary.ps1 diff/bin/diff; do + if ! tar tzf "$f" | grep -q "^${member}$"; then + echo "ERROR: ${member} missing from ${f}" + missing=1 + fi + done + done + if [ "$missing" -ne 0 ]; then + echo "Smoke test failed: required plugin files missing from one or more archives" + exit 1 + fi + echo "Smoke test passed: all archives bundle plugin.yaml, install scripts, and binary" - name: Export and upload public key if: ${{ startsWith(github.ref, 'refs/tags/v') }} diff --git a/.goreleaser.yml b/.goreleaser.yml index c0f53c1e..2c91cfd5 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -52,6 +52,8 @@ archives: - README.md - plugin.yaml - LICENSE + - install-binary.sh + - install-binary.ps1 signs: - id: plugin-provenance diff --git a/README.md b/README.md index a3241623..fb11d451 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,17 @@ helm plugin install https://github.com/databus23/helm-diff If installing this in an offline/airgapped environment, download the platform-specific binary archive (e.g., `helm-diff-linux-amd64.tgz` or `helm-diff-windows-amd64.tgz`) from [releases](https://github.com/databus23/helm-diff/releases). Make sure to select the correct `.tgz` file for your operating system and architecture. +The release archives include everything needed to install the plugin (binary, `plugin.yaml`, and the install scripts). The simplest way to install offline is to extract the archive and point `helm plugin install` at the extracted directory: + +``` +tar xzf helm-diff-linux-amd64.tgz # extracts into a ./diff directory +helm plugin install ./diff +``` + +The install script detects that the binary is already bundled and skips the GitHub download. + +Alternatively, if you keep a separate local checkout of the plugin source, you can point the installer at a downloaded `.tgz` via the `HELM_DIFF_BIN_TGZ` environment variable. + Set `HELM_DIFF_BIN_TGZ` to the absolute path to the downloaded binary archive: **POSIX shell:** diff --git a/install-binary.ps1 b/install-binary.ps1 index 90e92261..fcb752fc 100644 --- a/install-binary.ps1 +++ b/install-binary.ps1 @@ -56,6 +56,17 @@ $ErrorActionPreference = "Stop" $archiveName = "helm-diff.tgz" $arch = Get-Architecture + +# If installing (not updating) and the binary is already staged in the +# plugin dir (e.g. installing from a release archive that bundles the +# correct platform binary), skip the redundant download. Update mode +# always re-downloads. +$pluginBin = Join-Path $env:HELM_PLUGIN_DIR "bin" "diff.exe" +if (-not $Update -and (Test-Path $pluginBin -PathType Leaf)) { + Write-Host "Binary already present at $pluginBin, skipping download" + exit 0 +} + $tmpDir = New-TemporaryDirectory trap { Remove-Item -path $tmpDir -Recurse -Force } $output = Join-Path $tmpDir $archiveName diff --git a/install-binary.sh b/install-binary.sh index 3cbbc6e4..e522d2e0 100755 --- a/install-binary.sh +++ b/install-binary.sh @@ -154,6 +154,17 @@ exit_trap() { exit $result } +# alreadyInstalled returns 0 when the platform binary is already staged in +# the plugin dir. This is the case when installing from a release archive +# (which bundles the correct platform binary), so the redundant download +# can be skipped. Update mode always re-downloads. +alreadyInstalled() { + [ "$SCRIPT_MODE" = "install" ] || return 1 + bin="$HELM_PLUGIN_DIR/bin/diff" + [ "$OS" = "windows" ] && bin="$bin.exe" + [ -x "$bin" ] +} + # --- Execution --- # Stop execution on any error trap "exit_trap" EXIT @@ -162,6 +173,13 @@ set -e initArch initOS verifySupported + +if alreadyInstalled; then + echo "Binary already present at $HELM_PLUGIN_DIR/bin/diff, skipping download" + trap - EXIT + exit 0 +fi + getDownloadURL mkTempDir downloadFile From fce3408feb1d29decac2e80af85ebe6155c2d494 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Mon, 15 Jun 2026 08:47:28 +0800 Subject: [PATCH 2/4] fix(ci): accept .exe suffix in archive smoke test Windows archives bundle diff/bin/diff.exe (not diff/bin/diff). The core fix is confirmed working: all archives (incl. Windows) now bundle the install scripts and binary. Signed-off-by: yxxhero --- .github/workflows/release.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 75bb0a3d..76996bbe 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -75,12 +75,17 @@ jobs: for f in dist/helm-diff-*.tgz; do echo "== $f ==" tar tzf "$f" - for member in diff/plugin.yaml diff/install-binary.sh diff/install-binary.ps1 diff/bin/diff; do + for member in diff/plugin.yaml diff/install-binary.sh diff/install-binary.ps1; do if ! tar tzf "$f" | grep -q "^${member}$"; then echo "ERROR: ${member} missing from ${f}" missing=1 fi done + # the binary has a .exe suffix on windows archives + if ! tar tzf "$f" | grep -qE '^diff/bin/diff(\.exe)?$'; then + echo "ERROR: diff/bin/diff missing from ${f}" + missing=1 + fi done if [ "$missing" -ne 0 ]; then echo "Smoke test failed: required plugin files missing from one or more archives" From f777df0c10b7f1f37fba28edcd05cdb7d837472a Mon Sep 17 00:00:00 2001 From: yxxhero Date: Mon, 15 Jun 2026 09:37:24 +0800 Subject: [PATCH 3/4] fix(install): retry binary download with backoff The git-clone install path resolves to releases/latest/download/, which can 404 during a release window when the new release is published but its assets are not fully uploaded yet. curl -sSf under 'set -e' failed immediately with no retry. Add a 5-attempt retry loop with linear backoff (3s,6s,9s,12s) around the curl/wget download in install-binary.sh, and equivalent try/catch retry in install-binary.ps1. This absorbs the release upload window and general transient network errors. The archive-install path is unaffected (it skips the download entirely via the existing guard). Signed-off-by: yxxhero --- install-binary.ps1 | 17 ++++++++++++++++- install-binary.sh | 31 +++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/install-binary.ps1 b/install-binary.ps1 index fcb752fc..56e33874 100644 --- a/install-binary.ps1 +++ b/install-binary.ps1 @@ -40,7 +40,22 @@ function Get-Url { function Download-Plugin { param ([Parameter(Mandatory=$true)][string] $Url, [Parameter(Mandatory=$true)][string] $Output) - Invoke-WebRequest -OutFile $Output $Url + # Retry with backoff to absorb transient failures, e.g. a release window + # where the "latest" asset is already published but not fully uploaded yet. + $maxAttempts = 5 + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + try { + Invoke-WebRequest -OutFile $Output $Url + return + } catch { + if ($attempt -eq $maxAttempts) { + throw "Failed to download $Url after $maxAttempts attempts: $_" + } + $backoff = $attempt * 3 + Write-Host "Download failed (attempt $attempt/$maxAttempts), retrying in ${backoff}s..." + Start-Sleep -Seconds $backoff + } + } } function Install-Plugin { diff --git a/install-binary.sh b/install-binary.sh index e522d2e0..1e4de20f 100755 --- a/install-binary.sh +++ b/install-binary.sh @@ -123,10 +123,33 @@ downloadFile() { fi echo "Downloading $DOWNLOAD_URL" - if command -v curl >/dev/null 2>&1; then - curl -sSf -L "$DOWNLOAD_URL" >"$PLUGIN_TMP_FILE" - elif command -v wget >/dev/null 2>&1; then - wget -q -O - "$DOWNLOAD_URL" >"$PLUGIN_TMP_FILE" + # Retry with backoff to absorb transient failures, e.g. a release window + # where the "latest" asset is already published but not fully uploaded yet. + dl_attempts=5 + dl_attempt=1 + dl_ok=0 + while [ "$dl_attempt" -le "$dl_attempts" ]; do + if command -v curl >/dev/null 2>&1; then + if curl -sSfL "$DOWNLOAD_URL" >"$PLUGIN_TMP_FILE"; then + dl_ok=1 + break + fi + elif command -v wget >/dev/null 2>&1; then + if wget -q -O "$PLUGIN_TMP_FILE" "$DOWNLOAD_URL"; then + dl_ok=1 + break + fi + else + echo "Either curl or wget is required" + exit 1 + fi + echo "Download failed (attempt $dl_attempt/$dl_attempts), retrying in $((dl_attempt * 3))s..." + sleep "$((dl_attempt * 3))" + dl_attempt=$((dl_attempt + 1)) + done + if [ "$dl_ok" -ne 1 ]; then + echo "Error: failed to download $DOWNLOAD_URL after $dl_attempts attempts" + exit 1 fi } From b0644879ffd1e8aa7c5c30ff42ca2160f052ea05 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Mon, 15 Jun 2026 11:28:33 +0800 Subject: [PATCH 4/4] ci: add end-to-end archive install test for #504 Reproduces the issue #504 scenario in CI: extracts a snapshot linux archive, runs 'helm plugin install ./diff', asserts the install hook skips the network download (bundled binary already staged) and that 'helm diff version' runs. Prevents regressions in the archive-install path. Signed-off-by: yxxhero --- .github/workflows/release.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 76996bbe..1e4c39e8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -92,6 +92,34 @@ jobs: exit 1 fi echo "Smoke test passed: all archives bundle plugin.yaml, install scripts, and binary" + - + name: Set up Helm + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + uses: azure/setup-helm@v5 + with: + version: v3.18.6 + - + name: End-to-end archive install test (snapshot only) + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: | + set -e + # Reproduce issue #504: extract a release archive and install from it. + mkdir -p /tmp/archive-test + tar xzf dist/helm-diff-linux-amd64.tgz -C /tmp/archive-test + echo "Extracted archive layout:" + find /tmp/archive-test/diff -maxdepth 2 -type f | sort + + out="$(helm plugin install /tmp/archive-test/diff 2>&1)" + echo "$out" + # The install hook must find the bundled binary already staged in + # HELM_PLUGIN_DIR and skip the network download. + echo "$out" | grep -q "skipping download" || { + echo "ERROR: install hook did not skip the download." + echo "Archive install must not hit the network (binary is already bundled)." + exit 1 + } + helm diff version + echo "End-to-end archive install test passed: installed from archive without downloading" - name: Export and upload public key if: ${{ startsWith(github.ref, 'refs/tags/v') }}