OpenTofu 静态评估 RFC 深度解析:在 init 阶段求值常量变量与 locals 的架构设计与落地
2026/9/19 18:08:45 网站建设 项目流程

OpenTofu 静态评估 RFC 深度解析:在 init 阶段求值常量变量与 locals 的架构设计与落地

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

本文是对 OpenTofu 官方 RFC(rfc/20240513-static-evaluation.md)的深度解读,并对照当前仓库源码(internal/configs 等)验证其落地状态。读者将掌握:OpenTofu 为什么需要"init 期静态求值"、它如何在不依赖动态信息的前提下求值变量与 locals、tofu init阶段配置加载与图引用求值的完整调用链,以及 backend 配置、module source、加密配置、provider 迭代等被阻塞功能的统一解法。

一、背景与动机:为什么tofu init阶段需要求值能力

OpenTofu 的许多用户在多个"本应支持变量和 locals"的位置上遇到了限制,常见场景包括但不限于:

  • 模块源地址(module sources)module "x" { source = ... }的地址;
  • Provider 指派(provider assignments)provider = ...
  • Backend 配置terraform { backend "..." { ... } }
  • 加密配置(encryption configuration)

这些信息都必须在tofu init期间确定,无法纳入常规的tofu plan/apply系统。init 建立的是对项目配置的静态理解,后续 plan/apply 在此基础上运作。这是理解本 RFC 的第一把钥匙:求值被分为两个阶段——init 期的"静态阶段"和 plan/apply 期的"动态阶段"。

二、提议的解决方案总览

简单来说,该提案旨在增强 OpenTofu 的配置处理能力,支持对不依赖任何动态信息(资源、数据源、provider 等)的变量和 local 引用进行求值

需要特别强调的是:该提案本身并不直接增加面向用户的端到端新功能,而是一个"支撑系统"(support system),供上面提到的 backend 配置、module source 等示例建立在它之上。在 internal/configs/static_evaluator.go 中可以看到这一"支撑系统"的落地形态:StaticIdentifier(保存可引用对象及其声明位置)、StaticModuleVariables(变量求值回调函数类型)、StaticModuleCall(调用某个模块所需的静态信息集合)等核心类型均已实现。

三、用户可见的行为设计:错误消息与限制示例

RFC 指出,作为支撑系统它必须直接与用户交互。以下是基于真实场景的错误消息设计(RFC 原文注明所有错误/警告均为简化占位符,实际实现会附带更好的措辞、格式与源码位置)。

3.1 可配置 Backend 示例

先看一个同时依赖变量和 locals 的 backend 配置:

variable "key" { type = string } locals { region = "us-east-1" key_check = md5sum(var.key) } terraform { backend "somebackend" { region = local.region key = var.key key_check = local.key_check } }

首次运行tofu init时会产生两个错误:

  1. terraform.backend.key要求提供变量"key"
  2. terraform.backend.key_check要求提供 local"key_check",而它又依赖变量"key"

变量"key"可以通过terraform.tfvars文件或 CLI 参数-var "key=somevalue"提供,这意味着-varCLI 参数需要被添加到大多数 OpenTofu 命令中

注(RFC 原注):在这种场景下,除了报错,也可以改为要求用户提供所需变量值——这在 provider 配置流程中已经发生。可以统一该流程以减少代码重复和现有的各种变通写法。

接下来考虑运行tofu apply时的情况:如果terraform.tfvars-var "key="发生改变,backend 配置将与tofu init时不再匹配,并向用户返回错误。这要求用户团队谨慎决定允许哪些变量进入 backend 以及如何管理它们。

引用追踪的重要性:正如用户今天试图在 backend 中使用"动态"值(资源/数据源)一样,未来一定会有人写出这样的配置:

resource "mycloud_account" { } locals { account_id = mycloud.account.id } terraform { backend "somebackend" { account_id = local.account_id } }

此时会产生如下错误消息:

  • terraform.backend.account_id无法被解析
    • 字段"account_id"依赖 local"account_id",而它又依赖资源"mycloud.account",此处不允许使用。

这类示例旨在强调**引用追踪(reference tracking)**的价值——它能向用户清晰解释为什么他们尝试的操作不被允许。

3.2 模块源地址示例

模块是一个静态求值必须与之交互的复杂概念。先看一个不涉及子模块的简单示例:

