mirror of
https://github.com/hanzokms/terraform-provider.git
synced 2026-10-30 20:29:45 +00:00
415 lines
13 KiB
Go
415 lines
13 KiB
Go
package resource
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
kmsclient "github.com/hanzokms/terraform-provider/internal/client"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
|
"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/resource/schema/planmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
)
|
|
|
|
var (
|
|
SUPPORTED_DNS_PROVIDERS = []string{"route53", "cloudflare", "dns-made-easy"}
|
|
)
|
|
|
|
var (
|
|
_ resource.Resource = &certManagerExternalCAACMEResource{}
|
|
)
|
|
|
|
func NewCertManagerExternalCAACMEResource() resource.Resource {
|
|
return &certManagerExternalCAACMEResource{}
|
|
}
|
|
|
|
type certManagerExternalCAACMEResource struct {
|
|
client *kmsclient.Client
|
|
}
|
|
|
|
type certManagerExternalCAACMEResourceModel struct {
|
|
ProjectSlug types.String `tfsdk:"project_slug"`
|
|
Id types.String `tfsdk:"id"`
|
|
Name types.String `tfsdk:"name"`
|
|
Status types.String `tfsdk:"status"`
|
|
DNSAppConnectionId types.String `tfsdk:"dns_app_connection_id"`
|
|
DNSProvider types.String `tfsdk:"dns_provider"`
|
|
DNSHostedZoneId types.String `tfsdk:"dns_hosted_zone_id"`
|
|
DirectoryUrl types.String `tfsdk:"directory_url"`
|
|
AccountEmail types.String `tfsdk:"account_email"`
|
|
EABKid types.String `tfsdk:"eab_kid"`
|
|
EABHmacKey types.String `tfsdk:"eab_hmac_key"`
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_cert_manager_external_ca_acme"
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Create and manage external ACME certificate authorities in Kms. Only Machine Identity authentication is supported for this resource.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"project_slug": schema.StringAttribute{
|
|
Description: "The slug of the cert-manager project",
|
|
Required: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"name": schema.StringAttribute{
|
|
Description: "The name of the ACME CA",
|
|
Required: true,
|
|
},
|
|
"status": schema.StringAttribute{
|
|
Description: "The status of the CA. Supported values: " + strings.Join(SUPPORTED_CA_STATUSES, ", ") + ". Defaults to 'active'.",
|
|
Optional: true,
|
|
Computed: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.UseStateForUnknown(),
|
|
},
|
|
Validators: []validator.String{
|
|
stringvalidator.OneOf(SUPPORTED_CA_STATUSES...),
|
|
},
|
|
},
|
|
"dns_app_connection_id": schema.StringAttribute{
|
|
Description: "The ID of the DNS app connection for ACME challenge validation",
|
|
Required: true,
|
|
},
|
|
"dns_provider": schema.StringAttribute{
|
|
Description: "The DNS provider for ACME challenge validation. Supported values: " + strings.Join(SUPPORTED_DNS_PROVIDERS, ", "),
|
|
Required: true,
|
|
Validators: []validator.String{
|
|
stringvalidator.OneOf(SUPPORTED_DNS_PROVIDERS...),
|
|
},
|
|
},
|
|
"dns_hosted_zone_id": schema.StringAttribute{
|
|
Description: "The hosted zone ID for DNS-01 challenge validation",
|
|
Optional: true,
|
|
},
|
|
"directory_url": schema.StringAttribute{
|
|
Description: "The ACME directory URL",
|
|
Required: true,
|
|
},
|
|
"account_email": schema.StringAttribute{
|
|
Description: "The email address for ACME account registration",
|
|
Required: true,
|
|
},
|
|
"eab_kid": schema.StringAttribute{
|
|
Description: "External Account Binding (EAB) Key ID (optional)",
|
|
Optional: true,
|
|
},
|
|
"eab_hmac_key": schema.StringAttribute{
|
|
Description: "External Account Binding (EAB) HMAC key (optional)",
|
|
Optional: true,
|
|
Sensitive: true,
|
|
},
|
|
"id": schema.StringAttribute{
|
|
Description: "The ID of the ACME CA",
|
|
Computed: true,
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
client, ok := req.ProviderData.(*kmsclient.Client)
|
|
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected Resource Configure Type",
|
|
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
r.client = client
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to create ACME CA",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
var plan certManagerExternalCAACMEResourceModel
|
|
diags := req.Plan.Get(ctx, &plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
project, err := r.client.GetProject(kmsclient.GetProjectRequest{
|
|
Slug: plan.ProjectSlug.ValueString(),
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error reading project",
|
|
"Couldn't read project from Kms, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
status := "active"
|
|
if !plan.Status.IsNull() && !plan.Status.IsUnknown() {
|
|
status = plan.Status.ValueString()
|
|
}
|
|
|
|
dnsProviderConfig := make(map[string]interface{})
|
|
dnsProviderConfig["provider"] = plan.DNSProvider.ValueString()
|
|
if !plan.DNSHostedZoneId.IsNull() && !plan.DNSHostedZoneId.IsUnknown() {
|
|
dnsProviderConfig["hostedZoneId"] = plan.DNSHostedZoneId.ValueString()
|
|
}
|
|
|
|
configuration := kmsclient.CertificateAuthorityConfiguration{
|
|
DNSAppConnectionId: plan.DNSAppConnectionId.ValueString(),
|
|
DNSProviderConfig: dnsProviderConfig,
|
|
DirectoryUrl: plan.DirectoryUrl.ValueString(),
|
|
AccountEmail: plan.AccountEmail.ValueString(),
|
|
}
|
|
|
|
if !plan.EABKid.IsNull() && !plan.EABKid.IsUnknown() {
|
|
configuration.EABKid = plan.EABKid.ValueString()
|
|
}
|
|
|
|
if !plan.EABHmacKey.IsNull() && !plan.EABHmacKey.IsUnknown() {
|
|
configuration.EABHmacKey = plan.EABHmacKey.ValueString()
|
|
}
|
|
|
|
newCA, err := r.client.CreateACMECA(kmsclient.CreateACMECARequest{
|
|
ProjectId: project.ID,
|
|
Name: plan.Name.ValueString(),
|
|
Status: status,
|
|
Configuration: configuration,
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error creating ACME CA",
|
|
"Couldn't create ACME CA in Kms, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
plan.Id = types.StringValue(newCA.Id)
|
|
plan.Status = types.StringValue(newCA.Status)
|
|
|
|
if newCA.Configuration.DNSAppConnectionId != "" {
|
|
plan.DNSAppConnectionId = types.StringValue(newCA.Configuration.DNSAppConnectionId)
|
|
}
|
|
if newCA.Configuration.DirectoryUrl != "" {
|
|
plan.DirectoryUrl = types.StringValue(newCA.Configuration.DirectoryUrl)
|
|
}
|
|
if newCA.Configuration.AccountEmail != "" {
|
|
plan.AccountEmail = types.StringValue(newCA.Configuration.AccountEmail)
|
|
}
|
|
|
|
diags = resp.State.Set(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to read ACME CA",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
var state certManagerExternalCAACMEResourceModel
|
|
diags := req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
ca, err := r.client.GetACMECA(kmsclient.GetCARequest{
|
|
CAId: state.Id.ValueString(),
|
|
})
|
|
|
|
if err != nil {
|
|
if err == kmsclient.ErrNotFound {
|
|
resp.State.RemoveResource(ctx)
|
|
return
|
|
}
|
|
|
|
resp.Diagnostics.AddError(
|
|
"Error reading ACME CA",
|
|
"Couldn't read ACME CA from Kms, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
state.Name = types.StringValue(ca.Name)
|
|
state.Status = types.StringValue(ca.Status)
|
|
|
|
if ca.Configuration.DNSAppConnectionId != "" {
|
|
state.DNSAppConnectionId = types.StringValue(ca.Configuration.DNSAppConnectionId)
|
|
}
|
|
if ca.Configuration.DirectoryUrl != "" {
|
|
state.DirectoryUrl = types.StringValue(ca.Configuration.DirectoryUrl)
|
|
}
|
|
if ca.Configuration.AccountEmail != "" {
|
|
state.AccountEmail = types.StringValue(ca.Configuration.AccountEmail)
|
|
}
|
|
|
|
if ca.Configuration.DNSProviderConfig != nil {
|
|
if provider, ok := ca.Configuration.DNSProviderConfig["provider"].(string); ok && provider != "" {
|
|
state.DNSProvider = types.StringValue(provider)
|
|
}
|
|
if hostedZoneId, ok := ca.Configuration.DNSProviderConfig["hostedZoneId"].(string); ok && hostedZoneId != "" {
|
|
state.DNSHostedZoneId = types.StringValue(hostedZoneId)
|
|
}
|
|
}
|
|
|
|
if ca.Configuration.EABKid != "" {
|
|
state.EABKid = types.StringValue(ca.Configuration.EABKid)
|
|
}
|
|
|
|
diags = resp.State.Set(ctx, state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to update ACME CA",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
var plan certManagerExternalCAACMEResourceModel
|
|
diags := req.Plan.Get(ctx, &plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
var state certManagerExternalCAACMEResourceModel
|
|
diags = req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
project, err := r.client.GetProject(kmsclient.GetProjectRequest{
|
|
Slug: plan.ProjectSlug.ValueString(),
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error reading project",
|
|
"Couldn't read project from Kms, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
dnsProviderConfig := make(map[string]interface{})
|
|
dnsProviderConfig["provider"] = plan.DNSProvider.ValueString()
|
|
if !plan.DNSHostedZoneId.IsNull() && !plan.DNSHostedZoneId.IsUnknown() {
|
|
dnsProviderConfig["hostedZoneId"] = plan.DNSHostedZoneId.ValueString()
|
|
}
|
|
|
|
configuration := kmsclient.CertificateAuthorityConfiguration{
|
|
DNSAppConnectionId: plan.DNSAppConnectionId.ValueString(),
|
|
DNSProviderConfig: dnsProviderConfig,
|
|
DirectoryUrl: plan.DirectoryUrl.ValueString(),
|
|
AccountEmail: plan.AccountEmail.ValueString(),
|
|
}
|
|
|
|
if !plan.EABKid.IsNull() && !plan.EABKid.IsUnknown() {
|
|
configuration.EABKid = plan.EABKid.ValueString()
|
|
}
|
|
|
|
if !plan.EABHmacKey.IsNull() && !plan.EABHmacKey.IsUnknown() {
|
|
configuration.EABHmacKey = plan.EABHmacKey.ValueString()
|
|
}
|
|
|
|
_, err = r.client.UpdateACMECA(kmsclient.UpdateACMECARequest{
|
|
ProjectId: project.ID,
|
|
CAId: plan.Id.ValueString(),
|
|
Name: plan.Name.ValueString(),
|
|
Status: plan.Status.ValueString(),
|
|
Configuration: configuration,
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error updating ACME CA",
|
|
"Couldn't update ACME CA in Kms, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
diags = resp.State.Set(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to delete ACME CA",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
var state certManagerExternalCAACMEResourceModel
|
|
diags := req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
_, err := r.client.DeleteACMECA(kmsclient.DeleteCARequest{
|
|
CAId: state.Id.ValueString(),
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error deleting ACME CA",
|
|
"Couldn't delete ACME CA from Kms, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *certManagerExternalCAACMEResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
|
parts := strings.Split(req.ID, ":")
|
|
if len(parts) != 2 {
|
|
resp.Diagnostics.AddError(
|
|
"Invalid import ID",
|
|
"Import ID must be in the format 'project_slug:ca_id'",
|
|
)
|
|
return
|
|
}
|
|
|
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_slug"), parts[0])...)
|
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), parts[1])...)
|
|
}
|