Renovate 自定义数据源(Custom Datasource)完全指南:用 HTTP(S) 通用端点驱动任意依赖的版本升级
【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovate
导读
本文聚焦 Renovate 的custom数据源(datasource),讲解如何通过customDatasources配置项,从任意 HTTP(S) 通用端点(或本地file://文件)获取版本数据,从而让 Renovate 为那些没有现成数据源支持的软件自动升级依赖。你将掌握defaultRegistryUrlTemplate、format、transformTemplates三大核心参数的用法,五种响应格式(json/plain/yaml/toml/html)的转换规则,以及 K3s、Hashicorp、Grafana Dashboard、nginx 目录列表等真实场景的完整配置方案,最终能独立为任意软件编写自定义数据源。
一、认识 Custom Datasource:Renovate 的"万能数据源"
Renovate 内置了大量数据源(如 npm、maven、docker 等),但在真实项目中,总有一些依赖的版本信息来自"非标准"位置:某个自建 API、一个简单的目录列表页面、甚至一个纯文本文件。custom数据源就是为这类场景设计的通用方案。
在 lib/modules/datasource/custom/index.ts 中,CustomDatasource被定义为static readonly id = 'custom',同时设置了customRegistrySupport = true,意味着它允许在依赖提取时通过registryUrl覆盖默认的 registry 地址。它的工作模式非常简单:
- 从配置中取出该数据源的
defaultRegistryUrlTemplate、format与transformTemplates; - 按
format选择对应的 fetcher(html/json/plain/toml/yaml)发起 HTTP 请求或读取本地文件; - 依次执行每条
transformTemplates中的 JSONata 表达式,前一条的输出作为后一条的输入; - 用 Zod schema 校验最终结果,通过后返回标准的
ReleaseResult结构。
整个流程在getReleases方法(index.ts)中清晰可见。配置解析逻辑则在 utils.ts 的getCustomConfig/massageCustomDatasourceConfig中:datasource: "custom.xxx"中的custom.前缀会被剥离,剩下部分作为customDatasources对象中的键名去查找对应配置。
二、核心配置项与模板变量
2.1 配置项速查表
customDatasources是一个"数据源名 -> 配置对象"的记录(record),其中每个数据源配置支持以下选项:
| option | 默认值 | 说明 |
|---|---|---|
defaultRegistryUrlTemplate | "" | 当查找新版本时未提供registryUrl时使用的 URL 模板 |
format | "json" | API 响应格式。可选值:html、json、plain、toml、yaml |
transformTemplates | [] | 用于转换 API 输出的 JSONata 规则 列表,每条规则依次执行,结果作为下一条的输入 |
从 lib/config/types.ts 的CustomDatasourceConfig定义可以确认,format的合法取值被限定为html、json、plain、toml、yaml五种,且三个字段均为可选。
2.2 模板变量
在defaultRegistryUrlTemplate与transformTemplates中都可以使用 Handlebars 风格模板,可用变量为:
packageName—— 当前依赖的包名;currentValue—— 当前依赖版本值。
模板编译发生在 utils.ts:template.compile(registryUrlTemplate, templateInput)会在每次查找版本时将{ packageName, currentValue }注入模板。这意味着同一个自定义数据源可以通过 URL 模板为不同包服务,例如 Hashicorp 示例中的https://api.releases.hashicorp.com/v1/releases/{{packageName}}?license_class=oss。
提示:JSONata 表达式本身也可以用模板变量,例如
transformTemplates: ['{{packageName}}'],用于从多层嵌套的 JSON 中取出与包名对应的节点(测试用例见 index.spec.ts)。建议使用 JSONata Exerciser 在线调试你的规则。
三、五种响应格式详解
format决定 fetcher 如何解析 HTTP 响应体。fetcher 的注册表位于 formats/index.ts,五种格式都实现了统一的fetch(http, registryURL)与readFile(registryURL)接口(见 formats/types.ts),因此它们既可以请求远程 URL,也可以读取本地文件。
3.1 JSON(默认)
当format为json时,响应体被直接解析为 JSON 并交给转换规则处理(实现见 formats/json.ts)。最理想的情况是 API 本身就返回 Renovate 的ReleaseResult结构,此时无需任何转换即可直接使用(测试用例见 index.spec.ts)。
3.2 Plain(纯文本)
当format为plain时,Renovate 会以Accept: text/plain头调用 HTTP 端点(实现见 formats/plain.ts),响应体被当作纯文本,按换行分割、每行 trim 后作为一条版本:
1.0.0 2.0.0 3.0.0会被转换为:
{ "releases": [ { "version": "1.0.0" }, { "version": "2.0.0" }, { "version": "3.0.0" } ] }从源码看,convertLinesToVersions对每行执行line.trim(),因此带空白的行也能被正确解析(对应测试见 index.spec.ts)。转换完成后,transformTemplates中的 JSONata 规则会照常继续处理。
3.3 YAML
当format为yaml时,响应体通过parseSingleYaml解析并转换为 JSON 供后续处理(实现见 formats/yaml.ts)。例如:
releases: - version: 1.0.0 - version: 2.0.0 - version: 3.0.0会被转换为:
{ "releases": [ { "version": "1.0.0" }, { "version": "2.0.0" }, { "version": "3.0.0" } ] }之后同样应用transformTemplates中的 JSONata 规则。
3.4 TOML
当format为toml时,响应体按 TOML 解析(实现见 formats/toml.ts)。下面的 TOML 文档:
[[releases]] version = "1.0.0" [[releases]] version = "2.0.0" [[releases]] version = "3.0.0"会被转换为:
{ "releases": [ { "version": "1.0.0" }, { "version": "2.0.0" }, { "version": "3.0.0" } ] }随后应用transformTemplates中的 JSONata 规则。
3.5 HTML
当format为html时,Renovate 以Accept: text/html头调用端点,将响应体作为 HTML 文档解析,提取页面中所有<a>链接的href属性作为版本(实现见 formats/html.ts)。例如:
<html> <body> <a href="package-1.0.tar.gz">package-1.0.tar.gz</a> <a href="package-2.0.tar.gz">package-2.0.tar.gz</a> </body> </html>会生成:
{ "releases": [ { "version": "package-1.0.tar.gz" }, { "version": "package-1.0.tar.gz" } ] }注意:
HtmlFetcher的extractLinks会特殊处理<pre>块——先解析<pre>内部的文本,再提取其中的链接。这是为了兼容 nginx 等 Web 服务器把目录列表包裹在<pre>标签中的情况(源码注释与测试用例均验证了这一点,见 formats/html.ts 与 index.spec.ts)。测试还覆盖了畸形 HTML 与不完整 HTML(如<a>标签未闭合),解析器均能容错处理。
转换完成后,JSONata 规则照常应用。由于提取到的是完整文件名(如package-1.0.tar.gz)而非纯净的版本号,通常需要用extractVersion或 JSONata 规则进一步提取版本号。
四、结果数据结构:Renovate 期望的输出格式
无论采用哪种格式、经过多少层转换,最终结果都必须符合 Renovate 的ReleaseResult结构,由 schema.ts 中的ReleaseResultZod校验。
最小可用结构:
{ "releases": [ { "version": "v1.1.0" }, { "version": "v1.2.0" } ] }全部可选字段:
{ "releases": [ { "version": "v1.0.0", "isDeprecated": true, "releaseTimestamp": "2022-12-24T18:21Z", "changelogUrl": "https://github.com/demo-org/demo/blob/main/CHANGELOG.md#v0710", "sourceUrl": "https://github.com/demo-org/demo", "sourceDirectory": "monorepo/folder", "digest": "c667f758f9e46e1d8111698e8d3a181c0b10f430", "isStable": true } ], "sourceUrl": "https://github.com/demo-org/demo", "sourceDirectory": "monorepo/folder", "changelogUrl": "https://github.com/demo-org/demo/blob/main/CHANGELOG.md", "homepage": "https://demo.org" }各字段说明(依据 schema.ts 的 Zod 定义):
| 字段 | 层级 | 说明 |
|---|---|---|
version | releases[] 必填 | 版本号字符串,必填 |
isDeprecated | releases[] 可选 | 该版本是否已废弃 |
releaseTimestamp | releases[] 可选 | 发布时间戳,需符合MaybeTimestamp校验 |
changelogUrl | releases[] 可选 | 该版本对应的变更日志 URL |
sourceUrl | releases[] 可选 | 源码仓库 URL |
sourceDirectory | releases[] 可选 | 源码仓库内子目录(适用于 monorepo) |
digest | releases[] 可选 | 摘要值,会被重命名为newDigest供后续使用 |
isStable | releases[] 可选 | 是否稳定版 |
tags | 顶层可选 | 标签到版本的映射,如{"latest": "v1.0.0"} |
sourceUrl/sourceDirectory/changelogUrl/homepage | 顶层可选 | 整体仓库级元数据 |
值得注意的细节:schema 在解析时会执行transform,把digest字段重命名为newDigest(schema.ts),因此数据源可以直接在releases中提供digest来支持 digest 型更新;而CustomDatasource.getDigest方法默认返回null,正是为了"让 digest 由 getReleases 提供"(见 index.ts 及对应测试 index.spec.ts)。
另外,schema 解析成功后会执行structuredClone(parsed)返回结果;若校验失败,则记录 debug 日志与 trace 日志并返回null,不会抛出异常中断流程。
五、端到端示例:customDatasources + regex 自定义管理器
custom数据源通常与 regex 自定义管理器 配合使用:regex manager 负责从文件中提取依赖声明,custom datasource 负责查询版本。下面以更新k3s.version文件为例:
{ "customManagers": [ { "customType": "regex", "managerFilePatterns": ["/k3s.version/"], "matchStrings": ["(?<currentValue>\\S+)"], "depNameTemplate": "k3s", "versioningTemplate": "semver-coerced", "datasourceTemplate": "custom.k3s" } ], "customDatasources": { "k3s": { "defaultRegistryUrlTemplate": "https://update.k3s.io/v1-release/channels", "transformTemplates": [ "{\"releases\":[{\"version\": $$.(data[id = 'stable'].latest),\"sourceUrl\":\"https://github.com/k3s-io/k3s\",\"changelogUrl\":$join([\"https://github.com/k3s-io/k3s/releases/tag/\",data[id = 'stable'].latest])}],\"sourceUrl\": \"https://github.com/k3s-io/k3s\",\"homepage\": \"https://k3s.io/\"}" ] } } }整个依赖查找链路为:regex manager 从k3s.version文件匹配出currentValue→datasourceTemplate: "custom.k3s"指定数据源 →getCustomConfig剥离custom.前缀找到k3s配置 → 以json格式请求 K3s 频道 API → JSONata 从返回数据中取出stable频道的latest版本,并拼接出changelogUrl→ 校验后返回releases数组。
六、真实场景实战案例
6.1 跟踪 K3s 最新稳定版
单独使用customDatasources即可查询 K3s 最新稳定版(与上节配合 regex manager 使用效果更佳):
{ "customDatasources": { "k3s": { "defaultRegistryUrlTemplate": "https://update.k3s.io/v1-release/channels", "transformTemplates": [ "{\"releases\":[{\"version\": $$.(data[id = 'stable'].latest),\"sourceUrl\":\"https://github.com/k3s-io/k3s\",\"changelogUrl\":$join([\"https://github.com/k3s-io/k3s/releases/tag/\",data[id = 'stable'].latest])}],\"sourceUrl\": \"https://github.com/k3s-io/k3s\",\"homepage\": \"https://k3s.io/\"}" ] } } }6.2 追踪 Hashicorp 产品线(Nomad、Vault、Terraform 等)
Hashicorp 为所有产品提供了统一的 release API,配合{{packageName}}模板变量即可用一份配置覆盖所有产品:
{ "customManagers": [ { "customType": "regex", "managerFilePatterns": ["/\\.yml$/"], "datasourceTemplate": "custom.hashicorp", "matchStrings": [ "#\\s*renovate:\\s*(datasource=(?<datasource>.*?) )?depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?\\s*\\w*:\\s*(?<currentValue>.*)\\s" ], "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}" } ], "customDatasources": { "hashicorp": { "defaultRegistryUrlTemplate": "https://api.releases.hashicorp.com/v1/releases/{{packageName}}?license_class=oss", "transformTemplates": [ "{ \"releases\": $map($, function($v) { { \"version\": $v.version, \"releaseTimestamp\": $v.timestamp_created, \"changelogUrl\": $v.url_changelog, \"sourceUrl\": $v.url_source_repository } }), \"homepage\": $[0].url_project_website, \"sourceUrl\": $[0].url_source_repository }" ] } } }要让 Ansible 变量文件中的 Nomad 版本保持最新,在上述配置之外,只需在 YAML 中加入 renvoate 注释指令:
# renovate: depName=nomad nomad_version: 1.6.0这里的{{packageName}}会被替换为nomad,从而请求 Nomad 的 release API。
6.3 升级 Grafana Helm chart 中的 Dashboard
Grafana Dashboard 的版本号存于 Helm chart 的values.yaml(形如gnetId+revision),可通过自定义数据源查询 Grafana API:
{ "customManagers": [ { "customType": "regex", "managerFilePatterns": ["/\\.yml$/"], "matchStrings": [ "#\\s+renovate:\\s+depName=\"(?<depName>.*)\"\\n\\s+gnetId:\\s+(?<packageName>.*?)\\n\\s+revision:\\s+(?<currentValue>.*)" ], "versioningTemplate": "regex:^(?<major>\\d+)$", "datasourceTemplate": "custom.grafana-dashboards" } ], "customDatasources": { "grafana-dashboards": { "defaultRegistryUrlTemplate": "https://grafana.com/api/dashboards/{{packageName}}", "format": "json", "transformTemplates": [ "{\"releases\":[{\"version\": $string(revision)}]}" ] } } }Grafana Helm chartvalues.yaml中的对应片段:
dashboards: default: 1860-node-exporter-full: # renovate: depName="Node Exporter Full" gnetId: 1860 revision: 31 datasource: Prometheus 15760-kubernetes-views-pods: # renovate: depName="Kubernetes / Views / Pods" gnetId: 15760 revision: 20 datasource: Prometheus这里的gnetId被提取为packageName用于构造请求 URL,revision被提取为currentValue,JSONata 将 API 返回的revision转换为字符串版本号。
6.4 无 API 时的替代方案:自定义离线依赖文件
有时依赖的版本来源根本没有可用 API。变通做法是自建"版本追踪文件"(dependency files),通过 HTTP(S) 暴露给 Renovate。例如为软件something准备versiontracker.json:
[ { "version": "77" }, { "version": "76" } ]再编写如下自定义数据源(示例以 Nexus 作为 Web 服务器):
{ "customDatasources": { "nexus_generic": { "defaultRegistryUrlTemplate": "https://nexus.example.com/repository/versiontrackers/{{packageName}}/versiontracker.json", "transformTemplates": [ "{ \"releases\": $map($, function($v) { { \"version\": $v.version, \"sourceUrl\": $v.filelink } }) }" ] } } }配合自定义管理器即可更新 Ansible YAML 中的版本号:
# renovate: datasource=custom.nexus_generic depName=something versioning=loose something_version: '77'对应的 regex 自定义管理器:
{ "customManagers": [ { "customType": "regex", "managerFilePatterns": ["/\\.yml$/"], "datasourceTemplate": "custom.nexus_generic", "matchStrings": [ "#\\s*renovate:\\s*(datasource=(?<datasource>.*?)\\s*)?depName=(?<depName>.*?)(\\s*versioning=(?<versioning>.*?))?\\s*\\w*:\\s*[\"']?(?<currentValue>.+?)[\"']?\\s" ], "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}" } ] }如果不想搭建 HTTP 服务,也可以把版本追踪文件放在本地仓库中,用file://前缀指向相对路径:
{ "customDatasources": { "local_generic": { "defaultRegistryUrlTemplate": "file://dependencies/{{packageName}}/versiontracker.json", "transformTemplates": [ "{ \"releases\": $map($, function($v) { { \"version\": $v.version, \"sourceUrl\": $v.filelink } }) }" ] } } }此时 Renovate 会从当前工作目录解析该文件。源码层面,isLocalRegistry的判断逻辑是defaultRegistryUrlTemplate.startsWith('file://'),命中后调用fetcher.readFile(...)读取本地文件(见 index.ts);五种格式均实现了readFile,因此 json/plain/yaml/toml/html 全部支持本地文件模式(对应测试见 index.spec.ts、index.spec.ts、index.spec.ts 与 index.spec.ts)。
6.5 解析 nginx 目录列表
当只有"一个装满文件的目录 + 能生成目录列表的 HTTP 服务器"时,html格式可以派上用场。以下配置跟踪 nginx 官方下载目录:
{ "customDatasources": { "nginx": { "defaultRegistryUrlTemplate": "https://nginx.org/download", "format": "html" } }, "packageRules": [ { "matchDatasources": ["custom.nginx"], "extractVersion": "^nginx-(?<version>.+)\\.tar\\.gz$" } ] }由于html格式提取到的是完整文件名,必须用extractVersion正则从中提取纯版本号。
6.6 解析普通 HTML "Downloads" 页面
同理,html格式也可以用于典型的下载页面,例如 curl 官网:
{ "customDatasources": { "curl": { "defaultRegistryUrlTemplate": "https://curl.se/download.html", "format": "html" } }, "packageRules": [ { "matchDatasources": ["custom.curl"], "extractVersion": "/curl-(?<version>.+)\\.tar\\.gz$" } ] }注意extractVersion既支持字符串正则,也支持/.../包裹的正则字面量写法。
七、故障排查与日志调试
7.1 内置的追踪日志
Renovate 在转换之前会写入 trace 级日志(Custom datasource API fetcher '${format}' received data. Starting transformation.),如果发现意外的数据格式,还会在转换之后额外写一条 trace 日志。这意味着LOG_LEVEL=trace时,你可以在日志中同时看到转换前后的原始数据与结果数据(见 index.ts 与 index.ts)。
调试经验:
- JSONata 表达式编译失败时,会以
logger.once.warn输出Invalid JSONata expression(如$[.name = "Alice" and这类语法错误,测试见 index.spec.ts); - JSONata 表达式执行抛错时,会输出
Error while evaluating JSONata expression(测试见 index.spec.ts); - 结果未通过 schema 校验时,会输出 debug 级
Response has failed validation与 trace 级的数据快照,然后返回null; - 未找到对应自定义数据源、未提供 registry URL、HTTP 请求失败等场景均返回
null并伴随对应日志(测试覆盖见 index.spec.ts)。
7.2 在 Mend 托管 App 上获取 trace 日志
如果使用 Mend Renovate 托管应用,可通过logLevelRemap配置项把特定消息提升到 info 级别输出:
{ "logLevelRemap": [ { "matchMessage": "/^Custom datasource/", "newLogLevel": "info" } ] }7.3 自托管时获取 trace 日志
自托管 Renovate 时,按以下步骤开启 trace 日志:
- 设置环境变量
LOG_FILE_LEVEL为trace; - 以
dryRun模式运行 Renovate,避免实际创建 PR,安全观察数据源返回的版本数据。
八、最佳实践小结
- 优先复用内置数据源:
custom数据源适用于内置数据源无法覆盖的"通用 HTTP(S) 端点"场景,不要用它重复造轮子。 - 模板变量让一份配置服务多个包:善用
{{packageName}}与{{currentValue}}构造 URL 和转换规则,如 Hashicorp、Grafana 示例。 - JSONata 转换保持"链式、幂等":多条
transformTemplates依次执行、层层递进,建议先在 JSONata Exerciser 中验证表达式再写入配置。 - 让 API 直接输出 Renovate 格式:若 API 归你控制,直接返回
{"releases": [...]}结构可省去全部转换逻辑(测试验证了"直接暴露 Renovate 格式"的场景,见 index.spec.ts)。 - 结合
extractVersion清洗版本:html/plain等格式提取到的常是文件名或整行文本,用extractVersion或 JSONata 提取纯净版本号。 - 注意 schema 校验的严格性:
version是releases中唯一必填字段,其余均为可选;非法数据不会导致任务失败,而是静默返回null并记录日志,调试时务必开启 trace 级日志。
九、延伸阅读
- Custom Datasource 源码 与 配置解析
- 结果结构校验 schema 与 五种格式 fetcher
- 单元测试(含全部格式与异常路径)
- regex 自定义管理器
extractVersion配置说明logLevelRemap配置说明- 自托管配置中的
dryRun
【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考