Terraform插件框架中的属性更改

huangapple go评论93阅读模式
英文:

Changed attributes in Terraform Plugin Framework

问题

我正在创建一个使用新的Terraform插件框架的自定义Terraform提供程序。

我想将以下对象保存到Terraform状态中:

"attributes": {
	"name_servers": {
	"ns1": {
		"host": "host1",
		"id": 100,
		"ip": "",
		"is_used": true
	},
	"ns2": {
		"host": "host2",
		"id": 101,
		"ip": "",
		"is_used": true,
	}
	},
	"domain": "example.com"
},

并且它与以下Terraform资源一起工作:

...
type FooResourceModel struct {
	Domain      types.String                `tfsdk:"domain"`
	NameServers map[string]NameServersModel `tfsdk:"name_servers"`
}

type NameServersModel struct {
	ID     types.Int64  `tfsdk:"id"`
	Host   types.String `tfsdk:"host"`
	IP     types.String `tfsdk:"ip"`
	IsUsed types.Bool   `tfsdk:"is_used"`
}


func (r *FooNSResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"domain": schema.StringAttribute{
				Required: true,
			},
			"name_servers": schema.MapNestedAttribute{
				Required: true,
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"id": schema.Int64Attribute{
							Computed: true,
						},
						"host": schema.StringAttribute{
							Optional: true,
							Computed: true,
						},
						"ip": schema.StringAttribute{
							Optional: true,
							Computed: true,
						},
						"is_used": schema.BoolAttribute{
							Computed: true,
						},
					},
				},
			},
		},
	}
}

func (r *FooNSResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
	var data *FooNSResourceModel
	diags := req.State.Get(ctx, &data)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	serviceName := data.ServiceName.ValueString()
	// Read Terraform prior state data into the model
	resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

	if resp.Diagnostics.HasError() {
		return
	}

	nameServers, err := getNs(r, serviceName)

	data.NameServers = nameServers

	diags = resp.State.Set(ctx, &data)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}
}

func getNs(r *FooNSResource, domain string) (map[string]NameServersModel, error) {
	nameServers := make(map[string]NameServersModel)
	var ids []uint64

	err := r.client.Get(domain, &ids)
	if err != nil {
		return nil, err
	}

	for key, id := range ids {
		nsResponse := NameServerResponse{}
		err := r.client.Get(id, &nsResponse)
		if err != nil {
			return nil, err
		}

		nameServers["ns"+strconv.Itoa(key+1)] = NameServersModel{
			ID:     types.Int64Value(int64(nsResponse.Id)),
			Host:   types.StringValue(nsResponse.GetHost()),
			IP:     types.StringValue(nsResponse.GetIP()),
			IsUsed: types.BoolValue(nsResponse.IsUsed),
		}
	}

	return nameServers, nil
}

func (r *FooNSResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
	serviceName := req.ID
	nameServers, err := getNs(r, serviceName)

	resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("domain"), serviceName)...)
	resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name_servers"), nameServers)...)
}
...

所以当我从API导入数据时,它会将其保存到Terraform状态中。

我在我的Terraform文件中有以下内容:

...
resource "my_custom_provider" "my-server" {
	service_name = "example.com"

	name_servers = {
		"ns1" = {
			host = "host1"
		},
		"ns2" = {
			host = "host2"
		},
	}
}
...

当我运行terraform plan时,它显示一切正常:

No changes. Your infrastructure matches the configuration.

但是当我添加ip值时:

...
resource "my_custom_provider" "my-server" {
	service_name = "example.com"

	name_servers = {
		"ns1" = {
			host = "host1"
		},
		"ns2" = {
			host = "host2"
			ip   = "127.0.0.1"
		},
	}
}
...

然后,Terraform希望更新所有内容:

Terraform will perform the following actions:

# my_custom_provider.my-server will be updated in-place
~ resource "my_custom_provider" "my-server" {
	~ name_servers = {
		~ "ns1" = {
			~ id      = 100 -> (known after apply)
			~ ip      = ""  -> (known after apply)
			~ is_used = true -> (known after apply)
				# (1 unchanged attribute hidden)
			},
		~ "ns2" = {
			~ id      = 101 -> (known after apply)
			~ ip      = ""  -> "127.0.0.1"
			~ is_used = true -> (known after apply)
				# (1 unchanged attribute hidden)
			},
		}
		# (1 unchanged attribute hidden)
	}

