Go语言 Terraform插件框架中更改的属性

uttx8gqw  于 2023-04-03  发布在  Go
关注(0)|答案(1)|浏览(147)

我正在使用新的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"
        },
        }
    }
    ...

当我运行地形计划时,它说一切正常:
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中未更改的属性是my host。
问题是:为什么Terraform认为一切都会改变?我如何防止它,所以只有ns 1或ns 2值host/ip会被更新?

kmbjn2e3

kmbjn2e31#

我找到了一个解决方案- PlanModifier:https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#common-use-case-attribute-plan-modifiers
使用PlanModifierUseStateForUnknown()函数:
UseStateForUnknown():复制先前的状态值(如果不为空)。这对于减少(应用后已知)已知不随时间变化的计算属性的计划输出很有用。
Schema现在看起来像这样:

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计划中的输出看起来应该是这样的。
我不知道这是否是最好的解决方案,但它的作品:)测试Terraform 1.4.2.

相关问题