package config
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/openeuler/Conch/pkg/ulog"
"gopkg.in/yaml.v3"
)
type Config struct {
App AppConfig `yaml:"app"`
Log LogConfig `yaml:"log"`
Server ServerConfig `yaml:"server"`
Network NetworkConfig `yaml:"network"`
Containerd ContainerdConfig `yaml:"containerd"`
Image ImageConfig `yaml:"image"`
Sandbox SandboxConfig `yaml:"sandbox"`
}
type AppConfig struct {
Name string `yaml:"name"`
}
type LogConfig struct {
Level string `yaml:"level"`
Output string `yaml:"output"`
}
type ServerConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
UnixSocket *string `yaml:"unix_socket"`
PIDFile string `yaml:"pid_file"`
WorkDir string `yaml:"work_dir"`
}
type NetworkConfig struct {
PoolSize int `yaml:"pool_size"`
DynamicReservation bool `yaml:"dynamic_reservation"`
TapIP string `yaml:"tap_ip"`
TapMask int `yaml:"tap_mask"`
}
type ContainerdConfig struct {
Socket string `yaml:"socket"`
DefaultNamespace string `yaml:"default_namespace"`
}
type ImageConfig struct {
DefaultKernelImage string `yaml:"default_kernel_image"`
}
const DefaultKernelImage = "hub.oepkgs.net/conch/kernel:6.6.0"
type SandboxConfig struct {
VsockSignalRetry time.Duration `yaml:"vsock_signal_retry"`
VsockSignalTimeout time.Duration `yaml:"vsock_signal_timeout"`
RequestTimeout time.Duration `yaml:"request_timeout"`
}
func DefaultConfig() *Config {
defaultUnixSocket := "/var/run/conchd/conchd.sock"
defaultPIDFile := "/var/run/conchd/conchd.pid"
defaultWorkDir := "/var/run/conch"
return &Config{
App: AppConfig{
Name: "conch",
},
Log: LogConfig{
Level: "debug",
Output: "stdout",
},
Server: ServerConfig{
Host: "127.0.0.1",
Port: 4063,
UnixSocket: &defaultUnixSocket,
PIDFile: defaultPIDFile,
WorkDir: defaultWorkDir,
},
Network: NetworkConfig{
PoolSize: 250,
DynamicReservation: false,
TapIP: "192.168.100.2",
TapMask: 24,
},
Containerd: ContainerdConfig{
Socket: "/run/containerd/containerd.sock",
DefaultNamespace: "default",
},
Image: ImageConfig{
DefaultKernelImage: DefaultKernelImage,
},
Sandbox: SandboxConfig{
VsockSignalRetry: 10 * time.Millisecond,
VsockSignalTimeout: 60 * time.Second,
RequestTimeout: 60 * time.Second,
},
}
}
func LoadConfig(configPath string) (*Config, error) {
if configPath == "" {
return DefaultConfig(), nil
}
data, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
return DefaultConfig(), nil
}
return nil, fmt.Errorf("failed to read config file: %w", err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
defaultCfg := DefaultConfig()
if cfg.App.Name == "" {
cfg.App.Name = defaultCfg.App.Name
}
if cfg.Log.Level == "" {
cfg.Log.Level = defaultCfg.Log.Level
}
if cfg.Log.Output == "" {
cfg.Log.Output = defaultCfg.Log.Output
}
if cfg.Server.Host == "" {
cfg.Server.Host = defaultCfg.Server.Host
}
if cfg.Server.Port == 0 {
cfg.Server.Port = defaultCfg.Server.Port
}
if cfg.Server.UnixSocket == nil {
cfg.Server.UnixSocket = defaultCfg.Server.UnixSocket
}
if cfg.Server.PIDFile == "" {
cfg.Server.PIDFile = defaultCfg.Server.PIDFile
}
if cfg.Server.WorkDir == "" {
cfg.Server.WorkDir = defaultCfg.Server.WorkDir
}
if cfg.Network.PoolSize == 0 {
cfg.Network.PoolSize = defaultCfg.Network.PoolSize
}
if cfg.Network.TapIP == "" {
cfg.Network.TapIP = defaultCfg.Network.TapIP
}
if cfg.Network.TapMask == 0 {
cfg.Network.TapMask = defaultCfg.Network.TapMask
}
if cfg.Containerd.Socket == "" {
cfg.Containerd.Socket = defaultCfg.Containerd.Socket
}
if cfg.Containerd.DefaultNamespace == "" {
cfg.Containerd.DefaultNamespace = defaultCfg.Containerd.DefaultNamespace
}
if cfg.Image.DefaultKernelImage == "" {
cfg.Image.DefaultKernelImage = defaultCfg.Image.DefaultKernelImage
}
if cfg.Sandbox.VsockSignalRetry == 0 {
cfg.Sandbox.VsockSignalRetry = defaultCfg.Sandbox.VsockSignalRetry
}
if cfg.Sandbox.VsockSignalTimeout == 0 {
cfg.Sandbox.VsockSignalTimeout = defaultCfg.Sandbox.VsockSignalTimeout
}
if cfg.Sandbox.RequestTimeout == 0 {
cfg.Sandbox.RequestTimeout = defaultCfg.Sandbox.RequestTimeout
}
return &cfg, nil
}
func (c *Config) GetLogConfig() (ulog.Config, error) {
level, err := parseLogLevel(c.Log.Level)
if err != nil {
return ulog.Config{}, fmt.Errorf("invalid log level: %w", err)
}
var stdout bool
var outputPath string
switch c.Log.Output {
case "stdout":
stdout = true
outputPath = ""
case "file":
stdout = false
outputPath = "/var/log/conchd/"
case "both":
stdout = true
outputPath = "/var/log/conchd/"
default:
return ulog.Config{}, fmt.Errorf("invalid log output mode: %s (must be stdout, file, or both)", c.Log.Output)
}
return ulog.Config{
Level: level,
OutputPath: outputPath,
Stdout: stdout,
}, nil
}
func (c *Config) GetServerAddress() string {
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
}
func (c *Config) GetServerUnixSocket() string {
if c == nil || c.Server.UnixSocket == nil {
return ""
}
return *c.Server.UnixSocket
}
func parseLogLevel(level string) (ulog.LogLevel, error) {
switch level {
case "debug":
return ulog.DebugLevel, nil
case "info":
return ulog.InfoLevel, nil
case "warn":
return ulog.WarnLevel, nil
case "error":
return ulog.ErrorLevel, nil
case "fatal":
return ulog.FatalLevel, nil
default:
return 0, fmt.Errorf("unknown log level: %s", level)
}
}
func FindConfigFile() string {
locations := []string{
"/etc/conch/config.yaml",
"config/config.yaml",
}
for _, loc := range locations {
if _, err := os.Stat(loc); err == nil {
absPath, _ := filepath.Abs(loc)
return absPath
}
}
return ""
}