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
|
#!/usr/bin/env python3
"""
TODO
"""
from __future__ import annotations
import argparse
import json
import os.path
from enum import Enum
from typing import NamedTuple
import ruamel.yaml # type: ignore[import]
class LintSeverity(str, Enum):
ERROR = "error"
WARNING = "warning"
ADVICE = "advice"
DISABLED = "disabled"
class LintMessage(NamedTuple):
path: str | None
line: int | None
char: int | None
code: str
severity: LintSeverity
name: str
original: str | None
replacement: str | None
description: str | None
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="github actions linter",
fromfile_prefix_chars="@",
)
parser.add_argument(
"filenames",
nargs="+",
help="paths to lint",
)
args = parser.parse_args()
for fn in args.filenames:
with open(fn) as f:
contents = f.read()
yaml = ruamel.yaml.YAML() # type: ignore[attr-defined]
try:
r = yaml.load(contents)
except Exception as err:
msg = LintMessage(
path=None,
line=None,
char=None,
code="GHA",
severity=LintSeverity.ERROR,
name="YAML load failure",
original=None,
replacement=None,
description=f"Failed due to {err.__class__.__name__}:\n{err}",
)
print(json.dumps(msg._asdict()), flush=True)
continue
for job_name, job in r.get("jobs", {}).items():
# This filter is flexible, the idea is to avoid catching all of
# the random label jobs that don't need secrets
# TODO: binary might be good to have too, but it's a lot and
# they're autogenerated too
uses = os.path.basename(job.get("uses", ""))
if ("build" in uses or "test" in uses) and "binary" not in uses:
if job.get("secrets") != "inherit":
desc = "missing 'secrets: inherit' field"
if job.get("secrets") is not None:
desc = "has 'secrets' field which is not standard form 'secrets: inherit'"
msg = LintMessage(
path=fn,
line=job.lc.line,
char=None,
code="GHA",
severity=LintSeverity.ERROR,
name="missing secrets: inherit",
original=None,
replacement=None,
description=(f"GitHub actions job '{job_name}' {desc}"),
)
print(json.dumps(msg._asdict()), flush=True)
|