88 lines
2.5 KiB
Go
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
|
|
}
|