dev-pod-api-build/internal/k8s/client.go
2026-04-16 04:16:36 +00:00

102 lines
3.1 KiB
Go

package k8s
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// Client wraps a Kubernetes clientset with config used for template generation.
type Client struct {
Clientset kubernetes.Interface
Dynamic dynamic.Interface
Config Config
}
// Config holds cluster-specific settings for pod template generation.
type Config struct {
Domain string // e.g. "spinoff.dev"
Registry string // e.g. "10.22.0.56:30500"
GoldenImage string // e.g. "dev-golden:v2"
VPNGatewayNS string // e.g. "claw-system"
VPNGatewaySecret string // e.g. "vpn-gateway-secrets"
AnthropicKey string // baseroute anthropic key
OpenAIKey string // baseroute openai key
ForgejoURL string // e.g. "http://forgejo.dev-infra.svc:3000"
DevPodAPIURL string // e.g. "http://dev-pod-api.dev-infra.svc:8080"
}
// ConfigFromEnv reads Config from environment variables with defaults.
func ConfigFromEnv() Config {
return Config{
Domain: envOrDefault("DOMAIN", "spinoff.dev"),
Registry: envOrDefault("REGISTRY", "10.22.0.56:30500"),
GoldenImage: envOrDefault("GOLDEN_IMAGE", "dev-golden:v2"),
VPNGatewayNS: envOrDefault("VPN_GATEWAY_NS", "claw-system"),
VPNGatewaySecret: envOrDefault("VPN_GATEWAY_SECRET", "vpn-gateway-secrets"),
AnthropicKey: os.Getenv("DEFAULT_ANTHROPIC_KEY"),
OpenAIKey: os.Getenv("DEFAULT_OPENAI_KEY"),
ForgejoURL: envOrDefault("FORGEJO_URL", "http://forgejo.dev-infra.svc:3000"),
DevPodAPIURL: envOrDefault("DEV_POD_API_URL", "http://dev-pod-api.dev-infra.svc:8080"),
}
}
// NewClient creates a Kubernetes client using in-cluster config,
// falling back to kubeconfig for local development.
func NewClient(cfg Config) (*Client, error) {
restCfg, err := rest.InClusterConfig()
if err != nil {
slog.Info("in-cluster config not available, falling back to kubeconfig")
restCfg, err = kubeconfigFromDefault()
if err != nil {
return nil, fmt.Errorf("create k8s config: %w", err)
}
}
clientset, err := kubernetes.NewForConfig(restCfg)
if err != nil {
return nil, fmt.Errorf("create k8s clientset: %w", err)
}
dyn, err := dynamic.NewForConfig(restCfg)
if err != nil {
return nil, fmt.Errorf("create dynamic client: %w", err)
}
return &Client{
Clientset: clientset,
Dynamic: dyn,
Config: cfg,
}, nil
}
// NewClientWithClientset creates a Client with pre-configured clients (for testing).
func NewClientWithClientset(clientset kubernetes.Interface, dyn dynamic.Interface, cfg Config) *Client {
return &Client{
Clientset: clientset,
Dynamic: dyn,
Config: cfg,
}
}
func kubeconfigFromDefault() (*rest.Config, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("get home dir: %w", err)
}
kubeconfig := filepath.Join(home, ".kube", "config")
return clientcmd.BuildConfigFromFlags("", kubeconfig)
}
func envOrDefault(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}