| // 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/stringplanmodifier" |
| "github.com/hashicorp/terraform-plugin-framework/types" |
| ) |
| |
| // Ensure the implementation satisfies the expected interfaces. |
| var ( |
| _ resource.Resource = &bitbucketServerConnectionScopeResource{} |
| _ resource.ResourceWithConfigure = &bitbucketServerConnectionScopeResource{} |
| _ resource.ResourceWithImportState = &bitbucketServerConnectionScopeResource{} |
| ) |
| |
| // NewBitbucketServerConnectionScopeResource is a helper function to simplify the provider implementation. |
| func NewBitbucketServerConnectionScopeResource() resource.Resource { |
| return &bitbucketServerConnectionScopeResource{} |
| } |
| |
| // bitbucketServerConnectionScopeResource is the resource implementation. |
| type bitbucketServerConnectionScopeResource struct { |
| client *client.Client |
| } |
| |
| // bitbucketServerConnectionScopeResourceModel maps the resource schema data. |
| type bitbucketServerConnectionScopeResourceModel struct { |
| ID types.String `tfsdk:"id"` |
| LastUpdated types.String `tfsdk:"last_updated"` |
| CloneUrl types.String `tfsdk:"clone_url"` |
| ConnectionId types.String `tfsdk:"connection_id"` |
| CreatedAt types.String `tfsdk:"created_at"` |
| Description types.String `tfsdk:"description"` |
| HTMLUrl types.String `tfsdk:"html_url"` |
| Name types.String `tfsdk:"name"` |
| ScopeConfigId types.String `tfsdk:"scope_config_id"` |
| } |
| |
| // Metadata returns the resource type name. |
| func (r *bitbucketServerConnectionScopeResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { |
| resp.TypeName = req.ProviderTypeName + "_bitbucketserver_connection_scope" |
| } |
| |
| // Schema defines the schema for the resource. |
| func (r *bitbucketServerConnectionScopeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { |
| resp.Schema = schema.Schema{ |
| Attributes: map[string]schema.Attribute{ |
| "id": schema.StringAttribute{ |
| Required: true, |
| Description: "The Bitbucket project and repository in the format '<PROJECT>/repos/<REPOSITORY>'.", |
| 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(), |
| }, |
| }, |
| "clone_url": schema.StringAttribute{ |
| Description: "The Bitbucket https clone url.", |
| Required: true, |
| }, |
| "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{ |
| Description: "A description for the connection scope.", |
| Required: true, |
| }, |
| "html_url": schema.StringAttribute{ |
| Description: "The Bitbucket HTML browse url.", |
| Required: true, |
| }, |
| "name": schema.StringAttribute{ |
| Description: "A name for the connection scope.", |
| 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 *bitbucketServerConnectionScopeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { |
| // Retrieve values from plan |
| var plan bitbucketServerConnectionScopeResourceModel |
| diags := req.Plan.Get(ctx, &plan) |
| resp.Diagnostics.Append(diags...) |
| if resp.Diagnostics.HasError() { |
| return |
| } |
| |
| // Generate API request body from plan |
| connectionId, err := strconv.Atoi(plan.ConnectionId.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Error creating devlake bitbucket server connection scope", |
| "Could not create devlake bitbucket server connection scope, unexpected error: "+err.Error(), |
| ) |
| return |
| } |
| scopeConfigId, err := strconv.Atoi(plan.ScopeConfigId.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Error creating devlake bitbucket server connection scope", |
| "Could not create devlake bitbucket server connection scope, unexpected error: "+err.Error(), |
| ) |
| return |
| } |
| now := time.Now().Format(time.RFC3339) |
| var bitbucketServerConnectionScopeCreate = client.BitbucketServerConnectionScope{ |
| BitbucketId: plan.ID.ValueString(), |
| CloneUrl: plan.CloneUrl.ValueString(), |
| ConnectionId: connectionId, |
| CreatedAt: now, |
| Description: plan.Description.ValueString(), |
| HTMLUrl: plan.HTMLUrl.ValueString(), |
| Name: plan.Name.ValueString(), |
| ScopeConfigId: scopeConfigId, |
| UpdatedAt: now, |
| } |
| |
| // Create new bitbucketserverconnectionscope |
| bitbucketServerConnectionScope, err := r.client.CreateBitbucketServerConnectionScope(plan.ConnectionId.ValueString(), bitbucketServerConnectionScopeCreate) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Error creating devlake bitbucket server connection scope", |
| "Could not create devlake bitbucket server connection scope, unexpected error: "+err.Error(), |
| ) |
| return |
| } |
| |
| // Map response body to schema and populate Computed attribute values |
| plan.ID = types.StringValue(bitbucketServerConnectionScope.BitbucketId) |
| plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC3339)) |
| plan.CloneUrl = types.StringValue(bitbucketServerConnectionScope.CloneUrl) |
| plan.ConnectionId = types.StringValue(strconv.Itoa(bitbucketServerConnectionScope.ConnectionId)) |
| plan.CreatedAt = types.StringValue(bitbucketServerConnectionScope.CreatedAt) |
| plan.Description = types.StringValue(bitbucketServerConnectionScope.Description) |
| plan.HTMLUrl = types.StringValue(bitbucketServerConnectionScope.HTMLUrl) |
| plan.Name = types.StringValue(bitbucketServerConnectionScope.Name) |
| plan.ScopeConfigId = types.StringValue(strconv.Itoa(bitbucketServerConnectionScope.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 *bitbucketServerConnectionScopeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { |
| // Get current state |
| var state bitbucketServerConnectionScopeResourceModel |
| diags := req.State.Get(ctx, &state) |
| resp.Diagnostics.Append(diags...) |
| if resp.Diagnostics.HasError() { |
| return |
| } |
| |
| // Get refreshed bitbucket server connection scope value from Devlake |
| bitbucketServerConnectionScope, err := r.client.ReadBitbucketServerConnectionScope(state.ConnectionId.ValueString(), state.ID.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Unable to read devlake bitbucket server connection scope", |
| err.Error(), |
| ) |
| return |
| } |
| |
| // Overwrite connection with refreshed state |
| state.ID = types.StringValue(bitbucketServerConnectionScope.BitbucketId) |
| state.CloneUrl = types.StringValue(bitbucketServerConnectionScope.CloneUrl) |
| state.ConnectionId = types.StringValue(strconv.Itoa(bitbucketServerConnectionScope.ConnectionId)) |
| state.CreatedAt = types.StringValue(bitbucketServerConnectionScope.CreatedAt) |
| state.Description = types.StringValue(bitbucketServerConnectionScope.Description) |
| state.HTMLUrl = types.StringValue(bitbucketServerConnectionScope.HTMLUrl) |
| state.Name = types.StringValue(bitbucketServerConnectionScope.Name) |
| state.ScopeConfigId = types.StringValue(strconv.Itoa(bitbucketServerConnectionScope.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 *bitbucketServerConnectionScopeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { |
| // Retrieve values from plan |
| var plan bitbucketServerConnectionScopeResourceModel |
| diags := req.Plan.Get(ctx, &plan) |
| resp.Diagnostics.Append(diags...) |
| if resp.Diagnostics.HasError() { |
| return |
| } |
| |
| // Generate API request body from plan |
| connectionId, err := strconv.Atoi(plan.ConnectionId.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Error updating devlake bitbucket server connection scope", |
| "Could not update devlake bitbucket server connection scope, unexpected error: "+err.Error(), |
| ) |
| return |
| } |
| scopeConfigId, err := strconv.Atoi(plan.ScopeConfigId.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Error creating devlake bitbucket server connection scope", |
| "Could not create devlake bitbucket server connection scope, unexpected error: "+err.Error(), |
| ) |
| return |
| } |
| var bitbucketServerConnectionScopeUpdate = client.BitbucketServerConnectionScope{ |
| BitbucketId: plan.ID.ValueString(), |
| CloneUrl: plan.CloneUrl.ValueString(), |
| ConnectionId: connectionId, |
| CreatedAt: plan.CreatedAt.ValueString(), |
| Description: plan.Description.ValueString(), |
| HTMLUrl: plan.HTMLUrl.ValueString(), |
| Name: plan.Name.ValueString(), |
| ScopeConfigId: scopeConfigId, |
| UpdatedAt: time.Now().Format(time.RFC3339), |
| } |
| |
| // Update existing connection scope |
| updatedBitbucketServerConnectionScope, err := r.client.UpdateBitbucketServerConnectionScope(plan.ConnectionId.ValueString(), plan.ID.ValueString(), bitbucketServerConnectionScopeUpdate) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Error updating devlake bitbucket server connection scope", |
| "Could not update devlake bitbucket server connection scope, unexpected error: "+err.Error(), |
| ) |
| return |
| } |
| plan.ID = types.StringValue(updatedBitbucketServerConnectionScope.BitbucketId) |
| plan.CloneUrl = types.StringValue(updatedBitbucketServerConnectionScope.CloneUrl) |
| plan.ConnectionId = types.StringValue(strconv.Itoa(updatedBitbucketServerConnectionScope.ConnectionId)) |
| plan.CreatedAt = types.StringValue(updatedBitbucketServerConnectionScope.CreatedAt) |
| plan.Description = types.StringValue(updatedBitbucketServerConnectionScope.Description) |
| plan.HTMLUrl = types.StringValue(updatedBitbucketServerConnectionScope.HTMLUrl) |
| plan.Name = types.StringValue(updatedBitbucketServerConnectionScope.Name) |
| plan.ScopeConfigId = types.StringValue(strconv.Itoa(updatedBitbucketServerConnectionScope.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 *bitbucketServerConnectionScopeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { |
| // Retrieve values from state |
| var state bitbucketServerConnectionScopeResourceModel |
| diags := req.State.Get(ctx, &state) |
| resp.Diagnostics.Append(diags...) |
| if resp.Diagnostics.HasError() { |
| return |
| } |
| |
| // Delete existing apikey |
| err := r.client.DeleteBitbucketServerConnectionScope(state.ConnectionId.ValueString(), state.ID.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Error deleting devlake bitbucket server connection scope", |
| "Could not delete devlake bitbucket server connection scope, unexpected error: "+err.Error(), |
| ) |
| return |
| } |
| } |
| |
| func (r *bitbucketServerConnectionScopeResource) 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 *bitbucketServerConnectionScopeResource) 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 |
| } |