# main.tf variable "version" { type = string } module "helper" { source = "git@github.com:org/my-utils?ref=${var.version}" }

这将产生与 backend 示例相同类型的错误消息。

再看带子模块源的复杂示例:

# main.tf variable "version" { type = string } module "common_first" { source = "./common" version = var.version region = "us-east-1" } module "common_second" { source = "./common" version = var.version region = "us-east-2" }
# ./common/main.tf variable "version" { type = string } variable "region" { type = string } module "helper" { source = "git@github.com:org/my-utils?ref=${var.version}" region = var.region }

这给引用错误增加了新的维度。当变量"version"未提供时,会产生如下错误:

  • 模块common_first.helper的 source 无法确定
    • common_first.helper.source依赖变量common_first.version,它又依赖变量"version",而后者未被提供
  • 模块common_second.helper的 source 无法确定
    • common_second.helper.source依赖变量common_second.version,它又依赖变量"version",而后者未被提供

一旦提供了"version"的值,一切即可正常工作。

该配置还可以用for_each进一步简化:

# main.tf variable "version" { type = string } module "common" { for_each = {first = "us-east-1", second = "us-east-2"} source = "./common" version = var.version region = each.value }

这会合并为单个错误而不是多个:

  • 模块common.helper的 source 无法确定
    • common.helper.source依赖变量"common.version",它又依赖变量"version",而后者未被提供

注意,for_each完全没有与静态引用系统交互。

for_each/count的禁止:那么,如果用户在必须静态已知的字段中使用each.value会怎样?

# main.tf variable "version" { type = string } module "common" { for_each = {first = "us-east-1", second = "us-east-2"} source = "./common" version = "${var.version}_${each.key}" region = each.value }

无论变量"version"的值是什么,都会产生如下错误:

  • 模块common.helper的 source 无法确定
    • common.helper.source依赖变量"common.version",而它又依赖一个for_eachkey,此处禁止使用

支持静态表达式中的for_each/count存在重大技术障碍,RFC 明确禁止之。更多背景见 rfc/20240513-static-evaluation/module-expansion.md(该附属文档记录了原型阶段的探索结论:若要在配置层支持静态模块展开,需要把addrs.Module全面迁移到addrs.ModuleInstance,让超过一半的 OpenTofu 组件理解"实例化"概念,开发与测试成本巨大,因此暂缓实现)。

四、技术基础:表达式求值的两阶段模型

虽然该方案在 OpenTofu 中的范围主要局限于一两个包,但理解它将模仿并与哪些复杂系统交互至关重要。

注(RFC 原注):强烈建议在深入本文前先阅读 docs/architecture.md。下文将对该文档中的许多概念从理解本提案的角度进行展开。

4.1 表达式(Expressions)

表达式(如1 + var.bar)的求值取决于表达式引用的值和函数。上例中你需要知道var.bar的值。这种依赖通过HCL Traversals概念获知——它表示属性访问路径,并可被转换为强类型的OpenTofu References。实践中可以说"该表达式依赖一个名为 bar 的 OpenTofu 变量"。

一旦知道表达式的需求(hcl.Expression),就可以构建求值上下文(hcl.EvalContext)来提供这些需求,或在缺失时返回错误。上例中,求值上下文必须包含{"var": {"bar": <somevalue>}}

当前表达式求值分为两个阶段:配置加载(config loading)图引用求值(graph reference evaluation)

4.2 配置加载(Config Loading)

在配置加载期间,HCL 或 JSON 配置被 hcl 包拆分为 Blocks 和 Attributes。Block 可以包含 Attributes 和嵌套 Blocks;Attribute 只是命名表达式(例如foo = 1 + var.bar)。

some_block { some_attribute = "some value" }

这些 Blocks/Attributes 是配置的抽象表示,尚未被求值为可操作的值。处理某个 block 或 attribute 时,会决定是"立即求值"(如必要)还是"保留抽象表示供后续处理"。若保留,后续将由图引用求值转换为值。

以具体例子说明:module -> source字段必须在配置加载期间已知,因为继续下一轮加载过程需要它;而module -> for_each这类属性可能依赖资源属性值或其他配置加载期未知的信息,因此以表达式形式存储,留给图引用求值:

