Skip to content

Commit 20b38bd

Browse files
authored
Merge pull request #10 from linuxfoundation/misc-fixes
Optimize User Service lookups
2 parents 660c559 + 49eca14 commit 20b38bd

File tree

10 files changed

+82
-77
lines changed

10 files changed

+82
-77
lines changed

charts/lfx-v1-sync-helper/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ apiVersion: v2
55
name: lfx-v1-sync-helper
66
description: LFX Platform v1 Sync Helper chart
77
type: application
8-
version: 0.2.1
8+
version: 0.2.2

charts/lfx-v1-sync-helper/values.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ app:
1919
# NATS_URL is required
2020
NATS_URL:
2121
value: nats://lfx-platform-nats.lfx.svc.cluster.local:4222
22-
# DEBUG is optional
23-
DEBUG:
24-
value: "false"
2522
# PORT is optional
2623
PORT:
2724
value: "8080"

v1-sync-helper/client_committees.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import (
99
"fmt"
1010

1111
committeeservice "github.com/linuxfoundation/lfx-v2-committee-service/gen/committee_service"
12+
"github.com/nats-io/nats.go/jetstream"
1213
)
1314

1415
// fetchCommitteeBase fetches an existing committee base from the Committee Service API.
1516
func fetchCommitteeBase(ctx context.Context, committeeUID string) (*committeeservice.CommitteeBaseWithReadonlyAttributes, string, error) {
16-
token, err := generateCachedJWTToken(committeeServiceAudience, UserInfo{})
17+
token, err := generateCachedJWTToken(committeeServiceAudience, "", nil)
1718
if err != nil {
1819
return nil, "", err
1920
}
@@ -35,8 +36,8 @@ func fetchCommitteeBase(ctx context.Context, committeeUID string) (*committeeser
3536
}
3637

3738
// createCommittee creates a new committee via the Committee Service API.
38-
func createCommittee(ctx context.Context, payload *committeeservice.CreateCommitteePayload, userInfo UserInfo) (*committeeservice.CommitteeFullWithReadonlyAttributes, error) {
39-
token, err := generateCachedJWTToken(committeeServiceAudience, userInfo)
39+
func createCommittee(ctx context.Context, payload *committeeservice.CreateCommitteePayload, v1Principal string, mappingsKV jetstream.KeyValue) (*committeeservice.CommitteeFullWithReadonlyAttributes, error) {
40+
token, err := generateCachedJWTToken(committeeServiceAudience, v1Principal, mappingsKV)
4041
if err != nil {
4142
return nil, err
4243
}
@@ -52,7 +53,7 @@ func createCommittee(ctx context.Context, payload *committeeservice.CreateCommit
5253
}
5354

5455
// updateCommittee updates a committee by separately handling base and settings if there are changes.
55-
func updateCommittee(ctx context.Context, payload *committeeservice.UpdateCommitteeBasePayload, userInfo UserInfo) error {
56+
func updateCommittee(ctx context.Context, payload *committeeservice.UpdateCommitteeBasePayload, v1Principal string, mappingsKV jetstream.KeyValue) error {
5657
// Fetch current committee base.
5758
currentBase, baseETag, err := fetchCommitteeBase(ctx, *payload.UID)
5859
if err != nil {
@@ -73,7 +74,7 @@ func updateCommittee(ctx context.Context, payload *committeeservice.UpdateCommit
7374
baseChanged := !committeeBasesEqual(currentBase, updatedBase)
7475

7576
if baseChanged {
76-
token, err := generateCachedJWTToken(committeeServiceAudience, userInfo)
77+
token, err := generateCachedJWTToken(committeeServiceAudience, v1Principal, mappingsKV)
7778
if err != nil {
7879
return fmt.Errorf("failed to generate token for committee base update: %w", err)
7980
}
@@ -103,8 +104,8 @@ func committeeBasesEqual(a, b *committeeservice.CommitteeBaseWithReadonlyAttribu
103104
}
104105

105106
// createCommitteeMember creates a new committee member via the Committee Service API.
106-
func createCommitteeMember(ctx context.Context, payload *committeeservice.CreateCommitteeMemberPayload, userInfo UserInfo) (*committeeservice.CommitteeMemberFullWithReadonlyAttributes, error) {
107-
token, err := generateCachedJWTToken(committeeServiceAudience, userInfo)
107+
func createCommitteeMember(ctx context.Context, payload *committeeservice.CreateCommitteeMemberPayload, v1Principal string, mappingsKV jetstream.KeyValue) (*committeeservice.CommitteeMemberFullWithReadonlyAttributes, error) {
108+
token, err := generateCachedJWTToken(committeeServiceAudience, v1Principal, mappingsKV)
108109
if err != nil {
109110
return nil, err
110111
}
@@ -121,7 +122,7 @@ func createCommitteeMember(ctx context.Context, payload *committeeservice.Create
121122

122123
// fetchCommitteeMember fetches an existing committee member from the Committee Service API.
123124
func fetchCommitteeMember(ctx context.Context, committeeUID, memberUID string) (*committeeservice.CommitteeMemberFullWithReadonlyAttributes, string, error) {
124-
token, err := generateCachedJWTToken(committeeServiceAudience, UserInfo{})
125+
token, err := generateCachedJWTToken(committeeServiceAudience, "", nil)
125126
if err != nil {
126127
return nil, "", err
127128
}
@@ -145,7 +146,7 @@ func fetchCommitteeMember(ctx context.Context, committeeUID, memberUID string) (
145146
}
146147

147148
// updateCommitteeMember updates an existing committee member via the Committee Service API.
148-
func updateCommitteeMember(ctx context.Context, payload *committeeservice.UpdateCommitteeMemberPayload, userInfo UserInfo) error {
149+
func updateCommitteeMember(ctx context.Context, payload *committeeservice.UpdateCommitteeMemberPayload, v1Principal string, mappingsKV jetstream.KeyValue) error {
149150
// Fetch current committee member for comparison.
150151
currentMember, etag, err := fetchCommitteeMember(ctx, payload.UID, payload.MemberUID)
151152
if err != nil {
@@ -156,7 +157,7 @@ func updateCommitteeMember(ctx context.Context, payload *committeeservice.Update
156157
memberChanged := !committeeMembersEqual(currentMember, payload)
157158

158159
if memberChanged {
159-
token, err := generateCachedJWTToken(committeeServiceAudience, userInfo)
160+
token, err := generateCachedJWTToken(committeeServiceAudience, v1Principal, mappingsKV)
160161
if err != nil {
161162
return fmt.Errorf("failed to generate token for committee member update: %w", err)
162163
}

v1-sync-helper/client_projects.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import (
99
"fmt"
1010

1111
projectservice "github.com/linuxfoundation/lfx-v2-project-service/api/project/v1/gen/project_service"
12+
"github.com/nats-io/nats.go/jetstream"
1213
)
1314

1415
// fetchProjectBase fetches an existing project base from the Project Service API.
1516
var fetchProjectBase = func(ctx context.Context, projectUID string) (*projectservice.ProjectBase, string, error) {
16-
token, err := generateCachedJWTToken(projectServiceAudience, UserInfo{})
17+
token, err := generateCachedJWTToken(projectServiceAudience, "", nil)
1718
if err != nil {
1819
return nil, "", err
1920
}
@@ -36,7 +37,7 @@ var fetchProjectBase = func(ctx context.Context, projectUID string) (*projectser
3637

3738
// fetchProjectSettings fetches an existing project settings from the Project Service API.
3839
func fetchProjectSettings(ctx context.Context, projectUID string) (*projectservice.ProjectSettings, string, error) {
39-
token, err := generateCachedJWTToken(projectServiceAudience, UserInfo{})
40+
token, err := generateCachedJWTToken(projectServiceAudience, "", nil)
4041
if err != nil {
4142
return nil, "", err
4243
}
@@ -58,8 +59,8 @@ func fetchProjectSettings(ctx context.Context, projectUID string) (*projectservi
5859
}
5960

6061
// createProject creates a new project via the Project Service API.
61-
func createProject(ctx context.Context, payload *projectservice.CreateProjectPayload, userInfo UserInfo) (*projectservice.ProjectFull, error) {
62-
token, err := generateCachedJWTToken(projectServiceAudience, userInfo)
62+
func createProject(ctx context.Context, payload *projectservice.CreateProjectPayload, v1Principal string, mappingsKV jetstream.KeyValue) (*projectservice.ProjectFull, error) {
63+
token, err := generateCachedJWTToken(projectServiceAudience, v1Principal, mappingsKV)
6364
if err != nil {
6465
return nil, err
6566
}
@@ -75,7 +76,7 @@ func createProject(ctx context.Context, payload *projectservice.CreateProjectPay
7576
}
7677

7778
// updateProject updates a project by separately handling base and settings if there are changes.
78-
func updateProject(ctx context.Context, basePayload *projectservice.UpdateProjectBasePayload, settingsPayload *projectservice.UpdateProjectSettingsPayload, userInfo UserInfo) error {
79+
func updateProject(ctx context.Context, basePayload *projectservice.UpdateProjectBasePayload, settingsPayload *projectservice.UpdateProjectSettingsPayload, v1Principal string, mappingsKV jetstream.KeyValue) error {
7980
// Fetch current project base.
8081
currentBase, baseETag, err := fetchProjectBase(ctx, *basePayload.UID)
8182
if err != nil {
@@ -113,7 +114,7 @@ func updateProject(ctx context.Context, basePayload *projectservice.UpdateProjec
113114
baseChanged := !projectBasesEqual(currentBase, updatedBase)
114115

115116
if baseChanged {
116-
token, err := generateCachedJWTToken(projectServiceAudience, userInfo)
117+
token, err := generateCachedJWTToken(projectServiceAudience, v1Principal, mappingsKV)
117118
if err != nil {
118119
return fmt.Errorf("failed to generate token for base update: %w", err)
119120
}
@@ -156,7 +157,7 @@ func updateProject(ctx context.Context, basePayload *projectservice.UpdateProjec
156157
}
157158

158159
if settingsChanged {
159-
token, err := generateCachedJWTToken(projectServiceAudience, userInfo)
160+
token, err := generateCachedJWTToken(projectServiceAudience, v1Principal, mappingsKV)
160161
if err != nil {
161162
return fmt.Errorf("failed to generate token for settings update: %w", err)
162163
}

v1-sync-helper/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var projectAllowlist = []string{
2323
// "lfenergy",
2424
}
2525

26-
// ProjectFamilyAllowlist contains the list of projects slugs that are allowed
26+
// projectFamilyAllowlist contains the list of project slugs that are allowed
2727
// to be synced along with their child projects. *All entries must be
2828
// lowercase* (lookups downcase for case-insensitive matching).
2929
var projectFamilyAllowlist = []string{

v1-sync-helper/handlers.go

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,6 @@ func shouldSkipSync(ctx context.Context, v1Data map[string]any) bool {
2727
return false
2828
}
2929

30-
// extractUserInfo extracts user information from v1 data for API calls and JWT impersonation.
31-
func extractUserInfo(ctx context.Context, v1Data map[string]any, mappingsKV jetstream.KeyValue) UserInfo {
32-
// Extract platform ID from lastmodifiedbyid
33-
if lastModifiedBy, ok := v1Data["lastmodifiedbyid"].(string); ok && lastModifiedBy != "" {
34-
// Check if this is a machine user with @clients suffix
35-
if strings.HasSuffix(lastModifiedBy, "@clients") {
36-
// Machine user - pass through with @clients only on principal
37-
return UserInfo{
38-
Username: strings.TrimSuffix(lastModifiedBy, "@clients"), // Subject without @clients
39-
Email: "", // No email for machine users
40-
Principal: lastModifiedBy, // Principal includes @clients
41-
}
42-
}
43-
44-
// Regular platform ID - look up via v1 API
45-
userInfo, err := getUserInfoFromV1(ctx, lastModifiedBy, mappingsKV)
46-
if err != nil || userInfo.Username == "" {
47-
logger.With(errKey, err, "platform_id", lastModifiedBy).WarnContext(ctx, "failed to get user info from v1 API, falling back to service account")
48-
return UserInfo{} // Empty UserInfo triggers fallback to v1_sync_helper@clients
49-
}
50-
51-
return userInfo
52-
}
53-
return UserInfo{}
54-
}
55-
5630
// kvHandler processes KV bucket updates from Meltano
5731
func kvHandler(entry jetstream.KeyValueEntry, v1KV jetstream.KeyValue, mappingsKV jetstream.KeyValue) {
5832
ctx := context.Background()
@@ -89,16 +63,13 @@ func handleKVPut(ctx context.Context, entry jetstream.KeyValueEntry, _ jetstream
8963
return
9064
}
9165

92-
// Extract user information for API calls and JWT impersonation
93-
userInfo := extractUserInfo(ctx, v1Data, mappingsKV)
94-
9566
// Determine the object type based on the key pattern
9667
if strings.HasPrefix(key, "salesforce-project__c.") {
97-
handleProjectUpdate(ctx, key, v1Data, userInfo, mappingsKV)
68+
handleProjectUpdate(ctx, key, v1Data, mappingsKV)
9869
} else if strings.HasPrefix(key, "platform-collaboration__c.") {
99-
handleCommitteeUpdate(ctx, key, v1Data, userInfo, mappingsKV)
70+
handleCommitteeUpdate(ctx, key, v1Data, mappingsKV)
10071
} else if strings.HasPrefix(key, "platform-community__c.") {
101-
handleCommitteeMemberUpdate(ctx, key, v1Data, userInfo, mappingsKV)
72+
handleCommitteeMemberUpdate(ctx, key, v1Data, mappingsKV)
10273
} else {
10374
logger.With("key", key).DebugContext(ctx, "unknown object type, ignoring")
10475
}

v1-sync-helper/handlers_committees.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,18 @@ func mapTypeToCategory(ctx context.Context, typeVal, committeeName string) *stri
133133
}
134134

135135
// handleCommitteeUpdate processes a committee update from the KV bucket.
136-
func handleCommitteeUpdate(ctx context.Context, key string, v1Data map[string]any, userInfo UserInfo, mappingsKV jetstream.KeyValue) {
136+
func handleCommitteeUpdate(ctx context.Context, key string, v1Data map[string]any, mappingsKV jetstream.KeyValue) {
137137
// Check if we should skip this sync operation.
138138
if shouldSkipSync(ctx, v1Data) {
139139
return
140140
}
141141

142+
// Extract v1Principal from lastmodifiedbyid for JWT generation.
143+
v1Principal := ""
144+
if lastModifiedBy, ok := v1Data["lastmodifiedbyid"].(string); ok && lastModifiedBy != "" {
145+
v1Principal = lastModifiedBy
146+
}
147+
142148
// Extract committee SFID.
143149
sfid, ok := v1Data["sfid"].(string)
144150
if !ok || sfid == "" {
@@ -169,7 +175,7 @@ func handleCommitteeUpdate(ctx context.Context, key string, v1Data map[string]an
169175
return
170176
}
171177

172-
err = updateCommittee(ctx, payload, userInfo)
178+
err = updateCommittee(ctx, payload, v1Principal, mappingsKV)
173179
uid = existingUID
174180
} else {
175181
// Check if parent project exists in mappings before creating new committee.
@@ -193,7 +199,7 @@ func handleCommitteeUpdate(ctx context.Context, key string, v1Data map[string]an
193199
}
194200

195201
var response *committeeservice.CommitteeFullWithReadonlyAttributes
196-
response, err = createCommittee(ctx, payload, userInfo)
202+
response, err = createCommittee(ctx, payload, v1Principal, mappingsKV)
197203
if response != nil && response.UID != nil {
198204
uid = *response.UID
199205
}
@@ -353,12 +359,18 @@ func mapV1DataToCommitteeUpdateBasePayload(ctx context.Context, committeeUID str
353359
}
354360

355361
// handleCommitteeMemberUpdate processes a committee member update from platform-community__c records.
356-
func handleCommitteeMemberUpdate(ctx context.Context, key string, v1Data map[string]any, userInfo UserInfo, mappingsKV jetstream.KeyValue) {
362+
func handleCommitteeMemberUpdate(ctx context.Context, key string, v1Data map[string]any, mappingsKV jetstream.KeyValue) {
357363
// Check if we should skip this sync operation.
358364
if shouldSkipSync(ctx, v1Data) {
359365
return
360366
}
361367

368+
// Extract v1Principal from lastmodifiedbyid for JWT generation.
369+
v1Principal := ""
370+
if lastModifiedBy, ok := v1Data["lastmodifiedbyid"].(string); ok && lastModifiedBy != "" {
371+
v1Principal = lastModifiedBy
372+
}
373+
362374
// Extract committee member SFID.
363375
sfid, ok := v1Data["sfid"].(string)
364376
if !ok || sfid == "" {
@@ -419,7 +431,7 @@ func handleCommitteeMemberUpdate(ctx context.Context, key string, v1Data map[str
419431
return
420432
}
421433

422-
err = updateCommitteeMember(ctx, payload, userInfo)
434+
err = updateCommitteeMember(ctx, payload, v1Principal, mappingsKV)
423435
memberUID = existingMemberUID
424436
} else {
425437
// Create new committee member.
@@ -434,7 +446,7 @@ func handleCommitteeMemberUpdate(ctx context.Context, key string, v1Data map[str
434446
}
435447

436448
var response *committeeservice.CommitteeMemberFullWithReadonlyAttributes
437-
response, err = createCommitteeMember(ctx, payload, userInfo)
449+
response, err = createCommitteeMember(ctx, payload, v1Principal, mappingsKV)
438450
if response != nil && response.UID != nil {
439451
memberUID = *response.UID
440452
}

v1-sync-helper/handlers_projects.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,18 @@ func mapAdminCategoryToCategory(adminCategory string) *string {
125125
}
126126

127127
// handleProjectUpdate processes a project update from the KV bucket.
128-
func handleProjectUpdate(ctx context.Context, key string, v1Data map[string]any, userInfo UserInfo, mappingsKV jetstream.KeyValue) {
128+
func handleProjectUpdate(ctx context.Context, key string, v1Data map[string]any, mappingsKV jetstream.KeyValue) {
129129
// Check if we should skip this sync operation.
130130
if shouldSkipSync(ctx, v1Data) {
131131
return
132132
}
133133

134+
// Extract v1Principal from lastmodifiedbyid for JWT generation.
135+
v1Principal := ""
136+
if lastModifiedBy, ok := v1Data["lastmodifiedbyid"].(string); ok && lastModifiedBy != "" {
137+
v1Principal = lastModifiedBy
138+
}
139+
134140
// Extract project SFID (primary key).
135141
sfid, ok := v1Data["sfid"].(string)
136142
if !ok || sfid == "" {
@@ -172,7 +178,7 @@ func handleProjectUpdate(ctx context.Context, key string, v1Data map[string]any,
172178
return
173179
}
174180

175-
err = updateProject(ctx, payload, settingsPayload, userInfo)
181+
err = updateProject(ctx, payload, settingsPayload, v1Principal, mappingsKV)
176182
uid = existingUID
177183
} else {
178184
// Check allowlist before creating new project.
@@ -194,7 +200,7 @@ func handleProjectUpdate(ctx context.Context, key string, v1Data map[string]any,
194200
}
195201

196202
var response *projectservice.ProjectFull
197-
response, err = createProject(ctx, payload, userInfo)
203+
response, err = createProject(ctx, payload, v1Principal, mappingsKV)
198204
if response != nil && response.UID != nil {
199205
uid = *response.UID
200206
}

v1-sync-helper/lfx_v1_client.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,28 +643,40 @@ func parseWebsiteURL(website string) string {
643643
}
644644

645645
// getUserInfoFromV1 converts a Platform ID to LFX username and email using v1 API with caching
646-
func getUserInfoFromV1(ctx context.Context, platformID string, mappingsKV jetstream.KeyValue) (UserInfo, error) {
646+
func getUserInfoFromV1(ctx context.Context, platformID string, mappingsKV jetstream.KeyValue) UserInfo {
647647
if platformID == "" {
648-
return UserInfo{}, nil
648+
return UserInfo{}
649+
}
650+
651+
if platformID == "platform" {
652+
return UserInfo{}
653+
}
654+
655+
// Check if this is a machine user with @clients suffix.
656+
if strings.HasSuffix(platformID, "@clients") {
657+
// Machine user - pass through with @clients only on principal.
658+
return UserInfo{
659+
Username: strings.TrimSuffix(platformID, "@clients"), // Subject without @clients.
660+
Email: "", // No email for machine users.
661+
Principal: platformID, // Principal includes @clients.
662+
}
649663
}
650664

651665
user, err := lookupUser(ctx, platformID, mappingsKV)
652666
if err != nil {
653667
logger.With(errKey, err, "platform_id", platformID).WarnContext(ctx, "failed to lookup user from v1 API, falling back to service account")
654-
return UserInfo{}, nil // Return empty to trigger fallback
668+
return UserInfo{} // Return empty to trigger fallback
655669
}
656670

657671
// Check for cached error/invalid states
658672
if user.Username == "" {
659673
logger.With("platform_id", platformID).WarnContext(ctx, "user has empty username, falling back to service account")
660-
return UserInfo{}, nil // Return empty to trigger fallback
674+
return UserInfo{} // Return empty to trigger fallback
661675
}
662676

663677
// Return user info for JWT impersonation
664-
userInfo := UserInfo{
678+
return UserInfo{
665679
Username: user.Username,
666680
Email: user.Email,
667681
}
668-
669-
return userInfo, nil
670682
}

0 commit comments

Comments
 (0)