Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Bolt's Journal

## 2026-03-09 - GCP ID Token Credentials Caching & Context Stability
**Learning:** Discovered that every invocation of `GetIDToken` triggered full Google application-default credentials discovery. This involves file I/O (credential files parsing) and GCP Metadata Server HTTP checks, adding up to ~300ms overhead on every ID token request. Caching `google.Credentials` using a thread-safe pattern (`sync.Mutex` with a package-level variable) completely eliminates this overhead on subsequent calls. Crucially, passing `context.Background()` during credentials discovery prevents transient, canceled request contexts from invalidating or closing the cached credentials' network transports.
**Action:** Always lazily initialize and cache GCP default credentials (`google.Credentials`) inside identity token clients using a thread-safe mutex and robust context initialization.

## 2026-03-08 - GCP Logging Client Caching & Connection Longevity
**Learning:** Establishing the GCP Stackdriver Logging client requires repeated Google credential discovery and connection establishment, causing high latency (~300ms) inside a reactive TUI interface. Caching `logadmin.Client` instances via a project-aware map with thread-safe `sync.Mutex` ensures subsequent streaming and log extraction operations are instantaneous. Crucially, calling `Close()` on individual stream terminations must be a no-op to prevent premature teardown of connection pools shared across other active streaming views.
**Action:** Keep GCP Logging clients cached globally by project and handle connection termination via a no-op `Close` method, while adding test-isolation resets in unit tests.
Expand Down
23 changes: 20 additions & 3 deletions internal/run/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os/user"
"path/filepath"
"strings"
"sync"

api_region "github.com/JulienBreux/run-cli/internal/run/api/region"
"github.com/JulienBreux/run-cli/internal/run/model/common/info"
Expand All @@ -36,6 +37,11 @@ var (
"openid",
"email",
}

// Variables for dependency injection and caching of credentials
findDefaultCredentials = google.FindDefaultCredentials
idTokenCreds *google.Credentials
idTokenCredsMu sync.Mutex
)

func getConfigDir() (string, error) {
Expand Down Expand Up @@ -131,11 +137,22 @@ func parseConfig(path string) (info.Info, error) {
}

// GetIDToken retrieves an identity token for the given audience using Google Cloud credentials.
// It uses a thread-safe lazy-initialization and caching pattern via sync.Mutex and package-level
// variables to prevent repeated, high-latency credential discovery (~300ms overhead per call).
var GetIDToken = func(ctx context.Context) (string, error) {
creds, err := google.FindDefaultCredentials(ctx, scopes...)
if err != nil {
return "", fmt.Errorf("failed to find default credentials: %w", err)
idTokenCredsMu.Lock()
if idTokenCreds == nil {
// Use context.Background() during credential discovery to prevent transient request context
// cancellations from closing or invalidating the cached credentials.
creds, err := findDefaultCredentials(context.Background(), scopes...)
if err != nil {
idTokenCredsMu.Unlock()
return "", fmt.Errorf("failed to find default credentials: %w", err)
}
idTokenCreds = creds
}
creds := idTokenCreds
idTokenCredsMu.Unlock()

token, err := creds.TokenSource.Token()
if err != nil {
Expand Down
77 changes: 77 additions & 0 deletions internal/run/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ limitations under the License.
package auth

import (
"context"
"os"
"path/filepath"
"sync"
"testing"
"time"

api_region "github.com/JulienBreux/run-cli/internal/run/api/region"
"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)

func TestGetInfo(t *testing.T) {
Expand Down Expand Up @@ -133,3 +139,74 @@ func TestGetInfo_Defaults(t *testing.T) {
t.Errorf("Expected default Region 'all', got '%s'", info.Region)
}
}

type mockTokenSource struct {
token *oauth2.Token
err error
}

func (m *mockTokenSource) Token() (*oauth2.Token, error) {
return m.token, m.err
}

func TestGetIDToken_CachingAndThreadSafety(t *testing.T) {
// Backup and restore package-level variables
origFindDefaultCredentials := findDefaultCredentials
origIdTokenCreds := idTokenCreds
defer func() {
findDefaultCredentials = origFindDefaultCredentials
idTokenCreds = origIdTokenCreds
}()

// Reset cached credentials
idTokenCreds = nil

var callCount int
var mu sync.Mutex

// Mock token source that returns a valid ID token
mockTok := &oauth2.Token{
AccessToken: "mock-access-token",
Expiry: time.Now().Add(1 * time.Hour),
}
mockTok = mockTok.WithExtra(map[string]interface{}{
"id_token": "cached-id-token-xyz",
})

// Mock findDefaultCredentials
findDefaultCredentials = func(ctx context.Context, scopes ...string) (*google.Credentials, error) {
mu.Lock()
callCount++
mu.Unlock()
return &google.Credentials{
TokenSource: &mockTokenSource{token: mockTok},
}, nil
}

ctx := context.Background()

// Call concurrently to verify thread safety and that it's only discovered once
var wg sync.WaitGroup
numGoroutines := 10
results := make([]string, numGoroutines)
errors := make([]error, numGoroutines)

for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
token, err := GetIDToken(ctx)
results[index] = token
errors[index] = err
}(i)
}
wg.Wait()

// Assertions
assert.Equal(t, 1, callCount, "findDefaultCredentials should only be called once due to caching")

for i := 0; i < numGoroutines; i++ {
assert.NoError(t, errors[i])
assert.Equal(t, "cached-id-token-xyz", results[i])
}
}
Loading