resource "aws_instance" "example" { name = "server-${count.index}" count = 5 # (other resource arguments...) } module "dnsentries" { source = "./dnsentries" hostname = each.value for_each = toset(aws_instance.example.*.name) }

在整个配置加载过程中不会构建或提供任何求值上下文。因此,由于缺少求值上下文,配置加载期间不得使用任何函数、locals 或变量——这正是本 RFC 想要解决的限制。

4.3 图引用求值(Graph Reference Evaluation)

配置完全加载后,会被转换和处理为有向无环图(DAG)中的节点。这些节点使用其 blocks/attributes 中(配置加载时未求值的部分)携带的"OpenTofu References"来构建图中的依赖边,并在这些引用可用后构建求值上下文(见 internal/addrs/parse_ref.go)。

这一理论简单的过程被**模块依赖树及其中的展开(expansion)**极大地复杂化。由于for_eachcount在所需引用可用时才被求值,子图(sub-graph)被动态创建。该过程的大部分逻辑存在于紧密耦合的tofulang两个包中。

例如,一个模块的for_each可能需要资源中的数据:for_each = resource.aws_s3_bucket.foo.tags。在求值前,模块必须等待"OpenTofu 资源引用aws_s3_bucket.foo"可用。这会表示为模块节点与具体资源节点之间的依赖边,求值上下文随后包含{"resource": {"aws_s3_bucket": {"foo": {"tags": <provided value>}}}}

注(RFC 原注):一个常见误解是模块是"对象"。实际上模块更接近"命名空间",只要没有引用循环,它们可以互相引用彼此的 vars/outputs。

4.4 背景小结

如上面所述,配置加载阶段缺少求值上下文,导致任何含引用的表达式都无法展开;该阶段目前只允许基本类型和纯表达式

通过引入在配置加载期间构建和管理求值上下文的能力,就可以让某些引用在配置加载过程中被求值。例如,许多用户期望能在module -> source中使用local值以简化升级、减少配置重复,这在目前不可行,因为module -> source的值必须在配置加载阶段已知,不能推迟到图求值:

local { gitrepo = "git://..." } module "mymodule" { source = locals.gitrepo }

利用 Traversals/References,可以追踪哪些值在整个配置加载过程中是静态已知的。这将遵循与图引用求值类似的模式,但在可解析的内容上有限制。在把 Attribute/Block 求值为值时,任何缺失的引用都必须以用户易于调试和理解的方式报告;贯穿模块树的变量也必须携带其关联信息(如引用)向下传递。

五、当前加载/求值流程(附源码佐证)

在 OpenTofu 中执行操作(init/plan/apply 等)大体遵循以下步骤(简化版)。一个 command 包中的命令根据 CLI 参数创建并执行,执行以下动作:

5.1 解析并加载配置模块

从根模块(当前目录)开始,创建config.Config结构。该结构是一棵树的根节点,代表组成项目的所有module {}调用。树中每个节点包含一个config.Module和一个addrs.Module路径。

树的构建方式:安装模块源码 → 加载模块 → 检查模块调用 → 深度优先递归。

// 伪代码,对应实现见 internal/configs/config_build.go func buildConfig(source string) configs.Config { c := configs.Config{} path = installModule(source) // internal/initwd/module_install.go c.module = loadModule(path) // internal/configs/parser_config_dir.go for name, call := range c.module.calls { // internal/configs/config_build.go c.children[name] = buildConfig(call.source) } return c } root = buildConfig(".")

当前仓库中,这一流程已演化为 internal/configs/config_build.go 的BuildConfig:它通过ModuleWalker接口加载所有后代模块,先构建符号库(buildSymbolLibraries),再对根模块执行Finalize,随后递归buildChildModules加载子模块、buildTestModules加载测试模块,最后在无错误时解析 provider 类型并做验证。

configs.Module结构是模块的表示,混合了两类字段:配置过程中即计算的字段,与推迟到稍后求值的字段。例如module -> source必须在配置加载完成前已知,而资源配置体可以推迟到图节点求值期间。

模块加载流程:取一个目录,把每个 hcl/json 文件转成configs.File结构,合并后返回configs.Module

// 伪代码,对应实现见 internal/configs/parser_config_dir.go 与 internal/configs/module.go func loadModule(path string) configs.Module { var files = []file.File for filepath in range(list_files(path)) { files = append(files, loadFile(filepath)) } module := configs.Module{} for _, file in range(files) { module.appendFile(file) } return module } func loadFile(filepath string) configs.File { file := configs.File{} hclBody = hcl.parse(filepath) for _, hclBlock in range(hclBody) { switch (hclBlock.Type) { case "module": file.ModuleCalls = append(file.ModuleCalls, decodeModuleCall(hclBlock)) case "variable": file.Variables = append(file.Variables, decodeVariable(hclBlock)) // 其余受支持 block 的模式省略 } } return file }

在 internal/configs/module_call.go 中可以看到ModuleCall的字段设计:除了Sourcehcl.Expression)与解析后的SourceAddraddrs.ModuleSource),还保存了VariablesStaticModuleVariables)与Workspace,注释明确写道"Used when building the corresponding StaticModuleCall"——这正是 RFC 提出的"存储表达式、需要时求值"模式的落地。

