Files
esctl/pkg/cfg/config.go
2026-07-07 23:46:43 +02:00

291 lines
7.2 KiB
Go

/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cfg
import (
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"github.com/alecthomas/repr"
"gopkg.in/yaml.v3"
)
const (
Version string = `v0.0.26`
)
var (
// initialized during build, see Makefile:buildlocal
APIVERSION, GOVERSION, BUILD, COMMIT, BRANCH string
)
type Config struct {
ConfigFile string // -c
CurrentCluster string // -C
Debug bool // -d
Output string // -o <mode>
Clusters map[string]*Cluster
DefaultCluster *Cluster
HaveJQ bool // determined at runtime by ourselfes
ProfileFile string // for internal use (golang profiling)
AlignInts bool // -I
Index string // index: -i
Failed, Partials bool // index: flags
Shards, Replicas int // index create+allocation: -s -r
Wait bool // index create: -w
Policy string // index create: -p
Primary bool // index allocation: -p
Searchable bool // index fields: -s
Aggretable bool // index fields: -a
Priority int // index template create: -p
Settings []string // index template create: -s
Patterns []string // index template create: -i
Components []string // index template create: -c
Meta []string // index template create: -M
Aliases []string // index template create: -A
Stream bool // index template create: -S
AutoCreate bool // index template create: -a
Mode string // index template create: -a
Retention string // index template create: -r
Rollover bool // index template create: -R
From, To, MaxItems int // search: flags
Filter []string // search: -F
Path string // search+doc sh: -p
Subhelp bool // search+doc sh: -H
Tail bool // search: -f [tail]
Or bool // search: -O
Range string // search: -r
TimestampFormat string // search: --timestamp-format
Explain bool // search: -e
Validate bool // search: --validate
SortBy string // sort: -k
Ascending bool // sort: -a
Exclude string // cluster compare: -e (regexp)
All, Verbose bool // cluster status: -a -v
Persistent, Transient, Default bool // cluster settings set: -p -t -D
Force bool // ccr follower renew: -f
DebugHTTP bool // root: --debug-http
Separator string // role diff: -s
NotDeployed bool // role diff: -n
Undefined bool // role diff: -u
Diff bool // role diff: -D
Hidden bool // ds ls: -H
// rollover
MaxAge string
MaxDocs, MaxShardSize, MaxShardDocs int // roll over
DryRun bool // rollover: -n
Tag string // api ls: -t
Ilm Ilm // ilm create
FromNode, ToNode string // cluster reroute move: -f + -t
AllowPrimary, AcceptDataLoss bool // cluster reroute cancel: -p,-a
Pager string // api repl: -p || PAGER
}
func NewConfig() *Config {
return &Config{Clusters: map[string]*Cluster{}}
}
func getDefaultPath() string {
return filepath.Join([]string{os.Getenv("HOME"), ".config", "esctl", "config.yaml"}...)
}
func (conf *Config) Init() error {
DefaultConfig := getDefaultPath()
if conf.ConfigFile == "" && fileExists(DefaultConfig) {
conf.ConfigFile = DefaultConfig
}
switch {
case conf.ConfigFile != "":
if err := conf.LoadConfig(); err != nil {
return err
}
default:
if err := conf.LoadEnv(); err != nil {
return err
}
}
if err := conf.SetupES(); err != nil {
return err
}
if err := conf.determineDefaultCluster(); err != nil {
return err
}
conf.HaveJQ = isJQinstalled()
conf.PrintDebug()
return nil
}
// we are using reflect to clone a config obj w/o the ES stuff for
// shorter repr.Println() output (the ES structure is just too large)
func (conf *Config) Clone() Config {
clone := Config{}
ref := reflect.ValueOf(*conf)
typeOfS := ref.Type()
for idx := range ref.NumField() {
field := typeOfS.Field(idx).Name
if field == "Clusters" || field == "DefaultCluster" || !ref.Field(idx).CanInterface() {
continue
}
reflect.ValueOf(&clone).Elem().FieldByName(field).Set(reflect.ValueOf(ref.Field(idx).Interface()))
}
return clone
}
func (conf *Config) PrintDebug() {
if !conf.Debug {
return
}
clone := conf.Clone()
fmt.Println("config:")
repr.Println(clone)
}
func (conf *Config) LoadEnv() error {
cluster := Cluster{
Uri: os.Getenv("ES_URI"),
User: os.Getenv("ES_USER"),
Pass: os.Getenv("ES_PASS"),
Token: os.Getenv("ES_TOKEN"),
}
switch {
case cluster.Uri == "":
return errors.New("ES_URI unset")
case cluster.User == "" || cluster.Token == "":
return errors.New("ES_USER and ES_TOKEN unset")
}
conf.Clusters["default"] = &cluster
conf.DefaultCluster = &cluster
return nil
}
func (conf *Config) LoadConfig() error {
if conf.ConfigFile == "" {
return nil
}
data, err := os.ReadFile(conf.ConfigFile)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
newconf := &Config{}
err = yaml.Unmarshal(data, newconf)
if err != nil {
return fmt.Errorf("failed to unmarshal config file: %w", err)
}
if len(newconf.Clusters) > 0 {
conf.Clusters = newconf.Clusters
for _, cluster := range conf.Clusters {
if cluster.Default {
conf.DefaultCluster = cluster
break
}
}
if conf.DefaultCluster == nil {
conf.DefaultCluster = &Cluster{}
}
}
return nil
}
func (conf *Config) determineDefaultCluster() error {
if conf.CurrentCluster != "" {
// -C specified, set current cluster explicitly, no matter what the config says
current, exists := conf.Clusters[conf.CurrentCluster]
if !exists {
return fmt.Errorf("no cluster with alias %s configured", conf.CurrentCluster)
} else {
conf.DefaultCluster = current
// disable all others
for _, cluster := range conf.Clusters {
cluster.Default = false
}
conf.DefaultCluster.Default = true
}
} else {
// we need to determine ourselfes
if len(conf.Clusters) == 1 {
// ok, just one cluster configured, use this, of course
for name, cluster := range conf.Clusters {
conf.DefaultCluster = cluster
conf.CurrentCluster = name
conf.DefaultCluster.Default = true
}
} else {
// multiple ones exists, look if one is set as default
for name, cluster := range conf.Clusters {
if cluster.Default {
conf.DefaultCluster = cluster
conf.CurrentCluster = name
}
}
}
}
return nil
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if err != nil {
// return false on any error
return false
}
return !info.IsDir()
}