feat: init
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
hcl "github.com/hashicorp/hcl/v2/hclsimple"
|
||||
)
|
||||
|
||||
const (
|
||||
CSIModeAll = "all"
|
||||
CSIModeController = "controller"
|
||||
CSIModeNode = "node"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Driver *DriverConf `hcl:"driver,block"`
|
||||
Client *ClientConf `hcl:"client,block"`
|
||||
}
|
||||
|
||||
type DriverConf struct {
|
||||
Name string `hcl:"name,optional"`
|
||||
Endpoint string `hcl:"endpoint,optional"`
|
||||
NodeID string `hcl:"node_id,optional"`
|
||||
Mode string `hcl:"mode,optional"`
|
||||
}
|
||||
|
||||
type ClientConf struct {
|
||||
ServerURL string `hcl:"server_url,optional"`
|
||||
AccessToken string `hcl:"access_token,optional"`
|
||||
Token string `hcl:"token,optional"`
|
||||
TimeoutSeconds int `hcl:"timeout_seconds,optional"`
|
||||
}
|
||||
|
||||
func Load(filename string) (*Config, error) {
|
||||
cfg := &Config{}
|
||||
if err := hcl.DecodeFile(filename, nil, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cfg.normalize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// LoadClient loads driver config from file and/or env vars used by the Helm chart.
|
||||
func LoadClient(filename string) (*Config, error) {
|
||||
var cfg *Config
|
||||
if filename != "" {
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
loaded, err := Load(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg = loaded
|
||||
}
|
||||
}
|
||||
if cfg == nil {
|
||||
cfg = &Config{
|
||||
Driver: &DriverConf{},
|
||||
Client: &ClientConf{},
|
||||
}
|
||||
if err := cfg.normalize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ApplyEnvOverrides(cfg)
|
||||
ApplyClientTimeoutFromEnv(cfg)
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
if c.Client == nil || strings.TrimSpace(c.Client.ServerURL) == "" {
|
||||
return fmt.Errorf("client.server_url is required")
|
||||
}
|
||||
if strings.TrimSpace(c.Client.BearerToken()) == "" {
|
||||
return fmt.Errorf("client.access_token is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) normalize() error {
|
||||
if c.Driver == nil {
|
||||
c.Driver = &DriverConf{}
|
||||
}
|
||||
if c.Client == nil {
|
||||
c.Client = &ClientConf{}
|
||||
}
|
||||
|
||||
if c.Driver.Name == "" {
|
||||
c.Driver.Name = "storage.csi.kloud.team"
|
||||
}
|
||||
if c.Driver.Endpoint == "" {
|
||||
c.Driver.Endpoint = "unix:///var/lib/kubelet/plugins/storage.csi.kloud.team/csi.sock"
|
||||
}
|
||||
if c.Driver.NodeID == "" {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return fmt.Errorf("driver.node_id is empty and hostname lookup failed: %w", err)
|
||||
}
|
||||
c.Driver.NodeID = hostname
|
||||
}
|
||||
if c.Driver.Mode == "" {
|
||||
c.Driver.Mode = CSIModeAll
|
||||
}
|
||||
|
||||
mode := strings.ToLower(strings.TrimSpace(c.Driver.Mode))
|
||||
switch mode {
|
||||
case CSIModeAll, CSIModeController, CSIModeNode:
|
||||
c.Driver.Mode = mode
|
||||
default:
|
||||
return fmt.Errorf("driver.mode must be one of: all, controller, node")
|
||||
}
|
||||
|
||||
c.Client.ServerURL = strings.TrimRight(strings.TrimSpace(c.Client.ServerURL), "/")
|
||||
if c.Client.TimeoutSeconds <= 0 {
|
||||
c.Client.TimeoutSeconds = 30
|
||||
}
|
||||
if strings.TrimSpace(c.Client.AccessToken) == "" && strings.TrimSpace(c.Client.Token) != "" {
|
||||
c.Client.AccessToken = strings.TrimSpace(c.Client.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientConf) Timeout() time.Duration {
|
||||
return time.Duration(c.TimeoutSeconds) * time.Second
|
||||
}
|
||||
|
||||
func (c *ClientConf) BearerToken() string {
|
||||
if strings.TrimSpace(c.AccessToken) != "" {
|
||||
return strings.TrimSpace(c.AccessToken)
|
||||
}
|
||||
return strings.TrimSpace(c.Token)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadNormalizesClientDefaults(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "csi.hcl")
|
||||
if err := os.WriteFile(path, []byte(`client {
|
||||
server_url = "http://192.168.84.10:9766"
|
||||
access_token = "cluster-token"
|
||||
}`), 0o600); err != nil {
|
||||
t.Fatalf("write config: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
|
||||
if cfg.Driver.Name != "storage.csi.kloud.team" {
|
||||
t.Fatalf("unexpected driver name: %q", cfg.Driver.Name)
|
||||
}
|
||||
if cfg.Client.ServerURL != "http://192.168.84.10:9766" {
|
||||
t.Fatalf("unexpected client server url: %q", cfg.Client.ServerURL)
|
||||
}
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Fatalf("Validate failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRequiresAccessToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &Config{
|
||||
Driver: &DriverConf{},
|
||||
Client: &ClientConf{ServerURL: "http://127.0.0.1:9766"},
|
||||
}
|
||||
if err := cfg.Validate(); err == nil {
|
||||
t.Fatal("expected error for missing access token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAcceptsLegacyTokenField(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := &Config{
|
||||
Driver: &DriverConf{},
|
||||
Client: &ClientConf{
|
||||
ServerURL: "http://127.0.0.1:9766",
|
||||
Token: "legacy-token",
|
||||
},
|
||||
}
|
||||
if err := cfg.normalize(); err != nil {
|
||||
t.Fatalf("normalize failed: %v", err)
|
||||
}
|
||||
if cfg.Client.AccessToken != "legacy-token" {
|
||||
t.Fatalf("expected legacy token migration, got %q", cfg.Client.AccessToken)
|
||||
}
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Fatalf("Validate failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyEnvOverrides(t *testing.T) {
|
||||
t.Setenv(envCSIServerURL, "http://csi.example:9766")
|
||||
t.Setenv(envCSIAccessToken, "cluster-token")
|
||||
t.Setenv(envCSIDriverMode, "node")
|
||||
t.Setenv(envCSINodeID, "worker-1")
|
||||
|
||||
cfg := &Config{
|
||||
Driver: &DriverConf{},
|
||||
Client: &ClientConf{},
|
||||
}
|
||||
ApplyEnvOverrides(cfg)
|
||||
|
||||
if cfg.Client.ServerURL != "http://csi.example:9766" {
|
||||
t.Fatalf("unexpected server url: %q", cfg.Client.ServerURL)
|
||||
}
|
||||
if cfg.Client.AccessToken != "cluster-token" {
|
||||
t.Fatalf("unexpected token: %q", cfg.Client.AccessToken)
|
||||
}
|
||||
if cfg.Driver.Mode != CSIModeNode {
|
||||
t.Fatalf("unexpected mode: %q", cfg.Driver.Mode)
|
||||
}
|
||||
if cfg.Driver.NodeID != "worker-1" {
|
||||
t.Fatalf("unexpected node id: %q", cfg.Driver.NodeID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
envCSIServerURL = "KKS_CSI_SERVER_URL"
|
||||
envCSIAccessToken = "KKS_CSI_ACCESS_TOKEN"
|
||||
envCSIDriverMode = "KKS_CSI_DRIVER_MODE"
|
||||
envCSINodeID = "KKS_CSI_NODE_ID"
|
||||
envCSIDriverName = "KKS_CSI_DRIVER_NAME"
|
||||
envCSIDriverEndpoint = "KKS_CSI_DRIVER_ENDPOINT"
|
||||
)
|
||||
|
||||
// ApplyEnvOverrides applies environment variables used by the Helm chart.
|
||||
func ApplyEnvOverrides(cfg *Config) {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
if cfg.Driver == nil {
|
||||
cfg.Driver = &DriverConf{}
|
||||
}
|
||||
if cfg.Client == nil {
|
||||
cfg.Client = &ClientConf{}
|
||||
}
|
||||
|
||||
if v := strings.TrimSpace(os.Getenv(envCSIServerURL)); v != "" {
|
||||
cfg.Client.ServerURL = strings.TrimRight(v, "/")
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv(envCSIAccessToken)); v != "" {
|
||||
cfg.Client.AccessToken = v
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv(envCSIDriverMode)); v != "" {
|
||||
cfg.Driver.Mode = strings.ToLower(v)
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv(envCSINodeID)); v != "" {
|
||||
cfg.Driver.NodeID = v
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv(envCSIDriverName)); v != "" {
|
||||
cfg.Driver.Name = v
|
||||
}
|
||||
if v := strings.TrimSpace(os.Getenv(envCSIDriverEndpoint)); v != "" {
|
||||
cfg.Driver.Endpoint = v
|
||||
}
|
||||
|
||||
if cfg.Client.TimeoutSeconds <= 0 {
|
||||
cfg.Client.TimeoutSeconds = 30
|
||||
}
|
||||
if cfg.Driver.Mode == "" {
|
||||
cfg.Driver.Mode = CSIModeNode
|
||||
}
|
||||
if cfg.Driver.Name == "" {
|
||||
cfg.Driver.Name = "storage.csi.kloud.team"
|
||||
}
|
||||
if cfg.Driver.Endpoint == "" {
|
||||
cfg.Driver.Endpoint = "unix:///var/lib/kubelet/plugins/storage.csi.kloud.team/csi.sock"
|
||||
}
|
||||
}
|
||||
|
||||
func ApplyClientTimeoutFromEnv(cfg *Config) {
|
||||
if cfg == nil || cfg.Client == nil {
|
||||
return
|
||||
}
|
||||
raw := strings.TrimSpace(os.Getenv("KKS_CSI_CLIENT_TIMEOUT_SECONDS"))
|
||||
if raw == "" {
|
||||
return
|
||||
}
|
||||
seconds, err := strconv.Atoi(raw)
|
||||
if err != nil || seconds <= 0 {
|
||||
return
|
||||
}
|
||||
cfg.Client.TimeoutSeconds = seconds
|
||||
}
|
||||
Reference in New Issue
Block a user