mirror of
https://github.com/storytold/craft-common-actions.git
synced 2026-11-02 18:06:16 +00:00
ci: pin nextest to 0.9.150
Use the fixed release for GHSA-vc25-qg58-w57c.
This commit is contained in:
@@ -47,10 +47,10 @@ on:
|
||||
required: false
|
||||
default: "wasm32-unknown-unknown"
|
||||
tools:
|
||||
description: "Prebuilt cargo tools from taiki-e/install-action, comma-separated, each with its version (e.g. cargo-nextest@0.9.149,cargo-deny@0.20.2); empty installs none"
|
||||
description: "Prebuilt cargo tools from taiki-e/install-action, comma-separated, each with its version (e.g. cargo-nextest@0.9.150,cargo-deny@0.20.2); empty installs none"
|
||||
type: string
|
||||
required: false
|
||||
default: "cargo-nextest@0.9.149"
|
||||
default: "cargo-nextest@0.9.150"
|
||||
env:
|
||||
description: "KEY=VALUE lines added to the environment of every leg before the cache is restored"
|
||||
type: string
|
||||
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
rustup default stable
|
||||
- uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29
|
||||
with:
|
||||
tool: cargo-llvm-cov,cargo-nextest
|
||||
tool: cargo-llvm-cov,cargo-nextest@0.9.150
|
||||
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
libgl1-mesa-dev mesa-vulkan-drivers
|
||||
- uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
tool: cargo-nextest@0.9.150
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: nextest-archive
|
||||
|
||||
@@ -340,7 +340,7 @@ The quality gate for an app's pull requests and pushes to main. It runs `command
|
||||
persisted credentials, and craft-i18n when the app has `craft-i18n.rev`;
|
||||
- `packages` installed through `actions/install-apt-packages` on Linux legs;
|
||||
- the channel from `rust-toolchain.toml`, or `toolchain`, with clippy, rustfmt and `targets`;
|
||||
- prebuilt cargo tools from `tools` (by default `cargo-nextest`);
|
||||
- prebuilt cargo tools from `tools` (by default `cargo-nextest@0.9.150`);
|
||||
- `CARGO_PROFILE_DEV_DEBUG=line-tables-only` (set `dev-debug: ""` to keep the repository's
|
||||
profile) and the caller's `env`, `linux-env` and `windows-env` lines, each `KEY=VALUE`;
|
||||
- a rust-cache dependency cache that only the default branch saves, so pull requests restore
|
||||
@@ -382,6 +382,21 @@ jobs:
|
||||
|
||||
Concurrency belongs to the caller: a reusable workflow cannot set it for the run.
|
||||
|
||||
The gate default and sharded SonarCloud coverage pin cargo-nextest to **0.9.150**, which fixes
|
||||
[GHSA-vc25-qg58-w57c](https://github.com/nextest-rs/nextest/security/advisories/GHSA-vc25-qg58-w57c).
|
||||
A caller overriding `tools` must also update its own nextest pin; changing the shared default
|
||||
cannot override it. Set a required minimum at the top of the caller's `.config/nextest.toml`,
|
||||
keeping its existing profiles and test groups:
|
||||
|
||||
```toml
|
||||
nextest-version = "0.9.150"
|
||||
```
|
||||
|
||||
A `recommended` minimum only warns; it does not enforce this requirement. The minimum-version
|
||||
check does not upgrade a developer's installed executable, and can be explicitly overridden.
|
||||
Do not enable experimental run recording or rerun untrusted portable recordings with an
|
||||
affected version. Ordinary compiled test archives used by coverage are a different feature.
|
||||
|
||||
An app that also checks Windows on main, on manual runs and on pull requests labelled `full-ci`,
|
||||
with per-OS environment and a shorter Windows gate (PDFCraft's call):
|
||||
|
||||
@@ -395,7 +410,7 @@ jobs:
|
||||
packages: >-
|
||||
libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev
|
||||
libgtk-3-dev qpdf poppler-utils
|
||||
tools: cargo-nextest@0.9.149,cargo-deny@0.20.2
|
||||
tools: cargo-nextest@0.9.150,cargo-deny@0.20.2
|
||||
linux-env: PDFCRAFT_ORACLES_REQUIRED=1
|
||||
windows-env: PDFCRAFT_REQUIRE_WINRES=1
|
||||
# Windows checks what differs by platform: the build (with resources) and the tests.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Keep shared nextest installers on the reviewed fix for GHSA-vc25-qg58-w57c.
|
||||
|
||||
Only YAML data is inspected. Negative controls mutate actual parsed installer values in memory;
|
||||
no workflow scripts, nextest executable, test binary or recording are executed.
|
||||
"""
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
import re
|
||||
import unittest
|
||||
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PIN = 'cargo-nextest@0.9.150'
|
||||
GATE = '.github/workflows/gate.yml'
|
||||
SONAR = '.github/workflows/sonarcloud.yml'
|
||||
|
||||
|
||||
def workflow_documents():
|
||||
documents = {}
|
||||
for directory in (ROOT / '.github/workflows', ROOT / 'actions'):
|
||||
for path in sorted(directory.rglob('*')):
|
||||
if path.is_file() and path.suffix in ('.yml', '.yaml'):
|
||||
documents[path.relative_to(ROOT).as_posix()] = yaml.load(
|
||||
path.read_text(), Loader=yaml.BaseLoader)
|
||||
return documents
|
||||
|
||||
|
||||
def tool_values(value, path=()):
|
||||
"""Find literal installer tool lists and reusable workflow tools defaults."""
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
child_path = path + (key,)
|
||||
if key in ('tool', 'tools') and isinstance(child, str):
|
||||
yield child_path, child
|
||||
elif key == 'tools' and isinstance(child, dict) and 'default' in child:
|
||||
yield child_path + ('default',), child['default']
|
||||
yield from tool_values(child, child_path)
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
yield from tool_values(child, path + (index,))
|
||||
|
||||
|
||||
def nextest_tool_values(documents):
|
||||
for filename, document in documents.items():
|
||||
for path, value in tool_values(document):
|
||||
if isinstance(value, str):
|
||||
specs = re.split(r'[,\s]+', value.strip())
|
||||
if any(spec.split('@', 1)[0] == 'cargo-nextest' for spec in specs):
|
||||
yield filename, path, value, specs
|
||||
|
||||
|
||||
def replace_value(document, path, value):
|
||||
for key in path[:-1]:
|
||||
document = document[key]
|
||||
document[path[-1]] = value
|
||||
|
||||
|
||||
class NextestVersionTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.documents = workflow_documents()
|
||||
|
||||
def assert_fixed_installers(self, documents):
|
||||
# Discover every tool/default site, not just fixtures for the three known sites.
|
||||
found = list(nextest_tool_values(documents))
|
||||
self.assertEqual(len(found), 3, 'expected gate default and both coverage installers')
|
||||
self.assertEqual(sorted(filename for filename, _, _, _ in found), [GATE, SONAR, SONAR])
|
||||
for filename, path, _, specs in found:
|
||||
nextest = [spec for spec in specs if spec.split('@', 1)[0] == 'cargo-nextest']
|
||||
# Do not wrap this assertion in subTest: negative controls must catch the exception.
|
||||
self.assertEqual(nextest, [PIN],
|
||||
f'{filename}:{path}: nextest must use the exact reviewed fixed release')
|
||||
|
||||
def test_every_shared_nextest_installer_is_fixed(self):
|
||||
self.assert_fixed_installers(self.documents)
|
||||
|
||||
def test_gate_default_still_reaches_the_installer_and_allows_caller_overrides(self):
|
||||
gate = self.documents[GATE]
|
||||
self.assertEqual(gate['on']['workflow_call']['inputs']['tools']['default'], PIN)
|
||||
installers = [step for step in gate['jobs']['gate']['steps']
|
||||
if step.get('uses', '').startswith('taiki-e/install-action@')]
|
||||
self.assertEqual(len(installers), 1)
|
||||
self.assertEqual(installers[0]['with']['tool'], '${{ inputs.tools }}')
|
||||
self.assertEqual(installers[0]['if'], "inputs.tools != ''")
|
||||
|
||||
def test_coverage_keeps_llvm_cov_and_the_two_nextest_installers(self):
|
||||
jobs = self.documents[SONAR]['jobs']
|
||||
for job, expected in [('build', 'cargo-llvm-cov,' + PIN), ('coverage', PIN)]:
|
||||
with self.subTest(job=job):
|
||||
installers = [step for step in jobs[job]['steps']
|
||||
if step.get('uses', '').startswith('taiki-e/install-action@')]
|
||||
self.assertEqual(len(installers), 1)
|
||||
self.assertEqual(installers[0]['with']['tool'], expected)
|
||||
|
||||
def test_rejects_vulnerable_floating_and_prerelease_pins_at_each_real_site(self):
|
||||
for filename, path, value, _ in nextest_tool_values(self.documents):
|
||||
for bad in ('cargo-nextest@0.9.149', 'cargo-nextest',
|
||||
'cargo-nextest@latest', 'cargo-nextest@0.9.150-rc.1'):
|
||||
with self.subTest(filename=filename, path=path, bad=bad):
|
||||
changed = deepcopy(self.documents)
|
||||
replace_value(changed[filename], path, value.replace(PIN, bad))
|
||||
with self.assertRaises(AssertionError):
|
||||
self.assert_fixed_installers(changed)
|
||||
|
||||
def test_rejects_an_additional_vulnerable_installer(self):
|
||||
changed = deepcopy(self.documents)
|
||||
changed[SONAR]['jobs']['coverage']['steps'].append({
|
||||
'uses': 'taiki-e/install-action@' + '0' * 40,
|
||||
'with': {'tool': 'cargo-nextest@0.9.149'},
|
||||
})
|
||||
with self.assertRaises(AssertionError):
|
||||
self.assert_fixed_installers(changed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user