internal/provider/datasource_gitlab_group_membership.go (211 lines of code) (raw):

package provider import ( "context" "strconv" "strings" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/path" "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" ) // Ensure the implementation satisfies the expected interfaces. var ( _ datasource.DataSource = &gitlabGroupMembershipDataSource{} _ datasource.DataSourceWithConfigure = &gitlabGroupMembershipDataSource{} ) func init() { registerDataSource(NewGitLabGroupMembershipDataSource) } // NewGitLabGroupMembershipDataSource is a helper function to simplify the provider implementation. func NewGitLabGroupMembershipDataSource() datasource.DataSource { return &gitlabGroupMembershipDataSource{} } // gitlabGroupMembershipDataSource is the data source implementation. type gitlabGroupMembershipDataSource struct { client *gitlab.Client } // gitlabGroupMembershipDataSourceModel describes the data source data model. type gitlabGroupMembershipDataSourceModel struct { ID types.String `tfsdk:"id"` GroupID types.Int64 `tfsdk:"group_id"` FullPath types.String `tfsdk:"full_path"` Inherited types.Bool `tfsdk:"inherited"` AccessLevel types.String `tfsdk:"access_level"` Members []gitlabGroupMembershipMemberModel `tfsdk:"members"` } type gitlabGroupMembershipMemberModel struct { ID types.Int64 `tfsdk:"id"` Username types.String `tfsdk:"username"` Name types.String `tfsdk:"name"` State types.String `tfsdk:"state"` AvatarURL types.String `tfsdk:"avatar_url"` WebURL types.String `tfsdk:"web_url"` AccessLevel types.String `tfsdk:"access_level"` ExpiresAt types.String `tfsdk:"expires_at"` } // Metadata returns the data source type name. func (d *gitlabGroupMembershipDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_group_membership" } // Schema defines the schema for the data source. func (d *gitlabGroupMembershipDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: `The ` + "`gitlab_group_membership`" + ` data source allows to list and filter all members of a group specified by either its id or full path. **Upstream API**: [GitLab REST API docs](https://docs.gitlab.com/api/members/#list-all-members-of-a-group-or-project)`, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ MarkdownDescription: "The ID of the group membership. In the format of `<group-id:access-level>`.", Computed: true, }, "group_id": schema.Int64Attribute{ MarkdownDescription: "The ID of the group.", Computed: true, Optional: true, Validators: []validator.Int64{int64validator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("group_id"), path.MatchRelative().AtParent().AtName("full_path"))}, }, "full_path": schema.StringAttribute{ MarkdownDescription: "The full path of the group.", Computed: true, Optional: true, Validators: []validator.String{stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("group_id"), path.MatchRelative().AtParent().AtName("full_path"))}, }, "inherited": schema.BoolAttribute{ MarkdownDescription: "Return all project members including members through ancestor groups.", Optional: true, }, "access_level": schema.StringAttribute{ MarkdownDescription: "Only return members with the desired access level. Acceptable values are: `guest`, `reporter`, `developer`, `maintainer`, `owner`.", Optional: true, Computed: true, Validators: []validator.String{stringvalidator.OneOf(api.ValidGroupAccessLevelNames...)}, }, "members": schema.ListNestedAttribute{ MarkdownDescription: "The list of group members.", Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "id": schema.Int64Attribute{ MarkdownDescription: "The unique id assigned to the user by the gitlab server.", Computed: true, }, "username": schema.StringAttribute{ MarkdownDescription: "The username of the user.", Computed: true, }, "name": schema.StringAttribute{ MarkdownDescription: "The name of the user.", Computed: true, }, "state": schema.StringAttribute{ MarkdownDescription: "Whether the user is active or blocked.", Computed: true, }, "avatar_url": schema.StringAttribute{ MarkdownDescription: "The avatar URL of the user.", Computed: true, }, "web_url": schema.StringAttribute{ MarkdownDescription: "User's website URL.", Computed: true, }, "access_level": schema.StringAttribute{ MarkdownDescription: "The level of access to the group.", Computed: true, }, "expires_at": schema.StringAttribute{ MarkdownDescription: "Expiration date for the group membership.", Computed: true, }, }, }, }, }, } } // Configure adds the provider configured client to the data source. func (d *gitlabGroupMembershipDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { if req.ProviderData == nil { return } datasource := req.ProviderData.(*GitLabDatasourceData) d.client = datasource.Client } func (d *gitlabGroupMembershipDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { var data gitlabGroupMembershipDataSourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } tflog.Info(ctx, "[INFO] Reading Gitlab group") var groupLookup string if !data.GroupID.IsNull() { groupLookup = strconv.Itoa(int(data.GroupID.ValueInt64())) } else { groupLookup = data.FullPath.ValueString() } group, _, err := d.client.Groups.GetGroup(groupLookup, nil, gitlab.WithContext(ctx)) if err != nil { resp.Diagnostics.AddError("API call to get group failed", err.Error()) return } tflog.Info(ctx, "[INFO] Reading Gitlab group memberships") // Get group memberships listOptions := &gitlab.ListGroupMembersOptions{ ListOptions: gitlab.ListOptions{ PerPage: 20, Page: 1, }, } var allGms []*gitlab.GroupMember listMembers := d.client.Groups.ListGroupMembers if !data.Inherited.IsNull() && data.Inherited.ValueBool() { listMembers = d.client.Groups.ListAllGroupMembers } for { gms, response, err := listMembers(group.ID, listOptions, gitlab.WithContext(ctx)) if err != nil { resp.Diagnostics.AddError("API call to ListGroupMembers failed", err.Error()) return } allGms = append(allGms, gms...) if response.NextPage == 0 { break } listOptions.Page = response.NextPage } data.GroupID = types.Int64Value(int64(group.ID)) data.FullPath = types.StringValue(group.FullPath) data.Members = flattenGitlabGroupMembers(data.AccessLevel, allGms) var optionsHash strings.Builder optionsHash.WriteString(strconv.Itoa(group.ID)) if !data.AccessLevel.IsNull() { optionsHash.WriteString(data.AccessLevel.ValueString()) } data.ID = types.StringValue(optionsHash.String()) diags := resp.State.Set(ctx, &data) resp.Diagnostics.Append(diags...) } func flattenGitlabGroupMembers(accessLevel types.String, members []*gitlab.GroupMember) []gitlabGroupMembershipMemberModel { membersList := []gitlabGroupMembershipMemberModel{} filterAccessLevel := gitlab.NoPermissions if !accessLevel.IsNull() { filterAccessLevel = api.AccessLevelNameToValue[accessLevel.ValueString()] } for _, member := range members { if filterAccessLevel != gitlab.NoPermissions && filterAccessLevel != member.AccessLevel { continue } memberModel := gitlabGroupMembershipMemberModel{ ID: types.Int64Value(int64(member.ID)), Username: types.StringValue(member.Username), Name: types.StringValue(member.Name), State: types.StringValue(member.State), AvatarURL: types.StringValue(member.AvatarURL), WebURL: types.StringValue(member.WebURL), AccessLevel: types.StringValue(api.AccessLevelValueToName[member.AccessLevel]), } if member.ExpiresAt != nil { memberModel.ExpiresAt = types.StringValue(member.ExpiresAt.String()) } membersList = append(membersList, memberModel) } return membersList }