Skip to content
Draft
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
117 changes: 117 additions & 0 deletions pipelines/test/tw/version-pin-drift.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: version-pin-drift

description: |
Flag drift between a version pinned in a melange package and the version the
upstream project declares it targets. Fetches the upstream version config from
its repo (at the package version, trying both X.Y.Z and vX.Y.Z tags) and
checks the pin against it.

Per ecosystem, the config file and comparison:
python : .python-version (==); requires-python / python_requires (in range)
nodejs : .nvmrc, .node-version (==); package.json engines.node (in range)
go : go.mod `go` directive (pin >= it, it is a minimum)
dotnet : global.json sdk.version (pin >= it)
ruby : .ruby-version, .tool-versions (==); Gemfile ruby (in range)

The pin's granularity drives the comparison: "3.11" compares major.minor, "18"
major. Fails on a mismatch, an out-of-range pin, or no upstream evidence.

Usage (ref defaults to the package version; only pin and repo are required):
- uses: test/tw/version-pin-drift
with:
ecosystem: python
repository: https://github.com/mlrun/mlrun
pin: ${{vars.python-version}}

needs:
packages:
- busybox
- coreutils
- curl

inputs:
ecosystem:
description: python, nodejs, go, dotnet or ruby.
required: true
repository:
description: Upstream GitHub repo URL (https://github.com/owner/repo).
required: true
pin:
description: The pinned version to validate (e.g. "3.11" or "18").
required: true
ref:
description: |
Git ref to read the upstream config from. Optional: defaults to the
package version, trying both X.Y.Z and vX.Y.Z tags. Set it explicitly
when the upstream tag doesn't follow that pattern (e.g. release-1.2.3).
required: false
default: ""

pipeline:
- name: Check pinned version against upstream declaration
runs: |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the goals of tw is to get rid of yaml-bash.
I think its a win to replace that with a reasonably clear program call in yaml to a package-supplied bash script.
(As example, see 'ldd-check'... basically i'm asking you take this out of yaml and put it into a shell script that takes parameters).

Alternatively, you can put it in a go program that accepts parameters, which makes unit tests much easier to write.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, I'll write a go program for this.

set -eu
pin="${{inputs.pin}}"
repo_path=$(echo "${{inputs.repository}}" | sed -E 's#^https?://github.com/##; s#\.git$##')
refs="${{inputs.ref}}"
[ -n "$refs" ] || refs="${{package.version}} v${{package.version}}"
found=""

# Fetch an upstream file, trying each candidate ref (empty if none has it).
fetch() {
for r in $refs; do
out=$(curl -fsSL "https://raw.githubusercontent.com/${repo_path}/${r}/$1" 2>/dev/null) && { printf '%s' "$out"; return 0; }
done
}
# First number in a string, trimmed to the pin's component count.
ncomp=$(echo "$pin" | awk -F. '{print NF}')
trunc() { echo "$1" | grep -oE '[0-9][0-9.]*' | head -1 | cut -d. -f1-"$ncomp"; }
le() { [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -1)" = "$1" ]; } # $1 <= $2

want_eq() { # pin must equal this exact version (raw string, label)
got=$(trunc "$1"); [ -n "$got" ] || return 0
[ "$got" = "$pin" ] || { echo "FAIL: upstream $2=$got != pin $pin"; exit 1; }
echo "ok $2=$got (==)"; found=1
}
want_min() { # pin must be >= this minimum (raw string, label)
got=$(trunc "$1"); [ -n "$got" ] || return 0
le "$got" "$pin" || { echo "FAIL: pin $pin below upstream $2 minimum $got"; exit 1; }
echo "ok $2=$got (pin>=)"; found=1
}
want_range() { # pin must satisfy the >= / > / <= / < bounds (spec, label)
[ -n "$1" ] || return 0
s=$(echo "$1" | tr -d '[:space:]') # ">= 3.1" -> ">=3.1"
for b in $(echo "$s" | grep -oE '>=?[0-9][0-9.]*'); do le "$(trunc "$b")" "$pin" || { echo "FAIL: pin $pin below $b ($1)"; exit 1; }; done
for b in $(echo "$s" | grep -oE '<=[0-9][0-9.]*'); do le "$pin" "$(trunc "$b")" || { echo "FAIL: pin $pin above $b ($1)"; exit 1; }; done
for b in $(echo "$s" | grep -oE '<[0-9][0-9.]*'); do t=$(trunc "$b"); { [ "$pin" != "$t" ] && le "$pin" "$t"; } || { echo "FAIL: pin $pin not < $b ($1)"; exit 1; }; done
echo "ok $2: $1 (in range)"; found=1
}

case "${{inputs.ecosystem}}" in
python)
want_eq "$(fetch .python-version)" ".python-version"
want_range "$(fetch pyproject.toml | grep -iE 'requires-python' | grep -oE '"[^"]+"' | head -1 | tr -d '"')" "requires-python"
want_range "$(fetch setup.py | grep -iE 'python_requires' | grep -oE '"[^"]+"' | head -1 | tr -d '"')" "python_requires"
;;
nodejs)
want_eq "$(fetch .nvmrc)" ".nvmrc"
want_eq "$(fetch .node-version)" ".node-version"
want_range "$(fetch package.json | tr -d '\n' | grep -oE '"node"[[:space:]]*:[[:space:]]*"[^"]+"' | grep -oE '"[^"]+"$' | tr -d '"')" "engines.node"
;;
go)
want_min "$(fetch go.mod | grep -oE '^go[[:space:]]+[0-9][0-9.]*' | head -1)" "go.mod go directive"
;;
dotnet)
want_min "$(fetch global.json | tr -d '\n' | grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1)" "global.json sdk.version"
;;
ruby)
want_eq "$(fetch .ruby-version)" ".ruby-version"
want_eq "$(fetch .tool-versions | grep -iE '^ruby[[:space:]]' | head -1)" ".tool-versions"
want_range "$(fetch Gemfile | grep -iE '^[[:space:]]*ruby[[:space:]]' | head -1)" "Gemfile ruby"
;;
*)
echo "FAIL: unsupported ecosystem '${{inputs.ecosystem}}'"; exit 1 ;;
esac

