Archived
Preserve unknown computed fields from state during dns update
This commit is contained in:
@@ -198,18 +198,26 @@ func (r *dnsRecordResource) Read(ctx context.Context, req resource.ReadRequest,
|
|||||||
|
|
||||||
func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
var plan dnsRecordResourceModel
|
var plan dnsRecordResourceModel
|
||||||
|
var state dnsRecordResourceModel
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||||
if resp.Diagnostics.HasError() {
|
if resp.Diagnostics.HasError() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domainID, recordID, err := parseDNSRecordID(plan.ID.ValueString())
|
domainID, recordID, err := parseDNSRecordID(state.ID.ValueString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Invalid resource ID", err.Error())
|
resp.Diagnostics.AddError("Invalid resource ID", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domainName := strings.TrimSpace(plan.DomainName.ValueString())
|
domainName := ""
|
||||||
|
if !plan.DomainName.IsNull() && !plan.DomainName.IsUnknown() {
|
||||||
|
domainName = strings.TrimSpace(plan.DomainName.ValueString())
|
||||||
|
}
|
||||||
|
if domainName == "" && !state.DomainName.IsNull() && !state.DomainName.IsUnknown() {
|
||||||
|
domainName = strings.TrimSpace(state.DomainName.ValueString())
|
||||||
|
}
|
||||||
if domainName == "" {
|
if domainName == "" {
|
||||||
_, resolvedDomainName, err := r.clientProvider.client.GetRootDomain(ctx, plan.Hostname.ValueString())
|
_, resolvedDomainName, err := r.clientProvider.client.GetRootDomain(ctx, plan.Hostname.ValueString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -226,13 +234,13 @@ func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateReq := dynuclient.UpdateDNSRecordRequest{
|
updateReq := dynuclient.UpdateDNSRecordRequest{
|
||||||
NodeName: recordNodeName(plan.NodeName, plan.Hostname, domainName),
|
NodeName: recordNodeName(preferKnownString(plan.NodeName, state.NodeName), plan.Hostname, domainName),
|
||||||
RecordType: recordType,
|
RecordType: recordType,
|
||||||
Content: stringPointerFromOptionalContent(plan.Content),
|
Content: stringPointerFromOptionalContent(plan.Content),
|
||||||
TTL: int64FromOptional(plan.TTL),
|
TTL: int64FromOptional(preferKnownInt64(plan.TTL, state.TTL)),
|
||||||
State: boolPointerFromOptional(plan.State),
|
State: boolPointerFromOptional(preferKnownBool(plan.State, state.State)),
|
||||||
Group: stringFromOptional(plan.Group),
|
Group: stringFromOptional(preferKnownString(plan.Group, state.Group)),
|
||||||
Host: stringFromOptional(plan.Host),
|
Host: stringFromOptional(preferKnownString(plan.Host, state.Host)),
|
||||||
}
|
}
|
||||||
if !validateDNSRecordContentForType(updateReq.RecordType, updateReq.Content, dynamicIntent, &resp.Diagnostics) {
|
if !validateDNSRecordContentForType(updateReq.RecordType, updateReq.Content, dynamicIntent, &resp.Diagnostics) {
|
||||||
return
|
return
|
||||||
@@ -267,7 +275,7 @@ func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
nextState := mapDNSRecordToState(*record, dynamicIntent)
|
nextState := mapDNSRecordToState(*record, dynamicIntent)
|
||||||
nextState.ID = types.StringValue(formatDNSRecordID(record.DomainID, record.ID))
|
nextState.ID = state.ID
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &nextState)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &nextState)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,6 +390,36 @@ func stringFromOptional(value types.String) string {
|
|||||||
return strings.TrimSpace(value.ValueString())
|
return strings.TrimSpace(value.ValueString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func preferKnownString(planValue types.String, stateValue types.String) types.String {
|
||||||
|
if !planValue.IsNull() && !planValue.IsUnknown() {
|
||||||
|
return planValue
|
||||||
|
}
|
||||||
|
if !stateValue.IsNull() && !stateValue.IsUnknown() {
|
||||||
|
return stateValue
|
||||||
|
}
|
||||||
|
return planValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func preferKnownInt64(planValue types.Int64, stateValue types.Int64) types.Int64 {
|
||||||
|
if !planValue.IsNull() && !planValue.IsUnknown() {
|
||||||
|
return planValue
|
||||||
|
}
|
||||||
|
if !stateValue.IsNull() && !stateValue.IsUnknown() {
|
||||||
|
return stateValue
|
||||||
|
}
|
||||||
|
return planValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func preferKnownBool(planValue types.Bool, stateValue types.Bool) types.Bool {
|
||||||
|
if !planValue.IsNull() && !planValue.IsUnknown() {
|
||||||
|
return planValue
|
||||||
|
}
|
||||||
|
if !stateValue.IsNull() && !stateValue.IsUnknown() {
|
||||||
|
return stateValue
|
||||||
|
}
|
||||||
|
return planValue
|
||||||
|
}
|
||||||
|
|
||||||
func stringPointerFromOptionalContent(value types.String) *string {
|
func stringPointerFromOptionalContent(value types.String) *string {
|
||||||
if value.IsNull() || value.IsUnknown() {
|
if value.IsNull() || value.IsUnknown() {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func TestIntegrationResourceDNSRecordLifecycleAndImport(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateResp := resource.UpdateResponse{State: tfsdk.State{Schema: schemaResp.Schema}}
|
updateResp := resource.UpdateResponse{State: tfsdk.State{Schema: schemaResp.Schema}}
|
||||||
r.Update(ctx, resource.UpdateRequest{Plan: plan}, &updateResp)
|
r.Update(ctx, resource.UpdateRequest{Plan: plan, State: createResp.State}, &updateResp)
|
||||||
if updateResp.Diagnostics.HasError() {
|
if updateResp.Diagnostics.HasError() {
|
||||||
t.Fatalf("update diagnostics: %v", updateResp.Diagnostics)
|
t.Fatalf("update diagnostics: %v", updateResp.Diagnostics)
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ func TestIntegrationResourceDNSRecordDynamicAStateStableAndTransitionToStatic(t
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateResp := resource.UpdateResponse{State: tfsdk.State{Schema: schemaResp.Schema}}
|
updateResp := resource.UpdateResponse{State: tfsdk.State{Schema: schemaResp.Schema}}
|
||||||
r.Update(ctx, resource.UpdateRequest{Plan: plan}, &updateResp)
|
r.Update(ctx, resource.UpdateRequest{Plan: plan, State: createResp.State}, &updateResp)
|
||||||
if updateResp.Diagnostics.HasError() {
|
if updateResp.Diagnostics.HasError() {
|
||||||
t.Fatalf("update diagnostics: %v", updateResp.Diagnostics)
|
t.Fatalf("update diagnostics: %v", updateResp.Diagnostics)
|
||||||
}
|
}
|
||||||
@@ -219,6 +219,89 @@ func TestIntegrationResourceDNSRecordDynamicAStateStableAndTransitionToStatic(t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntegrationResourceDNSRecordUpdateUsesStateIDWhenPlanIDUnknown(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
fake := fakedynu.NewServer()
|
||||||
|
defer fake.Close()
|
||||||
|
|
||||||
|
r := NewDNSRecordResource().(*dnsRecordResource)
|
||||||
|
configureResource(t, r, fake.BaseURL())
|
||||||
|
|
||||||
|
var schemaResp resource.SchemaResponse
|
||||||
|
r.Schema(ctx, resource.SchemaRequest{}, &schemaResp)
|
||||||
|
|
||||||
|
createPlan := dnsRecordResourceModel{
|
||||||
|
Hostname: types.StringValue("api.a.example.com"),
|
||||||
|
RecordType: types.StringValue("TXT"),
|
||||||
|
Content: types.StringValue("v=one"),
|
||||||
|
TTL: types.Int64Value(60),
|
||||||
|
State: types.BoolValue(true),
|
||||||
|
Group: types.StringValue("test-group"),
|
||||||
|
Host: types.StringValue("test-host"),
|
||||||
|
NodeName: types.StringValue("test-node"),
|
||||||
|
}
|
||||||
|
|
||||||
|
plan := tfsdk.Plan{Schema: schemaResp.Schema}
|
||||||
|
if diags := plan.Set(ctx, &createPlan); diags.HasError() {
|
||||||
|
t.Fatalf("set create plan diagnostics: %v", diags)
|
||||||
|
}
|
||||||
|
|
||||||
|
createResp := resource.CreateResponse{State: tfsdk.State{Schema: schemaResp.Schema}}
|
||||||
|
r.Create(ctx, resource.CreateRequest{Plan: plan}, &createResp)
|
||||||
|
if createResp.Diagnostics.HasError() {
|
||||||
|
t.Fatalf("create diagnostics: %v", createResp.Diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
var state dnsRecordResourceModel
|
||||||
|
if diags := createResp.State.Get(ctx, &state); diags.HasError() {
|
||||||
|
t.Fatalf("state get diagnostics: %v", diags)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePlan := state
|
||||||
|
updatePlan.ID = types.StringUnknown()
|
||||||
|
updatePlan.DomainID = types.Int64Unknown()
|
||||||
|
updatePlan.DomainName = types.StringUnknown()
|
||||||
|
updatePlan.Dynamic = types.BoolUnknown()
|
||||||
|
updatePlan.Group = types.StringUnknown()
|
||||||
|
updatePlan.Host = types.StringUnknown()
|
||||||
|
updatePlan.NodeName = types.StringUnknown()
|
||||||
|
updatePlan.UpdatedOn = types.StringUnknown()
|
||||||
|
updatePlan.Content = types.StringValue("v=two")
|
||||||
|
|
||||||
|
plan = tfsdk.Plan{Schema: schemaResp.Schema}
|
||||||
|
if diags := plan.Set(ctx, &updatePlan); diags.HasError() {
|
||||||
|
t.Fatalf("set update plan diagnostics: %v", diags)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateResp := resource.UpdateResponse{State: tfsdk.State{Schema: schemaResp.Schema}}
|
||||||
|
r.Update(ctx, resource.UpdateRequest{
|
||||||
|
Plan: plan,
|
||||||
|
State: createResp.State,
|
||||||
|
}, &updateResp)
|
||||||
|
if updateResp.Diagnostics.HasError() {
|
||||||
|
t.Fatalf("update diagnostics: %v", updateResp.Diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diags := updateResp.State.Get(ctx, &state); diags.HasError() {
|
||||||
|
t.Fatalf("updated state diagnostics: %v", diags)
|
||||||
|
}
|
||||||
|
if state.ID.IsNull() || state.ID.IsUnknown() {
|
||||||
|
t.Fatalf("expected ID to remain known after update")
|
||||||
|
}
|
||||||
|
if state.Content.ValueString() != "v=two" {
|
||||||
|
t.Fatalf("expected updated content, got %q", state.Content.ValueString())
|
||||||
|
}
|
||||||
|
if state.Group.ValueString() != "test-group" {
|
||||||
|
t.Fatalf("expected group preserved from state, got %q", state.Group.ValueString())
|
||||||
|
}
|
||||||
|
if state.Host.ValueString() != "test-host" {
|
||||||
|
t.Fatalf("expected host preserved from state, got %q", state.Host.ValueString())
|
||||||
|
}
|
||||||
|
if state.NodeName.ValueString() != "test-node" {
|
||||||
|
t.Fatalf("expected node_name preserved from state, got %q", state.NodeName.ValueString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func configureResource(t *testing.T, r resource.ResourceWithConfigure, baseURL string) {
|
func configureResource(t *testing.T, r resource.ResourceWithConfigure, baseURL string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
resp := resource.ConfigureResponse{}
|
resp := resource.ConfigureResponse{}
|
||||||
|
|||||||
Reference in New Issue
Block a user