started implementing auto config loader

This commit is contained in:
2026-07-02 13:49:32 +02:00
parent b573d63c54
commit 489074da01
3 changed files with 214 additions and 21 deletions

174
pkg/cfg/automate.go Normal file
View File

@@ -0,0 +1,174 @@
/*
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 (
"bytes"
"context"
"errors"
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"gopkg.in/yaml.v3"
)
type Automator struct {
IsReachable bool
Error error
Env []string
Cluster *Cluster
// all bash commands, .name must succeed first
Name string `yaml:"name"`
User string `yaml:"user"`
Pass string `yaml:"pass"`
Uri string `yaml:"uri"`
}
type AutomatorRunner struct {
Output string
Error error
}
func NewAutoEnv() []string {
return []string{
"PATH=" + os.Getenv("PATH"),
"HOME=" + os.Getenv("HOME"),
"SHELL=" + os.Getenv("SHELL"),
"KUBECONFIG=" + os.Getenv("KUBECONFIG"),
}
}
func NewAutomator() Automator {
autocfg := filepath.Join([]string{os.Getenv("HOME"), ".config", "esctl", "automate.yaml"}...)
auto := Automator{Env: NewAutoEnv()}
if !fileExists(autocfg) {
return auto
}
data, err := os.ReadFile(autocfg)
if err != nil {
auto.Error = fmt.Errorf("failed to read config file: %w", err)
return auto
}
err = yaml.Unmarshal(data, &auto)
if err != nil {
auto.Error = fmt.Errorf("failed to unmarshal config file: %w", err)
return auto
}
hasname := execute(auto.Name, auto.Env)
if hasname.Error != nil {
auto.Error = hasname.Error
return auto
}
auto.Name = hasname.Output
hasuser := execute(auto.User, auto.Env)
if hasuser.Error != nil {
auto.Error = hasuser.Error
return auto
}
auto.User = hasuser.Output
haspass := execute(auto.Pass, auto.Env)
if haspass.Error != nil {
auto.Error = haspass.Error
return auto
}
auto.Pass = haspass.Output
hasuri := execute(auto.Uri, auto.Env)
if hasuri.Error != nil {
auto.Error = hasuri.Error
return auto
}
auto.Uri = hasuri.Output
auto.IsReachable = true
auto.Cluster = &Cluster{
Name: auto.Name,
Uri: auto.Uri,
User: auto.User,
Pass: auto.Pass,
}
return auto
}
// FIXME: execute auto.Exec, if defined, in a go routine and let it run forever, might be a tunnel
func (auto *Automator) Exec() {}
// FIXME: cache automator results, only check auto.Name and if it matches the cache use those vars, but run auto.Exec anyway
func (auto *Automator) Cache() {}
func execute(code string, env []string) *AutomatorRunner {
timeoutCtx, cancel := context.WithTimeout(context.Background(),
time.Duration(10)*time.Second)
defer cancel()
var cmd *exec.Cmd
// pipe code into bash
cmd = exec.CommandContext(timeoutCtx, "bash")
cmd.Stdin = strings.NewReader(code)
cmd.Env = env
errbuf := &bytes.Buffer{}
cmd.Stderr = errbuf
done := make(chan bool)
out := AutomatorRunner{}
go func() {
output, err := cmd.Output()
out.Output = strings.TrimSpace(string(output))
switch {
case err != nil:
out.Error = err
case errbuf.Len() > 0:
out.Error = fmt.Errorf(errbuf.String())
case timeoutCtx.Err() == context.DeadlineExceeded:
out.Error = errors.New("timed out")
}
slog.Debug("executed automator",
"code", code,
"output", out.Output,
"error", out.Error)
done <- true
}()
for {
select {
case <-done:
return &out
}
}
}

View File

@@ -223,8 +223,7 @@ func (cluster *Cluster) IsReachable() (bool, error) {
return false, nil return false, nil
} }
func (conf *Config) SetupES() error { func (conf *Config) SetupElasticClient(name string, cluster *Cluster) error {
for name, cluster := range conf.Clusters {
cluster.Name = name cluster.Name = name
cluster.DebugHTTP = conf.DebugHTTP cluster.DebugHTTP = conf.DebugHTTP
@@ -244,6 +243,15 @@ func (conf *Config) SetupES() error {
} }
cluster.SetClient(es) cluster.SetClient(es)
return nil
}
func (conf *Config) SetupElasticClients() error {
for name, cluster := range conf.Clusters {
if err := conf.SetupElasticClient(name, cluster); err != nil {
return err
}
} }
return nil return nil

View File

@@ -28,7 +28,7 @@ import (
) )
const ( const (
Version string = `v0.0.24` Version string = `v0.0.25`
) )
var ( var (
@@ -133,7 +133,7 @@ func (conf *Config) Init() error {
} }
} }
if err := conf.SetupES(); err != nil { if err := conf.SetupElasticClients(); err != nil {
return err return err
} }
@@ -153,6 +153,9 @@ func (conf *Config) Init() error {
conf.DefaultCluster.Default = true conf.DefaultCluster.Default = true
} }
} else { } else {
// load auto config, if any
auto := NewAutomator()
// we need to determine ourselfes // we need to determine ourselfes
if len(conf.Clusters) == 1 { if len(conf.Clusters) == 1 {
// ok, just one cluster configured, use this, of course // ok, just one cluster configured, use this, of course
@@ -161,6 +164,14 @@ func (conf *Config) Init() error {
conf.CurrentCluster = name conf.CurrentCluster = name
conf.DefaultCluster.Default = true conf.DefaultCluster.Default = true
} }
} else if auto.Error == nil && auto.IsReachable {
// use auto conf
if err := conf.SetupElasticClient(auto.Name, auto.Cluster); err != nil {
return err
}
conf.DefaultCluster = auto.Cluster
conf.CurrentCluster = auto.Name
} else { } else {
// multiple ones exists, look if one is set as default // multiple ones exists, look if one is set as default
for name, cluster := range conf.Clusters { for name, cluster := range conf.Clusters {