news 2026/9/19 12:42:39

OpenTofu 通过 OCI 镜像仓库分发与安装 Provider 与 Module:从 RFC 设计到落地实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenTofu 通过 OCI 镜像仓库分发与安装 Provider 与 Module:从 RFC 设计到落地实现

OpenTofu 通过 OCI 镜像仓库分发与安装 Provider 与 Module:从 RFC 设计到落地实现

【免费下载链接】opentofuOpenTofu lets you declaratively manage your cloud infrastructure.项目地址: https://gitcode.com/gh_mirrors/op/opentofu

本文基于 rfc/20241206-oci-registries.md 及其 10 个配套章节整理,系统讲解 OpenTofu 如何复用 OCI Distribution 协议(即 Docker/容器镜像仓库协议)来分发 Provider 与 Module。文中涵盖社区调研结论、OCI 协议速览、Provider 镜像源(oci_mirror)与 Module 新地址方案(oci://)的配置方法、认证体系、制品布局规范、签名/SBOM 等安全议题,以及贯穿源码级的实现细节,帮助读者在隔离网络、私有分发等场景中直接落地 OCI 镜像仓库作为 OpenTofu 依赖分发通道。

背景与动机:为什么 OpenTofu 要接入 OCI 镜像仓库

OCI registry(历史上也称 Docker registry)是容器生态的基础设施:它以 HTTP 接口提供manifests(描述内容元数据)与blobs(实际数据),通过分层(layer)机制实现高效的增量发布。得益于其通用架构,社区已经出现如 ORAS(OCI Registry As Storage)等项目,将 OCI 仓库用作容器镜像之外的任意数据存储。

OpenTofu 自己的 Provider Registry Protocol 与 Module Registry Protocol 与 OCI 标准化几乎是同期演进,但设计目标不同:

  • OpenTofu 的 Provider 注册协议关注制品签名,并将索引(index)与下载(download)解耦
  • OCI Distribution 协议更关注层拉取效率等容器场景问题;
  • 截至 RFC 撰写时,OCI 制品签名仍处于发展中(sigstore/cosign 等项目正在解决)。

企业用户的现实痛点

  • 隔离网络(air-gapped):大型组织常存在无法访问公共仓库的网络环境。而 OCI 镜像仓库在 Kubernetes 普及的今天几乎处处可用(云厂商提供托管服务、Docker Hub、GitHub Container Registry 等),无需额外的合规负担;
  • 无需额外服务:运行一个 OpenTofu/Terraform 私有仓库需要部署独立软件,而 OCI 仓库通常已经就绪;
  • 安全能力开箱即用:Harbor 等仓库自带漏洞/许可证扫描(Trivy、Clair),并能生成 SBOM(软件物料清单)——这些是 OpenTofu/Terraform 注册表今天不具备的。

RFC 明确将 OCI 定位为运营商配置的 Provider 镜像Module 的新地址方案,而非替代现有注册协议成为默认安装方式。

社区调研:OCI 需求与使用现状

RFC 附带的 2-survey-results.md 记录了面向社区的一次调研(103 份有效回答),关键结论如下。

Provider 分发需求(82% 需要私有仓库)

  • 96/103(93%)对使用 OCI 分发 Provider 感兴趣;
  • 其中 85 人(82%)希望用 OCI 镜像现有公共 Provider(64%)或发布私有 Provider(61%);
  • 13 人(13%)正在创作公共 Provider 并希望发布到 OCI 仓库;
  • 有受访者提醒:通用 OCI 仓库不会像官方仓库那样自动生成并发布 Provider 文档。

Module 分发需求(90% 需要私有仓库)

  • 93/103(90%)对 OCI 分发 Module 感兴趣,其中 84% 希望用于私有发布(79%)或镜像公共 Module(59%);
  • 当前 Module 分发方式分布:私有 Git 仓库 72、默认公共 Registry 37、公共 Git 仓库 37、私有 OpenTofu/Terraform 仓库 32 等。

安全扫描与隔离网络

  • 85% 的受访者希望使用安全扫描工具(Trivy 47、TFLint 34、Snyk 26……);
  • 33%(35 人)存在不同程度的隔离网络环境,这是 RFC 中最令人意外的发现,也是 OCI 镜像方案的核心驱动场景。

期望使用的仓库实现

GitHub(ghcr.io)57、AWS ECR 54、Azure Container Registry 31、自建 Harbor 27、自建 registry 27、GCR 25、Docker Hub 22、自建 GitLab 22、自建 JFrog 19、GitLab.com 11、Quay.io 9 等。

工具与凭据偏好

  • 27 人希望用 Podman/Docker/Containerd 工作流推送制品;24 人希望 OpenTofu 内置工具;24 人无偏好但要求能在 GitHub Actions 中工作;
  • 凭据方面:55 人希望复用容器生态既有凭据;36 人使用云集成凭据;19 人使用 Docker/Podman credential helper。

OCI 协议速览:Manifest、Blob、Pull/Push 与 ORAS

若对 OCI 协议不熟悉,1-oci-primer.md 提供了一份入门指南,要点如下(其中的 curl 示例均可自行复现)。

认证:从WWW-Authenticate到 Bearer Token

访问仓库 API 时,服务端可能返回WWW-Authenticate头指示需要认证。例如访问https://ghcr.io/v2/opentofu/opentofu/tags/list会得到:

www-authenticate: Bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:opentofu/opentofu:pull"

realm字段即认证端点,可用GET请求换取临时 bearer token:

curl -u user:password 'https://ghcr.io/token?service=ghcr.io&scope=repository:opentofu/opentofu:pull'

成功响应形如{"token":"djE6b3Blb..."},随后在请求头携带Authorization: Bearer ...即可。

Index manifest 与 Image manifest

  • Index manifestapplication/vnd.oci.image.index.v1+jsonapplication/vnd.docker.distribution.manifest.list.v2+json)包含多个平台对应的 image manifest 列表,用于分发多平台制品;
  • Image manifestapplication/vnd.oci.image.manifest.v1+jsonapplication/vnd.docker.distribution.manifest.v2+json)包含configlayers列表,每个 layer 指向一个 blob。