5.2 Backend 被加载

命令从配置构造 backend。backend 是与状态存储交互的组件,负责实际执行和管理状态操作(plan/apply)。

5.3 操作被执行

命令使用 backend 和配置执行操作。随后:

  • 从已加载配置构建图并转换使其可被遍历。转换的关键环节包括:
    • 基于节点间检测到的引用进行转换和链接(internal/tofu/transform_reference.go)。节点依赖通过检查 blocks 和 attributes 确定,这些 blocks/attributes 在lang包中被转换为引用;
    • 资源节点被链接到其所需的 provider 节点;
    • 模块变量通过模块调用从父模块映射到子模块。
  • 图随后被求值:在每个节点的依赖被求值后遍历各节点。

求值某个图节点时,使用tofu.EvalContext(由tofu.BuiltinEvalContext实现,见 internal/tofu/eval_context_builtin.go)基于节点指定的引用构建和使用lang.Scope——由于转换后图所表示的依赖结构,这些引用应当都已求值完毕。

lang.Scope负责获取 OpenTofu 引用并从当前可用的值与函数中构建hcl.EvalContext

整体请求流程可参考架构图:

六、提议的变更:Static Evaluation Context 设计

需要修改上述设计,在配置加载过程中以不同作用域追踪引用/值,这被称为Static Evaluation Context(静态求值上下文)

加载模块时必须提供静态上下文

  • command等外部包调用时,静态上下文包含来自 CLI 选项和 .tfvars 文件的 tfvars;
  • configs.Config树构建过程内部调用时,它会把来自config.ModuleCall的值作为已提供的变量以静态引用方式传入;
  • 无论哪种情况,内置 OpenTofu 命令均可用。
// 伪代码 func buildConfig(source string, ctx StaticContext) configs.Config { c := configs.Config{} path = installModule(source) c.module = loadModule(path, ctx) for name, call := range c.module.calls { // 此时应具备求值子模块 source 字段所需的信息 source := ctx.Evaluate(call.source) // 基于 call 的配置属性构建新的 StaticContext childCtx = ctx.FromModuleCall(call.Name, call.Config) c.children[name] = buildConfig(source, childCtx) } return c } func loadModule(path string, ctx StaticContext) configs.Module { var files = []file.File for filepath in range(list_files(path)) { files = append(files, loadFile(filepath)) } module := configs.Module{} for _, file in range(files) { module.appendFile(file) } // 接入当前变量和 locals ctx.AddVariables(module.Variables) ctx.AddLocals(module.Locals) // 可在此使用 StaticContext 对模块字段进行额外处理 return module } root = buildConfig(".", StaticContextFromTFVars(command.TFVars))

对照当前仓库:internal/configs/static_evaluator.go 中StaticEvaluator的注释明确写道——"一个静态求值器包含构建 EvalContext 所需的信息,该 EvalContext 只理解'静态'(非状态)数据";internal/configs/static_scope.go 则承载了静态作用域的实现(同时服务于配置中 backend/cloud/encryption 等需要早期求值的场景,这点可从internal/configs/backend.gointernal/encryption/keyprovider.go等处对静态求值类型的引用得到印证)。这些实现与 RFC 的伪代码设计一一对应。

6.1 静态上下文设计要求

项目核心是一个求值上下文,与tofulang包中现有上下文目的相似,但需求有差异。任何静态求值器都必须能够:

  • 把 hcl 表达式或 block 求值为单个 cty 值,并对"为什么某表达式/block 无法转为 cty 值"提供详细洞察;
  • 用来自父静态上下文(对应父模块)的变量构造——主要用于沿模块调用栈向下传递值,同时维护引用;
  • 理解当前 locals 及其依赖。

6.2 三种实现路径

