Compare commits

...

7 Commits

17 changed files with 1239 additions and 137 deletions

View File

@@ -23,6 +23,8 @@ Features:
- Shell completion support (bash, zsh and fish). Put this into your
rc: `source <(esctl completion bash)`.
- 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
logical condition (OR, AND), use PIT, limit datetime (ES date math
can be used), etc. It is however not yet possible to create
@@ -44,10 +46,12 @@ Features:
- API documentation (`api list` and `api show <path>`) with
interactive markdown pager for endpoint documentation.
- Repl: this is an interactive REPL (read eval print loop) towards the
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
has line editing and history support. If `jq` is installed output
JSON will be syntax highlighted.
has line editing and history support. If `jq` is installed output
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
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
@@ -546,6 +550,8 @@ index - manage indicies
create - create a new index template
update - update a new index template
delete - delete an index template
license - manage cluster license
show - show details about the cluster license
node - manage nodes
list - list nodes
show - show details about a node
@@ -567,7 +573,7 @@ task - manage tasks
version - show esctl version information
debug - developer only
help-jsonpath - show jsonpath help
help-command-overview - show overview of all available commands
help-usage - show overview of all available commands
```
# Development

View File

@@ -83,6 +83,16 @@ func ApiRepl(conf *cfg.Config) *cli.Command {
Aliases: []string{"shell"},
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 {
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 {
args := cmd.Args()

View File

@@ -33,6 +33,7 @@ const (
Capi
Cilm
Cnode
Cclustersettings
)
func complete(cmd *cli.Command, what int) {
@@ -67,6 +68,8 @@ func complete(cmd *cli.Command, what int) {
list, err = es.IlmNames(conf)
case Cnode:
list, err = es.NodeNames(conf)
case Cclustersettings:
list, err = es.ClusterSettingsNames(conf)
}
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),
Ilm(conf),
Index(conf),
License(conf),
Node(conf),
Roles(conf),
Search(conf),
@@ -248,9 +249,11 @@ func Debug(conf *cfg.Config) *cli.Command {
func HelpUsage(conf *cfg.Config) *cli.Command {
return &cli.Command{
Name: "help-command-overview",
Usage: "show overview of all available commands",
Aliases: []string{"usage"},
Name: "help-usage",
Usage: "show overview of all available commands",
UsageText: "help-usage [<filter>]",
Aliases: []string{"usage"},
CustomHelpTemplate: addReference(`<filter> implies -f`),
Flags: []cli.Flag{
&cli.BoolFlag{
@@ -259,16 +262,32 @@ func HelpUsage(conf *cfg.Config) *cli.Command {
Destination: &conf.Hidden,
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 {
maxCommandWidth := 0
filter := cmd.Args().Get(0)
if filter != "" {
conf.Force = true
}
// first pass, determine max command width
if err := walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error {
path := cmd.Path()
size := len(path[len(path)-1])
if conf.Force {
path := strings.Join(cmd.Path(), " ")
size = len(path)
}
if size > maxCommandWidth {
maxCommandWidth = size
}
@@ -283,9 +302,19 @@ func HelpUsage(conf *cfg.Config) *cli.Command {
// second pass, build tree
return walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error {
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
}

View File

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

View File

@@ -28,7 +28,7 @@ import (
)
const (
Version string = `v0.0.24`
Version string = `v0.0.25`
)
var (
@@ -106,6 +106,8 @@ type Config struct {
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 {
@@ -137,6 +139,18 @@ func (conf *Config) Init() error {
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 != "" {
// -C specified, set current cluster explicitly, no matter what the config says
current, exists := conf.Clusters[conf.CurrentCluster]
@@ -172,10 +186,6 @@ func (conf *Config) Init() error {
}
}
conf.HaveJQ = isJQinstalled()
conf.PrintDebug()
return nil
}

View File

@@ -36,3 +36,14 @@ func GetTermWidth() int {
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))
}
if err := prettyfiJson(conf, raw); err != nil {
fmt.Println(err)
}
pageJsonOutput(conf, raw)
}
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 {
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}
if conf.DebugHTTP {
client = &http.Client{
Transport: &cfg.DebugTransport{
Transport: tr}}
}
req, err := http.NewRequest(verb, conf.DefaultCluster.Uri+path, bytes.NewBuffer([]byte(data)))
if err != nil {
return nil, err
@@ -197,7 +243,7 @@ func CallAPI(conf *cfg.Config, verb, path, data string) ([]byte, error) {
return body, nil
}
func prettyfiJson(conf *cfg.Config, raw []byte) error {
func prettyfiJson(conf *cfg.Config, raw []byte) (string, error) {
if conf.HaveJQ {
cmd := exec.CommandContext(context.Background(), "jq", "-C")
cmd.Stdin = bytes.NewReader(raw)
@@ -207,23 +253,18 @@ func prettyfiJson(conf *cfg.Config, raw []byte) error {
err := cmd.Run()
if err != nil {
return err
return "", err
}
fmt.Println(out.String())
return nil
return out.String(), nil
}
var pretty bytes.Buffer
err := json.Indent(&pretty, raw, "", "\t")
if err != nil {
return fmt.Errorf("json parse error: %s", err)
return "", fmt.Errorf("json parse error: %s", err)
}
fmt.Println(pretty.String())
return nil
return pretty.String(), nil
}
// interactively read arbitrary JSON data from STDIN, which is

View File

@@ -31,44 +31,62 @@ import (
type ClusterIndices map[string]map[string]*types.IndicesRecord
func ClusterList(conf *cfg.Config) error {
table := printer.NewTable(conf, 5, len(conf.Clusters))
type clusterReachable struct {
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")
idx := 0
for name, cluster := range conf.Clusters {
reachable := "no"
reachableStr := "no"
current := "no"
errmsg := ""
online, err := cluster.IsReachable()
if online {
reachable = printer.Colorize(conf, "green", "reachable")
if reachable[name].reachable {
reachableStr = printer.Colorize(conf, "green", "reachable")
}
if cluster.Default {
current = printer.Colorize(conf, "green", "yes")
if !online {
reachable = printer.Colorize(conf, "red", "no")
errmsg = err.Error()
if !reachable[name].reachable {
reachableStr = printer.Colorize(conf, "red", "no")
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++
}
table.Sort()
if err := table.Print(); err != nil {
return err
}
return nil
return table.Print()
}
// We're using goroutines here to parallelize API requests, since we

View File

@@ -20,15 +20,19 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/printer"
"github.com/urfave/cli/v3"
)
func ClusterSettingsNames(conf *cfg.Config) ([]string, error) {
return validClusterSettings, nil
}
func ClusterSettingsList(conf *cfg.Config) error {
res, err := conf.DefaultCluster.ES().Cluster.GetSettings().
FlatSettings(true).
Do(context.Background())
if err != nil {
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 {
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(map[string]string{}, data, topic)
slog.Debug("settings", topic, paths)
for setting, value := range paths {
entries = append(entries, []string{setting, fmt.Sprintf("%v", value)})
}
entries = append(entries, []string{topic, string(val)})
}
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"
"fmt"
"regexp"
"strconv"
"strings"
"codeberg.org/scip/esctl/pkg/cfg"
@@ -330,44 +329,3 @@ func splitArg(arg string) (string, string) {
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 {
raw, err := CallAPI(conf, "GET", "/_index_template/"+tplname, "")
raw, err := CallAPI(conf, "GET", "/_index_template/"+tplname+"?flat_settings", "")
if err != nil {
return err
}
@@ -39,9 +39,11 @@ func getIndexTemplateSettings(conf *cfg.Config, tplname string, table *printer.T
}
if conf.Debug {
if err := prettyfiJson(conf, raw); err != nil {
output, err := prettyfiJson(conf, raw)
if err != nil {
return err
}
fmt.Println(output)
}
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
for topic, val := range tpl {
paths := getJsonPath(map[string]string{}, val.(map[string]any), topic)
for setting, value := range paths {
table.Entries = append(table.Entries, []string{setting, fmt.Sprintf("%v", value)})
}
table.Entries = append(table.Entries, []string{topic, fmt.Sprintf("%v", val)})
}
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
}
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 {
switch data.Mode {
case "markdown", "md":
@@ -157,7 +175,7 @@ func (data *Table) PrintTSV() error {
for idx, entry := range entries {
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
wrapper := wordwrap.Wrapper(data.maxwidth-currentWidth, false)
wrapped := wrapper(entry)