IAM: Refactor team bindings to store one pair of team and member (#111871)

* store one pair of team member in team bindings object

* generate queries for sql unit tests

* remove TeamBinding struct
This commit is contained in:
Mihai Doarna
2025-10-03 16:48:51 +03:00
committed by GitHub
parent e9499bb60c
commit 6d7c50f18d
16 changed files with 120 additions and 186 deletions
+4 -3
View File
@@ -4,12 +4,13 @@ TeamBindingSpec: {
#Subject: {
// uid of the identity
name: string
// permission of the identity in the team
permission: TeamPermission
}
subjects: [...#Subject]
subject: #Subject
teamRef: TeamRef
// permission of the identity in the team
permission: TeamPermission
}
TeamRef:{
@@ -6,8 +6,6 @@ package v0alpha1
type TeamBindingspecSubject struct {
// uid of the identity
Name string `json:"name"`
// permission of the identity in the team
Permission TeamBindingTeamPermission `json:"permission"`
}
// NewTeamBindingspecSubject creates a new TeamBindingspecSubject object.
@@ -15,14 +13,6 @@ func NewTeamBindingspecSubject() *TeamBindingspecSubject {
return &TeamBindingspecSubject{}
}
// +k8s:openapi-gen=true
type TeamBindingTeamPermission string
const (
TeamBindingTeamPermissionAdmin TeamBindingTeamPermission = "admin"
TeamBindingTeamPermissionMember TeamBindingTeamPermission = "member"
)
// +k8s:openapi-gen=true
type TeamBindingTeamRef struct {
// Name is the unique identifier for a team.
@@ -34,16 +24,26 @@ func NewTeamBindingTeamRef() *TeamBindingTeamRef {
return &TeamBindingTeamRef{}
}
// +k8s:openapi-gen=true
type TeamBindingTeamPermission string
const (
TeamBindingTeamPermissionAdmin TeamBindingTeamPermission = "admin"
TeamBindingTeamPermissionMember TeamBindingTeamPermission = "member"
)
// +k8s:openapi-gen=true
type TeamBindingSpec struct {
Subjects []TeamBindingspecSubject `json:"subjects"`
TeamRef TeamBindingTeamRef `json:"teamRef"`
Subject TeamBindingspecSubject `json:"subject"`
TeamRef TeamBindingTeamRef `json:"teamRef"`
// permission of the identity in the team
Permission TeamBindingTeamPermission `json:"permission"`
}
// NewTeamBindingSpec creates a new TeamBindingSpec object.
func NewTeamBindingSpec() *TeamBindingSpec {
return &TeamBindingSpec{
Subjects: []TeamBindingspecSubject{},
TeamRef: *NewTeamBindingTeamRef(),
Subject: *NewTeamBindingspecSubject(),
TeamRef: *NewTeamBindingTeamRef(),
}
}
@@ -2103,17 +2103,10 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback)
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"subjects": {
"subject": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject"),
},
},
},
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject"),
},
},
"teamRef": {
@@ -2122,8 +2115,16 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback)
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef"),
},
},
"permission": {
SchemaProps: spec.SchemaProps{
Description: "permission of the identity in the team",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"subjects", "teamRef"},
Required: []string{"subject", "teamRef", "permission"},
},
},
Dependencies: []string{
@@ -2210,16 +2211,8 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingspecSubject(ref common.ReferenceCal
Format: "",
},
},
"permission": {
SchemaProps: spec.SchemaProps{
Description: "permission of the identity in the team",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"name", "permission"},
Required: []string{"name"},
},
},
}
+8 -26
View File
@@ -421,7 +421,7 @@ type ListTeamBindingsQuery struct {
}
type ListTeamBindingsResult struct {
Bindings []TeamBinding
Bindings []TeamMember
Continue int64
RV int64
}
@@ -445,11 +445,6 @@ func (m TeamMember) MemberID() string {
return claims.NewTypeID(claims.TypeUser, m.UserUID)
}
type TeamBinding struct {
TeamUID string
Members []TeamMember
}
var sqlQueryTeamBindingsTemplate = mustTemplate("team_bindings_query.sql")
type listTeamBindingsQuery struct {
@@ -505,11 +500,11 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
return nil, err
}
res := &ListTeamBindingsResult{}
grouped := map[string][]TeamMember{}
res := &ListTeamBindingsResult{
Bindings: make([]TeamMember, 0, int(query.Pagination.Limit)),
}
var lastID int64
var atTeamLimit bool
for rows.Next() {
m := TeamMember{}
@@ -518,16 +513,11 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
return res, err
}
lastID = m.TeamID
members, ok := grouped[m.TeamUID]
if ok {
grouped[m.TeamUID] = append(members, m)
} else if !atTeamLimit {
grouped[m.TeamUID] = []TeamMember{m}
}
res.Bindings = append(res.Bindings, m)
if len(grouped) >= int(query.Pagination.Limit)-1 {
atTeamLimit = true
lastID = m.ID
if len(res.Bindings) >= int(query.Pagination.Limit)-1 {
res.Continue = lastID
}
}
@@ -536,14 +526,6 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa
res.RV, err = sql.GetResourceVersion(ctx, "team_member", "updated")
}
res.Bindings = make([]TeamBinding, 0, len(grouped))
for uid, members := range grouped {
res.Bindings = append(res.Bindings, TeamBinding{
TeamUID: uid,
Members: members,
})
}
return res, err
}
@@ -3,18 +3,13 @@ FROM {{ .Ident .TeamMemberTable }} tm
INNER JOIN {{ .Ident .TeamTable }} t ON tm.team_id = t.id
INNER JOIN {{ .Ident .UserTable }} u ON tm.user_id = u.id
WHERE
{{ if .Query.UID }}
t.uid = {{ .Arg .Query.UID }}
{{ else }}
t.uid IN(
SELECT uid
FROM {{ .Ident .TeamTable }} t
{{ if .Query.Pagination.Continue }}
WHERE t.id >= {{ .Arg .Query.Pagination.Continue }}
{{ end }}
ORDER BY t.id ASC LIMIT {{ .Arg .Query.Pagination.Limit }}
)
{{ end }}
AND tm.org_id = {{ .Arg .Query.OrgID}}
tm.org_id = {{ .Arg .Query.OrgID}}
{{ if .Query.UID }}
AND t.uid = {{ .Arg .Query.UID }}
{{ end }}
{{- if .Query.Pagination.Continue }}
AND tm.id >= {{ .Arg .Query.Pagination.Continue }}
{{- end }}
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT {{ .Arg .Query.Pagination.Limit }};
@@ -3,7 +3,8 @@ FROM `grafana`.`team_member` tm
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
WHERE
t.uid = 'team-1'
AND tm.org_id = 1
tm.org_id = 1
AND t.uid = 'team-1'
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 1;
@@ -3,11 +3,7 @@ FROM `grafana`.`team_member` tm
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
WHERE
t.uid IN(
SELECT uid
FROM `grafana`.`team` t
ORDER BY t.id ASC LIMIT 5
)
AND tm.org_id = 1
tm.org_id = 1
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 5;
@@ -3,12 +3,8 @@ FROM `grafana`.`team_member` tm
INNER JOIN `grafana`.`team` t ON tm.team_id = t.id
INNER JOIN `grafana`.`user` u ON tm.user_id = u.id
WHERE
t.uid IN(
SELECT uid
FROM `grafana`.`team` t
WHERE t.id >= 2
ORDER BY t.id ASC LIMIT 1
)
AND tm.org_id = 1
tm.org_id = 1
AND tm.id >= 2
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 1;
@@ -3,7 +3,8 @@ FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE
t.uid = 'team-1'
AND tm.org_id = 1
tm.org_id = 1
AND t.uid = 'team-1'
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 1;
@@ -3,11 +3,7 @@ FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE
t.uid IN(
SELECT uid
FROM "grafana"."team" t
ORDER BY t.id ASC LIMIT 5
)
AND tm.org_id = 1
tm.org_id = 1
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 5;
@@ -3,12 +3,8 @@ FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE
t.uid IN(
SELECT uid
FROM "grafana"."team" t
WHERE t.id >= 2
ORDER BY t.id ASC LIMIT 1
)
AND tm.org_id = 1
tm.org_id = 1
AND tm.id >= 2
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 1;
@@ -3,7 +3,8 @@ FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE
t.uid = 'team-1'
AND tm.org_id = 1
tm.org_id = 1
AND t.uid = 'team-1'
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 1;
@@ -3,11 +3,7 @@ FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE
t.uid IN(
SELECT uid
FROM "grafana"."team" t
ORDER BY t.id ASC LIMIT 5
)
AND tm.org_id = 1
tm.org_id = 1
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 5;
@@ -3,12 +3,8 @@ FROM "grafana"."team_member" tm
INNER JOIN "grafana"."team" t ON tm.team_id = t.id
INNER JOIN "grafana"."user" u ON tm.user_id = u.id
WHERE
t.uid IN(
SELECT uid
FROM "grafana"."team" t
WHERE t.id >= 2
ORDER BY t.id ASC LIMIT 1
)
AND tm.org_id = 1
tm.org_id = 1
AND tm.id >= 2
AND NOT tm.external
ORDER BY t.id ASC;
ORDER BY t.id ASC
LIMIT 1;
+12 -22
View File
@@ -117,46 +117,36 @@ func (l *LegacyBindingStore) List(ctx context.Context, options *internalversion.
return &list, nil
}
func mapToBindingObject(ns claims.NamespaceInfo, b legacy.TeamBinding) iamv0alpha1.TeamBinding {
func mapToBindingObject(ns claims.NamespaceInfo, tm legacy.TeamMember) iamv0alpha1.TeamBinding {
rv := time.Time{}
ct := time.Now()
for _, m := range b.Members {
if m.Updated.After(rv) {
rv = m.Updated
}
if m.Created.Before(ct) {
ct = m.Created
}
if tm.Updated.After(rv) {
rv = tm.Updated
}
if tm.Created.Before(ct) {
ct = tm.Created
}
return iamv0alpha1.TeamBinding{
ObjectMeta: metav1.ObjectMeta{
Name: b.TeamUID,
Name: tm.TeamUID,
Namespace: ns.Value,
ResourceVersion: strconv.FormatInt(rv.UnixMilli(), 10),
CreationTimestamp: metav1.NewTime(ct),
},
Spec: iamv0alpha1.TeamBindingSpec{
TeamRef: iamv0alpha1.TeamBindingTeamRef{
Name: b.TeamUID,
Name: tm.TeamUID,
},
Subjects: mapToSubjects(b.Members),
Subject: iamv0alpha1.TeamBindingspecSubject{
Name: tm.UserUID,
},
Permission: common.MapTeamPermission(tm.Permission),
},
}
}
func mapToSubjects(members []legacy.TeamMember) []iamv0alpha1.TeamBindingspecSubject {
out := make([]iamv0alpha1.TeamBindingspecSubject, 0, len(members))
for _, m := range members {
out = append(out, iamv0alpha1.TeamBindingspecSubject{
Name: m.UserUID,
Permission: common.MapTeamPermission(m.Permission),
})
}
return out
}
func mapPermisson(p team.PermissionType) iamv0.TeamPermission {
if p == team.PermissionTypeAdmin {
return iamv0.TeamPermissionAdmin
@@ -3737,20 +3737,23 @@
"com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingSpec": {
"type": "object",
"required": [
"subjects",
"teamRef"
"subject",
"teamRef",
"permission"
],
"properties": {
"subjects": {
"type": "array",
"items": {
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingspecSubject"
}
]
}
"permission": {
"description": "permission of the identity in the team",
"type": "string",
"default": ""
},
"subject": {
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingspecSubject"
}
]
},
"teamRef": {
"default": {},
@@ -3778,19 +3781,13 @@
"com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBindingspecSubject": {
"type": "object",
"required": [
"name",
"permission"
"name"
],
"properties": {
"name": {
"description": "uid of the identity",
"type": "string",
"default": ""
},
"permission": {
"description": "permission of the identity in the team",
"type": "string",
"default": ""
}
}
},
@@ -5594,15 +5591,18 @@
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec": {
"type": "object",
"required": [
"subjects",
"teamRef"
"subject",
"teamRef",
"permission"
],
"properties": {
"subjects": {
"type": "array",
"items": {
"default": {}
}
"permission": {
"description": "permission of the identity in the team",
"type": "string",
"default": ""
},
"subject": {
"default": {}
},
"teamRef": {
"default": {}
@@ -5644,19 +5644,13 @@
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject": {
"type": "object",
"required": [
"name",
"permission"
"name"
],
"properties": {
"name": {
"description": "uid of the identity",
"type": "string",
"default": ""
},
"permission": {
"description": "permission of the identity in the team",
"type": "string",
"default": ""
}
}
},