mirror of
https://codeberg.org/scip/esctl.git
synced 2026-08-24 09:44:17 +02:00
293 lines
7.3 KiB
Go
293 lines
7.3 KiB
Go
/*
|
|
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"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"codeberg.org/scip/esctl/pkg/cfg"
|
|
"github.com/elastic/go-elasticsearch/v9/typedapi/cat/indices"
|
|
"github.com/elastic/go-elasticsearch/v9/typedapi/esdsl"
|
|
"github.com/elastic/go-elasticsearch/v9/typedapi/types/enums/healthstatus"
|
|
)
|
|
|
|
// used for completion
|
|
func IndexNames(conf *cfg.Config) ([]string, error) {
|
|
res, err := conf.DefaultCluster.ES.Cat.Indices().
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json").
|
|
Do(context.Background())
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get indicies: %s", err)
|
|
}
|
|
|
|
indices := make([]string, len(res))
|
|
for idx, index := range res {
|
|
indices[idx] = *index.Index
|
|
}
|
|
|
|
return indices, nil
|
|
}
|
|
|
|
func filterIndices(conf *cfg.Config, list indices.Response) indices.Response {
|
|
if len(conf.Filter) == 0 {
|
|
return list
|
|
}
|
|
|
|
// we support just one filter here, for now
|
|
filter := *regexp.MustCompile(conf.Filter[0])
|
|
newlist := indices.Response{}
|
|
|
|
for _, index := range list {
|
|
if filter.MatchString(*index.Index) {
|
|
newlist = append(newlist, index)
|
|
}
|
|
}
|
|
|
|
return newlist
|
|
}
|
|
|
|
func IndexList(conf *cfg.Config) error {
|
|
cat := conf.DefaultCluster.ES.Cat.Indices().
|
|
// we need to add custom request headers, required for older ES instances
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json")
|
|
|
|
if conf.Failed {
|
|
cat = cat.Health(healthstatus.Red)
|
|
}
|
|
|
|
res, err := cat.Do(context.Background())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get indicies: %s", err)
|
|
}
|
|
|
|
slog.Debug("ES result", "indicies", res)
|
|
|
|
list := filterIndices(conf, res)
|
|
|
|
size := len(list)
|
|
|
|
if conf.MaxItems > 0 {
|
|
if size > conf.MaxItems {
|
|
size = conf.MaxItems
|
|
}
|
|
}
|
|
|
|
table := NewTable(3, size)
|
|
table.Addheaders("name", "size", "docscount")
|
|
|
|
for idx, index := range list {
|
|
name := Colorize(*index.Health, *index.Index)
|
|
|
|
table.entries[idx] = []string{name, *index.DatasetSize, *index.DocsCount}
|
|
|
|
if idx == size-1 {
|
|
break
|
|
}
|
|
}
|
|
|
|
table.Sort()
|
|
if err := table.PrintMarkdown(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IndexShow(conf *cfg.Config, index string) error {
|
|
res, err := conf.DefaultCluster.ES.Indices.Get(index).
|
|
// we need to add custom request headers, required for older ES instances
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json").
|
|
Do(context.Background())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get index: %s", err)
|
|
}
|
|
|
|
slog.Debug("ES result", "index", res)
|
|
|
|
table := NewTable(2, 5)
|
|
table.Addheaders("index property", "value")
|
|
|
|
ts, err := strconv.ParseInt(res[index].Settings.Index.CreationDate.(string), 10, 64)
|
|
if err != nil {
|
|
ts = 0
|
|
}
|
|
|
|
created := time.Unix(ts/1000, 0)
|
|
|
|
table.entries = [][]string{
|
|
{"name", index},
|
|
{"replicas", *res[index].Settings.Index.NumberOfReplicas},
|
|
{"shards", *res[index].Settings.Index.NumberOfShards},
|
|
{"created", created.Format("2006-01-02 15:04:05")},
|
|
{"uuid", *res[index].Settings.Index.Uuid},
|
|
}
|
|
|
|
if err := table.PrintMarkdown(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IndexCreate(conf *cfg.Config, index string, mappings []string) error {
|
|
settings := esdsl.NewIndexSettings()
|
|
|
|
create := conf.DefaultCluster.ES.Indices.Create(index).
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json")
|
|
|
|
if conf.Wait {
|
|
create.WaitForActiveShards("all")
|
|
}
|
|
|
|
if conf.Shards > 0 {
|
|
settings = settings.NumberOfShards(strconv.Itoa(conf.Shards))
|
|
}
|
|
if conf.Replicas > 0 {
|
|
settings = settings.NumberOfReplicas(strconv.Itoa(conf.Replicas))
|
|
}
|
|
|
|
if len(mappings) > 0 {
|
|
maps := esdsl.NewTypeMapping()
|
|
|
|
for _, mapping := range mappings {
|
|
parts := strings.Split(mapping, ":")
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("invalid mapping %s, expect <name:type> (type: integer, text, date, keyword)", mapping)
|
|
}
|
|
|
|
switch parts[1] {
|
|
case "text":
|
|
maps.AddProperty(parts[0], esdsl.NewTextProperty())
|
|
case "integer":
|
|
maps.AddProperty(parts[0], esdsl.NewIntegerNumberProperty())
|
|
case "date":
|
|
maps.AddProperty(parts[0], esdsl.NewDateProperty())
|
|
case "keyword":
|
|
maps.AddProperty(parts[0], esdsl.NewKeywordProperty())
|
|
}
|
|
}
|
|
|
|
create.Mappings(maps)
|
|
}
|
|
|
|
_, err := create.Settings(settings).
|
|
Do(context.Background())
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create index: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IndexDelete(conf *cfg.Config, index string) error {
|
|
_, err := conf.DefaultCluster.ES.Indices.Delete(index).
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json").
|
|
Do(context.Background())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete index: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IndexClose(conf *cfg.Config, index string) error {
|
|
create := conf.DefaultCluster.ES.Indices.Close(index).
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json")
|
|
|
|
_, err := create.Do(context.Background())
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to close index: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IndexAllocation(conf *cfg.Config, index string) error {
|
|
res, err := conf.DefaultCluster.ES.Cluster.AllocationExplain().
|
|
Index(index).
|
|
Primary(conf.Primary).
|
|
Shard(conf.Shards).
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json").
|
|
Do(context.Background())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get index allocation explain: %s", err)
|
|
}
|
|
|
|
slog.Debug("ES result", "index", res)
|
|
|
|
currentNode := res.CurrentNode
|
|
|
|
table := NewTable(2, 10)
|
|
table.Addheaders("index allocation setting", "value")
|
|
|
|
roles := make([]string, len(currentNode.Roles))
|
|
for idx, role := range currentNode.Roles {
|
|
roles[idx] = role.Name
|
|
}
|
|
|
|
table.entries = [][]string{
|
|
{"Index", index},
|
|
{"Current node", currentNode.Name},
|
|
{"Current k8s node", currentNode.Attributes["k8s_node_name"]},
|
|
{"Current node address", currentNode.TransportAddress},
|
|
{"Current node id", currentNode.Id},
|
|
{"Current node weight", fmt.Sprintf("%d", currentNode.WeightRanking)},
|
|
{"Current node roles", strings.Join(roles, ",")},
|
|
{"Can rebalance cluster", res.CanRebalanceCluster.Name},
|
|
{"Can rebalance to another node", res.CanRebalanceToOtherNode.Name},
|
|
{"Can remain on current node", res.CanRemainOnCurrentNode.Name},
|
|
}
|
|
|
|
if err := table.PrintMarkdown(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IndexModify(conf *cfg.Config, index string) error {
|
|
settings := esdsl.NewIndexSettings().NumberOfReplicas(strconv.Itoa(conf.Replicas))
|
|
|
|
_, err := conf.DefaultCluster.ES.Indices.PutSettings().
|
|
Indices(index).
|
|
Index(settings).
|
|
Header("content-type", "application/json").
|
|
Header("accept", "application/json").
|
|
Do(context.Background())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to modify index settings: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|