All checks were successful
Publish Hugo Deployment Image / publish (push) Successful in 54s
The deploy job downloads Hugo Extended 0.165.0 from the GitHub release, verifies its SHA-256 and runs it from RUNNER_TEMP instead of the runner's auto-refreshing snap. Deploys fail before rsync if hugo is a different version, not Extended, or not the pinned binary.
226 lines
9.2 KiB
Python
226 lines
9.2 KiB
Python
import os
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
import textwrap
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
|
INFRASTRUCTURE_ROOT = REPOSITORY_ROOT.parent / "hetzner.develton.com"
|
|
EXPECTED_HUGO_VERSION = "0.165.0"
|
|
EXPECTED_HUGO_SHA256 = "f43494894cdf4a8630a201d5c828051c77f523cc66bb3938b30806835470ac20"
|
|
|
|
|
|
def read(path: Path) -> str:
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def workflow_run_block(workflow: str, step_name: str) -> str:
|
|
match = re.search(
|
|
rf"^ - name: {re.escape(step_name)}\n"
|
|
r" run: \|\n"
|
|
r"(?P<body>(?: .*\n)+)",
|
|
workflow,
|
|
re.MULTILINE,
|
|
)
|
|
if match is None:
|
|
raise AssertionError(f"Workflow step not found: {step_name}")
|
|
return textwrap.dedent(match.group("body"))
|
|
|
|
|
|
class HugoVersionPinTests(unittest.TestCase):
|
|
def test_legacy_image_uses_expected_hugo_version(self) -> None:
|
|
dockerfile = read(REPOSITORY_ROOT / "Dockerfile")
|
|
match = re.search(r"^ARG HUGO_VERSION=(\S+)$", dockerfile, re.MULTILINE)
|
|
|
|
self.assertIsNotNone(match, "Dockerfile must declare HUGO_VERSION")
|
|
self.assertEqual(EXPECTED_HUGO_VERSION, match.group(1))
|
|
self.assertIn("hugo_extended_${HUGO_VERSION}_", dockerfile)
|
|
|
|
def test_shared_workflow_pins_and_validates_extended_hugo(self) -> None:
|
|
workflow = read(
|
|
REPOSITORY_ROOT / ".forgejo" / "workflows" / "deploy-hugo-site.yml"
|
|
)
|
|
self.assertRegex(
|
|
workflow,
|
|
re.compile(
|
|
rf'^ HUGO_VERSION: ["\']{re.escape(EXPECTED_HUGO_VERSION)}["\']$',
|
|
re.MULTILINE,
|
|
),
|
|
)
|
|
|
|
self.assertIn(f' HUGO_SHA256: "{EXPECTED_HUGO_SHA256}"', workflow)
|
|
path = re.search(r"^ PATH: (.+)$", workflow, re.MULTILINE)
|
|
self.assertIsNotNone(path)
|
|
self.assertNotIn("/snap/hugo", path.group(1))
|
|
checkout_position = workflow.find("- name: Checkout repository")
|
|
install_position = workflow.find("- name: Install pinned Hugo Extended")
|
|
self.assertGreaterEqual(checkout_position, 0)
|
|
self.assertGreater(install_position, checkout_position)
|
|
installation = workflow_run_block(workflow, "Install pinned Hugo Extended")
|
|
self.assertIn("sha256sum --check --strict", installation)
|
|
self.assertIn("GITHUB_PATH", installation)
|
|
|
|
validation_position = workflow.find("- name: Validate Hugo version")
|
|
deploy_position = workflow.find("- name: Run deploy script")
|
|
self.assertGreater(validation_position, install_position)
|
|
self.assertGreater(deploy_position, validation_position)
|
|
|
|
validation = workflow_run_block(workflow, "Validate Hugo version")
|
|
self.assertIn("hugo version", validation)
|
|
self.assertIn("exit 1", validation)
|
|
|
|
def test_shared_workflow_version_guard_fails_closed(self) -> None:
|
|
workflow = read(
|
|
REPOSITORY_ROOT / ".forgejo" / "workflows" / "deploy-hugo-site.yml"
|
|
)
|
|
validation = workflow_run_block(workflow, "Validate Hugo version")
|
|
|
|
with tempfile.TemporaryDirectory() as temporary_directory:
|
|
hugo_directory = Path(temporary_directory) / f"hugo-{EXPECTED_HUGO_VERSION}"
|
|
hugo_directory.mkdir()
|
|
fake_hugo = hugo_directory / "hugo"
|
|
fake_hugo.write_text(
|
|
"#!/bin/sh\nprintf '%s\\n' \"$FAKE_HUGO_VERSION_OUTPUT\"\n",
|
|
encoding="utf-8",
|
|
)
|
|
fake_hugo.chmod(0o755)
|
|
|
|
environment = os.environ.copy()
|
|
environment["PATH"] = f"{hugo_directory}:{environment['PATH']}"
|
|
environment["RUNNER_TEMP"] = temporary_directory
|
|
environment["HUGO_VERSION"] = EXPECTED_HUGO_VERSION
|
|
|
|
accepted_outputs = (
|
|
(
|
|
"hugo v0.165.0+extended linux/amd64 "
|
|
"BuildDate=unknown VendorInfo=gohugoio"
|
|
),
|
|
(
|
|
"hugo v0.165.0-67fb390c6309dc2b8361e714fc5d71e3e4d625cf"
|
|
"+extended linux/amd64 BuildDate=unknown VendorInfo=snap:0.165.0"
|
|
),
|
|
)
|
|
for accepted_output in accepted_outputs:
|
|
with self.subTest(accepted_output=accepted_output):
|
|
environment["FAKE_HUGO_VERSION_OUTPUT"] = accepted_output
|
|
accepted = subprocess.run(
|
|
["bash", "-c", validation],
|
|
env=environment,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
self.assertEqual(0, accepted.returncode, accepted.stderr)
|
|
|
|
rejected_outputs = (
|
|
"hugo v0.164.0+extended linux/amd64 BuildDate=unknown",
|
|
"hugo v0.165.1+extended linux/amd64 BuildDate=unknown",
|
|
"hugo v0.165.1-abcdef+extended linux/amd64 BuildDate=unknown",
|
|
"hugo v0.165.0 linux/amd64 BuildDate=unknown",
|
|
"hugo v0.165.0-abcdef linux/amd64 BuildDate=unknown",
|
|
)
|
|
for rejected_output in rejected_outputs:
|
|
with self.subTest(rejected_output=rejected_output):
|
|
environment["FAKE_HUGO_VERSION_OUTPUT"] = rejected_output
|
|
rejected = subprocess.run(
|
|
["bash", "-c", validation],
|
|
env=environment,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
self.assertNotEqual(0, rejected.returncode)
|
|
|
|
other_directory = Path(temporary_directory) / "other"
|
|
other_directory.mkdir()
|
|
other_hugo = other_directory / "hugo"
|
|
other_hugo.write_text(fake_hugo.read_text(), encoding="utf-8")
|
|
other_hugo.chmod(0o755)
|
|
environment["FAKE_HUGO_VERSION_OUTPUT"] = accepted_outputs[0]
|
|
environment["PATH"] = f"{other_directory}:{environment['PATH']}"
|
|
rejected = subprocess.run(
|
|
["bash", "-c", validation],
|
|
env=environment,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
self.assertNotEqual(0, rejected.returncode)
|
|
self.assertIn("Expected hugo at", rejected.stderr)
|
|
|
|
def test_install_step_rejects_checksum_mismatch(self) -> None:
|
|
workflow = read(
|
|
REPOSITORY_ROOT / ".forgejo" / "workflows" / "deploy-hugo-site.yml"
|
|
)
|
|
installation = workflow_run_block(workflow, "Install pinned Hugo Extended")
|
|
with tempfile.TemporaryDirectory() as temporary_directory:
|
|
temporary_path = Path(temporary_directory)
|
|
fake_bin = temporary_path / "bin"
|
|
fake_bin.mkdir()
|
|
fake_curl = fake_bin / "curl"
|
|
fake_curl.write_text(
|
|
'#!/bin/sh\n'
|
|
'while [ "$#" -gt 0 ]; do\n'
|
|
' if [ "$1" = "--output" ]; then\n'
|
|
' printf junk > "$2"\n'
|
|
' exit 0\n'
|
|
' fi\n'
|
|
' shift\n'
|
|
'done\n'
|
|
'exit 1\n',
|
|
encoding="utf-8",
|
|
)
|
|
fake_curl.chmod(0o755)
|
|
github_path = temporary_path / "github_path"
|
|
github_path.touch()
|
|
environment = os.environ.copy()
|
|
environment.update(
|
|
PATH=f"{fake_bin}:{environment['PATH']}",
|
|
RUNNER_TEMP=temporary_directory,
|
|
GITHUB_PATH=str(github_path),
|
|
HUGO_VERSION=EXPECTED_HUGO_VERSION,
|
|
HUGO_SHA256=EXPECTED_HUGO_SHA256,
|
|
)
|
|
rejected = subprocess.run(
|
|
["bash", "-c", installation],
|
|
env=environment,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
self.assertNotEqual(0, rejected.returncode)
|
|
self.assertIn("FAILED", rejected.stdout)
|
|
self.assertEqual("", github_path.read_text())
|
|
self.assertFalse((temporary_path / f"hugo-{EXPECTED_HUGO_VERSION}" / "hugo").exists())
|
|
|
|
def test_infrastructure_scaffold_uses_expected_hugo_version(self) -> None:
|
|
bootstrap = read(
|
|
INFRASTRUCTURE_ROOT
|
|
/ "ansible"
|
|
/ "tasks"
|
|
/ "bootstrap-hugo-site-from-deployment.yml"
|
|
)
|
|
self.assertIn(f'HUGO_VERSION: "{EXPECTED_HUGO_VERSION}"', bootstrap)
|
|
self.assertNotRegex(bootstrap, r'HUGO_VERSION: "(?!0\.165\.0)[^"]+"')
|
|
|
|
def test_readme_documents_supported_sites_and_version_guarantee(self) -> None:
|
|
readme = read(REPOSITORY_ROOT / "README.md")
|
|
expected_sites = (
|
|
"neilborromeo.com",
|
|
"boholtoday.com",
|
|
"boholcentral.com",
|
|
"komsysnet.com",
|
|
"thefreelancemastery.com",
|
|
)
|
|
documented_sites = re.findall(r"^- `([^`]+)`$", readme, re.MULTILINE)
|
|
|
|
self.assertEqual(list(expected_sites), documented_sites[: len(expected_sites)])
|
|
self.assertIn("Hugo Extended 0.165.0", readme)
|
|
self.assertIn("fails before deployment", readme)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|