Skip to content

Commit

Permalink
Merge pull request #927 from Juniper/923-check-behavior-of-ipv4_pool-…
Browse files Browse the repository at this point in the history
…resource-when-renaming

Eliminate apparent state churn with resource pool computed attributes
  • Loading branch information
chrismarget-j authored Oct 18, 2024
2 parents 904fd00 + 913e2fe commit 8f6d687
Show file tree
Hide file tree
Showing 16 changed files with 332 additions and 104 deletions.
185 changes: 185 additions & 0 deletions apstra/apstra_plan_modifier/use_null_state_for_unknown.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package apstraplanmodifier

// the contents of this file are based on:
// https://github.com/hashicorp/terraform-plugin-framework/blob/v1.9.0/resource/schema/stringplanmodifier/use_state_for_unknown.go
//
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
)

// UseNullStateForUnknown returns a plan modifier that copies a known prior state
// value into the planned value. Use this when it is known that an unconfigured
// value will remain the same after a resource update.
//
// To prevent Terraform errors, the framework automatically sets unconfigured
// and Computed attributes to an unknown value "(known after apply)" on update.
// Using this plan modifier will instead display the prior state value in the
// plan, unless a prior plan modifier adjusts the value.
func UseNullStateForUnknown() useNullStateForUnknownModifier {
return useNullStateForUnknownModifier{}
}

var (
_ planmodifier.Bool = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.Float64 = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.Int64 = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.List = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.Map = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.Number = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.Object = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.Set = (*useNullStateForUnknownModifier)(nil)
_ planmodifier.String = (*useNullStateForUnknownModifier)(nil)
)

// useNullStateForUnknownModifier implements the plan modifier.
type useNullStateForUnknownModifier struct{}

// Description returns a human-readable description of the plan modifier.
func (m useNullStateForUnknownModifier) Description(_ context.Context) string {
return "Once set, the value of this attribute in state will not change, even if it's null."
}

// MarkdownDescription returns a markdown description of the plan modifier.
func (m useNullStateForUnknownModifier) MarkdownDescription(_ context.Context) string {
return "Once set, the value of this attribute in state will not change, even if it's null."
}

// PlanModifyBool implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyBool(_ context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifyInt64 implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyInt64(_ context.Context, req planmodifier.Int64Request, resp *planmodifier.Int64Response) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifyFloat64 implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyFloat64(_ context.Context, req planmodifier.Float64Request, resp *planmodifier.Float64Response) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifyList implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyList(_ context.Context, req planmodifier.ListRequest, resp *planmodifier.ListResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifyMap implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyMap(_ context.Context, req planmodifier.MapRequest, resp *planmodifier.MapResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifyNumber implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyNumber(_ context.Context, req planmodifier.NumberRequest, resp *planmodifier.NumberResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifyObject implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyObject(_ context.Context, req planmodifier.ObjectRequest, resp *planmodifier.ObjectResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifySet implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifySet(_ context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}

// PlanModifyString implements the plan modification logic.
func (m useNullStateForUnknownModifier) PlanModifyString(_ context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}

// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}

resp.PlanValue = req.StateValue
}
13 changes: 9 additions & 4 deletions apstra/resources/asn_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/Juniper/apstra-go-sdk/apstra"
apstraplanmodifier "github.com/Juniper/terraform-provider-apstra/apstra/apstra_plan_modifier"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
Expand Down Expand Up @@ -95,20 +96,24 @@ func (o AsnPool) ResourceAttributes() map[string]resourceSchema.Attribute {
},
},
"total": resourceSchema.Int64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"status": resourceSchema.StringAttribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.String{apstraplanmodifier.UseNullStateForUnknown()},
},
"used": resourceSchema.Int64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"used_percentage": resourceSchema.Float64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Float64{apstraplanmodifier.UseNullStateForUnknown()},
},
}
}
Expand Down
14 changes: 10 additions & 4 deletions apstra/resources/asn_pool_range.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"math"

"github.com/Juniper/apstra-go-sdk/apstra"
apstraplanmodifier "github.com/Juniper/terraform-provider-apstra/apstra/apstra_plan_modifier"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
Expand Down Expand Up @@ -83,20 +85,24 @@ func (o AsnPoolRange) ResourceAttributes() map[string]resourceSchema.Attribute {
},
},
"total": resourceSchema.Int64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"status": resourceSchema.StringAttribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.String{apstraplanmodifier.UseNullStateForUnknown()},
},
"used": resourceSchema.Int64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"used_percentage": resourceSchema.Float64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Float64{apstraplanmodifier.UseNullStateForUnknown()},
},
}
}
Expand Down
22 changes: 12 additions & 10 deletions apstra/resources/integer_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/Juniper/apstra-go-sdk/apstra"
apstraplanmodifier "github.com/Juniper/terraform-provider-apstra/apstra/apstra_plan_modifier"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
Expand Down Expand Up @@ -95,23 +96,24 @@ func (o IntegerPool) ResourceAttributes() map[string]resourceSchema.Attribute {
},
},
"total": resourceSchema.Int64Attribute{
MarkdownDescription: "Total number of Integers in the Integer Pool.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"status": resourceSchema.StringAttribute{
MarkdownDescription: "Status of the Integer Pool. " +
"Note that this element is probably better read from a `data` source because it will be more up-to-date.",
Computed: true,
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.String{apstraplanmodifier.UseNullStateForUnknown()},
},
"used": resourceSchema.Int64Attribute{
MarkdownDescription: "Count of used Integers in the Integer Pool. " +
"Note that this element is probably better read from a `data` source because it will be more up-to-date.",
Computed: true,
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"used_percentage": resourceSchema.Float64Attribute{
MarkdownDescription: "Percent of used Integers in the Integer Pool. " +
"Note that this element is probably better read from a `data` source because it will be more up-to-date.",
Computed: true,
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Float64{apstraplanmodifier.UseNullStateForUnknown()},
},
}
}
Expand Down
14 changes: 10 additions & 4 deletions apstra/resources/integer_pool_range.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"math"

"github.com/Juniper/apstra-go-sdk/apstra"
apstraplanmodifier "github.com/Juniper/terraform-provider-apstra/apstra/apstra_plan_modifier"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
Expand Down Expand Up @@ -83,20 +85,24 @@ func (o IntegerPoolRange) ResourceAttributes() map[string]resourceSchema.Attribu
},
},
"total": resourceSchema.Int64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"status": resourceSchema.StringAttribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.String{apstraplanmodifier.UseNullStateForUnknown()},
},
"used": resourceSchema.Int64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Int64{apstraplanmodifier.UseNullStateForUnknown()},
},
"used_percentage": resourceSchema.Float64Attribute{
MarkdownDescription: "Mutable read-only is always null in a Resource. Use the matching Data Source for this information.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Float64{apstraplanmodifier.UseNullStateForUnknown()},
},
}
}
Expand Down
22 changes: 12 additions & 10 deletions apstra/resources/ipv4_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/Juniper/apstra-go-sdk/apstra"
apstraplanmodifier "github.com/Juniper/terraform-provider-apstra/apstra/apstra_plan_modifier"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
Expand Down Expand Up @@ -95,23 +96,24 @@ func (o Ipv4Pool) ResourceAttributes() map[string]resourceSchema.Attribute {
},
},
"total": resourceSchema.NumberAttribute{
MarkdownDescription: "Total number of addresses in the IPv4 pool.",
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Number{apstraplanmodifier.UseNullStateForUnknown()},
},
"status": resourceSchema.StringAttribute{
MarkdownDescription: "Status of the IPv4 pool. " +
"Note that this element is probably better read from a `data` source because it will be more up-to-date.",
Computed: true,
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.String{apstraplanmodifier.UseNullStateForUnknown()},
},
"used": resourceSchema.NumberAttribute{
MarkdownDescription: "Count of used addresses in the IPv4 pool. " +
"Note that this element is probably better read from a `data` source because it will be more up-to-date.",
Computed: true,
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Number{apstraplanmodifier.UseNullStateForUnknown()},
},
"used_percentage": resourceSchema.Float64Attribute{
MarkdownDescription: "Percent of used addresses in the IPv4 pool. " +
"Note that this element is probably better read from a `data` source because it will be more up-to-date.",
Computed: true,
MarkdownDescription: "Mutable read-only attribute is always null in a Resource. Use the matching Data Source for this information.",
Computed: true,
PlanModifiers: []planmodifier.Float64{apstraplanmodifier.UseNullStateForUnknown()},
},
}
}
Expand Down
Loading

0 comments on commit 8f6d687

Please sign in to comment.