Plan: 0 to add, 1 to change, 0 to destroy.

name_servers中未更改的属性是我的主机。

问题是:
为什么Terraform认为将更改所有内容?如何防止它,只更新ns1或ns2的host/ip值?

英文:

I'm creating a custom terraform provider with the new Terraform Plugin Framework.

I want to save objects like this to terraform state:

	"attributes": {
		"name_servers": {
		"ns1": {
			"host": "host1",
			"id": 100,
			"ip": "",
			"is_used": true
		},
		"ns2": {
			"host": "host2",
			"id": 101,
			"ip": "",
			"is_used": true,
		}
		},
		"domain": "example.com"
	},

And it's working with following terraform resource:

	...
	type FooResourceModel struct {
		Domain types.String                `tfsdk:"domain"`
		NameServers map[string]NameServersModel `tfsdk:"name_servers"`
	}

	type NameServersModel struct {
		ID       types.Int64  `tfsdk:"id"`
		Host     types.String `tfsdk:"host"`
		IP       types.String `tfsdk:"ip"`
		IsUsed   types.Bool   `tfsdk:"is_used"`
	}


	func (r *FooNSResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
		resp.Schema = schema.Schema{
			Attributes: map[string]schema.Attribute{
				"domain": schema.StringAttribute{
					Required:            true,
				},
				"name_servers": schema.MapNestedAttribute{
					Required: true,
					NestedObject: schema.NestedAttributeObject{
						Attributes: map[string]schema.Attribute{
							"id": schema.Int64Attribute{
								Computed: true,
							},
							"host": schema.StringAttribute{
								Optional: true,
								Computed: true,
							},
							"ip": schema.StringAttribute{
								Optional: true,
								Computed: true,
							},
							"is_used": schema.BoolAttribute{
								Computed: true,
							},
						},
					},
				},
			},
		}
	}

	func (r *FooNSResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
		var data *FooNSResourceModel
		diags := req.State.Get(ctx, &data)
		resp.Diagnostics.Append(diags...)
		if resp.Diagnostics.HasError() {
			return
		}

		serviceName := data.ServiceName.ValueString()
		// Read Terraform prior state data into the model
		resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

		if resp.Diagnostics.HasError() {
			return
		}

		nameServers, err := getNs(r, serviceName)

		data.NameServers = nameServers

		diags = resp.State.Set(ctx, &data)
		resp.Diagnostics.Append(diags...)
		if resp.Diagnostics.HasError() {
			return
		}
	}

	func getNs(r *FooNSResource, domain string) (map[string]NameServersModel, error) {
		nameServers := make(map[string]NameServersModel)
		var ids []uint64

		err := r.client.Get(domain, &ids)
		if err != nil {
			return nil, err
		}

		for key, id := range ids {
			nsResponse := NameServerResponse{}
			err := r.client.Get(id, &nsResponse)
			if err != nil {
				return nil, err
			}

			nameServers["ns"+strconv.Itoa(key+1)] = NameServersModel{
				ID:       types.Int64Value(int64(nsResponse.Id)),
				Host:     types.StringValue(nsResponse.GetHost()),
				IP:       types.StringValue(nsResponse.GetIP()),
				IsUsed:   types.BoolValue(nsResponse.IsUsed),
			}
		}

		return nameServers, nil
	}

	func (r *FooNSResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
		serviceName := req.ID
		nameServers, err := getNs(r, serviceName)

		resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("domain"), serviceName)...)
		resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name_servers"), nameServers)...)
	}
	...

So when I'm importing data from API, it saves it into terraform state.

I have this in my Terraform file:

	...
	resource "my_custom_provider" "my-server" {
		service_name = "example.com"
	
		name_servers = {
		"ns1" = {
			host = "host1"
		},
		"ns2" = {
			host = "host2"
		},
		}
	}
	...

And when I run terraform plan, it says everything is fine:

No changes. Your infrastructure matches the configuration.

