Documentation
¶
Overview ¶
Package github provides a generic, reusable GitHub client for interacting with the GitHub API. It abstracts the underlying SDK and exposes only domain types to prevent SDK leakage.
Index ¶
- type Client
- func (c *Client) AddLabel(ctx context.Context, repo Repository, number int, label string) error
- func (c *Client) AddLabelToPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
- func (c *Client) GetFile(ctx context.Context, repo Repository, path string, ref string) (*File, error)
- func (c *Client) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)
- func (c *Client) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)
- func (c *Client) ListFiles(ctx context.Context, repo Repository, path string, ref string) ([]string, error)
- func (c *Client) ListPRFiles(ctx context.Context, repo Repository, number int) ([]PRFile, error)
- func (c *Client) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)
- func (c *Client) ListReviews(ctx context.Context, repo Repository, number int) ([]Review, error)
- func (c *Client) ListStatuses(ctx context.Context, repo Repository, ref string) ([]Status, error)
- func (c *Client) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error
- func (c *Client) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error
- func (c *Client) RemoveLabelFromPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
- func (c *Client) RemoveLabelsWithPrefixBatched(ctx context.Context, repo Repository, prNumbers []int, prefix string, ...) error
- type ClientConfig
- type File
- type InstrumentedClient
- func (ic *InstrumentedClient) AddLabel(ctx context.Context, repo Repository, number int, label string) error
- func (ic *InstrumentedClient) AddLabelToPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
- func (ic *InstrumentedClient) GetFile(ctx context.Context, repo Repository, path string, ref string) (*File, error)
- func (ic *InstrumentedClient) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)
- func (ic *InstrumentedClient) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)
- func (ic *InstrumentedClient) ListFiles(ctx context.Context, repo Repository, path string, ref string) ([]string, error)
- func (ic *InstrumentedClient) ListPRFiles(ctx context.Context, repo Repository, number int) ([]PRFile, error)
- func (ic *InstrumentedClient) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)
- func (ic *InstrumentedClient) ListReviews(ctx context.Context, repo Repository, number int) ([]Review, error)
- func (ic *InstrumentedClient) ListStatuses(ctx context.Context, repo Repository, ref string) ([]Status, error)
- func (ic *InstrumentedClient) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error
- func (ic *InstrumentedClient) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error
- func (ic *InstrumentedClient) RemoveLabelFromPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
- func (ic *InstrumentedClient) RemoveLabelsWithPrefixBatched(ctx context.Context, repo Repository, prNumbers []int, prefix string, ...) error
- type Label
- type Logger
- type MockClient
- func (m *MockClient) AddLabel(ctx context.Context, repo Repository, number int, label string) error
- func (m *MockClient) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)
- func (m *MockClient) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)
- func (m *MockClient) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)
- func (m *MockClient) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error
- func (m *MockClient) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error
- type PRFile
- type PullRequest
- type PullRequestState
- type PullRequestWebhookPayload
- type Repository
- type Review
- type Status
- type WebhookEvent
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps the GitHub SDK and exposes domain-specific operations. This is a pointer type (holds state, manages HTTP client lifecycle).
func NewClient ¶
func NewClient(cfg ClientConfig) *Client
NewClient creates a new GitHub client with the provided configuration. The HTTPClient should be configured with authentication (e.g., via AuthTransport).
Example:
httpClient := httpclient.NewClient().WithToken("ghp_...")
cfg := github.ClientConfig{
BaseURL: "https://api.github.com",
HTTPClient: httpClient,
Logger: log,
}
client := github.NewClient(cfg)
Example ¶
ExampleNewClient demonstrates how to create a GitHub client with authentication.
package main
import (
"github.com/grhili/cd-operator/pkg/github"
"github.com/grhili/cd-operator/pkg/httpclient"
"github.com/grhili/cd-operator/pkg/logger"
)
func main() {
// Create HTTP client with GitHub token
token := "ghp_xxxxxxxxxxxx"
httpClient := httpclient.NewClient().
WithAuthToken(httpclient.Token(token))
// Create logger
log, _ := logger.New(logger.Config{
Level: "info",
Format: "json",
})
// Create GitHub client
cfg := github.ClientConfig{
BaseURL: "https://api.github.com",
HTTPClient: httpClient,
Logger: log,
}
client := github.NewClient(cfg)
_ = client // Use client for API operations
}
Output:
func (*Client) AddLabel ¶
AddLabel adds a label to a pull request. Creates the label if it doesn't exist in the repository.
Example ¶
ExampleClient_AddLabel demonstrates how to add a label to a pull request.
package main
import (
"context"
"fmt"
"github.com/grhili/cd-operator/pkg/github"
)
const (
testRepoOwner = "grhili"
testRepoName = "cd-operator"
)
func main() {
// Setup (omitted for brevity)
var client *github.Client
ctx := context.Background()
repo := github.Repository{
Owner: testRepoOwner,
Name: testRepoName,
}
err := client.AddLabel(ctx, repo, 42, "bug")
if err != nil {
fmt.Printf("failed to add label: %v\n", err)
return
}
fmt.Println("Label added successfully")
}
Output:
func (*Client) AddLabelToPRsBatched ¶
func (c *Client) AddLabelToPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
AddLabelToPRsBatched adds a label to multiple PRs in a single batch operation. This is much more efficient than calling AddLabel for each PR individually. For N PRs, this makes 1 API call instead of N calls.
func (*Client) GetFile ¶
func (c *Client) GetFile(ctx context.Context, repo Repository, path string, ref string) (*File, error)
GetFile retrieves a file from a repository at a specific commit/branch/tag reference. Returns the file content as a domain File object.
func (*Client) GetPR ¶
func (c *Client) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)
GetPR retrieves a specific pull request by number. Returns nil if the PR does not exist.
Example ¶
ExampleClient_GetPR demonstrates how to get a specific pull request.
package main
import (
"context"
"fmt"
"github.com/grhili/cd-operator/pkg/github"
)
const (
testRepoOwner = "grhili"
testRepoName = "cd-operator"
fmtPRLog = "PR #%d: %s\n"
)
func main() {
// Setup (omitted for brevity)
var client *github.Client
ctx := context.Background()
repo := github.Repository{
Owner: testRepoOwner,
Name: testRepoName,
}
pr, err := client.GetPR(ctx, repo, 42)
if err != nil {
fmt.Printf("failed to get PR: %v\n", err)
return
}
fmt.Printf(fmtPRLog, pr.Number, pr.Title)
fmt.Printf("State: %s\n", pr.State)
fmt.Printf("Head SHA: %s\n", pr.HeadSHA)
fmt.Printf("Base Branch: %s\n", pr.BaseBranch)
}
Output:
func (*Client) GetPRFiles ¶
GetPRFiles retrieves all files changed in a pull request. Returns a slice of File domain objects with their content.
func (*Client) ListFiles ¶
func (c *Client) ListFiles(ctx context.Context, repo Repository, path string, ref string) ([]string, error)
ListFiles lists all files in a directory at a specific commit/branch/tag reference. Returns a slice of file paths (not content).
func (*Client) ListPRFiles ¶
ListPRFiles retrieves all files modified in a pull request. Returns a slice of PRFile domain objects.
func (*Client) ListPRs ¶
func (c *Client) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)
ListPRs retrieves all open pull requests for the specified repository. Returns a slice of PullRequest domain objects, never GitHub SDK types.
Example ¶
ExampleClient_ListPRs demonstrates how to list pull requests.
package main
import (
"context"
"fmt"
"github.com/grhili/cd-operator/pkg/github"
)
const (
testRepoOwner = "grhili"
testRepoName = "cd-operator"
fmtPRLog = "PR #%d: %s\n"
)
func main() {
// Setup (omitted for brevity)
var client *github.Client
ctx := context.Background()
// List all open pull requests
repo := github.Repository{
Owner: testRepoOwner,
Name: testRepoName,
}
prs, err := client.ListPRs(ctx, repo)
if err != nil {
fmt.Printf("failed to list PRs: %v\n", err)
return
}
for _, pr := range prs {
fmt.Printf(fmtPRLog, pr.Number, pr.Title)
}
}
Output:
func (*Client) ListReviews ¶
ListReviews retrieves all reviews for a pull request. Returns a slice of Review domain objects.
func (*Client) ListStatuses ¶
ListStatuses retrieves all status checks for a given ref (commit SHA or branch name). Returns a slice of Status domain objects.
func (*Client) MergePR ¶
MergePR merges a pull request using the specified commit message. Returns an error if the merge fails (e.g., conflicts, not mergeable).
Example ¶
ExampleClient_MergePR demonstrates how to merge a pull request.
package main
import (
"context"
"fmt"
"github.com/grhili/cd-operator/pkg/github"
)
const (
testRepoOwner = "grhili"
testRepoName = "cd-operator"
)
func main() {
// Setup (omitted for brevity)
var client *github.Client
ctx := context.Background()
repo := github.Repository{
Owner: testRepoOwner,
Name: testRepoName,
}
err := client.MergePR(ctx, repo, 42, "Merge pull request #42")
if err != nil {
fmt.Printf("failed to merge PR: %v\n", err)
return
}
fmt.Println("PR merged successfully")
}
Output:
func (*Client) RemoveLabel ¶
RemoveLabel removes a label from a pull request. Returns an error if the label doesn't exist on the PR.
func (*Client) RemoveLabelFromPRsBatched ¶
func (c *Client) RemoveLabelFromPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
RemoveLabelFromPRsBatched removes a label from multiple PRs in a single batch operation. This is much more efficient than calling RemoveLabel for each PR individually. For N PRs, this makes 1 API call instead of N calls.
func (*Client) RemoveLabelsWithPrefixBatched ¶
func (c *Client) RemoveLabelsWithPrefixBatched(ctx context.Context, repo Repository, prNumbers []int, prefix string, excludeLabels []string) error
RemoveLabelsWithPrefixBatched removes all labels with a given prefix from multiple PRs. For example, prefix "deployment:" removes "deployment:success", "deployment:failure", etc. excludeLabels specifies labels that should not be removed.
type ClientConfig ¶
type ClientConfig struct {
// BaseURL is the GitHub API base URL (default: https://api.github.com).
BaseURL string
// HTTPClient is the HTTP client to use for requests.
HTTPClient *httpclient.Client
// Logger is the logger instance for structured logging.
Logger Logger
}
ClientConfig holds configuration for creating a GitHub client. This is a value type (immutable configuration).
type File ¶
type File struct {
// Path is the file path within the repository.
Path string
// Content is the file content (base64 decoded).
Content string
}
File represents a file in a GitHub repository. This is a pointer type (Content can be large).
type InstrumentedClient ¶
type InstrumentedClient struct {
// contains filtered or unexported fields
}
InstrumentedClient wraps a GitHub Client with Prometheus metrics instrumentation. All operations are recorded with timing, success/failure counts, and rate limit tracking.
This follows the decorator pattern to add observability without modifying the core client.
func NewInstrumentedClient ¶
func NewInstrumentedClient(client *Client, metrics *observability.ComponentMetrics) *InstrumentedClient
NewInstrumentedClient creates a new GitHub client with metrics instrumentation.
Example:
client := github.NewClient(cfg) instrumentedClient := github.NewInstrumentedClient(client, metrics)
func (*InstrumentedClient) AddLabel ¶
func (ic *InstrumentedClient) AddLabel(ctx context.Context, repo Repository, number int, label string) error
AddLabel adds a label to a pull request with metrics recording.
func (*InstrumentedClient) AddLabelToPRsBatched ¶
func (ic *InstrumentedClient) AddLabelToPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
AddLabelToPRsBatched adds a label to multiple PRs with metrics recording.
func (*InstrumentedClient) GetFile ¶
func (ic *InstrumentedClient) GetFile(ctx context.Context, repo Repository, path string, ref string) (*File, error)
GetFile retrieves a file from a repository with metrics recording.
func (*InstrumentedClient) GetPR ¶
func (ic *InstrumentedClient) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)
GetPR retrieves a specific pull request with metrics recording.
func (*InstrumentedClient) GetPRFiles ¶
func (ic *InstrumentedClient) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)
GetPRFiles retrieves all files changed in a pull request with metrics recording.
func (*InstrumentedClient) ListFiles ¶
func (ic *InstrumentedClient) ListFiles(ctx context.Context, repo Repository, path string, ref string) ([]string, error)
ListFiles lists all files in a directory with metrics recording.
func (*InstrumentedClient) ListPRFiles ¶
func (ic *InstrumentedClient) ListPRFiles(ctx context.Context, repo Repository, number int) ([]PRFile, error)
ListPRFiles retrieves all files modified in a pull request with metrics recording.
func (*InstrumentedClient) ListPRs ¶
func (ic *InstrumentedClient) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)
ListPRs retrieves all open pull requests with metrics recording.
func (*InstrumentedClient) ListReviews ¶
func (ic *InstrumentedClient) ListReviews(ctx context.Context, repo Repository, number int) ([]Review, error)
ListReviews retrieves all reviews for a pull request with metrics recording.
func (*InstrumentedClient) ListStatuses ¶
func (ic *InstrumentedClient) ListStatuses(ctx context.Context, repo Repository, ref string) ([]Status, error)
ListStatuses retrieves all status checks for a given ref with metrics recording.
func (*InstrumentedClient) MergePR ¶
func (ic *InstrumentedClient) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error
MergePR merges a pull request with metrics recording.
func (*InstrumentedClient) RemoveLabel ¶
func (ic *InstrumentedClient) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error
RemoveLabel removes a label from a pull request with metrics recording.
func (*InstrumentedClient) RemoveLabelFromPRsBatched ¶
func (ic *InstrumentedClient) RemoveLabelFromPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error
RemoveLabelFromPRsBatched removes a label from multiple PRs with metrics recording.
func (*InstrumentedClient) RemoveLabelsWithPrefixBatched ¶
func (ic *InstrumentedClient) RemoveLabelsWithPrefixBatched(ctx context.Context, repo Repository, prNumbers []int, prefix string, excludeLabels []string) error
RemoveLabelsWithPrefixBatched removes all labels with a given prefix from multiple PRs with metrics recording.
type Label ¶
type Label struct {
// Name is the label name.
Name string
// Color is the label color (hex code without #).
Color string
}
Label represents a GitHub label. This is a value type (small, immutable).
type Logger ¶
type Logger interface {
Debug(msg string, fields ...zap.Field)
Info(msg string, fields ...zap.Field)
Warn(msg string, fields ...zap.Field)
Error(msg string, fields ...zap.Field)
}
Logger defines the logging interface used by the GitHub client.
type MockClient ¶
type MockClient struct {
// ListPRsFunc is the mock function for ListPRs.
ListPRsFunc func(ctx context.Context, repo Repository) ([]PullRequest, error)
// GetPRFunc is the mock function for GetPR.
GetPRFunc func(ctx context.Context, repo Repository, number int) (*PullRequest, error)
// GetPRFilesFunc is the mock function for GetPRFiles.
GetPRFilesFunc func(ctx context.Context, repo Repository, number int) ([]File, error)
// MergePRFunc is the mock function for MergePR.
MergePRFunc func(ctx context.Context, repo Repository, number int, commitMsg string) error
// AddLabelFunc is the mock function for AddLabel.
AddLabelFunc func(ctx context.Context, repo Repository, number int, label string) error
// RemoveLabelFunc is the mock function for RemoveLabel.
RemoveLabelFunc func(ctx context.Context, repo Repository, number int, label string) error
// contains filtered or unexported fields
}
MockClient is a mock implementation of the GitHub client for testing. It allows tests to simulate GitHub API responses without making real HTTP calls.
func NewMockClient ¶
func NewMockClient(log Logger) *MockClient
NewMockClient creates a new mock GitHub client with no-op implementations. Tests should override specific functions as needed.
Example ¶
ExampleMockClient demonstrates how to use the mock client in tests.
package main
import (
"context"
"fmt"
"github.com/grhili/cd-operator/pkg/github"
"github.com/grhili/cd-operator/pkg/logger"
)
const fmtPRLog = "PR #%d: %s\n"
func main() {
log := logger.NewFakeLogger() // Use fake logger to avoid output
mock := github.NewMockClient(log)
// Override specific methods for testing
mock.GetPRFunc = func(ctx context.Context, repo github.Repository, number int) (*github.PullRequest, error) {
return &github.PullRequest{
Number: number,
Title: "Test PR",
State: github.PullRequestStateOpen,
}, nil
}
// Use mock in tests
ctx := context.Background()
pr, _ := mock.GetPR(ctx, github.Repository{Owner: "test", Name: "repo"}, 42)
fmt.Printf(fmtPRLog, pr.Number, pr.Title)
}
Output: PR #42: Test PR
func (*MockClient) AddLabel ¶
func (m *MockClient) AddLabel(ctx context.Context, repo Repository, number int, label string) error
AddLabel calls the mock AddLabelFunc.
func (*MockClient) GetPR ¶
func (m *MockClient) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)
GetPR calls the mock GetPRFunc.
func (*MockClient) GetPRFiles ¶
func (m *MockClient) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)
GetPRFiles calls the mock GetPRFilesFunc.
func (*MockClient) ListPRs ¶
func (m *MockClient) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)
ListPRs calls the mock ListPRsFunc.
func (*MockClient) MergePR ¶
func (m *MockClient) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error
MergePR calls the mock MergePRFunc.
func (*MockClient) RemoveLabel ¶
func (m *MockClient) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error
RemoveLabel calls the mock RemoveLabelFunc.
type PRFile ¶
type PRFile struct {
// Filename is the file path.
Filename string
// Status is the file change status (added, modified, removed, renamed).
Status string
// Additions is the number of lines added.
Additions int
// Deletions is the number of lines deleted.
Deletions int
// Changes is the total number of changes (additions + deletions).
Changes int
// Patch is the unified diff patch.
Patch string
// Size is the file size in bytes.
Size int64
}
PRFile represents a file modified in a pull request. This is a value type (immutable).
type PullRequest ¶
type PullRequest struct {
// ID is the unique pull request ID.
ID int64
// Number is the PR number within the repository.
Number int
// Owner is the repository owner (user or organization).
Owner string
// Repo is the repository name.
Repo string
// Title is the PR title.
Title string
// HeadSHA is the commit SHA at the head of the PR branch.
HeadSHA string
// BaseBranch is the target branch name.
BaseBranch string
// State is the current state of the PR (open, closed, merged).
State PullRequestState
// Labels are the labels attached to the PR.
Labels []Label
// CreatedAt is the timestamp when the PR was created.
CreatedAt time.Time
// UpdatedAt is the timestamp when the PR was last updated.
UpdatedAt time.Time
// Mergeable indicates whether the PR can be merged (nil if unknown).
Mergeable *bool
// MergeableState describes the mergeable state (e.g., "clean", "unstable", "dirty").
MergeableState string
}
PullRequest represents a GitHub pull request with essential fields. This is a pointer type (expensive to copy, often mutated).
type PullRequestState ¶
type PullRequestState string
PullRequestState represents the state of a pull request.
const ( // PullRequestStateOpen indicates the PR is open and can be modified. PullRequestStateOpen PullRequestState = "open" // PullRequestStateClosed indicates the PR is closed but not merged. PullRequestStateClosed PullRequestState = "closed" // PullRequestStateMerged indicates the PR is closed and merged. PullRequestStateMerged PullRequestState = "merged" )
type PullRequestWebhookPayload ¶
type PullRequestWebhookPayload struct {
// Action is the action performed (e.g., "opened", "closed", "synchronize").
Action string
// PullRequest is the pull request that triggered the event.
PullRequest PullRequest
// Repository is the repository where the event occurred.
Repository Repository
}
PullRequestWebhookPayload represents a pull_request webhook event.
type Repository ¶
type Repository struct {
// Owner is the repository owner (user or organization).
Owner string
// Name is the repository name.
Name string
}
Repository represents a GitHub repository identifier. This is a value type (immutable, cheap to copy).
type Review ¶
type Review struct {
// ID is the unique review ID.
ID int64
// User is the GitHub username of the reviewer.
User string
// State is the review state (APPROVED, CHANGES_REQUESTED, COMMENTED).
State string
// Body is the review comment body.
Body string
// SubmittedAt is when the review was submitted.
SubmittedAt time.Time
}
Review represents a GitHub pull request review. This is a value type (immutable).
type Status ¶
type Status struct {
// State is the status state (success, failure, error, pending).
State string
// Context is the status context (identifies the check).
Context string
// Description is a human-readable description of the status.
Description string
// TargetURL is the URL to the detailed status information.
TargetURL string
// CreatedAt is when the status was created.
CreatedAt time.Time
// UpdatedAt is when the status was last updated.
UpdatedAt time.Time
}
Status represents a GitHub commit status check. This is a value type (immutable).
type WebhookEvent ¶
type WebhookEvent struct {
// Type is the event type (e.g., "pull_request", "push", "issues").
Type string
// DeliveryID is the unique delivery ID for this webhook.
DeliveryID string
// Payload is the parsed event payload as a domain-specific type.
Payload interface{}
}
WebhookEvent represents a parsed GitHub webhook event with metadata.
func ParseWebhook ¶
func ParseWebhook(req *http.Request, secret string) (*WebhookEvent, error)
ParseWebhook parses and validates a GitHub webhook request. It verifies the signature, extracts the event type, and parses the payload. Returns a WebhookEvent with the parsed payload or an error if validation fails.
Example:
event, err := github.ParseWebhook(req, "my-webhook-secret")
if err != nil {
return err
}
switch payload := event.Payload.(type) {
case *PullRequestWebhookPayload:
// Handle pull request event
}
Example ¶
ExampleParseWebhook demonstrates how to parse a GitHub webhook.
package main
import ()
func main() {
// In a real HTTP handler:
// event, err := github.ParseWebhook(req, "webhook-secret")
// if err != nil {
// http.Error(w, "Invalid webhook", http.StatusBadRequest)
// return
// }
// Handle different event types
// switch payload := event.Payload.(type) {
// case *github.PullRequestWebhookPayload:
// fmt.Printf("PR #%d %s\n", payload.PullRequest.Number, payload.Action)
// default:
// fmt.Printf("Unhandled event type: %s\n", event.Type)
// }
}
Output: