252 lines
6 KiB
Go
252 lines
6 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
const (
|
|
RaftDir = "raft"
|
|
RaftListen = ":7001"
|
|
RaftLogLevel = "off"
|
|
|
|
EtcdLogLevel = "error"
|
|
EtcdPrefix = "/haproxy-lb"
|
|
|
|
HAProxyService = "haproxy"
|
|
HAProxyConfigFile = "/etc/haproxy/haproxy.cfg"
|
|
)
|
|
|
|
func ParseFile(file string) (cfg *Config, err error) {
|
|
if file == "" {
|
|
return nil, errors.New("missing config file")
|
|
}
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer f.Close()
|
|
cfg = new(Config)
|
|
err = yaml.NewDecoder(f).Decode(cfg)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = Validate(cfg)
|
|
return
|
|
}
|
|
|
|
func Validate(c *Config) error {
|
|
if c.HAProxyConfig == "" {
|
|
c.HAProxyConfig = HAProxyConfigFile
|
|
}
|
|
if c.HAProxyService == "" {
|
|
c.HAProxyService = HAProxyService
|
|
}
|
|
|
|
failover := c.Failover
|
|
if failover != nil {
|
|
if failover.IP == "" {
|
|
return errors.New("missing failover.ip config")
|
|
|
|
}
|
|
_, _, err := net.ParseCIDR(failover.IP)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse failover.ip: %w", err)
|
|
}
|
|
}
|
|
|
|
if c.LeaderHook != "" {
|
|
if !filepath.IsAbs(c.LeaderHook) {
|
|
return errors.New("leaderHook path must be absolute")
|
|
}
|
|
}
|
|
if c.FollowerHook != "" {
|
|
if !filepath.IsAbs(c.FollowerHook) {
|
|
return errors.New("followerHook path must be absolute")
|
|
}
|
|
}
|
|
|
|
vip := c.VIP
|
|
if vip.Prefix == "" {
|
|
return errors.New("missing vip.prefix config")
|
|
}
|
|
local := vip.Store.Local
|
|
netbox := vip.Store.Netbox
|
|
if local == nil && netbox == nil {
|
|
return errors.New("missing vip.store config: local or netbox")
|
|
}
|
|
if local != nil && netbox != nil {
|
|
return errors.New("only one vip.store config allowed: local or netbox")
|
|
}
|
|
if netbox != nil {
|
|
if netbox.Endpoint == "" {
|
|
return errors.New("missing vip.store.netbox.endpoint config")
|
|
}
|
|
if netbox.Token == "" {
|
|
return errors.New("missing vip.store.netbox.token config")
|
|
}
|
|
}
|
|
|
|
raft := c.Cluster.Raft
|
|
if raft != nil {
|
|
if raft.LogLevel == "" {
|
|
raft.LogLevel = RaftLogLevel
|
|
}
|
|
if raft.Dir == "" {
|
|
raft.Dir = RaftDir
|
|
}
|
|
if raft.ID == "" {
|
|
return errors.New("missing raft.id config")
|
|
}
|
|
if raft.Listen == "" {
|
|
raft.Listen = RaftListen
|
|
}
|
|
if len(raft.Peers) < 2 {
|
|
return errors.New("minimum number of remote peers: 2")
|
|
}
|
|
for i, v := range raft.Peers {
|
|
if v.ID == "" {
|
|
return fmt.Errorf("missing raft.peers[%d].id config", i)
|
|
}
|
|
if v.Address == "" {
|
|
return fmt.Errorf("missing raft.peers[%d].address config", i)
|
|
}
|
|
}
|
|
}
|
|
etcd := c.Cluster.Etcd
|
|
if etcd != nil {
|
|
if etcd.LogLevel == "" {
|
|
etcd.LogLevel = EtcdLogLevel
|
|
}
|
|
if len(etcd.Endpoints) == 0 {
|
|
return errors.New("missing etcd.endpoints config")
|
|
}
|
|
if etcd.Prefix == "" {
|
|
etcd.Prefix = EtcdPrefix
|
|
} else {
|
|
if !strings.HasPrefix(etcd.Prefix, "/") {
|
|
etcd.Prefix = "/" + etcd.Prefix
|
|
}
|
|
if strings.HasSuffix(etcd.Prefix, "/") {
|
|
etcd.Prefix = strings.TrimSuffix(etcd.Prefix, "/")
|
|
}
|
|
}
|
|
if etcd.ClusterName == "" {
|
|
return errors.New("missing etcd.clusterName config")
|
|
} else if strings.HasSuffix(etcd.ClusterName, "/") {
|
|
etcd.ClusterName = strings.TrimSuffix(etcd.ClusterName, "/")
|
|
}
|
|
}
|
|
if raft == nil && etcd == nil {
|
|
return errors.New("missing cluster config: raft or etcd")
|
|
}
|
|
if raft != nil && etcd != nil {
|
|
return errors.New("only one cluster config allowed: raft or etcd")
|
|
}
|
|
if !c.APIServer.DisableAuth {
|
|
if len(c.APIServer.BasicAuth) == 0 {
|
|
return errors.New("missing basic auth configuration for api users")
|
|
}
|
|
for _, u := range c.APIServer.BasicAuth {
|
|
if u.User == "" {
|
|
return errors.New("basic auth username can not be empty")
|
|
}
|
|
_, err := bcrypt.Cost([]byte(u.Hash))
|
|
if u.Hash == "" {
|
|
return fmt.Errorf("invalid basic auth hash for user %s: %w", u.User, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Config struct {
|
|
LeaderHook string `yaml:"leaderHook,omitempty"`
|
|
FollowerHook string `yaml:"followerHook,omitempty"`
|
|
HAProxyConfig string `yaml:"haproxyConfig,omitempty"`
|
|
HAProxyService string `yaml:"haproxyService,omitempty"`
|
|
Failover *Failover `yaml:"failover"`
|
|
VIP VIP `yaml:"vip"`
|
|
Cluster Cluster `yaml:"cluster"`
|
|
APIServer APIServer `yaml:"apiServer"`
|
|
}
|
|
|
|
type Failover struct {
|
|
IP string `yaml:"ip"`
|
|
Interface string `yaml:"interface"`
|
|
}
|
|
|
|
type VIP struct {
|
|
Prefix string `yaml:"prefix"`
|
|
Store Store `yaml:"store"`
|
|
Interface string `yaml:"interface,omitempty"`
|
|
Label string `yaml:"label,omitempty"`
|
|
}
|
|
|
|
type Store struct {
|
|
Local *struct{} `yaml:"local"`
|
|
Netbox *Netbox `yaml:"netbox"`
|
|
}
|
|
|
|
type Netbox struct {
|
|
Endpoint string `yaml:"endpoint"`
|
|
Token string `yaml:"token"`
|
|
Insecure bool `yaml:"insecure,omitempty"`
|
|
}
|
|
|
|
type Cluster struct {
|
|
Raft *Raft `yaml:"raft,omitempty"`
|
|
Etcd *Etcd `yaml:"etcd,omitempty"`
|
|
}
|
|
|
|
type Raft struct {
|
|
Dir string `yaml:"dir,omitempty"`
|
|
ID string `yaml:"id"`
|
|
Listen string `yaml:"listen"`
|
|
Peers []RaftPeer `yaml:"peers"`
|
|
TLS *TLS `yaml:"tls,omitempty"`
|
|
LogLevel string `yaml:"logLevel,omitempty"`
|
|
}
|
|
|
|
type TLS struct {
|
|
Cert string `yaml:"cert,omitempty"`
|
|
Key string `yaml:"key,omitempty"`
|
|
CA string `yaml:"ca,omitempty"`
|
|
Insecure bool `yaml:"insecure,omitempty"`
|
|
}
|
|
|
|
// RaftPeer details the configuration of all cluster peers
|
|
type RaftPeer struct {
|
|
ID string `yaml:"id"`
|
|
Address string `yaml:"address"`
|
|
}
|
|
|
|
type Etcd struct {
|
|
Endpoints []string `yaml:"endpoints"`
|
|
ClusterName string `yaml:"clusterName"`
|
|
Prefix string `yaml:"prefix,omitempty"`
|
|
Username string `yaml:"username,omitempty"`
|
|
Password string `yaml:"password,omitempty"`
|
|
TLS *TLS `yaml:"tls,omitempty"`
|
|
LogLevel string `yaml:"logLevel,omitempty"`
|
|
}
|
|
|
|
type APIServer struct {
|
|
Listen string `yaml:"listen"`
|
|
TLS *TLS `yaml:"tls"`
|
|
DisableAuth bool `yaml:"disableAuth"`
|
|
BasicAuth []BasicAuth `yaml:"basicAuth"`
|
|
}
|
|
|
|
type BasicAuth struct {
|
|
User string `yaml:"user"`
|
|
Hash string `yaml:"hash"`
|
|
Prefix string `yaml:"prefix"`
|
|
}
|