Skip to content

🌱 add a client to support token request - #189

Merged
openshift-merge-bot[bot] merged 1 commit into
open-cluster-management-io:mainfrom
skeeey:token-req
Jan 20, 2026
Merged

🌱 add a client to support token request#189
openshift-merge-bot[bot] merged 1 commit into
open-cluster-management-io:mainfrom
skeeey:token-req

Conversation

@skeeey

@skeeey skeeey commented Jan 19, 2026

Copy link
Copy Markdown
Member

Summary

Related issue(s)

Fixes #

Summary by CodeRabbit

  • New Features

    • Service account token creation via CloudEvents with response subscription, timeouts, and retries.
  • Bug Fixes

    • Access review now recognizes token-request operations for service accounts.
  • Tests

    • Unit tests for token request encoding/decoding and unsupported client methods.
    • Integration tests validating token issuance, timeouts, and sequential requests.
  • Chores

    • Adjusted logging configuration for improved verbosity and diagnostics.

✏️ Tip: You can customize this high-level summary in your review settings.

@openshift-ci
openshift-ci Bot requested review from deads2k and qiujian16 January 19, 2026 01:08
@coderabbitai

coderabbitai Bot commented Jan 19, 2026

Copy link
Copy Markdown

Walkthrough

Adds a CloudEvents-driven Kubernetes ServiceAccount client and TokenRequest codec, with unit and integration tests for token issuance over gRPC, and updates SAR authorization handling and server logging configuration.

Changes

Cohort / File(s) Summary
ServiceAccount client
pkg/cloudevents/clients/serviceaccount/client.go, pkg/cloudevents/clients/serviceaccount/client_test.go
New ServiceAccountClient implementing corev1client.ServiceAccountInterface; most methods return NotSupported except CreateToken, which issues TokenRequests via CloudEvents (subscription-before-publish, timeout, request/response matching). Tests cover construction and unsupported-method behavior.
TokenRequest codec
pkg/cloudevents/clients/serviceaccount/codec.go, pkg/cloudevents/clients/serviceaccount/codec_test.go
New TokenRequestCodec and TokenRequestDataType for CloudEvents encoding/decoding of authentication.k8s.io/v1 TokenRequest objects; includes encode/decode logic and comprehensive round-trip/unit tests.
Integration tests
pkg/cloudevents/clients/serviceaccount/integration_test.go
In-memory tokenRequestService and gRPC broker tests exercising CreateToken end-to-end, timeouts, and multiple sequential requests.
Authorization and server logging
pkg/cloudevents/server/grpc/authz/kube/sar.go, pkg/server/grpc/server.go
Adds handling of serviceaccount.TokenRequestDataType to SAR construction for serviceaccounts/token create; adjusts server logging to use textlogger with increased verbosity.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

approved, lgtm

