Files
kandev/apps/backend/internal/github/factory.go
T

88 lines
2.5 KiB
Go

package github
import (
"context"
"fmt"
"os"
"go.uber.org/zap"
"github.com/kandev/kandev/internal/common/logger"
)
// SecretProvider is the interface the factory uses to look up a GitHub PAT.
type SecretProvider interface {
List(ctx context.Context) ([]*SecretListItem, error)
Reveal(ctx context.Context, id string) (string, error)
}
// SecretListItem mirrors secrets.SecretListItem to avoid a direct import cycle.
type SecretListItem struct {
ID string `json:"id"`
Name string `json:"name"`
HasValue bool `json:"has_value"`
}
// NewClient creates a GitHub client using the best available auth method.
// It tries the gh CLI first, then falls back to a PAT from the secret store.
func NewClient(ctx context.Context, secrets SecretProvider, log *logger.Logger) (Client, string, error) {
// Mock client for E2E testing
if os.Getenv("KANDEV_MOCK_GITHUB") == "true" {
log.Info("using mock client for GitHub integration")
return NewMockClient(), "mock", nil
}
// Try gh CLI first
if GHAvailable() {
ghClient := NewGHClient()
ok, err := ghClient.IsAuthenticated(ctx)
if err == nil && ok {
log.Info("using gh CLI for GitHub integration")
return ghClient, "gh_cli", nil
}
log.Debug("gh CLI available but not authenticated", zap.Error(err))
}
// Fall back to PAT from environment variable
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
log.Info("using GITHUB_TOKEN from environment for GitHub integration")
return NewPATClient(token), AuthMethodPAT, nil
}
if token := os.Getenv("GH_TOKEN"); token != "" {
log.Info("using GH_TOKEN from environment for GitHub integration")
return NewPATClient(token), AuthMethodPAT, nil
}
// Fall back to PAT from secrets store
if secrets != nil {
token, err := findGitHubPAT(ctx, secrets)
if err == nil && token != "" {
log.Info("using PAT from secrets store for GitHub integration")
return NewPATClient(token), AuthMethodPAT, nil
}
if err != nil {
log.Debug("failed to find GitHub PAT in secrets", zap.Error(err))
}
}
return &NoopClient{}, AuthMethodNone, nil
}
// findGitHubPAT looks for a secret named "GITHUB_TOKEN" or "github_token".
func findGitHubPAT(ctx context.Context, secrets SecretProvider) (string, error) {
items, err := secrets.List(ctx)
if err != nil {
return "", fmt.Errorf("list secrets: %w", err)
}
for _, item := range items {
if !item.HasValue {
continue
}
name := item.Name
if name == "GITHUB_TOKEN" || name == "github_token" {
return secrets.Reveal(ctx, item.ID)
}
}
return "", nil
}