Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ make update-apps APPS="tenant redis"
make show-target RELEASE_TAG=v1.3.0 # prints the resolved DOC_VERSION/BRANCH
```

Required tools: Hugo extended v0.164.0, Go 1.23+, Node 22+, yq v4+ (for version lifecycle targets).
Required tools: Hugo extended v0.164.0, Go 1.23+, Node 22+, yq v4+ (for version lifecycle targets), jq (for `hack/download_openapi.sh` in the production build and `make update-all` without `RELEASE_TAG`).

## Architecture

Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,9 @@ make show-target RELEASE_TAG=v1.3.0 # prints resolved DOC_VERSION / BRANCH
```

Required tools: Hugo extended 0.164.0, Go 1.23+, Node 22+, `yq` v4+ (for the
version lifecycle targets and Makefile routing).
version lifecycle targets and Makefile routing), `jq` (for
`hack/download_openapi.sh` in the production build and `make update-all`
without `RELEASE_TAG`).

## Where the architecture is implemented

Expand Down
2 changes: 1 addition & 1 deletion data/versions/next.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# next/ trunk track upstream cozystack/cozystack@main:
#
# talos / talos_minor ← packages/core/talos/images/talos/profiles/installer.yaml @ main
# cozystack_version / _tag ← latest final release tag (the upcoming release's
# cozystack_version / _tag ← latest published release (the upcoming release's
# own tag/assets don't exist until it is cut;
# hack/release_next.sh overrides these from
# RELEASE_TAG when next/ is promoted).
Expand Down
15 changes: 5 additions & 10 deletions data/versions/v1.6.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
# AUTOGENERATED by hack/update_versions.sh — do not edit by hand.
# Pinned upstream-tool versions for the Cozystack v1.6 docs.
#
# Regenerated by 'make update-all' so the {{< version-pin >}} values in the
# next/ trunk track upstream cozystack/cozystack@main:
#
# talos / talos_minor ← packages/core/talos/images/talos/profiles/installer.yaml @ main
# cozystack_version / _tag ← latest final release tag (the upcoming release's
# own tag/assets don't exist until it is cut;
# hack/release_next.sh overrides these from
# RELEASE_TAG when next/ is promoted).
# Snapshotted from data/versions/next.yaml by hack/release_next.sh when v1.6
# was released. 'make update-all' does not rewrite it; patch releases
# update it by hand (hack/release-checklist.md).

# Cozystack release the docs are pinned to.
cozystack_version: "1.6.0"
cozystack_tag: "v1.6.0"

# Talos version shipped by the Cozystack installer for this trunk.
# Talos version shipped by the Cozystack installer for this minor.
talos: "v1.13.6"
talos_minor: "v1.13" # the docs minor used by talos.dev URLs
21 changes: 17 additions & 4 deletions hack/release_next.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,33 @@ fi

# 5. Snapshot data/versions/next.yaml → data/versions/$DOC_VERSION.yaml so the
# {{< version-pin >}} shortcode in the released docs keeps resolving to the
# values that were true at the cut. next.yaml is unchanged; update it
# separately for the next development cycle.
# values that were true at the cut. next.yaml is unchanged. The upstream
# promote workflow (cozystack/cozystack promote-rc.yaml) regenerates it from
# the release staging branch before calling release-next, so the snapshot
# carries that release's Talos pins.
VERSIONS_DIR="data/versions"
NEXT_DATA="${VERSIONS_DIR}/next.yaml"
TARGET_DATA="${VERSIONS_DIR}/${DOC_VERSION}.yaml"
if [[ -f "$NEXT_DATA" ]]; then
if [[ -e "$TARGET_DATA" ]]; then
echo "! $TARGET_DATA already exists; leaving it as-is." >&2
else
cp "$NEXT_DATA" "$TARGET_DATA"
# Replace the trunk header (the leading comment block) and the trunk wording
# in section comments: both describe next.yaml, not a frozen snapshot.
{
echo "# Pinned upstream-tool versions for the Cozystack ${DOC_VERSION} docs."
echo "#"
echo "# Snapshotted from ${NEXT_DATA} by hack/release_next.sh when ${DOC_VERSION}"
echo "# was released. 'make update-all' does not rewrite it; patch releases"
echo "# update it by hand (hack/release-checklist.md)."
echo ""
awk 'h && /^#/ {next} h && /^$/ {h=0; next} {h=0; print}' h=1 "$NEXT_DATA" \
| sed 's|for this trunk\.|for this minor.|'
} > "$TARGET_DATA"
# Pin the release-coupled Cozystack version from RELEASE_TAG so a stale
# next.yaml can't silently freeze the wrong version into the snapshot —
# this is exactly how v1.5.yaml once inherited next.yaml's v1.3.0 values.
# Talos pins are left as snapshotted; they're refreshed manually per cycle.
# Talos pins are left as snapshotted.
VERSION_BARE="${RELEASE_TAG#v}"
sed -i.bak \
-e "s|^\(cozystack_version:[[:space:]]*\).*|\1\"${VERSION_BARE}\"|" \
Expand Down
106 changes: 106 additions & 0 deletions hack/test_version_pins.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# Offline self-check for the data/versions/*.yaml pin pipeline
# (hack/update_versions.sh, hack/release_next.sh).
# Run from the repo root: hack/test_version_pins.sh
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
fail=0
check() { # check <description> <expected> <actual>
if [[ "$2" == "$3" ]]; then
echo "ok $1"
else
echo "FAIL $1: expected '$2', got '$3'"; fail=1
fi
}

