/* 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 }