Suggested reviewers

  • deads2k
  • qiujian16
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. It contains only the template with empty placeholder sections (Summary and Related issue(s)) and no actual implementation details. Fill in the Summary section describing the token request client implementation, its purpose, and key features. Add any related issue references in the Related issue(s) section.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new ServiceAccount client with token request support. It is concise and specific.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@pkg/cloudevents/clients/serviceaccount/codec.go`:
- Around line 50-56: In TokenRequestCodec.Decode, add a nil check for the
incoming evt and return a clear error if evt == nil, then on DataAs failure do
not include raw event bytes (evt.Data()) in the error; instead return a safe
error message that references TokenRequestCodec.Decode and
authenticationv1.TokenRequest and optionally include non-sensitive metadata
(e.g. data length or error type) but never the raw token data; ensure the
function still returns nil, error on failure and the parsed
*authenticationv1.TokenRequest on success.
- Around line 31-46: Guard against a nil tokenRequest at the start of
TokenRequestCodec.Encode by returning a descriptive error if tokenRequest ==
nil, and move any dereferences (like tokenRequest.UID and
tokenRequest.Namespace) after that guard; also avoid potentially incorrect %s
formatting for eventType.CloudEventsDataType by using %v (or including the value
via fmt.Sprintf) in the fmt.Errorf call so the error message formats the data
type correctly. Ensure you reference TokenRequestCodec.Encode,
TokenRequestDataType, types.NewEventBuilder, and evt.SetData when making the
changes.

In `@pkg/cloudevents/clients/serviceaccount/integration_test.go`:
- Around line 54-75: The HandleStatusUpdate method may panic if s.handler is
nil; update tokenRequestService.HandleStatusUpdate to check s.handler before
calling s.handler.OnCreate: after storing s.tokenRequests[string(tokenReq.UID)]
= tokenReq verify if s.handler != nil and only call s.handler.OnCreate when
non-nil (otherwise return a non-nil error or nil per existing behavior),
ensuring no nil pointer dereference occurs when RegisterHandler has not been
called.
- Around line 301-314: The test has a data race on the receivedUIDs map: protect
concurrent access by adding a sync.Mutex (or sync.RWMutex) alongside
receivedUIDs in the test scope, then lock before writing to receivedUIDs in
tokenRequestService.handleStatusUpdateFn and lock/ RLock before
reading/asserting receivedUIDs in the test goroutine; ensure you unlock
appropriately around the map access used by the assertion so reads see the
updated entries.
🧹 Nitpick comments (4)
pkg/cloudevents/clients/serviceaccount/integration_test.go (4)

83-103: Redundant cleanup: defer grpcServer.Stop() and t.Cleanup with GracefulStop.

The defer grpcServer.Stop() at line 84 and grpcServer.GracefulStop() in t.Cleanup at line 101 are redundant. Since t.Cleanup runs after the test function returns, both will execute, potentially causing double-stop issues. Keep only the t.Cleanup approach for cleaner resource management.

Proposed fix
 	// Set up gRPC server
 	grpcServer := grpc.NewServer()
-	defer grpcServer.Stop()
 
 	grpcEventServer := grpcserver.NewGRPCBroker(grpcserver.NewBrokerOptions())

163-164: Same redundant cleanup issue.

Same as TestCreateToken_Integration - remove defer grpcServer.Stop() since t.Cleanup handles shutdown.


197-199: Mutating package-level TokenRequestTimeout is not parallel-safe.

If tests run with t.Parallel(), concurrent modification of TokenRequestTimeout can cause race conditions and flaky tests. Consider accepting the timeout as a client option or using a test-specific client configuration instead.


257-290: Consider strengthening test assertions.

The test allows failures with "may be timing issue" and only requires 1 out of 2 successes. This is a weak assertion that could mask real bugs. If timing issues are expected, consider:

  1. Investigating root cause of connection reuse issues
  2. Increasing the delay or using exponential backoff
  3. At minimum, requiring all requests to succeed since numRequests is only 2
Proposed stricter assertion
-	// At least one request should succeed
-	if successCount < 1 {
-		t.Errorf("Expected at least 1 successful request, got %d", successCount)
+	// All requests should succeed
+	if successCount != numRequests {
+		t.Errorf("Expected all %d requests to succeed, got %d", numRequests, successCount)
 	}

Comment thread pkg/cloudevents/clients/serviceaccount/codec.go
Comment thread pkg/cloudevents/clients/serviceaccount/codec.go
Comment thread pkg/cloudevents/clients/serviceaccount/integration_test.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@pkg/cloudevents/clients/serviceaccount/client.go`:
- Around line 81-135: The CreateToken method currently dereferences tokenRequest
via tokenRequest.DeepCopy() and will panic if tokenRequest is nil; add a nil
guard at the top of CreateToken (before tokenRequestCtx or use the incoming ctx)
that checks if tokenRequest == nil and returns a clear error (e.g., fmt.Errorf
or errors.New) so the function fails fast; update callers/tests if needed and
reference the tokenRequest parameter, the CreateToken method, and the
newTokenRequest assignment where DeepCopy() is called.

