mirror of
https://github.com/hanzokms/terraform-provider.git
synced 2026-10-30 20:29:45 +00:00
467 lines
15 KiB
Go
467 lines
15 KiB
Go
package resource
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
kmsclient "github.com/hanzokms/terraform-provider/internal/client"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
)
|
|
|
|
type RotateAtUtc struct {
|
|
Hours types.Int64 `tfsdk:"hours"`
|
|
Minutes types.Int64 `tfsdk:"minutes"`
|
|
}
|
|
|
|
// SecretRotationBaseResource is the resource implementation.
|
|
type SecretRotationBaseResource struct {
|
|
Provider kmsclient.SecretRotationProvider
|
|
ResourceTypeName string // terraform resource name suffix
|
|
SecretRotationName string // complete descriptive name of the secret rotation
|
|
AppConnection kmsclient.AppConnectionApp
|
|
client *kmsclient.Client
|
|
|
|
ParametersAttributes map[string]schema.Attribute
|
|
ReadParametersFromPlan func(ctx context.Context, plan SecretRotationBaseResourceModel) (map[string]interface{}, diag.Diagnostics)
|
|
ReadParametersFromApi func(ctx context.Context, secretRotation kmsclient.SecretRotation) (types.Object, diag.Diagnostics)
|
|
|
|
SecretsMappingAttributes map[string]schema.Attribute
|
|
ReadSecretsMappingFromPlan func(ctx context.Context, plan SecretRotationBaseResourceModel) (map[string]interface{}, diag.Diagnostics)
|
|
ReadSecretsMappingFromApi func(ctx context.Context, secretRotation kmsclient.SecretRotation) (types.Object, diag.Diagnostics)
|
|
|
|
TemporaryParametersAttributes map[string]schema.Attribute
|
|
ReadTemporaryParametersFromPlan func(ctx context.Context, plan SecretRotationBaseResourceModel) (map[string]interface{}, diag.Diagnostics)
|
|
ReadTemporaryParametersFromApi func(ctx context.Context, secretRotation kmsclient.SecretRotation) (types.Object, diag.Diagnostics)
|
|
}
|
|
|
|
type SecretRotationBaseResourceModel struct {
|
|
ID types.String `tfsdk:"id"`
|
|
Name types.String `tfsdk:"name"`
|
|
Description types.String `tfsdk:"description"`
|
|
AutoRotationEnabled types.Bool `tfsdk:"auto_rotation_enabled"`
|
|
ProjectID types.String `tfsdk:"project_id"`
|
|
ConnectionID types.String `tfsdk:"connection_id"`
|
|
Environment types.String `tfsdk:"environment"`
|
|
SecretPath types.String `tfsdk:"secret_path"`
|
|
|
|
RotationInterval types.Int32 `tfsdk:"rotation_interval"`
|
|
RotateAtUtc RotateAtUtc `tfsdk:"rotate_at_utc"`
|
|
|
|
Parameters types.Object `tfsdk:"parameters"`
|
|
SecretsMapping types.Object `tfsdk:"secrets_mapping"`
|
|
TemporaryParameters types.Object `tfsdk:"temporary_parameters"`
|
|
}
|
|
|
|
// Metadata returns the resource type name.
|
|
func (r *SecretRotationBaseResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + r.ResourceTypeName
|
|
}
|
|
|
|
func (r *SecretRotationBaseResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: fmt.Sprintf("Create and manage %s Secret Rotations", r.SecretRotationName),
|
|
Attributes: map[string]schema.Attribute{
|
|
"id": schema.StringAttribute{
|
|
Description: "The ID of the secret rotation.",
|
|
Computed: true,
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
|
|
},
|
|
"name": schema.StringAttribute{
|
|
Description: "The name of the secret rotation.",
|
|
Required: true,
|
|
},
|
|
"description": schema.StringAttribute{
|
|
Optional: true,
|
|
Description: "The description of the secret rotation.",
|
|
},
|
|
"auto_rotation_enabled": schema.BoolAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
Description: "Whether secrets should be automatically rotated.",
|
|
Default: booldefault.StaticBool(true),
|
|
},
|
|
"project_id": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "The ID of the Kms project to create the secret rotation in.",
|
|
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
|
},
|
|
"connection_id": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "The ID of the connection to use for the secret rotation.",
|
|
},
|
|
"environment": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "The slug of the project environment to rotate secrets from.",
|
|
},
|
|
"secret_path": schema.StringAttribute{
|
|
Required: true,
|
|
Description: "The folder path to rotate secrets from.",
|
|
},
|
|
|
|
"rotation_interval": schema.Int32Attribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
Description: "How many days to wait between each rotation.",
|
|
Default: int32default.StaticInt32(30),
|
|
},
|
|
"rotate_at_utc": schema.SingleNestedAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
Description: "At which UTC time the rotation should occur.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"hours": schema.Int64Attribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
Description: "The hour at which the rotation should occur (UTC).",
|
|
Default: int64default.StaticInt64(0),
|
|
},
|
|
"minutes": schema.Int64Attribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
Description: "The minute at which the rotation should occur (UTC).",
|
|
Default: int64default.StaticInt64(0),
|
|
},
|
|
},
|
|
Default: objectdefault.StaticValue(types.ObjectValueMust(
|
|
map[string]attr.Type{
|
|
"hours": types.Int64Type,
|
|
"minutes": types.Int64Type,
|
|
},
|
|
map[string]attr.Value{
|
|
"hours": types.Int64Value(0),
|
|
"minutes": types.Int64Value(0),
|
|
},
|
|
)),
|
|
},
|
|
|
|
"parameters": schema.SingleNestedAttribute{
|
|
Required: true,
|
|
Description: "Parameters to modify how secrets are rotated.",
|
|
Attributes: r.ParametersAttributes,
|
|
},
|
|
"secrets_mapping": schema.SingleNestedAttribute{
|
|
Required: true,
|
|
Description: "Secret mappings to modify how secrets are rotated.",
|
|
Attributes: r.SecretsMappingAttributes,
|
|
},
|
|
"temporary_parameters": schema.SingleNestedAttribute{
|
|
Optional: true,
|
|
Description: "Temporary parameters to modify how secrets are rotated.",
|
|
Attributes: r.TemporaryParametersAttributes,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Configure adds the provider configured client to the resource.
|
|
func (r *SecretRotationBaseResource) 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 Source Configure Type",
|
|
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
r.client = client
|
|
}
|
|
|
|
// Create creates the resource and sets the initial Terraform state.
|
|
func (r *SecretRotationBaseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to create secret rotation",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
// Retrieve values from plan
|
|
var plan SecretRotationBaseResourceModel
|
|
diags := req.Plan.Get(ctx, &plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
parameters, diags := r.ReadParametersFromPlan(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
secretsMapping, diags := r.ReadSecretsMappingFromPlan(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
var temporaryParameters map[string]interface{}
|
|
if r.ReadTemporaryParametersFromPlan != nil {
|
|
if !(plan.TemporaryParameters.IsNull() || plan.TemporaryParameters.IsUnknown()) {
|
|
temporaryParameters, diags = r.ReadTemporaryParametersFromPlan(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
} else {
|
|
temporaryParameters = nil
|
|
}
|
|
} else {
|
|
temporaryParameters = nil
|
|
}
|
|
|
|
secretRotation, err := r.client.CreateSecretRotation(kmsclient.CreateSecretRotationRequest{
|
|
Provider: r.Provider,
|
|
Name: plan.Name.ValueString(),
|
|
Description: plan.Description.ValueString(),
|
|
AutoRotationEnabled: plan.AutoRotationEnabled.ValueBool(),
|
|
ProjectID: plan.ProjectID.ValueString(),
|
|
ConnectionID: plan.ConnectionID.ValueString(),
|
|
Environment: plan.Environment.ValueString(),
|
|
SecretPath: plan.SecretPath.ValueString(),
|
|
|
|
RotationInterval: plan.RotationInterval.ValueInt32(),
|
|
RotateAtUtc: kmsclient.SecretRotationRotateAtUtc{
|
|
Hours: plan.RotateAtUtc.Hours.ValueInt64(),
|
|
Minutes: plan.RotateAtUtc.Minutes.ValueInt64(),
|
|
},
|
|
|
|
Parameters: parameters,
|
|
SecretsMapping: secretsMapping,
|
|
TemporaryParameters: temporaryParameters,
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error creating secret rotation",
|
|
"Couldn't create secret rotation, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
plan.ID = types.StringValue(secretRotation.ID)
|
|
|
|
diags = resp.State.Set(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Read refreshes the Terraform state with the latest data.
|
|
func (r *SecretRotationBaseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to read secret rotation",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
// Get current state
|
|
var state SecretRotationBaseResourceModel
|
|
diags := req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
secretRotation, err := r.client.GetSecretRotationById(kmsclient.GetSecretRotationByIdRequest{
|
|
Provider: r.Provider,
|
|
ID: state.ID.ValueString(),
|
|
})
|
|
|
|
if err != nil {
|
|
if err == kmsclient.ErrNotFound {
|
|
resp.State.RemoveResource(ctx)
|
|
return
|
|
} else {
|
|
resp.Diagnostics.AddError(
|
|
"Error reading secret rotation",
|
|
"Couldn't read secret rotation, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
}
|
|
|
|
state.Name = types.StringValue(secretRotation.Name)
|
|
|
|
fmt.Printf("Secret rotation description from API: %s\n", secretRotation.Description)
|
|
|
|
if !(state.Description.IsNull() && secretRotation.Description == "") {
|
|
state.Description = types.StringValue(secretRotation.Description)
|
|
}
|
|
|
|
state.AutoRotationEnabled = types.BoolValue(secretRotation.AutoRotationEnabled)
|
|
state.ProjectID = types.StringValue(secretRotation.ProjectID)
|
|
state.ConnectionID = types.StringValue(secretRotation.Connection.ConnectionID)
|
|
state.Environment = types.StringValue(secretRotation.Environment.Slug)
|
|
state.SecretPath = types.StringValue(secretRotation.SecretFolder.Path)
|
|
|
|
state.RotationInterval = types.Int32Value(secretRotation.RotationInterval)
|
|
if secretRotation.RotateAtUtc != nil {
|
|
state.RotateAtUtc.Hours = types.Int64Value(secretRotation.RotateAtUtc.Hours)
|
|
state.RotateAtUtc.Minutes = types.Int64Value(secretRotation.RotateAtUtc.Minutes)
|
|
} else {
|
|
state.RotateAtUtc.Hours = types.Int64Value(0)
|
|
state.RotateAtUtc.Minutes = types.Int64Value(0)
|
|
}
|
|
|
|
state.Parameters, diags = r.ReadParametersFromApi(ctx, secretRotation)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
state.SecretsMapping, diags = r.ReadSecretsMappingFromApi(ctx, secretRotation)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
if r.ReadTemporaryParametersFromApi != nil {
|
|
state.TemporaryParameters, diags = r.ReadTemporaryParametersFromApi(ctx, secretRotation)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
}
|
|
|
|
diags = resp.State.Set(ctx, state)
|
|
resp.Diagnostics.Append(diags...)
|
|
}
|
|
|
|
func (r *SecretRotationBaseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to update secret rotation",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
// Retrieve values from plan
|
|
var plan SecretRotationBaseResourceModel
|
|
diags := req.Plan.Get(ctx, &plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
var state SecretRotationBaseResourceModel
|
|
diags = req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
parameters, diags := r.ReadParametersFromPlan(ctx, plan)
|
|
if diags.HasError() {
|
|
resp.Diagnostics.Append(diags...)
|
|
return
|
|
}
|
|
|
|
secretsMapping, diags := r.ReadSecretsMappingFromPlan(ctx, plan)
|
|
if diags.HasError() {
|
|
resp.Diagnostics.Append(diags...)
|
|
return
|
|
}
|
|
|
|
var temporaryParameters map[string]interface{}
|
|
if r.ReadTemporaryParametersFromPlan != nil {
|
|
if !(plan.TemporaryParameters.IsNull() || plan.TemporaryParameters.IsUnknown()) {
|
|
temporaryParameters, diags = r.ReadTemporaryParametersFromPlan(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
} else {
|
|
temporaryParameters = nil
|
|
}
|
|
} else {
|
|
temporaryParameters = nil
|
|
}
|
|
|
|
_, err := r.client.UpdateSecretRotation(kmsclient.UpdateSecretRotationRequest{
|
|
Provider: r.Provider,
|
|
ID: state.ID.ValueString(),
|
|
Name: plan.Name.ValueString(),
|
|
Description: plan.Description.ValueString(),
|
|
AutoRotationEnabled: plan.AutoRotationEnabled.ValueBool(),
|
|
ConnectionID: plan.ConnectionID.ValueString(),
|
|
Environment: plan.Environment.ValueString(),
|
|
SecretPath: plan.SecretPath.ValueString(),
|
|
|
|
RotationInterval: plan.RotationInterval.ValueInt32(),
|
|
RotateAtUtc: kmsclient.SecretRotationRotateAtUtc{
|
|
Hours: plan.RotateAtUtc.Hours.ValueInt64(),
|
|
Minutes: plan.RotateAtUtc.Minutes.ValueInt64(),
|
|
},
|
|
|
|
Parameters: parameters,
|
|
SecretsMapping: secretsMapping,
|
|
TemporaryParameters: temporaryParameters,
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error updating secret rotation",
|
|
"Couldn't update secret rotation, unexpected error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
diags = resp.State.Set(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *SecretRotationBaseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
if !r.client.Config.IsMachineIdentityAuth {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to delete secret rotation",
|
|
"Only Machine Identity authentication is supported for this operation",
|
|
)
|
|
return
|
|
}
|
|
|
|
var state SecretRotationBaseResourceModel
|
|
diags := req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
_, err := r.client.DeleteSecretRotation(kmsclient.DeleteSecretRotationRequest{
|
|
Provider: r.Provider,
|
|
ID: state.ID.ValueString(),
|
|
})
|
|
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error deleting secret rotation",
|
|
"Couldn't delete secret rotation from Kms, unexpected error: "+err.Error(),
|
|
)
|
|
}
|
|
}
|