mirror of
https://github.com/rustfs/rustfs.git
synced 2026-10-26 22:07:53 +00:00
test(validation): verify isolated package publication failures
This commit is contained in:
@@ -130,14 +130,18 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ -n "${R2_ACCESS_KEY_ID}" && -n "${R2_SECRET_ACCESS_KEY}" && -n "${R2_ENDPOINT}" && -n "${R2_BUCKET}" ]]
|
||||
if [[ -z "${R2_ACCESS_KEY_ID}" || -z "${R2_SECRET_ACCESS_KEY}" || -z "${R2_ENDPOINT}" || -z "${R2_BUCKET}" ]]; then
|
||||
echo "Validation package publication requires complete R2 configuration" >&2
|
||||
exit 1
|
||||
fi
|
||||
test "$(git rev-parse HEAD)" = "${VALIDATION_SOURCE_SHA}"
|
||||
export AWS_ACCESS_KEY_ID="${R2_ACCESS_KEY_ID}" AWS_SECRET_ACCESS_KEY="${R2_SECRET_ACCESS_KEY}" AWS_DEFAULT_REGION=auto
|
||||
package_sha256="$(sha256sum "${PACKAGE_FILE}" | cut -d ' ' -f 1)"
|
||||
key="artifacts/rustfs/packages/validation/runs/${GITHUB_RUN_ID}/${GITHUB_RUN_ATTEMPT}/${package_sha256}/rustfs.deb"
|
||||
url="https://dl.rustfs.com/${key}"
|
||||
AWS_CLI=aws
|
||||
if ! "${AWS_CLI}" s3api put-object --generate-cli-skeleton input | jq -e 'has("IfNoneMatch")' >/dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -qq python3-venv
|
||||
AWS_CLI_DIR="$(mktemp -d "${RUNNER_TEMP}/validation-awscli.XXXXXX")"
|
||||
trap 'rm -rf "${AWS_CLI_DIR}"' EXIT
|
||||
@@ -156,11 +160,11 @@ jobs:
|
||||
--argjson build_run_id "${GITHUB_RUN_ID}" --argjson build_run_attempt "${GITHUB_RUN_ATTEMPT}" \
|
||||
--arg package_url "${url}" --arg package_sha256 "${package_sha256}" \
|
||||
'{schema: 3, workflow_sha: $workflow_sha, source_ref: $source_ref, source_sha: $source_sha, channel: $channel, build_run_id: $build_run_id, build_run_attempt: $build_run_attempt, package_url: $package_url, package_sha256: $package_sha256}' > "${manifest}"
|
||||
echo "manifest=${manifest}" >> "${GITHUB_OUTPUT}"
|
||||
echo "candidate_file=${manifest}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Upload validation candidate manifest
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: validation-candidate-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.publish.outputs.manifest }}
|
||||
path: ${{ steps.publish.outputs.candidate_file }}
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -54,6 +54,7 @@ def validate_manifest(manifest, run):
|
||||
require(isinstance(manifest["source_ref"], str) and bool(re.fullmatch(r"[A-Za-z0-9_./-]{1,200}", manifest["source_ref"])), "invalid build source ref")
|
||||
if version == 3:
|
||||
require(manifest["channel"] == "validation", "validation candidate has the wrong publication channel")
|
||||
require(manifest["source_ref"] == manifest["source_sha"], "validation candidate must bind an exact source SHA")
|
||||
channel = "validation" if version == 3 else "nightly"
|
||||
expected = (f"https://dl.rustfs.com/artifacts/rustfs/packages/{channel}/runs/{run['id']}/"
|
||||
f"{run['run_attempt']}/{manifest['package_sha256']}/rustfs.deb")
|
||||
|
||||
@@ -77,7 +77,7 @@ class CandidateTests(unittest.TestCase):
|
||||
self.assertEqual(result["manifest"]["source_sha"], "b" * 40)
|
||||
|
||||
def test_validation_manifest_binds_requested_source_and_private_channel(self):
|
||||
manifest = {**self.manifest, "schema": 3, "channel": "validation",
|
||||
manifest = {**self.manifest, "schema": 3, "channel": "validation", "source_ref": self.manifest["source_sha"],
|
||||
"package_url": self.manifest["package_url"].replace("/packages/nightly/", "/packages/validation/")}
|
||||
run = {**self.run, "path": ".github/workflows/rustfs-validation-package.yml"}
|
||||
payload = io.BytesIO()
|
||||
|
||||
@@ -221,5 +221,82 @@ SH
|
||||
self.assertNotIn(" overwrite: true", upload)
|
||||
|
||||
|
||||
class ValidationPackageTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Exercise the real upload shell with the same conditional object-store
|
||||
# fixture used by the nightly publication tests.
|
||||
self.fixture = NightlyCandidateTests("test_success_binds_actual_package_checkout_and_attempt")
|
||||
self.fixture.setUp()
|
||||
self.addCleanup(self.fixture.temp.cleanup)
|
||||
self.workflow = ROOT / ".github/workflows/rustfs-validation-package.yml"
|
||||
job = yaml_block(self.workflow.read_text().splitlines(), "build", 2)
|
||||
start = job.index(" - name: Upload validation package to immutable R2 path")
|
||||
end = job.index(" - name: Upload validation candidate manifest")
|
||||
upload = job[start:end]
|
||||
begin = upload.index(" run: |") + 1
|
||||
self.fixture.shell = "\n".join(line[10:] for line in upload[begin:] if not line.strip() or line.startswith(" "))
|
||||
self.fixture.shims.write_text(self.fixture.shims.read_text().replace("/packages/nightly/runs/*", "/packages/validation/runs/*"))
|
||||
self.fixture.env.update(PACKAGE_FILE=self.fixture.package.name, VALIDATION_SOURCE_SHA=self.fixture.sha,
|
||||
VALIDATION_SOURCE_REF=self.fixture.sha, GITHUB_SHA="f" * 40)
|
||||
|
||||
def test_success_uploads_only_one_immutable_object_and_exact_source(self):
|
||||
result = self.fixture.run_publish()
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
manifest = self.fixture.manifest()
|
||||
self.assertEqual(manifest["schema"], 3)
|
||||
self.assertEqual(manifest["channel"], "validation")
|
||||
self.assertEqual(manifest["source_sha"], self.fixture.sha)
|
||||
self.assertEqual(manifest["source_ref"], self.fixture.sha)
|
||||
self.assertEqual(manifest["workflow_sha"], "f" * 40)
|
||||
validate_manifest(manifest, {"id": 12345, "run_attempt": 1, "head_sha": "f" * 40})
|
||||
objects = [path for path in self.fixture.store.rglob("*") if path.is_file()]
|
||||
self.assertEqual(len(objects), 1)
|
||||
self.assertEqual(objects[0].read_bytes(), self.fixture.package.read_bytes())
|
||||
self.assertEqual(str(objects[0].relative_to(self.fixture.store)),
|
||||
f"artifacts/rustfs/packages/validation/runs/12345/1/{self.fixture.digest}/rustfs.deb")
|
||||
self.assertNotIn("s3 cp", (self.fixture.root / "aws.log").read_text())
|
||||
|
||||
def test_failure_or_missing_credentials_cannot_certify_a_candidate(self):
|
||||
for index, mode in enumerate(("upload-failure", "missing-public-url", "wrong-public-bytes", "incomplete-download",
|
||||
"bootstrap-failure", "install-failure", "broken-install")):
|
||||
with self.subTest(mode=mode):
|
||||
result = self.fixture.run_publish(FAKE_MODE=mode, GITHUB_RUN_ID=str(20000 + index))
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertFalse(self.fixture.output.exists())
|
||||
self.assertEqual(list(self.fixture.root.glob("validation-candidate-*.json")), [])
|
||||
for key in ("R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_ENDPOINT", "R2_BUCKET"):
|
||||
with self.subTest(key=key):
|
||||
self.assertNotEqual(self.fixture.run_publish(**{key: ""}).returncode, 0)
|
||||
self.assertFalse(self.fixture.output.exists())
|
||||
|
||||
def test_stale_source_and_duplicate_objects_are_rejected(self):
|
||||
result = self.fixture.run_publish(VALIDATION_SOURCE_SHA="e" * 40)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertFalse(self.fixture.store.exists())
|
||||
result = self.fixture.run_publish()
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
manifest = self.fixture.manifest()
|
||||
key = manifest["package_url"].removeprefix("https://dl.rustfs.com/")
|
||||
stored = self.fixture.store / key
|
||||
stored.write_bytes(b"preexisting conflicting object")
|
||||
(self.fixture.root / "validation-candidate-12345-1.json").unlink()
|
||||
result = self.fixture.run_publish()
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertEqual(stored.read_bytes(), b"preexisting conflicting object")
|
||||
self.assertFalse(self.fixture.output.exists())
|
||||
|
||||
def test_exact_source_and_workflow_identity_are_preserved_separately(self):
|
||||
lines = self.workflow.read_text().splitlines()
|
||||
build = "\n".join(yaml_block(lines, "build", 2))
|
||||
source = "\n".join(yaml_block(lines, "resolve-source", 2))
|
||||
self.assertIn('[[ "${REQUESTED_SHA}" =~ ^[0-9a-f]{40}$ ]]', source)
|
||||
self.assertIn('test "$(git -C validation-source rev-parse FETCH_HEAD)" = "${REQUESTED_SHA}"', source)
|
||||
self.assertIn("ref: ${{ needs.resolve-source.outputs.source_sha }}", build)
|
||||
self.assertIn('--arg source_sha "${VALIDATION_SOURCE_SHA}"', build)
|
||||
self.assertIn('--arg workflow_sha "${GITHUB_SHA}"', build)
|
||||
self.assertNotIn("PF_TESTING_GH_TOKEN", build)
|
||||
self.assertNotIn("publish_nightly_assets", build)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user