In `@pkg/cloudevents/server/grpc/authz/kube/sar.go`:
- Around line 207-212: In the serviceaccount.TokenRequestDataType branch, change
the SAR mapping to set sar.Spec.ResourceAttributes.Resource = "serviceaccounts"
and sar.Spec.ResourceAttributes.Subresource = "token" (keep Group = "" and Verb
= "create"), and when the service account name is available populate
sar.Spec.ResourceAttributes.Name with that name so the subresource token
authorization checks align correctly; update the case handling around
serviceaccount.TokenRequestDataType to apply these fields instead of using
"serviceaccounts/token".
♻️ Duplicate comments (4)
pkg/cloudevents/clients/serviceaccount/integration_test.go (2)

54-75: Guard against nil handler in HandleStatusUpdate.

🛠️ Suggested fix
 	s.tokenRequests[string(tokenReq.UID)] = tokenReq
 
 	// Notify the handler that we have a response ready
+	if s.handler == nil {
+		return nil
+	}
 	return s.handler.OnCreate(ctx, TokenRequestDataType, string(tokenReq.UID))

300-372: Protect receivedUIDs with a mutex to avoid data races.

🛠️ Suggested fix
+	var mu sync.Mutex
 	// Create a service that tracks UIDs
 	receivedUIDs := make(map[types.UID]bool)
