mirror of
https://github.com/ysoftdevs/terraform-provider-bitbucketserver.git
synced 2026-04-27 11:06:58 +02:00
feat: Support repository access tokens
This is very much a WIP. It will be extended quite a bit to support automatic recreation and similar stuff. For now it will only be pushed to be used internally.
This commit is contained in:
212
bitbucket/resource_repository_access_token.go
Normal file
212
bitbucket/resource_repository_access_token.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/xvlcwk-terraform/terraform-provider-bitbucketserver/bitbucket/util"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type repositoryAccessTokenModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Permissions types.Set `tfsdk:"permissions"`
|
||||
ExpireIn types.Int64 `tfsdk:"expire_in"`
|
||||
Project types.String `tfsdk:"project"`
|
||||
Repository types.String `tfsdk:"repository"`
|
||||
Token types.String `tfsdk:"token"`
|
||||
CreatedDate types.Int64 `tfsdk:"created_date"`
|
||||
}
|
||||
|
||||
type repositoryAccessTokenResource struct {
|
||||
resourceHelper *util.AccessTokenResourceHelper
|
||||
}
|
||||
|
||||
func newRepositoryAccessTokenResource() resource.Resource {
|
||||
return &repositoryAccessTokenResource{
|
||||
resourceHelper: util.NewAccessTokenResourceHelper(),
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the implementation satisfies the desired interfaces.
|
||||
var _ resource.ResourceWithConfigure = &repositoryAccessTokenResource{}
|
||||
|
||||
// Metadata should return the full name of the resource.
|
||||
func (r *repositoryAccessTokenResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_repository_access_token"
|
||||
}
|
||||
|
||||
// Schema should return the schema for this resource.
|
||||
func (r *repositoryAccessTokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "An HTTP-Access token limited to the given repository",
|
||||
Attributes: r.resourceHelper.Schema(map[string]schema.Attribute{
|
||||
"repository": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The repository slug",
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var data repositoryAccessTokenModel
|
||||
|
||||
// Read Terraform plan data into the model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
payload, diagnostics := r.createRequestData(ctx, data)
|
||||
|
||||
if diagnostics != nil {
|
||||
resp.Diagnostics.Append(diagnostics...)
|
||||
}
|
||||
|
||||
tokenResponse, tokenErrorResponse := r.resourceHelper.Client.Put(r.getUrlForProject(data), bytes.NewBuffer(payload))
|
||||
response, convertingResponseDiagnostics := r.readResponse(tokenErrorResponse, tokenResponse, &data)
|
||||
if convertingResponseDiagnostics != nil {
|
||||
resp.Diagnostics.Append(convertingResponseDiagnostics)
|
||||
return
|
||||
}
|
||||
|
||||
data.Token = types.StringValue(response.Token)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
func (r *repositoryAccessTokenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var data repositoryAccessTokenModel
|
||||
|
||||
// Read Terraform plan data into the model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tokenResponse, tokenErrorResponse := r.resourceHelper.Client.Get(r.getUrlForId(data))
|
||||
_, diagnostic := r.readResponse(tokenErrorResponse, tokenResponse, &data)
|
||||
if diagnostic != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var data repositoryAccessTokenModel
|
||||
|
||||
// Read Terraform plan data into the model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
payload, diagnostics := r.createRequestData(ctx, data)
|
||||
|
||||
if diagnostics != nil {
|
||||
resp.Diagnostics.Append(diagnostics...)
|
||||
}
|
||||
tokenResponse, tokenErrorResponse := r.resourceHelper.Client.Post(r.getUrlForId(data), bytes.NewBuffer(payload))
|
||||
_, convertingResponseDiagnostics := r.readResponse(tokenErrorResponse, tokenResponse, &data)
|
||||
if convertingResponseDiagnostics != nil {
|
||||
resp.Diagnostics.Append(convertingResponseDiagnostics)
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var data repositoryAccessTokenModel
|
||||
|
||||
// Read Terraform plan data into the model
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tokenResponse, tokenErrorResponse := r.resourceHelper.Client.Delete(r.getUrlForId(data))
|
||||
if tokenErrorResponse != nil {
|
||||
resp.Diagnostics.Append(diag.NewErrorDiagnostic(
|
||||
"Unable to Delete Resource",
|
||||
"An unexpected error occurred while deleting the resource. "+
|
||||
"Please report this issue to the provider developers.\n\n"+
|
||||
"Error: "+tokenErrorResponse.Error()))
|
||||
}
|
||||
|
||||
if tokenResponse.StatusCode != 204 {
|
||||
resp.Diagnostics.Append(diag.NewErrorDiagnostic(
|
||||
"Unable to Delete Resource",
|
||||
"An unexpected statusCode occurred while deleting the resource. "+
|
||||
"Please report this issue to the provider developers.\n\n"+
|
||||
"Status: "+tokenResponse.Status))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) Configure(ctx context.Context, configureRequest resource.ConfigureRequest, configureResponse *resource.ConfigureResponse) {
|
||||
r.resourceHelper.Configure(ctx, configureRequest, configureResponse)
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) createRequestData(ctx context.Context, data repositoryAccessTokenModel) ([]byte, diag.Diagnostics) {
|
||||
var permissions []string
|
||||
permissionConversionDiagnostics := data.Permissions.ElementsAs(ctx, &permissions, false)
|
||||
if permissionConversionDiagnostics != nil {
|
||||
return nil, permissionConversionDiagnostics
|
||||
}
|
||||
tokenRequest := &util.CreateAccessTokenRequest{
|
||||
ExpiryDays: data.ExpireIn.ValueInt64(),
|
||||
Name: data.Name.ValueString(),
|
||||
Permissions: permissions,
|
||||
}
|
||||
payload, jsonEncodingError := json.Marshal(tokenRequest)
|
||||
if jsonEncodingError != nil {
|
||||
return nil, diag.Diagnostics{diag.NewErrorDiagnostic("Marshalling error", fmt.Sprintf("Failed to encode %v. Is it valid Json?", tokenRequest))}
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) readResponse(tokenErrorResponse error, tokenResponse *http.Response, data *repositoryAccessTokenModel) (*util.AccessTokenResponse, *diag.ErrorDiagnostic) {
|
||||
if tokenErrorResponse != nil {
|
||||
diagnostic := diag.NewErrorDiagnostic("http error", tokenErrorResponse.Error())
|
||||
return nil, &diagnostic
|
||||
}
|
||||
if tokenResponse.StatusCode != 200 {
|
||||
diagnostic := diag.NewErrorDiagnostic("http error", fmt.Sprintf("Response Status: %d", tokenResponse.StatusCode))
|
||||
return nil, &diagnostic
|
||||
}
|
||||
|
||||
body, readBodyError := io.ReadAll(tokenResponse.Body)
|
||||
if readBodyError != nil {
|
||||
diagnostic := diag.NewErrorDiagnostic("Error reading response", "Failed to read response. Is it valid Json?")
|
||||
return nil, &diagnostic
|
||||
}
|
||||
|
||||
response := &util.AccessTokenResponse{}
|
||||
unmarshallError := json.Unmarshal(body, response)
|
||||
if unmarshallError != nil {
|
||||
diagnostic := diag.NewErrorDiagnostic("Error reading response", fmt.Sprintf("Failed to read response %v. Is it valid Json?, %v", string(body), unmarshallError))
|
||||
return nil, &diagnostic
|
||||
}
|
||||
|
||||
data.Id = types.StringValue(response.Id)
|
||||
data.CreatedDate = types.Int64Value(response.CreatedDate)
|
||||
data.Name = types.StringValue(response.Name)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) getUrlForId(data repositoryAccessTokenModel) string {
|
||||
return fmt.Sprintf("%v/%v", r.getUrlForProject(data), data.Id.ValueString())
|
||||
}
|
||||
|
||||
func (r *repositoryAccessTokenResource) getUrlForProject(data repositoryAccessTokenModel) string {
|
||||
projectKey := data.Project.ValueString()
|
||||
repositorySlug := data.Repository.ValueString()
|
||||
repositoryUrl := fmt.Sprintf("/rest/access-tokens/latest/projects/%v/repos/%v", projectKey, repositorySlug)
|
||||
return repositoryUrl
|
||||
}
|
||||
Reference in New Issue
Block a user