Compare commits

..

7 Commits

18 changed files with 1258 additions and 349 deletions

View File

@@ -23,6 +23,8 @@ Features:
- Shell completion support (bash, zsh and fish). Put this into your - Shell completion support (bash, zsh and fish). Put this into your
rc: `source <(esctl completion bash)`. rc: `source <(esctl completion bash)`.
- Cluster settings can be viewed and modified. - Cluster settings can be viewed and modified.
- Comprehensive cluster status.
- Cluster reroute support.
- Search: you can search indices using full text or by fields, select - Search: you can search indices using full text or by fields, select
logical condition (OR, AND), use PIT, limit datetime (ES date math logical condition (OR, AND), use PIT, limit datetime (ES date math
can be used), etc. It is however not yet possible to create can be used), etc. It is however not yet possible to create
@@ -47,7 +49,9 @@ Features:
elasticsearch API. You can run API calls on the current selected elasticsearch API. You can run API calls on the current selected
cluster w/o the hassle to specify the whole url, credentials etc. It cluster w/o the hassle to specify the whole url, credentials etc. It
has line editing and history support. If `jq` is installed output has line editing and history support. If `jq` is installed output
JSON will be syntax highlighted. JSON will be syntax highlighted. A simple internal pager will be
used if output exceeds the terminal height. You can tweak this using
the `$ES_JSON_PAGER` environment variable (I'd recommend [fx](https://fx.wtf/)).
- Doc support. You can put, delete and show docs for an index. Very - Doc support. You can put, delete and show docs for an index. Very
handy if you want to play with it. Just create a new index: handy if you want to play with it. Just create a new index:
`esctl index create foo` and then insert docs into it for search `esctl index create foo` and then insert docs into it for search
@@ -546,6 +550,8 @@ index - manage indicies
create - create a new index template create - create a new index template
update - update a new index template update - update a new index template
delete - delete an index template delete - delete an index template
license - manage cluster license
show - show details about the cluster license
node - manage nodes node - manage nodes
list - list nodes list - list nodes
show - show details about a node show - show details about a node
@@ -567,7 +573,7 @@ task - manage tasks
version - show esctl version information version - show esctl version information
debug - developer only debug - developer only
help-jsonpath - show jsonpath help help-jsonpath - show jsonpath help
help-command-overview - show overview of all available commands help-usage - show overview of all available commands
``` ```
# Development # Development

View File

@@ -83,6 +83,16 @@ func ApiRepl(conf *cfg.Config) *cli.Command {
Aliases: []string{"shell"}, Aliases: []string{"shell"},
Usage: "interactive API repl", Usage: "interactive API repl",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "pager",
Usage: "external viewer program (default:internal)",
Destination: &conf.Pager,
Aliases: []string{"p"},
Sources: cli.EnvVars("PAGER", "ES_JSON_PAGER"),
},
},
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
return es.ApiRepl(conf) return es.ApiRepl(conf)
}, },

View File

@@ -104,6 +104,10 @@ func ClusterSettingsSet(conf *cfg.Config) *cli.Command {
}, },
}, },
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
complete(cmd, Cclustersettings)
},
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
args := cmd.Args() args := cmd.Args()

View File

