forked from mirrors/terraform-provider-binarylane
9a58a8112c
* 👍 Bump BinaryLane OpenAPI schema to v0.34.0 * 💄 Support change of IPv4 addresses
340 lines
10 KiB
Go
340 lines
10 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"terraform-provider-binarylane/internal/binarylane"
|
|
"terraform-provider-binarylane/internal/resources"
|
|
|
|
"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/booldefault"
|
|
"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/types"
|
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
)
|
|
|
|
// Ensure the implementation satisfies the expected interfaces.
|
|
var (
|
|
_ resource.Resource = &sshKeyResource{}
|
|
_ resource.ResourceWithConfigure = &sshKeyResource{}
|
|
_ resource.ResourceWithImportState = &sshKeyResource{}
|
|
)
|
|
|
|
func NewSshKeyResource() resource.Resource {
|
|
return &sshKeyResource{}
|
|
}
|
|
|
|
type sshKeyResource struct {
|
|
bc *BinarylaneClient
|
|
}
|
|
|
|
type sshKeyModel struct {
|
|
resources.SshKeyModel
|
|
Fingerprint types.String `tfsdk:"fingerprint"`
|
|
PublicKey TrimmedStringValue `tfsdk:"public_key"`
|
|
}
|
|
|
|
func (d *sshKeyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
bc, ok := req.ProviderData.(BinarylaneClient)
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected Resource Configure Type",
|
|
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
d.bc = &bc
|
|
}
|
|
|
|
func (r *sshKeyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_ssh_key"
|
|
}
|
|
|
|
func (r *sshKeyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = resources.SshKeyResourceSchema(ctx)
|
|
|
|
// Overrides
|
|
idDescription := "The numeric ID of the SSH key."
|
|
resp.Schema.Attributes["id"] = schema.Int64Attribute{
|
|
Required: false,
|
|
Optional: false,
|
|
Computed: true,
|
|
Description: idDescription,
|
|
MarkdownDescription: idDescription,
|
|
PlanModifiers: []planmodifier.Int64{
|
|
int64planmodifier.UseStateForUnknown(),
|
|
},
|
|
}
|
|
|
|
defaultDescription := "If `true`, this SSH key will be included on all new server installations (if the operating " +
|
|
"system supports SSH key injection). Default is `false`."
|
|
resp.Schema.Attributes["default"] = schema.BoolAttribute{
|
|
Required: false,
|
|
Optional: true,
|
|
Computed: true,
|
|
Description: defaultDescription,
|
|
MarkdownDescription: defaultDescription,
|
|
Default: booldefault.StaticBool(false), // Add default to backups
|
|
}
|
|
|
|
// Additional attributes
|
|
publicKeyDescription := "The public key in OpenSSH \"authorized_keys\" format. This should be a valid public key, " +
|
|
"such as one generated by `ssh-keygen`."
|
|
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
|
|
Required: true,
|
|
Optional: false,
|
|
Computed: false,
|
|
Description: publicKeyDescription,
|
|
MarkdownDescription: publicKeyDescription,
|
|
PlanModifiers: []planmodifier.String{
|
|
RequiresReplaceForTrimmedStringModifier{},
|
|
},
|
|
CustomType: TrimmedStringType{}, // Ignore leading/trailing whitespace
|
|
}
|
|
|
|
fingerprintDescription := "The fingerprint of the SSH key."
|
|
resp.Schema.Attributes["fingerprint"] = schema.StringAttribute{
|
|
Description: fingerprintDescription,
|
|
MarkdownDescription: fingerprintDescription,
|
|
Optional: false,
|
|
Required: false,
|
|
Computed: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.UseStateForUnknown(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
var data sshKeyModel
|
|
|
|
// Read Terraform plan data into the model
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
sshResp, err := r.bc.client.PostAccountKeysWithResponse(ctx, binarylane.SshKeyRequest{
|
|
Name: data.Name.ValueString(),
|
|
Default: data.Default.ValueBoolPointer(),
|
|
PublicKey: data.PublicKey.ValueString(),
|
|
})
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
fmt.Sprintf("Error creating SSH Key: name=%s", data.Name.ValueString()),
|
|
err.Error(),
|
|
)
|
|
return
|
|
}
|
|
if sshResp.StatusCode() == http.StatusNotFound {
|
|
tflog.Warn(ctx, fmt.Sprintf("SSH Key not found, removing from state: name=%s", data.Name.String()))
|
|
resp.State.RemoveResource(ctx)
|
|
return
|
|
}
|
|
if sshResp.StatusCode() != http.StatusOK {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected HTTP status code creating SSH key",
|
|
fmt.Sprintf("Received %s creating SSH key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Set data values
|
|
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
|
|
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Save data into Terraform state
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
|
}
|
|
|
|
func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
var data sshKeyModel
|
|
|
|
// Read Terraform prior state data into the model
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Read API call logic
|
|
sshResp, err := r.bc.client.GetAccountKeysKeyIdWithResponse(ctx, int(data.Id.ValueInt64()))
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
fmt.Sprintf("Error creating SSH Key: name=%s", data.Name.ValueString()),
|
|
err.Error(),
|
|
)
|
|
return
|
|
}
|
|
if sshResp.StatusCode() != http.StatusOK {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected HTTP status code getting SSH key",
|
|
fmt.Sprintf("Received %s getting SSH key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Set data values
|
|
data.Id = types.Int64Value(sshResp.JSON200.SshKey.Id)
|
|
data.Default = types.BoolValue(sshResp.JSON200.SshKey.Default)
|
|
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
|
|
data.PublicKey = TrimmedStringValue{
|
|
StringValue: types.StringValue(sshResp.JSON200.SshKey.PublicKey),
|
|
}
|
|
data.Fingerprint = types.StringValue(sshResp.JSON200.SshKey.Fingerprint)
|
|
|
|
// Save updated data into Terraform state
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
|
}
|
|
|
|
func (r *sshKeyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
var plan, state sshKeyModel
|
|
|
|
// Read Terraform prior state data into the model
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Read Terraform plan data into the model
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Update API call logic
|
|
if !state.Name.Equal(plan.Name) || !state.Default.Equal(plan.Default) {
|
|
sshResp, err := r.bc.client.PutAccountKeysKeyIdWithResponse(ctx, int(state.Id.ValueInt64()), binarylane.UpdateSshKeyRequest{
|
|
Name: plan.Name.ValueString(),
|
|
Default: plan.Default.ValueBoolPointer(),
|
|
})
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
fmt.Sprintf("Error updating SSH Key: name=%s", plan.Name.ValueString()),
|
|
err.Error(),
|
|
)
|
|
return
|
|
}
|
|
if sshResp.StatusCode() != http.StatusOK {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected HTTP status code updating SSH Key",
|
|
fmt.Sprintf("Received %s updating new SSH Key: name=%s. Details: %s", sshResp.Status(), plan.Name.ValueString(), sshResp.Body),
|
|
)
|
|
return
|
|
}
|
|
state.Name = plan.Name
|
|
state.Default = plan.Default
|
|
}
|
|
|
|
// Save updated data into Terraform state
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
|
}
|
|
|
|
func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
var state sshKeyModel
|
|
|
|
// Read Terraform prior state into the model
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Delete API call logic
|
|
sshResp, err := r.bc.client.DeleteAccountKeysKeyIdWithResponse(ctx, int(state.Id.ValueInt64()))
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
fmt.Sprintf("Error deleting SSH Key: name=%s", state.Name.ValueString()),
|
|
err.Error(),
|
|
)
|
|
return
|
|
}
|
|
if sshResp.StatusCode() != http.StatusNoContent {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected HTTP status code deleting SSH Key",
|
|
fmt.Sprintf("Received %s deleting SSH Key: name=%s. Details: %s", sshResp.Status(), state.Name.ValueString(), sshResp.Body),
|
|
)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *sshKeyResource) ImportState(
|
|
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
|
|
) {
|
|
// Import by ID
|
|
id, err := strconv.ParseInt(req.ID, 10, 32)
|
|
if err == nil {
|
|
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id))
|
|
resp.Diagnostics.Append(diags...)
|
|
return
|
|
}
|
|
|
|
// Import by name
|
|
var page int32 = 1
|
|
perPage := int32(200)
|
|
var sshKey binarylane.SshKey
|
|
var nextPage bool = true
|
|
|
|
for nextPage { // Need to paginate because the API does not support filtering by fingerprint
|
|
params := binarylane.GetAccountKeysParams{
|
|
Page: &page,
|
|
PerPage: &perPage,
|
|
}
|
|
|
|
sshResp, err := r.bc.client.GetAccountKeysWithResponse(ctx, ¶ms)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(fmt.Sprintf("Error getting SSH keys for import: fingerprint=%s", req.ID), err.Error())
|
|
return
|
|
}
|
|
|
|
if sshResp.StatusCode() != http.StatusOK {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected HTTP status code getting SSH key for import",
|
|
fmt.Sprintf("Received %s getting SSH key for import: fingerprint=%s. Details: %s", sshResp.Status(), req.ID,
|
|
sshResp.Body))
|
|
return
|
|
}
|
|
|
|
sshKeys := sshResp.JSON200.SshKeys
|
|
for _, key := range sshKeys {
|
|
if key.Fingerprint == req.ID {
|
|
sshKey = key
|
|
nextPage = false
|
|
break
|
|
}
|
|
}
|
|
if sshResp.JSON200.Links == nil || sshResp.JSON200.Links.Pages.Next == nil {
|
|
nextPage = false
|
|
break
|
|
}
|
|
|
|
page++
|
|
}
|
|
|
|
if sshKey.Id == 0 {
|
|
resp.Diagnostics.AddError(
|
|
"Could not find SSH key by fingerprint",
|
|
fmt.Sprintf("Error finding SSH key: fingerprint=%s", req.ID),
|
|
)
|
|
return
|
|
}
|
|
|
|
diags := resp.State.SetAttribute(ctx, path.Root("id"), sshKey.Id)
|
|
resp.Diagnostics.Append(diags...)
|
|
}
|