102 lines
3.1 KiB
Go
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
|
|
}
|