Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Exit Codes #42

Merged
merged 8 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ Remember models are just like any other form of digital media, you should scan c
**NOTE**: LLMs are large files, it can take a few minutes to download them before scanning. Expect the process
to take just a few minutes to complete.

##### CLI Exit Codes
The CLI exit status codes are:
- `0`: Scan completed successfully, no vulnerabilities found
- `1`: Scan completed successfully, vulnerabilities found
- `2`: Scan failed, modelscan threw an error while scanning
- `3`: No supported files were passed to the tool
- `4`: Usage error, CLI was passed invalid or incomplete options

### Understanding The Results

Once a scan has been completed you'll see output like this if an issue is found:
Expand Down
84 changes: 57 additions & 27 deletions modelscan/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])


# redefine format_usage so the appropriate command name shows up
class ModelscanCommand(click.Command):
def format_usage(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
pieces = self.collect_usage_pieces(ctx)
formatter.write_usage("modelscan", " ".join(pieces))


@click.command(
context_settings=CONTEXT_SETTINGS,
cls=ModelscanCommand,
help="Modelscan detects machine learning model files that perform suspicious actions",
)
@click.version_option(__version__, "-v", "--version")
Expand Down Expand Up @@ -65,39 +73,61 @@ def cli(
if log is not None:
logger.setLevel(getattr(logging, log))

try:
modelscan = Modelscan()
if path is not None:
pathlibPath = Path().cwd() if path == "." else Path(path).absolute()
if not pathlibPath.exists():
raise FileNotFoundError(f"Path {path} does not exist")
else:
modelscan.scan_path(pathlibPath)
# elif url is not None:
# modelscan.scan_url(url)
elif huggingface is not None:
modelscan.scan_huggingface_model(huggingface)
modelscan = Modelscan()
if path is not None:
pathlibPath = Path().cwd() if path == "." else Path(path).absolute()
if not pathlibPath.exists():
raise FileNotFoundError(f"Path {path} does not exist")
else:
raise click.UsageError(
"Command line must include either a path or a Hugging Face model"
)
ConsoleReport.generate(
modelscan.issues,
modelscan.errors,
modelscan._skipped,
show_skipped=show_skipped,
modelscan.scan_path(pathlibPath)
# elif url is not None:
# modelscan.scan_url(url)
elif huggingface is not None:
modelscan.scan_huggingface_model(huggingface)
else:
raise click.UsageError(
"Command line must include either a path or a Hugging Face model"
)
return 0
ConsoleReport.generate(
modelscan.issues,
modelscan.errors,
modelscan._skipped,
show_skipped=show_skipped,
)

except click.UsageError as e:
click.echo(e)
click.echo(ctx.get_help())
# exit code 3 if no supported files were passed
if not modelscan.scanned:
return 3
# exit code 2 if scan encountered errors
elif modelscan.errors:
return 2
# exit code 1 if scan completed successfully and vulnerabilities were found
elif modelscan.issues.all_issues:
return 1
# exit code 0 if scan completed successfully and no vulnerabilities were found
else:
return 0


def main() -> None:
try:
result = cli.main(standalone_mode=False)

except click.ClickException as e:
click.echo(f"Error: {e}")
with click.Context(cli) as ctx:
click.echo(cli.get_help(ctx))
# exit code 4 for CLI usage errors
result = 4

except Exception as e:
logger.exception(f"Exception: {e}")
return 2
click.echo(f"Exception: {e}")
# exit code 2 if scan throws exceptions
result = 2

finally:
sys.exit(result)


if __name__ == "__main__":
sys.exit(cli())
main()
6 changes: 6 additions & 0 deletions modelscan/modelscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __init__(self) -> None:
self._issues = Issues()
self._errors: List[Error] = []
self._skipped: List[str] = []
self._scanned: List[str] = []

def scan_path(self, path: Path) -> None:
if path.is_dir():
Expand Down Expand Up @@ -124,6 +125,7 @@ def _scan_source(
if extension in scan.supported_extensions():
logger.info(f"Scanning {source} using {scan.name()} model scan")
issues, errors = scan.scan(source=source, data=data)
self._scanned.append(str(source))

self._issues.add_issues(issues)
self._errors.extend(errors)
Expand Down Expand Up @@ -154,6 +156,10 @@ def issues(self) -> Issues:
def errors(self) -> List[Error]:
return self._errors

@property
def scanned(self) -> List[str]:
return self._scanned

@property
def skipped(self) -> List[str]:
return self._skipped
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ packages = [{ include = "modelscan" }]
exclude = ["tests/*", "Makefile"]

[tool.poetry.scripts]
modelscan = "modelscan.cli:cli"
modelscan = "modelscan.cli:main"

[tool.poetry.dependencies]
python = ">=3.8,<3.12"
Expand Down
Loading