@@ -33,6 +33,7 @@ const (
Capi Capi
Cilm Cilm
Cnode Cnode
Cclustersettings
) )
func complete(cmd *cli.Command, what int) { func complete(cmd *cli.Command, what int) {
@@ -67,6 +68,8 @@ func complete(cmd *cli.Command, what int) {
list, err = es.IlmNames(conf) list, err = es.IlmNames(conf)
case Cnode: case Cnode:
list, err = es.NodeNames(conf) list, err = es.NodeNames(conf)
case Cclustersettings:
list, err = es.ClusterSettingsNames(conf)
} }
if err != nil { if err != nil {

49
cmd/license.go Normal file
View File

@@ -0,0 +1,49 @@
/*
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 cmd
import (
"context"
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/es"
"github.com/urfave/cli/v3"
)
func License(conf *cfg.Config) *cli.Command {
return &cli.Command{
Name: "license",
Usage: "manage cluster license",
Commands: []*cli.Command{
LicenseShow(conf),
},
}
}
func LicenseShow(conf *cfg.Config) *cli.Command {
return &cli.Command{
Name: "show",
Aliases: []string{"sh"},
Usage: "show details about the cluster license",
Action: func(ctx context.Context, cmd *cli.Command) error {
return es.LicenseShow(conf)
},
}
}

View File

@@ -108,6 +108,7 @@ func Main() int {
Doc(conf), Doc(conf),
Ilm(conf), Ilm(conf),
Index(conf), Index(conf),
License(conf),
Node(conf), Node(conf),
Roles(conf), Roles(conf),
Search(conf), Search(conf),
@@ -248,9 +249,11 @@ func Debug(conf *cfg.Config) *cli.Command {
func HelpUsage(conf *cfg.Config) *cli.Command { func HelpUsage(conf *cfg.Config) *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "help-command-overview", Name: "help-usage",
Usage: "show overview of all available commands", Usage: "show overview of all available commands",
UsageText: "help-usage [<filter>]",
Aliases: []string{"usage"}, Aliases: []string{"usage"},
CustomHelpTemplate: addReference(`<filter> implies -f`),
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
@@ -259,16 +262,32 @@ func HelpUsage(conf *cfg.Config) *cli.Command {
Destination: &conf.Hidden, Destination: &conf.Hidden,
Aliases: []string{"H"}, Aliases: []string{"H"},
}, },
&cli.BoolFlag{
Name: "full-commands",
Usage: "show full commands",
Destination: &conf.Force,
Aliases: []string{"f"},
},
}, },
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
maxCommandWidth := 0 maxCommandWidth := 0
filter := cmd.Args().Get(0)
if filter != "" {
conf.Force = true
}
// first pass, determine max command width // first pass, determine max command width
if err := walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error { if err := walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error {
path := cmd.Path() path := cmd.Path()
size := len(path[len(path)-1]) size := len(path[len(path)-1])
if conf.Force {
path := strings.Join(cmd.Path(), " ")
size = len(path)
}
if size > maxCommandWidth { if size > maxCommandWidth {
maxCommandWidth = size maxCommandWidth = size
} }
@@ -283,9 +302,19 @@ func HelpUsage(conf *cfg.Config) *cli.Command {
// second pass, build tree // second pass, build tree
return walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error { return walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error {
path := cmd.Path() path := cmd.Path()
command := path[len(path)-1]
if len(path) == 1 || command == "help" { if filter != "" {
if !strings.Contains(strings.Join(path, " "), filter) {
return nil
}
}
command := path[len(path)-1]
if conf.Force {
command = strings.Join(cmd.Path(), " ")
}
if len(path) == 1 || strings.HasSuffix(command, "help") {
return nil return nil
} }

View File

@@ -1,174 +0,0 @@
/*
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

@@ -17,10 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package cfg package cfg
import ( import (
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@@ -185,45 +185,28 @@ func (conf *Config) SwitchCluster(name string) error {
// We do NOT use go-elasticsearch to check for cluster reachability, // We do NOT use go-elasticsearch to check for cluster reachability,
// because at this stage, auth may not have been configured. So // because at this stage, auth may not have been configured. So
// instead we just connect to the cluster using plan net/http, ignore // instead we just connect to the cluster using plan net/tcp
// HTTP response status and return true if we could just reach ith
func (cluster *Cluster) IsReachable() (bool, error) { func (cluster *Cluster) IsReachable() (bool, error) {
ctx, cancel := context.WithTimeout( timeout := 500 * time.Millisecond
context.Background(),
time.Duration(500)*time.Millisecond)
defer cancel()
req, err := http.NewRequestWithContext( url := strings.TrimPrefix(strings.TrimPrefix(cluster.Uri, "https://"), "http://")
ctx,
"GET",
cluster.Uri,
nil,
)
host := strings.Split(url, "/")
if !strings.Contains(host[0], ":") {
host[0] += ":443"
}
conn, err := net.DialTimeout("tcp", host[0], timeout)
if err != nil { if err != nil {
return false, err return false, err
} }
client := &http.Client{Transport: &http.Transport{ return true, conn.Close()
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
resp, err := client.Do(req)
if err != nil {
return false, err
}
if resp != nil {
// at this stage we do not care if the elasticsearch cluster
// accepts our request or if it's misconfigured in some way
return true, nil
}
return false, nil
} }
func (conf *Config) SetupElasticClient(name string, cluster *Cluster) error { func (conf *Config) SetupES() error {
for name, cluster := range conf.Clusters {
cluster.Name = name cluster.Name = name
cluster.DebugHTTP = conf.DebugHTTP cluster.DebugHTTP = conf.DebugHTTP
@@ -243,15 +226,6 @@ func (conf *Config) SetupElasticClient(name string, cluster *Cluster) 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

@@ -106,6 +106,8 @@ type Config struct {
FromNode, ToNode string // cluster reroute move: -f + -t FromNode, ToNode string // cluster reroute move: -f + -t
AllowPrimary, AcceptDataLoss bool // cluster reroute cancel: -p,-a AllowPrimary, AcceptDataLoss bool // cluster reroute cancel: -p,-a
Pager string // api repl: -p || PAGER
} }
func NewConfig() *Config { func NewConfig() *Config {
@@ -133,10 +135,22 @@ func (conf *Config) Init() error {
} }
} }
if err := conf.SetupElasticClients(); err != nil { if err := conf.SetupES(); err != nil {
return err return err
} }
if err := conf.determineDefaultCluster(); err != nil {
return err
}
conf.HaveJQ = isJQinstalled()
conf.PrintDebug()
return nil
}
func (conf *Config) determineDefaultCluster() error {
if conf.CurrentCluster != "" { if conf.CurrentCluster != "" {
// -C specified, set current cluster explicitly, no matter what the config says // -C specified, set current cluster explicitly, no matter what the config says
current, exists := conf.Clusters[conf.CurrentCluster] current, exists := conf.Clusters[conf.CurrentCluster]
@@ -153,9 +167,6 @@ 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
@@ -164,14 +175,6 @@ 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 {
@@ -183,10 +186,6 @@ func (conf *Config) Init() error {
} }
} }
conf.HaveJQ = isJQinstalled()
conf.PrintDebug()
return nil return nil
} }

View File

@@ -36,3 +36,14 @@ func GetTermWidth() int {
return 80 return 80
} }
func GetTermHeight() int {
if term.IsTerminal(int(os.Stdout.Fd())) {
_, height, err := term.GetSize(int(os.Stdout.Fd()))
if err == nil {
return height
}
}
return 25
}

View File

@@ -141,14 +141,54 @@ func ApiRepl(conf *cfg.Config) error {
fmt.Printf("failed to call API: %s\n", esErrorString(err)) fmt.Printf("failed to call API: %s\n", esErrorString(err))
} }
if err := prettyfiJson(conf, raw); err != nil { pageJsonOutput(conf, raw)
fmt.Println(err)
}
} }
return nil return nil
} }
func pageJsonOutput(conf *cfg.Config, raw []byte) {
tmpconf := &cfg.Config{HaveJQ: conf.HaveJQ}
if conf.Pager != "" {
tmpconf.HaveJQ = false
}
output, err := prettyfiJson(tmpconf, raw)
if err != nil {
fmt.Println(err)
}
lines := len(strings.Split(output, "\n"))
height := cfg.GetTermHeight()
if lines > height {
if conf.Pager != "" {
cmd := strings.Split(conf.Pager, " ")
pager := exec.Command(cmd[0], cmd[1:]...)
var buf bytes.Buffer
buf.WriteString(output)
pager.Stdout = os.Stdout
pager.Stdin = &buf
pager.Stderr = os.Stderr
err := pager.Run()
if err != nil {
fmt.Printf("failed to execute pager '%s': %s", conf.Pager, err)
}
} else {
printer.Pager("json output", output)
}
return
}
fmt.Println(output)
}
func encodeAuth(username, password string) string { func encodeAuth(username, password string) string {
return base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
} }
@@ -163,6 +203,12 @@ func CallAPI(conf *cfg.Config, verb, path, data string) ([]byte, error) {
client := &http.Client{Transport: tr} client := &http.Client{Transport: tr}
if conf.DebugHTTP {
client = &http.Client{
Transport: &cfg.DebugTransport{
Transport: tr}}
}
req, err := http.NewRequest(verb, conf.DefaultCluster.Uri+path, bytes.NewBuffer([]byte(data))) req, err := http.NewRequest(verb, conf.DefaultCluster.Uri+path, bytes.NewBuffer([]byte(data)))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -197,7 +243,7 @@ func CallAPI(conf *cfg.Config, verb, path, data string) ([]byte, error) {
return body, nil return body, nil
} }
func prettyfiJson(conf *cfg.Config, raw []byte) error { func prettyfiJson(conf *cfg.Config, raw []byte) (string, error) {
if conf.HaveJQ { if conf.HaveJQ {
cmd := exec.CommandContext(context.Background(), "jq", "-C") cmd := exec.CommandContext(context.Background(), "jq", "-C")
cmd.Stdin = bytes.NewReader(raw) cmd.Stdin = bytes.NewReader(raw)
@@ -207,23 +253,18 @@ func prettyfiJson(conf *cfg.Config, raw []byte) error {
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return err return "", err
} }
fmt.Println(out.String()) return out.String(), nil
return nil
} }
var pretty bytes.Buffer var pretty bytes.Buffer
err := json.Indent(&pretty, raw, "", "\t") err := json.Indent(&pretty, raw, "", "\t")
if err != nil { if err != nil {
return fmt.Errorf("json parse error: %s", err) return "", fmt.Errorf("json parse error: %s", err)
} }
fmt.Println(pretty.String()) return pretty.String(), nil
return nil
} }
// interactively read arbitrary JSON data from STDIN, which is // interactively read arbitrary JSON data from STDIN, which is

View File

@@ -31,44 +31,62 @@ import (
type ClusterIndices map[string]map[string]*types.IndicesRecord type ClusterIndices map[string]map[string]*types.IndicesRecord
func ClusterList(conf *cfg.Config) error { type clusterReachable struct {
table := printer.NewTable(conf, 5, len(conf.Clusters)) reachable bool
err error
}
func ClusterList(conf *cfg.Config) error {
var mu sync.Mutex
var wg sync.WaitGroup
reachable := make(map[string]clusterReachable, len(conf.Clusters))
// check endpoints in parallel to speed things up
for name, cluster := range conf.Clusters {
wg.Add(1)
go func() {
defer wg.Done()
online, err := cluster.IsReachable()
mu.Lock()
reachable[name] = clusterReachable{reachable: online, err: err}
mu.Unlock()
}()
}
wg.Wait()
table := printer.NewTable(conf, 5, len(conf.Clusters))
table.Addheaders("cluster", "uri", "reachable", "current", "error") table.Addheaders("cluster", "uri", "reachable", "current", "error")
idx := 0 idx := 0
for name, cluster := range conf.Clusters { for name, cluster := range conf.Clusters {
reachable := "no" reachableStr := "no"
current := "no" current := "no"
errmsg := "" errmsg := ""
online, err := cluster.IsReachable() if reachable[name].reachable {
reachableStr = printer.Colorize(conf, "green", "reachable")
if online {
reachable = printer.Colorize(conf, "green", "reachable")
} }
if cluster.Default { if cluster.Default {
current = printer.Colorize(conf, "green", "yes") current = printer.Colorize(conf, "green", "yes")
if !online { if !reachable[name].reachable {
reachable = printer.Colorize(conf, "red", "no") reachableStr = printer.Colorize(conf, "red", "no")
errmsg = err.Error() errmsg = reachable[name].err.Error()
} }
} }
table.Entries[idx] = []string{name, cluster.Uri, reachable, current, errmsg} table.Entries[idx] = []string{name, cluster.Uri, reachableStr, current, errmsg}
idx++ idx++
} }
table.Sort() table.Sort()
if err := table.Print(); err != nil { return table.Print()
return err
}
return nil
} }
// We're using goroutines here to parallelize API requests, since we // We're using goroutines here to parallelize API requests, since we

View File

@@ -20,15 +20,19 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog"
"codeberg.org/scip/esctl/pkg/cfg" "codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/printer" "codeberg.org/scip/esctl/pkg/printer"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
func ClusterSettingsNames(conf *cfg.Config) ([]string, error) {
return validClusterSettings, nil
}
func ClusterSettingsList(conf *cfg.Config) error { func ClusterSettingsList(conf *cfg.Config) error {
res, err := conf.DefaultCluster.ES().Cluster.GetSettings(). res, err := conf.DefaultCluster.ES().Cluster.GetSettings().
FlatSettings(true).
Do(context.Background()) Do(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("failed to get cluster settings: %s", esErrorString(err)) return fmt.Errorf("failed to get cluster settings: %s", esErrorString(err))
@@ -48,19 +52,7 @@ func ClusterSettingsList(conf *cfg.Config) error {
} }
for topic, val := range settingshash { for topic, val := range settingshash {
data := map[string]any{} entries = append(entries, []string{topic, string(val)})
err := json.Unmarshal(val, &data)
if err != nil {
return fmt.Errorf("failed to unmarshall setting for topic %s: %s", topic, err)
}
paths := getJsonPath(map[string]string{}, data, topic)
slog.Debug("settings", topic, paths)
for setting, value := range paths {
entries = append(entries, []string{setting, fmt.Sprintf("%v", value)})
}
} }
table.Entries = entries table.Entries = entries

View File

@@ -0,0 +1,914 @@
/*
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 es
// manually extracted from https://www.elastic.co/docs/reference/elasticsearch/configuration-reference
var validClusterSettings []string = []string{
"xpack.security.audit.enabled",
"xpack.security.audit.logfile.events.include",
"xpack.security.audit.logfile.events.exclude",
"xpack.security.audit.logfile.events.emit_request_body",
"xpack.security.audit.logfile.emit_node_name",
"xpack.security.audit.logfile.emit_node_host_address",
"xpack.security.audit.logfile.emit_node_host_name",
"xpack.security.audit.logfile.emit_node_id",
"indices.breaker.total.use_real_memory",
"indices.breaker.total.limit",
"indices.breaker.fielddata.limit",
"indices.breaker.fielddata.overhead",
"indices.breaker.request.limit",
"indices.breaker.request.overhead",
"network.breaker.inflight_requests.limit",
"network.breaker.inflight_requests.overhead",
"script.max_compilations_rate",
"script.painless.regex.enabled",
"breaker.eql_sequence.limit",
"breaker.eql_sequence.overhead",
"breaker.eql_sequence.type",
"breaker.model_inference.limit",
"breaker.model_inference.overhead",
"breaker.model_inference.type",
"cluster.routing.allocation.enable",
"cluster.routing.allocation.same_shard.host",
"cluster.routing.allocation.total_shards_per_node",
"cluster.routing.allocation.node_concurrent_incoming_recoveries",
"cluster.routing.allocation.node_concurrent_outgoing_recoveries",
"cluster.routing.allocation.node_concurrent_recoveries",
"cluster.routing.allocation.node_initial_primaries_recoveries",
"cluster.routing.allocation.allow_rebalance",
"cluster.routing.rebalance.enable",
"cluster.routing.allocation.cluster_concurrent_rebalance",
"cluster.routing.allocation.type",
"cluster.routing.allocation.balance.threshold",
"cluster.routing.allocation.balance.shard",
"cluster.routing.allocation.balance.index",
"cluster.routing.allocation.balance.disk_usage",
"cluster.routing.allocation.balance.write_load",
"cluster.routing.allocation.disk.threshold_enabled",
"cluster.routing.allocation.disk.watermark.low",
"cluster.routing.allocation.disk.watermark.low.max_headroom",
"cluster.routing.allocation.disk.watermark.high",
"cluster.routing.allocation.disk.watermark.high.max_headroom",
"cluster.routing.allocation.disk.watermark.enable_for_single_data_node",
"cluster.routing.allocation.disk.watermark.flood_stage",
"cluster.routing.allocation.disk.watermark.flood_stage.max_headroom",
"cluster.routing.allocation.disk.watermark.flood_stage.frozen",
"cluster.routing.allocation.disk.watermark.flood_stage.frozen.max_headroom",
"cluster.info.update.interval",
"cluster.routing.allocation.awareness.attributes",
"cluster.routing.allocation.awareness.force.*",
"cluster.routing.allocation.include.{{attribute}}",
"cluster.routing.allocation.require.{{attribute}}",
"cluster.routing.allocation.exclude.{{attribute}}",
"cluster.routing.allocation.stats.cache.ttl",
"ccr.indices.recovery.max_bytes_per_sec",
"ccr.indices.recovery.max_concurrent_file_chunks",
"ccr.indices.recovery.chunk_size",
"ccr.indices.recovery.recovery_activity_timeout",
"ccr.indices.recovery.internal_action_timeout",
"data_streams.lifecycle.retention.max",
"data_streams.lifecycle.retention.default",
"data_streams.lifecycle.poll_interval",
"cluster.lifecycle.default.rollover",
"data_streams.lifecycle.target.merge.policy.merge_factor",
"data_streams.lifecycle.target.merge.policy.floor_segment",
"data_streams.lifecycle.signalling.error_retry_interval",
"data_streams.lifecycle.downsampling.max_indices_in_progress",
"dlm.frozen.transition.poll_interval",
"dlm.frozen.transition.thread_pool.size",
"dlm.frozen.transition.thread_pool.queue_size",
"dlm.frozen.cleanup.poll_interval",
"index.lifecycle.prefer_ilm",
"index.lifecycle.origination_date",
"index.dlm.frozen.created",
"discovery.seed_hosts",
"discovery.seed_providers",
"discovery.type",
"cluster.initial_master_nodes",
"discovery.cluster_formation_warning_timeout",
"discovery.find_peers_interval",
"discovery.probe.connect_timeout",
"discovery.probe.handshake_timeout",
"discovery.request_peers_timeout",
"discovery.find_peers_warning_timeout",
"discovery.seed_resolver.max_concurrent_resolvers",
"discovery.seed_resolver.timeout",
"cluster.auto_shrink_voting_configuration",
"cluster.election.duration",
"cluster.election.initial_timeout",
"cluster.election.max_timeout",
"cluster.fault_detection.follower_check.timeout",
"cluster.fault_detection.follower_check.retry_count",
"cluster.fault_detection.leader_check.interval",
"cluster.fault_detection.leader_check.timeout",
"cluster.fault_detection.leader_check.retry_count",
"cluster.follower_lag.timeout",
"cluster.max_voting_config_exclusions",
"cluster.publish.info_timeout",
"cluster.publish.timeout",
"cluster.discovery_configuration_check.interval",
"cluster.join_validation.cache_timeout",
"cluster.no_master_block",
"monitor.fs.health.enabled",
"monitor.fs.health.refresh_interval",
"monitor.fs.health.slow_path_logging_threshold",
"enrich.cache_size",
"enrich.coordinator_proxy.max_concurrent_requests",
"enrich.coordinator_proxy.max_lookups_per_request",
"enrich.coordinator_proxy.queue_capacity",
"enrich.fetch_size",
"enrich.max_force_merge_attempts",
"enrich.cleanup_period",
"enrich.max_concurrent_policy_executions",
"indices.fielddata.cache.size",
"health.master_history.has_master_lookup_timeframe",
"master_history.max_age",
"health.master_history.identity_changes_threshold",
"health.master_history.no_master_transitions_threshold",
"health.node.enabled",
"health.reporting.local.monitor.interval",
"health.ilm.max_time_on_action",
"health.ilm.max_time_on_step",
"health.ilm.max_retries_per_step",
"health.periodic_logger.enabled",
"health.periodic_logger.poll_interval",
"health.shard_capacity.unhealthy_threshold.yellow",
"health.shard_capacity.unhealthy_threshold.red",
"health.master_history.has_master_lookup_timeframe",
"master_history.max_age",
"health.master_history.identity_changes_threshold",
"health.master_history.no_master_transitions_threshold",
"health.node.enabled",
"health.reporting.local.monitor.interval",
"health.ilm.max_time_on_action",
"health.ilm.max_time_on_step",
"health.ilm.max_retries_per_step",
"health.periodic_logger.enabled",
"health.periodic_logger.poll_interval",
"health.shard_capacity.unhealthy_threshold.yellow",
"health.shard_capacity.unhealthy_threshold.red",
"indices.memory.index_buffer_size",
"indices.memory.min_index_buffer_size",
"indices.memory.max_index_buffer_size",
"indexing_pressure.memory.limit",
"xpack.ilm.enabled",
"indices.lifecycle.history_index_enabled",
"indices.lifecycle.poll_interval",
"indices.lifecycle.rollover.only_if_has_documents",
"index.lifecycle.indexing_complete",
"index.lifecycle.name",
"index.lifecycle.origination_date",
"index.lifecycle.parse_origination_date",
"index.lifecycle.step.wait_time_threshold",
"index.lifecycle.rollover_alias",
"action.auto_create_index",
"action.destructive_requires_name",
"cluster.indices.close.enable",
"stack.templates.enabled",
"xpack.profiling.enabled",
"xpack.profiling.templates.enabled",
"xpack.otel_data.registry.enabled",
"xpack.otel_data.histogram_field_type",
"reindex.remote.whitelist",
"reindex.remote.blocklist",
"cluster.reindex.pit.keep_alive",
"reindex.ssl.certificate",
"reindex.ssl.certificate_authorities",
"reindex.ssl.key",
"reindex.ssl.key_passphrase",
"reindex.ssl.keystore.key_password",
"reindex.ssl.keystore.password",
"reindex.ssl.keystore.path",
"reindex.ssl.keystore.type",
"reindex.ssl.secure_key_passphrase",
"reindex.ssl.keystore.secure_key_password",
"reindex.ssl.keystore.secure_password",
"reindex.ssl.truststore.password",
"reindex.ssl.truststore.path",
"reindex.ssl.truststore.secure_password",
"reindex.ssl.truststore.type",
"reindex.ssl.verification_mode",
"indices.recovery.max_bytes_per_sec",
"indices.recovery.max_concurrent_file_chunks",
"indices.recovery.max_concurrent_operations",
"indices.recovery.use_snapshots",
"indices.recovery.max_concurrent_snapshot_file_downloads",
"indices.recovery.max_concurrent_snapshot_file_downloads_per_node",
"node.bandwidth.recovery.disk.read",
"node.bandwidth.recovery.disk.write",
"node.bandwidth.recovery.network",
"node.bandwidth.recovery.factor.read",
"node.bandwidth.recovery.factor.write",
"node.bandwidth.recovery.operator.factor.read",
"node.bandwidth.recovery.operator.factor.write",
"node.bandwidth.recovery.operator.factor",
"node.bandwidth.recovery.operator.factor.max_overcommit",
"xpack.inference.query_timeout",
"xpack.inference.logging.reset_interval",
"xpack.inference.logging.wait_duration",
"xpack.inference.http.max_response_size",
"xpack.inference.http.max_total_connections",
"xpack.inference.http.max_route_connections",
"xpack.inference.http.connection_eviction_interval",
"xpack.inference.http.connection_eviction_max_idle_time",
"xpack.inference.http.request_executor.queue_capacity",
"xpack.inference.http.retry.initial_delay",
"xpack.inference.http.retry.max_delay_bound",
"xpack.inference.http.retry.timeout",
"xpack.inference.truncator.reduction_percentage",
"xpack.inference.endpoint.cache.enabled",
"xpack.inference.endpoint.cache.weight",
"xpack.inference.endpoint.cache.expiry_time",
"xpack.inference.oauth2.token_cache.enabled",
"xpack.inference.oauth2.token_cache.weight",
"xpack.inference.oauth2.token_cache.expiry_time",
"xpack.inference.ccm.cache.weight",
"xpack.inference.ccm.cache.expiry_time",
"xpack.license.self_generated.type",
"gateway.expected_data_nodes",
"gateway.recover_after_time",
"gateway.recover_after_data_nodes",
"node.roles: [ ml ]",
"xpack.ml.enabled",
"xpack.ml.inference_model.cache_size",
"xpack.ml.inference_model.time_to_live",
"xpack.ml.max_inference_processors",
"xpack.ml.max_machine_memory_percent",
"xpack.ml.max_model_memory_limit",
"xpack.ml.max_open_jobs",
"xpack.ml.nightly_maintenance_requests_per_second",
"xpack.ml.results_index_rollover_max_size",
"xpack.ml.anomalies.heal_reindexed_v7.enabled",
"xpack.ml.idle_job_auto_close_timeout",
"xpack.ml.node_concurrent_job_allocations",
"xpack.ml.enable_config_migration",
"xpack.ml.max_anomaly_records",
"xpack.ml.max_lazy_ml_nodes",
"xpack.ml.max_ml_node_size",
"xpack.ml.trained_models.graph_validation_enabled",
"xpack.ml.model_repository",
"xpack.ml.persist_results_max_retries",
"xpack.ml.process_connect_timeout",
"xpack.ml.use_auto_machine_memory_percent",
"xpack.monitoring.enabled",
"xpack.monitoring.collection.enabled",
"xpack.monitoring.collection.interval",
"xpack.monitoring.elasticsearch.collection.enabled",
"xpack.monitoring.collection.cluster.stats.timeout",
"xpack.monitoring.collection.node.stats.timeout",
"xpack.monitoring.collection.indices",
"xpack.monitoring.collection.index.stats.timeout",
"xpack.monitoring.collection.index.recovery.active_only",
"xpack.monitoring.collection.index.recovery.timeout",
"xpack.monitoring.history.duration",
"xpack.monitoring.exporters",
"cluster_alerts.management.enabled",
"wait_master.timeout",
"auth.username",
"auth.secure_password",
"connection.timeout",
"connection.read_timeout",
"proxy.base_path",
"index.name.time_format",
"cluster_alerts.management.enabled",
"cluster_alerts.management.blacklist",
"xpack.monitoring.exporters.$NAME.ssl.supported_protocols",
"xpack.monitoring.exporters.$NAME.ssl.verification_mode",
"xpack.monitoring.exporters.$NAME.ssl.cipher_suites",
"xpack.monitoring.exporters.$NAME.ssl.key",
"xpack.monitoring.exporters.$NAME.ssl.key_passphrase",
"xpack.monitoring.exporters.$NAME.ssl.secure_key_passphrase",
"xpack.monitoring.exporters.$NAME.ssl.certificate",
"xpack.monitoring.exporters.$NAME.ssl.certificate_authorities",
"xpack.monitoring.exporters.$NAME.ssl.keystore.path",
"xpack.monitoring.exporters.$NAME.ssl.keystore.password",
"xpack.monitoring.exporters.$NAME.ssl.keystore.secure_password",
"xpack.monitoring.exporters.$NAME.ssl.keystore.key_password",
"xpack.monitoring.exporters.$NAME.ssl.keystore.secure_key_password",
"xpack.monitoring.exporters.$NAME.ssl.truststore.path",
"xpack.monitoring.exporters.$NAME.ssl.truststore.password",
"xpack.monitoring.exporters.$NAME.ssl.truststore.secure_password",
"xpack.monitoring.exporters.$NAME.ssl.keystore.path",
"xpack.monitoring.exporters.$NAME.ssl.keystore.type",
"xpack.monitoring.exporters.$NAME.ssl.keystore.password",
"xpack.monitoring.exporters.$NAME.ssl.keystore.secure_password",
"xpack.monitoring.exporters.$NAME.ssl.keystore.key_password",
"xpack.monitoring.exporters.$NAME.ssl.keystore.secure_key_password",
"xpack.monitoring.exporters.$NAME.ssl.truststore.path",
"xpack.monitoring.exporters.$NAME.ssl.truststore.type",
"xpack.monitoring.exporters.$NAME.ssl.truststore.password",
"xpack.monitoring.exporters.$NAME.ssl.truststore.secure_password",
"network.host",
"http.port",
"transport.port",
"remote_cluster.port",
"0.0.0.0",
"network.bind_host",
"network.publish_host",
"network.tcp.keep_alive",
"network.tcp.keep_idle",
"network.tcp.keep_interval",
"network.tcp.keep_count",
"network.tcp.no_delay",
"network.tcp.reuse_address",
"network.tcp.send_buffer_size",
"network.tcp.receive_buffer_size",
"http.host",
"http.bind_host",
"http.publish_host",
"http.publish_port",
"http.max_content_length",
"http.max_initial_line_length",
"http.max_header_size",
"http.compression",
"http.compression_level",
"http.cors.enabled",
"http.detailed_errors.enabled",
"http.pipelining.max_events",
"http.max_warning_header_count",
"http.max_warning_header_size",
"http.tcp.keep_alive",
"http.tcp.keep_idle",
"http.tcp.keep_interval",
"http.tcp.keep_count",
"http.tcp.no_delay",
"http.tcp.reuse_address",
"http.tcp.send_buffer_size",
"http.tcp.receive_buffer_size",
"http.client_stats.enabled",
"http.client_stats.closed_channels.max_count",
"http.client_stats.closed_channels.max_age",
"transport.host",
"transport.bind_host",
"transport.publish_host",
"transport.publish_port",
"transport.connect_timeout",
"transport.compress",
"transport.compression_scheme",
"transport.tcp.keep_alive",
"transport.tcp.keep_idle",
"transport.tcp.keep_interval",
"transport.tcp.keep_count",
"transport.tcp.no_delay",
"transport.tcp.reuse_address",
"transport.tcp.send_buffer_size",
"transport.tcp.receive_buffer_size",
"transport.ping_schedule",
"remote_cluster_server.enabled",
"remote_cluster.host",
"remote_cluster.bind_host",
"remote_cluster.publish_host",
"remote_cluster.publish_port",
"remote_cluster.tcp.keep_alive",
"remote_cluster.tcp.keep_idle",
"remote_cluster.tcp.keep_interval",
"remote_cluster.tcp.keep_count",
"remote_cluster.tcp.no_delay",
"remote_cluster.tcp.reuse_address",
"remote_cluster.tcp.send_buffer_size",
"remote_cluster.tcp.receive_buffer_size",
"org.elasticsearch.transport.InboundHandler",
"org.elasticsearch.transport.OutboundHandler",
"org.elasticsearch.common.network.ThreadWatchdog",
"network.thread.watchdog.interval",
"network.thread.watchdog.quiet_time",
"indices.queries.cache.size",
"index.queries.cache.enabled",
"vectors.indexing.use_gpu",
"cluster.remote.initial_connect_timeout",
"cluster.remote.node.attr",
"cluster.remote.signing.certificate_authorities",
"cluster.remote.signing.truststore.path",
"cluster.remote.signing.truststore.secure_password",
"cluster.remote.signing.truststore.algorithm",
"cluster.remote.signing.truststore.type",
"cluster.remote.signing.diagnose.trust",
"indices.query.bool.max_clause_count",
"search.max_buckets",
"search.aggs.only_allowed_metric_scripts",
"search.aggs.allowed_inline_metric_scripts",
"search.aggs.allowed_stored_metric_scripts",
"indices.query.bool.max_nested_depth",
"search.task_watchdog.enabled",
"search.task_watchdog.coordinator_threshold",
"search.task_watchdog.data_node_threshold",
"search.task_watchdog.interval",
"search.task_watchdog.cooldown_period",
"xpack.security.enabled",
"xpack.security.autoconfiguration.enabled",
"xpack.security.enrollment.enabled",
"xpack.security.hide_settings",
"xpack.security.fips_mode.enabled",
"xpack.security.fips_mode.required_providers",
"xpack.security.authc.password_hashing.algorithm",
"xpack.security.authc.anonymous.username",
"xpack.security.authc.anonymous.roles",
"xpack.security.authc.anonymous.authz_exception",
"xpack.security.automata.max_determinized_states",
"xpack.security.automata.cache.enabled",
"xpack.security.automata.cache.size",
"xpack.security.automata.cache.ttl",
"xpack.security.dls_fls.enabled",
"xpack.security.dls.bitset.cache.ttl",
"xpack.security.dls.bitset.cache.size",
"xpack.security.authc.token.enabled",
"xpack.security.authc.token.timeout",
"xpack.security.authc.api_key.enabled",
"xpack.security.authc.api_key.cache.ttl",
"xpack.security.authc.api_key.cache.max_keys",
"xpack.security.authc.api_key.cache.hash_algo",
"xpack.security.authc.api_key.delete.retention_period",
"xpack.security.authc.api_key.delete.interval",
"xpack.security.authc.api_key.delete.timeout",
"xpack.security.authc.api_key.hashing.algorithm",
"xpack.security.authc.realms.saml.*",
"xpack.security.authc.realms.oidc.*",
"xpack.security.authc.realms.kerberos.*",
"xpack.security.authc.realms.jwt.*",
"cache.ttl",
"cache.max_users",
"cache.hash_algo",
"authentication.enabled",
"cache.ttl",
"cache.max_users",
"cache.hash_algo",
"authentication.enabled",
"load_balance.type",
"load_balance.cache_ttl",
"user_search.base_dn",
"user_search.scope",
"user_search.filter",
"user_search.attribute",
"user_search.pool.enabled",
"user_search.pool.size",
"user_search.pool.initial_size",
"user_search.pool.health_check.enabled",
"user_search.pool.health_check.dn",
"user_search.pool.health_check.interval",
"group_search.base_dn",
"group_search.scope",
"group_search.filter",
"group_search.user_attribute",
"files.role_mapping",
"timeout.tcp_connect",
"timeout.tcp_read",
"timeout.response",
"timeout.ldap_search",
"ssl.key",
"ssl.key_passphrase",
"ssl.secure_key_passphrase",
"ssl.certificate",
"ssl.certificate_authorities",
"ssl.keystore.path",
"ssl.keystore.type",
"ssl.keystore.password",
"ssl.keystore.secure_password",
"ssl.keystore.key_password",
"ssl.keystore.secure_key_password",
"ssl.truststore.path",
"ssl.truststore.password",
"ssl.truststore.secure_password",
"ssl.truststore.type",
"ssl.verification_mode",
"ssl.supported_protocols",
"ssl.cipher_suites",
"cache.ttl",
"cache.max_users",
"cache.hash_algo",
"authentication.enabled",
"load_balance.type",
"load_balance.cache_ttl",
"files.role_mapping",
"user_search.base_dn",
"user_search.scope",
"user_search.filter",
"user_search.upn_filter",
"user_search.down_level_filter",
"user_search.pool.enabled",
"user_search.pool.size",
"user_search.pool.initial_size",
"user_search.pool.health_check.enabled",
"user_search.pool.health_check.dn",
"user_search.pool.health_check.interval",
"group_search.base_dn",
"group_search.scope",
"timeout.tcp_connect",
"timeout.tcp_read",
"timeout.response",
"timeout.ldap_search",
"ssl.certificate",
"ssl.certificate_authorities",
"ssl.key",
"ssl.key_passphrase",
"ssl.secure_key_passphrase",
"ssl.keystore.key_password",
"ssl.keystore.secure_key_password",
"ssl.keystore.password",
"ssl.secure_keystore.password",
"ssl.keystore.path",
"ssl.keystore.type",
"ssl.truststore.password",
"ssl.truststore.secure_password",
"ssl.truststore.path",
"ssl.truststore.type",
"ssl.verification_mode",
"ssl.supported_protocols",
"ssl.cipher_suites",
"cache.ttl",
"cache.max_users",
"cache.hash_algo",
"authentication.enabled",
"truststore.algorithm",
"truststore.password",
"truststore.secure_password",
"truststore.path",
"files.role_mapping",
"cache.ttl",
"cache.max_users",
"delegation.enabled",
"idp.entity_id",
"idp.metadata.path",
"idp.metadata.http.fail_on_error",
"idp.metadata.http.connect_timeout",
"idp.metadata.http.read_timeout",
"idp.metadata.http.refresh",
"idp.metadata.http.minimum_refresh",
"idp.use_single_logout",
"sp.entity_id",
"sp.acs",
"sp.logout",
"attributes.principal",
"attributes.groups",
"attributes.name",
"attributes.mail",
"attributes.dn",
"attribute_patterns.principal",
"attribute_patterns.groups",
"attribute_patterns.name",
"attribute_patterns.mail",
"attribute_patterns.dn",
"attribute_delimiters.groups",
"nameid.allow_create",
"nameid.sp_qualifier",
"signing.saml_messages",
"signing.key",
"signing.secure_key_passphrase",
"signing.certificate",
"signing.keystore.path",
"signing.keystore.type",
"signing.keystore.alias",
"signing.keystore.secure_password",
"signing.keystore.secure_key_password",
"encryption.key",
"encryption.secure_key_passphrase",
"encryption.certificate",
"encryption.keystore.path",
"encryption.keystore.type",
"encryption.keystore.alias",
"encryption.keystore.secure_password",
"encryption.keystore.secure_key_password",
"ssl.key",
"ssl.key_passphrase",
"ssl.secure_key_passphrase",
"ssl.certificate",
"ssl.certificate_authorities",
"ssl.keystore.path",
"ssl.keystore.type",
"ssl.keystore.password",
"ssl.keystore.secure_password",
"ssl.keystore.key_password",
"ssl.keystore.secure_key_password",
"ssl.truststore.path",
"ssl.truststore.type",
"ssl.truststore.password",
"ssl.truststore.secure_password",
"ssl.verification_mode",
"ssl.supported_protocols",
"ssl.cipher_suites",
"keytab.path",
"krb.debug",
"cache.ttl",
"cache.max_users",
"op.issuer",
"op.authorization_endpoint",
"op.token_endpoint",
"op.userinfo_endpoint",
"op.endsession_endpoint",
"op.jwkset_path",
"rp.client_id",
"rp.client_secret",
"rp.client_auth_method",
"rp.client_auth_jwt_signature_algorithm",
"rp.redirect_uri",
"rp.response_type",
"rp.signature_algorithm",
"rp.requested_scopes",
"rp.post_logout_redirect_uri",
"claims.principal",
"claims.groups",
"claims.name",
"claims.mail",
"claims.dn",
"claim_patterns.principal",
"claim_patterns.groups",
"claim_patterns.name",
"claim_patterns.mail",
"claim_patterns.dn",
"http.proxy.host",
"http.proxy.scheme",
"http.proxy.port",
"http.connect_timeout",
"http.connection_read_timeout",
"http.socket_timeout",
"http.max_connections",
"http.max_endpoint_connections",
"http.tcp.keep_alive",
"http.connection_pool_ttl",
"ssl.key",
"ssl.key_passphrase",
"ssl.secure_key_passphrase",
"ssl.certificate",
"ssl.certificate_authorities",
"ssl.keystore.path",
"ssl.keystore.type",
"ssl.keystore.password",
"ssl.keystore.secure_password",
"ssl.keystore.key_password",
"ssl.keystore.secure_key_password",
"ssl.truststore.path",
"ssl.truststore.type",
"ssl.truststore.password",
"ssl.truststore.secure_password",
"ssl.verification_mode",
"ssl.supported_protocols",
"ssl.cipher_suites",
"fallback_claims.sub",
"fallback_claims.aud",
"claims.dn",
"claim_patterns.dn",
"claims.groups",
"claim_patterns.group",
"claims.mail",
"claim_patterns.mail",
"claims.name",
"claim_patterns.name",
"claims.principal",
"claim_patterns.principal",
"client_authentication.type",
"client_authentication.shared_secret",
"client_authentication.rotation_grace_period",
"http.proxy.host",
"http.proxy.scheme",
"http.proxy.port",
"http.connect_timeout",
"http.connection_read_timeout",
"http.socket_timeout",
"http.max_connections",
"http.max_endpoint_connections",
"jwt.cache.size",
"jwt.cache.ttl",
"pkc_jwkset_reload.enabled",
"pkc_jwkset_reload.file_interval",
"pkc_jwkset_reload.url_interval_min",
"pkc_jwkset_reload.url_interval_max",
"ssl.key",
"ssl.key_passphrase",
"ssl.secure_key_passphrase",
"ssl.certificate",
"ssl.certificate_authorities",
"ssl.keystore.path",
"ssl.keystore.type",
"ssl.keystore.password",
"ssl.keystore.secure_password",
"ssl.keystore.key_password",
"ssl.keystore.secure_key_password",
"ssl.truststore.path",
"ssl.truststore.type",
"ssl.truststore.password",
"ssl.truststore.secure_password",
"ssl.verification_mode",
"ssl.supported_protocols",
"ssl.cipher_suites",
"xpack.security.ssl.diagnose.trust",
"xpack.security.http.ssl.enabled",
"xpack.security.http.ssl.supported_protocols",
"xpack.security.http.ssl.client_authentication",
"xpack.security.http.ssl.verification_mode",
"xpack.security.http.ssl.cipher_suites",
"xpack.security.http.ssl.key",
"xpack.security.http.ssl.key_passphrase",
"xpack.security.http.ssl.secure_key_passphrase",
"xpack.security.http.ssl.certificate",
"xpack.security.http.ssl.certificate_authorities",
"xpack.security.http.ssl.keystore.path",
"xpack.security.http.ssl.keystore.password",
"xpack.security.http.ssl.keystore.secure_password",
"xpack.security.http.ssl.keystore.key_password",
"xpack.security.http.ssl.keystore.secure_key_password",
"xpack.security.http.ssl.truststore.path",
"xpack.security.http.ssl.truststore.password",
"xpack.security.http.ssl.truststore.secure_password",
"xpack.security.http.ssl.keystore.path",
"xpack.security.http.ssl.keystore.type",
"xpack.security.http.ssl.keystore.password",
"xpack.security.http.ssl.keystore.secure_password",
"xpack.security.http.ssl.keystore.key_password",
"xpack.security.http.ssl.keystore.secure_key_password",
"xpack.security.http.ssl.truststore.path",
"xpack.security.http.ssl.truststore.type",
"xpack.security.http.ssl.truststore.password",
"xpack.security.http.ssl.truststore.secure_password",
"xpack.security.transport.ssl.enabled",
"xpack.security.transport.ssl.supported_protocols",
"xpack.security.transport.ssl.client_authentication",
"xpack.security.transport.ssl.verification_mode",
"xpack.security.transport.ssl.cipher_suites",
"xpack.security.transport.ssl.trust_restrictions.x509_fields",
"xpack.security.transport.ssl.handshake_timeout",
"xpack.security.transport.ssl.key",
"xpack.security.transport.ssl.key_passphrase",
"xpack.security.transport.ssl.secure_key_passphrase",
"xpack.security.transport.ssl.certificate",
"xpack.security.transport.ssl.certificate_authorities",
"xpack.security.loginAssistanceMessage",
"xpack.security.transport.ssl.keystore.path",
"xpack.security.transport.ssl.keystore.password",
"xpack.security.transport.ssl.keystore.secure_password",
"xpack.security.transport.ssl.keystore.key_password",
"xpack.security.transport.ssl.keystore.secure_key_password",
"xpack.security.transport.ssl.truststore.path",
"xpack.security.transport.ssl.truststore.password",
"xpack.security.transport.ssl.truststore.secure_password",
"xpack.security.transport.ssl.keystore.path",
"xpack.security.transport.ssl.keystore.type",
"xpack.security.transport.ssl.keystore.password",
"xpack.security.transport.ssl.keystore.secure_password",
"xpack.security.transport.ssl.keystore.key_password",
"xpack.security.transport.ssl.keystore.secure_key_password",
"xpack.security.transport.ssl.truststore.path",
"xpack.security.transport.ssl.truststore.type",
"xpack.security.transport.ssl.truststore.password",
"xpack.security.transport.ssl.truststore.secure_password",
"xpack.security.remote_cluster_server.ssl.enabled",
"xpack.security.remote_cluster_server.ssl.supported_protocols",
"xpack.security.remote_cluster_server.ssl.client_authentication",
"xpack.security.remote_cluster_server.ssl.verification_mode",
"xpack.security.remote_cluster_server.ssl.cipher_suites",
"xpack.security.remote_cluster_server.ssl.handshake_timeout",
"xpack.security.remote_cluster_server.ssl.key",
"xpack.security.remote_cluster_server.ssl.secure_key_passphrase",
"xpack.security.remote_cluster_server.ssl.certificate",
"xpack.security.remote_cluster_server.ssl.certificate_authorities",
"xpack.security.remote_cluster_server.ssl.keystore.path",
"xpack.security.remote_cluster_server.ssl.keystore.secure_password",
"xpack.security.remote_cluster_server.ssl.keystore.secure_key_password",
"xpack.security.remote_cluster_server.ssl.truststore.path",
"xpack.security.remote_cluster_server.ssl.truststore.secure_password",
"xpack.security.remote_cluster_server.ssl.keystore.path",
"xpack.security.remote_cluster_server.ssl.keystore.type",
"xpack.security.remote_cluster_server.ssl.keystore.secure_password",
"xpack.security.remote_cluster_server.ssl.keystore.secure_key_password",
"xpack.security.remote_cluster_server.ssl.truststore.path",
"xpack.security.remote_cluster_server.ssl.truststore.type",
"xpack.security.remote_cluster_server.ssl.truststore.secure_password",
"xpack.security.remote_cluster_client.ssl.enabled",
"xpack.security.remote_cluster_client.ssl.supported_protocols",
"xpack.security.remote_cluster_client.ssl.verification_mode",
"xpack.security.remote_cluster_client.ssl.cipher_suites",
"xpack.security.remote_cluster_client.ssl.handshake_timeout",
"xpack.security.remote_cluster_client.ssl.key",
"xpack.security.remote_cluster_client.ssl.secure_key_passphrase",
"xpack.security.remote_cluster_client.ssl.certificate",
"xpack.security.remote_cluster_client.ssl.certificate_authorities",
"xpack.security.remote_cluster_client.ssl.keystore.path",
"xpack.security.remote_cluster_client.ssl.keystore.secure_password",
"xpack.security.remote_cluster_client.ssl.keystore.secure_key_password",
"xpack.security.remote_cluster_client.ssl.truststore.path",
"xpack.security.remote_cluster_client.ssl.truststore.secure_password",
"xpack.security.remote_cluster_client.ssl.keystore.path",
"xpack.security.remote_cluster_client.ssl.keystore.type",
"xpack.security.remote_cluster_client.ssl.keystore.secure_password",
"xpack.security.remote_cluster_client.ssl.keystore.secure_key_password",
"xpack.security.remote_cluster_client.ssl.truststore.path",
"xpack.security.remote_cluster_client.ssl.truststore.type",
"xpack.security.remote_cluster_client.ssl.truststore.secure_password",
"xpack.security.transport.filter.allow",
"xpack.security.transport.filter.deny",
"xpack.security.http.filter.allow",
"xpack.security.http.filter.deny",
"transport.profiles.$PROFILE.xpack.security.filter.allow",
"transport.profiles.$PROFILE.xpack.security.filter.deny",
"xpack.security.remote_cluster.filter.allow",
"xpack.security.remote_cluster.filter.deny",
"indices.requests.cache.size",
"indices.requests.cache.expire",
"snapshot.max_concurrent_operations",
"repositories.default_repository",
"slm.history_index_enabled",
"slm.retention_schedule",
"slm.retention_duration",
"slm.health.failed_snapshot_warn_threshold",
"node.roles: [ transform ]",
"xpack.transform.enabled",
"xpack.transform.num_transform_failure_retries",
"xpack.watcher.enabled",
"xpack.watcher.encrypt_sensitive_data",
"xpack.watcher.encryption_key",
"xpack.watcher.max.history.record.size",
"xpack.watcher.trigger.schedule.engine",
"xpack.watcher.history.cleaner_service.enabled",
"xpack.http.proxy.host",
"xpack.http.proxy.port",
"xpack.http.proxy.scheme",
"xpack.http.default_connection_timeout",
"xpack.http.default_read_timeout",
"xpack.http.tcp.keep_alive",
"xpack.http.connection_pool_ttl",
"xpack.http.max_response_size",
"xpack.http.whitelist",
"xpack.http.ssl.supported_protocols",
"xpack.http.ssl.verification_mode",
"xpack.http.ssl.cipher_suites",
"xpack.http.ssl.key",
"xpack.http.ssl.secure_key_passphrase",
"xpack.http.ssl.certificate",
"xpack.http.ssl.certificate_authorities",
"xpack.http.ssl.keystore.path",
"xpack.http.ssl.keystore.secure_password",
"xpack.http.ssl.keystore.secure_key_password",
"xpack.http.ssl.truststore.path",
"xpack.http.ssl.truststore.secure_password",
"xpack.http.ssl.keystore.path",
"xpack.http.ssl.keystore.type",
"xpack.http.ssl.keystore.secure_password",
"xpack.http.ssl.keystore.secure_key_password",
"xpack.http.ssl.truststore.path",
"xpack.http.ssl.truststore.type",
"xpack.http.ssl.truststore.secure_password",
"xpack.notification.email.default_account",
"xpack.notification.email.recipient_allowlist",
"xpack.notification.email.account",
"xpack.notification.email.account.domain_allowlist",
"email_defaults.*",
"smtp.auth",
"smtp.host",
"smtp.port",
"smtp.user",
"smtp.secure_password",
"smtp.starttls.enable",
"smtp.starttls.required",
"smtp.ssl.trust",
"smtp.timeout",
"smtp.connection_timeout",
"smtp.write_timeout",
"smtp.local_address",
"smtp.local_port",
"smtp.send_partial",
"smtp.wait_on_quit",
"xpack.notification.email.html.sanitization.allow",
"xpack.notification.email.html.sanitization.disallow",
"xpack.notification.email.html.sanitization.enabled",
"xpack.notification.email.ssl.supported_protocols",
"xpack.notification.email.ssl.verification_mode",
"xpack.notification.email.ssl.cipher_suites",
"xpack.notification.email.ssl.key",
"xpack.notification.email.ssl.secure_key_passphrase",
"xpack.notification.email.ssl.certificate",
"xpack.notification.email.ssl.certificate_authorities",
"xpack.notification.email.ssl.keystore.path",
"xpack.notification.email.ssl.keystore.secure_password",
"xpack.notification.email.ssl.keystore.secure_key_password",
"xpack.notification.email.ssl.truststore.path",
"xpack.notification.email.ssl.truststore.secure_password",
"xpack.notification.email.ssl.keystore.path",
"xpack.notification.email.ssl.keystore.type",
"xpack.notification.email.ssl.keystore.secure_password",
"xpack.notification.email.ssl.keystore.secure_key_password",
"xpack.notification.email.ssl.truststore.path",
"xpack.notification.email.ssl.truststore.type",
"xpack.notification.email.ssl.truststore.secure_password",
"xpack.notification.slack",
"xpack.notification.slack.default_account",
"xpack.notification.slack.account",
"xpack.notification.jira.default_account",
"xpack.notification.jira.account",
"xpack.notification.pagerduty",
"xpack.notification.pagerduty.default_account",
"xpack.notification.pagerduty.account",
"xpack.notification.webhook.additional_token_enabled",
}

View File

@@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"strings" "strings"
"codeberg.org/scip/esctl/pkg/cfg" "codeberg.org/scip/esctl/pkg/cfg"
@@ -330,44 +329,3 @@ func splitArg(arg string) (string, string) {
return parts[0], parts[1] return parts[0], parts[1]
} }
} }
// recursively traverse the raw settings hash and build a flat map
// consisting of the translated path and its value.
//
// e.g.
// logger:
//
// org:
// elasticsearch:
// transport:
// OutboundHandler: "ERROR"
//
// gets:
//
// logger.org.elasticsearch.transport.OutboundHandler: "ERROR"
func getJsonPath(paths map[string]string, raw map[string]any, topic string) map[string]string {
for name, data := range raw {
path := topic + "." + name
switch value := data.(type) {
case string:
paths[path] = value
case *string:
paths[path] = *value
case int:
paths[path] = strconv.Itoa(value)
case *int:
paths[path] = strconv.Itoa(*value)
case map[string]any:
paths = getJsonPath(paths, value, path)
case []any:
val := []string{}
for _, item := range value {
val = append(val, fmt.Sprintf("%v", item))
}
paths[path] = strings.Join(val, ",")
}
}
return paths
}

View File

@@ -28,7 +28,7 @@ type Tpl struct {
} }
func getIndexTemplateSettings(conf *cfg.Config, tplname string, table *printer.Table) error { func getIndexTemplateSettings(conf *cfg.Config, tplname string, table *printer.Table) error {
raw, err := CallAPI(conf, "GET", "/_index_template/"+tplname, "") raw, err := CallAPI(conf, "GET", "/_index_template/"+tplname+"?flat_settings", "")
if err != nil { if err != nil {
return err return err
} }
@@ -39,9 +39,11 @@ func getIndexTemplateSettings(conf *cfg.Config, tplname string, table *printer.T
} }
if conf.Debug { if conf.Debug {
if err := prettyfiJson(conf, raw); err != nil { output, err := prettyfiJson(conf, raw)
if err != nil {
return err return err
} }
fmt.Println(output)
} }
if len(data.IndexTemplates) == 0 { if len(data.IndexTemplates) == 0 {
@@ -50,11 +52,7 @@ func getIndexTemplateSettings(conf *cfg.Config, tplname string, table *printer.T
tpl := data.IndexTemplates[0].IndexTemplate.Template.Settings tpl := data.IndexTemplates[0].IndexTemplate.Template.Settings
for topic, val := range tpl { for topic, val := range tpl {
paths := getJsonPath(map[string]string{}, val.(map[string]any), topic) table.Entries = append(table.Entries, []string{topic, fmt.Sprintf("%v", val)})
for setting, value := range paths {
table.Entries = append(table.Entries, []string{setting, fmt.Sprintf("%v", value)})
}
} }
return nil return nil

59
pkg/es/license.go Normal file
View File

@@ -0,0 +1,59 @@
/*
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 es
import (
"context"
"fmt"
"log/slog"
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/printer"
)
func LicenseShow(conf *cfg.Config) error {
res, err := conf.DefaultCluster.ES().License.Get().
Do(context.Background())
if err != nil {
return fmt.Errorf("failed to get license: %s", esErrorString(err))
}
slog.Debug("license show", "license", res)
table := printer.NewTableEmpty(conf).WithHeaders("license setting", "value")
lic := res.License
var maxnodes = "infinite"
if lic.MaxNodes != nil {
maxnodes = fmt.Sprintf("%d", *lic.MaxNodes)
}
table.Entries = [][]string{
{"UID", lic.Uid},
{"Issued to", lic.IssuedTo},
{"Expires", lic.ExpiryDate.(string)},
{"Issued", lic.IssueDate.(string)},
{"Max nodes", maxnodes},
{"Max resource units", fmt.Sprintf("%d", *lic.MaxResourceUnits)},
{"Type", lic.Type.Name},
{"Status", lic.Status.Name},
}
return table.Print()
}

View File

@@ -53,6 +53,24 @@ func NewTable(conf *cfg.Config, columns, rows int) *Table {
return &table return &table
} }
func NewTableEmpty(conf *cfg.Config) *Table {
table := Table{Mode: conf.Output, maxwidth: cfg.GetTermWidth()}
table.alignInts = conf.AlignInts
return &table
}
func (table *Table) WithHeaders(headers ...string) *Table {
count := len(headers)
table.Entries = [][]string{}
table.lenHeaders = make([]int, count)
table.Headers = make([]string, count)
table.Addheaders(headers...)
return table
}
func (data *Table) Print() error { func (data *Table) Print() error {
switch data.Mode { switch data.Mode {
case "markdown", "md": case "markdown", "md":
@@ -157,7 +175,7 @@ func (data *Table) PrintTSV() error {
for idx, entry := range entries { for idx, entry := range entries {
length := visibleLen(entry) length := visibleLen(entry)
if length+currentWidth > data.maxwidth { if length+currentWidth > data.maxwidth && data.maxwidth-currentWidth > 1 {
// text is too wide to be put into one line, wrap it // text is too wide to be put into one line, wrap it
wrapper := wordwrap.Wrapper(data.maxwidth-currentWidth, false) wrapper := wordwrap.Wrapper(data.maxwidth-currentWidth, false)
wrapped := wrapper(entry) wrapped := wrapper(entry)