1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
#!/usr/bin/env -S uv run --script
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "check-jsonschema",
# "typer >= 0.12.5",
# "typing-extensions",
# ]
# ///
# SEE: https://peps.python.org/pep-0723/
# SEE: https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to-create-an-executable-file
"""
Check a github-workflow YAML file.
RELATED: JSON schema for Github Action workflows
* https://dev.to/robertobutti/vscode-how-to-check-workflow-syntax-for-github-actions-4k0o
- https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
- https://github.com/actions/starter-workflows/tree/master/ci
- https://github.com/actions/starter-workflows/blob/main/ci/python-publish.yml
REQUIRES:
pip install check-jsonschema
DOWNLOAD: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json
USE: check-jsonschema --schemafile github-workflow.json_schema.txt
.github/workflows/release-to-pypi.yml
* https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-action.json
* MAYBE: https://github.com/softprops/github-actions-schemas/blob/master/workflow.json
REQUIRES:
* pip install check-jsonschema
* pip install typer >= 0.12.5
* pip install typing-extensions
GITHUB WORKFLOW SCHEMA:
* https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json
"""
from pathlib import Path
from subprocess import run
from typing import Optional
from typing_extensions import Self
import typer
# -----------------------------------------------------------------------------
# CONSTANTS
# -----------------------------------------------------------------------------
HERE = Path(__file__).parent.absolute()
GITHUB_WORKFLOW_SCHEMA_URL = "https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json"
GITHUB_WORKFLOW_SCHEMA_PATH = HERE/"github-workflow.schema.json"
# -----------------------------------------------------------------------------
# CLASSES:
# -----------------------------------------------------------------------------
class Verdict:
def __init__(self, path: Path, outcome: bool, message: Optional[str] = None):
self.path = path
self.outcome = outcome
self.message = message or ""
@property
def verdict(self):
the_verdict = "FAILED"
if self.outcome:
the_verdict = "OK"
return the_verdict
def as_bool(self):
return bool(self.outcome)
def __bool__(self):
return self.as_bool()
def __str__(self):
return f"{self.verdict}: {self.path} {self.message}".strip()
def __repr__(self):
class_name = self.__class__.__name__
return f"<{class_name}: path={self.path}, verdict={self.verdict}, message='{self.message}'>"
@classmethod
def make_success(cls, path: Path, message: Optional[str] = None) -> Self:
return cls(path, outcome=True, message=message)
@classmethod
def make_failure(cls, path: Path, message: Optional[str] = None) -> Self:
return cls(path, outcome=False, message=message)
def workflow_check(path: Path) -> Verdict:
schema = GITHUB_WORKFLOW_SCHEMA_PATH
print(f"CHECK: {path} ... ")
result = run(["check-jsonschema", f"--schemafile={schema}", f"{path}"])
if result.returncode == 0:
return Verdict.make_success(path)
# -- OTHERWISE:
return Verdict.make_failure(path)
def workflow_check_many(paths: list[Path]) -> list[Verdict]:
verdicts = []
for path in paths:
verdict = workflow_check(path)
verdicts.append(verdict)
return verdicts
def main(paths: list[Path]) -> int:
"""
Check github-workflow YAML file(s).
:param paths: Paths to YAML file(s).
:return: 0, if all checks pass. 1, otherwise
"""
verdicts = workflow_check_many(paths)
count_passed = 0
count_failed = 0
for verdict in verdicts:
# DISABLED: print(str(verdict))
if verdict:
count_passed += 1
else:
count_failed += 1
summary = f"SUMMARY: {len(verdicts)} files, {count_passed} passed, {count_failed} failed"
print(summary)
result = 1
if count_failed == 0:
result = 0
return result
if __name__ == '__main__':
typer.run(main)
|