contrib: verify-binaries accept full arch-platform specifier

Currently the verify-binaries script will accept "wildcards" such as
"linux" or "darwin" as binary specifiers.

Builders however usually only want to verify for their specific
architecture-platform that they want to use, or verify everything. Update the
script to accept single full architecture-platform specifiers as used by the
binary repositories.

With the new parser it's also possible to specify "25.0-linux-gnu" or
"25.0-x86_64" to verify multiple builds if so desired.
This commit is contained in:
willcl-ark 2024-01-23 09:50:35 +00:00
parent 651fb034d8
commit 1d16fee21d
No known key found for this signature in database
GPG Key ID: CE6EC49945C17EA6
2 changed files with 50 additions and 29 deletions

View File

@ -42,14 +42,14 @@ See the `Config` object for various options.
Validate releases with default settings:
```sh
./contrib/verify-binaries/verify.py pub 22.0
./contrib/verify-binaries/verify.py pub 22.0-rc3
./contrib/verify-binaries/verify.py pub 26.0
./contrib/verify-binaries/verify.py pub 25.1-rc1
```
Get JSON output and don't prompt for user input (no auto key import):
```sh
./contrib/verify-binaries/verify.py --json pub 22.0-x86
./contrib/verify-binaries/verify.py --json pub 26.0-x86_64
```
Rely only on local GPG state and manually specified keys, while requiring a
@ -57,20 +57,21 @@ threshold of at least 10 trusted signatures:
```sh
./contrib/verify-binaries/verify.py \
--trusted-keys 74E2DEF5D77260B98BC19438099BAD163C70FBFA,9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C \
--min-good-sigs 10 pub 22.0-x86
--min-good-sigs 10 pub 22.1-x86_64
```
If you only want to download the binaries for a certain platform, add the corresponding suffix, e.g.:
If you only want to download the binaries for a certain architecture and/or platform, add the corresponding suffix, e.g.:
```sh
./contrib/verify-binaries/verify.py pub 24.0.1-darwin
./contrib/verify-binaries/verify.py pub 23.1-rc1-win64
./contrib/verify-binaries/verify.py pub 24.2-rc1-win64
./contrib/verify-binaries/verify.py pub 25.1-linux-gnu
./contrib/verify-binaries/verify.py pub 26.0-x86_64-linux-gnu
```
If you do not want to keep the downloaded binaries, specify the cleanup option.
If you do not want to keep the downloaded binaries, specify the `cleanup` option.
```sh
./contrib/verify-binaries/verify.py pub --cleanup 22.0
./contrib/verify-binaries/verify.py pub --cleanup 22.1
```
Use the bin subcommand to verify all files listed in a local checksum file
@ -83,6 +84,6 @@ Verify only a subset of the files listed in a local checksum file
```sh
./contrib/verify-binaries/verify.py bin ~/Downloads/SHA256SUMS \
~/Downloads/bitcoin-24.0.1-x86_64-linux-gnu.tar.gz \
~/Downloads/bitcoin-24.0.1-arm-linux-gnueabihf.tar.gz
~/Downloads/bitcoin-24.1-x86_64-linux-gnu.tar.gz \
~/Downloads/bitcoin-24.1-arm-linux-gnueabihf.tar.gz
```

View File

