From 336b44cd499813d7ca43915a91f2f7ddf5593cd5 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Wed, 8 Jul 2026 13:03:58 +0300 Subject: [PATCH 1/5] feat(cli): support vX and vX.Y selectors in install scripts Accept floating major (vX) and minor (vX.Y) version selectors in the Linux/macOS and Windows installers, resolving them to the newest matching exact vX.Y.Z release via the GitHub releases API (mirroring resolve_opentaint_version.py). Only exact vX.Y.Z tags are considered, so floating and prefixed tags are ignored. Resolution is skipped when the download base URL is already pinned (CI path), and a GitHub token is used when available to avoid API rate limits. --- docs/installation.md | 26 +++++--- scripts/install/install.cmd | 4 +- scripts/install/install.ps1 | 65 ++++++++++++++++++-- scripts/install/install.sh | 116 +++++++++++++++++++++++++++++++++--- 4 files changed, 192 insertions(+), 19 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index d8d6b2d45..ced5f34d0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -33,15 +33,23 @@ The correct binary for your platform (Linux, macOS, or Windows; x64 or arm64) is ## Install Scripts -The install scripts accept an optional version argument. Without one, the latest GitHub release is installed. +The install scripts accept an optional version argument (leading `v` optional). Without one, the latest GitHub release is installed. You can pin to: + +- an exact version — `v1.2.3` +- the newest patch of a minor line — `v1.2` (resolves to the newest `v1.2.x`) +- the newest release of a major line — `v1` (resolves to the newest `v1.x.y`) + +The leading `v` is optional in every form (`1.2.3` works the same as `v1.2.3`). **Linux/macOS:** ```bash # Latest curl -fsSL https://opentaint.org/install.sh | bash -# Specific version (leading 'v' is optional) -curl -fsSL https://opentaint.org/install.sh | bash -s -- 1.2.3 +# Exact version, minor line, or major line +curl -fsSL https://opentaint.org/install.sh | bash -s -- v1.2.3 +curl -fsSL https://opentaint.org/install.sh | bash -s -- v1.2 +curl -fsSL https://opentaint.org/install.sh | bash -s -- v1 ``` **Windows (PowerShell):** @@ -49,8 +57,10 @@ curl -fsSL https://opentaint.org/install.sh | bash -s -- 1.2.3 # Latest irm https://opentaint.org/install.ps1 | iex -# Specific version -& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version 1.2.3 +# Exact version, minor line, or major line +& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v1.2.3 +& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v1.2 +& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v1 ``` **Windows (CMD):** @@ -58,8 +68,10 @@ irm https://opentaint.org/install.ps1 | iex :: Latest curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd && del install.cmd -:: Specific version -curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd 1.2.3 && del install.cmd +:: Exact version, minor line, or major line +curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v1.2.3 && del install.cmd +curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v1.2 && del install.cmd +curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v1 && del install.cmd ``` ### Environment variables diff --git a/scripts/install/install.cmd b/scripts/install/install.cmd index 530585660..1a05a975d 100644 --- a/scripts/install/install.cmd +++ b/scripts/install/install.cmd @@ -2,7 +2,9 @@ REM OpenTaint installer for Windows (CMD wrapper) REM This script invokes the PowerShell installer. REM Usage: install.cmd installs latest -REM install.cmd 1.2.3 installs version 1.2.3 +REM install.cmd v1.2.3 installs exact version ('v' optional) +REM install.cmd v0 installs newest v0.x.y +REM install.cmd v0.2 installs newest v0.2.x where powershell >nul 2>nul if %ERRORLEVEL% equ 0 ( diff --git a/scripts/install/install.ps1 b/scripts/install/install.ps1 index 811b6ec38..a7b8f8c26 100644 --- a/scripts/install/install.ps1 +++ b/scripts/install/install.ps1 @@ -1,7 +1,9 @@ # OpenTaint installer for Windows (PowerShell) # Usage: # irm https://raw.githubusercontent.com/seqra/opentaint/main/scripts/install/install.ps1 | iex -# & ([scriptblock]::Create((irm https://raw.githubusercontent.com/seqra/opentaint/main/scripts/install/install.ps1))) -Version 1.2.3 +# & ([scriptblock]::Create((irm .../install.ps1))) -Version v1.2.3 # exact version ('v' optional) +# & ([scriptblock]::Create((irm .../install.ps1))) -Version v0 # newest v0.x.y +# & ([scriptblock]::Create((irm .../install.ps1))) -Version v0.2 # newest v0.2.x param( [string]$Version = "latest" @@ -9,6 +11,49 @@ param( $ErrorActionPreference = 'Stop' +$Repo = if ($env:OPENTAINT_REPOSITORY) { $env:OPENTAINT_REPOSITORY } else { "seqra/opentaint" } + +# Resolves a floating major/minor selector (e.g. v0 or v0.2) to the newest +# matching exact vX.Y.Z release tag via the GitHub releases API. Only exact +# vX.Y.Z tags are considered, mirroring scripts/resolve_opentaint_version.py. +function Resolve-FloatingSelector { + param( + [string]$Selector # e.g. v0 or v0.2 + ) + + $headers = @{ "Accept" = "application/vnd.github+json" } + $token = if ($env:OPENTAINT_GITHUB_TOKEN) { $env:OPENTAINT_GITHUB_TOKEN } elseif ($env:GITHUB_TOKEN) { $env:GITHUB_TOKEN } else { $null } + if ($token) { $headers["Authorization"] = "Bearer $token" } + + $apiUrl = "https://api.github.com/repos/$Repo/releases?per_page=100" + try { + $releases = Invoke-RestMethod -Uri $apiUrl -Headers $headers -UseBasicParsing + } catch { + [Console]::Error.WriteLine("Error: failed to query the GitHub releases API to resolve '$Selector'.") + exit 2 + } + + # v0 must match v0.*, not v0.* only after a dot boundary; the trailing dot + # ensures v1 does not match v10.x and v0.1 does not match v0.10.x. + $prefix = "$Selector." + $best = $null + foreach ($release in $releases) { + $tag = $release.tag_name + if ($tag -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+$') { continue } + if (-not $tag.StartsWith($prefix)) { continue } + $ver = [version]($tag.Substring(1)) + if (-not $best -or $ver -gt $best.Ver) { + $best = @{ Ver = $ver; Tag = $tag } + } + } + + if (-not $best) { + [Console]::Error.WriteLine("Error: no release found matching selector '$Selector'.") + exit 2 + } + return $best.Tag +} + function Test-Version { param([string]$Raw) @@ -21,7 +66,21 @@ function Test-Version { return @{ PathSegment = "download/v$normalized"; Tag = "v$normalized" } } - [Console]::Error.WriteLine("Error: Invalid version '$Raw'. Expected 'latest' or 'X.Y.Z' (optionally prefixed with 'v').") + if ($Raw -match '^(v)?(?[0-9]+\.[0-9]+)$') { + $selector = "v$($Matches['ver'])" + Write-Host "Resolving $selector to an exact release..." + $tag = Resolve-FloatingSelector -Selector $selector + return @{ PathSegment = "download/$tag"; Tag = $tag } + } + + if ($Raw -match '^(v)?(?[0-9]+)$') { + $selector = "v$($Matches['ver'])" + Write-Host "Resolving $selector to an exact release..." + $tag = Resolve-FloatingSelector -Selector $selector + return @{ PathSegment = "download/$tag"; Tag = $tag } + } + + [Console]::Error.WriteLine("Error: Invalid version '$Raw'. Expected 'latest', 'X', 'X.Y', or 'X.Y.Z' (optionally prefixed with 'v').") exit 2 } @@ -45,8 +104,6 @@ function Test-HomebrewInstall { return $null } -$Repo = if ($env:OPENTAINT_REPOSITORY) { $env:OPENTAINT_REPOSITORY } else { "seqra/opentaint" } - function Get-Architecture { $arch = $env:PROCESSOR_ARCHITECTURE switch ($arch) { diff --git a/scripts/install/install.sh b/scripts/install/install.sh index d6ccf386d..a3783dc78 100755 --- a/scripts/install/install.sh +++ b/scripts/install/install.sh @@ -4,26 +4,35 @@ set -euo pipefail # OpenTaint installer for Linux and macOS # Usage: # curl -fsSL https://raw.githubusercontent.com/seqra/opentaint/main/scripts/install/install.sh | bash -# curl -fsSL https://raw.githubusercontent.com/seqra/opentaint/main/scripts/install/install.sh | bash -s -- 1.2.3 +# curl -fsSL .../install.sh | bash -s -- v1.2.3 # exact version ('v' optional) +# curl -fsSL .../install.sh | bash -s -- v0 # newest v0.x.y +# curl -fsSL .../install.sh | bash -s -- v0.2 # newest v0.2.x REPO="${OPENTAINT_REPOSITORY:-seqra/opentaint}" INSTALL_DIR="${OPENTAINT_INSTALL_DIR:-}" DOWNLOADER="" +# Populated by validate_version(): one of latest|exact|major|minor. +VERSION_SELECTOR_KIND="" +# Floating selector (e.g. v0 or v0.1) when kind is major|minor; resolved later. +VERSION_SELECTOR="" -# Populates VERSION_PATH_SEGMENT and VERSION_TAG from the raw version argument. +# Populates VERSION_SELECTOR_KIND, VERSION_SELECTOR, VERSION_PATH_SEGMENT and +# VERSION_TAG from the raw version argument. # Accepts: # (empty) -> latest # latest -# X.Y.Z -# vX.Y.Z -# X.Y.Z-suffix -# vX.Y.Z-suffix +# X / vX (floating major, resolved to newest vX.Y.Z) +# X.Y / vX.Y (floating minor, resolved to newest vX.Y.Z) +# X.Y.Z / vX.Y.Z (exact, optionally with -suffix) +# For floating selectors VERSION_PATH_SEGMENT is left empty and filled in later +# by resolve_floating_selector() once a downloader is available. # Exits 2 on invalid input. validate_version() { local raw="${1:-latest}" if [ "$raw" = "latest" ] || [ -z "$raw" ]; then + VERSION_SELECTOR_KIND="latest" VERSION_PATH_SEGMENT="latest/download" VERSION_TAG="latest" return @@ -31,16 +40,98 @@ validate_version() { if [[ "$raw" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9._-]+)?$ ]]; then local normalized="${raw#v}" + VERSION_SELECTOR_KIND="exact" VERSION_PATH_SEGMENT="download/v${normalized}" VERSION_TAG="v${normalized}" return fi + if [[ "$raw" =~ ^v?[0-9]+\.[0-9]+$ ]]; then + VERSION_SELECTOR_KIND="minor" + VERSION_SELECTOR="v${raw#v}" + VERSION_TAG="$VERSION_SELECTOR" + return + fi + + if [[ "$raw" =~ ^v?[0-9]+$ ]]; then + VERSION_SELECTOR_KIND="major" + VERSION_SELECTOR="v${raw#v}" + VERSION_TAG="$VERSION_SELECTOR" + return + fi + echo "Error: invalid version '$raw'." >&2 - echo "Expected 'latest' or 'X.Y.Z' (optionally prefixed with 'v')." >&2 + echo "Expected 'latest', 'X', 'X.Y', or 'X.Y.Z' (optionally prefixed with 'v')." >&2 exit 2 } +# Fetches a URL to stdout using the selected downloader. Adds a GitHub token +# header when OPENTAINT_GITHUB_TOKEN or GITHUB_TOKEN is set (avoids API rate +# limits). Returns non-zero on failure. +fetch_stdout() { + local url="$1" + local token="${OPENTAINT_GITHUB_TOKEN:-${GITHUB_TOKEN:-}}" + + case "$DOWNLOADER" in + curl) + if [ -n "$token" ]; then + curl -fsSL -H "Authorization: Bearer $token" "$url" + else + curl -fsSL "$url" + fi + ;; + wget) + if [ -n "$token" ]; then + wget -qO- --header="Authorization: Bearer $token" "$url" + else + wget -qO- "$url" + fi + ;; + *) + return 1 + ;; + esac +} + +# Resolves a floating major/minor selector (e.g. v0 or v0.1) to the newest +# matching exact vX.Y.Z release tag by querying the GitHub releases API. Only +# exact vX.Y.Z tags are considered (floating and prefixed tags are ignored), +# mirroring scripts/resolve_opentaint_version.py. Prints the resolved tag. +resolve_floating_selector() { + local selector="$1" + local api_url="https://api.github.com/repos/${REPO}/releases?per_page=100" + + local body + if ! body="$(fetch_stdout "$api_url")"; then + echo "Error: failed to query the GitHub releases API to resolve '$selector'." >&2 + exit 2 + fi + + # Match e.g. ^v0\. (major) or ^v0\.1\. (minor) against vX.Y.Z tags. + local pattern + if [ "$VERSION_SELECTOR_KIND" = "minor" ]; then + pattern="^${selector//./\\.}\\." + else + pattern="^${selector}\\." + fi + + local best + best="$(printf '%s' "$body" \ + | grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"v[0-9]+\.[0-9]+\.[0-9]+"' \ + | sed -E 's/.*"(v[0-9]+\.[0-9]+\.[0-9]+)"$/\1/' \ + | grep -E "$pattern" \ + | sed 's/^v//' \ + | sort -t. -k1,1n -k2,2n -k3,3n \ + | tail -1)" + + if [ -z "$best" ]; then + echo "Error: no release found matching selector '$selector'." >&2 + exit 2 + fi + + echo "v${best}" +} + # Prints the resolved path of an existing opentaint binary if it appears to # belong to a Homebrew installation (mirrors cli/internal/utils/updater.go # classification). Prints nothing otherwise. @@ -206,6 +297,17 @@ main() { exit 3 fi + # Floating major/minor selectors are resolved to an exact tag, unless the + # caller already pinned a fully-resolved download base URL (e.g. CI). + if [ -z "${OPENTAINT_DOWNLOAD_BASE_URL:-}" ] && \ + { [ "$VERSION_SELECTOR_KIND" = "major" ] || [ "$VERSION_SELECTOR_KIND" = "minor" ]; }; then + echo "Resolving ${VERSION_SELECTOR} to an exact release..." + local resolved_tag + resolved_tag="$(resolve_floating_selector "$VERSION_SELECTOR")" + VERSION_PATH_SEGMENT="download/${resolved_tag}" + VERSION_TAG="$resolved_tag" + fi + DOWNLOAD_BASE_URL="${OPENTAINT_DOWNLOAD_BASE_URL:-https://github.com/${REPO}/releases/${VERSION_PATH_SEGMENT}}" echo "Version: $VERSION_TAG" From 7cb03e8a77bea96c0d3c41e0927b5199c06d2088 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Wed, 8 Jul 2026 13:03:58 +0300 Subject: [PATCH 2/5] fix(ci): stop publishing floating pre-releases, keep floating tags update-floating-tags no longer creates GitHub release objects for the latest/vX.Y floating pointers; it only pushes the floating git tags (latest, vX, vX.Y), which are what `uses:`/GitLab includes resolve. No consumer read those release objects: the CI resolver, CLI build, and install script all resolve to exact vX.Y.Z. Also drop the now-unused --prerelease flag from the internal analyzer/autobuilder/go-server */latest releases (still consumed by ci-rules) and remove the dead action inputs from all callers. --- .../actions/update-floating-tags/action.yml | 47 +------------------ .github/workflows/publish-analyzer.yaml | 1 - .github/workflows/publish-autobuilder.yaml | 1 - .github/workflows/publish-go-server.yaml | 1 - .github/workflows/release-cli.yaml | 2 - .github/workflows/release-github.yaml | 1 - .github/workflows/release-gitlab.yaml | 1 - .github/workflows/release-rules.yaml | 2 - 8 files changed, 1 insertion(+), 55 deletions(-) diff --git a/.github/actions/update-floating-tags/action.yml b/.github/actions/update-floating-tags/action.yml index 21ad9229d..a7d496626 100644 --- a/.github/actions/update-floating-tags/action.yml +++ b/.github/actions/update-floating-tags/action.yml @@ -1,5 +1,5 @@ name: 'Update floating tags' -description: 'Push floating major/minor/latest git tags and recreate corresponding GitHub releases' +description: 'Push floating major/minor/latest git tags for a released version' inputs: release-version: @@ -9,17 +9,6 @@ inputs: description: 'Git tag prefix (e.g. "gitlab/", "github/", "rules/", or empty for CLI)' required: true default: '' - component-name: - description: 'Human-readable component name for release notes (e.g. "GitLab CI template")' - required: true - release-assets: - description: 'Files to attach to floating releases (space-separated paths)' - required: false - default: '' - copy-assets-from: - description: 'Tag to copy assets from (e.g. v0.1.2). Mutually exclusive with release-assets.' - required: false - default: '' runs: using: 'composite' @@ -31,7 +20,6 @@ runs: VERSION="$(echo "${{ inputs.release-version }}" | sed 's/^v//')" PREFIX="${{ inputs.tag-prefix }}" - COMPONENT="${{ inputs.component-name }}" MAJOR="$(echo "$VERSION" | cut -d. -f1)" MAJOR_TAG="${PREFIX}v${MAJOR}" MINOR_TAG="${PREFIX}v$(echo "$VERSION" | cut -d. -f1-2)" @@ -46,36 +34,3 @@ runs: git push origin "$MINOR_TAG" --force git tag -f "$LATEST_TAG" git push origin "$LATEST_TAG" --force - - ASSET_ARGS=() - ASSETS_DIR="" - if [ -n "${{ inputs.copy-assets-from }}" ]; then - ASSETS_DIR=$(mktemp -d) - gh release download "${{ inputs.copy-assets-from }}" --dir "$ASSETS_DIR" - for f in "$ASSETS_DIR"/*; do - [ -f "$f" ] || continue - ASSET_ARGS+=("$f") - done - elif [ -n "${{ inputs.release-assets }}" ]; then - read -ra ASSET_ARGS <<< "${{ inputs.release-assets }}" - fi - - gh release delete "$LATEST_TAG" --yes || true - gh release create "$LATEST_TAG" \ - --title "$LATEST_TAG" \ - --notes "Floating release tracking the latest ${COMPONENT} version (${PREFIX}v${VERSION})" \ - --latest=false \ - --prerelease \ - "${ASSET_ARGS[@]+"${ASSET_ARGS[@]}"}" - - gh release delete "$MINOR_TAG" --yes || true - gh release create "$MINOR_TAG" \ - --title "$MINOR_TAG" \ - --notes "Floating release tracking the latest ${COMPONENT} ${MINOR_TAG#${PREFIX}}.x version (${PREFIX}v${VERSION})" \ - --latest=false \ - --prerelease \ - "${ASSET_ARGS[@]+"${ASSET_ARGS[@]}"}" - - if [ -n "$ASSETS_DIR" ]; then - rm -rf "$ASSETS_DIR" - fi diff --git a/.github/workflows/publish-analyzer.yaml b/.github/workflows/publish-analyzer.yaml index a3ac7821d..d7f970912 100644 --- a/.github/workflows/publish-analyzer.yaml +++ b/.github/workflows/publish-analyzer.yaml @@ -101,6 +101,5 @@ jobs: --title "analyzer/latest" \ --notes "Floating release tracking the latest analyzer version (v${{ steps.changes.outputs.version }})" \ --latest=false \ - --prerelease \ core/build/libs/opentaint-project-analyzer.jar \ opentaint-models.tar.gz diff --git a/.github/workflows/publish-autobuilder.yaml b/.github/workflows/publish-autobuilder.yaml index 74d5f1216..d915eab7a 100644 --- a/.github/workflows/publish-autobuilder.yaml +++ b/.github/workflows/publish-autobuilder.yaml @@ -95,5 +95,4 @@ jobs: --title "autobuilder/latest" \ --notes "Floating release tracking the latest autobuilder version (v${{ steps.changes.outputs.version }})" \ --latest=false \ - --prerelease \ core/opentaint-jvm-autobuilder/build/libs/opentaint-project-auto-builder.jar diff --git a/.github/workflows/publish-go-server.yaml b/.github/workflows/publish-go-server.yaml index ab07e8723..b5d4928a0 100644 --- a/.github/workflows/publish-go-server.yaml +++ b/.github/workflows/publish-go-server.yaml @@ -124,5 +124,4 @@ jobs: --title "go-server/latest" \ --notes "Floating release tracking the latest go-ssa-server version (v${{ steps.changes.outputs.version }})" \ --latest=false \ - --prerelease \ dist/* diff --git a/.github/workflows/release-cli.yaml b/.github/workflows/release-cli.yaml index 9ad1613b0..b185e6e5f 100644 --- a/.github/workflows/release-cli.yaml +++ b/.github/workflows/release-cli.yaml @@ -347,8 +347,6 @@ jobs: with: release-version: ${{ steps.release_version.outputs.RELEASE_VERSION }} tag-prefix: '' - component-name: 'CLI' - copy-assets-from: v${{ steps.release_version.outputs.RELEASE_VERSION }} outputs: release_version: ${{ steps.release_version.outputs.RELEASE_VERSION }} diff --git a/.github/workflows/release-github.yaml b/.github/workflows/release-github.yaml index b4e4d871a..2fd28c062 100644 --- a/.github/workflows/release-github.yaml +++ b/.github/workflows/release-github.yaml @@ -112,7 +112,6 @@ jobs: with: release-version: ${{ steps.manual_release.outputs.new-version || steps.version.outputs.new_release_version }} tag-prefix: 'github/' - component-name: 'GitHub Action' outputs: release_version: ${{ steps.manual_release.outputs.new-version || steps.version.outputs.new_release_version }} diff --git a/.github/workflows/release-gitlab.yaml b/.github/workflows/release-gitlab.yaml index 1899c8ffa..58e6c5285 100644 --- a/.github/workflows/release-gitlab.yaml +++ b/.github/workflows/release-gitlab.yaml @@ -129,7 +129,6 @@ jobs: with: release-version: ${{ steps.manual_release.outputs.new-version || steps.release_version.outputs.RELEASE_VERSION }} tag-prefix: 'gitlab/' - component-name: 'GitLab CI template' outputs: release_version: ${{ steps.manual_release.outputs.new-version || steps.release_version.outputs.RELEASE_VERSION }} diff --git a/.github/workflows/release-rules.yaml b/.github/workflows/release-rules.yaml index 96d6897ff..2d95064d1 100644 --- a/.github/workflows/release-rules.yaml +++ b/.github/workflows/release-rules.yaml @@ -134,8 +134,6 @@ jobs: with: release-version: ${{ steps.manual_release.outputs.new-version || steps.release_version.outputs.RELEASE_VERSION }} tag-prefix: 'rules/' - component-name: 'rules' - release-assets: opentaint-rules.tar.gz outputs: release_version: ${{ steps.manual_release.outputs.new-version || steps.release_version.outputs.RELEASE_VERSION }} From 45bf781cdce069370ec57a7acbb6fcb7119bd3cd Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Sat, 1 Aug 2026 20:56:39 +0300 Subject: [PATCH 3/5] fix(install): paginate partial version resolution --- docs/installation.md | 26 ++++++++--------- github/README.md | 29 +++++++++++++------ gitlab/README.md | 25 ++++++++-------- scripts/install/install.Tests.ps1 | 44 +++++++++++++++++++++++++++++ scripts/install/install.cmd | 4 +-- scripts/install/install.ps1 | 24 ++++++++++------ scripts/install/install.sh | 31 +++++++++++++------- scripts/install/install_test.sh | 47 +++++++++++++++++++++++++++++++ 8 files changed, 175 insertions(+), 55 deletions(-) create mode 100644 scripts/install/install.Tests.ps1 create mode 100644 scripts/install/install_test.sh diff --git a/docs/installation.md b/docs/installation.md index ced5f34d0..c1e400cc8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -35,11 +35,11 @@ The correct binary for your platform (Linux, macOS, or Windows; x64 or arm64) is The install scripts accept an optional version argument (leading `v` optional). Without one, the latest GitHub release is installed. You can pin to: -- an exact version — `v1.2.3` -- the newest patch of a minor line — `v1.2` (resolves to the newest `v1.2.x`) -- the newest release of a major line — `v1` (resolves to the newest `v1.x.y`) +- an exact version — `v0.4.5` +- the newest patch of a minor line — `v0.4` (resolves to the newest `v0.4.x`) +- the newest release of a major line — `v0` (resolves to the newest `v0.x.y`) -The leading `v` is optional in every form (`1.2.3` works the same as `v1.2.3`). +The leading `v` is optional in every form (`0.4.5` works the same as `v0.4.5`). **Linux/macOS:** ```bash @@ -47,9 +47,9 @@ The leading `v` is optional in every form (`1.2.3` works the same as `v1.2.3`). curl -fsSL https://opentaint.org/install.sh | bash # Exact version, minor line, or major line -curl -fsSL https://opentaint.org/install.sh | bash -s -- v1.2.3 -curl -fsSL https://opentaint.org/install.sh | bash -s -- v1.2 -curl -fsSL https://opentaint.org/install.sh | bash -s -- v1 +curl -fsSL https://opentaint.org/install.sh | bash -s -- v0.4.5 +curl -fsSL https://opentaint.org/install.sh | bash -s -- v0.4 +curl -fsSL https://opentaint.org/install.sh | bash -s -- v0 ``` **Windows (PowerShell):** @@ -58,9 +58,9 @@ curl -fsSL https://opentaint.org/install.sh | bash -s -- v1 irm https://opentaint.org/install.ps1 | iex # Exact version, minor line, or major line -& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v1.2.3 -& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v1.2 -& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v1 +& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v0.4.5 +& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v0.4 +& ([scriptblock]::Create((irm https://opentaint.org/install.ps1))) -Version v0 ``` **Windows (CMD):** @@ -69,9 +69,9 @@ irm https://opentaint.org/install.ps1 | iex curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd && del install.cmd :: Exact version, minor line, or major line -curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v1.2.3 && del install.cmd -curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v1.2 && del install.cmd -curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v1 && del install.cmd +curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v0.4.5 && del install.cmd +curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v0.4 && del install.cmd +curl -fsSL https://opentaint.org/install.cmd -o install.cmd && install.cmd v0 && del install.cmd ``` ### Environment variables diff --git a/github/README.md b/github/README.md index 49e9ae80b..34ceb91cb 100644 --- a/github/README.md +++ b/github/README.md @@ -149,23 +149,36 @@ After the job completes, you’ll find: ## Version Selection +There are two independent version selectors. + +### GitHub Action version + +The tag after `@` selects the integration code. Major and minor tags move to the newest compatible release; exact tags remain pinned: + +* `github/v0` — latest action in major version 0 +* `github/v0.4` — latest action in minor version 0.4 +* `github/v0.4.5` — exact action version 0.4.5 + +```yaml +uses: seqra/opentaint/github@github/v0.4 +``` + +### OpenTaint CLI version + `opentaint-version` supports flexible selectors so you do not need to update this action for every OpenTaint release: * `latest` - always use the latest stable release * `v0` - use the latest stable release in major version 0 (default) -* `v0.1` - use the latest stable patch in minor version 0.1 -* `v0.1.0` - pin an exact release +* `v0.4` - use the latest stable patch in minor version 0.4 +* `v0.4.5` - pin an exact release Examples: ```yaml with: - opentaint-version: 'latest' -``` - -```yaml -with: - opentaint-version: 'v0.1' + opentaint-version: 'v0' # latest v0.x.y + # opentaint-version: 'v0.4' # latest v0.4.x + # opentaint-version: 'v0.4.5' ``` diff --git a/gitlab/README.md b/gitlab/README.md index f7ce96d50..d734cb9d7 100644 --- a/gitlab/README.md +++ b/gitlab/README.md @@ -98,16 +98,16 @@ There are two independent version selectors: ### CI template version -Controlled by the tag in the `include:` URL. Using a major-version tag ensures you always get the latest compatible template without manual updates. +Controlled by the tag in the `include:` URL. Major and minor tags move to the newest compatible template release; exact tags remain pinned. -* `gitlab/v0` — latest stable template in major version 0 (recommended) -* `gitlab/v0.1` — pin to a specific minor version -* `gitlab/v0.1.0` — pin to an exact version -* `gitlab/latest` — always use the latest template +* `gitlab/v0` — latest template in major version 0 +* `gitlab/v0.4` — latest template in minor version 0.4 +* `gitlab/v0.4.5` — exact template version 0.4.5 +* `gitlab/latest` — latest template release ```yaml include: - - remote: https://raw.githubusercontent.com/seqra/opentaint/gitlab/v0.1/gitlab/opentaint.gitlab-ci.yml + - remote: https://raw.githubusercontent.com/seqra/opentaint/gitlab/v0.4/gitlab/opentaint.gitlab-ci.yml ``` ### OpenTaint CLI version @@ -116,17 +116,14 @@ Controlled by the `OPENTAINT_VERSION` variable. This determines which release of * `latest` — always use the latest stable release * `v0` — use the latest stable release in major version 0 (default) -* `v0.1` — use the latest stable patch in minor version 0.1 -* `v0.1.0` — pin an exact release +* `v0.4` — use the latest stable patch in minor version 0.4 +* `v0.4.5` — pin an exact release ```yaml variables: - OPENTAINT_VERSION: "latest" -``` - -```yaml -variables: - OPENTAINT_VERSION: "v0.1" + OPENTAINT_VERSION: "v0" # latest v0.x.y + # OPENTAINT_VERSION: "v0.4" # latest v0.4.x + # OPENTAINT_VERSION: "v0.4.5" ``` diff --git a/scripts/install/install.Tests.ps1 b/scripts/install/install.Tests.ps1 new file mode 100644 index 000000000..2183f8714 --- /dev/null +++ b/scripts/install/install.Tests.ps1 @@ -0,0 +1,44 @@ +BeforeAll { + $installer = Get-Content (Join-Path $PSScriptRoot "install.ps1") -Raw + $installer = $installer -replace '(?m)^Main\s*$', '' + Invoke-Expression $installer +} + +Describe "Resolve-FloatingSelector pagination" { + BeforeEach { + Mock Invoke-RestMethod { + param($Uri, $Headers, [switch]$UseBasicParsing) + + if ($Uri -match 'page=1$') { + return 1..100 | ForEach-Object { + [pscustomobject]@{ tag_name = "analyzer/2026.01.$_.abcdef0" } + } + } + if ($Uri -match 'page=2$') { + return @( + [pscustomobject]@{ tag_name = "v0.4.5" }, + [pscustomobject]@{ tag_name = "v0.5.1" } + ) + } + throw "unexpected page: $Uri" + } + } + + It "resolves a major selector from a later page" { + Resolve-FloatingSelector -Selector "v0" | Should -Be "v0.5.1" + Should -Invoke Invoke-RestMethod -Times 2 + } + + It "resolves a minor selector from a later page" { + Resolve-FloatingSelector -Selector "v0.4" | Should -Be "v0.4.5" + Should -Invoke Invoke-RestMethod -Times 2 + } +} + +Describe "Test-Version prerelease support" { + It "keeps exact prerelease installation supported" { + $result = Test-Version -Raw "v0.4.5-rc.1" + $result.Tag | Should -Be "v0.4.5-rc.1" + $result.PathSegment | Should -Be "download/v0.4.5-rc.1" + } +} diff --git a/scripts/install/install.cmd b/scripts/install/install.cmd index 1a05a975d..129b4cf77 100644 --- a/scripts/install/install.cmd +++ b/scripts/install/install.cmd @@ -2,9 +2,9 @@ REM OpenTaint installer for Windows (CMD wrapper) REM This script invokes the PowerShell installer. REM Usage: install.cmd installs latest -REM install.cmd v1.2.3 installs exact version ('v' optional) +REM install.cmd v0.4.5 installs exact version ('v' optional) REM install.cmd v0 installs newest v0.x.y -REM install.cmd v0.2 installs newest v0.2.x +REM install.cmd v0.4 installs newest v0.4.x where powershell >nul 2>nul if %ERRORLEVEL% equ 0 ( diff --git a/scripts/install/install.ps1 b/scripts/install/install.ps1 index a7b8f8c26..1862af636 100644 --- a/scripts/install/install.ps1 +++ b/scripts/install/install.ps1 @@ -1,9 +1,9 @@ # OpenTaint installer for Windows (PowerShell) # Usage: # irm https://raw.githubusercontent.com/seqra/opentaint/main/scripts/install/install.ps1 | iex -# & ([scriptblock]::Create((irm .../install.ps1))) -Version v1.2.3 # exact version ('v' optional) +# & ([scriptblock]::Create((irm .../install.ps1))) -Version v0.4.5 # exact version ('v' optional) # & ([scriptblock]::Create((irm .../install.ps1))) -Version v0 # newest v0.x.y -# & ([scriptblock]::Create((irm .../install.ps1))) -Version v0.2 # newest v0.2.x +# & ([scriptblock]::Create((irm .../install.ps1))) -Version v0.4 # newest v0.4.x param( [string]$Version = "latest" @@ -25,12 +25,20 @@ function Resolve-FloatingSelector { $token = if ($env:OPENTAINT_GITHUB_TOKEN) { $env:OPENTAINT_GITHUB_TOKEN } elseif ($env:GITHUB_TOKEN) { $env:GITHUB_TOKEN } else { $null } if ($token) { $headers["Authorization"] = "Bearer $token" } - $apiUrl = "https://api.github.com/repos/$Repo/releases?per_page=100" - try { - $releases = Invoke-RestMethod -Uri $apiUrl -Headers $headers -UseBasicParsing - } catch { - [Console]::Error.WriteLine("Error: failed to query the GitHub releases API to resolve '$Selector'.") - exit 2 + $releases = @() + $page = 1 + while ($true) { + $apiUrl = "https://api.github.com/repos/$Repo/releases?per_page=100&page=$page" + try { + $pageReleases = @(Invoke-RestMethod -Uri $apiUrl -Headers $headers -UseBasicParsing) + } catch { + [Console]::Error.WriteLine("Error: failed to query the GitHub releases API to resolve '$Selector'.") + exit 2 + } + + $releases += $pageReleases + if ($pageReleases.Count -lt 100) { break } + $page++ } # v0 must match v0.*, not v0.* only after a dot boundary; the trailing dot diff --git a/scripts/install/install.sh b/scripts/install/install.sh index a3783dc78..698bdb12e 100755 --- a/scripts/install/install.sh +++ b/scripts/install/install.sh @@ -4,9 +4,9 @@ set -euo pipefail # OpenTaint installer for Linux and macOS # Usage: # curl -fsSL https://raw.githubusercontent.com/seqra/opentaint/main/scripts/install/install.sh | bash -# curl -fsSL .../install.sh | bash -s -- v1.2.3 # exact version ('v' optional) +# curl -fsSL .../install.sh | bash -s -- v0.4.5 # exact version ('v' optional) # curl -fsSL .../install.sh | bash -s -- v0 # newest v0.x.y -# curl -fsSL .../install.sh | bash -s -- v0.2 # newest v0.2.x +# curl -fsSL .../install.sh | bash -s -- v0.4 # newest v0.4.x REPO="${OPENTAINT_REPOSITORY:-seqra/opentaint}" INSTALL_DIR="${OPENTAINT_INSTALL_DIR:-}" @@ -99,13 +99,24 @@ fetch_stdout() { # mirroring scripts/resolve_opentaint_version.py. Prints the resolved tag. resolve_floating_selector() { local selector="$1" - local api_url="https://api.github.com/repos/${REPO}/releases?per_page=100" + local page=1 + local bodies="" + + while true; do + local api_url="https://api.github.com/repos/${REPO}/releases?per_page=100&page=${page}" + local body + if ! body="$(fetch_stdout "$api_url")"; then + echo "Error: failed to query the GitHub releases API to resolve '$selector'." >&2 + exit 2 + fi - local body - if ! body="$(fetch_stdout "$api_url")"; then - echo "Error: failed to query the GitHub releases API to resolve '$selector'." >&2 - exit 2 - fi + if [[ "$body" =~ ^[[:space:]]*\[[[:space:]]*\][[:space:]]*$ ]]; then + break + fi + + bodies+=$'\n'"$body" + page=$((page + 1)) + done # Match e.g. ^v0\. (major) or ^v0\.1\. (minor) against vX.Y.Z tags. local pattern @@ -116,13 +127,13 @@ resolve_floating_selector() { fi local best - best="$(printf '%s' "$body" \ + best="$(printf '%s' "$bodies" \ | grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"v[0-9]+\.[0-9]+\.[0-9]+"' \ | sed -E 's/.*"(v[0-9]+\.[0-9]+\.[0-9]+)"$/\1/' \ | grep -E "$pattern" \ | sed 's/^v//' \ | sort -t. -k1,1n -k2,2n -k3,3n \ - | tail -1)" + | tail -1 || true)" if [ -z "$best" ]; then echo "Error: no release found matching selector '$selector'." >&2 diff --git a/scripts/install/install_test.sh b/scripts/install/install_test.sh new file mode 100644 index 000000000..7f97e35a9 --- /dev/null +++ b/scripts/install/install_test.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Load the installer functions without executing main. +# shellcheck disable=SC1090 +source <(sed '$d' "$SCRIPT_DIR/install.sh") + +assert_eq() { + local expected="$1" + local actual="$2" + if [ "$expected" != "$actual" ]; then + echo "Expected '$expected', got '$actual'" >&2 + exit 1 + fi +} + +fetch_stdout() { + case "$1" in + *"&page=1") printf '%s' '[{"tag_name":"analyzer/2026.01.01.abcdef0"}]' ;; + *"&page=2") printf '%s' '[{"tag_name":"v0.4.5"},{"tag_name":"v0.5.1"}]' ;; + *"&page=3") printf '%s' '[]' ;; + *) return 1 ;; + esac +} + +VERSION_SELECTOR_KIND="major" +assert_eq "v0.5.1" "$(resolve_floating_selector v0)" + +VERSION_SELECTOR_KIND="minor" +assert_eq "v0.4.5" "$(resolve_floating_selector v0.4)" + +VERSION_SELECTOR_KIND="major" +if (resolve_floating_selector v9) >/dev/null 2>&1; then + echo "Expected an unknown selector to fail" >&2 + exit 1 +elif [ "$?" -ne 2 ]; then + echo "Expected an unknown selector to exit 2" >&2 + exit 1 +fi + +validate_version v0.4.5-rc.1 +assert_eq "exact" "$VERSION_SELECTOR_KIND" +assert_eq "v0.4.5-rc.1" "$VERSION_TAG" + +echo "install.sh version resolution tests passed" From a7d803a4f4f7533f5dcdff291ad7b76918691f9d Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Sun, 2 Aug 2026 10:02:48 +0400 Subject: [PATCH 4/5] Update gitlab/README.md --- gitlab/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/README.md b/gitlab/README.md index d734cb9d7..890b77ab1 100644 --- a/gitlab/README.md +++ b/gitlab/README.md @@ -98,7 +98,7 @@ There are two independent version selectors: ### CI template version -Controlled by the tag in the `include:` URL. Major and minor tags move to the newest compatible template release; exact tags remain pinned. +Controlled by the tag in the `include:` URL. Major and minor tags move to the newest compatible template release. Exact tags remain pinned. * `gitlab/v0` — latest template in major version 0 * `gitlab/v0.4` — latest template in minor version 0.4 From ac1652e71a475921ee8440c17345d9306faffdf7 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Sun, 2 Aug 2026 10:03:24 +0400 Subject: [PATCH 5/5] Update github/README.md --- github/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/README.md b/github/README.md index 34ceb91cb..927229286 100644 --- a/github/README.md +++ b/github/README.md @@ -153,7 +153,7 @@ There are two independent version selectors. ### GitHub Action version -The tag after `@` selects the integration code. Major and minor tags move to the newest compatible release; exact tags remain pinned: +The tag after `@` selects the integration code. Major and minor tags move to the newest compatible release. Exact tags remain pinned: * `github/v0` — latest action in major version 0 * `github/v0.4` — latest action in minor version 0.4