如何让Terraform Provider中计算值的漂移触发更新?
Terraform Provider 漂移检测与更新触发问题
背景
输入参数
- name
- keepers(任意映射)
- some_attribute(字符串类型)
计算属性
- version
预期工作流程
- 接收输入参数并在内部计算一个值
- 将该值存储到由
name字段唯一标识的远程位置,name对应的存储位置在创建步骤中生成 - 存储完成后,远程服务器返回一个
version字符串 - 读取
name对应的存储时,始终返回最新的version - 不会将计算值存储到状态文件中,仅将
version作为计算属性存储 - 若
name输入参数变更,资源将被重新创建(重新创建意味着远程服务器会彻底删除原name存储,重新计算值,创建新的name存储位置并存储值,同时生成新的version) - 若
keepers或some_attribute变更,资源将被原地更新(原地更新意味着重新计算值,并在现有name存储位置下以新version存储) - 若有人在外部为
name存储位置创建了新的version,则会发生漂移。当Terraform读取name时,得到的version会与计划中的值不同,此时希望触发原地更新。
具体问题
上述1-7点均已按预期实现,但第8点无法正常工作。原本认为在Read步骤中将状态中的version设置为漂移后的版本,Terraform就能识别到需要进行更新。虽然它能检测到漂移,但并未触发更新,而是直接将当前状态中的version改为漂移后的版本(猜测这是因为它将其视为应使用的新计算值)。请问如何正确实现该需求?
当前代码实现
Schema定义
func (r *someResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "keepers": schema.MapAttribute{ ElementType: types.StringType, Optional: true, }, "some_attribute": schema.StringAttribute{ Optional: true, }, "version": schema.StringAttribute{ Computed: true, }, "name": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, }, } }
Read函数
func (r *someResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state someModelV0 diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } // Get most recent version number from remote storage version, err := remote.GetVersion(context.Background(), r.client, state.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("Read error") return } // Update the version in the state so that we know that drift has occured state.Version = types.StringValue(version) diags = resp.State.Set(ctx, state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } }
解决方案
问题核心在于Terraform默认会将计算属性的漂移视为“状态同步”而非“需要修正的差异”。要触发更新,需要通过计划阶段的校验标记差异,让Terraform意识到当前远程的version不符合预期,进而触发Update操作。
步骤1:修改version字段的Schema配置
给version添加计划修饰器,对比远程版本与状态版本,若不一致则标记差异:
"version": schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), func(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { // 创建资源时跳过校验 if req.State.Raw.IsNull() { return } var state someModelV0 diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } // 获取远程最新版本 remoteVersion, err := remote.GetVersion(ctx, r.client, state.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("Failed to fetch remote version", err.Error()) return } // 远程版本与状态版本不一致时,将计划值设为状态版本,触发差异 if remoteVersion != state.Version.ValueString() { resp.PlanValue = state.Version } }, }, },
步骤2:调整Read函数逻辑
Read阶段不再直接覆盖状态中的version,仅标记漂移并保留旧状态,直到Update执行完成:
func (r *someResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state someModelV0 diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } // 获取远程最新版本 remoteVersion, err := remote.GetVersion(ctx, r.client, state.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("Read error", err.Error()) return } // 版本不一致时标记漂移,不更新状态 if remoteVersion != state.Version.ValueString() { resp.Diagnostics.AddWarning( "Detected version drift", fmt.Sprintf("Remote version %s does not match state version %s. Update will be triggered.", remoteVersion, state.Version.ValueString()), ) return } // 版本一致时同步状态 state.Version = types.StringValue(remoteVersion) diags = resp.State.Set(ctx, state) resp.Diagnostics.Append(diags...) }
步骤3:确保Update函数处理漂移场景
在Update函数中重新计算值并推送至远程,更新状态中的version:
func (r *someResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan someModelV0 diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } // 根据输入参数重新计算值 computedValue := computeValue(plan.Keepers, plan.SomeAttribute) // 推送新值到远程并获取新version newVersion, err := remote.UpdateValue(ctx, r.client, plan.Name.ValueString(), computedValue) if err != nil { resp.Diagnostics.AddError("Failed to update value", err.Error()) return } // 更新状态中的version plan.Version = types.StringValue(newVersion) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) }
原理说明
- 计划修饰器在计划阶段对比远程与状态版本,不一致时将计划值设为状态版本,让Terraform识别出差异并触发Update。
- Read阶段保留旧状态版本,确保差异能被持续检测到,直到Update执行完成。
- Update阶段重新生成符合输入参数的计算值并推送,同步新的
version到状态,完成漂移修正。
内容的提问来源于stack exchange,提问作者Karl
相关产品推荐
相关产品推荐

