diff --git a/.github/workflows/helm-release.yml b/.github/workflows/helm-release.yml index a483057..45e74bb 100644 --- a/.github/workflows/helm-release.yml +++ b/.github/workflows/helm-release.yml @@ -165,6 +165,39 @@ jobs: [ -n "$name" ] || continue tag="${name}-v${version}" asset="${name}-${version}.tgz" + + # git ls-remote --exit-code exits 2 for "no such tag" and non-zero + # for anything else (e.g. an unreachable remote); only 2 may be + # read as "no tag" or a network blip would silently fall through + # to hand-creating a release with no tag verification at all. + git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null -v* tags: refuse to trust it unless its + # commit is on main and its tree for this chart (plus any + # manifest this chart's own README points at) is identical to + # $target's, so the release always documents the commit it + # actually came from. + git fetch -q --no-tags origin "+refs/tags/${tag}:refs/tags/${tag}" -v* tags are never created outside this job; delete ${tag} and let this job recreate it." + exit 1 + fi + manifests="$(git show "refs/tags/${tag}:helm-charts/${name}/README.md" 2>/dev/null | grep -oE '\bmanifests/[A-Za-z0-9_.-]+\.ya?ml\b' | sort -u || true)" + if ! git diff --quiet "refs/tags/${tag}" "${target}" -- "helm-charts/${name}" ${manifests} -v* tags are never created outside this job; delete ${tag} and let this job recreate it." + exit 1 + fi + fi + if gh release view "$tag" >/dev/null 2>&1 /dev/null -v* tags: refuse to attach the release unless the tag's - # tree for this chart and manifests/ is identical to $target's, - # so the release always documents the commit it actually came - # from. - git fetch -q --no-tags origin "+refs/tags/${tag}:refs/tags/${tag}" -v* tags are never created outside this job; delete ${tag} and let this job recreate it." - exit 1 - fi + if [ "$has_tag" = "true" ]; then gh release create "$tag" "_site/${asset}" --verify-tag \ --title "${name} ${version}" --notes "${notes}" . verify_against_tag_tree() { local tag="$1" name="$2" version="$3" asset="$4" - local chart src="$work/recover-src/$tag" - chart="$(verify_tag_chart_version "$tag" "$name" "$version")" || - die "tag $tag is not on $MAIN_BRANCH, or has no $CHARTS_DIR/*/Chart.yaml named $name at version $version; refusing to recover $name $version from it" + local chart src="$work/recover-src/$tag" rc + chart="$(verify_tag_chart_version "$tag" "$name" "$version")" && rc=0 || rc=$? + if [ "$rc" = 2 ]; then + die "tag $tag is not on $MAIN_BRANCH; refusing to recover $name $version from it" + elif [ "$rc" != 0 ]; then + die "tag $tag has no $CHARTS_DIR/*/Chart.yaml named $name at version $version; refusing to recover $name $version from it" + fi mkdir -p "$src/pkg" git archive "refs/tags/$tag^{commit}" "$chart" | tar -x -C "$src" || die "could not extract $chart from tag $tag" @@ -275,24 +279,27 @@ recover_from_release() { # reports every other failure (a 5xx, a rate limit, a reset) the same way a # published release's absence is reported, which would silently drop a live # version instead of failing the build. + local tag_rc if ! gh api "repos/$REPOSITORY/releases/tags/$tag" "$listing" 2>"$errors"; then grep -qE 'HTTP 404' "$errors" || die "looking up release $tag of $REPOSITORY failed: $(tr '\n' ' ' <"$errors")" - if verify_tag_chart_version "$tag" "$name" "$version" >/dev/null; then - echo "::warning::$name $version is tagged ($tag) but has no GitHub release, so nothing was ever published for it; it stays unpublished until $MAIN_BRANCH carries that version" - else - echo "::warning::$tag is not on $MAIN_BRANCH, or its tree is not $name $version, and it has no GitHub release either; it is not a real chart release and stays unpublished" - fi + verify_tag_chart_version "$tag" "$name" "$version" >/dev/null && tag_rc=0 || tag_rc=$? + case "$tag_rc" in + 0) echo "::warning::$name $version is tagged ($tag) but has no GitHub release, so nothing was ever published for it; it stays unpublished until $MAIN_BRANCH carries that version" ;; + 2) echo "::warning::$tag is not on $MAIN_BRANCH, and it has no GitHub release either; it is not a real chart release and stays unpublished" ;; + *) echo "::warning::$tag's tree is not $name $version, and it has no GitHub release either; it is not a real chart release and stays unpublished" ;; + esac return 1 fi local recorded actual chart_yaml legacy recorded="$(jq -r --arg a "$asset" '.assets[] | select(.name == $a) | .digest // "none"' "$listing")" if [ -z "$recorded" ]; then - if verify_tag_chart_version "$tag" "$name" "$version" >/dev/null; then - echo "::warning::release $tag has no asset $asset, so $name $version cannot be restored from it; it stays unpublished until $MAIN_BRANCH carries that version" - else - echo "::warning::$tag is not on $MAIN_BRANCH, or its tree is not $name $version, and its release has no $asset either; it is not a real chart release and stays unpublished" - fi + verify_tag_chart_version "$tag" "$name" "$version" >/dev/null && tag_rc=0 || tag_rc=$? + case "$tag_rc" in + 0) echo "::warning::release $tag has no asset $asset, so $name $version cannot be restored from it; it stays unpublished until $MAIN_BRANCH carries that version" ;; + 2) echo "::warning::$tag is not on $MAIN_BRANCH, and its release has no $asset either; it is not a real chart release and stays unpublished" ;; + *) echo "::warning::$tag's tree is not $name $version, and its release has no $asset either; it is not a real chart release and stays unpublished" ;; + esac return 1 fi gh release download "$tag" -R "$REPOSITORY" -p "$asset" -D "$dir"