@@
 	svc.handleStatusUpdateFn = func(ctx context.Context, evt *cloudevents.Event) error {
 		codec := NewTokenRequestCodec()
 		tokenReq, err := codec.Decode(evt)
 		if err != nil {
 			return err
 		}
+		mu.Lock()
 		receivedUIDs[tokenReq.UID] = true
+		mu.Unlock()
@@
-	if !receivedUIDs[result.UID] {
+	mu.Lock()
+	received := receivedUIDs[result.UID]
+	mu.Unlock()
+	if !received {
 		t.Errorf("Service did not receive request with UID %s", result.UID)
 	}
pkg/cloudevents/clients/serviceaccount/codec.go (2)

32-40: Guard against nil TokenRequest and improve error formatting.

🛠️ Suggested fix
 func (c *TokenRequestCodec) Encode(source string, eventType types.CloudEventsType, tokenRequest *authenticationv1.TokenRequest) (*cloudevents.Event, error) {
+	if tokenRequest == nil {
+		return nil, fmt.Errorf("tokenRequest is nil")
+	}
 	if eventType.CloudEventsDataType != TokenRequestDataType {
-		return nil, fmt.Errorf("unsupported cloudevents data type %s", eventType.CloudEventsDataType)
+		return nil, fmt.Errorf("unsupported cloudevents data type %v", eventType.CloudEventsDataType)
 	}

50-54: Avoid nil-event panic and leaking raw event data in errors.

🛠️ Suggested fix
 func (c *TokenRequestCodec) Decode(evt *cloudevents.Event) (*authenticationv1.TokenRequest, error) {
+	if evt == nil {
+		return nil, fmt.Errorf("nil cloudevent")
+	}
 	tokenRequest := &authenticationv1.TokenRequest{}
 	if err := evt.DataAs(tokenRequest); err != nil {
-		return nil, fmt.Errorf("failed to unmarshal event data %s, %v", string(evt.Data()), err)
+		return nil, fmt.Errorf("failed to unmarshal event data: %w", err)
 	}

Comment thread pkg/cloudevents/clients/serviceaccount/client.go
Comment thread pkg/cloudevents/server/grpc/authz/kube/sar.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test_stability.sh`:
- Around line 8-17: The test loop currently detects failures by grepping
"$result" for "FAIL:", which can miss build or package errors; change the logic
in the loop that runs `go test -count=1 ./pkg/cloudevents/clients/serviceaccount
-run TestCreateToken` so you capture both the command output and its exit status
(keep the `result` variable for output and check the command's exit code `$?` or
use `if ! go test ...; then`), then increment `failed` when the exit code is
non-zero and `passed` when it is zero; keep printing the captured `result` for
diagnostics and continue using RUNS, TestCreateToken, failed, and passed
variables as before.
♻️ Duplicate comments (1)
pkg/cloudevents/clients/serviceaccount/integration_test.go (1)

297-368: Guard receivedUIDs with a mutex to avoid a data race.

handleStatusUpdateFn runs in a gRPC handler goroutine while the assertion reads in the test goroutine. Unsynchronized map access can race or panic. This matches a previously flagged issue.

🔧 Suggested fix
 import (
 	"context"
 	"net"
+	"sync"
 	"testing"
 	"time"
@@
 	grpcEventServer := grpcserver.NewGRPCBroker(grpcserver.NewBrokerOptions())
 	pbv1.RegisterCloudEventServiceServer(grpcServer, grpcEventServer)
 
 	// Create a service that tracks UIDs
+	var mu sync.Mutex
 	receivedUIDs := make(map[types.UID]bool)
@@
 	svc.handleStatusUpdateFn = func(ctx context.Context, evt *cloudevents.Event) error {
 		codec := NewTokenRequestCodec()
 		tokenReq, err := codec.Decode(evt)
 		if err != nil {
 			return err
 		}
+		mu.Lock()
 		receivedUIDs[tokenReq.UID] = true
+		mu.Unlock()
@@
 	// Verify the service received a request with this UID
-	if !receivedUIDs[result.UID] {
+	mu.Lock()
+	received := receivedUIDs[result.UID]
+	mu.Unlock()
+	if !received {
 		t.Errorf("Service did not receive request with UID %s", result.UID)
 	}
🧹 Nitpick comments (2)
pkg/cloudevents/clients/serviceaccount/client.go (1)

114-123: Avoid potential blocking on duplicate responses.

If multiple responses for the same UID arrive, the buffered channel can fill and block the handler goroutine. A non-blocking send (or ctx-aware send) avoids this edge case.

♻️ Proposed tweak
 	cloudEventsClient.Subscribe(tokenRequestCtx, func(handlerCtx context.Context, resp *authenticationv1.TokenRequest) error {
 		if resp.UID != requestID {
 			return nil
 		}
 
 		logger := klog.FromContext(handlerCtx)
 		logger.V(4).Info("response token", "requestID", resp.UID, "serviceAccountName", resp.Name)
-		responseChan <- resp
+		select {
+		case responseChan <- resp:
+		case <-handlerCtx.Done():
+		}
 		return nil
 	})
pkg/cloudevents/clients/serviceaccount/integration_test.go (1)

222-288: Avoid masking failures in the multi-request test.

Allowing request failures can hide regressions. Consider asserting all requests succeed and use readiness/backoff to improve determinism instead of tolerating errors.

Comment thread test_stability.sh Outdated
@skeeey
skeeey force-pushed the token-req branch 2 times, most recently from 4590693 to 8880031 Compare January 19, 2026 02:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@pkg/cloudevents/clients/serviceaccount/client_test.go`:
- Around line 120-122: The inline comment above TestCreateToken is stale; update
or remove it so it no longer claims CreateToken lacks integration coverage. Edit
the comment block near TestCreateToken in
pkg/cloudevents/clients/serviceaccount/client_test.go to either delete the note
or replace it with a brief accurate statement referencing the new integration
tests (e.g., mention that integration coverage exists), ensuring the
TestCreateToken comment reflects the current test coverage.

In `@pkg/cloudevents/clients/serviceaccount/integration_test.go`:
- Around line 53-77: HandleStatusUpdate writes to the tokenRequests map without
synchronization which races with concurrent readers; add a mutex field (e.g.,
sync.RWMutex named mu) to the tokenRequestService struct and use
mu.Lock()/mu.Unlock() around the write in HandleStatusUpdate when assigning
s.tokenRequests[string(tokenReq.UID)] = tokenReq, and update any reader method
(e.g., Get) to use mu.RLock()/mu.RUnlock() to prevent data races while allowing
concurrent reads.
🧹 Nitpick comments (2)
pkg/cloudevents/clients/serviceaccount/integration_test.go (2)

83-157: Consider replacing the fixed sleep with a readiness probe.
A dial-with-timeout or retry loop will reduce flakiness in slow CI environments.


255-286: Tolerating failures hides real regressions.
The test allows requests to fail due to timing issues; consider making it deterministic (e.g., fresh client per request or explicit connection reset) so all requests must succeed.

Comment thread pkg/cloudevents/clients/serviceaccount/client_test.go Outdated
Comment thread pkg/cloudevents/clients/serviceaccount/integration_test.go
@skeeey
skeeey force-pushed the token-req branch 2 times, most recently from 9bab3c9 to 160cfd7 Compare January 19, 2026 05:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pkg/cloudevents/clients/serviceaccount/codec.go`:
- Around line 54-61: TokenRequestCodec.Decode is missing a nil check for the evt
parameter which will cause a panic when calling evt.DataAs; add a guard at the
start of the Decode method to return a descriptive error if evt == nil (e.g.,
"nil event passed to TokenRequestCodec.Decode") before calling evt.DataAs on the
event to safely handle callers that pass nil.
🧹 Nitpick comments (4)
pkg/cloudevents/clients/serviceaccount/integration_test.go (3)

119-120: Consider replacing time.Sleep with a ready-signal mechanism.

Using time.Sleep(100 * time.Millisecond) to wait for server startup is fragile and can cause flaky tests on slow CI environments. Consider using a channel or polling mechanism to confirm the server is ready.

Example approach
// Instead of time.Sleep, verify connectivity:
conn, err := grpc.DialContext(ctx, lis.Addr().String(), grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(time.Second))
if err != nil {
    t.Fatalf("failed to connect: %v", err)
}
conn.Close()

200-203: Mutating package-level variable creates test isolation risk.

Modifying TokenRequestTimeout directly affects other tests running in parallel. While defer restores the value, consider using t.Parallel() avoidance or injecting timeout via constructor/options for better isolation.


262-292: Lenient assertion may mask reliability issues.

Requiring only 1 of 2 requests to succeed and logging failures as "timing issues" could hide genuine bugs. Consider making the test more deterministic or documenting why intermittent failures are expected.

pkg/cloudevents/clients/serviceaccount/client.go (1)

24-27: Exported mutable variable creates testing/concurrency concerns.

TokenRequestTimeout is exported and mutable, which can lead to race conditions if modified during concurrent requests. Consider using a functional option pattern or making it a field on ServiceAccountClient.

Alternative approach
-var (
-	// TokenRequestTimeout is the timeout for CreateToken requests
-	TokenRequestTimeout = 10 * time.Second
-)
+const defaultTokenRequestTimeout = 10 * time.Second

 type ServiceAccountClient struct {
 	grpcOptions *grpc.GRPCOptions
 	clusterName string
+	timeout     time.Duration
 }

Comment on lines +54 to +61
func (c *TokenRequestCodec) Decode(evt *cloudevents.Event) (*authenticationv1.TokenRequest, error) {
tokenRequest := &authenticationv1.TokenRequest{}
if err := evt.DataAs(tokenRequest); err != nil {
return nil, fmt.Errorf("failed to unmarshal event data: %w", err)
}

return tokenRequest, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add nil guard for evt parameter.

A past review flagged this concern and it was marked as addressed, but the current code shows no nil check. If evt is nil, evt.DataAs() will panic.

Suggested fix
 func (c *TokenRequestCodec) Decode(evt *cloudevents.Event) (*authenticationv1.TokenRequest, error) {
+	if evt == nil {
+		return nil, fmt.Errorf("event is nil")
+	}
 	tokenRequest := &authenticationv1.TokenRequest{}
 	if err := evt.DataAs(tokenRequest); err != nil {
 		return nil, fmt.Errorf("failed to unmarshal event data: %w", err)
 	}
 
 	return tokenRequest, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c *TokenRequestCodec) Decode(evt *cloudevents.Event) (*authenticationv1.TokenRequest, error) {
tokenRequest := &authenticationv1.TokenRequest{}
if err := evt.DataAs(tokenRequest); err != nil {
return nil, fmt.Errorf("failed to unmarshal event data: %w", err)
}
return tokenRequest, nil
}
func (c *TokenRequestCodec) Decode(evt *cloudevents.Event) (*authenticationv1.TokenRequest, error) {
if evt == nil {
return nil, fmt.Errorf("event is nil")
}
tokenRequest := &authenticationv1.TokenRequest{}
if err := evt.DataAs(tokenRequest); err != nil {
return nil, fmt.Errorf("failed to unmarshal event data: %w", err)
}
return tokenRequest, nil
}
🤖 Prompt for AI Agents
In `@pkg/cloudevents/clients/serviceaccount/codec.go` around lines 54 - 61,
TokenRequestCodec.Decode is missing a nil check for the evt parameter which will
cause a panic when calling evt.DataAs; add a guard at the start of the Decode
method to return a descriptive error if evt == nil (e.g., "nil event passed to
TokenRequestCodec.Decode") before calling evt.DataAs on the event to safely
handle callers that pass nil.

Signed-off-by: Wei Liu <liuweixa@redhat.com>
@skeeey

skeeey commented Jan 19, 2026

Copy link
Copy Markdown
Member Author

/assign @qiujian16 @elgnay

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@pkg/cloudevents/clients/serviceaccount/integration_test.go`:
- Around line 200-203: The test mutates the package-level TokenRequestTimeout
causing races; instead add an injectable timeout on ServiceAccountClient (e.g.,
a timeout field on ServiceAccountClient and an optional parameter to
NewServiceAccountClient that defaults to TokenRequestTimeout) and update the
code path that uses TokenRequestTimeout to read the client's timeout field; then
change the test to construct a ServiceAccountClient with the short timeout
rather than modifying TokenRequestTimeout. Ensure symbols updated:
TokenRequestTimeout (default), ServiceAccountClient.timeout (new), and
NewServiceAccountClient (new optional timeout parameter) and update tests to use
that constructor.

In `@pkg/server/grpc/server.go`:
- Around line 84-85: The code calls log.SetLogger(textlogger.NewLogger(...))
inside Run which mutates the global controller-runtime logger; remove that
global call and instead accept a configured logr.Logger (or an options struct)
into the server constructor/Run entrypoint and use that logger instance for
local logging; replace usages that rely on the global log with the injected
logger, and keep references to textlogger.NewLogger and textlogger.NewConfig
only where the application (main) constructs the logger before creating the
server.
♻️ Duplicate comments (1)
pkg/cloudevents/clients/serviceaccount/codec.go (1)

54-61: Missing nil guard for evt parameter.

The past review correctly identified that evt.DataAs() will panic if evt is nil. The code still lacks this guard.

Suggested fix
 func (c *TokenRequestCodec) Decode(evt *cloudevents.Event) (*authenticationv1.TokenRequest, error) {
+	if evt == nil {
+		return nil, fmt.Errorf("event is nil")
+	}
 	tokenRequest := &authenticationv1.TokenRequest{}
 	if err := evt.DataAs(tokenRequest); err != nil {
 		return nil, fmt.Errorf("failed to unmarshal event data: %w", err)
 	}
 
 	return tokenRequest, nil
 }
🧹 Nitpick comments (3)
pkg/cloudevents/clients/serviceaccount/client.go (1)

81-110: Consider consistent error wrapping.

Errors from BuildCloudEventsAgentOptions (line 98) and NewCloudEventAgentClient (line 109) are returned as-is, while other errors in this method are wrapped with errors.NewInternalError. Consider wrapping these for consistency.

Suggested fix
 	options, err := builder.BuildCloudEventsAgentOptions(
 		sa.grpcOptions,
 		sa.clusterName,
 		sa.clusterName,
 		TokenRequestDataType,
 	)
 	if err != nil {
-		return nil, err
+		return nil, errors.NewInternalError(err)
 	}

 	cloudEventsClient, err := clients.NewCloudEventAgentClient(
 		tokenRequestCtx,
 		options,
 		nil, // resync is disabled, so lister is not required
 		nil, // resync is disabled, so statusHashGetter is not required
 		&TokenRequestCodec{},
 	)
 	if err != nil {
-		return nil, err
+		return nil, errors.NewInternalError(err)
 	}
pkg/cloudevents/clients/serviceaccount/integration_test.go (2)

119-121: Consider replacing sleep with a ready signal.

Using time.Sleep to wait for server startup is a flaky pattern. Consider using a synchronization mechanism like a channel or polling for server readiness. This pattern repeats in other tests (lines 198, 254).

Example approach using a ready channel
ready := make(chan struct{})
go func() {
	close(ready) // Signal that goroutine started
	if err := grpcServer.Serve(lis); err != nil {
		t.Logf("server stopped: %v", err)
	}
}()
<-ready
// Optionally add a small dial check to confirm server is accepting connections

261-292: Weak assertion may mask flakiness.

Asserting "at least 1 successful request" out of 2 (line 290) is lenient and may hide intermittent failures. The comment at line 266 mentioning "connection reuse issues" suggests there might be underlying problems worth investigating. Consider either strengthening the assertion or documenting the expected behavior more explicitly.

Comment on lines +200 to +203
// Override timeout for this test
originalTimeout := TokenRequestTimeout
TokenRequestTimeout = 200 * time.Millisecond
defer func() { TokenRequestTimeout = originalTimeout }()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mutating package-level variable is not parallel-safe.

Modifying TokenRequestTimeout during the test will cause race conditions if tests run in parallel with t.Parallel(). While the defer restores the original value, concurrent tests could observe the modified value.

Alternative: Inject timeout via struct field or test option

Consider adding an optional timeout parameter to ServiceAccountClient or NewServiceAccountClient to allow test-specific overrides without mutating package state:

type ServiceAccountClient struct {
	grpcOptions *grpc.GRPCOptions
	clusterName string
	timeout     time.Duration // optional, defaults to TokenRequestTimeout
}
🤖 Prompt for AI Agents
In `@pkg/cloudevents/clients/serviceaccount/integration_test.go` around lines 200
- 203, The test mutates the package-level TokenRequestTimeout causing races;
instead add an injectable timeout on ServiceAccountClient (e.g., a timeout field
on ServiceAccountClient and an optional parameter to NewServiceAccountClient
that defaults to TokenRequestTimeout) and update the code path that uses
TokenRequestTimeout to read the client's timeout field; then change the test to
construct a ServiceAccountClient with the short timeout rather than modifying
TokenRequestTimeout. Ensure symbols updated: TokenRequestTimeout (default),
ServiceAccountClient.timeout (new), and NewServiceAccountClient (new optional
timeout parameter) and update tests to use that constructor.

Comment thread pkg/server/grpc/server.go
Comment on lines +84 to +85
// Set textlogger with verbosity level 4 for controller-runtime logging
log.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(4))))

@coderabbitai coderabbitai Bot Jan 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

controller-runtime log.SetLogger best practices library code main

💡 Result:

Recommended practices for controller-runtime logging (log.SetLogger)

Key rules

  • Call log.SetLogger exactly once in your program’s main (not in library code or init()). Libraries should not call SetLogger. [2][3]
  • Use controller-runtime’s zap wrapper (pkg/log/zap) and bind its flags so runtime configuration (level/encoder) is possible. [1]
  • Pass scoped logr.Logger values into libraries (or use logf.Log.WithName/WithValues inside packages) rather than setting global logger from a library. This keeps libraries testable and avoids surprising global state. [1][2]
  • Prefer structured key/value logging (WithValues) and avoid formatting messages with Sprintf. Don’t log an error and also return it from a reconciler (controller-runtime will handle logging in many cases). [1][4]

Minimal main.go pattern

  • bind flags, create zap logger with flag options, call log.SetLogger in main, then construct manager/controllers.

Example (concise):
package main

import (
"flag"
"os"

logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

)

func main() {
opts := zap.Options{}
opts.BindFlags(flag.CommandLine) // allow runtime config via flags
flag.Parse()

logger := zap.New(zap.UseFlagOptions(&opts))
logf.SetLogger(logger) // set global logger once, in main

// ... create manager, controllers, pass loggers to components ...
_ = os.Exit(0)

}

Practices for libraries/packages

  • Do NOT call log.SetLogger in init() or package-level code. Instead:
    • Use logf.Log.WithName("pkg") or accept a logr.Logger parameter: func NewController(l logr.Logger, ...) {...}.
    • Create scoped loggers via l.WithValues(...). [2][3]

Further reading / references

  • Operator SDK logging examples (zap binding & SetLogger in main). [1]
  • controller-runtime manager/docs: warning not to call SetLogger in init(). [2]
  • Logging message/key guidelines and avoiding double-logging. [4]

Sources
[1] Operator SDK — Logging (examples and zap flags).
[2] controller-runtime manager docs / pkg examples (don’t call SetLogger in init()).
[3] controller-runtime pkg documentation / usage patterns.
[4] Gardener logging guidelines (structured logging, WithValues, avoid double-logging).


🏁 Script executed:

# Find the Run method and its context in the server.go file
fd pkg/server/grpc/server.go -x cat -n {}

Repository: open-cluster-management-io/sdk-go

Length of output: 59


🏁 Script executed:

# Check if there's an options structure and what logging options exist
rg -B 5 -A 5 "type.*options" pkg/server/grpc/server.go

Repository: open-cluster-management-io/sdk-go

Length of output: 59


🏁 Script executed:

# Search for other SetLogger calls in the codebase to see patterns
rg "SetLogger" --type go

Repository: open-cluster-management-io/sdk-go

Length of output: 4545


Avoid setting the global controller-runtime logger inside Run.

Line 84-85 calls log.SetLogger in library code, which violates controller-runtime best practices. SetLogger is intended to be called exactly once in main only—not in library or package code. This globally overrides the host application's logging configuration and creates races if multiple tests or servers initialize logging. Libraries should instead accept a configured logr.Logger as a parameter or gate logging setup behind an explicit option.

Suggested adjustment
-// Set textlogger with verbosity level 4 for controller-runtime logging
-log.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(4))))
+// Expect caller to configure controller-runtime logging once (e.g., in main),
+// or gate it behind an explicit option.
+if b.options.ControllerRuntimeLogger != nil {
+	log.SetLogger(b.options.ControllerRuntimeLogger)
+}
🤖 Prompt for AI Agents
In `@pkg/server/grpc/server.go` around lines 84 - 85, The code calls
log.SetLogger(textlogger.NewLogger(...)) inside Run which mutates the global
controller-runtime logger; remove that global call and instead accept a
configured logr.Logger (or an options struct) into the server constructor/Run
entrypoint and use that logger instance for local logging; replace usages that
rely on the global log with the injected logger, and keep references to
textlogger.NewLogger and textlogger.NewConfig only where the application (main)
constructs the logger before creating the server.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this this fine to us, we don't use the controller-runtime to start the broker, only leverage its certwatcher

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skeeey, understood! Since you're only leveraging the certwatcher component and not using the full controller-runtime manager setup, the global logger configuration in this context makes sense for your use case. Thank you for the clarification.


✏️ Learnings added
Learnt from: skeeey
Repo: open-cluster-management-io/sdk-go PR: 189
File: pkg/server/grpc/server.go:84-85
Timestamp: 2026-01-19T07:13:02.082Z
Learning: In the open-cluster-management-io/sdk-go repository, setting log.SetLogger in pkg/server/grpc/server.go's Run method is acceptable because the codebase only uses controller-runtime's certwatcher component, not the full controller-runtime manager/controller setup.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@qiujian16 qiujian16 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/approve

@openshift-ci

openshift-ci Bot commented Jan 19, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: qiujian16, skeeey

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@elgnay

elgnay commented Jan 20, 2026

Copy link
Copy Markdown

/lgtm

@openshift-ci openshift-ci Bot added the lgtm label Jan 20, 2026
@openshift-merge-bot
openshift-merge-bot Bot merged commit 6d087c9 into open-cluster-management-io:main Jan 20, 2026
12 checks passed
@skeeey
skeeey deleted the token-req branch January 28, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants