Skip to content

Commit

Permalink
Setting attributes within nested subject block to computed and to use…
Browse files Browse the repository at this point in the history
… plan modifier which examines state and plan for string and list types (#284)
  • Loading branch information
bendbennett committed Oct 12, 2022
1 parent 7733cf3 commit 0494451
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package attribute_plan_modifier

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func RequiresReplaceNullEmpty() tfsdk.AttributePlanModifier {
return requiresReplaceNullEmpty{}
}

type requiresReplaceNullEmpty struct{}

func (r requiresReplaceNullEmpty) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) {
if req.AttributeConfig == nil || req.AttributePlan == nil || req.AttributeState == nil {
// shouldn't happen, but let's not panic if it does
return
}

emptyStateString := types.String{}
emptyPlanString := types.String{
Unknown: true,
}

if req.AttributePlan.Equal(emptyPlanString) {
resp.AttributePlan = emptyStateString
return
}

emptyStateList := types.List{
Null: true,
ElemType: types.StringType,
}
emptyStateListEmptyElems := types.List{
ElemType: types.StringType,
Elems: []attr.Value{},
}
emptyPlanList := types.List{
Unknown: true,
ElemType: types.StringType,
}

if req.AttributePlan.Equal(emptyPlanList) && req.AttributeState.Equal(emptyStateList) {
resp.AttributePlan = emptyStateList
return
}

if req.AttributePlan.Equal(emptyPlanList) && req.AttributeState.Equal(emptyStateListEmptyElems) {
resp.AttributePlan = emptyStateListEmptyElems
return
}

if req.AttributePlan.Equal(req.AttributeState) {
// if the plan and the state are in agreement, this attribute
// isn't changing, don't require replace
return
}

resp.RequiresReplace = true
}

func (r requiresReplaceNullEmpty) Description(ctx context.Context) string {
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
}

func (r requiresReplaceNullEmpty) MarkdownDescription(ctx context.Context) string {
return "If the value of this attribute changes, Terraform will destroy and recreate the resource."
}
28 changes: 19 additions & 9 deletions internal/provider/resource_self_signed_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"

"github.com/hashicorp/terraform-provider-tls/internal/provider/attribute_plan_modifier"
)

Expand Down Expand Up @@ -235,24 +236,27 @@ func (r *selfSignedCertResource) GetSchema(_ context.Context) (tfsdk.Schema, dia
"organization": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `O`",
},
"common_name": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `CN`",
},
"organizational_unit": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `OU`",
},
Expand All @@ -261,48 +265,54 @@ func (r *selfSignedCertResource) GetSchema(_ context.Context) (tfsdk.Schema, dia
ElemType: types.StringType,
},
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `STREET`",
},
"locality": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `L`",
},
"province": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `ST`",
},
"country": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `C`",
},
"postal_code": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `PC`",
},
"serial_number": {
Type: types.StringType,
Optional: true,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
resource.RequiresReplace(),
attribute_plan_modifier.RequiresReplaceNullEmpty(),
},
Description: "Distinguished name: `SERIALNUMBER`",
},
Expand Down
56 changes: 56 additions & 0 deletions internal/provider/resource_self_signed_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

r "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

"github.com/hashicorp/terraform-provider-tls/internal/provider/fixtures"
tu "github.com/hashicorp/terraform-provider-tls/internal/provider/testutils"
Expand Down Expand Up @@ -192,6 +193,51 @@ func TestAccResourceSelfSignedCert_UpgradeFromVersion3_4_0(t *testing.T) {
})
}

func TestAccResourceSelfSignedCert_UpgradeFromVersion3_4_0_Preserve_Empty_Strings(t *testing.T) {
var id1, id2 string

config := `resource "tls_private_key" "example" {
algorithm = "ECDSA"
}
resource "tls_self_signed_cert" "example" {
private_key_pem = tls_private_key.example.private_key_pem
is_ca_certificate = true
subject {
organization = "Example"
}
# 3 Years
validity_period_hours = 24 * 365 * 3
allowed_uses = [
"cert_signing",
"crl_signing",
]
}`

r.Test(t, r.TestCase{
Steps: []r.TestStep{
{
ExternalProviders: providerVersion340(),
Config: config,
Check: r.ComposeTestCheckFunc(
testExtractResourceAttr("tls_self_signed_cert.example", "id", &id1),
),
},
{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
Config: config,
Check: r.ComposeTestCheckFunc(
testExtractResourceAttr("tls_self_signed_cert.example", "id", &id2),
testCheckAttributeValuesEqual(&id1, &id2),
),
},
},
})
}

func TestResourceSelfSignedCert_DetectExpiringAndExpired(t *testing.T) {
oldNow := overridableTimeFunc
r.UnitTest(t, r.TestCase{
Expand Down Expand Up @@ -753,3 +799,13 @@ func TestResourceSelfSignedCert_NoSubject(t *testing.T) {
},
})
}

func testCheckAttributeValuesEqual(i *string, j *string) r.TestCheckFunc {
return func(s *terraform.State) error {
if testStringValue(i) != testStringValue(j) {
return fmt.Errorf("attribute values are different, got %s and %s", testStringValue(i), testStringValue(j))
}

return nil
}
}

0 comments on commit 0494451

Please sign in to comment.