[ -n "$found" ] || { echo "FAIL: no upstream version evidence for ${{inputs.ecosystem}} (refs: $refs)"; exit 1; }
echo "PASS: pinned version '$pin' is consistent with upstream"
93 changes: 93 additions & 0 deletions tests/suites/version-pin-drift.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: version-pin-drift pipeline validation tests

description: Test suite for the test/tw/version-pin-drift pipeline across python and nodejs, using pinned upstream refs.

testcases:
- name: python exact match (.python-version)
description: mlrun v1.10.2 ships .python-version=3.11; pin 3.11 is consistent
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: python
repository: https://github.com/mlrun/mlrun
ref: v1.10.2
pin: "3.11"
expect_pass: true
- name: python drift (.python-version mismatch)
description: mlrun v1.10.2 targets 3.11; a 3.12 pin must be flagged
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: python
repository: https://github.com/mlrun/mlrun
ref: v1.10.2
pin: "3.12"
expect_pass: false
- name: python range (requires-python)
description: fastapi 0.115.0 declares requires-python >=3.8; pin 3.11 satisfies it
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: python
repository: https://github.com/fastapi/fastapi
ref: 0.115.0
pin: "3.11"
expect_pass: true
- name: nodejs exact match (.node-version)
description: next.js v14.2.0 ships .node-version=18; pin 18 is consistent
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: nodejs
repository: https://github.com/vercel/next.js
ref: v14.2.0
pin: "18"
expect_pass: true
- name: nodejs drift (.node-version mismatch)
description: next.js v14.2.0 targets node 18; a node 16 pin must be flagged
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: nodejs
repository: https://github.com/vercel/next.js
ref: v14.2.0
pin: "16"
expect_pass: false
- name: go minimum satisfied (go.mod directive)
description: cosign v2.4.1 go.mod needs go 1.22; a 1.23 pin is >= the minimum
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: go
repository: https://github.com/sigstore/cosign
ref: v2.4.1
pin: "1.23"
expect_pass: true
- name: go below minimum (go.mod directive)
description: cosign v2.4.1 go.mod needs go 1.22; a 1.19 pin is below the minimum
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: go
repository: https://github.com/sigstore/cosign
ref: v2.4.1
pin: "1.19"
expect_pass: false
- name: ruby exact match (.ruby-version)
description: mastodon v4.2.0 ships .ruby-version=3.2.2; pin 3.2 is consistent
package: bash
pipelines:
- uses: test/tw/version-pin-drift
with:
ecosystem: ruby
repository: https://github.com/mastodon/mastodon
ref: v4.2.0
pin: "3.2"
expect_pass: true
Loading