But when I add ip value:

	...
	resource "my_custom_provider" "my-server" {
		service_name = "example.com"
	
		name_servers = {
		"ns1" = {
			host = "host1"
		},
		"ns2" = {
			host = "host2"
			ip = "127.0.0.1"
		},
		}
	}
	...

then Terraform wants to update everything:

	Terraform will perform the following actions:

	# my_custom_provider.my-server will be updated in-place
	~ resource "my_custom_provider" "my-server" {
		~ name_servers = {
			~ "ns1" = {
				~ id        = 100 -> (known after apply)
				~ ip        = "" -> (known after apply)
				~ is_used   = true -> (known after apply)
					# (1 unchanged attribute hidden)
				},
			~ "ns2" = {
				~ id        = 101 -> (known after apply)
				~ ip        = "" -> "127.0.0.1"
				~ is_used   = true -> (known after apply)
					# (1 unchanged attribute hidden)
				},
			}
			# (1 unchanged attribute hidden)
		}

	Plan: 0 to add, 1 to change, 0 to destroy.

The unchanged attribute in name_servers is my host.

The question is:
Why Terraform thinks that everything will be changed? How can I prevent it, so only ns1 or ns2 values host/ip are going to be updated?

答案1

得分: 2

我找到了一个解决方案-PlanModifier: https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#common-use-case-attribute-plan-modifiers

使用PlanModifierUseStateForUnknown()函数:

UseStateForUnknown(): 如果先前的状态值不为空,则复制该值。这对于减少计算属性的(在应用后已知)计划输出很有用,这些属性在时间上已知不会更改。

现在的模式看起来是这样的:

    NestedObject: schema.NestedAttributeObject{
        Attributes: map[string]schema.Attribute{
            "id": schema.Int64Attribute{
                Computed: true,
                PlanModifiers: []planmodifier.Int64{
                    int64planmodifier.UseStateForUnknown(),
                },
            },
            "host": schema.StringAttribute{
                Optional: true,
                Computed: true,
                PlanModifiers: []planmodifier.String{
                    stringplanmodifier.UseStateForUnknown(),
                },
            },
            "ip": schema.StringAttribute{
                Optional: true,
                Computed: true,
                PlanModifiers: []planmodifier.String{
                    stringplanmodifier.UseStateForUnknown(),
                },
            },
            "is_used": schema.BoolAttribute{
                Computed: true,
                PlanModifiers: []planmodifier.Bool{
                    boolplanmodifier.UseStateForUnknown(),
                },
            },
        },
    },

对于每个属性,我都添加了计划修改器,现在terraform plan的输出看起来正常。

我不知道这是否是最佳解决方案,但它有效 Terraform插件框架中的属性更改 在Terraform 1.4.2上进行了测试。

英文:

I've found a solution - PlanModifier: https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#common-use-case-attribute-plan-modifiers

Use PlanModifier with UseStateForUnknown() function:

>UseStateForUnknown(): Copies the prior state value, if not null. This is useful for reducing (known after apply) plan outputs for computed attributes which are known to not change over time.

The Schema looks like this right now:

    NestedObject: schema.NestedAttributeObject{
        Attributes: map[string]schema.Attribute{
            "id": schema.Int64Attribute{
                Computed: true,
                PlanModifiers: []planmodifier.Int64{
                    int64planmodifier.UseStateForUnknown(),
                },
            },
            "host": schema.StringAttribute{
                Optional: true,
                Computed: true,
                PlanModifiers: []planmodifier.String{
                    stringplanmodifier.UseStateForUnknown(),
                },
            },
            "ip": schema.StringAttribute{
                Optional: true,
                Computed: true,
                PlanModifiers: []planmodifier.String{
                    stringplanmodifier.UseStateForUnknown(),
                },
            },
            "is_used": schema.BoolAttribute{
                Computed: true,
                PlanModifiers: []planmodifier.Bool{
                    boolplanmodifier.UseStateForUnknown(),
                },
            },
        },
    },

For every attribute I've added the plan modifier, and now output in terraform plan looks as it should.

I don't know if its a best solution, but it works Terraform插件框架中的属性更改 Tested on Terraform 1.4.2.

huangapple
  • 本文由 发表于 2023年3月30日 03:40:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/75881511.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定