Skip to content

Commit e95c268

Browse files
committed
get group counts and store on profile, skip work if counts are 0
1 parent 28b2867 commit e95c268

File tree

4 files changed

+122
-4
lines changed

4 files changed

+122
-4
lines changed

pkg/connector/client/client.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ import (
1717
"google.golang.org/grpc/status"
1818
)
1919

20+
const graphQLApiPath = "https://gitlab.com/api/graphql"
21+
22+
const groupCountQuery = `query GetGroupsWithMemberCount($groupIds: [ID!]!) {
23+
groups(ids: $groupIds) {
24+
nodes {
25+
id
26+
groupMembersCount
27+
descendantGroupsCount
28+
projectsCount
29+
}
30+
}
31+
}`
32+
2033
type GitlabClient struct {
2134
httpClient *uhttp.BaseHttpClient
2235
baseURL string
@@ -160,6 +173,51 @@ func (c *GitlabClient) ListGroups(ctx context.Context, nextLink string) ([]*Grou
160173
return groups, newNextLink, rateLimitDesc, nil
161174
}
162175

176+
func (c *GitlabClient) ListGroupsWithCounts(ctx context.Context, groupIds []string) ([]GroupWithCount, *v2.RateLimitDescription, error) {
177+
variables := map[string]interface{}{
178+
"groupIds": groupIds,
179+
}
180+
181+
payload := map[string]interface{}{
182+
"query": groupCountQuery,
183+
"variables": variables,
184+
}
185+
186+
options := []uhttp.RequestOption{
187+
uhttp.WithAcceptJSONHeader(),
188+
uhttp.WithJSONBody(payload),
189+
uhttp.WithBearerToken(c.accessToken),
190+
}
191+
192+
graphQLURL, err := url.Parse(graphQLApiPath)
193+
if err != nil {
194+
return nil, nil, fmt.Errorf("gitlab-connector: failed to parse graphql url: %w", err)
195+
}
196+
197+
var rateLimitData v2.RateLimitDescription
198+
var errorResponse GitlabError
199+
res := &GroupCountsListResponse{}
200+
doOptions := []uhttp.DoOption{
201+
uhttp.WithRatelimitData(&rateLimitData),
202+
uhttp.WithErrorResponse(&errorResponse),
203+
uhttp.WithJSONResponse(&res),
204+
}
205+
206+
req, err := c.httpClient.NewRequest(ctx, http.MethodPost, graphQLURL, options...)
207+
if err != nil {
208+
return nil, &rateLimitData, err
209+
}
210+
resp, err := c.httpClient.Do(
211+
req,
212+
doOptions...,
213+
)
214+
if err != nil {
215+
return nil, &rateLimitData, fmt.Errorf("gitlab-connector: failed to list group with counts: %w", err)
216+
}
217+
defer resp.Body.Close()
218+
return res.Data.Groups.Nodes, &rateLimitData, nil
219+
}
220+
163221
// GetGroup retrieves a specific group by ID.
164222
func (c *GitlabClient) GetGroup(ctx context.Context, groupID string) (*Group, *v2.RateLimitDescription, error) {
165223
var group Group

pkg/connector/client/models.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,21 @@ type AddProjectMemberRequest struct {
103103
AccessLevel AccessLevelValue `json:"access_level"`
104104
}
105105

106+
type GroupWithCount struct {
107+
Id string `json:"id"`
108+
GroupMembersCount int `json:"groupMembersCount"`
109+
DescendantGroupsCount int `json:"descendantGroupsCount"`
110+
ProjectsCount int `json:"projectsCount"`
111+
}
112+
113+
type GroupCountsListResponse struct {
114+
Data struct {
115+
Groups struct {
116+
Nodes []GroupWithCount `json:"nodes"`
117+
} `json:"groups"`
118+
} `json:"data"`
119+
}
120+
106121
type ISOTime time.Time
107122

108123
// ISO 8601 date format.

pkg/connector/groups.go

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import (
2323
"google.golang.org/protobuf/proto"
2424
)
2525

26+
const projectsCountProfileKey = "projects_count"
27+
const groupMembersCountProfileKey = "group_members_count"
28+
const descendantGroupsCountProfileKey = "descendant_groups_count"
29+
2630
type groupBuilder struct {
2731
client *client.GitlabClient
2832
}
@@ -61,12 +65,31 @@ func (o *groupBuilder) List(ctx context.Context, parentResourceID *v2.ResourceId
6165
if err != nil {
6266
return nil, "", outputAnnotations, err
6367
}
68+
groupIds := make([]string, 0)
69+
for _, g := range groups {
70+
gId := gitlabFullGroupID(g.ID)
71+
groupIds = append(groupIds, gId)
72+
}
73+
74+
groupsWithCounts, rateLimitDesc, err := o.client.ListGroupsWithCounts(ctx, groupIds)
75+
if rateLimitDesc != nil {
76+
outputAnnotations.WithRateLimiting(rateLimitDesc)
77+
}
78+
if err != nil {
79+
return nil, "", outputAnnotations, err
80+
}
81+
82+
groupCountMap := make(map[string]*client.GroupWithCount)
83+
for _, gc := range groupsWithCounts {
84+
groupCountMap[gc.Id] = &gc
85+
}
6486

6587
outResources := make([]*v2.Resource, 0, len(groups))
6688
for _, group := range groups {
6789
parentResourceID = getParentGroup(group.ParentID)
68-
69-
resource, err := groupResource(group, parentResourceID, o.client.IsOnPremise)
90+
fullGroupID := gitlabFullGroupID(group.ID)
91+
groupCounts := groupCountMap[fullGroupID]
92+
resource, err := groupResource(ctx, group, parentResourceID, o.client.IsOnPremise, groupCounts)
7093
if err != nil {
7194
return nil, "", outputAnnotations, err
7295
}
@@ -116,6 +139,16 @@ func (o *groupBuilder) Entitlements(_ context.Context, resource *v2.Resource, _
116139
}
117140

118141
func (o *groupBuilder) Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
142+
groupTrait, err := resourceSdk.GetGroupTrait(resource)
143+
if err != nil {
144+
return nil, "", nil, fmt.Errorf("okta-connectorv2: failed to get group trait: %w", err)
145+
}
146+
groupMembersCount, ok := resourceSdk.GetProfileInt64Value(groupTrait.Profile, groupMembersCountProfileKey)
147+
148+
if ok && groupMembersCount == 0 {
149+
return nil, "", nil, nil
150+
}
151+
119152
var outGrants []*v2.Grant
120153
var outputAnnotations = annotations.New()
121154
var users []*client.GroupMember
@@ -270,7 +303,7 @@ func (o *groupBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations
270303
return outputAnnotations, nil
271304
}
272305

273-
func groupResource(group *client.Group, parentResourceID *v2.ResourceId, isOnPremise bool) (*v2.Resource, error) {
306+
func groupResource(ctx context.Context, group *client.Group, parentResourceID *v2.ResourceId, isOnPremise bool, groupCounts *client.GroupWithCount) (*v2.Resource, error) {
274307
profile := map[string]interface{}{
275308
"id": group.ID,
276309
"name": group.Name,
@@ -288,8 +321,16 @@ func groupResource(group *client.Group, parentResourceID *v2.ResourceId, isOnPre
288321
profile["parent_group_id"] = group.ParentID
289322
}
290323

324+
hasProjects := true
325+
if groupCounts != nil {
326+
hasProjects = groupCounts.ProjectsCount > 0
327+
profile["projects_count"] = groupCounts.ProjectsCount
328+
profile["group_members_count"] = groupCounts.GroupMembersCount
329+
profile["descendant_groups_count"] = groupCounts.DescendantGroupsCount
330+
}
331+
291332
annos := make([]proto.Message, 0)
292-
if parentResourceID == nil {
333+
if parentResourceID == nil && hasProjects {
293334
annos = append(annos, &v2.ChildResourceType{ResourceTypeId: projectResourceType.Id})
294335
}
295336

pkg/connector/helpers.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ func parseAccessLevelFromEntitlementID(entitlementID string) (int, error) {
3232
}
3333
return int(levelValue), nil
3434
}
35+
36+
func gitlabFullGroupID(groupId int) string {
37+
return fmt.Sprintf("gid://gitlab/Group/%d", groupId)
38+
}

0 commit comments

Comments
 (0)