Coverage for .tox/coverage/lib/python3.13/site-packages/wuttatell/cli/audit.py: 100%
44 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-10-23 19:04 -0500
« prev ^ index » next coverage.py v7.16.1, created at 2026-10-23 19:04 -0500
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# WuttaTell -- Telemetry submission for Wutta Framework
5# Copyright © 2025-2026 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24See also: :ref:`wutta-audit`
25"""
27import logging
28import os
29import subprocess
30import sys
31import tempfile
33try:
34 from typing import Annotated
35except ImportError: # pragma: no cover
36 from typing_extensions import Annotated
38import typer
40from wuttjamaican.cli import wutta_typer
42log = logging.getLogger(__name__)
45@wutta_typer.command()
46def audit(
47 ctx: typer.Context,
48 path: Annotated[
49 str,
50 typer.Option("--path", help="Restrict audit to the given path."),
51 ] = None,
52 quiet: Annotated[
53 bool,
54 typer.Option("--quiet", "-q", help="Suppress output unless audit fails."),
55 ] = False,
56):
57 """
58 Run the 'pip-audit' command and report findings
59 """
60 config = ctx.parent.wutta_config
62 # TODO: how should we be doing this
63 pip_audit = os.path.join(sys.prefix, "bin", "pip-audit")
64 if not os.path.exists(pip_audit):
65 raise FileNotFoundError(f"pip-audit not found: {pip_audit}")
67 cmd = [pip_audit]
68 if path:
69 cmd.extend(["--path", path])
71 if ignored := config.get_list(f"{config.appname}.pip_audit.ignored_vulns"):
72 for vuln in ignored:
73 cmd.extend(["--ignore-vuln", vuln])
75 log.debug("will run pip-audit command: %s", cmd)
77 if quiet:
78 fd, temp_out = tempfile.mkstemp()
79 os.close(fd)
80 fd, temp_err = tempfile.mkstemp()
81 os.close(fd)
83 cmd.extend(["--progress-spinner=off", "--output", temp_out])
84 try:
85 with open(temp_err, "wb") as ferr:
86 subprocess.run(cmd, check=True, stderr=ferr)
88 except subprocess.CalledProcessError as err:
89 with open(temp_err, "rt", encoding="utf_8") as f:
90 sys.stderr.write(f.read())
91 with open(temp_out, "rt", encoding="utf_8") as f:
92 sys.stderr.write(f.read())
93 sys.stderr.write("\n")
94 sys.exit(err.returncode)
96 finally:
97 os.remove(temp_out)
98 os.remove(temp_err)
100 sys.exit(0)
102 else: # not quiet, so just run and exit w/ same code
103 result = subprocess.run(cmd, check=False)
104 sys.exit(result.returncode)