mirror of
https://github.com/hashicorp/vault.git
synced 2025-12-18 15:56:29 -05:00
* adding verify prod release binaries in release procedure yaml * adding verify prod release binaries in release procedure yaml * fixing lint * fixing lint * fixing lint * fixing lint * fixing lint * fixing lint * adding list binary versions * adding list binary * adding list binary * adding unit tests * adding comments to tests and scripts * adding comments to tests and scripts * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * fixing conflicts * testing pipeline * update verification logic * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * adding slack notification * adding slack notification * adding slack notification * adding slack notification * adding slack notification * adding slack notification * adding slack notification * adding slack notification * adding slack notification * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * testing pipeline * addressing comment Co-authored-by: Tin Vo <tintvo08@gmail.com>
This commit is contained in:
parent
56de87a0c8
commit
f7accefc40
7 changed files with 746 additions and 5 deletions
152
.github/scripts/verify-prod-release-binaries.sh
vendored
Executable file
152
.github/scripts/verify-prod-release-binaries.sh
vendored
Executable file
|
|
@ -0,0 +1,152 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright IBM Corp. 2016, 2025
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
set -e
|
||||
|
||||
binaries="$1"
|
||||
|
||||
if [ -z "$binaries" ]; then
|
||||
echo "Error: JSON input is required."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# Initialize result holders
|
||||
##########################################
|
||||
|
||||
version_missing=""
|
||||
variant_missing=""
|
||||
os_missing=""
|
||||
|
||||
valid_versions_output=""
|
||||
valid_variants_output=""
|
||||
valid_os_output=""
|
||||
|
||||
##########################################
|
||||
# 1. Verify Versions
|
||||
##########################################
|
||||
|
||||
invalid_versions=$(echo "$binaries" | jq -r '.invalid_versions[]?')
|
||||
|
||||
valid_versions_output+="Versions:\n "
|
||||
|
||||
versions=$(echo "$binaries" | jq -r '.valid_versions | keys[]')
|
||||
|
||||
for v in $versions; do
|
||||
valid_versions_output+="✓ $v, "
|
||||
done
|
||||
|
||||
valid_versions_output+="\n"
|
||||
|
||||
if [ -n "$invalid_versions" ]; then
|
||||
for v in $invalid_versions; do
|
||||
version_missing+="Missing Versions: $v\n"
|
||||
done
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# 2. Verify Variants
|
||||
##########################################
|
||||
|
||||
for v in $versions; do
|
||||
base_version="${v%-lts}"
|
||||
|
||||
valid_variants_output+="Version: $v\n "
|
||||
|
||||
req=(
|
||||
"$base_version"
|
||||
"${base_version}+ent"
|
||||
"${base_version}+ent.fips"
|
||||
"${base_version}+ent.hsm"
|
||||
"${base_version}+ent.hsm.fips"
|
||||
)
|
||||
|
||||
actual_variants=$(echo "$binaries" | jq -r ".valid_versions[\"$v\"].variants[].variant")
|
||||
|
||||
for r in "${req[@]}"; do
|
||||
if echo "$actual_variants" | grep -q "^$r"; then
|
||||
valid_variants_output+="✓ $r, "
|
||||
else
|
||||
variant_missing+="Version $v is missing variant: $r\n"
|
||||
fi
|
||||
done
|
||||
|
||||
valid_variants_output+="\n"
|
||||
done
|
||||
|
||||
##########################################
|
||||
# 3. Verify OS
|
||||
##########################################
|
||||
|
||||
for v in $versions; do
|
||||
base_version="${v%-lts}"
|
||||
variants=$(echo "$binaries" | jq -r ".valid_versions[\"$v\"].variants[].variant")
|
||||
|
||||
valid_os_output+="Version: $v"
|
||||
|
||||
for variant in $variants; do
|
||||
valid_os_output+="\n Variant: $variant\n "
|
||||
|
||||
os_list=$(echo "$binaries" | jq -r \
|
||||
".valid_versions[\"$v\"].variants[] | select(.variant==\"$variant\") | .os[]")
|
||||
|
||||
if [[ "$variant" == "$base_version" || "$variant" == "${base_version}+ent" ]]; then
|
||||
for os in darwin freebsd linux netbsd openbsd solaris windows; do
|
||||
if grep -qx "$os" <<< "$os_list"; then
|
||||
valid_os_output+=" ✓ $os, "
|
||||
else
|
||||
os_missing+="Variant $variant of version $v is missing OS: $os\n"
|
||||
fi
|
||||
done
|
||||
else
|
||||
for os in $os_list; do
|
||||
if [[ "$os" == "linux" ]]; then
|
||||
valid_os_output+=" ✓ linux"
|
||||
else
|
||||
os_missing+="Variant $variant of version $v has invalid OS: $os\n"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
valid_os_output+="\n"
|
||||
done
|
||||
|
||||
##########################################
|
||||
# 4. OUTPUT LOGIC
|
||||
##########################################
|
||||
|
||||
if [ -z "$version_missing" ] && [ -z "$variant_missing" ] && [ -z "$os_missing" ]; then
|
||||
echo "*Available Versions:*"
|
||||
printf "%b" "$valid_versions_output"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# If ANYTHING is missing → print full output
|
||||
##########################################
|
||||
|
||||
echo "*Available Versions:*"
|
||||
printf "%b" "$valid_versions_output"
|
||||
|
||||
echo -e "\n*Available Variants:*"
|
||||
printf "%b" "$valid_variants_output"
|
||||
|
||||
echo -e "\n*Available OS:*"
|
||||
printf "%b" "$valid_os_output"
|
||||
|
||||
if [ -n "$version_missing" ]; then
|
||||
echo -e "\n*Missing Release Binary Versions:*"
|
||||
printf "%b" "$version_missing"
|
||||
fi
|
||||
|
||||
if [ -n "$variant_missing" ]; then
|
||||
echo -e "\n*Missing Release Binary Variants:*"
|
||||
printf "%b" "$variant_missing"
|
||||
fi
|
||||
|
||||
if [ -n "$os_missing" ]; then
|
||||
echo -e "\n*Missing Release Binary OS:*"
|
||||
printf "%b" "$os_missing"
|
||||
fi
|
||||
|
|
@ -7,6 +7,7 @@ ignore internal/pkg/golang/fixtures
|
|||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/PuerkitoBio/goquery v1.11.0
|
||||
github.com/avast/retry-go/v4 v4.6.1
|
||||
github.com/google/go-github/v74 v74.0.0
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
|
|
@ -24,6 +25,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
|
|
@ -70,11 +72,11 @@ require (
|
|||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,8 +16,12 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q
|
|||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
|
|
@ -74,6 +78,7 @@ github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a
|
|||
github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE=
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM=
|
||||
|
|
@ -96,6 +101,7 @@ github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQx
|
|||
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
|
||||
github.com/hashicorp/releases-api v0.2.3 h1:mwNR+lKgJtIyeSQXYGM86fZ0u8ed09v7NS2ePKmVvyc=
|
||||
github.com/hashicorp/releases-api v0.2.3/go.mod h1:J8AiSwS1Qy/m/RmHskUGDu9YQRLKreBBswc6ZTY5/tI=
|
||||
github.com/hashicorp/releases-api v0.2.9/go.mod h1:TKqUiMrAF06VtpFVS50uUbjt+A+syM/59EV6uNx7W7M=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
|
|
@ -189,6 +195,7 @@ github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
|||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/veqryn/slog-context v0.8.0 h1:lDhwAgjwx52K5StqqQzi5d0Y/F4SNyGZbsXGd8MtucM=
|
||||
github.com/veqryn/slog-context v0.8.0/go.mod h1:8rsT72p0kzzN9lmkwtabIhxg7ZkpnKblt9x3Eix8Tc0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE=
|
||||
github.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||
|
|
@ -207,30 +214,103 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx
|
|||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ func newReleasesListCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
listCmd.AddCommand(newReleasesVersionsBetweenCmd())
|
||||
listCmd.AddCommand(newReleasesListBinaryVersionsCmd())
|
||||
listCmd.AddCommand(newReleasesListActiveVersionsCmd())
|
||||
listCmd.AddCommand(newReleasesListUpdatedVersionsCmd())
|
||||
|
||||
|
|
|
|||
132
tools/pipeline/internal/cmd/releases_list_binary_versions.go
Normal file
132
tools/pipeline/internal/cmd/releases_list_binary_versions.go
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright IBM Corp. 2016, 2025
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/releases"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var listBinaryVersionsReq = &releases.ListBinaryVersionsReq{}
|
||||
|
||||
func newReleasesListBinaryVersionsCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "binary-versions <versions>",
|
||||
Short: "List available binary release variants for the given version labels",
|
||||
Long: "List available binary release variants for the given version labels",
|
||||
RunE: runListBinaryVersions,
|
||||
Args: cobra.MinimumNArgs(1), // Require at least the versions argument
|
||||
}
|
||||
|
||||
// Allows the user to control whether table or JSON is printed to stdout.
|
||||
cmd.PersistentFlags().StringVar(&rootCfg.format, "format", "table", `Output format: table|json`)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runListBinaryVersions(cmd *cobra.Command, args []string) error {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
listBinaryVersionsReq.VersionsString = args[0]
|
||||
|
||||
// Optional second argument: boolean controlling whether JSON is saved to a file.
|
||||
saveToFile := false
|
||||
if len(args) > 1 {
|
||||
v, err := strconv.ParseBool(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("second argument must be true or false: %w", err)
|
||||
}
|
||||
saveToFile = v
|
||||
}
|
||||
|
||||
// Executes the backend query to retrieve binary version metadata.
|
||||
res, err := listBinaryVersionsReq.Run(context.TODO())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list binary versions: %w", err)
|
||||
}
|
||||
|
||||
// Pre-encode JSON once since we may print it or save it depending on flags/args.
|
||||
jsonBytes, err := res.ToJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode JSON: %w", err)
|
||||
}
|
||||
|
||||
// If the user requested saving the response, write the JSON output to a local file.
|
||||
// This occurs regardless of the --format flag.
|
||||
if saveToFile {
|
||||
fileName := "binary-versions-output.json"
|
||||
if err := os.WriteFile(fileName, jsonBytes, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write JSON to file: %w", err)
|
||||
}
|
||||
fmt.Printf("Saved JSON output to %s\n", fileName)
|
||||
}
|
||||
|
||||
switch rootCfg.format {
|
||||
case "json":
|
||||
fmt.Println(string(jsonBytes))
|
||||
default:
|
||||
printBinaryTable(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printBinaryTable(res *releases.ListBinaryVersionsRes) {
|
||||
// Pretty-print table writer for terminal output.
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.SetStyle(table.StyleLight)
|
||||
t.AppendHeader(table.Row{"Version", "Status", "Variants Count", "Variants", "OS"})
|
||||
|
||||
// Iterate through all versions requested, including ones the backend marked missing.
|
||||
for _, label := range res.AllVersions {
|
||||
entry, ok := res.ValidVersions[label]
|
||||
|
||||
// Default values for versions not found.
|
||||
status := "MISSING"
|
||||
count := "—"
|
||||
variantsStr := ""
|
||||
osStr := ""
|
||||
|
||||
if ok {
|
||||
// Version exists — fill in detailed information.
|
||||
status = strings.ToUpper(entry.Status)
|
||||
count = fmt.Sprintf("%d", len(entry.Variants))
|
||||
|
||||
// Collect variant labels and all OS values.
|
||||
var variantNames []string
|
||||
osSet := make(map[string]struct{}) // Deduplicate OS entries across variants.
|
||||
|
||||
for _, v := range entry.Variants {
|
||||
variantNames = append(variantNames, v.Variant)
|
||||
for _, osName := range v.OS {
|
||||
osSet[osName] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Join variant names like: "enterprise, oss, fips"
|
||||
variantsStr = strings.Join(variantNames, ", ")
|
||||
|
||||
// Convert deduped OS set into sorted list for stable output.
|
||||
var osList []string
|
||||
for osName := range osSet {
|
||||
osList = append(osList, osName)
|
||||
}
|
||||
slices.Sort(osList)
|
||||
osStr = strings.Join(osList, ", ")
|
||||
}
|
||||
|
||||
// Add row representing this version label.
|
||||
t.AppendRow(table.Row{label, status, count, variantsStr, osStr})
|
||||
}
|
||||
|
||||
t.Render()
|
||||
}
|
||||
262
tools/pipeline/internal/pkg/releases/list_binary_versions.go
Normal file
262
tools/pipeline/internal/pkg/releases/list_binary_versions.go
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
// Copyright IBM Corp. 2016, 2025
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package releases
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type ListBinaryVersionsReq struct {
|
||||
VersionsString string
|
||||
}
|
||||
|
||||
type VariantInfo struct {
|
||||
Variant string `json:"variant"`
|
||||
OS []string `json:"os,omitempty"`
|
||||
}
|
||||
|
||||
// - ValidVersions: details for versions that exist
|
||||
// - InvalidVersions: versions requested by the user but not found upstream
|
||||
// - AllVersions: original input order
|
||||
type ListBinaryVersionsRes struct {
|
||||
ValidVersions map[string]struct {
|
||||
Status string `json:"status"`
|
||||
Variants []VariantInfo `json:"variants"`
|
||||
} `json:"valid_versions"`
|
||||
|
||||
InvalidVersions []string `json:"invalid_versions"`
|
||||
AllVersions []string `json:"all_versions"`
|
||||
}
|
||||
|
||||
func NewListBinaryVersionsReq(s string) *ListBinaryVersionsReq {
|
||||
return &ListBinaryVersionsReq{VersionsString: s}
|
||||
}
|
||||
|
||||
// normalizeLabel transforms user-friendly labels into a canonical form
|
||||
// that matches HashiCorp’s release naming scheme.
|
||||
func normalizeLabel(label string) (normalized, display string) {
|
||||
display = label
|
||||
normalized = strings.TrimSuffix(label, "-ce")
|
||||
normalized = strings.TrimSuffix(normalized, "-lts")
|
||||
if strings.HasSuffix(label, "-ent") {
|
||||
normalized = strings.TrimSuffix(normalized, "-ent") + "+ent"
|
||||
}
|
||||
return normalized, display
|
||||
}
|
||||
|
||||
const baseURL = "https://releases.hashicorp.com/vault/"
|
||||
|
||||
// fetchAvailableVersions scrapes the Vault releases index page and returns
|
||||
func fetchAvailableVersions(ctx context.Context) ([]string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Parse HTML returned by releases.hashicorp.com
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract version folder names from links ("/vault/1.18.1/")
|
||||
var versions []string
|
||||
doc.Find("body > ul > li > a").Each(func(_ int, sel *goquery.Selection) {
|
||||
href, exists := sel.Attr("href")
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
// Normalize: trim trailing "/", strip parent path
|
||||
href = strings.TrimSuffix(href, "/")
|
||||
if idx := strings.LastIndex(href, "/"); idx != -1 {
|
||||
href = href[idx+1:]
|
||||
}
|
||||
|
||||
if href != "" {
|
||||
versions = append(versions, href)
|
||||
}
|
||||
})
|
||||
|
||||
// Sort + deduplicate for stable deterministic results
|
||||
slices.Sort(versions)
|
||||
return slices.Compact(versions), nil
|
||||
}
|
||||
|
||||
// fetchAvailableVariants returns all variant names that match a "base version".
|
||||
func fetchAvailableVariants(available []string, version string) []string {
|
||||
var result []string
|
||||
prefix := version
|
||||
plusPrefix := version + "+" // enterprise or extra flavors
|
||||
|
||||
for _, v := range available {
|
||||
if v == prefix || strings.HasPrefix(v, plusPrefix) {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// fetchAvailableOs inspects each variant directory and extracts the OS
|
||||
func fetchAvailableOs(ctx context.Context, items []string) (map[string][]string, error) {
|
||||
result := make(map[string][]string)
|
||||
|
||||
for _, full := range items {
|
||||
result[full] = []string{}
|
||||
url := baseURL + full + "/"
|
||||
|
||||
// Fetch the specific variant page
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status for %s: %d", full, resp.StatusCode)
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ZIP filename format starts with: vault_<variant>_<os>_<arch>.zip
|
||||
prefix := "vault_" + full
|
||||
|
||||
doc.Find("body > ul > li > a").Each(func(_ int, sel *goquery.Selection) {
|
||||
name := sel.Text()
|
||||
|
||||
// Ignore unrelated files
|
||||
if !strings.HasPrefix(name, prefix) || !strings.HasSuffix(name, ".zip") {
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(strings.TrimSuffix(name, ".zip"), "_")
|
||||
if len(parts) < 3 {
|
||||
return // unexpected format
|
||||
}
|
||||
|
||||
// OS is always the second-to-last component ("linux", "darwin", etc.)
|
||||
osName := parts[len(parts)-2]
|
||||
|
||||
// Avoid duplicates
|
||||
if !slices.Contains(result[full], osName) {
|
||||
result[full] = append(result[full], osName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *ListBinaryVersionsReq) Run(ctx context.Context) (*ListBinaryVersionsRes, error) {
|
||||
if r == nil || r.VersionsString == "" {
|
||||
return &ListBinaryVersionsRes{}, nil
|
||||
}
|
||||
|
||||
// User-provided labels (raw, with suffixes)
|
||||
labels := strings.Fields(r.VersionsString)
|
||||
|
||||
// Initialize response container
|
||||
res := &ListBinaryVersionsRes{
|
||||
ValidVersions: make(map[string]struct {
|
||||
Status string `json:"status"`
|
||||
Variants []VariantInfo `json:"variants"`
|
||||
}),
|
||||
AllVersions: labels,
|
||||
}
|
||||
|
||||
// Fetch the list of *all* versions from HashiCorp
|
||||
availableVersions, err := fetchAvailableVersions(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed fetching versions: %w", err)
|
||||
}
|
||||
|
||||
// Process each requested version label
|
||||
for _, label := range labels {
|
||||
normalized, display := normalizeLabel(label)
|
||||
|
||||
// If the base version doesn't exist, the entire entry is invalid
|
||||
if !slices.Contains(availableVersions, normalized) {
|
||||
res.InvalidVersions = append(res.InvalidVersions, display)
|
||||
continue
|
||||
}
|
||||
|
||||
// Find all variants associated with this version
|
||||
variants := fetchAvailableVariants(availableVersions, normalized)
|
||||
if len(variants) == 0 {
|
||||
res.InvalidVersions = append(res.InvalidVersions, display)
|
||||
continue
|
||||
}
|
||||
|
||||
// Fetch OS availability for each variant
|
||||
osMap, err := fetchAvailableOs(ctx, variants)
|
||||
if err != nil {
|
||||
// Non-fatal: mark the version invalid but continue processing others
|
||||
slog.WarnContext(ctx, "failed fetching OS", "version", normalized, "error", err)
|
||||
res.InvalidVersions = append(res.InvalidVersions, display)
|
||||
continue
|
||||
}
|
||||
|
||||
// Build variant list
|
||||
slices.Sort(variants)
|
||||
var vlist []VariantInfo
|
||||
for _, v := range variants {
|
||||
vlist = append(vlist, VariantInfo{Variant: v, OS: osMap[v]})
|
||||
}
|
||||
|
||||
// Add a valid entry for this version label
|
||||
res.ValidVersions[display] = struct {
|
||||
Status string `json:"status"`
|
||||
Variants []VariantInfo `json:"variants"`
|
||||
}{
|
||||
Status: "valid",
|
||||
Variants: vlist,
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(res.InvalidVersions)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ToJSON returns a pretty-printed JSON representation of the results.
|
||||
func (r *ListBinaryVersionsRes) ToJSON() ([]byte, error) {
|
||||
return json.MarshalIndent(r, "", " ")
|
||||
}
|
||||
|
||||
// String provides a concise human-readable summary (counts only).
|
||||
func (r *ListBinaryVersionsRes) String() string {
|
||||
total := 0
|
||||
for _, v := range r.ValidVersions {
|
||||
total += len(v.Variants)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"Listed %d → %d valid (%d variants), %d missing",
|
||||
len(r.AllVersions), len(r.ValidVersions), total, len(r.InvalidVersions),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright IBM Corp. 2016, 2025
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package releases
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type ListBinaryVersionsReq struct {
|
||||
VersionsString string
|
||||
}
|
||||
|
||||
type VariantInfo struct {
|
||||
Variant string `json:"variant"`
|
||||
OS []string `json:"os,omitempty"`
|
||||
}
|
||||
|
||||
type ListBinaryVersionsRes struct {
|
||||
ValidVersions map[string]struct {
|
||||
Status string `json:"status"`
|
||||
Variants []VariantInfo `json:"variants"`
|
||||
} `json:"valid_versions"`
|
||||
|
||||
InvalidVersions []string `json:"invalid_versions"`
|
||||
AllVersions []string `json:"all_versions"`
|
||||
}
|
||||
|
||||
func NewListBinaryVersionsReq(s string) *ListBinaryVersionsReq {
|
||||
return &ListBinaryVersionsReq{VersionsString: s}
|
||||
}
|
||||
|
||||
// JSON formatting helper; test ensures indentation and content correctness.
|
||||
func (r *ListBinaryVersionsRes) ToJSON() ([]byte, error) {
|
||||
return json.MarshalIndent(r, "", " ")
|
||||
}
|
||||
|
||||
// Human-readable summary—unit test checks that counts are computed correctly
|
||||
// and output string contains expected substrings.
|
||||
func (r *ListBinaryVersionsRes) String() string {
|
||||
total := 0
|
||||
for _, v := range r.ValidVersions {
|
||||
total += len(v.Variants)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"Listed %d → %d valid (%d variants), %d missing",
|
||||
len(r.AllVersions), len(r.ValidVersions), total, len(r.InvalidVersions),
|
||||
)
|
||||
}
|
||||
|
||||
func TestNewListBinaryVersionsReq(t *testing.T) {
|
||||
// Verify that the constructor returns a valid object
|
||||
req := NewListBinaryVersionsReq("1.2.3 4.5.6-ent")
|
||||
if req == nil {
|
||||
t.Fatal("returned nil")
|
||||
}
|
||||
|
||||
// Ensure the input string is stored without modification
|
||||
if req.VersionsString != "1.2.3 4.5.6-ent" {
|
||||
t.Errorf("got %q", req.VersionsString)
|
||||
}
|
||||
}
|
||||
|
||||
// Unit test: JSON output and String() summary formatting
|
||||
func TestListBinaryVersionsRes_ToJSON_and_String(t *testing.T) {
|
||||
// Create a fake response object
|
||||
res := &ListBinaryVersionsRes{
|
||||
ValidVersions: map[string]struct {
|
||||
Status string `json:"status"`
|
||||
Variants []VariantInfo `json:"variants"`
|
||||
}{
|
||||
"1.17.0-ent": {
|
||||
Status: "valid",
|
||||
Variants: []VariantInfo{
|
||||
{Variant: "1.17.0+ent", OS: []string{"linux", "darwin"}},
|
||||
{Variant: "1.17.0+ent.hsm", OS: []string{"linux"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
InvalidVersions: []string{"9.9.9"},
|
||||
AllVersions: []string{"1.17.0-ent", "9.9.9"},
|
||||
}
|
||||
|
||||
// Test: ToJSON output
|
||||
b, err := res.ToJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure the JSON contains known variant values
|
||||
if !strings.Contains(string(b), "1.17.0+ent.hsm") {
|
||||
t.Error("JSON missing variant")
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Test: String() summary
|
||||
// -------------------------
|
||||
// Expected values based on the test fixture:
|
||||
// total requested: 2
|
||||
// valid versions: 1
|
||||
// total variants: 2
|
||||
// missing: 1
|
||||
expected := "2 → 1 valid (2 variants), 1 missing"
|
||||
|
||||
// Check that the summary contains the correct computed text.
|
||||
if !strings.Contains(res.String(), expected) {
|
||||
t.Errorf("String() = %q, want substring %q", res.String(), expected)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue