Skip to content

Commit

Permalink
Adding plan modifier to set ready_for_renewal true in state if validi…
Browse files Browse the repository at this point in the history
…ty_period_hours is zero or early_renewal_hours is greater than or equal to validity_period_hours at time of locally or self-signed cert creation (#268)
  • Loading branch information
bendbennett committed Oct 5, 2022
1 parent 9115ea8 commit 60601c7
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 0 deletions.
57 changes: 57 additions & 0 deletions internal/provider/attribute_plan_modifier/default_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"

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

// defaultValueAttributePlanModifier specifies a default value (attr.Value) for an attribute.
Expand Down Expand Up @@ -42,3 +44,58 @@ func (apm *defaultValueAttributePlanModifier) Modify(_ context.Context, req tfsd

res.AttributePlan = apm.DefaultValue
}

// readyForRenewalAttributePlanModifier determines whether the certificate is ready for renewal.
type readyForRenewalAttributePlanModifier struct {
}

// ReadyForRenewal is an helper to instantiate a defaultValueAttributePlanModifier.
func ReadyForRenewal() tfsdk.AttributePlanModifier {
return &readyForRenewalAttributePlanModifier{}
}

var _ tfsdk.AttributePlanModifier = (*readyForRenewalAttributePlanModifier)(nil)

func (apm *readyForRenewalAttributePlanModifier) Description(ctx context.Context) string {
return apm.MarkdownDescription(ctx)
}

func (apm *readyForRenewalAttributePlanModifier) MarkdownDescription(ctx context.Context) string {
return "Sets the value of ready_for_renewal depending on value of validity_period_hours and early_renewal_hours"
}

func (apm *readyForRenewalAttributePlanModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, res *tfsdk.ModifyAttributePlanResponse) {
var validityPeriodHours types.Int64

res.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("validity_period_hours"), &validityPeriodHours)...)
if res.Diagnostics.HasError() {
return
}

if validityPeriodHours.Value == 0 {
res.AttributePlan = types.Bool{
Value: true,
}

return
}

var earlyRenewalHours types.Int64

res.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("early_renewal_hours"), &earlyRenewalHours)...)
if res.Diagnostics.HasError() {
return
}

if earlyRenewalHours.IsNull() || earlyRenewalHours.IsUnknown() {
return
}

if earlyRenewalHours.Value >= validityPeriodHours.Value {
res.AttributePlan = types.Bool{
Value: true,
}

return
}
}
19 changes: 19 additions & 0 deletions internal/provider/resource_locally_signed_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ func (r *locallySignedCertResource) GetSchema(_ context.Context) (tfsdk.Schema,
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
attribute_plan_modifier.DefaultValue(types.Bool{Value: false}),
attribute_plan_modifier.ReadyForRenewal(),
},
Description: "Is the certificate either expired (i.e. beyond the `validity_period_hours`) " +
"or ready for an early renewal (i.e. within the `early_renewal_hours`)?",
Expand Down Expand Up @@ -267,6 +268,24 @@ func (r *locallySignedCertResource) Create(ctx context.Context, req resource.Cre
return
}

// Check whether the certificate is ready for renewal at the time of creation
if newState.EarlyRenewalHours.Value >= newState.ValidityPeriodHours.Value && newState.ValidityPeriodHours.Value > 0 {
tflog.Warn(ctx, "Certificate is ready for renewal at time of creation")
res.Diagnostics.AddWarning(
"Certificate is ready for renewal at time of creation",
fmt.Sprintf("Early renewal hours (%d) is greater than or equal to validity period hours (%d)", newState.EarlyRenewalHours.Value, newState.ValidityPeriodHours.Value),
)
}

// Check whether the certificate is already expired at the time of creation
if newState.ValidityPeriodHours.Value == 0 {
tflog.Warn(ctx, "Certificate is already expired at time of creation")
res.Diagnostics.AddWarning(
"Certificate is already expired at time of creation",
"Validity period hours is zero",
)
}

// Store the certificate into the state
tflog.Debug(ctx, "Storing locally signed certificate into the state")
newState.ID = types.String{Value: certificate.id}
Expand Down
48 changes: 48 additions & 0 deletions internal/provider/resource_locally_signed_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,54 @@ func TestResourceLocallySignedCert_DetectExpired_Refresh(t *testing.T) {
overridableTimeFunc = oldNow
}

func TestResourceLocallySignedCert_ReadyForRenewal_ValidityPeriodZero(t *testing.T) {
oldNow := overridableTimeFunc
r.UnitTest(t, r.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
PreCheck: setTimeForTest("2019-06-14T12:00:00Z"),
Steps: []r.TestStep{
{
Config: locallySignedCertConfig(0, 0),
ExpectNonEmptyPlan: true,
Check: r.TestCheckResourceAttr("tls_locally_signed_cert.test", "ready_for_renewal", "true"),
},
},
})
overridableTimeFunc = oldNow
}

