-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Generate SBOM for a single file (#330)
- Loading branch information
Showing
5 changed files
with
174 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# Copyright 2025 Lawrence Livermore National Security, LLC | ||
# See the top-level LICENSE file for details. | ||
# | ||
# SPDX-License-Identifier: MIT | ||
import json | ||
import os | ||
import pathlib | ||
|
||
import click | ||
|
||
|
||
# pylint: disable=too-few-public-methods | ||
class SpecimenConfigParamType(click.Path): | ||
""" | ||
A custom Click parameter type for handling configuration paths. | ||
This class extends `click.Path` to provide additional functionality for | ||
handling different types of configuration paths, including files, directories, | ||
and JSON configuration files. | ||
Attributes: | ||
name (str): The name of the parameter type, set to "config". | ||
Methods: | ||
convert(value, param, ctx): | ||
Converts the input value based on its prefix and returns the appropriate | ||
configuration data. Supports the following prefixes: | ||
- "file:" for file paths | ||
- "dir:" for directory paths | ||
- "config:" for JSON configuration files | ||
If no prefix is provided, it attempts to determine if the value is a file | ||
and loads it as JSON if possible. Otherwise, it treats the value as a | ||
directory path. | ||
""" | ||
|
||
name = "specimen_config" | ||
|
||
@staticmethod | ||
def _get_param_type(filename: str): | ||
"""Determines the type and properties of a parameter based on its filename prefix. | ||
Args: | ||
filename (str): The filename string to analyze, optionally with a type prefix | ||
Returns: | ||
tuple: A 3-tuple containing: | ||
- str: The parameter type ('FILE', 'DIR', 'CONFIG', or '' for default) | ||
- Path: The filepath as a Path object | ||
- Path or None: The install prefix path (for files), directory path (for dirs), | ||
or None (for config or default) | ||
""" | ||
|
||
if filename.startswith("file:"): | ||
filepath = pathlib.Path(filename[5:]) | ||
return "FILE", filepath, filepath.parent | ||
if filename.startswith("dir:"): | ||
filepath = pathlib.Path(filename[4:]) | ||
return "DIR", filepath, filepath | ||
if filename.startswith("config:"): | ||
filepath = pathlib.Path(filename[7:]) | ||
return "CONFIG", filepath, None | ||
return "", pathlib.Path(filename), None | ||
|
||
def convert(self, value, param, ctx): | ||
# value received may already be the right type | ||
if isinstance(value, list): | ||
return value | ||
|
||
param_type, filepath, installprefix = self._get_param_type(value) | ||
|
||
# validate filepath exists and is readable | ||
if not filepath.exists(): | ||
self.fail(f"{value!r} does not exist", param, ctx) | ||
if not os.access(filepath, os.R_OK): | ||
self.fail(f"{value!r} is not readable", param, ctx) | ||
|
||
# no explicit type given, use heuristics to determine correct type | ||
if not param_type: | ||
# if it's a file (that ends in .json) then treat it as a CONFIG file | ||
# if it's not a file then probably a directory (or something odd...) | ||
if filepath.is_file(): | ||
if filepath.suffix.lower() == ".json": | ||
param_type = "CONFIG" | ||
else: | ||
param_type = "FILE" | ||
installprefix = filepath.parent | ||
else: | ||
param_type = "DIR" | ||
installprefix = filepath | ||
|
||
if param_type in ("FILE", "DIR"): | ||
# avoid a relative directory of "./" as the install prefix | ||
if not installprefix.is_absolute() and len(installprefix.parts) == 0: | ||
installprefix = "" | ||
else: | ||
installprefix = installprefix.as_posix() | ||
# emulate a configuration file with the given path | ||
config = [{"extractPaths": [filepath.as_posix()], "installPrefix": installprefix}] | ||
elif param_type in ("CONFIG"): | ||
with click.open_file(filepath) as f: | ||
try: | ||
config = json.load(f) | ||
except json.decoder.JSONDecodeError as err: | ||
self.fail( | ||
f"{filepath.as_posix()!r} config file contains invalid JSON", param, ctx | ||
) | ||
|
||
for entry in config: | ||
extract_path = entry["extractPaths"] | ||
for pth in extract_path: | ||
extract_path_convert = pathlib.Path(pth) | ||
if not extract_path_convert.exists(): | ||
self.fail(f"invalid extract path in config file: {pth}", param, ctx) | ||
else: | ||
self.fail(f"{value!r} is not a valid specimen config type", param, ctx) | ||
|
||
return config |