From b302c5d7cf523f58ee0f7ba699cf39874243b952 Mon Sep 17 00:00:00 2001 From: Chris Norman <17420369+chrnorm@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:49:53 +0100 Subject: [PATCH] Add ACM cert resource (#34) * add ACM cert resource * fix variable * update docs --- docs/data-sources/deployment.md | 3 +- docs/resources/aws_acm_certificate.md | 28 +++ go.mod | 4 +- go.sum | 4 +- internal/provider/aws_acm_certificate.go | 218 ++++++++++++++++++++ internal/provider/deployment_data_source.go | 14 +- internal/provider/provider.go | 1 + 7 files changed, 256 insertions(+), 16 deletions(-) create mode 100644 docs/resources/aws_acm_certificate.md create mode 100644 internal/provider/aws_acm_certificate.go diff --git a/docs/data-sources/deployment.md b/docs/data-sources/deployment.md index b9670bb..837c72c 100644 --- a/docs/data-sources/deployment.md +++ b/docs/data-sources/deployment.md @@ -21,6 +21,5 @@ data "deploymeta_deployment" "this" {} ### Read-Only -- `default_subdomain` (String) The default DNS subdomain associated with the deployment -- `dns_zone_name` (String) The default DNS zone name associated with the deployment +- `default_app_domain` (String) The default app domain for the deployment - `id` (String) The deployment ID diff --git a/docs/resources/aws_acm_certificate.md b/docs/resources/aws_acm_certificate.md new file mode 100644 index 0000000..be94043 --- /dev/null +++ b/docs/resources/aws_acm_certificate.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "deploymeta_aws_acm_certificate Resource - deploymeta" +subcategory: "" +description: |- + Registers an AWS ACM certificate for a Common Fate deployment. +--- + +# deploymeta_aws_acm_certificate (Resource) + +Registers an AWS ACM certificate for a Common Fate deployment. + + + + +## Schema + +### Required + +- `arn` (String) The Amazon Resource Name (ARN) of the certificate +- `domain_name` (String) The domain name for the certificate, for example: 'www.example.com' +- `status` (String) The status of the certificate +- `validation_cname_name` (String) The CNAME name used for domain validation +- `validation_cname_value` (String) The CNAME value used for domain validation + +### Read-Only + +- `id` (String) The certificate ID diff --git a/go.mod b/go.mod index 120e92e..8d74cf9 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,9 @@ toolchain go1.22.1 require ( connectrpc.com/connect v1.14.0 - github.com/common-fate/sdk v1.51.2-0.20240805112400-843ff371965e + github.com/common-fate/sdk v1.51.2-0.20240805171122-82f5839f67c0 github.com/hashicorp/terraform-plugin-docs v0.19.1 github.com/hashicorp/terraform-plugin-framework v1.8.0 - github.com/hashicorp/terraform-plugin-go v0.22.2 github.com/hashicorp/terraform-plugin-log v0.9.0 ) @@ -40,6 +39,7 @@ require ( github.com/hashicorp/hc-install v0.6.4 // indirect github.com/hashicorp/terraform-exec v0.20.0 // indirect github.com/hashicorp/terraform-json v0.21.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.22.2 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect diff --git a/go.sum b/go.sum index c14b48f..14a0d9a 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/common-fate/sdk v1.51.2-0.20240805112400-843ff371965e h1:U/Hnbj7xvxNH7FFdxcTzgqh/tvu6a9DVKkTbhKfT/2s= -github.com/common-fate/sdk v1.51.2-0.20240805112400-843ff371965e/go.mod h1:OrXhzB2Y1JSrKGHrb4qRmY+6MF2M3MFb+3edBnessXo= +github.com/common-fate/sdk v1.51.2-0.20240805171122-82f5839f67c0 h1:ToHYNnsIdNfLj/HdmxO5MyBokLHJLNSLJfzVCZH7B4s= +github.com/common-fate/sdk v1.51.2-0.20240805171122-82f5839f67c0/go.mod h1:OrXhzB2Y1JSrKGHrb4qRmY+6MF2M3MFb+3edBnessXo= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/provider/aws_acm_certificate.go b/internal/provider/aws_acm_certificate.go new file mode 100644 index 0000000..f7be482 --- /dev/null +++ b/internal/provider/aws_acm_certificate.go @@ -0,0 +1,218 @@ +package provider + +import ( + "context" + "fmt" + + "connectrpc.com/connect" + "github.com/common-fate/sdk/factory/service/deployment" + "github.com/common-fate/sdk/factoryconfig" + deploymentv1alpha1 "github.com/common-fate/sdk/gen/commonfate/factory/deployment/v1alpha1" + "github.com/common-fate/sdk/gen/commonfate/factory/deployment/v1alpha1/deploymentv1alpha1connect" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &AWSACMCertificateResource{} +var _ resource.ResourceWithImportState = &AWSACMCertificateResource{} + +func NewAWSACMCertificateResource() resource.Resource { + return &AWSACMCertificateResource{} +} + +// AWSACMCertificateResource defines the resource implementation. +type AWSACMCertificateResource struct { + client deploymentv1alpha1connect.DeploymentServiceClient +} + +type AWSACMCertificateResourceModel struct { + ID types.String `tfsdk:"id"` + ARN types.String `tfsdk:"arn"` + DomainName types.String `tfsdk:"domain_name"` + ValidationCNameName types.String `tfsdk:"validation_cname_name"` + ValidationCNameValue types.String `tfsdk:"validation_cname_value"` + Status types.String `tfsdk:"status"` +} + +func (r *AWSACMCertificateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_aws_acm_certificate" +} + +func (r *AWSACMCertificateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Registers an AWS ACM certificate for a Common Fate deployment.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The certificate ID", + Computed: true, + }, + "arn": schema.StringAttribute{ + MarkdownDescription: "The Amazon Resource Name (ARN) of the certificate", + Required: true, + }, + "domain_name": schema.StringAttribute{ + MarkdownDescription: "The domain name for the certificate, for example: 'www.example.com'", + Required: true, + }, + "validation_cname_name": schema.StringAttribute{ + MarkdownDescription: "The CNAME name used for domain validation", + Required: true, + }, + "validation_cname_value": schema.StringAttribute{ + MarkdownDescription: "The CNAME value used for domain validation", + Required: true, + }, + "status": schema.StringAttribute{ + MarkdownDescription: "The status of the certificate", + Required: true, + }, + }, + } +} + +func (r *AWSACMCertificateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + cfg, ok := req.ProviderData.(*factoryconfig.Context) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *factoryconfig.Context, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = deployment.NewFromConfig(cfg) +} + +func (r *AWSACMCertificateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data AWSACMCertificateResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + res, err := r.client.RegisterAWSACMCertificate(ctx, connect.NewRequest(&deploymentv1alpha1.RegisterAWSACMCertificateRequest{ + Arn: data.ARN.ValueString(), + DomainName: data.DomainName.ValueString(), + ValidationCnameName: data.ValidationCNameName.ValueString(), + ValidationCnameValue: data.ValidationCNameValue.ValueString(), + Status: data.Status.ValueString(), + })) + if err != nil { + resp.Diagnostics.AddError("Common Fate Deployment API error", fmt.Sprintf("Unable to register AWS ACM certificate for the deployment, got error: %s", err.Error())) + return + } + + tflog.Trace(ctx, "registered AWS ACM certificate") + + // Convert from the API data model to the Terraform data model + // and set any unknown attribute values. + data.ID = types.StringValue(res.Msg.Certificate.Id) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AWSACMCertificateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data AWSACMCertificateResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + apiRes, err := r.client.GetAWSACMCertificate(ctx, connect.NewRequest(&deploymentv1alpha1.GetAWSACMCertificateRequest{ + Id: data.ID.ValueString(), + })) + if connect.CodeOf(err) == connect.CodeNotFound { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read Common Fate DNS record, got error: %s", err)) + return + } + + data.ID = types.StringValue(apiRes.Msg.Certificate.Id) + data.ARN = types.StringValue(apiRes.Msg.Certificate.Arn) + data.DomainName = types.StringValue(apiRes.Msg.Certificate.DomainName) + data.ValidationCNameName = types.StringValue(apiRes.Msg.Certificate.ValidationCnameName) + data.ValidationCNameValue = types.StringValue(apiRes.Msg.Certificate.ValidationCnameValue) + data.Status = types.StringValue(apiRes.Msg.Certificate.Status) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +func (r *AWSACMCertificateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data AWSACMCertificateResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + res, err := r.client.UpdateAWSACMCertificate(ctx, connect.NewRequest(&deploymentv1alpha1.UpdateAWSACMCertificateRequest{ + Certificate: &deploymentv1alpha1.AWSACMCertificate{ + Id: data.ID.ValueString(), + Arn: data.ARN.ValueString(), + DomainName: data.DomainName.ValueString(), + ValidationCnameName: data.ValidationCNameName.ValueString(), + ValidationCnameValue: data.ValidationCNameValue.ValueString(), + Status: data.Status.ValueString(), + }, + })) + if err != nil { + resp.Diagnostics.AddError("Common Fate Deployment API error", fmt.Sprintf("Unable to update AWS ACM certificate for the deployment, got error: %s", err.Error())) + return + } + + tflog.Trace(ctx, "updated AWS ACM certificate") + + // Convert from the API data model to the Terraform data model + // and set any unknown attribute values. + data.ID = types.StringValue(res.Msg.Certificate.Id) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AWSACMCertificateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data AWSACMCertificateResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + _, err := r.client.DeregisterAWSACMCertificate(ctx, connect.NewRequest(&deploymentv1alpha1.DeregisterAWSACMCertificateRequest{ + Id: data.ID.ValueString(), + })) + if connect.CodeOf(err) == connect.CodeNotFound { + return + } + + if err != nil { + resp.Diagnostics.AddError("Common Fate Deployment API error", fmt.Sprintf("Unable to delete ACM certificate for the deployment, got error: %s", err.Error())) + return + } + + tflog.Trace(ctx, "deleted ACM cert") +} + +func (r *AWSACMCertificateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/deployment_data_source.go b/internal/provider/deployment_data_source.go index 326dd7d..fd7f025 100644 --- a/internal/provider/deployment_data_source.go +++ b/internal/provider/deployment_data_source.go @@ -29,9 +29,8 @@ type DeploymentDataSource struct { // DeploymentDataSourceModel describes the data source data model. type DeploymentDataSourceModel struct { - DNSZoneName types.String `tfsdk:"dns_zone_name"` - DefaultSubdomain types.String `tfsdk:"default_subdomain"` Id types.String `tfsdk:"id"` + DefaultAppDomain types.String `tfsdk:"default_app_domain"` } func (d *DeploymentDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -47,12 +46,8 @@ func (d *DeploymentDataSource) Schema(ctx context.Context, req datasource.Schema MarkdownDescription: "The deployment ID", Computed: true, }, - "dns_zone_name": schema.StringAttribute{ - MarkdownDescription: "The default DNS zone name associated with the deployment", - Computed: true, - }, - "default_subdomain": schema.StringAttribute{ - MarkdownDescription: "The default DNS subdomain associated with the deployment", + "default_app_domain": schema.StringAttribute{ + MarkdownDescription: "The default app domain for the deployment", Computed: true, }, }, @@ -96,8 +91,7 @@ func (d *DeploymentDataSource) Read(ctx context.Context, req datasource.ReadRequ } data.Id = types.StringValue(apiRes.Msg.Deployment.Id) - data.DNSZoneName = types.StringValue(apiRes.Msg.Deployment.DnsZoneName) - data.DefaultSubdomain = types.StringValue(apiRes.Msg.Deployment.DefaultSubdomain) + data.DefaultAppDomain = types.StringValue(apiRes.Msg.Deployment.DefaultAppDomain) tflog.Trace(ctx, "read deployment metadata") diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d04cc77..28cdc64 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -77,6 +77,7 @@ func (p *DeploymentProvider) Resources(ctx context.Context) []func() resource.Re return []func() resource.Resource{ NewDNSRecordResource, NewTerraformOutputResource, + NewAWSACMCertificateResource, } }