internal/provider/bitbucketserver_connection_resource.go (244 lines of code) (raw):
// Copyright (c) HashiCorp, Inc.
package provider
import (
"context"
"fmt"
"strconv"
"time"
"terraform-provider-devlake/internal/client"
"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/types"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &bitbucketServerConnectionResource{}
_ resource.ResourceWithConfigure = &bitbucketServerConnectionResource{}
_ resource.ResourceWithImportState = &bitbucketServerConnectionResource{}
)
// NewBitbucketServerConnectionResource is a helper function to simplify the provider implementation.
func NewBitbucketServerConnectionResource() resource.Resource {
return &bitbucketServerConnectionResource{}
}
// bitbucketServerConnectionResource is the resource implementation.
type bitbucketServerConnectionResource struct {
client *client.Client
}
// bitbucketServerConnectionResourceModel maps the resource schema data.
type bitbucketServerConnectionResourceModel struct {
ID types.String `tfsdk:"id"`
LastUpdated types.String `tfsdk:"last_updated"`
CreatedAt types.String `tfsdk:"created_at"`
Endpoint types.String `tfsdk:"endpoint"`
Name types.String `tfsdk:"name"`
Password types.String `tfsdk:"password"`
Proxy types.String `tfsdk:"proxy"`
RateLimitPerHour types.Int64 `tfsdk:"rate_limit_per_hour"`
UpdatedAt types.String `tfsdk:"updated_at"`
Username types.String `tfsdk:"username"`
}
// Metadata returns the resource type name.
func (r *bitbucketServerConnectionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_bitbucketserver_connection"
}
// Schema defines the schema for the resource.
func (r *bitbucketServerConnectionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Description: "Numeric identifier for the connection. This is a string for easier resource import.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"last_updated": schema.StringAttribute{
Computed: true,
Description: "Timestamp of the last Terraform update of the connection.",
},
"created_at": schema.StringAttribute{
Computed: true,
Description: "When the connection was created in devlake.",
},
"endpoint": schema.StringAttribute{
Description: "The REST API endpoint URL.",
Required: true,
},
"name": schema.StringAttribute{
Description: "The name of the bitbucket server connection.",
Required: true,
},
"password": schema.StringAttribute{
Description: "Service account password or token, the following permissions are required to collect data from Bitbucket repositories: Repository read.",
Required: true,
Sensitive: true,
},
"proxy": schema.StringAttribute{
Computed: true,
Description: "If you are behind a corporate firewall or VPN you may need to utilize a proxy server.",
Optional: true,
},
"rate_limit_per_hour": schema.Int64Attribute{
Optional: true,
Description: "DevLake uses a dynamic rate limit to collect Bitbucket Server/Data Center data. You can adjust the rate limit if you want to increase or lower the speed.",
Computed: true,
},
"updated_at": schema.StringAttribute{
Computed: true,
Description: "When the connection was updated in devlake.",
},
"username": schema.StringAttribute{
Description: "Service account username.",
Required: true,
},
},
}
}
// Create a new resource.
func (r *bitbucketServerConnectionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Retrieve values from plan
var plan bitbucketServerConnectionResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
now := time.Now().Format(time.RFC850)
// Generate API request body from plan
var bitbucketServerConnectionCreate = client.BitbucketServerConnection{
CreatedAt: now,
Endpoint: plan.Endpoint.ValueString(),
Name: plan.Name.ValueString(),
Password: plan.Password.ValueString(),
Proxy: plan.Proxy.ValueString(),
RateLimitPerHour: int(plan.RateLimitPerHour.ValueInt64()),
UpdatedAt: now,
Username: plan.Username.ValueString(),
}
// Create new bitbucketserverconnection
bitbucketServerConnection, err := r.client.CreateBitbucketServerConnection(bitbucketServerConnectionCreate)
if err != nil {
resp.Diagnostics.AddError(
"Error creating bitbucketServerConnection",
"Could not create bitbucketServerConnection, unexpected error: "+err.Error(),
)
return
}
// Map response body to schema and populate Computed attribute values
plan.ID = types.StringValue(strconv.Itoa(bitbucketServerConnection.ID))
plan.LastUpdated = types.StringValue(now)
plan.CreatedAt = types.StringValue(bitbucketServerConnection.CreatedAt)
plan.Endpoint = types.StringValue(bitbucketServerConnection.Endpoint)
plan.Name = types.StringValue(bitbucketServerConnection.Name)
plan.Proxy = types.StringValue(bitbucketServerConnection.Proxy)
plan.RateLimitPerHour = types.Int64Value(int64(bitbucketServerConnection.RateLimitPerHour))
plan.UpdatedAt = types.StringValue(bitbucketServerConnection.UpdatedAt)
plan.Username = types.StringValue(bitbucketServerConnection.Username)
// Set state to fully populated data
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 *bitbucketServerConnectionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// Get current state
var state bitbucketServerConnectionResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Get refreshed bitbucket server connection value from Devlake
bitbucketServerConnection, err := r.client.GetBitbucketServerConnection(state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Unable to Read Devlake BitbucketServerConnection",
err.Error(),
)
return
}
// Overwrite connection with refreshed state
state.ID = types.StringValue(strconv.Itoa(bitbucketServerConnection.ID))
state.CreatedAt = types.StringValue(bitbucketServerConnection.CreatedAt)
state.Endpoint = types.StringValue(bitbucketServerConnection.Endpoint)
state.Name = types.StringValue(bitbucketServerConnection.Name)
state.Proxy = types.StringValue(bitbucketServerConnection.Proxy)
state.RateLimitPerHour = types.Int64Value(int64(bitbucketServerConnection.RateLimitPerHour))
state.UpdatedAt = types.StringValue(bitbucketServerConnection.UpdatedAt)
state.Username = types.StringValue(bitbucketServerConnection.Username)
// Set refreshed state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Update updates the resource and sets the updated Terraform state on success.
func (r *bitbucketServerConnectionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// Retrieve values from plan
var plan bitbucketServerConnectionResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Generate API request body from plan
id, err := strconv.Atoi(plan.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error updating bitbucketserverconnection",
"Could not update bitbucketserverconnection, unexpected error: "+err.Error(),
)
return
}
var bitbucketServerConnectionUpdate = client.BitbucketServerConnection{
ID: id,
CreatedAt: plan.CreatedAt.ValueString(),
Endpoint: plan.Endpoint.ValueString(),
Name: plan.Name.ValueString(),
Password: plan.Password.ValueString(),
Proxy: plan.Proxy.ValueString(),
RateLimitPerHour: int(plan.RateLimitPerHour.ValueInt64()),
UpdatedAt: time.Now().Format(time.RFC850),
Username: plan.Username.ValueString(),
}
// Update existing order
updatedBitbucketServerConnection, err := r.client.UpdateBitbucketServerConnection(plan.ID.ValueString(), bitbucketServerConnectionUpdate)
if err != nil {
resp.Diagnostics.AddError(
"Error updating bitbucketserverconnection",
"Could not update bitbucketserverconnection, unexpected error: "+err.Error(),
)
return
}
plan.ID = types.StringValue(strconv.Itoa(updatedBitbucketServerConnection.ID))
plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
plan.CreatedAt = types.StringValue(updatedBitbucketServerConnection.CreatedAt)
plan.Endpoint = types.StringValue(updatedBitbucketServerConnection.Endpoint)
plan.Name = types.StringValue(updatedBitbucketServerConnection.Name)
plan.Proxy = types.StringValue(updatedBitbucketServerConnection.Proxy)
plan.RateLimitPerHour = types.Int64Value(int64(updatedBitbucketServerConnection.RateLimitPerHour))
plan.UpdatedAt = types.StringValue(updatedBitbucketServerConnection.UpdatedAt)
plan.Username = types.StringValue(updatedBitbucketServerConnection.Username)
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Delete deletes the resource and removes the Terraform state on success.
func (r *bitbucketServerConnectionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
// Retrieve values from state
var state bitbucketServerConnectionResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Delete existing apikey
err := r.client.DeleteBitbucketServerConnection(state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error deleting apikey",
"Could not delete apikey, unexpected error: "+err.Error(),
)
return
}
}
func (r *bitbucketServerConnectionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Retrieve import ID and save to id attribute
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
// Configure adds the provider configured client to the resource.
func (r *bitbucketServerConnectionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Add a nil check when handling ProviderData because Terraform
// sets that data after it calls the ConfigureProvider RPC.
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
r.client = client
}