mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-08-10 08:27:25 +00:00
fix(ci): harden publishing and validate examples
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import json
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
WORKFLOW_DIR = ROOT / ".github" / "workflows"
|
||||
FULL_ACTION_SHA = re.compile(r"^[^@\s]+@[0-9a-f]{40}$")
|
||||
|
||||
|
||||
class WorkflowSecurityTests(unittest.TestCase):
|
||||
@staticmethod
|
||||
def workflows():
|
||||
return sorted(WORKFLOW_DIR.glob("*.y*ml"))
|
||||
|
||||
def test_all_actions_are_immutable_with_version_comments(self):
|
||||
failures = []
|
||||
for workflow in self.workflows():
|
||||
for number, line in enumerate(workflow.read_text(encoding="utf-8").splitlines(), 1):
|
||||
match = re.search(r"\buses:\s*([^\s#]+)(?:\s+#\s*(\S+))?", line)
|
||||
if not match:
|
||||
continue
|
||||
action, version = match.groups()
|
||||
if not FULL_ACTION_SHA.fullmatch(action) or not version or not version.startswith("v"):
|
||||
failures.append(f"{workflow.name}:{number}: {line.strip()}")
|
||||
self.assertEqual(failures, [])
|
||||
|
||||
def test_checkout_never_persists_credentials(self):
|
||||
failures = []
|
||||
for workflow in self.workflows():
|
||||
lines = workflow.read_text(encoding="utf-8").splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
if "uses: actions/checkout@" not in line:
|
||||
continue
|
||||
step = "\n".join(lines[index : index + 8])
|
||||
if "persist-credentials: false" not in step:
|
||||
failures.append(f"{workflow.name}:{index + 1}")
|
||||
self.assertEqual(failures, [])
|
||||
|
||||
def test_every_workflow_declares_permissions(self):
|
||||
failures = []
|
||||
for workflow in self.workflows():
|
||||
text = workflow.read_text(encoding="utf-8")
|
||||
before_jobs = text.split("\njobs:", 1)[0]
|
||||
if not re.search(r"(?m)^permissions:", before_jobs):
|
||||
failures.append(workflow.name)
|
||||
self.assertEqual(failures, [])
|
||||
|
||||
def test_release_and_preview_separate_read_only_builds_from_writes(self):
|
||||
expectations = {
|
||||
"auto-release.yml": ("release",),
|
||||
"preview-pdf.yml": ("publish",),
|
||||
}
|
||||
for name, write_jobs in expectations.items():
|
||||
text = (WORKFLOW_DIR / name).read_text(encoding="utf-8")
|
||||
self.assertRegex(text, r"(?ms)^ build:\n permissions:\n contents: read\b", name)
|
||||
for job in write_jobs:
|
||||
self.assertRegex(
|
||||
text,
|
||||
rf"(?ms)^ {job}:.*?permissions:\n contents: write\b.*?needs: build\b",
|
||||
name,
|
||||
)
|
||||
|
||||
def test_downloads_dependencies_and_link_checker_are_integrity_pinned(self):
|
||||
combined = "\n".join(path.read_text(encoding="utf-8") for path in self.workflows())
|
||||
link_text = (WORKFLOW_DIR / "check-link.yml").read_text(encoding="utf-8")
|
||||
|
||||
self.assertNotRegex(combined, r"npm install\s+-g\s+@mermaid-js/mermaid-cli")
|
||||
self.assertIn("PANDOC_SHA256", combined)
|
||||
self.assertIn("KUBECONFORM_SHA256", combined)
|
||||
self.assertIn("ACTIONLINT_SHA256", combined)
|
||||
self.assertRegex(link_text, r"dkhamsing/awesome_bot@sha256:[0-9a-f]{64}")
|
||||
|
||||
def test_mermaid_is_exact_and_lockfile_backed(self):
|
||||
package = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
|
||||
version = package["devDependencies"]["@mermaid-js/mermaid-cli"]
|
||||
|
||||
self.assertRegex(version, r"^\d+\.\d+\.\d+$")
|
||||
self.assertTrue((ROOT / "package-lock.json").is_file())
|
||||
ignored = {
|
||||
line.strip()
|
||||
for line in (ROOT / ".gitignore").read_text(encoding="utf-8").splitlines()
|
||||
if line.strip() and not line.lstrip().startswith("#")
|
||||
}
|
||||
self.assertNotIn("package-lock.json", ignored)
|
||||
self.assertTrue(all("npm ci" in path.read_text(encoding="utf-8") for path in self.workflows() if path.name != "check-link.yml" and path.name != "dependabot-automerge.yml"))
|
||||
|
||||
def test_artifacts_are_smoke_tested_and_html_failures_are_not_silent(self):
|
||||
verifier = ROOT / "tools" / "verify_artifacts.py"
|
||||
self.assertTrue(verifier.is_file())
|
||||
for name in ("auto-release.yml", "ci.yaml", "preview-pdf.yml"):
|
||||
text = (WORKFLOW_DIR / name).read_text(encoding="utf-8")
|
||||
self.assertIn("tools/verify_artifacts.py", text, name)
|
||||
self.assertIn("SHA256SUMS", text, name)
|
||||
auto_release = (WORKFLOW_DIR / "auto-release.yml").read_text(encoding="utf-8")
|
||||
self.assertNotIn("continue-on-error: true", auto_release)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user