例如 ghcr.io 上 OpenTofu 1.8.0 的 index manifest 中列出386amd64arm64arm/v7等平台条目;而1.8.0-amd64的 image manifest 则列出多个application/vnd.docker.image.rootfs.diff.tar.gzip层。

Pull 类 API

  • Manifest 端点:/v2/<name>/manifests/<reference>(可基于Accept头做内容协商);
  • Blob 端点:/v2/<name>/blobs/<digest>可能返回 HTTP 重定向,客户端必须跟随Location头)。

<name>必须匹配[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*,即可由多个路径段组成(hostname+name 合计最多 255 字符);<reference>可以是 digest 或 tag(tag 匹配[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127});digest 形如scheme:value,规范要求支持sha256

注意:OCI 生态中 “reference” 一词有歧义——latest是仅指定 tag 的本地引用,而example.com/foo/bar/baz:latest是全限定引用。

Push 类 API

上传 blob 有两种方式:

  1. POST /v2/<name>/blobs/uploads,再向响应Location头中的 URL 执行PUT(支持用PATCH分块上传);
  2. 直接POST /v2/<name>/blobs/uploads/?digest=<digest>携带预计算 digest。

blob 上传完毕后,用PUT /v2/<name>/manifests/<reference>推送 manifest(reference 为 tag 名)。

Content discovery 与_catalog

可选端点包括:

  • /v2/<name>/tags/list:列出仓库所有 tag(通常分页,必须跟随Link),可用于实现基于 semver 的版本选择;
  • /v2/<name>/referrers/<digest>:列出引用某 blob 的 manifest 列表。manifest 可通过subject属性引用另一 manifest 形成制品树,常被用于附加签名、SBOM 等事后元数据(注意 Cosign 等工具更习惯用特殊命名的 tag 而非此 API)。

非标准的/v2/_catalog端点在 Docker Registry 中用于列出全部镜像,但公共仓库通常禁用或需认证。

ORAS:非容器布局的制品存储

ORAS 通过 layer 的mediaType区分制品类型,例如将 Provider 包声明为archive/zip层,并为 manifest 设置artifactType

{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.opentofu.provider", "config": { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "size": 2, "data": "e30=" }, "layers": [ { "mediaType": "archive/zip", "digest": "sha256:54b0178fd0fcbd60ce806b2569974694af59faaf0b2c734f703753f1fdfb1f21", "size": 146839280, "annotations": { "org.opencontainers.image.title": "terraform-provider-aws_5.84.0_linux_amd64.zip" } } ], "annotations": { "org.opencontainers.image.created": "2025-02-03T11:47:34Z" } }

ORAS 默认使用固定空配置 blob(application/vnd.oci.empty.v1+json,digest 恒为sha256:44136f...),ORAS 感知的软件会跳过它,但传统容器引擎(如 Docker)遇到此类非容器制品可能报 “invalid rootfs in image configuration” 等错误。可用如下命令体验:

oras push \ --artifact-type application/vnd.opentofu.provider \ localhost:5000/oras:latest \ terraform-provider-aws_5.84.0_linux_amd64.zip:archive/zip oras manifest fetch localhost:5000/oras:latest --pretty

注意:撰写 RFC 时 ORAS 尚不支持多平台镜像(可借助oras manifest子命令推送外部生成的 manifest)。

设计考虑:Provider 虚拟地址、OCI 布局与安全边界

3-design-considerations.md 阐述了若干关键设计决策。

Provider 地址是“虚拟”的

OpenTofu 中 Provider 使用HOSTNAME/NAMESPACE/TYPE形式的虚拟地址(HOSTNAME默认为registry.opentofu.orgNAMESPACE因历史原因默认为hashicorp)。该地址独立于实际下载 URL:默认情况下 OpenTofu 通过 Remote Service Discovery 与 Provider Registry Protocol 联系 origin registry,但运营商可重配置安装策略,此时 hostname 仅作为 Provider 唯一标识的一部分(例如状态快照中记录哪个 Provider 管理哪个资源,需在 plan/apply 之间保持一致)。

关键约束:不能让地址语法强制使用 OCI 协议或绑定某个具体 OCI 仓库。因此首个版本只把 OCI 作为新的 Providermirror类型(通过 CLI 配置显式启用),Provider Registry Protocol 仍是唯一默认安装方式。

OCI 布局:为什么坚持用archive/zip而不是容器层格式

RFC 最初考虑复用容器镜像的 differential-tar 布局(甚至可用 Dockerfile 构建):

FROM scratch ADD * / LABEL "org.opentofu.artifact-type"="provider"

但原型验证后放弃,核心原因:包校验和(checksum)必须与上游一致。OpenTofu 用 Provider 作者签名的.zip包校验和(记录在依赖锁文件 dependency-lock 中)验证镜像包。OCI digest(sha256:方案)与 OpenTofu 的 zip-checksum 格式本质等价,因此:

  • 每个 OS/架构的 Provider 包以.zip形式直接作为 OCI blob(不用 tar);
  • 每个平台对应一个含单个archive/zip层的 image manifest;
  • 主 manifest 为 index manifest,artifactType设为application/vnd.opentofu.provider
  • 复用现有解包代码,降低维护量,保证解包结果与其它来源一致。

SBOM:安全扫描的间接路径

Trivy 等主流扫描器开箱只支持容器层的 differential-tar 格式,暂不支持自定义制品类型;部分工具支持单镜像制品但不支持多平台 index manifest;TFLint 则完全不支持 OCI Distribution。因此 RFC 计划借道SBOM 间接扫描:由 Provider 开发者发布 SBOM,扫描器直接消费 SBOM 而非自行检查制品。但初始版本不做 SBOM 生成与消费(相关讨论见 PR #2494),优先实现 “能在 OCI 仓库里托管 Provider/Module 包” 这一基础能力。

制品签名:推迟到后续版本

现状:

  • Provider 用 GPG 签名(OpenTofu 注册表作为独立于作者的验证方);Sigstore/Cosign 支持仍有 issue 待解决(依赖稳定 Go 库);
  • Module 包目前完全没有签名机制;
  • OCI 生态对签名方案无共识:Cosign 用特殊命名 tag,Notary Project 用 Referrers 机制建立签名与制品的双向关系。

因此首个版本不做签名验证(与 Provider Network Mirror Protocol 现状一致),理由是 mirror 由运营商显式配置、默认可信。但可通过subject属性发布组织内部的事后签名/审批制品(如“某团队已审阅此依赖”),由 OpenTofu 之外的独立工具扫描仓库检查未签名制品。未来实现验证时,合法签名的 OCI manifest 能提供与 Provider Registry Protocol 等价的信息(签名公钥 + 覆盖各平台 SHA256 校验和的签名),可无缝接入现有签名验证模型。

Provider:把 OCI 仓库配置为镜像源

4-providers.md 定义了首个版本的 Provider 方案:仅支持 “mirroring” 场景——OCI 仓库作为 origin registry 之外的可选下载源;也允许手工构造并发布内部 Provider 制品,但更完善的支持留待后续。

provider_installation中的oci_mirror

OpenTofu 的 Provider 安装方式分两类:“direct” 与 “mirror”。oci_mirror属于 mirror 类:源地址中的 hostname 只作为标识,包实际从 OCI 仓库获取。在 CLI 配置文件(Provider Installation)中声明:

provider_installation { oci_mirror { repository_template = "example.com/examplenet-mirror/${namespace}-${type}" include = ["example.net/*/*"] } oci_mirror { repository_template = "example.com/exampleorg-mirror/${namespace}-${type}" include = ["example.org/*/*"] } direct { exclude = ["example.net/*/*", "example.org/*/*"] } }
  • repository_template必须include中每个通配(*)的地址组件提供替换变量:${hostname}${namespace}${type}(分别对应example.net/foo/bar的 hostname、foobar;两段式地址如hashicorp/kubernetes的 hostname 默认为registry.opentofu.org);
  • direct块可选:未匹配任何 mirror 的 Provider 仍从 origin registry 安装。

隔离网络示例

为支持无法直连registry.opentofu.org的隔离环境,可将所需 Provider 包复制到 OCI 仓库的固定命名模式中:

provider_installation { oci_mirror { repository_template = "example.com/opentofu-provider-mirror/${namespace}_${type}" include = ["registry.opentofu.org/*/*"] } }

此后依赖hashicorp/kubernetes的模块初始化时,OpenTofu 会从example.comopentofu-provider-mirror/hashicorp_kubernetes仓库安装,而registry.opentofu.org仅作为标识符。这与现有network_mirror安装方式对等(后者用的是 OpenTofu 专有协议)。

AWS ECR 场景(仓库格式为aws_account_id.dkr.ecr.region.amazonaws.com/repository:tag):

provider_installation { oci_mirror { repository_template = "YOUR_AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/${namespace}_${type}" include = ["registry.opentofu.org/*/*"] } }

这样无需改写任何模块源码即可从 ECR 安装hashicorp/kubernetes

OCI 中的存储规范

借鉴 ORAS 但在 OpenTofu 内直接实现兼容的 index manifest 布局(撰写 RFC 时 ORAS 的多平台支持仍在进行中):

  1. 每个 OS/架构(如linux_amd64)以.zip文件直接存为 OCI blob,不使用 tar;
  2. 每个平台必须有 image manifest,含单个mediaTypearchive/zip的层,且该层必须是 Provider 开发者官方分发包的逐字节副本
  3. 制品主 manifest 必须是index manifest,为每个支持的平台提供条目;artifactType必须为application/vnd.opentofu.provider。OCI 与 OpenTofu 恰好共享 Go 工具链的 OS/架构命名(linux_amd64"os":"linux","architecture":"amd64");
  4. 制品必须挂在与上游版本号同名的 tag 下;OpenTofu 会忽略无法识别为 semver 的 tag(包括latest)。semver 的+构建元数据在 OCI tag 中不允许,需替换为_
  5. index manifest 的manifests中所有条目都视为 Provider 包,不得混入其它 manifest;但单个 image manifest 可以包含额外mediaType的层(OpenTofu 会忽略),且必须恰好有一个archive/zip

可用subject属性发布引用 index manifest 或 image manifest 的附加制品(签名、SBOM 等),通过 Referrers API 发现;OpenTofu 初始版本不会消费它们。

警告:OCI 中的 Provider 制品必须使用多平台(index)manifest,OpenTofu 会拒绝下载非多平台制品;而 Module 制品禁止使用多平台 manifest。

镜像/发布 Provider 的工具现状

撰写 RFC 时没有第三方工具能直接推送所需格式。若 ORAS 的多平台 manifest 提案未能及时落地,OpenTofu 将发布手工编写 index manifest 并使用oras manifest低层命令推送的指南(效果等价于 ORAS 提案中的oras manifest index create)。RFC 也考虑过提供类似tofu providers mirror的内置镜像工具,但为了控制首版范围而推迟。

Module:把oci://作为新的模块来源

与 Provider 不同,Module 的源地址本就支持多种 scheme,因此 Module 可直接在配置中用oci://前缀引用 OCI 仓库(见 5-modules.md):

module "foo" { source = "oci://example.com/opentofu-vpc-module" }

OpenTofu 会查找该仓库名为latest的 tag 并取回对应制品作为模块包。

显式 tag / digest 选择

沿用现有远程模块源地址的惯例,oci:scheme 支持两个互斥的查询参数:

  • tag=NAME:指定其它 tag,覆盖默认的latest
  • digest=DIGEST:直接指定 image manifest 的 digest,完全绕过 tag 命名空间。
module "foo" { source = "oci://example.com/opentofu-vpc-module?tag=1.0.0" }

说明:使用查询字符串语法与 OCI 惯用的:TAG/@DIGEST后缀不一致,原因在于 OpenTofu 的模块安装委托给第三方库 go-getter,查询字符串是其既有惯例;跟随 OCI 惯例反而会与 go-getter 的假设冲突,不利于用户在不同源地址类型间迁移知识。

基于版本约束的选择(当前不支持)

OpenTofu 目前只对模块注册表源地址保留“版本约束选择”能力(这类地址由 OpenTofu 直接处理而非 go-getter)。因此首版依赖直接选择 tag 或 digest(类似 Git 源用 ref 选择 commit)。未来可能为 OCI、Git、Mercurial 等统一引入版本约束(见 issue #2495),但这属于更广泛的架构变更,超出本 RFC 范围。

包内子目录

模块安装的最小单元是模块(一个可能包含零个或多个模块目录的文件系统树,如一个 Git 仓库整体)。OCI 制品默认在根目录放置模块,也可用惯用的子目录语法:

module "foo" { source = "oci://example.com/opentofu-vpc-module//subnets" }

tag/digest组合时,查询字符串必须放在子目录之后:oci://example.com/opentofu-vpc-module//subnets?tag=1.0.0

设计过程中也考虑过更 “OCI 原生” 的oci://example.com/opentofu-vpc-module:TAG-NAMEoci://...@DIGEST写法,但 go-getter 会把:TAG-NAME@DIGEST当作路径一部分,导致子路径语法与其它源类型不一致,故最终选择与 OpenTofu 自身一致的查询字符串方案。

模块制品布局

模块包是平台无关的,因此采用直接的 ORAS 风格 image artifact,不使用多平台 index manifest

  • image manifest 的artifactType必须为application/vnd.opentofu.modulepkg
  • 必须恰好有一个mediaTypearchive/zip的层,指向包含模块包内容的.zipblob;
  • 可包含其它mediaType的层(OpenTofu 会忽略,未来版本可能识别)。

用 ORAS CLI 发布模块包

oras push \ --artifact-type application/vnd.opentofu.modulepkg \ example.com/opentofu-vpc-module:latest \ opentofu-vpc-module.zip:archive/zip

通过 OpenTofu Module Registry 间接暴露 OCI 地址

现有 Module Registry Protocol 是对任意远程源地址的门面(façade),因此支持 OCI 的版本也允许注册表在 “Download Source Code for a Specific Module Version” 中返回oci://前缀的 location:

{"location":"oci://example.com/repository?digest=sha256:1d57d25084effd3fdfd902eca00020b34b1fb020253b84d7dd471301606015ac"}

这样可以隐藏 “包实际来自 OCI” 的实现细节,但代价是需要运行额外的 OpenTofu 专用注册服务。

警告:只有OpenTofu v1.10 及以上才支持oci:scheme,旧版本会直接拒绝此类模块源地址。

认证配置:既有凭据复用与 OpenTofu 专属凭据

6-authentication.md 定义了 OCI 认证体系:默认对要求认证的仓库尝试匿名认证;附加凭据可来自 Docker/Podman 等工具的配置文件,或来自 OpenTofu CLI 配置的新块类型。

自动发现环境凭据(ambient credentials)

默认情况下,OpenTofu 按containers-auth.json的约定搜索凭据——该格式兼容docker loginpodman loginoras login等写入的位置,从而复用既有凭据、避免凭据泛滥。

新块类型oci_default_credentials可定制自动发现行为:

oci_default_credentials { # 完全禁用自动凭据发现,强制只使用 CLI 配置中显式提供的凭据 discover_ambient_credentials = false }
oci_default_credentials { # 覆盖 Docker 风格配置文件的默认搜索位置 #(设置后仅搜索列出的文件,默认搜索位置被禁用) docker_style_config_files = [ "/etc/awesome-oci-tool/auth.json", ] }

还可指定默认的 Docker-style credential helper(用于没有更具体凭据配置的仓库):

oci_default_credentials { docker_credentials_helper = "osxkeychain" }

未来的环境凭据发现方法都必须在oci_default_credentials中有独立开关;若想用新方法替代 Docker 风格文件,可将docker_style_config_files设为空列表再配置新方法。

OpenTofu 专属显式配置:oci_credentials

对于只在 OpenTofu 中使用 OCI 仓库的用户,新增oci_credentials块,按仓库前缀匹配:

oci_credentials "example.com/foo/bar" { # 适用于该仓库下所有以 "foo/bar" 开头的仓库路径 username = "foobar" password = "example" }

每个oci_credentials必须且只能设置以下互斥组之一:

  • username+password:基础认证风格;
  • access_token+refresh_token:OAuth 风格;
  • 单独docker_credentials_helper:间接提供用户名/密码。

每个仓库一个独立顶层块,沿用了 OpenTofu 自有服务认证的既有惯例(如tofu login会把生成的凭据写入独立的 CLI 配置文件)。首版不提供tofu login风格的 OCI 凭据获取命令,后续版本再考虑。

凭据选择优先级

沿袭 Docker CLI 配置格式的匹配规则,OpenTofu 在所有 ambient 与显式凭据源中寻找对目标仓库最具体的匹配:

  • example.com/foo/barexample.com/foo更具体;
  • example.com/fooexample.com更具体;
  • 任何匹配域名的条目都比全局设置更具体(只有默认的 Docker 凭据 helper 属于 “全局”)。

当同一仓库地址同时存在显式与 ambient 配置时,显式oci_credentials优先。CLI 配置解析器会拒绝同一仓库前缀的多个oci_credentials块;ambient 凭据无此限制,按发现顺序(或docker_style_config_files中的顺序)靠前者优先。

实现细节:凭据策略层、Provider 源与 go-getter Getter

RFC 的后三个附录(8-auth-implementation-details.md、9-provider-implementation-details.md、10-module-implementation-details.md)记录了面向核心团队的实现蓝图。需要说明的是,这些代码签名是“示意性的”,以package mainpackage cliconfig的依赖倒置为主线。

认证是横切关注点

认证逻辑应被 Provider 与 Module 安装器共享,并集中管理凭据设置。CLI 配置由internal/command/cliconfig解码校验oci_default_credentialsoci_credentials块(对应实现见 oci_credentials.go),隐式配置(Docker 等配置文件)也被映射到同一内部数据类型。

凭据选择策略封装在独立的package ociauthconfig(位于internal/command/cliconfig/ociauthconfig)。核心抽象包括:

  • CredentialsConfig接口:CredentialsSourcesForRepository(ctx, registryDomain, repositoryPath)返回匹配该仓库的凭据源序列;
  • CredentialsSource接口:CredentialsSpecificity()+Credentials(ctx)——先用特异性选源,再取具体凭据,避免过早执行凭据 helper;
  • Credentials:封装具体凭据,初始实现仅提供ForORAS()方法转为 ORAS 库的orasauth.Credential
  • CredentialsSpecificity:分层级(NoCredentialsSpecificity<GlobalCredentialsSpecificity<DomainCredentialsSpecificity<RepositoryCredentialsSpecificity(pathSegments)),内部实现为整数但对外不承诺;
  • 入口CredentialsConfigs.CredentialsSourceForRepository:遍历所有配置源,选出特异性最高者;同特异性时取“最早声明”的源。package cliconfig将显式块排在 ambient 配置之前,实现显式优先。

选用自研实现而非第三方库的理由:候选库难以扩展接入 OpenTofu CLI 配置的显式方式,且常伴随大量非必要间接依赖,可能触发仅按 Go 模块粒度扫描的安全工具误报。

Provider 安装源:OCIMirrorSource

provider_installation中每种安装方法对应一个getproviders.Source实现:

  • directgetproviders.RegistrySource
  • filesystem_mirrorgetproviders.FilesystemMirrorSource
  • network_mirrorgetproviders.HTTPMirrorSource
  • 新增oci_mirrorgetproviders.OCIMirrorSource(实际实现见 oci_registry_mirror_source.go)。

示意构造签名:

func NewOCIMirrorSource( getRepositoryAddress(addrs.Provider) (registryDomainName, repositoryPath string, err error), getRegistryClient func(ctx context.Context, registryDomainName, repositoryPath string) (*orasregistry.Registry, error), ) *OCIMirrorSource

其中getRepositoryAddress委托package cliconfig求值repository_template(HCL 字符串模板);getRegistryClient先用ociauthconfig.CredentialsConfigs选择凭据,再实例化 ORAS 库的 registry client。ORAS 的 repository 客户端提供实现所需能力:枚举 tag(Tags)、拉取 manifest(ManifestStore)、拉取 blob(BlobStore)。整体实现预计与HTTPMirrorSource高度相似,只是改用 OCI Distribution 协议。

校验和与签名细节

  • 依赖锁文件记录已装 Provider 的版本与可接受校验和集合;
  • 从 origin registry 获取 GPG 签名后,OpenTofu 验证包匹配签名覆盖的校验和之一,并将签名覆盖的全部平台校验和记入锁文件;
  • Provider 包始终是.zip,签名覆盖含全部平台 SHA256 校验和的文档;对应zh:校验和,另生成本地内容校验和h1:
  • 由于 OCI 层就是官方.zip的逐字节副本,每个层的sha256:digest 可直接转成zh:校验和,无需下载后重算;
  • 首版不做签名:锁文件只记录下载制品本身的h1:zh:校验和;未来支持签名后,可用签名证明全部平台的zh:校验和,并在tofu init输出中公布签名 key ID(由运营商自行核对);
  • tofu providers lock默认忽略已配置安装方法、直连 origin registry,以支持“origin 取官方校验和 + mirror 校验镜像包”的隔离工作流;未来应增加-oci-mirror选项(首版不做,另有 issue 跟踪)。

模块安装:go-getter 的新Getter

OpenTofu 的远程模块包抓取大多委托给 go-getter。go-getter 两个关键概念:

  • Detector:预处理原始地址(如把github.com/example/example重写为git::https://github.com/example/example.git),Detector 可“堆叠”,但最后一个必须产出 URL-like 语法;
  • Getter:真正抓取并解包。地址可用git::双冒号前缀显式选择 Getter,否则 URL scheme 即 Getter 名(如https://...属于 “https” getter)。

oci:scheme 无显式前缀,因此需要向 OpenTofu 的 getters 表添加新条目"oci"(无需新增 Detector,地址本身符合 URL 语法)。上游 go-getter 不接受新Getter实现(HashiCorp 代表曾表示 “It can be hard to get changes through in go-getter”),故在package getmodules内实现 OpenTofu 专属 Getter(实际实现见 oci_getter.go)。

示意构造签名:

func NewOCIGetter( getRegistryClient func(ctx context.Context, registryDomainName, repositoryPath string) (*orasregistry.Registry, error), ) getter.Getter

该 Getter 用 ORAS 库完成:tag 解析为 manifest digest → 取 manifest 得知.zipblob digest → 取 blob 并解包到目标目录。解包直接复用 go-getter 的getter.ZipDecompressor,确保与 HTTP 等来源解包行为完全一致(含防路径穿越等安全检查)。go-getter 的通用Decompressor机制按文件后缀(.zip)而非 media type 匹配,不适合本场景,故不采用。

模块校验和现状

OpenTofu 目前对模块包没有通用校验和/签名/锁文件机制(属于跨源类型的更大议题)。现有常见折中是 Git getter +ref指向具体 commit(提供 SHA-1 校验)。OCI 的digest参数提供类似机制:它校验 manifest,manifest 内又含.zipblob 的校验和,因此使用digest而非tag的地址可保证 “匹配到的 manifest 必须与该 digest 一致”。未来或可用 Referrers API 自动发现 manifest 的签名,但不在本项目范围内。

开放问题与未来规划

7-open-questions.md 与主文档记录了尚待解答的问题与后续方向。

与 Terragrunt 的兼容性

Terragrunt 的 Provider Cache Server 通过未文档化的host块重写 remote service discovery,强制注册表请求走向缓存服务器——这只对使用 Provider Registry Protocol 的direct方式有效。因此首版中oci_mirror与 Terragrunt 缓存方案互斥:改用oci_mirror会绕过 Terragrunt 依赖的拦截机制。不过首版不改direct行为,未使用oci_mirror的既有部署不会受影响。

多认证域配置

同一 host 在不同代码库中可能需不同凭据。目前只能间接支持:用TF_CLI_CONFIG_FILE环境变量为特定代码库指定专属 CLI 配置文件。若后续发现这是普遍需求,再考虑引入认证上下文切换(应同时覆盖 OpenTofu 原生与 OCI 凭据)。

非 ASCII 字符的 Provider 地址

OCI 仓库名只允许 ASCII 字母数字与少量标点,而 OpenTofu 的 Provider 源地址(按 RFC 3491 Nameprep 规则)允许宽泛的 Unicode 字符。因此某些合法 Provider 地址无法机械地代入模板。初始原型直接报错:

requested provider address example.com/foo/ほげ contains characters that are not valid in an OCI distribution repository name, so this provider cannot be installed from an OCI repository as ghcr.io/examplecom-otf-providers/foo-ほげ

若未来确需支持,可考虑 Punycode 类编码变换,但可读性差且通用 OCI 仓库 UI 大概率不会自动转码。

未来计划

  • Provider 方向:当前 OCI 仅作为运营商配置的镜像;待制品签名方案成熟后,有望把 OCI Distribution 变为一种无需 CLI 配置的新 “origin registry” 类型;
  • 搜索与文档:OCI 实现尚无对应 OpenTofu Registry Search 的能力,未来可能仿照 Linux 发行版发布附带doc包的做法,需要 OpenTofu 内置文档渲染工具(可复用 registry-ui 源码,但需适配改造);
  • 替代方案对比:改善现有注册协议工具链(如基于静态文件维护私有注册表、复用 OpenTofu Registry 数据)与 OCI 代理(用 Provider Network Mirror 协议做翻译)虽可行,但均不如 OCI 基础设施“普遍且廉价”,且模块没有 mirror 协议对应物,代理方案无法覆盖模块场景。

仓库中的落地证据

RFC 描述的设计已在当前仓库中落地为实际代码,可作为深入阅读的入口:

  • CLI 配置解析:oci_credentials.go(oci_credentials/oci_default_credentials块的解码与校验)、provider_installation.go(oci_mirror安装方法);
  • 凭据策略层:ociauthconfig/(docker_cli_credentials_config.gorepository_addr.go等);
  • Provider 镜像源实现:oci_registry_mirror_source.go(对应getproviders.OCIMirrorSource);
  • 模块 OCI Getter:oci_getter.go 及配套测试 oci_getter_test.go。

如需验证本文涉及的示例命令(token 换取、manifest 拉取、blob 重定向、ORAS 推送等),可对照 1-oci-primer.md 中的 “Try it yourself” 片段逐一复现。

【免费下载链接】opentofuOpenTofu lets you declaratively manage your cloud infrastructure.项目地址: https://gitcode.com/gh_mirrors/op/opentofu

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 12:40:47

meshoptimizer|快速让 3D 网格更小更快的轻量优化工具库

meshoptimizer&#xff5c;快速让 3D 网格更小更快的轻量优化工具库 【免费下载链接】meshoptimizer Mesh optimization library that makes meshes smaller and faster to render 项目地址: https://gitcode.com/GitHub_Trending/me/meshoptimizer meshoptimizer 是一个…

作者头像 李华
网站建设 2026/9/19 12:40:45

量子计算实现无条件指数级优势的首次实证

1. 量子计算里程碑&#xff1a;无条件指数级优势的首次实证上周三凌晨三点&#xff0c;当我收到南加州大学研究团队发来的预印本时&#xff0c;咖啡杯差点从手中滑落——他们竟然在127量子比特处理器上&#xff0c;首次实现了量子计算机对经典计算机的无条件指数级优势突破。这…

作者头像 李华