/* 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" "fmt" "log" "log/slog" "regexp" "codeberg.org/scip/esctl/pkg/cfg" "github.com/elastic/go-elasticsearch/v9/typedapi/types" ) const ( DefaultExclude = `(part|monitoring|.internal|metrics-endpoint)` ) type ClusterIndices map[string]map[string]*types.IndicesRecord func ClusterCompare(conf *cfg.Config, leader, follower string) error { indices := ClusterIndices{} for _, alias := range []string{leader, follower} { cat := conf.Clusters[alias].ES.Cat.Indices(). // we need to add custom request headers, required for older ES instances Header("content-type", "application/json"). Header("accept", "application/json") res, err := cat.Do(context.Background()) if err != nil { return fmt.Errorf("Error getting indicies on %s: %s", alias, err) } indices[alias] = make(map[string]*types.IndicesRecord, len(res)) for _, index := range res { indices[alias][*index.Index] = &index } } findIndicesOnlyOnMaster(conf, indices, leader, follower) return nil } func List(conf *cfg.Config) error { table := NewTable(2, len(conf.Clusters)) table.Addheaders("cluster", "uri") idx := 0 for name, cluster := range conf.Clusters { table.entries[idx] = []string{name, cluster.Uri} idx++ } table.PrintMarkdown() return nil } func Status(conf *cfg.Config) error { clusters := []string{} if conf.All { for key, _ := range conf.Clusters { clusters = append(clusters, key) } } else { clusters = []string{"default"} } for _, cluster := range clusters { es := conf.DefaultCluster.ES if cluster != "default" { es = conf.Clusters[cluster].ES } res, err := es.Cluster.Health(). Header("content-type", "application/json"). Header("accept", "application/json"). Do(context.Background()) if err != nil { log.Fatalf("Error getting health: %s", err) } slog.Debug("ES result", "cluster health", res) table := NewTable(2, 5) table.Addheaders(cluster, "status") table.entries = [][]string{ {"Cluster Name", Colorize(*&res.Status.Name, res.ClusterName)}, {"Active Shards", fmt.Sprintf("%d", res.ActiveShards)}, {"Active Primary Shards", fmt.Sprintf("%d", res.ActivePrimaryShards)}, {"Indicies", fmt.Sprintf("%d", len(res.Indices))}, {"Nodes", fmt.Sprintf("%d", res.NumberOfNodes)}, } table.PrintMarkdown() } return nil } // look for indicies only on leader func findIndicesOnlyOnMaster(conf *cfg.Config, indices ClusterIndices, leader, follower string) { exclude := regexp.MustCompile(DefaultExclude) if conf.Exclude != "" { exclude = regexp.MustCompile(conf.Exclude) } indexOnlyOnLeader := map[string]*types.IndicesRecord{} for name, index := range indices[leader] { if exclude.MatchString(name) { continue } _, followerHasIt := indices[follower][name] if !followerHasIt { // fetch index details res, err := conf.Clusters[leader].ES.Indices.Get(name). Header("content-type", "application/json"). Header("accept", "application/json"). Do(context.Background()) if err != nil { continue // ignore it then } _, defined := res[name] if !defined { // json response map didn't contain the index continue } isWritable := false for _, alias := range res[name].Aliases { if *alias.IsWriteIndex { isWritable = true break } } if isWritable { // ignore index if associated alias index is writing continue } indexOnlyOnLeader[name] = index } } idx := 0 table := NewTable(3, len(indexOnlyOnLeader)) table.Addheaders("index only on leader", "size", "docscount") for name, index := range indexOnlyOnLeader { name := Colorize("red", name) table.entries[idx] = []string{name, *index.DatasetSize, *index.DocsCount} idx++ } table.Sort() table.PrintMarkdown() }