diff --git a/pkg/es/cluster_settings.go b/pkg/es/cluster_settings.go new file mode 100644 index 0000000..2cf3b9f --- /dev/null +++ b/pkg/es/cluster_settings.go @@ -0,0 +1,145 @@ +/* +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 . +*/ +package es + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + + "codeberg.org/scip/esctl/pkg/cfg" + "github.com/urfave/cli/v3" +) + +// 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(raw map[string]any, topic string) map[string]string { + paths := map[string]string{} + + for name, data := range raw { + path := topic + "." + name + switch value := data.(type) { + case string: + paths[path] = value + case map[string]any: + paths = getJsonPath(value, path) + } + } + + return paths +} + +func ClusterSettingsList(conf *cfg.Config) error { + res, err := conf.DefaultCluster.ES.Cluster.GetSettings(). + Header("content-type", "application/json"). + Header("accept", "application/json"). + Do(context.Background()) + if err != nil { + return fmt.Errorf("failed to get cluster settings: %s", err) + } + + table := NewTable(2, 0) + table.Addheaders("setting", "value") + entries := [][]string{} + + settingshash := res.Persistent // == map[string]json.RawMessage + + switch { + case conf.Transient: + settingshash = res.Transient + case conf.Default: + settingshash = res.Defaults + } + + for topic, val := range settingshash { + data := map[string]any{} + + err := json.Unmarshal(val, &data) + if err != nil { + return fmt.Errorf("failed to unmarshall setting for topic %s: %s", topic, err) + } + + paths := getJsonPath(data, topic) + slog.Debug("settings", topic, paths) + + for setting, value := range paths { + entries = append(entries, []string{ + fmt.Sprintf("%s", setting), + fmt.Sprintf("%v", value), + }) + + } + } + + table.entries = entries + table.Sort() + + if err := table.PrintMarkdown(); err != nil { + return err + } + + return nil +} + +func ClusterSettingsSet(conf *cfg.Config, args cli.Args) error { + put := conf.Clusters[conf.CurrentCluster].ES.Cluster.PutSettings() + + for _, arg := range args.Slice() { + setting, value := splitArg(arg) + + switch { + case conf.Transient: + message, err := json.Marshal(value) + if err != nil { + return fmt.Errorf("failed to marshall transient value <%v> to valid JSON: %s", value, err) + } + put.AddTransient(setting, message) + case conf.Persistent: + fallthrough + default: + message, err := json.Marshal(value) + if err != nil { + return fmt.Errorf("failed to marshall persistent value <%v> to valid JSON: %s", value, err) + } + put.AddPersistent(setting, message) + } + } + + _, err := put. + Header("content-type", "application/json"). + Header("accept", "application/json"). + Do(context.Background()) + + if err != nil { + return fmt.Errorf("failed to set settings: %s", err) + } + + return nil +}