

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 插件开发者指南
<a name="sbomgen-plugin-developer-guide"></a>

 本指南介绍了如何使用自定义 Lua 插件扩展 Amazon Inspector SBOM 生成器（inspector-sbomgen）。插件允许您添加对新包生态系统的支持，而无需修改 sbomgen 的源代码或重新编译。

 有关完整的函数目录，请参阅[插件 API 参考](sbomgen-plugin-api-reference.md)。有关编写测试的指导，请参阅[插件测试指南](sbomgen-plugin-testing-guide.md)。

## 概述
<a name="sbomgen-plugin-developer-guide-overview"></a>

 Sbomgen 插件是用 Lua 编写的，遵循两步流程：
+ **发现** — 扫描工件的文件列表并报告哪些文件与您的生态系统相关。
+ **集合** — 解析每个发现的文件并将软件包发现结果推送到 SBOM 中。

### 插件事件模型
<a name="sbomgen-plugin-developer-guide-plugin-event-model"></a>

 发现插件需要一种方法来告诉集合插件在清单下的工件中发现了包含包元数据的文件。为了便于这种数据共享，发现插件定义了**事件名称**并返回文件路径列表。集合插件**订阅**该事件并接收每个匹配的文件路径。这将文件检测与解析分开——你可以让一个发现插件为多个收集器供电（扇出模式）。但是，每个发现插件必须具有唯一的事件名称，并且每个集合插件必须具有唯一的收集器名称。有关详细信息，请参阅 [插件碰撞规则](#sbomgen-plugin-developer-guide-plugin-collision-rules)。

 开发人员可能会将其视为**观察**者的设计模式。

 这种设计允许单个发现插件以高性能的方式触发多个独立的分析。例如，一个发现插件可以找到工件`requirements.txt`中的每一个，然后提要：
+ 一个**包收集器**，可将每行解析为 SBOM 结果 () `name==version`。
+ 一个**机密收集器**，用于将包含意外固定的 API 密钥或令牌的行标记为版本。
+ 报告未固定或通配符版本说明符的**策略收集器**。

 每个收集器针对同一个文件列表独立运行，无需重新遍历工件的文件系统。这也使插件作者能够添加订阅现有事件的新集合插件，而无需更改相应的发现插件。

## 快速入门：创建新插件
<a name="sbomgen-plugin-developer-guide-quick-start-creating-new-plugins"></a>

 创建新插件的最快方法是使用内置脚手架命令：

```
inspector-sbomgen plugin new
```

 该命令会提示输入插件名称和项目目录。按 Enter 键接受方括号中显示的默认值：

```
Plugin name (identifies the software ecosystem your plugin will inventory, e.g. debian-dpkg, rhel-rpm, python-pip, cmake) [my-custom-ecosystem]: cmake
Project directory [my-sbomgen-plugins]:
```

 你也可以直接传递参数：

```
inspector-sbomgen plugin new --name cmake --path /tmp/custom-plugins
```

### 从一个可行的例子开始
<a name="sbomgen-plugin-developer-guide-starting-with-a-working-example"></a>

 如果你想在编写自己的逻辑之前尝试插件，请使用以下`--with-example`标志创建一个新插件：

```
inspector-sbomgen plugin new --with-example
```

 这将生成一个功能齐全的插件项目，其中包含示例锁文件解析器、测试数据和通过测试。该示例插件可发现`example.lock`文件、解析`name==version`条目并将软件包推送到 SBOM 中。您可以立即运行测试以查看插件系统的运行情况，然后修改代码以针对您的实际生态系统。

 对于包含所有可选覆盖函数（`get_scanner_name`、、`get_event_name``get_scanner_groups`、多事件发现等）的高级脚手架，请使用`--with-overrides`标志（稍后会详细介绍）：

```
inspector-sbomgen plugin new --with-overrides
```

### 完成你的插件
<a name="sbomgen-plugin-developer-guide-completing-your-plugin"></a>

 搭建脚手架后，生成的插件文件包含`TODO`标记，指示在何处添加您的生态系统特定逻辑。仔细研究这些标记，将脚手架变成一个可以正常工作的插件：
+ 用您的实际值替换所有`TODO`标记
+ 更新中的文件模式`discover()`以匹配您的目标文件
+ 在中实现解析逻辑`collect()`并调`sbomgen.push_package()`用每个软件包

### 测试你的插件
<a name="sbomgen-plugin-developer-guide-test-your-plugin"></a>

 有两种方法可以测试你的插件：

 **1。内置测试工具**-`inspector-sbomgen plugin test` 用于在开发过程中验证插件逻辑。这样就可以运行你的`init_test.lua`测试文件，而不需要真正的工件来扫描：

```
inspector-sbomgen plugin test --path ./my-plugins
```

 有关编写测试文件和使用 `testing.*` API 的详细信息，请参阅。[插件测试指南](sbomgen-plugin-testing-guide.md)

 **2。 End-to-end扫描** — 使用标准的 sbomgen 命令调用你的插件，以验证它是否能抵御真实的伪像。对于这种方法，你需要同时提供一个包含插件目标文件的工件（例如，带有`requirements.txt`或等效文件的目录）和插件目录的路径：

```
inspector-sbomgen directory \
    --path /path/to/test/dir \
    --plugin-dir ./my-plugins \
    --disable-native-scanners \
    -o sbom.json
```

 该`--disable-native-scanners`标志可确保只有你的 Lua 插件才能运行，从而更容易在没有内置（本机）扫描仪输出的情况下进行测试。

## IDE 安装程序
<a name="sbomgen-plugin-developer-guide-ide-setup"></a>

 Sbomgen 在 VS Code 中为整个 `sbomgen.*` API 提供代码完成、类型检查和内联文档。

### 使用 Lua 语言服务器的 VS 代码
<a name="sbomgen-plugin-developer-guide-vs-code-with-lua-language-server"></a>
+ [安装 Lua 扩展程序：sumneko.lua](https://marketplace.visualstudio.com/items?itemName=sumneko.lua)
+ 打开插件项目中的任何`.lua`文件

 就是这样！该`plugin new`命令生成`library/sbomgen.lua`，`.vscode/settings.json`并由 Lua 语言服务器自动检测到。你会立即得到：
+ 所有`sbomgen.*`函数的代码完成
+ 带有类型的参数提示
+ 悬停文档
+ 类型检查

## 插件目录结构
<a name="sbomgen-plugin-developer-guide-plugin-directory-structure"></a>

 Sbomgen 发现和收集插件必须遵循以下目录结构：

```
{plugin-dir}/
├── discovery/
│   └── {platform}/
│       └── {category}/
│           └── {ecosystem}/
│               └── init.lua          # REQUIRED entrypoint
└── collection/
    └── {platform}/
        └── {category}/
            └── {ecosystem}/
                └── init.lua          # REQUIRED entrypoint
```

 这些目录名称具有语义含义 — sbomgen 使用它们来派生插件的默认元数据，包括扫描器名称、事件名称、扫描仪组和平台筛选。这减少了开发人员原本必须编写的样板数量。选择正确的值可确保您的插件与 sbomgen 的扫描仪选择和执行模型正确集成。

 以下各节更详细地探讨了目录结构，提供了有关语义含义和惯例的指导。

### 平台
<a name="sbomgen-plugin-developer-guide-platform"></a>

 平台目录控制您的插件在哪些操作系统上运行。


| **值** | **何时使用** | 
| --- | --- | 
| cross-platform | 插件适用于任何操作系统（大多数插件） | 
| linux | Linux 特有的检测逻辑 | 
| windows | Windows 特有的检测逻辑 | 
| macos | 特定于 macOS 的检测逻辑 | 

### 类别
<a name="sbomgen-plugin-developer-guide-category"></a>

 类别目录决定了分配给您的插件的默认扫描器组，该组控制插件是默认运行还是需要明确选择加入。[扫描仪选择](#sbomgen-plugin-developer-guide-scanner-selection)有关分组如何影响执行的信息，请参阅。


| **值** | **默认群组** | **何时使用** | 
| --- | --- | --- | 
| proglang | programming-language-packages, pkg-scanner | 编程语言包（pip、npm、maven 等） | 
| os | os, pkg-scanner | 操作系统包管理器（dpkg、rpm、apk 等） | 
| extra-ecosystems | extra-ecosystems, pkg-scanner | 应用程序和运行时（nginx、curl、wordpress 等） | 

 如果您使用的类别名称与上述任何一个都不匹配，则类别名称本身将用作群组。

### 生态系统
<a name="sbomgen-plugin-developer-guide-ecosystem"></a>

 特定软件包生态系统的名称（例如、`python-pip``python-poetry`、`debian-dpkg`、`curl`）。连字符名称是一种常见的惯例，但不是严格的要求。

 扫描器名称和收集器名称直接源自生态系统目录名称。

### 活动名称配对
<a name="sbomgen-plugin-developer-guide-event-name-pairing"></a>

 位于同一目录路径的发现和收集插件会自动配对。例如，发现插件会`discovery/cross-platform/proglang/python-pip/`自动与配对`collection/cross-platform/proglang/python-pip/`。

 你可以通过在插件`subscribe_to_event()`中定义`get_event_name()`和来覆盖它。

## 探索插件
<a name="sbomgen-plugin-developer-guide-discovery-plugins"></a>

 发现插件只需要该`discover()`功能。所有其他函数都是可选的——默认值来自目录路径。

 大多数发现插件的工作原理是定位名称或路径标识特定生态系统的文件—— `requirements.txt` 例如，Python pip、`package.json` npm 或 Rust carg `Cargo.lock` o。这些`sbomgen.find_files_by_*`函数在 Lua VM 之外执行此匹配，这使得它们比在 Lua 中迭代完整文件列表要快得多：

```
-- REQUIRED: Scans the artifact and returns a table of file paths.
function discover()
    return sbomgen.find_files_by_name({"requirements.txt"})
end
```

 `discover()`必须返回一个包含字符串的 Lua 表（数组）。如果未找到文件，则返回一个空表`{}`。

### 常见的发现模式
<a name="sbomgen-plugin-developer-guide-common-discovery-patterns"></a>


| **Goal** | **推荐功能** | 
| --- | --- | 
| 匹配一个或多个精确的文件名 | sbomgen.find\_files\_by\_name({names}) | 
| 不区分大小写地匹配文件名 | sbomgen.find\_files\_by\_name\_icase({names}) | 
| 按路径后缀进行匹配（例如）/pom.properties | sbomgen.find\_files\_by\_suffix({suffixes}) | 
| 通过全路径正则表达式进行匹配 | sbomgen.find\_files\_by\_path\_regex({patterns}) | 
| Glob 样式的基本名称匹配（例如）\*.lock | sbomgen.glob\_find\_files(pattern) | 

 当您的逻辑需要后置筛选时（例如，保持文件与后缀匹配但不包括生成输出目录），请将`find_files_by_*`调用与 Lua 循环结合起来：

```
function discover()
    local found = {}
    for _, f in ipairs(sbomgen.find_files_by_suffix({".conda-meta.json"})) do
        if not f:match("[/\\]%.cache[/\\]") then
            table.insert(found, f)
        end
    end
    return found
end
```

 除非没有其他匹配器适合，否则请避免`sbomgen.get_file_list()`在发现中——它会将每条路径复制到 Lua VM 中，并且可能需要几秒钟的时间来处理大型工件。有关详细信息，[插件 API 参考](sbomgen-plugin-api-reference.md)请参阅。

### 多事件发现
<a name="sbomgen-plugin-developer-guide-multi-event-discovery"></a>

 默认情况下，返回的所有文件`discover()`都发布到单个事件（来自`get_event_name()`）。如果您的扫描器需要将不同的文件路由到不同的收集器，请改为返回密钥表：

```
function discover()
    return {
        EventNameFoundCurl = sbomgen.find_files_by_name({"curl", "curl.exe"}),
        EventNameFoundLibcurl = sbomgen.find_files_by_name({"curlver.h"}),
    }
end
```

 当`discover()`返回包含字符串键的表时，每个键都被视为单独的事件名称，其值（文件路径表）将发布到该事件。集合插件照常通过`subscribe_to_event()`订阅特定事件。

 这是向后兼容的 — 返回顺序表`{"file1", "file2"}`仍然可以作为单事件模式使用。检测是自动的：任何字符串键的表都是多事件的，只有整数键（或空）的表是单事件的。

 使用多事件时，`get_event_name()`不用于发布（事件名称来自返回的表键）。但是，在插件加载期间仍会调用它进行碰撞检测，因此应返回一个唯一值或省略该值以使用默认值。

### 可选的发现功能
<a name="sbomgen-plugin-developer-guide-optional-discovery-functions"></a>

 所有这些都有从目录路径派生的合理默认值。仅在需要覆盖时才定义它们：


| **函数** | **默认** | **在... 时覆盖** | 
| --- | --- | --- | 
| get\_scanner\_name() | {ecosystem}（例如，python-pip） | 你想要一个自定义的扫描仪名称 | 
| get\_scanner\_description() | "Lua discovery plugin: {ecosystem}" | 你想要一个自定义的描述 | 
| get\_scanner\_groups() | 源自类别目录 | 你需要非标准群组 | 
| get\_event\_name() | 源自目录路径 | 您需要自定义事件路由 | 
| get\_localhost\_scan\_paths() | 无 | 您的插件需要在扫描期间localhost扫描特定的路径 | 

### 本地主机扫描路径
<a name="sbomgen-plugin-developer-guide-localhost-scan-paths"></a>

 当 sbomgen 运行`localhost`扫描时，它会遍历用户指定的目录以及扫描器声明的任何默认路径。默认情况下，Lua 发现插件不提供任何路径，因此用户指定目录之外的文件不会出现在文件列表中。

 定义`get_localhost_scan_paths()`以返回 localhost walker 应包含的目录或文件路径：

```
function get_localhost_scan_paths()
    return {
        "/usr/bin",
        "/usr/local/bin",
    }
end
```

 只有在扫描期间，返回的路径才会附加到步行者的`localhost`扫描列表中，它们对`container``directory`、或`archive`扫描没有影响。

### 特定于平台的扫描路径
<a name="sbomgen-plugin-developer-guide-platform-specific-scan-paths"></a>

 当你关心的文件存放在 Windows、macOS 和 Linux 上的不同位置时，请分支`sbomgen.get_platform()`并返回相应的主机路径：

```
function get_localhost_scan_paths()
    local platform = sbomgen.get_platform()

    if platform == sbomgen.platform.WINDOWS then
        local drive = sbomgen.get_system_drive()
        return {
            drive .. "/Program Files/MyApp/myapp.exe",
            drive .. "/Program Files (x86)/MyApp/myapp.exe",
        }
    end

    if platform == sbomgen.platform.DARWIN then
        return {"/Applications/MyApp.app/Contents/MacOS/myapp"}
    end

    -- Linux
    return {
        "/usr/bin/myapp",
        "/usr/local/bin/myapp",
    }
end
```

 在 Windows 上，使用`sbomgen.get_system_drive()`来解析系统驱动器号（例如`"C:"`），而不是对其进行硬编码。对于从环境变量（如`LOCALAPPDATA`或）派生的路径`PROGRAMFILES`，请迭代`sbomgen.get_env_vars()`并按键查找值。有关详细信息，[插件 API 参考](sbomgen-plugin-api-reference.md)请参阅。

## 集合插件
<a name="sbomgen-plugin-developer-guide-collection-plugins"></a>

 集合插件只需要该`collect()`函数。所有其他功能都是可选的。

 `collect(file_path)`按配对发现插件发现的每个文件调用一次。典型的模式是：
+ 使用`sbomgen.read_file()`（对于加载到内存中的小文件）或`sbomgen.open_file()`（用于@@ **读**取大文件 line-by-line）读取文件内容。
+ **解析**内容 — 简单清单、JSON、`sbomgen.json_decode()` XML 或`sbomgen.search_binary()`编译后的`sbomgen.xml_decode()`二进制文件的字符串匹配。
+ 使用软件包的元数据调用`sbomgen.push_package()`，**发布**每个发现的软件包。

```
-- REQUIRED: Called once per discovered file.
-- Parse the file and call sbomgen.push_package() for each package found.
function collect(file_path)
    local content, err = sbomgen.read_file(file_path)
    if err or not content then return end

    for line in content:gmatch("[^\r\n]+") do
        local name, version = line:match("^([%w%-%_%.]+)==(.+)$")
        if name and version then
            sbomgen.push_package({
                name = name,
                version = version,
                purl_type = "pypi",
                component_type = sbomgen.component_types.LIBRARY,
            })
        end
    end
end
```

 `collect()`不返回值。每个`push_package()`呼叫都需要`name``purl_type`、和`component_type`。[插件 API 参考](sbomgen-plugin-api-reference.md)有关所有支持的字段，请参阅。

### 将元数据附加到组件
<a name="sbomgen-plugin-developer-guide-attaching-metadata-to-components"></a>

 **Sbomgen 支持两种将元数据附加到包组件的方法：**PURL 限定符**和 CycloneDX 属性。**它们有不同的用途，它们之间的选择会影响 Amazon Inspector 如何识别生成的 SBOM 中的漏洞。


| **机制** | **它出现在哪里** | **用于** | 
| --- | --- | --- | 
| qualifiers | 包裹内部 URL（例如pkg:deb/debian/curl@7.88.1?arch=amd64） | 作为包裹身份一部分的数据 | 
| properties | 在 SBOM 的数组中 components[].properties | 不会更改包裹标识方式的描述性元数据 | 

 **建议：对于自定义元数据，首选 CycloneDX 属性（在您自己的命名空间下）。**属性不会更改组件的身份，因此它们不会影响 Amazon Inspector 的漏洞识别。保留 PURL 限定词，以备您的生态系统的 PURL 类型需要它们时使用。

#### PURL 预选赛
<a name="sbomgen-plugin-developer-guide-purl-qualifiers"></a>

 某些 PURL 限定符对 Amazon Inspector 具有语义意义，会影响漏洞识别。例如，在`deb`组件上，Inspector 使用诸如`arch`和之类的限定符`distro`来选择正确的漏洞源；在编译后的二进制文件的`generic`组件上，使用诸如`go_toolchain`或`rust_toolchain`标识所使用的工具链之类的限定符。设置 Inspector 无法识别的限定词，或者忽略它所期望的限定词，可能会导致漏洞被遗漏或归因错误。

 请参阅[什么是包裹 URL？](https://docs.aws.amazon.com/inspector/latest/user/sbom-generator-purl-sbom.html) 在 Amazon Inspector 用户指南中，Inspector 按照 PURL 类型识别限定词惯例。

 通过以下`qualifiers`表格设置预选赛：`sbomgen.push_package()`

```
sbomgen.push_package({
    name = "curl",
    version = "7.88.1",
    purl_type = "deb",
    namespace = "debian",
    component_type = sbomgen.component_types.LIBRARY,
    qualifiers = {
        arch = "amd64",
        distro = "debian-12",
    },
})
```

 仅在符合检查员对 PURL 类型的期望时才设置限定词。如果您需要记录不属于软件包标识的元数据，请改用 CycloneDX 属性。

#### CycloneDX 属性
<a name="sbomgen-plugin-developer-guide-cyclonedx-properties"></a>

 CycloneDX 属性是出现在 SBOM 数组中的键值注释。`components[].properties`它们在不影响组件标识方式的情况下描述组件，因此它们是插件定义的元数据的安全选择。

 **`amazon:inspector:*`命名空间是为 Amazon Inspector 保留的。**具体来说：
+ `amazon:inspector:sbom_generator:*`— 专为 sbomgen 及其内置扫描仪保留。
+ `amazon:inspector:sbom_scanner:*`— 保留给 Amazon Inspector Scan API。

 插件作者不得在这些保留的命名空间内发出密钥。写入它们可能会干扰 Inspector 的行为，并且可能会被覆盖。有关保留密钥的完整列表，请参阅在 Amazon Inspector 中[使用 CycloneDX 命名空间](https://docs.aws.amazon.com/inspector/latest/user/cyclonedx-namespace.html)。

 定义属性时，请使用自己的命名空间（通常是您的组织或插件标识符）：

```
sbomgen.push_package({
    name = "requests",
    version = "2.28.1",
    purl_type = "pypi",
    component_type = sbomgen.component_types.LIBRARY,
    properties = {
        ["acme:python:manifest_path"] = file_path,
        ["acme:python:pinned"] = "true",
        ["acme:python:source"] = "requirements.txt",
    },
})
```

#### 密钥命名规则
<a name="sbomgen-plugin-developer-guide-key-naming-rules"></a>

 sbomgen 按如下方式处理属性密钥：
+ 在 SBOM **中逐字使用包含冒号**的密钥。请务必在密钥中至少包含一个冒号，这样您就可以控制命名空间。
+ **不包含冒号的键会自动加上**前缀 `amazon:inspector:sbom_generator:` — 将其放置在保留的 Inspector 命名空间中。对于自定义属性，请避免使用这种形状。

```
properties = {
    ["acme:my_plugin:detected_via"] = "lockfile",  -- used as-is (recommended)
    detected_via                   = "lockfile",  -- becomes "amazon:inspector:sbom_generator:detected_via" (avoid)
}
```

 这些`sbomgen.properties.*`常量的存在是为了让官方扫描仪在保留的命名空间内发出一致的密钥。它们不是自定义插件的扩展点，而是使用你自己的命名空间。

#### 子组件的属性和限定符
<a name="sbomgen-plugin-developer-guide-properties-and-qualifiers-on-child-components"></a>

 嵌套`children`是独立的组件。每个子项都有自己的`properties`和`qualifiers`表；在父项上设置的元数据不会传播给子代。为每个需要它们的孩子明确设置值。

### 可选的收集功能
<a name="sbomgen-plugin-developer-guide-optional-collection-functions"></a>


| **函数** | **默认** | **在... 时覆盖** | 
| --- | --- | --- | 
| get\_collector\_name() | {ecosystem}（例如，python-pip） | 你想要一个自定义的收藏家名称 | 
| get\_collector\_description() | 空字符串 | 你想要一个描述 | 
| subscribe\_to\_event() | 源自目录路径 | 您需要自定义事件路由 | 

## 运行你的插件
<a name="sbomgen-plugin-developer-guide-running-your-plugins"></a>

 为了让插件生成包元数据，必须为 sbomgen 提供一个要扫描的工件，其中包含您的插件目标文件（例如，包含`requirements.txt``package.json`、或等效软件包清单文件的目录）。

### 基本用法
<a name="sbomgen-plugin-developer-guide-basic-usage"></a>

```
inspector-sbomgen <artifact type> <arguments> --plugin-dir /path/to/plugins
```

 示例：

```
inspector-sbomgen directory --path /target -o /tmp/sbom.json --plugin-dir /path/to/plugins
```

### 禁用本机扫描器（仅限 Lua 模式）
<a name="sbomgen-plugin-developer-guide-with-native-scanners-disabled-lua-only-mode"></a>

```
inspector-sbomgen directory --path /target --plugin-dir /path/to/plugins --disable-native-scanners -o sbom.json
```

### 使用详细日志
<a name="sbomgen-plugin-developer-guide-with-verbose-logging"></a>

```
inspector-sbomgen directory --path /target --plugin-dir /path/to/plugins --verbose -o sbom.json
```

## 列出可用的扫描仪
<a name="sbomgen-plugin-developer-guide-listing-available-scanners"></a>

 `list-scanners`用于查看 sbomgen 可用的所有扫描仪。这包括内置的原生扫描器、任何与 sbomgen 捆绑在一起的官方 Lua 插件，以及您通过以下方式提供的任何自定义 Lua 插件：`--plugin-dir`

```
inspector-sbomgen list-scanners --plugin-dir /path/to/plugins
```

```
┌─────────────────────┬────────┬───────────────────────────────┬─────────────────────────────┐
│    SCANNER NAME     │ SOURCE │            GROUPS             │         DESCRIPTION         │
├─────────────────────┼────────┼───────────────────────────────┼─────────────────────────────┤
│ curl                │ custom │ extra-ecosystems              │ Discovers curl version      │
│                     │        │ pkg-scanner                   │ header files (curlver.h)    │
├─────────────────────┼────────┼───────────────────────────────┼─────────────────────────────┤
│ python-requirements │ custom │ pkg-scanner                   │ Discovers requirements*.txt │
│                     │        │ programming-language-packages │ files for Python pip        │
│                     │        │                               │ packages                    │
└─────────────────────┴────────┴───────────────────────────────┴─────────────────────────────┘
```

 SOURCE 列显示每台扫描仪的来源：


| **源** | **意义** | 
| --- | --- | 
| native | 与 sbomgen 捆绑在一起的内置扫描器 | 
| official | 与 sbomgen 捆绑在一起的 Lua 插件 | 
| custom | 用户提供的 Lua 插件通过以下方式加载 --plugin-dir | 

 在`list-scanners`不运行的情况下运行`--plugin-dir`还包括两者`native`兼而有之，而且`official`扫描仪始终可用。`--plugin-dir`旗帜会将您的`custom`扫描仪添加到列表中。

 要仅列出没有本机扫描仪的 Lua 扫描仪，请执行以下操作：

```
inspector-sbomgen list-scanners --plugin-dir /path/to/plugins --disable-native-scanners
```

## 扫描仪选择
<a name="sbomgen-plugin-developer-guide-scanner-selection"></a>

 Lua 发现插件参与的扫描仪选择模型与内置原生扫描器相同。默认情况下，sbomgen 会运行其组与构件类型的默认扫描仪组匹配的所有扫描仪。你可以用三个标志来覆盖它：

### 仅运行特定的扫描仪
<a name="sbomgen-plugin-developer-guide-run-only-specific-scanners"></a>

 `--scanners`用于仅运行已命名的扫描仪。所有其他扫描仪均不包括在内：

```
inspector-sbomgen directory --path /target \
    --plugin-dir /path/to/plugins \
    --scanners python-requirements \
    -o sbom.json
```

 这只运行`python-requirements`扫描仪。您可以传递多个以逗号分隔的扫描仪名称，也可以传递一个扫描仪组名称（例如`programming-language-packages`）以启用属于该组的每台扫描仪。

### 排除特定的扫描仪
<a name="sbomgen-plugin-developer-guide-exclude-specific-scanners"></a>

 `--skip-scanners`用于在运行其他所有内容时排除已命名的扫描器：

```
inspector-sbomgen directory --path /target \
    --plugin-dir /path/to/plugins \
    --skip-scanners python-poetry \
    -o sbom.json
```

 这将运行除之外的所有默认扫描器`python-poetry`。比如`--scanners`，这个标志也接受组名，因此传递`--skip-scanners programming-language-packages`会禁用该组中的所有扫描器。

**注意**  
`--scanners`并且`--skip-scanners`是相互排斥的。同时传递两者都会产生错误。

### 从非默认组中添加扫描仪
<a name="sbomgen-plugin-developer-guide-add-scanners-from-non-default-groups"></a>

 默认的扫描仪设置取决于要扫描的工件类型（参见[群组如何影响选择](#sbomgen-plugin-developer-guide-how-groups-affect-selection)下面的矩阵）。除非您选择加入，否则其群组不属于该构件类型的默认设置的扫描仪将无法运行。`--additional-scanners`用于在不替换默认设置的情况下将扫描仪附加到默认集合：

```
inspector-sbomgen directory --path /target \
    --plugin-dir /path/to/plugins \
    --additional-scanners my-extra-scanner \
    -o sbom.json
```

 此外，它还会运行工件类型的所有默认扫描器`my-extra-scanner`。该标志接受以逗号分隔的扫描仪名称或组名列表，并与默认设置堆叠在一起，而不是将其替换。`list-scanners`用于检查扫描仪属于哪些组。

### 群组如何影响选择
<a name="sbomgen-plugin-developer-guide-how-groups-affect-selection"></a>

 您的发现插件中的`get_scanner_groups()`功能决定了扫描仪属于哪些组。扫描仪是否在默认情况下运行取决于其组和要扫描的对象类型。下面的矩阵显示了每种工件类型的默认扫描仪集中包含哪些组：


| **Group** | **`directory` / `archive`** | **`container`** | **`localhost`** | **`volume`** | **`binary`** | 
| --- | --- | --- | --- | --- | --- | 
| os | — | ✓ | ✓ | ✓ | — | 
| programming-language-packages | ✓ | ✓ | ✓ | ✓ | — | 
| binary | ✓ | ✓ | — | — | ✓ | 
| extra-ecosystems | — | ✓ | ✓ | ✓ | — | 
| dockerfile | ✓ | ✓ | — | — | — | 
| custom | ✓ | ✓ | ✓ | ✓ | ✓ | 
| certificate | — | — | — | — | — | 
| machine-learning | — | — | — | — | — | 
| pkg-scanner | — | — | — | — | — | 

 ✓ 表示默认情况下，该组中的每台扫描仪都针对该工件类型运行。A `—` 表示该组不在默认设置中，因此只有通过`--scanners`或明确选择该组时，其扫描器才会运行`--additional-scanners`。

 值得注意的细节：
+ **`custom`**始终处于默认设置中 — 通过加载的自定义插件`--plugin-dir`会自动接收`custom`群组，因此无论构件类型如何，它们都会默认运行。
+ **`extra-ecosystems`**`container`、`localhost`和`volume`扫描的默认值为，但不是`directory``archive`、或`binary`扫描的默认值。对于这些类型，您必须传递`--additional-scanners`（按名称或按`extra-ecosystems`组）才能包含它们。
+ **`pkg-scanner`**是信息性的 — 它将扫描仪标记为包裹收集器以供显示`list-scanners`，但它本身并不会导致扫描仪运行。将其与中的执行组（例如`programming-language-packages`）配对`get_scanner_groups()`。

 例如，默认情况下，返回的插件`{sbomgen.groups.EXTRA_ECOSYSTEMS, sbomgen.groups.PACKAGE_COLLECTOR}`将在容器、本地主机和卷扫描中运行，但需要对目录、存档和二进制文件进行扫描`--additional-scanners`（或`--scanners`）。

## 插件碰撞规则
<a name="sbomgen-plugin-developer-guide-plugin-collision-rules"></a>

 Sbomgen 在所有加载的插件中强制使用唯一的元数据，以防止静默覆盖并确保 SBOM 的完整性。当检测到冲突时，会**跳过**后面的插件并记录警告。

### 检查了什么
<a name="sbomgen-plugin-developer-guide-what-is-checked"></a>


| **元数据** | **范围** | **碰撞时** | 
| --- | --- | --- | 
| 发现事件名称 (get\_event\_name) | 所有发现插件 | 第二个插件已跳过 | 
| 扫描仪名称 (get\_scanner\_name) | 所有发现插件 | 第二个插件已跳过 | 
| 收藏家姓名 (get\_collector\_name) | 所有集合插件 | 第二个插件已跳过 | 

### 允许什么
<a name="sbomgen-plugin-developer-guide-what-is-allowed"></a>

 多个集合插件**可以通过**订阅同一个事件`subscribe_to_event()`。这是预期的扇出模式——一个发现插件可以为多个收集器提供信息，每个收集器做不同的事情（例如，一个提取软件包，另一个检测秘密）。

### 避免碰撞
<a name="sbomgen-plugin-developer-guide-avoiding-collisions"></a>

 如果两个插件使用相同的扫描器名称、事件名称或收集器名称，则会跳过加载的第二个插件。要解决冲突，请通过在插件中定义相应的覆盖函数（`get_scanner_name()``get_event_name()`、或`get_collector_name()`）来重命名冲突的元数据。

### 碰撞警告示例
<a name="sbomgen-plugin-developer-guide-collision-warning-example"></a>

```
[custom:python-pip] SKIPPED: discovery event name "EventNameFoundPythonRequirements"
is already registered by [official:python-pip]. Each discovery plugin must have a
unique event name. Rename get_event_name() in your plugin to use a unique name.
```

 该警告告诉你跳过了哪个插件、发生了什么冲突、哪个插件已经拥有该名称以及要更改哪个函数。

## 调试
<a name="sbomgen-plugin-developer-guide-debugging"></a>

### 控制台日志记录
<a name="sbomgen-plugin-developer-guide-console-logging"></a>

 插件可以使用以下函数向 sbomgen 的控制台输出发送消息：


| **函数** | **级别** | **默认情况下可见？** | 
| --- | --- | --- | 
| sbomgen.log\_debug(message) | 调试 | 否 — 需要 --verbose | 
| sbomgen.log\_info(message) | INFO | 是 | 
| sbomgen.log\_warn(message) | 警告 | 是 | 
| sbomgen.log\_error(message) | ERROR | 是 | 

 插件的所有日志输出都自动以插件的源和路径为前缀（例如`[custom:python-pip]`），因此来自不同插件的消息很容易区分。 `log_info``log_warn`、，并`log_error`始终打印；`log_debug`只有在调用 sbomgen 时才会打印。`--verbose`

```
function discover()
    sbomgen.log_info("starting discovery")
    local files = sbomgen.find_files_by_name({"requirements.txt"})
    sbomgen.log_debug(string.format("matched %d files", #files))
    if #files == 0 then
        sbomgen.log_warn("no requirements.txt files found")
    end
    return files
end
```

### 断点
<a name="sbomgen-plugin-developer-guide-breakpoints"></a>

 `sbomgen.breakpoint()`用于暂停插件执行并阻止，直到按下 Enter。这就像一个粗略的调试器——将其与日志语句结合使用以检查特定点的状态。

```
function discover()
    local files = sbomgen.find_files_by_name({"requirements.txt"})

    sbomgen.log_info(string.format("about to inspect %d files", #files))
    sbomgen.breakpoint("before file inspection — press Enter to continue")

    local found = {}
    for _, f in ipairs(files) do
        if not f:match("[/\\]tests[/\\]") then
            table.insert(found, f)
        end
    end

    sbomgen.log_info(string.format("kept %d files after filtering", #found))
    sbomgen.breakpoint("after filtering — press Enter to continue")

    return found
end
```

 断点消息将打印到 stderr。执行会暂停，直到您按下 Enter，这样您就可以有时间查看日志输出。

### 常见问题
<a name="sbomgen-plugin-developer-guide-common-issues"></a>


| **症状** | **原因** | **修复** | 
| --- | --- | --- | 
| 插件未加载 | 缺少 init.lua | 确保入口点存在于正确的目录深度 | 
| “缺少必需的功能” | 函数名称中有错字 | 检查get\_scanner\_name、、、get\_scanner\_description、get\_scanner\_groups、discover、get\_event\_name、get\_localhost\_scan\_paths、get\_collector\_name、collect、subscribe\_to\_event是否已定义 | 
| 从未调用过收藏插件 | 活动名称不匹配 | 验证get\_event\_name()并subscribe\_to\_event()返回相同的字符串 | 
| SBOM 中没有软件包 | push\_package未呼叫或缺少必填字段 | 确保name在每次push\_package通话（包括孩子）中component\_type都设置了、和。purl\_type使用sbomgen.component\_types.\*常量。 | 
| 插件中出现运行时错误 | 执行过程中出现 Lua 错误 | 检查 sbomgen 输出中是否有包含错误详细信息的警告消息 | 
| “已跳过：发现事件名称... 已注册” | 另一个插件使用相同的事件名称 | 重命名get\_event\_name()为唯一值 | 
| “已跳过：扫描仪名称... 已注册” | 另一个插件使用相同的扫描仪名称 | 重命名get\_scanner\_name()为唯一值 | 
| “已跳过：收藏家姓名... 已注册” | 另一个插件使用相同的收集器名称 | 重命名get\_collector\_name()为唯一值 | 

## API 参考
<a name="sbomgen-plugin-developer-guide-api-reference"></a>

 完整的功能目录保存在配套文档中：

 **→ [插件 API 参考](sbomgen-plugin-api-reference.md)** 

 API 参考涵盖了所有`sbomgen.*`函数（文件 I/O、二进制实用程序、包输出、正则表达式、结构化解析、Windows 注册表、日志记录、调试）、测试文件中可用的 `testing.*` API、所有内置常量（、`properties`、`groups``component_types`、`platform`）以及插件生命周期全局变量。

## 错误处理
<a name="sbomgen-plugin-developer-guide-error-handling"></a>

 可能失败的 API 函数返回两个值:`value, err`. 成功时，`err`是`nil`。失败时，第一个值为`nil`且`err`为错误字符串。

```
local content, err = sbomgen.read_file(path)
if err then
    sbomgen.log_error("failed to read " .. path .. ": " .. err)
    return
end
-- content is safe to use here
```

 如果插件引发未处理的 Lua 错误，sbomgen 会记录警告并继续处理下一个文件或插件。其他插件不受影响。

## 沙盒限制
<a name="sbomgen-plugin-developer-guide-sandbox-restrictions"></a>

 插件在沙箱 Lua 虚拟机中运行，标准库访问权限有限：


| **Library** | **可用** | **备注** | 
| --- | --- | --- | 
| base | ✓ | dofile、loadfile、loadstring已移除 | 
| string | ✓ | 全字符串操作 | 
| table | ✓ | 全表操作 | 
| math | ✓ | 完整的数学库 | 
| package | ✓ | require()仅限于插件目录 | 
| io | ✗ | 改用sbomgen.\* I/O 函数 | 
| os | ✗ | 出于安全原因被封锁 | 
| debug | ✗ | 已屏蔽以防止 VM 内省 | 
| coroutine | ✗ | 未加载 | 

 无法通过`io.open`或`os.execute`直接访问文件系统。所有文件操作都必须通过 `sbomgen` API 进行，这可确保跨工件类型的一致行为，并防止插件访问构件之外的文件。

 `require()`只能从插件自己的目录树中加载模块。父目录遍历等被阻止。`require("../shared")`

## 在插件之间共享代码
<a name="sbomgen-plugin-developer-guide-sharing-code-between-plugins"></a>

 你可以使用`require()`从插件的目录中加载帮助模块：

```
my-ecosystem/
├── init.lua
└── helpers.lua
```

```
-- helpers.lua
local M = {}
function M.parse_version(s)
    return string.match(s, "(%d+%.%d+%.%d+)")
end
return M
```

```
-- init.lua
local helpers = require("helpers")

function subscribe_to_event() return "MyEvent" end

function collect(file_path)
    local content, err = sbomgen.read_file(file_path)
    if err then return end
    local version = helpers.parse_version(content)
    -- ...
end
```

 还支持带的`init.lua`子目录：

```
my-ecosystem/
├── init.lua
└── parsers/
    └── init.lua
```

```
local parsers = require("parsers")
```

 `require()`仅限于插件的目录。您无法从其他插件或系统路径加载模块。不支持第三方 Lua 库（例如，来自 LuaRocks），只能加载插件目录中的本地帮助器模块。