teamcity/project.go (222 lines of code) (raw):
package teamcity
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/diag"
"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"
"terraform-provider-teamcity/client"
"terraform-provider-teamcity/models"
)
const (
defaultParentProjectId = "_Root"
)
var (
_ resource.Resource = &projectResource{}
_ resource.ResourceWithConfigure = &projectResource{}
_ resource.ResourceWithImportState = &projectResource{}
)
func NewProjectResource() resource.Resource {
return &projectResource{}
}
type projectResource struct {
client *client.Client
}
func (r *projectResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_project"
}
func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "A project in TeamCity is a collection of build configurations. More info [here](https://www.jetbrains.com/help/teamcity/project.html)",
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
},
"id": schema.StringAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"parent_project_id": schema.StringAttribute{
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Default: stringdefault.StaticString(defaultParentProjectId),
},
},
}
}
func (r *projectResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
r.client = req.ProviderData.(*client.Client)
}
func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan models.ProjectResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
project := models.ProjectJson{
Name: plan.Name.ValueString(),
}
if !plan.Id.IsUnknown() {
val := plan.Id.ValueString()
project.Id = &val
}
if !plan.ParentProjectId.IsUnknown() {
parentProjectId := plan.ParentProjectId.ValueString()
project.ParentProject = &models.ProjectJson{Id: &parentProjectId}
}
result, err := r.client.NewProject(project)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error setting project: %s", project.Name),
"Cannot set project, unexpected error: "+err.Error(),
)
return
}
newState := r.convertToResource(result)
diags = resp.State.Set(ctx, newState)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state models.ProjectResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
actual, err := r.client.GetProject(state.Id.ValueString())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading project with ID: %s", state.Id.ValueString()),
"Could not read project settings: "+err.Error(),
)
return
}
if actual == nil {
resp.State.RemoveResource(ctx)
return
}
newState := r.convertToResource(*actual)
diags = resp.State.Set(ctx, newState)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan models.ProjectResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var oldState models.ProjectResourceModel
diags = req.State.Get(ctx, &oldState)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var newState models.ProjectResourceModel
resourceId := oldState.Id.ValueString()
if result, ok := r.setFieldString(resourceId, "name", oldState.Name, plan.Name, &resp.Diagnostics); ok {
newState.Name = result
} else {
return
}
if result, ok := r.setFieldString(resourceId, "id", oldState.Id, plan.Id, &resp.Diagnostics); ok {
newState.Id = result
} else {
return
}
//send update request with parent project as body - TC rest API
if result, ok := r.updateParentProjectParam(resourceId, oldState.ParentProjectId, plan.ParentProjectId, &resp.Diagnostics); ok {
newState.ParentProjectId = result
} else {
return
}
diags = resp.State.Set(ctx, newState)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *projectResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state models.ProjectResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
err := r.client.DeleteProject(state.Id.ValueString())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting project with ID: %s", state.Id.ValueString()),
"Could not delete project, unexpected error: "+err.Error(),
)
return
}
}
func (r *projectResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
func (r *projectResource) convertToResource(result models.ProjectJson) models.ProjectResourceModel {
var newState models.ProjectResourceModel
newState.Name = types.StringValue(result.Name)
newState.Id = types.StringValue(*result.Id)
// compatibility: <0.0.72
if result.ParentProject == nil || result.ParentProject.Id == nil {
newState.ParentProjectId = types.StringValue(defaultParentProjectId)
} else {
newState.ParentProjectId = types.StringValue(*result.ParentProject.Id)
}
return newState
}
func (r *projectResource) setFieldString(id, name string, state, plan types.String, diag *diag.Diagnostics) (types.String, bool) {
if plan.Equal(state) {
return state, true
}
val := plan.ValueString()
result, err := r.client.SetField("projects", id, name, &val)
if err != nil {
diag.AddError(
fmt.Sprintf("Error setting project field %s for the Project with ID: %s", name, id),
err.Error(),
)
return types.String{}, false
}
return types.StringValue(result), true
}
func (r *projectResource) updateParentProjectParam(id string, state, plan types.String, diag *diag.Diagnostics) (types.String, bool) {
if plan.Equal(state) {
return state, true
}
val := plan.ValueString()
requestBody := map[string]interface{}{
"id": val,
}
_, err := r.client.SetFieldJson("projects", id, "parentProject", requestBody)
if err != nil {
diag.AddError(
fmt.Sprintf("Error setting Project parent to %s, for the Project with ID: %s", val, id),
err.Error(),
)
return types.String{}, false
} else {
return plan, true
}
}