File: gha_linter.py

package info (click to toggle)
pytorch-cuda 2.6.0%2Bdfsg-7
  • links: PTS, VCS
  • area: contrib
  • in suites: forky, sid, trixie
  • size: 161,620 kB
  • sloc: python: 1,278,832; cpp: 900,322; ansic: 82,710; asm: 7,754; java: 3,363; sh: 2,811; javascript: 2,443; makefile: 597; ruby: 195; xml: 84; objc: 68
file content (95 lines) | stat: -rw-r--r-- 2,810 bytes parent folder | download | duplicates (3)
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)