@ -96,24 +96,35 @@ def bool_from_env(key, default=False) -> bool:
raise ValueError(f"Unrecognized environment value {key}={raw!r}")
VERSION_FORMAT = "<major>.<minor>[.<patch>][-rc[0-9]][-platform]"
VERSION_EXAMPLE = "22.0-x86_64 or 23.1-rc1-darwin"
VERSION_FORMAT = "<major>.<minor>[.<patch>][-rc[0-9]][-arch][-operating_system]"
VERSION_EXAMPLE = "22.0-x86_64 or 23.1-rc1-apple-darwin or 25.0-x86_64-linux-gnu"
VERSION_ARCHES = ["aarch64", "arm", "arm64", "osx", "osx64", "powerpc64", "powerpc64le", "riscv64", "x86_64"]
VERSION_PLATFORMS = ["apple", "apple-darwin", "linux-gnu", "linux-gnueabihf", "win64"]
def parse_version_string(version_str):
parts = version_str.split('-')
version_base = parts[0]
version_base = version_str.split('-')[0]
version_rc = ""
version_os = ""
if len(parts) == 2: # "<version>-rcN" or "version-platform"
if "rc" in parts[1]:
version_rc = parts[1]
else:
version_os = parts[1]
elif len(parts) == 3: # "<version>-rcN-platform"
version_rc = parts[1]
version_os = parts[2]
version_arch = ""
version_platform = ""
return version_base, version_rc, version_os
def extract_segment(segment_list): # sort segment list by length in descending order to prioritize longer matches
segment_list = sorted(segment_list, key=len, reverse=True)
for s in segment_list:
if s in version_str_nonlocal[0]:
version_str_nonlocal[0] = version_str_nonlocal[0].replace(s, '').strip('-')
return s
return ""
version_str_nonlocal = [version_str] # wrapped in list to make it non-local for inner functions
version_rc = extract_segment([f"rc{i}" for i in range(20)]) # assume rc doesn't increment above 20
version_arch = extract_segment(VERSION_ARCHES)
version_platform = extract_segment(VERSION_PLATFORMS)
if version_str_nonlocal[0] != version_base:
raise ValueError(f"Unmatched segments found in specifier string '{version_str}'.")
return version_base, version_rc, version_arch, version_platform
def download_with_wget(remote_file, local_file):
@ -290,6 +301,8 @@ def get_files_from_hosts_and_compare(
"Have you specified the version number in the following format?\n"
f"{VERSION_FORMAT} "
f"(example: {VERSION_EXAMPLE})\n"
f"Supported architectures: {VERSION_ARCHES}\n"
f"Supported platforms: {VERSION_PLATFORMS}\n"
f"wget output:\n{indent(output)}")
return ReturnCode.FILE_GET_FAILED
else:
@ -433,7 +446,12 @@ def parse_sums_file(sums_file_path: str, filename_filter: list[str]) -> list[lis
# extract hashes/filenames of binaries to verify from hash file;
# each line has the following format: "<hash> <binary_filename>"
with open(sums_file_path, 'r', encoding='utf8') as hash_file:
return [line.split()[:2] for line in hash_file if len(filename_filter) == 0 or any(f in line for f in filename_filter)]
# Check if a line contains all filter strings
def filter_func(line):
return all(f in line for f in filename_filter)
# Filter and split lines
return [line.split()[:2] for line in hash_file if filter_func(line)]
def verify_binary_hashes(hashes_to_verify: list[list[str]]) -> tuple[ReturnCode, dict[str, str]]:
@ -468,12 +486,14 @@ def verify_published_handler(args: argparse.Namespace) -> ReturnCode:
# determine remote dir dependent on provided version string
try:
version_base, version_rc, os_filter = parse_version_string(args.version)
version_base, version_rc, version_arch, os_filter = parse_version_string(args.version)
version_tuple = [int(i) for i in version_base.split('.')]
except Exception as e:
log.debug(e)
log.error(f"unable to parse version; expected format is {VERSION_FORMAT}")
log.error(f" e.g. {VERSION_EXAMPLE}")
log.error(f"Supported architectures: {VERSION_ARCHES}")
log.error(f"Supported platforms: {VERSION_PLATFORMS}")
return ReturnCode.BAD_VERSION
remote_dir = f"/bin/{VERSIONPREFIX}{version_base}/"
@ -512,9 +532,9 @@ def verify_published_handler(args: argparse.Namespace) -> ReturnCode:
return sigs_status
# Extract hashes and filenames
hashes_to_verify = parse_sums_file(SUMS_FILENAME, [os_filter])
hashes_to_verify = parse_sums_file(SUMS_FILENAME, [version_arch, os_filter])
if not hashes_to_verify:
log.error("no files matched the platform specified")
log.error("no files matched the platform specified. check available versions on bitcoincore.org/bin")
return ReturnCode.NO_BINARIES_MATCH
# remove binaries that are known not to be hosted by bitcoincore.org