internal/provider/resource_gitlab_integration_redmine.go (205 lines of code) (raw):
package provider
import (
"context"
"fmt"
"regexp"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
gitlab "gitlab.com/gitlab-org/api/client-go"
"gitlab.com/gitlab-org/terraform-provider-gitlab/internal/provider/api"
"gitlab.com/gitlab-org/terraform-provider-gitlab/internal/provider/utils"
)
var (
_ resource.Resource = &gitlabIntegrationRedmineResource{}
_ resource.ResourceWithConfigure = &gitlabIntegrationRedmineResource{}
_ resource.ResourceWithImportState = &gitlabIntegrationRedmineResource{}
)
func init() {
registerResource(NewGitLabIntegrationRedmineResource)
}
func NewGitLabIntegrationRedmineResource() resource.Resource {
return &gitlabIntegrationRedmineResource{}
}
type gitlabIntegrationRedmineResource struct {
client *gitlab.Client
}
type gitlabIntegrationRedmineResourceModel struct {
ID types.String `tfsdk:"id"`
Project types.String `tfsdk:"project"`
NewIssueURL types.String `tfsdk:"new_issue_url"`
ProjectURL types.String `tfsdk:"project_url"`
IssuesURL types.String `tfsdk:"issues_url"`
UseInheritedSettings types.Bool `tfsdk:"use_inherited_settings"`
}
func (r *gitlabIntegrationRedmineResourceModel) redmineServiceToStateModel(projectId string, service *gitlab.RedmineService) {
r.ID = types.StringValue(projectId)
r.Project = types.StringValue(projectId)
r.NewIssueURL = types.StringValue(service.Properties.NewIssueURL)
r.ProjectURL = types.StringValue(service.Properties.ProjectURL)
r.IssuesURL = types.StringValue(service.Properties.IssuesURL)
r.UseInheritedSettings = types.BoolValue(bool(service.Properties.UseInheritedSettings))
}
func (r *gitlabIntegrationRedmineResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_integration_redmine"
}
func (r *gitlabIntegrationRedmineResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: `The ` + "`gitlab_integration_redmine`" + ` resource allows to manage the lifecycle of a project integration with Redmine.
~> Using Redmine requires that GitLab internal issue tracking is disabled for the project.
**Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/api/project_integrations/#redmine)`,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "The ID of the resource. Matches the `project` value.",
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"project": schema.StringAttribute{
MarkdownDescription: "ID of the project you want to activate integration on.",
Required: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
"new_issue_url": schema.StringAttribute{
MarkdownDescription: "The URL to use to create a new issue in the Redmine project linked to this GitLab project.",
Required: true,
Validators: []validator.String{utils.HttpUrlValidator},
},
"project_url": schema.StringAttribute{
MarkdownDescription: "The URL to the Redmine project to link to this GitLab project.",
Required: true,
Validators: []validator.String{utils.HttpUrlValidator},
},
"issues_url": schema.StringAttribute{
MarkdownDescription: "The URL to the Redmine project issue to link to this GitLab project.",
Required: true,
Validators: []validator.String{utils.HttpUrlValidator, stringvalidator.RegexMatches(
regexp.MustCompile(`:id\b`),
"must contain ':id'. GitLab replaces this ID with the issue number",
)},
},
"use_inherited_settings": schema.BoolAttribute{
MarkdownDescription: "Indicates whether or not to inherit default settings. Defaults to false.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()},
},
},
}
}
func (r *gitlabIntegrationRedmineResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
resourceData := req.ProviderData.(*GitLabResourceData)
r.client = resourceData.Client
}
func (r *gitlabIntegrationRedmineResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
func (r *gitlabIntegrationRedmineResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data gitlabIntegrationRedmineResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
tflog.Debug(ctx, "Update Redmine integration.", map[string]any{
"project": data.Project,
"new_issue_url": data.NewIssueURL,
"project_url": data.ProjectURL,
"issues_url": data.IssuesURL,
"use_inherited_settings": data.UseInheritedSettings,
})
err := r.updateRedmineService(ctx, &data)
if err != nil {
resp.Diagnostics.AddError("Failed to create Redmine integration", err.Error())
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *gitlabIntegrationRedmineResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data gitlabIntegrationRedmineResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
projectId := data.ID.ValueString()
redmineService, _, err := r.client.Services.GetRedmineService(projectId, gitlab.WithContext(ctx))
if err != nil {
if api.Is404(err) {
tflog.Debug(ctx, "Redmine integration doesn't exist, removing from state", map[string]any{
"project": data.Project,
"project_url": data.ProjectURL,
})
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("GitLab API error occurred", fmt.Sprintf("Error reading Redmine integration for project %s", err.Error()))
return
}
data.redmineServiceToStateModel(projectId, redmineService)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *gitlabIntegrationRedmineResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data gitlabIntegrationRedmineResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
tflog.Debug(ctx, "Update Redmine integration.", map[string]any{
"project": data.Project,
"new_issue_url": data.NewIssueURL,
"project_url": data.ProjectURL,
"issues_url": data.IssuesURL,
"use_inherited_settings": data.UseInheritedSettings,
})
err := r.updateRedmineService(ctx, &data)
if err != nil {
resp.Diagnostics.AddError("Failed to updateRedmineService Redmine integration", err.Error())
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *gitlabIntegrationRedmineResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data gitlabIntegrationRedmineResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
projectId := data.ID.ValueString()
if _, err := r.client.Services.DeleteRedmineService(projectId, gitlab.WithContext(ctx)); err != nil {
if api.Is404(err) {
tflog.Debug(ctx, "Redmine integration doesn't exist, removing from state", map[string]any{
"project": data.Project,
"project_url": data.ProjectURL,
})
return
}
resp.Diagnostics.AddError("GitLab API error occurred", fmt.Sprintf("Error deleting Redmine integration for project %s", err.Error()))
return
}
}
func (r *gitlabIntegrationRedmineResource) updateRedmineService(ctx context.Context, data *gitlabIntegrationRedmineResourceModel) error {
options := &gitlab.SetRedmineServiceOptions{
NewIssueURL: data.NewIssueURL.ValueStringPointer(),
ProjectURL: data.ProjectURL.ValueStringPointer(),
IssuesURL: data.IssuesURL.ValueStringPointer(),
UseInheritedSettings: data.UseInheritedSettings.ValueBoolPointer(),
}
projectId := data.Project.ValueString()
if _, _, err := r.client.Services.SetRedmineService(projectId, options, gitlab.WithContext(ctx)); err != nil {
return err
}
redmineService, _, err := r.client.Services.GetRedmineService(projectId)
if err != nil {
return err
}
data.redmineServiceToStateModel(projectId, redmineService)
return nil
}