# shellcheck source=hack/update_versions.sh
source hack/update_versions.sh

# GitHub lists releases newest-created first.
check "resolver skips a draft and a prerelease" "v1.6.3" "$(latest_published_tag <<'EOF'
[
{"tag_name": "v1.6.4", "draft": true, "prerelease": false},
{"tag_name": "v1.7.0-rc.1", "draft": false, "prerelease": true},
{"tag_name": "v1.6.3", "draft": false, "prerelease": false},
{"tag_name": "v1.6.2", "draft": false, "prerelease": false}
]
EOF
)"

check "resolver picks the highest version, not the newest release" "v1.6.3" "$(latest_published_tag <<'EOF'
[
{"tag_name": "v1.5.9", "draft": false, "prerelease": false},
{"tag_name": "v1.6.3", "draft": false, "prerelease": false},
{"tag_name": "v1.5.8", "draft": false, "prerelease": false}
]
EOF
)"

check "resolver prints nothing when no release is published" "" "$(latest_published_tag <<<'[{"tag_name": "v1.6.4", "draft": true, "prerelease": false}]')"

sandbox="$(mktemp -d)"
trap 'rm -rf "$sandbox"' EXIT

# update_versions.sh must send GITHUB_TOKEN on curl's stdin, never in argv
# where any process listing shows it. A PATH stub stands in for curl.
mkdir -p "$sandbox/bin"
cat > "$sandbox/bin/curl" <<'EOF'
#!/usr/bin/env bash
echo "argv: $*" >> "$CURL_LOG"
case "$*" in
*github.com/ghapi*)
if [[ "$*" == *"--header @-"* ]]; then sed 's/^/stdin: /' >> "$CURL_LOG"; fi
echo '[{"tag_name": "v1.6.3", "draft": false, "prerelease": false}]' ;;
*) echo 'version: v1.13.6' ;;
esac
EOF
chmod +x "$sandbox/bin/curl"
run_update() {
CURL_LOG="$sandbox/curl.log" PATH="$sandbox/bin:$PATH" \
hack/update_versions.sh --dest "$sandbox/pin.yaml" >/dev/null
}
GITHUB_TOKEN=s3cr3t run_update
check "token stays out of curl argv" "0" "$(grep --count '^argv: .*s3cr3t' "$sandbox/curl.log" || true)"
check "token is sent as a header on stdin" "1" "$(grep --count '^stdin: Authorization: token s3cr3t$' "$sandbox/curl.log" || true)"
check "pin file takes the resolved release" '"v1.6.3"' "$(awk '/^cozystack_tag:/{print $2}' "$sandbox/pin.yaml")"
rm "$sandbox/curl.log"
(unset GITHUB_TOKEN GH_TOKEN; run_update)
check "no token, plain request" "argv: -fsSL https://github.com/ghapi/repos/cozystack/cozystack/releases?per_page=100" "$(grep 'github.com/ghapi' "$sandbox/curl.log")"

# Without jq the default tag cannot be resolved: the error must say so rather
# than claim upstream has no published release. An explicit tag needs no jq.
mkdir -p "$sandbox/nojq"
for tool in bash awk sed grep sort tail mkdir dirname cat; do
ln -s "$(type -P "$tool")" "$sandbox/nojq/$tool"
done
ln -s "$sandbox/bin/curl" "$sandbox/nojq/curl"
run_nojq() {
CURL_LOG="$sandbox/curl.log" PATH="$sandbox/nojq" \
hack/update_versions.sh --dest "$sandbox/pin.yaml" "$@" 2>&1
}
echo keep > "$sandbox/pin.yaml"
rc=0; out="$(run_nojq)" || rc=$?
check "no jq, no tag: exits non-zero" "1" "$rc"
check "no jq, no tag: error names jq" "1" "$(grep --count 'jq is required' <<<"$out" || true)"
check "no jq, no tag: pin file untouched" "keep" "$(cat "$sandbox/pin.yaml")"
run_nojq --cozystack-tag v1.6.2 >/dev/null
check "no jq, explicit tag: pins it" '"v1.6.2"' "$(awk '/^cozystack_tag:/{print $2}' "$sandbox/pin.yaml")"

