teamcity/group.go (309 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" "terraform-provider-teamcity/client" ) var ( _ resource.Resource = &groupResource{} _ resource.ResourceWithConfigure = &groupResource{} _ resource.ResourceWithImportState = &groupResource{} ) type groupResource struct { client *client.Client } func NewGroupResource() resource.Resource { return &groupResource{} } func (r *groupResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_group" } type groupResourceModel struct { Id types.String `tfsdk:"id"` Key types.String `tfsdk:"key"` Name types.String `tfsdk:"name"` Roles []roleAssignment `tfsdk:"roles"` ParentGroups types.Set `tfsdk:"parent_groups"` } func (r *groupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: "User groups help manage user accounts more efficiently via roles and notification rules. More details [here](https://www.jetbrains.com/help/teamcity/creating-and-managing-user-groups.html).", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, Description: "Deprecated. Use key instead, id is generated on creation and is read only.", }, "key": schema.StringAttribute{ Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, Description: "Custom key (id) for the group. If not provided, TeamCity will generate one based on the name.", }, "name": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, "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, }, }, }, }, "parent_groups": schema.SetAttribute{ ElementType: types.StringType, Optional: true, }, }, } } func (r *groupResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil { return } r.client = req.ProviderData.(*client.Client) } func (r *groupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan groupResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } group := client.Group{ Name: plan.Name.ValueString(), } if !plan.Key.IsNull() { group.Key = plan.Key.ValueString() } group.Roles = &client.RoleAssignments{ RoleAssignment: []client.RoleAssignment{}, } if plan.Roles != nil { for _, role := range plan.Roles { assignment := client.RoleAssignment{ Id: role.Id.ValueString(), } assignment.Scope = scope(role) group.Roles.RoleAssignment = append(group.Roles.RoleAssignment, assignment) } } if !plan.ParentGroups.IsNull() { var parents []types.String diags = plan.ParentGroups.ElementsAs(ctx, &parents, false) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } group.Parents = &client.ParentGroups{} for _, i := range parents { val := i.ValueString() group.Parents.Group = append( group.Parents.Group, client.Group{Key: val}, ) } } actual, err := r.client.NewGroup(group) if err != nil { resp.Diagnostics.AddError( "Error adding group", err.Error(), ) return } newState := r.readState(actual) diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *groupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var oldState groupResourceModel diags := req.State.Get(ctx, &oldState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } actual, err := r.client.GetGroup(oldState.Id.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error Reading group", err.Error(), ) return } if actual == nil { resp.State.RemoveResource(ctx) return } newState := r.readState(actual) // Only set the key if it was explicitly set before if oldState.Key.IsNull() { newState.Key = types.StringNull() } diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *groupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan groupResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } var oldState groupResourceModel diags = req.State.Get(ctx, &oldState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } // items present in old state but missing in a plan -> remove for _, i := range oldState.Roles { if !contains3(plan.Roles, i) { err := r.client.RemoveGroupRole(plan.Id.ValueString(), i.Id.ValueString(), scope(i)) if err != nil { resp.Diagnostics.AddError( "Error removing group role", err.Error(), ) return } } } // items missing in old state but present in a plan -> add for _, i := range plan.Roles { if !contains3(oldState.Roles, i) { err := r.client.AddGroupRole(plan.Id.ValueString(), i.Id.ValueString(), scope(i)) if err != nil { resp.Diagnostics.AddError( "Error adding group role", err.Error(), ) return } } } if !plan.ParentGroups.Equal(oldState.ParentGroups) { var parents []string diags = plan.ParentGroups.ElementsAs(ctx, &parents, false) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } err := r.client.SetGroupParents(plan.Id.ValueString(), parents) if err != nil { resp.Diagnostics.AddError( "Error Reading group", err.Error(), ) return } } actual, err := r.client.GetGroup(plan.Id.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error Reading group", err.Error(), ) return } if actual == nil { resp.State.RemoveResource(ctx) return } newState := r.readState(actual) // Only set the key if it was explicitly set before if oldState.Key.IsNull() { newState.Key = types.StringNull() } diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *groupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state groupResourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } err := r.client.DeleteGroup(state.Id.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error Deleting group", err.Error(), ) return } } func (r *groupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } func (r *groupResource) readState(actual *client.Group) groupResourceModel { var newState groupResourceModel newState.Id = types.StringValue(actual.Key) newState.Key = types.StringValue(actual.Key) newState.Name = types.StringValue(actual.Name) 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) } } newState.ParentGroups = types.SetNull(types.StringType) for _, parent := range actual.Parents.Group { newState.ParentGroups, _ = types.SetValue( types.StringType, append(newState.ParentGroups.Elements(), types.StringValue(parent.Key)), ) } return newState } func scope(i roleAssignment) string { var scope string if i.Global.ValueBool() { scope = "g" } else { scope = "p:" + i.Project.ValueString() } return scope } func contains3(items []roleAssignment, item roleAssignment) bool { for _, i := range items { if i == item { return true } } return false }