/* * Copyright 2020 The Dragonfly Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package config import ( "errors" "fmt" "time" "d7y.io/dragonfly/v2/cmd/dependency/base" "d7y.io/dragonfly/v2/pkg/net/ip" "d7y.io/dragonfly/v2/pkg/objectstorage" "d7y.io/dragonfly/v2/pkg/rpc" "d7y.io/dragonfly/v2/pkg/slices" "d7y.io/dragonfly/v2/pkg/types" ) type Config struct { // Base options. base.Options `yaml:",inline" mapstructure:",squash"` // Server configuration. Server *ServerConfig `yaml:"server" mapstructure:"server"` // Database configuration. Database *DatabaseConfig `yaml:"database" mapstructure:"database"` // Cache configuration. Cache *CacheConfig `yaml:"cache" mapstructure:"cache"` // ObjectStorage configuration. ObjectStorage *ObjectStorageConfig `yaml:"objectStorage" mapstructure:"objectStorage"` // Metrics configuration. Metrics *MetricsConfig `yaml:"metrics" mapstructure:"metrics"` // Security configuration. Security *SecurityConfig `yaml:"security" mapstructure:"security"` } type ServerConfig struct { // Server name. Name string `yaml:"name" mapstructure:"name"` // Server dynamic config cache directory. CacheDir string `yaml:"cacheDir" mapstructure:"cacheDir"` // Server log directory. LogDir string `yaml:"logDir" mapstructure:"logDir"` // GRPC server configuration. GRPC *GRPCConfig `yaml:"grpc" mapstructure:"grpc"` // REST server configuration. REST *RestConfig `yaml:"rest" mapstructure:"rest"` } type DatabaseConfig struct { // Database type. Type string `yaml:"type" mapstructure:"type"` // Mysql configuration. Mysql *MysqlConfig `yaml:"mysql" mapstructure:"mysql"` // Postgres configuration. Postgres *PostgresConfig `yaml:"postgres" mapstructure:"postgres"` // Redis configuration. Redis *RedisConfig `yaml:"redis" mapstructure:"redis"` } type MysqlConfig struct { // Server username. User string `yaml:"user" mapstructure:"user"` // Server password. Password string `yaml:"password" mapstructure:"password"` // Server host. Host string `yaml:"host" mapstructure:"host"` // Server port. Port int `yaml:"port" mapstructure:"port"` // Server DB name. DBName string `yaml:"dbname" mapstructure:"dbname"` // TLS mode (can be one of "true", "false", "skip-verify", or "preferred"). TLSConfig string `yaml:"tlsConfig" mapstructure:"tlsConfig"` // Custom TLS configuration (overrides "TLSConfig" setting above). TLS *TLSConfig `yaml:"tls" mapstructure:"tls"` // Enable migration. Migrate bool `yaml:"migrate" mapstructure:"migrate"` } type TLSConfig struct { // Client certificate file path. Cert string `yaml:"cert" mapstructure:"cert"` // Client key file path. Key string `yaml:"key" mapstructure:"key"` // CA file path. CA string `yaml:"ca" mapstructure:"ca"` // InsecureSkipVerify controls whether a client verifies the // server's certificate chain and host name. InsecureSkipVerify bool `yaml:"insecureSkipVerify" mapstructure:"insecureSkipVerify"` } type PostgresConfig struct { // Server username. User string `yaml:"user" mapstructure:"user"` // Server password. Password string `yaml:"password" mapstructure:"password"` // Server host. Host string `yaml:"host" mapstructure:"host"` // Server port. Port int `yaml:"port" mapstructure:"port"` // Server DB name. DBName string `yaml:"dbname" mapstructure:"dbname"` // SSL mode. SSLMode string `yaml:"sslMode" mapstructure:"sslMode"` // Server timezone. Timezone string `yaml:"timezone" mapstructure:"timezone"` // Enable migration. Migrate bool `yaml:"migrate" mapstructure:"migrate"` } type RedisConfig struct { // DEPRECATED: Please use the `addrs` field instead. Host string `yaml:"host" mapstructure:"host"` // DEPRECATED: Please use the `addrs` field instead. Port int `yaml:"port" mapstructure:"port"` // Server addresses. Addrs []string `yaml:"addrs" mapstructure:"addrs"` // Server username. Username string `yaml:"username" mapstructure:"username"` // Server password. Password string `yaml:"password" mapstructure:"password"` // Server cache DB name. DB int `yaml:"db" mapstructure:"db"` // Server broker DB name. BrokerDB int `yaml:"brokerDB" mapstructure:"brokerDB"` // Server backend DB name. BackendDB int `yaml:"backendDB" mapstructure:"backendDB"` } type CacheConfig struct { // Redis cache configuration. Redis *RedisCacheConfig `yaml:"redis" mapstructure:"redis"` // Local cache configuration. Local *LocalCacheConfig `yaml:"local" mapstructure:"local"` } type RedisCacheConfig struct { // Cache TTL. TTL time.Duration `yaml:"ttl" mapstructure:"ttl"` } type LocalCacheConfig struct { // Size of LFU cache. Size int `yaml:"size" mapstructure:"size"` // Cache TTL. TTL time.Duration `yaml:"ttl" mapstructure:"ttl"` } type RestConfig struct { // REST server address. Addr string `yaml:"addr" mapstructure:"addr"` } type MetricsConfig struct { // Enable metrics service. Enable bool `yaml:"enable" mapstructure:"enable"` // Metrics service address. Addr string `yaml:"addr" mapstructure:"addr"` // Enable peer gauge metrics. EnablePeerGauge bool `yaml:"enablePeerGauge" mapstructure:"enablePeerGauge"` } type GRPCConfig struct { // DEPRECATED: Please use the `listenIP` field instead. Listen string `mapstructure:"listen" yaml:"listen"` // AdvertiseIP is advertise ip. AdvertiseIP string `yaml:"advertiseIP" mapstructure:"advertiseIP"` // ListenIP is listen ip, like: 0.0.0.0, 192.168.0.1. ListenIP string `mapstructure:"listenIP" yaml:"listenIP"` // Port is listen port. PortRange TCPListenPortRange `yaml:"port" mapstructure:"port"` } type TCPListenPortRange struct { Start int End int } type ObjectStorageConfig struct { // Enable object storage. Enable bool `yaml:"enable" mapstructure:"enable"` // Object storage name of type, it can be s3 or oss. Name string `mapstructure:"name" yaml:"name"` // Storage region. Region string `mapstructure:"region" yaml:"region"` // Datacenter endpoint. Endpoint string `mapstructure:"endpoint" yaml:"endpoint"` // Access key ID. AccessKey string `mapstructure:"accessKey" yaml:"accessKey"` // Access key secret. SecretKey string `mapstructure:"secretKey" yaml:"secretKey"` } type SecurityConfig struct { // AutoIssueCert indicates to issue client certificates for all grpc call. AutoIssueCert bool `yaml:"autoIssueCert" mapstructure:"autoIssueCert"` // CACert is the CA certificate for all grpc tls handshake, it can be path or PEM format string. CACert types.PEMContent `mapstructure:"caCert" yaml:"caCert"` // CAKey is the CA private key, it can be path or PEM format string. CAKey types.PEMContent `mapstructure:"caKey" yaml:"caKey"` // TLSPolicy controls the grpc shandshake behaviors: // force: both ClientHandshake and ServerHandshake are only support tls // prefer: ServerHandshake supports tls and insecure (non-tls), ClientHandshake will only support tls // default: ServerHandshake supports tls and insecure (non-tls), ClientHandshake will only support insecure (non-tls) TLSPolicy string `mapstructure:"tlsPolicy" yaml:"tlsPolicy"` // CertSpec is the desired state of certificate. CertSpec *CertSpec `mapstructure:"certSpec" yaml:"certSpec"` } type CertSpec struct { // DNSNames is a list of dns names be set on the certificate. DNSNames []string `mapstructure:"dnsNames" yaml:"dnsNames"` // IPAddresses is a list of ip addresses be set on the certificate. IPAddresses []string `mapstructure:"ipAddresses" yaml:"ipAddresses"` // ValidityPeriod is the validity period of certificate. ValidityPeriod time.Duration `mapstructure:"validityPeriod" yaml:"validityPeriod"` } // New config instance. func New() *Config { return &Config{ Server: &ServerConfig{ Name: DefaultServerName, GRPC: &GRPCConfig{ AdvertiseIP: ip.IPv4, ListenIP: DefaultGRPCListenIP, PortRange: TCPListenPortRange{ Start: DefaultGRPCPort, End: DefaultGRPCPort, }, }, REST: &RestConfig{ Addr: DefaultRESTAddr, }, }, Database: &DatabaseConfig{ Type: DatabaseTypeMysql, Mysql: &MysqlConfig{ Port: DefaultMysqlPort, DBName: DefaultMysqlDBName, Migrate: true, }, Postgres: &PostgresConfig{ Port: DefaultPostgresPort, DBName: DefaultPostgresDBName, SSLMode: DefaultPostgresSSLMode, Timezone: DefaultPostgresTimezone, Migrate: true, }, Redis: &RedisConfig{ DB: DefaultRedisDB, BrokerDB: DefaultRedisBrokerDB, BackendDB: DefaultRedisBackendDB, }, }, Cache: &CacheConfig{ Redis: &RedisCacheConfig{ TTL: DefaultRedisCacheTTL, }, Local: &LocalCacheConfig{ Size: DefaultLFUCacheSize, TTL: DefaultLFUCacheTTL, }, }, ObjectStorage: &ObjectStorageConfig{ Enable: false, }, Security: &SecurityConfig{ AutoIssueCert: false, TLSPolicy: rpc.PreferTLSPolicy, CertSpec: &CertSpec{ IPAddresses: DefaultCertIPAddresses, DNSNames: DefaultCertDNSNames, ValidityPeriod: DefaultCertValidityPeriod, }, }, Metrics: &MetricsConfig{ Enable: false, Addr: DefaultMetricsAddr, EnablePeerGauge: true, }, } } // Validate config values func (cfg *Config) Validate() error { if cfg.Server == nil { return errors.New("config requires parameter server") } if cfg.Server.Name == "" { return errors.New("server requires parameter name") } if cfg.Server.GRPC == nil { return errors.New("server requires parameter grpc") } if cfg.Server.GRPC.AdvertiseIP == "" { return errors.New("grpc requires parameter advertiseIP") } if cfg.Server.GRPC.ListenIP == "" { return errors.New("grpc requires parameter listenIP") } if cfg.Server.REST == nil { return errors.New("server requires parameter rest") } if cfg.Database == nil { return errors.New("config requires parameter database") } if cfg.Database.Type == "" { return errors.New("database requires parameter type") } if cfg.Database.Type == DatabaseTypeMysql || cfg.Database.Type == DatabaseTypeMariaDB { if cfg.Database.Mysql == nil { return errors.New("database requires parameter mysql") } if cfg.Database.Mysql.User == "" { return errors.New("mysql requires parameter user") } if cfg.Database.Mysql.Password == "" { return errors.New("mysql requires parameter password") } if cfg.Database.Mysql.Host == "" { return errors.New("mysql requires parameter host") } if cfg.Database.Mysql.Port <= 0 { return errors.New("mysql requires parameter port") } if cfg.Database.Mysql.DBName == "" { return errors.New("mysql requires parameter dbname") } if cfg.Database.Mysql.TLS != nil { if cfg.Database.Mysql.TLS.Cert == "" { return errors.New("tls requires parameter cert") } if cfg.Database.Mysql.TLS.Key == "" { return errors.New("tls requires parameter key") } if cfg.Database.Mysql.TLS.CA == "" { return errors.New("tls requires parameter ca") } } } if cfg.Database.Type == DatabaseTypePostgres { if cfg.Database.Postgres == nil { return errors.New("database requires parameter postgres") } if cfg.Database.Postgres.User == "" { return errors.New("postgres requires parameter user") } if cfg.Database.Postgres.Password == "" { return errors.New("postgres requires parameter password") } if cfg.Database.Postgres.Host == "" { return errors.New("postgres requires parameter host") } if cfg.Database.Postgres.Port <= 0 { return errors.New("postgres requires parameter port") } if cfg.Database.Postgres.DBName == "" { return errors.New("postgres requires parameter dbname") } if cfg.Database.Postgres.SSLMode == "" { return errors.New("postgres requires parameter sslMode") } if cfg.Database.Postgres.Timezone == "" { return errors.New("postgres requires parameter timezone") } } if cfg.Database.Redis == nil { return errors.New("database requires parameter redis") } if len(cfg.Database.Redis.Addrs) == 0 { return errors.New("redis requires parameter addrs") } if len(cfg.Database.Redis.Addrs) == 1 { if cfg.Database.Redis.DB < 0 { return errors.New("redis requires parameter db") } if cfg.Database.Redis.BrokerDB < 0 { return errors.New("redis requires parameter brokerDB") } if cfg.Database.Redis.BackendDB < 0 { return errors.New("redis requires parameter backendDB") } } if cfg.Database.Redis.BrokerDB < 0 { return errors.New("redis requires parameter brokerDB") } if cfg.Database.Redis.BackendDB < 0 { return errors.New("redis requires parameter backendDB") } if cfg.Cache == nil { return errors.New("config requires parameter cache") } if cfg.Cache.Redis == nil { return errors.New("cache requires parameter redis") } if cfg.Cache.Redis.TTL == 0 { return errors.New("redis requires parameter ttl") } if cfg.Cache.Local == nil { return errors.New("cache requires parameter local") } if cfg.Cache.Local.Size == 0 { return errors.New("local requires parameter size") } if cfg.Cache.Local.TTL == 0 { return errors.New("local requires parameter ttl") } if cfg.ObjectStorage != nil && cfg.ObjectStorage.Enable { if cfg.ObjectStorage.Name == "" { return errors.New("objectStorage requires parameter name") } if !slices.Contains([]string{objectstorage.ServiceNameS3, objectstorage.ServiceNameOSS}, cfg.ObjectStorage.Name) { return errors.New("objectStorage requires parameter name") } if cfg.ObjectStorage.AccessKey == "" { return errors.New("objectStorage requires parameter accessKey") } if cfg.ObjectStorage.SecretKey == "" { return errors.New("objectStorage requires parameter secretKey") } } if cfg.Security != nil && cfg.Security.AutoIssueCert { if cfg.Security.CACert == "" { return errors.New("security requires parameter caCert") } if cfg.Security.CAKey == "" { return errors.New("security requires parameter caKey") } if !slices.Contains([]string{rpc.DefaultTLSPolicy, rpc.ForceTLSPolicy, rpc.PreferTLSPolicy}, cfg.Security.TLSPolicy) { return errors.New("security requires parameter tlsPolicy") } if cfg.Security.CertSpec == nil { return errors.New("security requires parameter certSpec") } if len(cfg.Security.CertSpec.IPAddresses) == 0 { return errors.New("certSpec requires parameter ipAddresses") } if len(cfg.Security.CertSpec.DNSNames) == 0 { return errors.New("certSpec requires parameter dnsNames") } } if cfg.Metrics == nil { return errors.New("config requires parameter metrics") } if cfg.Metrics.Enable { if cfg.Metrics.Addr == "" { return errors.New("metrics requires parameter addr") } } return nil } func (cfg *Config) Convert() error { // TODO Compatible with deprecated fields host and port. if len(cfg.Database.Redis.Addrs) == 0 && cfg.Database.Redis.Host != "" && cfg.Database.Redis.Port > 0 { cfg.Database.Redis.Addrs = []string{fmt.Sprintf("%s:%d", cfg.Database.Redis.Host, cfg.Database.Redis.Port)} } // TODO Compatible with deprecated fields listen. if cfg.Server.GRPC.Listen != "" && cfg.Server.GRPC.ListenIP == "" { cfg.Server.GRPC.ListenIP = cfg.Server.GRPC.Listen } return nil }