From 19045dfed5f81e05d029578a13152c1d3dff9429 Mon Sep 17 00:00:00 2001 From: JulienBreux <964330+JulienBreux@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:17:00 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Implement=20gRPC=20clie?= =?UTF-8?q?nt=20caching=20for=20Cloud=20Run=20APIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This optimization implements a thread-safe lazy-initialization and caching pattern for GCP clients across all resource types (services, jobs, worker pools, domain mappings, and executions). Previously, a new GCP client was created and closed for every single API call. By caching these clients, we reuse the underlying gRPC/REST connections and avoid redundant credential discovery and authentication overhead. Measurable impact: - Significant latency reduction for multi-region operations (e.g., listing services across all 24+ regions). - Reduced load on the Google Cloud Authentication service. - Smoother TUI experience during rapid navigation. Tests: - Package-specific tests for each client. - Full project test suite (make test). - Manual verification of TUI responsiveness. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/bolt.md | 0 internal/run/api/domainmapping/client.go | 29 ++++++++++-- internal/run/api/job/client.go | 42 +++++++++++------ internal/run/api/job/execution/execution.go | 32 ++++++++++--- internal/run/api/service/client.go | 52 +++++++++++---------- internal/run/api/workerpool/client.go | 52 +++++++++++---------- 6 files changed, 135 insertions(+), 72 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..e69de29 diff --git a/internal/run/api/domainmapping/client.go b/internal/run/api/domainmapping/client.go index 8f94962..e54bba6 100644 --- a/internal/run/api/domainmapping/client.go +++ b/internal/run/api/domainmapping/client.go @@ -19,6 +19,7 @@ package domainmapping import ( "context" "fmt" + "sync" "github.com/JulienBreux/run-cli/internal/run/api/client" "golang.org/x/oauth2/google" @@ -59,10 +60,21 @@ type Client interface { } // GCPClient is the Google Cloud Platform implementation of the Client interface. -type GCPClient struct{} +type GCPClient struct { + mu sync.Mutex + cachedClient DomainMappingsClientWrapper // Optimization: Cache client to reuse gRPC connections +} + +// getOrCreateClient returns a cached client or creates a new one if it doesn't exist. +// This reduces latency by avoiding repeated authentication and connection overhead. +func (c *GCPClient) getOrCreateClient(ctx context.Context) (DomainMappingsClientWrapper, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.cachedClient != nil { + return c.cachedClient, nil + } -// ListDomainMappings lists domain mappings for a given project and region. -func (c *GCPClient) ListDomainMappings(ctx context.Context, project, region string) ([]*run.DomainMapping, error) { creds, err := client.FindDefaultCredentials(ctx, run.CloudPlatformScope) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) @@ -73,6 +85,17 @@ func (c *GCPClient) ListDomainMappings(ctx context.Context, project, region stri return nil, fmt.Errorf("failed to create domain mappings client: %w", err) } + c.cachedClient = dmClient + return c.cachedClient, nil +} + +// ListDomainMappings lists domain mappings for a given project and region. +func (c *GCPClient) ListDomainMappings(ctx context.Context, project, region string) ([]*run.DomainMapping, error) { + dmClient, err := c.getOrCreateClient(ctx) + if err != nil { + return nil, err + } + parent := fmt.Sprintf("projects/%s/locations/%s", project, region) var domainMappings []*run.DomainMapping diff --git a/internal/run/api/job/client.go b/internal/run/api/job/client.go index c7467e9..8183f8d 100644 --- a/internal/run/api/job/client.go +++ b/internal/run/api/job/client.go @@ -19,6 +19,7 @@ package job import ( "context" "fmt" + "sync" run "cloud.google.com/go/run/apiv2" "cloud.google.com/go/run/apiv2/runpb" @@ -98,10 +99,21 @@ type Client interface { var _ Client = (*GCPClient)(nil) // GCPClient is the Google Cloud Platform implementation of Client. -type GCPClient struct{} +type GCPClient struct { + mu sync.Mutex + cachedClient JobsClientWrapper // Optimization: Cache client to reuse gRPC connections +} + +// getOrCreateClient returns a cached client or creates a new one if it doesn't exist. +// This reduces latency by avoiding repeated authentication and connection overhead. +func (c *GCPClient) getOrCreateClient(ctx context.Context) (JobsClientWrapper, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.cachedClient != nil { + return c.cachedClient, nil + } -// ListJobs lists jobs for a project and region. -func (c *GCPClient) ListJobs(ctx context.Context, project, region string) ([]*runpb.Job, error) { creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) @@ -111,9 +123,17 @@ func (c *GCPClient) ListJobs(ctx context.Context, project, region string) ([]*ru if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() + + c.cachedClient = cClient + return c.cachedClient, nil +} + +// ListJobs lists jobs for a project and region. +func (c *GCPClient) ListJobs(ctx context.Context, project, region string) ([]*runpb.Job, error) { + cClient, err := c.getOrCreateClient(ctx) + if err != nil { + return nil, err + } req := &runpb.ListJobsRequest{ Parent: fmt.Sprintf("projects/%s/locations/%s", project, region), @@ -137,18 +157,10 @@ func (c *GCPClient) ListJobs(ctx context.Context, project, region string) ([]*ru // RunJob runs a job. func (c *GCPClient) RunJob(ctx context.Context, name string) (*runpb.Execution, error) { - creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) - if err != nil { - return nil, fmt.Errorf("failed to find default credentials: %w", err) - } - - cClient, err := createJobsClient(ctx, option.WithCredentials(creds)) + cClient, err := c.getOrCreateClient(ctx) if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() op, err := cClient.RunJob(ctx, &runpb.RunJobRequest{Name: name}) if err != nil { diff --git a/internal/run/api/job/execution/execution.go b/internal/run/api/job/execution/execution.go index 1f5b8d4..01ea4d3 100644 --- a/internal/run/api/job/execution/execution.go +++ b/internal/run/api/job/execution/execution.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "strings" + "sync" run "cloud.google.com/go/run/apiv2" "cloud.google.com/go/run/apiv2/runpb" @@ -99,10 +100,21 @@ type Client interface { } // GCPClient is the Google Cloud Platform implementation of Client. -type GCPClient struct{} +type GCPClient struct { + mu sync.Mutex + cachedClient ExecutionsClientWrapper // Optimization: Cache client to reuse gRPC connections +} + +// getOrCreateClient returns a cached client or creates a new one if it doesn't exist. +// This reduces latency by avoiding repeated authentication and connection overhead. +func (c *GCPClient) getOrCreateClient(ctx context.Context) (ExecutionsClientWrapper, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.cachedClient != nil { + return c.cachedClient, nil + } -// ListExecutions lists executions for a project, region and job. -func (c *GCPClient) ListExecutions(ctx context.Context, project, region, jobName string) ([]*runpb.Execution, error) { creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) @@ -112,9 +124,17 @@ func (c *GCPClient) ListExecutions(ctx context.Context, project, region, jobName if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() + + c.cachedClient = cClient + return c.cachedClient, nil +} + +// ListExecutions lists executions for a project, region and job. +func (c *GCPClient) ListExecutions(ctx context.Context, project, region, jobName string) ([]*runpb.Execution, error) { + cClient, err := c.getOrCreateClient(ctx) + if err != nil { + return nil, err + } // Filter by job name // The parent is the location. We filter by label or just iterate and filter? diff --git a/internal/run/api/service/client.go b/internal/run/api/service/client.go index b2346fb..022c3ff 100644 --- a/internal/run/api/service/client.go +++ b/internal/run/api/service/client.go @@ -19,6 +19,7 @@ package service import ( "context" "fmt" + "sync" run "cloud.google.com/go/run/apiv2" "cloud.google.com/go/run/apiv2/runpb" @@ -105,10 +106,21 @@ type Client interface { var _ Client = (*GCPClient)(nil) // GCPClient is the Google Cloud Platform implementation of Client. -type GCPClient struct{} +type GCPClient struct { + mu sync.Mutex + cachedClient ServicesClientWrapper // Optimization: Cache client to reuse gRPC connections +} + +// getOrCreateClient returns a cached client or creates a new one if it doesn't exist. +// This reduces latency by avoiding repeated authentication and connection overhead. +func (c *GCPClient) getOrCreateClient(ctx context.Context) (ServicesClientWrapper, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.cachedClient != nil { + return c.cachedClient, nil + } -// ListServices lists services for a project and region. -func (c *GCPClient) ListServices(ctx context.Context, project, region string) ([]*runpb.Service, error) { creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) @@ -118,9 +130,17 @@ func (c *GCPClient) ListServices(ctx context.Context, project, region string) ([ if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() + + c.cachedClient = cClient + return c.cachedClient, nil +} + +// ListServices lists services for a project and region. +func (c *GCPClient) ListServices(ctx context.Context, project, region string) ([]*runpb.Service, error) { + cClient, err := c.getOrCreateClient(ctx) + if err != nil { + return nil, err + } req := &runpb.ListServicesRequest{ Parent: fmt.Sprintf("projects/%s/locations/%s", project, region), @@ -144,36 +164,20 @@ func (c *GCPClient) ListServices(ctx context.Context, project, region string) ([ // GetService gets a single service. func (c *GCPClient) GetService(ctx context.Context, name string) (*runpb.Service, error) { - creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) - if err != nil { - return nil, fmt.Errorf("failed to find default credentials: %w", err) - } - - cClient, err := createServicesClient(ctx, option.WithCredentials(creds)) + cClient, err := c.getOrCreateClient(ctx) if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() return cClient.GetService(ctx, &runpb.GetServiceRequest{Name: name}) } // UpdateService updates a service. func (c *GCPClient) UpdateService(ctx context.Context, service *runpb.Service) (*runpb.Service, error) { - creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) - if err != nil { - return nil, fmt.Errorf("failed to find default credentials: %w", err) - } - - cClient, err := createServicesClient(ctx, option.WithCredentials(creds)) + cClient, err := c.getOrCreateClient(ctx) if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() op, err := cClient.UpdateService(ctx, &runpb.UpdateServiceRequest{Service: service}) if err != nil { diff --git a/internal/run/api/workerpool/client.go b/internal/run/api/workerpool/client.go index 109d959..37c1bf5 100644 --- a/internal/run/api/workerpool/client.go +++ b/internal/run/api/workerpool/client.go @@ -19,6 +19,7 @@ package workerpool import ( "context" "fmt" + "sync" run "cloud.google.com/go/run/apiv2" "cloud.google.com/go/run/apiv2/runpb" @@ -104,10 +105,21 @@ type Client interface { var _ Client = (*GCPClient)(nil) // GCPClient is the Google Cloud Platform implementation of Client. -type GCPClient struct{} +type GCPClient struct { + mu sync.Mutex + cachedClient WorkerPoolsClientWrapper // Optimization: Cache client to reuse gRPC connections +} + +// getOrCreateClient returns a cached client or creates a new one if it doesn't exist. +// This reduces latency by avoiding repeated authentication and connection overhead. +func (c *GCPClient) getOrCreateClient(ctx context.Context) (WorkerPoolsClientWrapper, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.cachedClient != nil { + return c.cachedClient, nil + } -// ListWorkerPools lists worker pools for a project and region. -func (c *GCPClient) ListWorkerPools(ctx context.Context, project, region string) ([]*runpb.WorkerPool, error) { creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) @@ -117,9 +129,17 @@ func (c *GCPClient) ListWorkerPools(ctx context.Context, project, region string) if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() + + c.cachedClient = cClient + return c.cachedClient, nil +} + +// ListWorkerPools lists worker pools for a project and region. +func (c *GCPClient) ListWorkerPools(ctx context.Context, project, region string) ([]*runpb.WorkerPool, error) { + cClient, err := c.getOrCreateClient(ctx) + if err != nil { + return nil, err + } req := &runpb.ListWorkerPoolsRequest{ Parent: fmt.Sprintf("projects/%s/locations/%s", project, region), @@ -143,36 +163,20 @@ func (c *GCPClient) ListWorkerPools(ctx context.Context, project, region string) // GetWorkerPool gets a worker pool. func (c *GCPClient) GetWorkerPool(ctx context.Context, name string) (*runpb.WorkerPool, error) { - creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) - if err != nil { - return nil, fmt.Errorf("failed to find default credentials: %w", err) - } - - cClient, err := createWorkerPoolsClient(ctx, option.WithCredentials(creds)) + cClient, err := c.getOrCreateClient(ctx) if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() return cClient.GetWorkerPool(ctx, &runpb.GetWorkerPoolRequest{Name: name}) } // UpdateWorkerPool updates a worker pool. func (c *GCPClient) UpdateWorkerPool(ctx context.Context, workerPool *runpb.WorkerPool) (*runpb.WorkerPool, error) { - creds, err := client.FindDefaultCredentials(ctx, run.DefaultAuthScopes()...) - if err != nil { - return nil, fmt.Errorf("failed to find default credentials: %w", err) - } - - cClient, err := createWorkerPoolsClient(ctx, option.WithCredentials(creds)) + cClient, err := c.getOrCreateClient(ctx) if err != nil { return nil, err } - defer func() { - _ = cClient.Close() - }() op, err := cClient.UpdateWorkerPool(ctx, &runpb.UpdateWorkerPoolRequest{WorkerPool: workerPool}) if err != nil { From 97c9c510ec99355cc530edd090f534ac70a53295 Mon Sep 17 00:00:00 2001 From: JulienBreux <964330+JulienBreux@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:23:08 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Implement=20gRPC=20clie?= =?UTF-8?q?nt=20caching=20and=20fix=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces two main changes: 1. Performance Optimization: Implemented a thread-safe lazy-initialization and caching pattern for GCP clients across all resource types. This significantly reduces latency by reusing gRPC connections and avoiding repeated authentication overhead. 2. CI Fix: Updated the goreleaser workflow to use the --snapshot flag when running on pull requests, preventing failures due to missing git tags or mismatched commits. All changes have been verified with the full test suite and code review. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 380067b..348df19 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: with: distribution: goreleaser version: "~> v2" - args: release --clean + args: release --clean ${{ github.event_name == 'pull_request' && '--snapshot' || '' }} env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GH_TOKEN }}