teamcity/user.go (287 lines of code) (raw):

package teamcity import ( "context" "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" "strconv" "terraform-provider-teamcity/client" "terraform-provider-teamcity/models" ) var ( _ resource.Resource = &userResource{} _ resource.ResourceWithConfigure = &userResource{} _ resource.ResourceWithValidateConfig = &userResource{} _ resource.ResourceWithImportState = &userResource{} ) type userResource struct { client *client.Client } func NewUserResource() resource.Resource { return &userResource{} } func (r *userResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_user" } type userResourceModel struct { Id types.String `tfsdk:"id"` Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` Github types.String `tfsdk:"github_username"` Roles []roleAssignment `tfsdk:"roles"` } type roleAssignment struct { Id types.String `tfsdk:"id"` Global types.Bool `tfsdk:"global"` Project types.String `tfsdk:"project"` } func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: "User account is a combination of username and password that allows TeamCity users to sign in to the server and use its features. More info [here](https://www.jetbrains.com/help/teamcity/creating-and-managing-users.html)", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "username": schema.StringAttribute{ Required: true, }, "password": schema.StringAttribute{ Optional: true, Sensitive: true, }, "github_username": schema.StringAttribute{ Optional: true, }, "roles": schema.SetNestedAttribute{ Optional: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Required: true, }, "global": schema.BoolAttribute{ Optional: true, }, "project": schema.StringAttribute{ Optional: true, }, }, }, }, }, } } func (r *userResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var config userResourceModel diags := req.Config.Get(ctx, &config) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } for _, role := range config.Roles { if role.Global.IsNull() && role.Project.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("roles"), //TODO path to specific set item "Either 'global' or 'project' must be specified", "", ) } if !role.Global.IsNull() && !role.Project.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("roles"), //TODO path to specific set item "'global' and 'project' cannot be specified together", "", ) } if !role.Global.ValueBool() { resp.Diagnostics.AddAttributeError( path.Root("roles"), //TODO path to specific set item "'global' must be set to 'true'", "", ) } } } func (r *userResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil { return } r.client = req.ProviderData.(*client.Client) } func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan userResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } user := r.update(plan) actual, err := r.client.NewUser(user) if err != nil { resp.Diagnostics.AddError( "Error setting user", "Cannot set user, unexpected error: "+err.Error(), ) return } newState := r.readState(actual) newState.Password = plan.Password diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var oldState userResourceModel diags := req.State.Get(ctx, &oldState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } var actual *client.User var err error if oldState.Id.IsNull() != true { actual, err = r.client.GetUser(oldState.Id.ValueString()) } else { actual, err = r.client.GetUserByName(oldState.Username.ValueString()) } if err != nil { resp.Diagnostics.AddError( "Error Reading user", "Could not read user settings: "+err.Error(), ) return } if actual == nil { resp.State.RemoveResource(ctx) return } newState := r.readState(actual) newState.Password = oldState.Password diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan userResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } id, err := strconv.ParseInt(plan.Id.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( "Error Parsing user id", err.Error(), ) return } user := r.update(plan) user.Id = &id result, err := r.client.SetUser(user) if err != nil { resp.Diagnostics.AddError( "Error updating user", err.Error(), ) return } newState := r.readState(result) newState.Password = plan.Password diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *userResource) update(plan userResourceModel) client.User { user := client.User{ Username: plan.Username.ValueString(), } if plan.Password.IsNull() != true { password := plan.Password.ValueString() user.Password = &password } if plan.Github.IsNull() != true { user.Properties = &models.Properties{ Property: []models.Property{ { Name: "plugin:auth:GitHubApp-oauth:userName", Value: plan.Github.ValueString(), }, }, } } user.Roles = &client.RoleAssignments{ RoleAssignment: []client.RoleAssignment{}, } if plan.Roles != nil { for _, role := range plan.Roles { assignment := client.RoleAssignment{ Id: role.Id.ValueString(), } if role.Global.ValueBool() { assignment.Scope = "g" } else { assignment.Scope = "p:" + role.Project.ValueString() } user.Roles.RoleAssignment = append(user.Roles.RoleAssignment, assignment) } } return user } func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state userResourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } err := r.client.DeleteUser(state.Id.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error Deleting user", "Could not delete user, unexpected error: "+err.Error(), ) return } } func (r *userResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("username"), req, resp) } func (r *userResource) readState(actual *client.User) userResourceModel { var newState userResourceModel newState.Id = types.StringValue(strconv.FormatInt(*actual.Id, 10)) newState.Username = types.StringValue(actual.Username) for _, p := range actual.Properties.Property { if p.Name == "plugin:auth:GitHubApp-oauth:userName" { newState.Github = types.StringValue(p.Value) break } } if actual.Roles != nil && len(actual.Roles.RoleAssignment) > 0 { newState.Roles = []roleAssignment{} for _, role := range actual.Roles.RoleAssignment { assignment := roleAssignment{ Id: types.StringValue(role.Id), } if role.Scope == "g" { assignment.Global = types.BoolValue(role.Scope == "g") } else { assignment.Project = types.StringValue(role.Scope[2:]) } newState.Roles = append(newState.Roles, assignment) } } return newState }