feat: Implement configuration management and DNS provider integration
- Added configuration management using Viper in internal/config/config.go - Implemented ClientConfig, ServerConfig, TLSConfig, HetznerConfig, UpstreamConfig, and main Config struct. - Created LoadConfig function to read and validate configuration files. - Developed Hetzner DNS provider in internal/provider/hetzner/hetzner.go with methods for updating DNS records. - Added comprehensive unit tests for configuration loading and Hetzner provider functionality. - Established HTTP server with metrics and update endpoint in internal/server/server.go. - Implemented request handling, authorization, and error management in the server. - Created integration tests for the Hetzner provider API interactions. - Removed legacy dynamic DNS integration tests in favor of the new API-based approach.
This commit is contained in:
62
internal/config/config.go
Normal file
62
internal/config/config.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
Exact []string `mapstructure:"exact"`
|
||||
Wildcard []string `mapstructure:"wildcard"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
BindAddress string `mapstructure:"bind_address"`
|
||||
TLS TLSConfig `mapstructure:"tls"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
CertFile string `mapstructure:"cert_file"`
|
||||
KeyFile string `mapstructure:"key_file"`
|
||||
}
|
||||
|
||||
type HetznerConfig struct {
|
||||
APIToken string `mapstructure:"api_token"`
|
||||
}
|
||||
|
||||
type UpstreamConfig struct {
|
||||
Provider string `mapstructure:"provider"`
|
||||
Hetzner HetznerConfig `mapstructure:"hetzner"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Upstream UpstreamConfig `mapstructure:"upstream"`
|
||||
Clients map[string]ClientConfig `mapstructure:"clients"`
|
||||
}
|
||||
|
||||
// LoadConfig reads the file at path (yaml, json, toml) into Config and validates it.
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigFile(path)
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("reading config: %w", err)
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("parsing config: %w", err)
|
||||
}
|
||||
|
||||
for name, client := range cfg.Clients {
|
||||
if len(client.Exact) == 0 && len(client.Wildcard) == 0 {
|
||||
return nil, fmt.Errorf("client %q must have at least one of exact or wildcard", name)
|
||||
}
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
64
internal/config/config_test.go
Normal file
64
internal/config/config_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.cloonar.com/cloonar/updns/internal/config"
|
||||
)
|
||||
|
||||
func TestLoadConfig_Success(t *testing.T) {
|
||||
content := `
|
||||
server:
|
||||
bind_address: ":9090"
|
||||
tls:
|
||||
enabled: true
|
||||
cert_file: "cert.pem"
|
||||
key_file: "key.pem"
|
||||
upstream:
|
||||
provider: hetzner
|
||||
hetzner:
|
||||
api_token: "token123"
|
||||
clients:
|
||||
clientA:
|
||||
secret: "sec"
|
||||
exact:
|
||||
- "foo.com"
|
||||
wildcard:
|
||||
- "bar.com"
|
||||
`
|
||||
tmp := filepath.Join(os.TempDir(), "config_test.yaml")
|
||||
if err := os.WriteFile(tmp, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to write temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tmp)
|
||||
|
||||
cfg, err := config.LoadConfig(tmp)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if cfg.Server.BindAddress != ":9090" {
|
||||
t.Errorf("expected bind_address :9090, got %s", cfg.Server.BindAddress)
|
||||
}
|
||||
if !cfg.Server.TLS.Enabled {
|
||||
t.Error("expected TLS enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_Failure(t *testing.T) {
|
||||
content := `
|
||||
clients:
|
||||
clientB:
|
||||
secret: "sec"
|
||||
`
|
||||
tmp := filepath.Join(os.TempDir(), "config_fail.yaml")
|
||||
if err := os.WriteFile(tmp, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to write temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tmp)
|
||||
|
||||
if _, err := config.LoadConfig(tmp); err == nil {
|
||||
t.Fatal("expected error for missing fields, got nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user