feat: standards, shared CI and release tooling for NAPALM drivers
STANDARDS.md binds every repository in the NAPALM org: one default branch, Conventional Commits, the device-types contract, test-driven development, clean-code limits, a quality ratchet for existing code, packaging rules and releases by tag to PyPI. napalm-standard holds what a tool can check: the conformance check (with release rules on a tag), the quality ratchet (ported from netOrk's quality_ratchet.py), and the Gitea release from the CHANGELOG section. .gitea/workflows/driver.yml is the CI every driver calls; template/ is a new driver's starting point and passes the check as it is.
This commit is contained in:
commit
db6b2e4ea7
37 files changed
+2894
No files matched your search
@@ -0,0 +1,101 @@
|
||||
"""napalm-standard: the commands the shared workflow runs in every driver repo.
|
||||
|
||||
napalm-standard check [--tag vX.Y.Z] [PATH] does the repo meet the standard?
|
||||
napalm-standard ratchet [--write] [PATH] hold the quality baseline, or rewrite it
|
||||
napalm-standard notes vX.Y.Z [PATH] print that version's CHANGELOG section
|
||||
napalm-standard release vX.Y.Z [PATH] create or update the Gitea release;
|
||||
reads GITEA_URL, GITEA_REPOSITORY, GITEA_TOKEN
|
||||
napalm-standard package [PATH] print the import package (for mypy, bandit, cov)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
from napalm_standard import ratchet
|
||||
from napalm_standard.changelog import changelog_section
|
||||
from napalm_standard.check import Repo, check_repo
|
||||
from napalm_standard.release import gitea_api, publish
|
||||
|
||||
_RELEASE_ENV = ("GITEA_URL", "GITEA_REPOSITORY", "GITEA_TOKEN")
|
||||
|
||||
|
||||
def _repo(path: pathlib.Path) -> Repo:
|
||||
return Repo(path, tomllib.loads((path / "pyproject.toml").read_text()))
|
||||
|
||||
|
||||
def _check(args: argparse.Namespace) -> int:
|
||||
findings = check_repo(args.path, tag=args.tag)
|
||||
for finding in findings:
|
||||
print(finding)
|
||||
if findings:
|
||||
print(f"{len(findings)} finding(s); see STANDARDS.md in NAPALM/driver-standard")
|
||||
return 1
|
||||
print("meets the standard")
|
||||
return 0
|
||||
|
||||
|
||||
def _ratchet(args: argparse.Namespace) -> int:
|
||||
code, text = ratchet.run(args.path, _repo(args.path).package, write=args.write)
|
||||
print(text)
|
||||
return code
|
||||
|
||||
|
||||
def _notes(args: argparse.Namespace) -> int:
|
||||
section = changelog_section((args.path / "CHANGELOG.md").read_text(), args.tag)
|
||||
if section is None:
|
||||
return 1
|
||||
print(section)
|
||||
return 0
|
||||
|
||||
|
||||
def _release(args: argparse.Namespace) -> int:
|
||||
missing = [name for name in _RELEASE_ENV if not os.environ.get(name)]
|
||||
if missing:
|
||||
print(f"missing environment: {', '.join(missing)}", file=sys.stderr)
|
||||
return 2
|
||||
api = gitea_api(*(os.environ[name] for name in _RELEASE_ENV))
|
||||
changelog = (args.path / "CHANGELOG.md").read_text()
|
||||
result = publish(args.tag, _repo(args.path).name, changelog, api)
|
||||
print(f"release {result.get('id')} for {args.tag}")
|
||||
return 0
|
||||
|
||||
|
||||
def _package(args: argparse.Namespace) -> int:
|
||||
print(_repo(args.path).package)
|
||||
return 0
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="napalm-standard",
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
commands = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
def command(name: str, handler: object) -> argparse.ArgumentParser:
|
||||
sub = commands.add_parser(name)
|
||||
sub.set_defaults(handler=handler)
|
||||
return sub
|
||||
|
||||
check = command("check", _check)
|
||||
check.add_argument("--tag", help="also apply the release rules for this tag")
|
||||
ratchet_cmd = command("ratchet", _ratchet)
|
||||
ratchet_cmd.add_argument("--write", action="store_true", help="record today's state")
|
||||
for name, handler in (("notes", _notes), ("release", _release)):
|
||||
command(name, handler).add_argument("tag")
|
||||
command("package", _package)
|
||||
for sub in commands.choices.values():
|
||||
sub.add_argument("path", nargs="?", type=pathlib.Path, default=pathlib.Path("."))
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = _parser().parse_args(argv)
|
||||
code: int = args.handler(args)
|
||||
return code
|
||||
Reference in new issue
Block a user