-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds read timeout with retry function to dnsimple_certificate data source. - Timout can be set in the plan data source definition and defaults to 5 minutes. - Retry checks are on the certificate state and run every 20 seconds. - During testing, certificates were issued between 40 seconds and 5 minutes after submitting the order.
- Loading branch information
Showing
5 changed files
with
135 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,21 @@ import ( | |
"fmt" | ||
"time" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
"github.com/terraform-providers/terraform-provider-dnsimple/internal/consts" | ||
"github.com/terraform-providers/terraform-provider-dnsimple/internal/framework/common" | ||
"github.com/terraform-providers/terraform-provider-dnsimple/internal/framework/utils" | ||
) | ||
|
||
const ( | ||
CertificateConverged = "certificate_converged" | ||
CertificateFailed = "certificate_failed" | ||
CertificateTimeout = "certificate_timeout" | ||
) | ||
|
||
// Ensure provider defined types fully satisfy framework interfaces. | ||
|
@@ -25,13 +36,14 @@ type CertificateDataSource struct { | |
|
||
// CertificateDataSourceModel describes the data source data model. | ||
type CertificateDataSourceModel struct { | ||
Id types.String `tfsdk:"id"` | ||
CertificateId types.Int64 `tfsdk:"certificate_id"` | ||
Domain types.String `tfsdk:"domain"` | ||
ServerCertificate types.String `tfsdk:"server_certificate"` | ||
RootCertificate types.String `tfsdk:"root_certificate"` | ||
CertificateChain types.List `tfsdk:"certificate_chain"` | ||
PrivateKey types.String `tfsdk:"private_key"` | ||
Id types.String `tfsdk:"id"` | ||
CertificateId types.Int64 `tfsdk:"certificate_id"` | ||
Domain types.String `tfsdk:"domain"` | ||
ServerCertificate types.String `tfsdk:"server_certificate"` | ||
RootCertificate types.String `tfsdk:"root_certificate"` | ||
CertificateChain types.List `tfsdk:"certificate_chain"` | ||
PrivateKey types.String `tfsdk:"private_key"` | ||
Timeouts timeouts.Value `tfsdk:"timeouts"` | ||
} | ||
|
||
func (d *CertificateDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { | ||
|
@@ -71,6 +83,9 @@ func (d *CertificateDataSource) Schema(ctx context.Context, req datasource.Schem | |
Computed: true, | ||
}, | ||
}, | ||
Blocks: map[string]schema.Block{ | ||
"timeouts": timeouts.Block(ctx), | ||
}, | ||
} | ||
} | ||
|
||
|
@@ -95,7 +110,7 @@ func (d *CertificateDataSource) Configure(ctx context.Context, req datasource.Co | |
} | ||
|
||
func (d *CertificateDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { | ||
var data CertificateDataSourceModel | ||
var data *CertificateDataSourceModel | ||
|
||
// Read Terraform configuration data into the model | ||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) | ||
|
@@ -104,38 +119,111 @@ func (d *CertificateDataSource) Read(ctx context.Context, req datasource.ReadReq | |
return | ||
} | ||
|
||
response, err := d.config.Client.Certificates.DownloadCertificate(ctx, d.config.AccountID, data.Domain.ValueString(), data.CertificateId.ValueInt64()) | ||
convergenceState, err := tryToConvergeCertificate(ctx, data, &resp.Diagnostics, d, data.CertificateId.ValueInt64()) | ||
|
||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"failed to download DNSimple Certificate", | ||
"failed to get certificate state", | ||
err.Error(), | ||
) | ||
return | ||
} | ||
|
||
data.ServerCertificate = types.StringValue(response.Data.ServerCertificate) | ||
data.RootCertificate = types.StringValue(response.Data.RootCertificate) | ||
chain, diag := types.ListValueFrom(ctx, types.StringType, response.Data.IntermediateCertificates) | ||
if err != nil { | ||
resp.Diagnostics.Append(diag...) | ||
if convergenceState == CertificateFailed || convergenceState == CertificateTimeout { | ||
// Response is already populated with the error we can safely return | ||
return | ||
} | ||
data.CertificateChain = chain | ||
|
||
response, err = d.config.Client.Certificates.GetCertificatePrivateKey(ctx, d.config.AccountID, data.Domain.ValueString(), data.CertificateId.ValueInt64()) | ||
if convergenceState == CertificateConverged { | ||
|
||
response, err := d.config.Client.Certificates.DownloadCertificate(ctx, d.config.AccountID, data.Domain.ValueString(), data.CertificateId.ValueInt64()) | ||
|
||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"failed to download DNSimple Certificate", | ||
err.Error(), | ||
) | ||
return | ||
} | ||
|
||
data.ServerCertificate = types.StringValue(response.Data.ServerCertificate) | ||
data.RootCertificate = types.StringValue(response.Data.RootCertificate) | ||
chain, diag := types.ListValueFrom(ctx, types.StringType, response.Data.IntermediateCertificates) | ||
if err != nil { | ||
resp.Diagnostics.Append(diag...) | ||
return | ||
} | ||
data.CertificateChain = chain | ||
|
||
response, err = d.config.Client.Certificates.GetCertificatePrivateKey(ctx, d.config.AccountID, data.Domain.ValueString(), data.CertificateId.ValueInt64()) | ||
|
||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"failed to download DNSimple Certificate private key", | ||
err.Error(), | ||
) | ||
return | ||
} | ||
|
||
data.PrivateKey = types.StringValue(response.Data.PrivateKey) | ||
data.Id = types.StringValue(time.Now().UTC().String()) | ||
|
||
// Save data into Terraform state | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} | ||
} | ||
|
||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"failed to download DNSimple Certificate private key", | ||
err.Error(), | ||
) | ||
return | ||
func tryToConvergeCertificate(ctx context.Context, data *CertificateDataSourceModel, diagnostics *diag.Diagnostics, d *CertificateDataSource, certificateID int64) (string, error) { | ||
readTimeout, diags := data.Timeouts.Read(ctx, 5*time.Minute) | ||
|
||
diagnostics.Append(diags...) | ||
|
||
if diagnostics.HasError() { | ||
return CertificateFailed, nil | ||
} | ||
|
||
data.PrivateKey = types.StringValue(response.Data.PrivateKey) | ||
data.Id = types.StringValue(time.Now().UTC().String()) | ||
err := utils.RetryWithTimeout(ctx, func() (error, bool) { | ||
|
||
certificate, err := d.config.Client.Certificates.GetCertificate(ctx, d.config.AccountID, data.Domain.ValueString(), data.CertificateId.ValueInt64()) | ||
|
||
if err != nil { | ||
return err, false | ||
} | ||
|
||
if certificate.Data.State == consts.CertificateStateFailed { | ||
diagnostics.AddError( | ||
fmt.Sprintf("failed to issue certificate: %s", data.Domain.ValueString()), | ||
"certificate order failed, please investigate why this happened. If you need assistance, please contact support at [email protected]", | ||
) | ||
return nil, true | ||
} | ||
|
||
if certificate.Data.State == consts.CertificateStateCancelled || certificate.Data.State == consts.CertificateStateRefunded { | ||
diagnostics.AddError( | ||
fmt.Sprintf("failed to issue certificate: %s", data.Domain.ValueString()), | ||
"certificate order failed, please investigate why this happened. If you need assistance, please contact support at [email protected]", | ||
) | ||
return nil, true | ||
} | ||
|
||
if certificate.Data.State != consts.CertificateStateIssued { | ||
tflog.Info(ctx, fmt.Sprintf("[RETRYING] Certificate order is not complete, current state: %s", certificate.Data.State)) | ||
|
||
return fmt.Errorf("certificate has not been issued, current state: %s. You can try to run terraform again to try and converge the certificate", certificate.Data.State), false | ||
} | ||
|
||
return nil, false | ||
}, readTimeout, 20*time.Second) | ||
|
||
if diagnostics.HasError() { | ||
// If we have diagnostic errors, we suspended the retry loop because the certificate is in a bad state, and cannot converge. | ||
return CertificateFailed, nil | ||
} | ||
|
||
if err != nil { | ||
// If we have an error, it means the retry loop timed out, and we cannot converge during this run. | ||
return CertificateTimeout, err | ||
} | ||
|
||
// Save data into Terraform state | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
return CertificateConverged, nil | ||
} |