mirror of
https://github.com/GlueOps/autoglue.git
synced 2026-02-13 21:00:06 +01:00
231 lines
7.6 KiB
Go
231 lines
7.6 KiB
Go
package provider
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"github.com/glueops/autoglue-sdk-go"
|
||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||
rschema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
||
"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 _ resource.Resource = &SshResource{}
|
||
var _ resource.ResourceWithConfigure = &SshResource{}
|
||
var _ resource.ResourceWithImportState = &SshResource{}
|
||
|
||
type SshResource struct{ client *Client }
|
||
|
||
func NewSshResource() resource.Resource { return &SshResource{} }
|
||
|
||
type sshResModel struct {
|
||
ID types.String `tfsdk:"id"`
|
||
Name types.String `tfsdk:"name"`
|
||
Comment types.String `tfsdk:"comment"`
|
||
Type types.String `tfsdk:"type"`
|
||
Bits types.Int64 `tfsdk:"bits"`
|
||
PublicKey types.String `tfsdk:"public_key"`
|
||
Fingerprint types.String `tfsdk:"fingerprint"`
|
||
CreatedAt types.String `tfsdk:"created_at"`
|
||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||
PrivateKeyPEM types.String `tfsdk:"private_key_pem"` // not populated by resource
|
||
}
|
||
|
||
func (r *SshResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||
resp.TypeName = req.ProviderTypeName + "_ssh_key"
|
||
}
|
||
|
||
func (r *SshResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||
resp.Schema = rschema.Schema{
|
||
Attributes: map[string]rschema.Attribute{
|
||
"id": rschema.StringAttribute{
|
||
Computed: true,
|
||
Description: "SSH key ID (UUID)",
|
||
PlanModifiers: []planmodifier.String{
|
||
stringplanmodifier.UseStateForUnknown(),
|
||
},
|
||
},
|
||
"name": rschema.StringAttribute{
|
||
Required: true,
|
||
Description: "Display name",
|
||
PlanModifiers: []planmodifier.String{
|
||
stringplanmodifier.RequiresReplace(),
|
||
},
|
||
},
|
||
"comment": rschema.StringAttribute{
|
||
Required: true,
|
||
Description: "Comment appended to authorized key",
|
||
PlanModifiers: []planmodifier.String{
|
||
stringplanmodifier.RequiresReplace(),
|
||
},
|
||
},
|
||
"type": rschema.StringAttribute{
|
||
Optional: true,
|
||
Description: "Key type: rsa or ed25519 (default rsa)",
|
||
Validators: []validator.String{
|
||
stringvalidator.OneOf("rsa", "ed25519", ""),
|
||
},
|
||
PlanModifiers: []planmodifier.String{
|
||
stringplanmodifier.RequiresReplace(),
|
||
},
|
||
},
|
||
"bits": rschema.Int64Attribute{
|
||
Optional: true,
|
||
Description: "RSA key size (2048/3072/4096). Ignored for ed25519.",
|
||
Validators: []validator.Int64{
|
||
int64validator.OneOf(2048, 3072, 4096),
|
||
},
|
||
PlanModifiers: []planmodifier.Int64{
|
||
int64planmodifier.RequiresReplace(),
|
||
},
|
||
},
|
||
"public_key": rschema.StringAttribute{
|
||
Computed: true,
|
||
Description: "OpenSSH authorized key",
|
||
},
|
||
"fingerprint": rschema.StringAttribute{
|
||
Computed: true,
|
||
Description: "SHA256 fingerprint",
|
||
},
|
||
"created_at": rschema.StringAttribute{
|
||
Computed: true,
|
||
Description: "Creation time (RFC3339, UTC)",
|
||
},
|
||
"updated_at": rschema.StringAttribute{
|
||
Computed: true,
|
||
Description: "Update time (RFC3339, UTC)",
|
||
},
|
||
"private_key_pem": rschema.StringAttribute{
|
||
Computed: true,
|
||
Sensitive: true,
|
||
Description: "Private key PEM (resource doesn’t reveal; stays empty).",
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
func (r *SshResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
|
||
if req.ProviderData == nil {
|
||
return
|
||
}
|
||
r.client = req.ProviderData.(*Client)
|
||
}
|
||
|
||
func (r *SshResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||
if r.client == nil || r.client.SDK == nil {
|
||
resp.Diagnostics.AddError("Client not configured", "Provider configuration missing")
|
||
return
|
||
}
|
||
|
||
var plan sshResModel
|
||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
body := autoglue.DtoCreateSSHRequest{
|
||
Name: plan.Name.ValueStringPointer(),
|
||
Comment: plan.Comment.ValueStringPointer(),
|
||
}
|
||
if t := plan.Type.ValueString(); t != "" {
|
||
body.Type = &t
|
||
}
|
||
if !plan.Bits.IsNull() && !plan.Bits.IsUnknown() {
|
||
b := int32(plan.Bits.ValueInt64())
|
||
body.Bits = &b
|
||
}
|
||
|
||
created, httpResp, err := r.client.SDK.SshAPI.CreateSSHKey(ctx).Body(body).Execute()
|
||
if err != nil {
|
||
resp.Diagnostics.AddError("Create ssh key failed", fmt.Sprintf("%v", httpErr(err, httpResp)))
|
||
return
|
||
}
|
||
|
||
state := sshResModel{
|
||
ID: types.StringPointerValue(created.Id),
|
||
Name: types.StringPointerValue(created.Name),
|
||
Comment: plan.Comment,
|
||
Type: plan.Type,
|
||
Bits: plan.Bits,
|
||
PublicKey: types.StringPointerValue(created.PublicKey),
|
||
Fingerprint: types.StringPointerValue(created.Fingerprint),
|
||
CreatedAt: types.StringPointerValue(created.CreatedAt),
|
||
UpdatedAt: types.StringPointerValue(created.UpdatedAt),
|
||
// PrivateKeyPEM left empty (no reveal on resource)
|
||
}
|
||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||
}
|
||
|
||
func (r *SshResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||
if r.client == nil || r.client.SDK == nil {
|
||
resp.Diagnostics.AddError("Client not configured", "Provider configuration missing")
|
||
return
|
||
}
|
||
|
||
var state sshResModel
|
||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
got, httpResp, err := r.client.SDK.SshAPI.GetSSHKey(ctx, state.ID.ValueString()).Execute()
|
||
if err != nil {
|
||
if isNotFound(httpResp) {
|
||
resp.State.RemoveResource(ctx)
|
||
return
|
||
}
|
||
resp.Diagnostics.AddError("Read ssh key failed", fmt.Sprintf("%v", httpErr(err, httpResp)))
|
||
return
|
||
}
|
||
|
||
// Map from flat fields on DtoSshRevealResponse
|
||
state.Name = types.StringPointerValue(got.Name)
|
||
state.PublicKey = types.StringPointerValue(got.PublicKey)
|
||
state.Fingerprint = types.StringPointerValue(got.Fingerprint)
|
||
state.CreatedAt = types.StringPointerValue(got.CreatedAt)
|
||
state.UpdatedAt = types.StringPointerValue(got.UpdatedAt)
|
||
// We intentionally do NOT set PrivateKeyPEM here (resource doesn't reveal)
|
||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||
}
|
||
|
||
func (r *SshResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||
// All changes are RequiresReplace; no server-side update.
|
||
var state sshResModel
|
||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||
}
|
||
|
||
func (r *SshResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||
if r.client == nil || r.client.SDK == nil {
|
||
resp.Diagnostics.AddError("Client not configured", "Provider configuration missing")
|
||
return
|
||
}
|
||
|
||
var state sshResModel
|
||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||
if resp.Diagnostics.HasError() {
|
||
return
|
||
}
|
||
|
||
_, httpResp, err := r.client.SDK.SshAPI.DeleteSSHKey(ctx, state.ID.ValueString()).Execute()
|
||
if err != nil && !isNotFound(httpResp) {
|
||
resp.Diagnostics.AddError("Delete ssh key failed", fmt.Sprintf("%v", httpErr(err, httpResp)))
|
||
}
|
||
}
|
||
|
||
func (r *SshResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...)
|
||
}
|