func TestResourceLocallySignedCert_ReadyForRenewal_EarlyRenewalGreaterThanValidityPeriod(t *testing.T) {
oldNow := overridableTimeFunc
r.UnitTest(t, r.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
PreCheck: setTimeForTest("2019-06-14T12:00:00Z"),
Steps: []r.TestStep{
{
Config: locallySignedCertConfig(1, 2),
ExpectNonEmptyPlan: true,
Check: r.TestCheckResourceAttr("tls_locally_signed_cert.test", "ready_for_renewal", "true"),
},
},
})
overridableTimeFunc = oldNow
}

func TestResourceLocallySignedCert_ReadyForRenewal_EarlyRenewalEqualsValidityPeriod(t *testing.T) {
oldNow := overridableTimeFunc
r.UnitTest(t, r.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
PreCheck: setTimeForTest("2019-06-14T12:00:00Z"),
Steps: []r.TestStep{
{
Config: locallySignedCertConfig(1, 1),
ExpectNonEmptyPlan: true,
Check: r.TestCheckResourceAttr("tls_locally_signed_cert.test", "ready_for_renewal", "true"),
},
},
})
overridableTimeFunc = oldNow
}

func TestResourceLocallySignedCert_RecreatesAfterExpired(t *testing.T) {
oldNow := overridableTimeFunc
var previousCert string
Expand Down
19 changes: 19 additions & 0 deletions internal/provider/resource_self_signed_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func (r *selfSignedCertResource) GetSchema(_ context.Context) (tfsdk.Schema, dia
Computed: true,
PlanModifiers: []tfsdk.AttributePlanModifier{
attribute_plan_modifier.DefaultValue(types.Bool{Value: false}),
attribute_plan_modifier.ReadyForRenewal(),
},
Description: "Is the certificate either expired (i.e. beyond the `validity_period_hours`) " +
"or ready for an early renewal (i.e. within the `early_renewal_hours`)?",
Expand Down Expand Up @@ -423,6 +424,24 @@ func (r *selfSignedCertResource) Create(ctx context.Context, req resource.Create
return
}

// Check whether the certificate is ready for renewal at the time of creation
if newState.EarlyRenewalHours.Value >= newState.ValidityPeriodHours.Value && newState.ValidityPeriodHours.Value > 0 {
tflog.Warn(ctx, "Certificate is ready for renewal at time of creation")
res.Diagnostics.AddWarning(
"Certificate is ready for renewal at time of creation",
fmt.Sprintf("Early renewal hours (%d) is greater than or equal to validity period hours (%d)", newState.EarlyRenewalHours.Value, newState.ValidityPeriodHours.Value),
)
}

// Check whether the certificate is already expired at the time of creation
if newState.ValidityPeriodHours.Value == 0 {
tflog.Warn(ctx, "Certificate is already expired at time of creation")
res.Diagnostics.AddWarning(
"Certificate is already expired at time of creation",
"Validity period hours is zero",
)
}

// Store the certificate into the state
tflog.Debug(ctx, "Storing self signed certificate into the state")
newState.ID = types.String{Value: certificate.id}
Expand Down
48 changes: 48 additions & 0 deletions internal/provider/resource_self_signed_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,54 @@ func TestResourceSelfSignedCert_DetectExpired_Refresh(t *testing.T) {
overridableTimeFunc = oldNow
}

func TestResourceSelfSignedCert_ReadyForRenewal_ValidityPeriodZero(t *testing.T) {
oldNow := overridableTimeFunc
r.UnitTest(t, r.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
PreCheck: setTimeForTest("2019-06-14T12:00:00Z"),
Steps: []r.TestStep{
{
Config: selfSignedCertConfig(0, 0),
ExpectNonEmptyPlan: true,
Check: r.TestCheckResourceAttr("tls_self_signed_cert.test1", "ready_for_renewal", "true"),
},
},
})
overridableTimeFunc = oldNow
}

func TestResourceSelfSignedCert_ReadyForRenewal_EarlyRenewalGreaterThanValidityPeriod(t *testing.T) {
oldNow := overridableTimeFunc
r.UnitTest(t, r.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
PreCheck: setTimeForTest("2019-06-14T12:00:00Z"),
Steps: []r.TestStep{
{
Config: selfSignedCertConfig(1, 2),
ExpectNonEmptyPlan: true,
Check: r.TestCheckResourceAttr("tls_self_signed_cert.test1", "ready_for_renewal", "true"),
},
},
})
overridableTimeFunc = oldNow
}

func TestResourceSelfSignedCert_ReadyForRenewal_EarlyRenewalEqualsValidityPeriod(t *testing.T) {
oldNow := overridableTimeFunc
r.UnitTest(t, r.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
PreCheck: setTimeForTest("2019-06-14T12:00:00Z"),
Steps: []r.TestStep{
{
Config: selfSignedCertConfig(1, 1),
ExpectNonEmptyPlan: true,
Check: r.TestCheckResourceAttr("tls_self_signed_cert.test1", "ready_for_renewal", "true"),
},
},
})
overridableTimeFunc = oldNow
}

func TestResourceSelfSignedCert_RecreatesAfterExpired(t *testing.T) {
oldNow := overridableTimeFunc
var previousCert string
Expand Down

0 comments on commit 60601c7

Please sign in to comment.