add: internal package (provider, core, registrykeys)
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/int64default"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"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"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
var _ resource.Resource = &PgAdminResource{}
|
||||
var _ resource.ResourceWithImportState = &PgAdminResource{}
|
||||
|
||||
type PgAdminResource struct {
|
||||
client *NubesClient
|
||||
}
|
||||
|
||||
func NewPgAdminResource() resource.Resource {
|
||||
return &PgAdminResource{}
|
||||
}
|
||||
|
||||
type PgAdminResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Domain types.String `tfsdk:"domain"`
|
||||
ResourceRealm types.String `tfsdk:"resource_realm"`
|
||||
Cpu types.Int64 `tfsdk:"cpu"`
|
||||
Memory types.Int64 `tfsdk:"memory"`
|
||||
Disk types.Int64 `tfsdk:"disk"`
|
||||
Email types.String `tfsdk:"email"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
DeletionProtection types.Bool `tfsdk:"deletion_protection"`
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (r *PgAdminResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_pgadmin"
|
||||
}
|
||||
|
||||
func (r *PgAdminResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Manages a pgAdmin instance.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"domain": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Domain name (param 164)",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"resource_realm": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Deployment platform (param 169). E.g. k8s-3.ext.nubes.ru",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"cpu": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: int64default.StaticInt64(200),
|
||||
Description: "CPU quota in milicores (param 165)",
|
||||
},
|
||||
"memory": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: int64default.StaticInt64(256),
|
||||
Description: "Memory quota in MB (param 166)",
|
||||
},
|
||||
"disk": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: int64default.StaticInt64(1),
|
||||
Description: "Disk size in GB (param 167)",
|
||||
},
|
||||
"email": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Login email (param 170)",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"password": schema.StringAttribute{
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
Description: "Login password (param 171)",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"deletion_protection": schema.BoolAttribute{
|
||||
MarkdownDescription: "If true, the resource will only be removed from Terraform state upon destroy, but will remain in the cloud. If false, destroy will trigger 'suspend' in Nubes.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (r *PgAdminResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
client, ok := req.ProviderData.(*NubesClient)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError("Unexpected Resource Configure Type", fmt.Sprintf("Expected *NubesClient, got: %T", req.ProviderData))
|
||||
return
|
||||
}
|
||||
r.client = client
|
||||
}
|
||||
|
||||
func (r *PgAdminResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan PgAdminResourceModel
|
||||
diags := req.Plan.Get(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Creating pgAdmin resource...")
|
||||
|
||||
// Params based on pg_admin.har analysis
|
||||
params := []InstanceParam{
|
||||
{SvcOperationCfsParamId: 164, ParamValue: plan.Domain.ValueString()},
|
||||
{SvcOperationCfsParamId: 169, ParamValue: plan.ResourceRealm.ValueString()},
|
||||
{SvcOperationCfsParamId: 165, ParamValue: fmt.Sprintf("%d", plan.Cpu.ValueInt64())},
|
||||
{SvcOperationCfsParamId: 166, ParamValue: fmt.Sprintf("%d", plan.Memory.ValueInt64())},
|
||||
{SvcOperationCfsParamId: 167, ParamValue: fmt.Sprintf("%d", plan.Disk.ValueInt64())},
|
||||
{SvcOperationCfsParamId: 170, ParamValue: plan.Email.ValueString()},
|
||||
{SvcOperationCfsParamId: 171, ParamValue: plan.Password.ValueString()},
|
||||
}
|
||||
|
||||
serviceId := 96
|
||||
svcOperationId, err := r.client.GetOperationId(ctx, serviceId, "create")
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Failed to get create operation ID for pgAdmin", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
instanceUid, opUid, err := r.client.CreateInstance(ctx, plan.Domain.ValueString(), serviceId, svcOperationId, params)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error creating pgAdmin", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = r.client.WaitForOperation(ctx, opUid)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error waiting for pgAdmin ready", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(instanceUid)
|
||||
// plan.DeletionProtection = types.BoolValue(true)
|
||||
|
||||
diags = resp.State.Set(ctx, plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (r *PgAdminResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state PgAdminResourceModel
|
||||
diags := req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceUid := state.ID.ValueString()
|
||||
// Fetch current state
|
||||
inst, err := r.client.GetInstanceState(ctx, instanceUid)
|
||||
if err != nil {
|
||||
// If 404, remove from state
|
||||
if strings.Contains(err.Error(), "status 404") {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.AddError("Error reading pgAdmin state", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Map params
|
||||
if inst.State != nil && inst.State.Params != nil {
|
||||
for k, v := range inst.State.Params {
|
||||
valStr := fmt.Sprintf("%v", v)
|
||||
// Log for debugging
|
||||
tflog.Info(ctx, fmt.Sprintf("PGAdmin Param: %s = %s", k, valStr))
|
||||
|
||||
switch k {
|
||||
case "resourceCPU":
|
||||
val, _ := strconv.ParseInt(valStr, 10, 64)
|
||||
state.Cpu = types.Int64Value(val)
|
||||
case "resourceMemory":
|
||||
val, _ := strconv.ParseInt(valStr, 10, 64)
|
||||
state.Memory = types.Int64Value(val)
|
||||
case "resourceDisk":
|
||||
val, _ := strconv.ParseInt(valStr, 10, 64)
|
||||
state.Disk = types.Int64Value(val)
|
||||
case "domain":
|
||||
state.Domain = types.StringValue(valStr)
|
||||
case "resourceRealm":
|
||||
state.ResourceRealm = types.StringValue(valStr)
|
||||
case "login":
|
||||
state.Email = types.StringValue(valStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diags = resp.State.Set(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
func (r *PgAdminResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan, state PgAdminResourceModel
|
||||
diags := req.Plan.Get(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
diags = req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Updating pgAdmin resource...")
|
||||
|
||||
// Find operation ID for 'modify'
|
||||
// Based on HAR/Screenshots, modify uses standard "modify" operation
|
||||
opId, err := r.client.GetOperationIdForInstance(ctx, state.ID.ValueString(), "modify")
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Failed to find update operation", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Payload for OP creation
|
||||
opPayload := map[string]interface{}{
|
||||
"instanceUid": state.ID.ValueString(),
|
||||
"svcOperationId": opId,
|
||||
"operation": "modify",
|
||||
}
|
||||
|
||||
// Create request
|
||||
var opUid string
|
||||
opUid, err = r.client.postIgnoreResponse(ctx, "/instanceOperations", opPayload, true)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Failed to create update operation", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch Op details to get correct CFS Param IDs for 'modify'
|
||||
opDetails, err := r.client.GetInstanceOperation(ctx, opUid)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Failed to fetch operation details", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paramMapping := make(map[string]int)
|
||||
for _, p := range opDetails.CfsParams {
|
||||
paramMapping[p.SvcOperationCfsParam] = p.SvcOperationCfsParamId
|
||||
}
|
||||
|
||||
// Create params list from plan
|
||||
// We assume standard param names "resourceCPU", "resourceMemory", "resourceDisk" based on screenshot
|
||||
// But we use mapping to be safe if IDs differ from Create
|
||||
|
||||
type Modification struct {
|
||||
Name string
|
||||
Val string
|
||||
}
|
||||
|
||||
mods := []Modification{
|
||||
{"resourceCPU", fmt.Sprintf("%d", plan.Cpu.ValueInt64())},
|
||||
{"resourceMemory", fmt.Sprintf("%d", plan.Memory.ValueInt64())},
|
||||
{"resourceDisk", fmt.Sprintf("%d", plan.Disk.ValueInt64())},
|
||||
}
|
||||
|
||||
params := []InstanceParam{}
|
||||
for _, m := range mods {
|
||||
if id, ok := paramMapping[m.Name]; ok {
|
||||
params = append(params, InstanceParam{SvcOperationCfsParamId: id, ParamValue: m.Val})
|
||||
} else {
|
||||
tflog.Warn(ctx, fmt.Sprintf("Param %s not found in modify operation", m.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// If no params (e.g. only email changed but modify doesn't support it?), skip
|
||||
if len(params) == 0 {
|
||||
tflog.Warn(ctx, "No matching parameters found for modify operation. Skipping.")
|
||||
} else {
|
||||
// Run op
|
||||
_, _, err = r.client.RunInstanceOperation(ctx, opUid, params)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error running modify operation", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Wait
|
||||
err = r.client.WaitForOperation(ctx, opUid)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error waiting for modification", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
diags = resp.State.Set(ctx, plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
|
||||
func (r *PgAdminResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state PgAdminResourceModel
|
||||
diags := req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// User requested NO DELETE for pgAdmin (suspend/protection).
|
||||
// We will simply remove it from Terraform state without calling API delete.
|
||||
|
||||
if state.DeletionProtection.ValueBool() {
|
||||
tflog.Warn(ctx, "Deletion Protection is ENABLED for PgAdmin. Resource will remain active in Nubes Cloud. Manual cleanup required.")
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Deletion Protection is DISABLED for PgAdmin. Triggering 'suspend'...")
|
||||
|
||||
instanceId := state.ID.ValueString()
|
||||
|
||||
// Выполняем операцию suspend
|
||||
err := r.client.RunAction(ctx, instanceId, "suspend")
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to suspend PgAdmin: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "PgAdmin suspended successfully. It will be permanently deleted from the cloud in 14 days.")
|
||||
}
|
||||
|
||||
|
||||
func (r *PgAdminResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
Reference in New Issue
Block a user