blob: b756b4740f7348a6c58cdb63e0c54e723b69610d [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
package provider
import (
"context"
"fmt"
"strconv"
"strings"
"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/stringdefault"
"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 = &githubConnectionScopeResource{}
_ resource.ResourceWithConfigure = &githubConnectionScopeResource{}
_ resource.ResourceWithImportState = &githubConnectionScopeResource{}
)
// NewGithubConnectionScopeResource is a helper function to simplify the provider implementation.
func NewGithubConnectionScopeResource() resource.Resource {
return &githubConnectionScopeResource{}
}
// githubConnectionScopeResource is the resource implementation.
type githubConnectionScopeResource struct {
client *client.Client
}
// githubConnectionScopeResourceModel maps the resource schema data.
type githubConnectionScopeResourceModel struct {
ID types.String `tfsdk:"id"`
LastUpdated types.String `tfsdk:"last_updated"`
ConnectionId types.String `tfsdk:"connection_id"`
CreatedAt types.String `tfsdk:"created_at"`
Description types.String `tfsdk:"description"`
FullName types.String `tfsdk:"full_name"`
ScopeConfigId types.String `tfsdk:"scope_config_id"`
}
// Metadata returns the resource type name.
func (r *githubConnectionScopeResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_github_connection_scope"
}
// Schema defines the schema for the resource.
func (r *githubConnectionScopeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "The id of the repository in github.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"last_updated": schema.StringAttribute{
Computed: true,
Description: "Timestamp of the last Terraform update of the connection scope.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"connection_id": schema.StringAttribute{
Description: "The Connection this scope is part of.",
Required: true,
},
"created_at": schema.StringAttribute{
Computed: true,
Description: "When the scope was created in devlake.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"description": schema.StringAttribute{
Computed: true,
Default: stringdefault.StaticString(""),
Description: "A description for the connection scope.",
Optional: true,
},
"full_name": schema.StringAttribute{
Description: "The Github org and repository in the format '<ORG>/<REPOSITORY>'.",
Required: true,
},
"scope_config_id": schema.StringAttribute{
Description: "The config used for the scope. Needs to be created first.",
Required: true,
},
},
}
}
// Create a new resource.
func (r *githubConnectionScopeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Retrieve values from plan
var plan githubConnectionScopeResourceModel
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 creating devlake github connection scope",
"Could not create devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
connectionId, err := strconv.Atoi(plan.ConnectionId.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error creating devlake github connection scope",
"Could not create devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
scopeConfigId, err := strconv.Atoi(plan.ScopeConfigId.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error creating devlake github connection scope",
"Could not create devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
now := time.Now().Format(time.RFC3339)
var githubConnectionScopeCreate = client.GithubConnectionScope{
GithubId: id,
CloneUrl: "https://github.com/" + plan.FullName.ValueString() + ".git",
ConnectionId: connectionId,
CreatedAt: now,
Description: plan.Description.ValueString(),
FullName: plan.FullName.ValueString(),
HTMLUrl: "https://github.com/" + plan.FullName.ValueString(),
Name: strings.Split(plan.FullName.ValueString(), "/")[1],
ScopeConfigId: scopeConfigId,
UpdatedAt: now,
CreatedDate: now,
UpdatedDate: now,
}
// Create new githubconnectionscope
githubConnectionScope, err := r.client.CreateGithubConnectionScope(plan.ConnectionId.ValueString(), githubConnectionScopeCreate)
if err != nil {
resp.Diagnostics.AddError(
"Error creating devlake github connection scope",
"Could not create devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
// Map response body to schema and populate Computed attribute values
plan.ID = types.StringValue(strconv.Itoa(githubConnectionScope.GithubId))
plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC3339))
plan.ConnectionId = types.StringValue(strconv.Itoa(githubConnectionScope.ConnectionId))
plan.CreatedAt = types.StringValue(githubConnectionScope.CreatedAt)
plan.Description = types.StringValue(githubConnectionScope.Description)
plan.FullName = types.StringValue(githubConnectionScope.FullName)
plan.ScopeConfigId = types.StringValue(strconv.Itoa(githubConnectionScope.ScopeConfigId))
// 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 *githubConnectionScopeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// Get current state
var state githubConnectionScopeResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Get refreshed github connection scope value from Devlake
githubConnectionScope, err := r.client.ReadGithubConnectionScope(state.ConnectionId.ValueString(), state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Unable to read devlake github connection scope",
err.Error(),
)
return
}
// Overwrite connection with refreshed state
state.ID = types.StringValue(strconv.Itoa(githubConnectionScope.GithubId))
state.ConnectionId = types.StringValue(strconv.Itoa(githubConnectionScope.ConnectionId))
state.CreatedAt = types.StringValue(githubConnectionScope.CreatedAt)
state.Description = types.StringValue(githubConnectionScope.Description)
state.FullName = types.StringValue(githubConnectionScope.FullName)
state.ScopeConfigId = types.StringValue(strconv.Itoa(githubConnectionScope.ScopeConfigId))
// Set refreshed state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Update fetches the resource and sets the updated Terraform state on success.
func (r *githubConnectionScopeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// Retrieve values from plan
var plan githubConnectionScopeResourceModel
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 devlake github connection scope",
"Could not update devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
connectionId, err := strconv.Atoi(plan.ConnectionId.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error updating devlake github connection scope",
"Could not update devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
scopeConfigId, err := strconv.Atoi(plan.ScopeConfigId.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error creating devlake github connection scope",
"Could not create devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
var githubConnectionScopeUpdate = client.GithubConnectionScope{
GithubId: id,
CloneUrl: "https://github.com/" + plan.FullName.ValueString() + ".git",
ConnectionId: connectionId,
CreatedAt: plan.CreatedAt.ValueString(),
Description: plan.Description.ValueString(),
FullName: plan.FullName.ValueString(),
HTMLUrl: "https://github.com/" + plan.FullName.ValueString(),
Name: strings.Split(plan.FullName.ValueString(), "/")[1],
ScopeConfigId: scopeConfigId,
UpdatedAt: time.Now().Format(time.RFC3339),
CreatedDate: plan.CreatedAt.ValueString(),
UpdatedDate: time.Now().Format(time.RFC3339),
}
// Update existing connection scope
updatedGithubConnectionScope, err := r.client.UpdateGithubConnectionScope(plan.ConnectionId.ValueString(), plan.ID.ValueString(), githubConnectionScopeUpdate)
if err != nil {
resp.Diagnostics.AddError(
"Error updating devlake github connection scope",
"Could not update devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
plan.ID = types.StringValue(strconv.Itoa(updatedGithubConnectionScope.GithubId))
plan.ConnectionId = types.StringValue(strconv.Itoa(updatedGithubConnectionScope.ConnectionId))
plan.CreatedAt = types.StringValue(updatedGithubConnectionScope.CreatedAt)
plan.Description = types.StringValue(updatedGithubConnectionScope.Description)
plan.FullName = types.StringValue(updatedGithubConnectionScope.FullName)
plan.ScopeConfigId = types.StringValue(strconv.Itoa(updatedGithubConnectionScope.ScopeConfigId))
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 *githubConnectionScopeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
// Retrieve values from state
var state githubConnectionScopeResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Delete existing apikey
err := r.client.DeleteGithubConnectionScope(state.ConnectionId.ValueString(), state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error deleting devlake github connection scope",
"Could not delete devlake github connection scope, unexpected error: "+err.Error(),
)
return
}
}
func (r *githubConnectionScopeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Retrieve import ID and connection id and save to attribute
idParts := strings.Split(req.ID, ",")
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: connection_id,scope_id. Got: %q", req.ID),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("connection_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...)
}
// Configure adds the provider configured client to the resource.
func (r *githubConnectionScopeResource) 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
}