RFC 探讨了三条潜在实现路径:

  1. 为当前问题及其用例构建定制方案:不与现有求值上下文共享;原型阶段即采用此方法;开发期灵活;不破坏其他包;但测试必须从零编写。
  2. 以新管道复用tofulang包的现有组件:可建立在已验证逻辑之上;组件可按需替换、较为灵活;但可能需要重构拟复用的组件,且可能因现有测试覆盖不足而意外破坏其他包。
  3. 复用tofulang包中的现有求值器/作用域构造:需要重新设计这些组件以在两种模式下工作;核心逻辑大多已实现;但由于现有测试覆盖不佳,破坏其他包的可能性高;重构规模越大,越可能需要"别扭"的适配。

该决策被推迟到实际实施阶段——这是深度的技术调研与讨论,不会显著影响本 RFC 的提议方案。但所有方案都应适配足够相似的接口,以免阻塞依赖任务的开发。

七、可被静态求值解锁的依赖问题

为更好理解要解决的确切问题,RFC 概述了可用静态求值上下文解决的部分问题。

7.1 Backend 配置

核心实现落地后,这可能是最容易实现的部分。原型阶段的笔记:

  • configs.Backend在配置加载时保存配置体而不求值它;
  • internal/command/meta_backend.go 的backendInitFromConfig()负责求值该体——它发生在图构建/求值之前,可视为配置加载阶段的延伸;
  • 可以把StaticContext的副本暂存在configs.Backend中,并在backendInitFromConfig()中使用它提供求值上下文,以便解码为给定 backend schema——原型中将其暂存于此处是让其工作起来的简单方式;
  • 别忘了更新configs.Backend.Hash()函数,它用于检测任何变更。

7.2 模块源地址

模块源地址必须在 init 时已知,因为它们会被下载并归入.terraform/modules。可通过使用限定在当前模块作用域的静态求值器检查 source 的hcl.Expression来实现。原型笔记:

  • config.ModuleCall中创建SourceExpression字段,初始不设置config.ModuleCall.Source字段;
  • NewModule构造函数中利用可用的静态上下文求值所有config.ModuleCall的 source 字段,检查错误引用及其他错误。

(对照源码:internal/configs/module_call.go 中ModuleCall.Source保存原始hcl.ExpressionSourceAddr/SourceAddrRaw/SourceSet保存解析结果,正是"先存表达式、需要时再解析"模式的体现。)

许多其他被阻塞的问题都遵循"在配置加载的第一阶段存储表达式、需要时再求值"这一高度相似的模式,故在此略去。

7.3 加密配置

加密功能试图成为独立包,努力限制对 OpenTofu 代码的依赖,未来有可能被独立使用。它直接使用 hcl 库,不遵循 OpenTofu 代码库的其他模式,这可能对 Static Evaluation Context 接口的设计产生显著影响(从源码引用关系看,internal/encryption/keyprovider.go 与 internal/encryption/methods.go 均已接入静态求值类型)。

7.4 Provider 迭代

由于展开复杂性,该部分在配套的 rfc/20240513-static-evaluation-providers.md(Provider 求值 RFC)中详述。核心要点:

  • 允许在provider块中使用for_eachalias必须同时设置,默认 provider 配置始终是单例),for_each参数最初仅允许早求值已知的值(输入变量及其派生的 locals),动态引用(如数据源)暂被禁止;
  • provider 引用语法扩展为aws.by_region["us"]形式——方括号内的索引段可以是任意 HCL 表达式,但必须仅由规划期已知的值派生、结果可转换为 string 且必须匹配已声明 provider 配置的某个实例 key;
  • resource/module块的providers参数可引用动态实例 key(如aws.by_region[each.key]),此处的表达式由主语言运行时动态求值,不受静态求值约束;
  • 测试场景语言(.tftest.hcl)中的provider/mock_provider/run.providers也随之调整:与主模块对应配置必须保持for_each一致性;
  • 明确不支持:providerfor_each与关联资源的for_each使用完全相同表达式(OpenTofu 会检测并给出"过于相似"警告,因为销毁资源时 provider 实例必须比资源实例至少多存活一个 plan/apply 周期);把 provider 引用当普通值传递;一个资源跨多个 provider 配置;在模块间传递 provider 实例集合;provider块中使用count(参数保留但暂不实现)。

