From c8198cc102846713ba0b2bd9093aed47791f9a7c Mon Sep 17 00:00:00 2001 From: "Yuhei Nakayama(gon)" Date: Fri, 19 Dec 2025 01:14:38 +0900 Subject: [PATCH 1/4] feat(client): add caching for GetSeats API to reduce rate limit errors Cache the /seats/ API response for the lifetime of the client instance, avoiding redundant API calls during a single terraform apply run. Cache is automatically invalidated after AssignSeat/UnassignSeat operations. --- internal/client/client.go | 40 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/internal/client/client.go b/internal/client/client.go index c6cccdd..59ba464 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -7,6 +7,7 @@ import ( "io" "math" "net/http" + "sync" "time" ) @@ -35,6 +36,11 @@ type Client struct { GitHubToken string HTTPClient *http.Client RetryConfig RetryConfig + + // Cache for seats response (valid for single terraform run) + seatsCache *SeatsResponse + seatsCacheMu sync.RWMutex + seatsCacheOnce sync.Once } // NewClient creates a new CodeRabbit API client @@ -234,8 +240,26 @@ func (c *Client) GetGitUserID(githubID string) (string, error) { return "", fmt.Errorf("GitHub API request failed after %d retries: %w", c.RetryConfig.MaxRetries, lastErr) } -// GetSeats retrieves all seat assignments +// GetSeats retrieves all seat assignments (cached for the lifetime of the client) func (c *Client) GetSeats() (*SeatsResponse, error) { + // Check cache first with read lock + c.seatsCacheMu.RLock() + if c.seatsCache != nil { + cached := c.seatsCache + c.seatsCacheMu.RUnlock() + return cached, nil + } + c.seatsCacheMu.RUnlock() + + // Fetch from API with write lock + c.seatsCacheMu.Lock() + defer c.seatsCacheMu.Unlock() + + // Double-check after acquiring write lock + if c.seatsCache != nil { + return c.seatsCache, nil + } + respBody, err := c.doRequest(http.MethodGet, "/seats/", nil) if err != nil { return nil, err @@ -246,9 +270,17 @@ func (c *Client) GetSeats() (*SeatsResponse, error) { return nil, fmt.Errorf("failed to unmarshal response: %w", err) } + c.seatsCache = &seats return &seats, nil } +// InvalidateSeatsCache clears the seats cache, forcing a fresh fetch on next GetSeats call +func (c *Client) InvalidateSeatsCache() { + c.seatsCacheMu.Lock() + defer c.seatsCacheMu.Unlock() + c.seatsCache = nil +} + // AssignSeat assigns a seat to a user func (c *Client) AssignSeat(gitUserID string) error { reqBody := AssignSeatRequest{GitUserID: gitUserID} @@ -266,6 +298,9 @@ func (c *Client) AssignSeat(gitUserID string) error { return fmt.Errorf("seat assignment failed") } + // Invalidate cache since seat state changed + c.InvalidateSeatsCache() + return nil } @@ -286,6 +321,9 @@ func (c *Client) UnassignSeat(gitUserID string) error { return fmt.Errorf("seat unassignment failed") } + // Invalidate cache since seat state changed + c.InvalidateSeatsCache() + return nil } From a57150d06c64929a1a6fd4c6217ebe20a4753297 Mon Sep 17 00:00:00 2001 From: "Yuhei Nakayama(gon)" Date: Fri, 19 Dec 2025 01:16:36 +0900 Subject: [PATCH 2/4] chore: add .gitignore for build artifacts --- .gitignore | 1 + internal/client/client.go | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..049acc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +terraform-provider-coderabbit diff --git a/internal/client/client.go b/internal/client/client.go index 59ba464..5b5ae01 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -38,9 +38,8 @@ type Client struct { RetryConfig RetryConfig // Cache for seats response (valid for single terraform run) - seatsCache *SeatsResponse - seatsCacheMu sync.RWMutex - seatsCacheOnce sync.Once + seatsCache *SeatsResponse + seatsCacheMu sync.RWMutex } // NewClient creates a new CodeRabbit API client From 794aa7a79c65a1f4214d3de3bb55b0611e510d0a Mon Sep 17 00:00:00 2001 From: "Yuhei Nakayama(gon)" Date: Fri, 19 Dec 2025 01:18:15 +0900 Subject: [PATCH 3/4] chore: add lint target to Makefile for golangci-lint --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 709c88d..d1b568f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build install test clean +.PHONY: build install test clean lint HOSTNAME=registry.terraform.io NAMESPACE=coderabbitai @@ -27,6 +27,9 @@ fmt: vet: go vet ./... +lint: + golangci-lint run + .PHONY: deps deps: go mod tidy From d0c5c968427b27c377c62c992d8138f10640d68c Mon Sep 17 00:00:00 2001 From: "Yuhei Nakayama(gon)" Date: Fri, 19 Dec 2025 01:19:58 +0900 Subject: [PATCH 4/4] fix: suppress errcheck warnings for resp.Body.Close() --- internal/client/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index 5b5ae01..7094558 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -158,7 +158,7 @@ func (c *Client) doRequest(method, path string, body any) ([]byte, error) { } respBody, err := io.ReadAll(resp.Body) - resp.Body.Close() + _ = resp.Body.Close() if err != nil { lastErr = fmt.Errorf("failed to read response body: %w", err) continue @@ -209,7 +209,7 @@ func (c *Client) GetGitUserID(githubID string) (string, error) { } respBody, err := io.ReadAll(resp.Body) - resp.Body.Close() + _ = resp.Body.Close() if err != nil { lastErr = fmt.Errorf("failed to read GitHub API response: %w", err) continue