# release_next.sh must give the snapshot a released-version header while
# copying every value line except the release-coupled cozystack pins.
mkdir -p "$sandbox/hack" "$sandbox/data/versions" "$sandbox/content/en/docs/next"
cp hugo.yaml "$sandbox/"
cp hack/release_next.sh hack/register_version.sh "$sandbox/hack/"
cp data/versions/next.yaml "$sandbox/data/versions/"
printf -- '---\ntitle: "Next"\n---\n' > "$sandbox/content/en/docs/next/_index.md"
(cd "$sandbox" && ./hack/release_next.sh --release-tag v9.9.0 >/dev/null)
snapshot="$sandbox/data/versions/v9.9.yaml"
check "snapshot header no longer mentions the trunk" "0" "$(grep --count --ignore-case 'trunk' "$snapshot" || true)"
check "snapshot header names the released version" "1" "$(grep --count '^# .*Cozystack v9.9 docs' "$snapshot" || true)"
check "snapshot pins cozystack_tag to the release" '"v9.9.0"' "$(awk '/^cozystack_tag:/{print $2}' "$snapshot")"
values() { grep --invert-match --extended-regexp '^(#|$|cozystack_version:|cozystack_tag:)' "$1"; }
check "snapshot keeps every other value line" "$(values data/versions/next.yaml)" "$(values "$snapshot")"

exit "$fail"
52 changes: 42 additions & 10 deletions hack/update_versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ Sources of truth:
* talos / talos_minor ← packages/core/talos/images/talos/profiles/installer.yaml
at --branch (always the version main/the tag ships).
* cozystack_version/_tag ← --cozystack-tag if given (e.g. an upcoming release
tag); otherwise the latest final upstream release
tag. Used as the `next` trunk's resolvable default
until the real release is cut.
tag); otherwise the highest published (non-draft,
non-prerelease) GitHub release, so every
releases/download/<tag>/ URL resolves. Used as the
`next` trunk's default until the real release is cut.

Options:
--dest PATH data/versions/<version>.yaml file to (re)generate (required)
--branch REF Git ref in cozystack/cozystack to read the Talos installer
from (default: main)
--cozystack-tag TAG Pin cozystack_tag to this vX.Y.Z tag instead of the latest
release (optional)
published release (optional)
-h, --help Show this help and exit

Examples:
Expand All @@ -30,6 +31,18 @@ Examples:
EOF
}

# Reads a GitHub releases-list JSON array on stdin and prints the highest
# published final vX.Y.Z tag. A tag exists before its release is published, so
# the tag list alone can name a version whose assets are still missing. Sorted
# by version, not creation time: a patch of an older minor can be the newest.
latest_published_tag() {
jq -r '.[] | select(.draft == false and .prerelease == false) | .tag_name' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1 || true
}

# Sourced by hack/test_version_pins.sh for latest_published_tag only.
[[ "${BASH_SOURCE[0]}" != "$0" ]] && return 0

SOURCE_REPO="cozystack/cozystack"
DEST=""
BRANCH="main"
Expand Down Expand Up @@ -73,11 +86,30 @@ talos_minor="${talos%.*}" # v1.13.0 -> v1.13

# -------------------- 2. Cozystack release tag --------------------
if [[ -z "$COZYSTACK_TAG" ]]; then
# Latest final release: top-level refs/tags/vX.Y.Z only (excludes the
# api/apps/v1alpha1/* submodule tags and any -rc/-beta pre-releases).
COZYSTACK_TAG="$(git ls-remote --tags --refs "https://github.com/${SOURCE_REPO}.git" 'v*.*.*' \
| awk -F/ '/refs\/tags\/v[0-9]+\.[0-9]+\.[0-9]+$/{print $NF}' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1 || true)"
if ! command -v jq >/dev/null; then
echo "Error: jq is required to resolve the default cozystack tag; install jq or pass --cozystack-tag." >&2
exit 1
fi
# Optional token for the higher API rate limit.
token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
RELEASES_URL="https://github.com/ghapi/repos/${SOURCE_REPO}/releases?per_page=100"
fetch_releases() {
if [[ -n "$token" ]]; then
# Header on stdin (curl >= 7.55.0): in argv the token shows in ps.
curl -fsSL --header @- "$RELEASES_URL" <<<"Authorization: token ${token}"
else
curl -fsSL "$RELEASES_URL"
fi
}
if ! releases="$(fetch_releases)"; then
echo "Error: GitHub releases API request failed: $RELEASES_URL (set GITHUB_TOKEN if rate-limited, or pass --cozystack-tag)." >&2
exit 1
fi
COZYSTACK_TAG="$(latest_published_tag <<<"$releases")"
if [[ -z "$COZYSTACK_TAG" ]]; then
echo "Error: no published vX.Y.Z release found at $RELEASES_URL." >&2
exit 1
fi
fi
if [[ ! "$COZYSTACK_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: could not determine a cozystack release tag (got '${COZYSTACK_TAG:-}')." >&2
Expand All @@ -94,7 +126,7 @@ cat > "$DEST" <<EOF
# next/ trunk track upstream cozystack/cozystack@${BRANCH}:
#
# talos / talos_minor ← ${INSTALLER_PATH} @ ${BRANCH}
# cozystack_version / _tag ← latest final release tag (the upcoming release's
# cozystack_version / _tag ← latest published release (the upcoming release's
# own tag/assets don't exist until it is cut;
# hack/release_next.sh overrides these from
# RELEASE_TAG when next/ is promoted).
Expand Down