teamcity/connection.go (308 lines of code) (raw):

package teamcity import ( "context" "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/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" "terraform-provider-teamcity/client" "terraform-provider-teamcity/models" ) var ( _ resource.Resource = &connectionResource{} _ resource.ResourceWithConfigure = &connectionResource{} ) func NewConnectionResource() resource.Resource { return &connectionResource{} } type connectionResource struct { client *client.Client } type connectionResourceModel struct { ProjectId types.String `tfsdk:"project_id"` FeatureId types.String `tfsdk:"feature_id"` GithubApp GithubApp `tfsdk:"github_app"` } type GithubApp struct { DisplayName types.String `tfsdk:"display_name"` OwnerUrl types.String `tfsdk:"owner_url"` AppId types.String `tfsdk:"app_id"` ClientId types.String `tfsdk:"client_id"` ClientSecret types.String `tfsdk:"client_secret"` PrivateKey types.String `tfsdk:"private_key"` WebhookSecret types.String `tfsdk:"webhook_secret"` } func (r *connectionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_connection" } func (r *connectionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: "TeamCity allows storing presets of connections to external services. Currently only GitHub App type is supported for adding SSO to the server. More info [here](https://www.jetbrains.com/help/teamcity/configuring-connections.html#GitHub)", Attributes: map[string]schema.Attribute{ "project_id": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, "feature_id": schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "github_app": schema.SingleNestedAttribute{ Required: true, Attributes: map[string]schema.Attribute{ "display_name": schema.StringAttribute{ Required: true, }, "owner_url": schema.StringAttribute{ Required: true, }, "app_id": schema.StringAttribute{ Required: true, }, "client_id": schema.StringAttribute{ Required: true, }, "client_secret": schema.StringAttribute{ Required: true, Sensitive: true, }, "private_key": schema.StringAttribute{ Required: true, Sensitive: true, }, "webhook_secret": schema.StringAttribute{ Required: true, Sensitive: true, }, }, }, }, } } func (r *connectionResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil { return } r.client = req.ProviderData.(*client.Client) } func (r *connectionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan connectionResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } feature := models.ProjectFeatureJson{ Type: "OAuthProvider", Properties: models.Properties{ Property: []models.Property{ { Name: "providerType", Value: "GitHubApp", }, { Name: "connectionSubtype", Value: "gitHubApp", }, { Name: "displayName", Value: plan.GithubApp.DisplayName.ValueString(), }, { Name: "gitHubApp.ownerUrl", Value: plan.GithubApp.OwnerUrl.ValueString(), }, { Name: "gitHubApp.appId", Value: plan.GithubApp.AppId.ValueString(), }, { Name: "gitHubApp.clientId", Value: plan.GithubApp.ClientId.ValueString(), }, { Name: "secure:gitHubApp.clientSecret", Value: plan.GithubApp.ClientSecret.ValueString(), }, { Name: "secure:gitHubApp.privateKey", Value: plan.GithubApp.PrivateKey.ValueString(), }, { Name: "secure:gitHubApp.webhookSecret", Value: plan.GithubApp.WebhookSecret.ValueString(), }, }, }, } result, err := r.client.NewProjectFeature(plan.ProjectId.ValueString(), feature) if err != nil { resp.Diagnostics.AddError( "Error adding project feature", err.Error(), ) return } newState := r.readState(result, plan) diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *connectionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var oldState connectionResourceModel diags := req.State.Get(ctx, &oldState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } result, err := r.client.GetProjectFeature(oldState.ProjectId.ValueString(), oldState.FeatureId.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error reading connection", err.Error(), ) return } if result == nil { resp.State.RemoveResource(ctx) return } newState := r.readState(*result, oldState) diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *connectionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan connectionResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } var oldState connectionResourceModel diags = req.State.Get(ctx, &oldState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } var newState connectionResourceModel newState.ProjectId = plan.ProjectId newState.FeatureId = plan.FeatureId projectId := plan.ProjectId.ValueString() featureId := plan.FeatureId.ValueString() if result, ok := r.setFieldString(projectId, featureId, "displayName", oldState.GithubApp.DisplayName, plan.GithubApp.DisplayName, &resp.Diagnostics); ok { newState.GithubApp.DisplayName = result } else { return } if result, ok := r.setFieldString(projectId, featureId, "gitHubApp.ownerUrl", oldState.GithubApp.OwnerUrl, plan.GithubApp.OwnerUrl, &resp.Diagnostics); ok { newState.GithubApp.OwnerUrl = result } else { return } if result, ok := r.setFieldString(projectId, featureId, "gitHubApp.appId", oldState.GithubApp.AppId, plan.GithubApp.AppId, &resp.Diagnostics); ok { newState.GithubApp.AppId = result } else { return } if result, ok := r.setFieldString(projectId, featureId, "gitHubApp.clientId", oldState.GithubApp.ClientId, plan.GithubApp.ClientId, &resp.Diagnostics); ok { newState.GithubApp.ClientId = result } else { return } if result, ok := r.setFieldString(projectId, featureId, "secure:gitHubApp.clientSecret", oldState.GithubApp.ClientSecret, plan.GithubApp.ClientSecret, &resp.Diagnostics); ok { newState.GithubApp.ClientSecret = result } else { return } if result, ok := r.setFieldString(projectId, featureId, "secure:gitHubApp.privateKey", oldState.GithubApp.PrivateKey, plan.GithubApp.PrivateKey, &resp.Diagnostics); ok { newState.GithubApp.PrivateKey = result } else { return } if result, ok := r.setFieldString(projectId, featureId, "secure:gitHubApp.webhookSecret", oldState.GithubApp.WebhookSecret, plan.GithubApp.WebhookSecret, &resp.Diagnostics); ok { newState.GithubApp.WebhookSecret = result } else { return } diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *connectionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state connectionResourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } err := r.client.DeleteProjectFeature(state.ProjectId.ValueString(), state.FeatureId.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error deleting connection", err.Error(), ) return } } func (r *connectionResource) readState(result models.ProjectFeatureJson, plan connectionResourceModel) connectionResourceModel { props := make(map[string]string) for _, p := range result.Properties.Property { props[p.Name] = p.Value } var newState connectionResourceModel newState.ProjectId = plan.ProjectId newState.FeatureId = types.StringValue(*result.Id) newState.GithubApp.DisplayName = types.StringValue(props["displayName"]) newState.GithubApp.OwnerUrl = types.StringValue(props["gitHubApp.ownerUrl"]) newState.GithubApp.AppId = types.StringValue(props["gitHubApp.appId"]) newState.GithubApp.ClientId = types.StringValue(props["gitHubApp.clientId"]) if _, ok := props["secure:gitHubApp.clientSecret"]; ok { newState.GithubApp.ClientSecret = plan.GithubApp.ClientSecret } if _, ok := props["secure:gitHubApp.privateKey"]; ok { newState.GithubApp.PrivateKey = plan.GithubApp.PrivateKey } if _, ok := props["secure:gitHubApp.webhookSecret"]; ok { newState.GithubApp.WebhookSecret = plan.GithubApp.WebhookSecret } return newState } func (r *connectionResource) setFieldString(projectId, featureId, name string, state, plan types.String, diag *diag.Diagnostics) (types.String, bool) { if plan.Equal(state) { return state, true } var strVal *string if plan.IsNull() { strVal = nil } else { val := plan.ValueString() strVal = &val } prop := fmt.Sprintf("projectFeatures/%s/properties/%s", featureId, name) result, err := r.client.SetField("projects", projectId, prop, strVal) if err != nil { diag.AddError( "Error setting property", err.Error(), ) return types.String{}, false } if result == "" { return types.StringNull(), true } return types.StringValue(result), true }