对照源码:internal/configs/provider.go 中Provider结构已含ForEach hcl.ExpressionInstances map[addrs.InstanceKey]instances.RepetitionData字段,并在alias缺失时禁止for_each(第 110-115 行),同时通过evalchecks.EvaluateForEachExpression求值for_each并填充实例映射(第 172-191 行)——provider 迭代功能已按 RFC 设计落地。

八、开发路径、阻塞项与性能考量

8.1 分阶段开发路径

鉴于变更范围,这是一项可能跨越多个版本发布的大量工作。RFC 明确反对在特性分支上长期开发或冻结所有相关代码,而是将其拆分为更小、离散、可测试的组件,部分组件可并行开发。每个组件添加/修改之前、期间、之后都补充测试。核心实现主要由 OpenTofu 核心团队完成;社区成员可以在静态求值解锁的、隔离且定义良好的 issue 上独立工作。

8.2 阻塞项

  • OpenTofu 现有测试零散且比期望稀疏,实现各阶段前后都需要补充测试覆盖;
  • 重构某个组件前应检查其代码覆盖率,以指导所需补充的测试;目标不是 100%,而是用覆盖率作为理解现有测试的工具;
  • 应编写一份全面的 e2e 测试指南(跟踪 issue 见 RFC 正文)。

8.3 性能考量

  • 多次解析配置:由于 command 包的部分重构,当前目录中的配置在许多步骤中被多次加载、解析和求值。静态求值会增加该动作的开销,值得聚焦于容易消除重复配置加载的地方;应创建/更新 issue 追踪 command 包清理工作;
  • 静态求值器开销:选择静态求值器实现方案时应关注性能。

九、开放问题

  • 是否支持在变量缺失时交互式询问值?这已是既有模式,但可能需要额外工作,或许推迟到后续迭代更稳妥(参考 3.1 节关于 provider 配置的注记);
  • 是否在静态求值上下文中支持 OpenTofu 核心函数?很可能支持,因为接入相当简单;
  • 是否在静态求值阶段支持 provider 函数?倾向不支持(无充分理由时),开发成本可能显著而收益甚微。检测有人试图在表达式/体中调用 provider 函数并标记该表达式结果为 dynamic 是很容易的。

十、未来考量:静态模块输出

一个很有价值的场景是:引入单个模块来定义组织内多个项目依赖的 sources 和版本,从而支持:

module "mycompany" { source = "git::.../sources" } module "capability" { source = ${module.mycompany.some_component} } module "other_capability" { source = ${module.mycompany.other_component} }

父模块引用的所有模块都会被下载并加入配置图,而无需理解任何相互依赖关系。要实现这一点,需要重写配置构建器以感知状态求值器,并增加该组件的复杂度。RFC 作者对工程投入是否值得持保留态度,但认为值得调研。

Static Module Expansion(rfc/20240513-static-evaluation/module-expansion.md)因所需架构变更巨大而暂被禁止;当前实现了一个受限版本——仅允许 provider 别名通过 for_each/count 指定。

十一、潜在替代方案

  • Terragrunt 等工具:在 OpenTofu 之上提供抽象层,许多用户认为有益。把部分功能内建到 OpenTofu 意味着开箱即用、无需额外工具;同时 terragrunt 可以更专注于编排多个 OpenTofu 项目间的复杂基础设施这类难题,而非修补 OpenTofu 的限制。
  • 独立的预处理器(pre-processor):需要设计并实现一套完全独立的语言来预处理配置,且难以与任何现有 OpenTofu 构造集成。

结语:从 RFC 到源码的落地轨迹

通过对照当前仓库可以看到,这份 2024 年 5 月的 RFC 提出的"Static Evaluation Context"设计已在 OpenTofu 中逐步落地为真实代码:StaticModuleCallStaticEvaluatorStaticIdentifier构成了静态求值核心(internal/configs/static_evaluator.go 与 internal/configs/static_scope.go);BuildConfig的递归加载链把静态调用信息沿模块树传递(internal/configs/config_build.go);providerfor_each及其实例映射已写入configs.Provider(internal/configs/provider.go),配套的 provider 求值 RFC 中的地址语法、测试场景规则与状态格式设计也一并在 rfc/20240513-static-evaluation-providers.md 中给出。对读者而言,理解本文的静态/动态两阶段求值模型,是深入阅读 OpenTofu 配置加载与图求值源码(configslangtofu三个包)的最佳切入点。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询