Compare commits

...

5 Commits
0.0.5 ... 0.0.6

Author SHA1 Message Date
T. von Dein
38ed9de0b4 Add CI pipelines, fix linting issues (#12) 2026-05-07 15:01:18 +02:00
T. von Dein
5e82a3706b enhance cluster status (#11) 2026-05-07 12:54:27 +02:00
0122009774 add es version to status output 2026-05-06 13:10:02 +02:00
8109481e23 check at bootstrap which cluster is reachable, fix current cluster 2026-05-06 13:02:28 +02:00
T. von Dein
af75bfa192 add -p -t -D flags to cluster settings ls (#10) 2026-05-06 07:25:27 +02:00
16 changed files with 452 additions and 127 deletions

42
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,42 @@
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 2
before:
hooks:
- go mod tidy
gitea_urls:
api: https://codeberg.org/api/v1
download: https://codeberg.org
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
groups:
- title: Improved
regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$'
order: 0
- title: Fixed
regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Changed
order: 999
release:
header: "# Release Notes"
footer: >-
---
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/epuppy/compare/{{ .PreviousTag }}...{{ .Tag }})

26
.woodpecker/build.yaml Normal file
View File

@@ -0,0 +1,26 @@
matrix:
platform:
- linux/amd64
goversion:
- 1.25
labels:
platform: ${platform}
steps:
build:
when:
event: [push,manual]
image: golang:${goversion}
commands:
- go get
- go build
linter:
when:
event: [push,manual]
image: golang:${goversion}
commands:
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
- golangci-lint --version
- golangci-lint run ./...

15
.woodpecker/release.yaml Normal file
View File

@@ -0,0 +1,15 @@
# build release
labels:
platform: linux/amd64
steps:
goreleaser:
image: goreleaser/goreleaser
when:
event: [tag,manual]
environment:
GITEA_TOKEN:
from_secret: DEPLOY_TOKEN
commands:
- goreleaser release --clean --verbose

18
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,18 @@
# CODE_OF_CONDUCT.md (compact model)
Purpose: Maintain a collaborative, harassment-free environment focused
on shipping quality software.
Standards: Be respectful; assume good intent; no harassment; no
discrimination; keep critiques technical.
Scope: All project spaces (issues, PRs, forums, events).
Reporting: Email tom AT vondein DOT org. Acknowledge within 72 hours.
Enforcement: Two maintainers review, one recuses on
conflict. Sanctions range from warning to removal. Summary posted (no
personal details).
Escalation: If you believe maintainers handled a report in bad faith,
escalate to our foundation committee (link) for independent review.

91
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,91 @@
## Project Goals
The idea behind this project is to build a small commandline tool to
manage an ElasticSearch cluster.
There will be no GUI, no web interface, no public API of some
sort. TUI subcommands maybe added.
The programming language used for this project will always be
[GOLANG](https://go.dev/) with the exception of the documentation
([Perl POD](https://perldoc.perl.org/perlpod)) and the Makefile.
# Contributing
You can contribute to this project in various ways:
## Open an issue
If you encounter a problem or don't understand how the program works
or if you think the documentation is unclear, please don't hesitate to
open an issue.
Please add as much information about the case as possible, such as:
- Your environment (operating system etc)
- program version
- Input data. Please replace sensitive information with mock data!
- Actual program output.
- Expected program output.
- Error message - if any.
Be aware that I am working on this (and some other) project in my
spare time which is scarce. Therefore please don't expect me to
respond to your query within hours or even days. Be patient, but I
WILL respond.
## Pull Requests
Code and documentation help is always much appreciated! Please follow
thes guidelines to successfully contribute:
- Every pull request shall be based on latest `development`
branch. `main` is only used for releases.
- Execute the unit tests before committing: `make test`. There shall
be no errors.
- Strive to be backwards compatible so that users who are already
using the program don't have to change their habits - unless it is
really neccessary.
- Try to add a unit test for your addition.
- Don't ever change existing unit tests!
- Add a meaningful and comprehensive rationale about your contribution:
- Why do you think it might be useful for others?
- What did you actually change or add?
- Is there an open issue which this PR fixes and if so, please link
to that issue.
- [Re-]format your code with `gofmt -s`.
- Avoid unneccesary dependencies, especially for very small functions.
- **If** a new dependency is being added, it must be compatible with
our [license agreement](LICENSE).
- You need to accept that the code or documentation you contribute
will be redistributed under the terms of said license agreement. If
your contribution is considerably large or if you contribute
regularly, then feel free to add your name and if you want your
email address to the *AUTHORS* section of the
manual page.
- Adhere to the above mentioned project goals.
- If you are unsure if your addition or change will be accepted,
better ask before starting coding. Open an issue about your proposal
and let's discuss it! That way we avoid doing unnessesary work on
both sides.
Each pull request will be carefully reviewed and if it is a useful
addition it will be accepted. However, please be prepared that
sometimes a PR will be rejected. The reasons may vary and will be
documented. Perhaps the above guidelines are not matched, or the
addition seems to be not so useful from my perspective, maybe there
are too much changes or there might be changes I don't even
understand.
But whatever happens: your contribution is always welcome!

View File

@@ -1,3 +1,5 @@
[![status-badge](https://ci.codeberg.org/api/badges/16999/status.svg)](https://ci.codeberg.org/repos/16999)
# esctl # esctl
Elasticsearch CLI Elasticsearch CLI

View File

@@ -3,3 +3,5 @@
- Fix index names custom completion - Fix index names custom completion
- add cluster default <name> which would add a flag to the config, so that no -C is needed subsequently - add cluster default <name> which would add a flag to the config, so that no -C is needed subsequently
- add `cluster stats` from `/_cluster/stats` like mem, procs, open files, num indices, shards etc
or add these to `cluster status`, maybe add a `--stats` to include stats there?

View File

@@ -140,6 +140,27 @@ func ClusterSettingsList(conf *cfg.Config) *cli.Command {
Usage: "show cluster settings", Usage: "show cluster settings",
Aliases: []string{"ls", "get"}, Aliases: []string{"ls", "get"},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "persistent",
Usage: "only show persistent setting[s] (default)",
Destination: &conf.Persistent,
Aliases: []string{"p"},
},
&cli.BoolFlag{
Name: "transient",
Usage: "only show transient setting[s]",
Destination: &conf.Transient,
Aliases: []string{"t"},
},
&cli.BoolFlag{
Name: "default",
Usage: "only show default setting[s]",
Destination: &conf.Default,
Aliases: []string{"D"},
},
},
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if err := es.ClusterSettingsList(conf); err != nil { if err := es.ClusterSettingsList(conf); err != nil {
return err return err

View File

@@ -17,6 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package cfg package cfg
import ( import (
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@@ -30,7 +31,7 @@ import (
) )
const ( const (
Version string = `v0.0.5` Version string = `v0.0.6`
) )
type Cluster struct { type Cluster struct {
@@ -39,20 +40,20 @@ type Cluster struct {
} }
type Config struct { type Config struct {
ConfigFile string // -c ConfigFile string // -c
CurrentCluster string // -C CurrentCluster string // -C
Debug bool // -d Debug bool // -d
Clusters map[string]*Cluster Clusters map[string]*Cluster
DefaultCluster *Cluster DefaultCluster *Cluster
Index string // index: -i Index string // index: -i
Failed, Partials bool // index: flags Failed, Partials bool // index: flags
Shards, Replicas int // index create: -s -r Shards, Replicas int // index create: -s -r
Wait bool // index create: -w Wait bool // index create: -w
From, To, MaxItems int // search: flags From, To, MaxItems int // search: flags
Filter []string // search: -F Filter []string // search: -F
Exclude string // cluster compare: -e (regexp) Exclude string // cluster compare: -e (regexp)
All bool // cluster status: -a All bool // cluster status: -a
Persistent, Transient bool // -p -t cluster settings set Persistent, Transient, Default bool // -p -t -D cluster settings set
} }
func NewConfig() *Config { func NewConfig() *Config {
@@ -76,6 +77,10 @@ func (conf *Config) Init() error {
} }
} }
if err := conf.SetupES(); err != nil {
return err
}
if conf.CurrentCluster != "" { if conf.CurrentCluster != "" {
current, exists := conf.Clusters[conf.CurrentCluster] current, exists := conf.Clusters[conf.CurrentCluster]
if !exists { if !exists {
@@ -83,14 +88,29 @@ func (conf *Config) Init() error {
} else { } else {
conf.DefaultCluster = current conf.DefaultCluster = current
} }
} else {
if len(conf.Clusters) == 1 {
for _, cluster := range conf.Clusters {
conf.DefaultCluster = cluster
}
} else {
for _, cluster := range conf.Clusters {
_, err := cluster.ES.Cluster.Health().
Header("content-type", "application/json").
Header("accept", "application/json").
Do(context.Background())
if err == nil {
conf.DefaultCluster = cluster
}
}
}
} }
if conf.Debug { if conf.Debug {
repr.Println(conf) repr.Println(conf)
} }
conf.SetupES()
return nil return nil
} }

View File

@@ -22,8 +22,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"sync"
"codeberg.org/scip/esctl/pkg/cfg" "codeberg.org/scip/esctl/pkg/cfg"
"github.com/elastic/go-elasticsearch/v9/typedapi/ccr/stats"
"github.com/elastic/go-elasticsearch/v9/typedapi/cluster/health"
"github.com/elastic/go-elasticsearch/v9/typedapi/core/info"
"github.com/elastic/go-elasticsearch/v9/typedapi/types" "github.com/elastic/go-elasticsearch/v9/typedapi/types"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
@@ -32,8 +36,22 @@ const (
DefaultExclude = `(part|monitoring|.internal|metrics-endpoint)` DefaultExclude = `(part|monitoring|.internal|metrics-endpoint)`
) )
const (
ResponseHealth = iota
ResponseInfo
ResponseCcr
)
type ClusterIndices map[string]map[string]*types.IndicesRecord type ClusterIndices map[string]map[string]*types.IndicesRecord
type apiResponse struct {
error error
info *info.Response
health *health.Response
ccr *stats.Response
which int
}
func ClusterCompare(conf *cfg.Config, leader, follower string) error { func ClusterCompare(conf *cfg.Config, leader, follower string) error {
if !checkClusterFollower(conf, leader) { if !checkClusterFollower(conf, leader) {
return errors.New("leader/follower attribution is invalid, reverse cluster attribution and retry") return errors.New("leader/follower attribution is invalid, reverse cluster attribution and retry")
@@ -60,7 +78,7 @@ func ClusterCompare(conf *cfg.Config, leader, follower string) error {
} }
if !checkClusterStatus(conf, leader, follower) { if !checkClusterStatus(conf, leader, follower) {
return errors.New("One of the two clusters is in a failed state") return errors.New("one of the two clusters is in a failed state")
} }
findIlmErrors(conf, leader, follower) findIlmErrors(conf, leader, follower)
@@ -75,26 +93,42 @@ func ClusterCompare(conf *cfg.Config, leader, follower string) error {
} }
func ClusterList(conf *cfg.Config) error { func ClusterList(conf *cfg.Config) error {
table := NewTable(2, len(conf.Clusters)) table := NewTable(3, len(conf.Clusters))
table.Addheaders("cluster", "uri")
table.Addheaders("cluster", "uri", "default")
idx := 0 idx := 0
for name, cluster := range conf.Clusters { for name, cluster := range conf.Clusters {
table.entries[idx] = []string{name, cluster.Uri} current := name == "default" || name == conf.CurrentCluster
_, err := cluster.ES.Cluster.Health().
Header("content-type", "application/json").
Header("accept", "application/json").
Do(context.Background())
if err == nil {
name = Colorize("green", name)
}
table.entries[idx] = []string{name, cluster.Uri, fmt.Sprintf("%t", current)}
idx++ idx++
} }
table.Sort() table.Sort()
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
return err
}
return nil return nil
} }
// We're using goroutines here to parallelize API requests, since we
// have to do 3 of'em for each cluster. This speeds things up.
func ClusterStatus(conf *cfg.Config) error { func ClusterStatus(conf *cfg.Config) error {
clusters := []string{} clusters := []string{}
if conf.All { if conf.All {
for key, _ := range conf.Clusters { for key := range conf.Clusters {
clusters = append(clusters, key) clusters = append(clusters, key)
} }
} else { } else {
@@ -107,28 +141,65 @@ func ClusterStatus(conf *cfg.Config) error {
es = conf.Clusters[cluster].ES es = conf.Clusters[cluster].ES
} }
res, err := es.Cluster.Health(). responses := make(chan apiResponse, 3)
Header("content-type", "application/json"). wg := &sync.WaitGroup{}
Header("accept", "application/json").
Do(context.Background()) wg.Add(3)
if err != nil { go getClusterData(es, wg, responses, "health")
return fmt.Errorf("failed to getcluster health: %s", err) go getClusterData(es, wg, responses, "info")
go getClusterData(es, wg, responses, "ccrstats")
wg.Wait()
var clusterhealth *health.Response
var info *info.Response
var ccrstats *stats.Response
for i := 0; i < 3; i++ {
r := <-responses
if r.error != nil {
return r.error
}
switch r.which {
case ResponseHealth:
clusterhealth = r.health
case ResponseCcr:
ccrstats = r.ccr
case ResponseInfo:
info = r.info
}
} }
slog.Debug("ES result", "cluster health", res) slog.Debug("ES result", "cluster health", clusterhealth)
ccrfollowing := ""
if len(ccrstats.AutoFollowStats.AutoFollowedClusters) > 0 {
// is following another cluster
ccrfollowing = fmt.Sprintf("%s (%d/%d)",
ccrstats.AutoFollowStats.AutoFollowedClusters[0].ClusterName,
ccrstats.AutoFollowStats.NumberOfSuccessfulFollowIndices,
ccrstats.AutoFollowStats.NumberOfFailedFollowIndices,
)
}
table := NewTable(2, 5) table := NewTable(2, 5)
table.Addheaders(cluster, "status") table.Addheaders(cluster, "status")
table.entries = [][]string{ table.entries = [][]string{
{"Cluster Name", Colorize(*&res.Status.Name, res.ClusterName)}, {"Cluster Name", Colorize(clusterhealth.Status.Name, clusterhealth.ClusterName)},
{"Active Shards", fmt.Sprintf("%d", res.ActiveShards)}, {"ES Version", info.Version.Int},
{"Active Primary Shards", fmt.Sprintf("%d", res.ActivePrimaryShards)}, {"Active Shards", fmt.Sprintf("%d", clusterhealth.ActiveShards)},
{"Indicies", fmt.Sprintf("%d", len(res.Indices))}, {"Active Primary Shards", fmt.Sprintf("%d", clusterhealth.ActivePrimaryShards)},
{"Nodes", fmt.Sprintf("%d", res.NumberOfNodes)}, {"Indicies", fmt.Sprintf("%d", len(clusterhealth.Indices))},
{"Nodes", fmt.Sprintf("%d", clusterhealth.NumberOfNodes)},
{"AutoFollow (success/failed indices)", ccrfollowing},
} }
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
return err
}
} }
return nil return nil
@@ -147,7 +218,16 @@ func ClusterSettingsList(conf *cfg.Config) error {
table.Addheaders("setting", "value") table.Addheaders("setting", "value")
entries := [][]string{} entries := [][]string{}
for topic, val := range res.Persistent { 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]map[string]any{} data := map[string]map[string]any{}
err := json.Unmarshal(val, &data) err := json.Unmarshal(val, &data)
@@ -168,9 +248,11 @@ func ClusterSettingsList(conf *cfg.Config) error {
} }
table.entries = entries table.entries = entries
table.Sort() table.Sort()
table.PrintMarkdown()
if err := table.PrintMarkdown(); err != nil {
return err
}
return nil return nil
} }

View File

@@ -18,77 +18,18 @@ package es
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"sync"
"codeberg.org/scip/esctl/pkg/cfg" "codeberg.org/scip/esctl/pkg/cfg"
"github.com/elastic/go-elasticsearch/v9"
"github.com/elastic/go-elasticsearch/v9/typedapi/cluster/health" "github.com/elastic/go-elasticsearch/v9/typedapi/cluster/health"
"github.com/elastic/go-elasticsearch/v9/typedapi/types" "github.com/elastic/go-elasticsearch/v9/typedapi/types"
) )
// 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()
}
func checkClusterFollower(conf *cfg.Config, leader string) bool { func checkClusterFollower(conf *cfg.Config, leader string) bool {
stats, err := conf.Clusters[leader].ES.Ccr.Stats(). stats, err := conf.Clusters[leader].ES.Ccr.Stats().
Header("content-type", "application/json"). Header("content-type", "application/json").
@@ -135,8 +76,8 @@ func checkClusterStatus(conf *cfg.Config, leader, follower string) bool {
table.entries = [][]string{ table.entries = [][]string{
{"Cluster Name", {"Cluster Name",
Colorize(*&status[leader].Status.Name, status[leader].ClusterName), Colorize(status[leader].Status.Name, status[leader].ClusterName),
Colorize(*&status[follower].Status.Name, status[follower].ClusterName), Colorize(status[follower].Status.Name, status[follower].ClusterName),
}, },
{"Active Shards", {"Active Shards",
fmt.Sprintf("%d", status[leader].ActiveShards), fmt.Sprintf("%d", status[leader].ActiveShards),
@@ -156,7 +97,10 @@ func checkClusterStatus(conf *cfg.Config, leader, follower string) bool {
}, },
} }
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
fmt.Println(err)
return false
}
if status[leader].Status.Name == "green" && status[follower].Status.Name == "green" { if status[leader].Status.Name == "green" && status[follower].Status.Name == "green" {
return true return true
@@ -212,7 +156,11 @@ func findIlmErrors(conf *cfg.Config, leader, follower string) bool {
} }
table.Sort() table.Sort()
table.PrintMarkdown()
if err := table.PrintMarkdown(); err != nil {
fmt.Println(err)
return false
}
} }
} }
@@ -282,7 +230,10 @@ func findIndicesOnlyOnLeader(conf *cfg.Config, indices ClusterIndices, leader, f
} }
table.Sort() table.Sort()
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
fmt.Println(err)
return false
}
return false return false
} }
@@ -329,7 +280,10 @@ func findOrphanedIndices(conf *cfg.Config, indices ClusterIndices, leader, follo
} }
table.Sort() table.Sort()
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
fmt.Println(err)
return false
}
return false return false
} }
@@ -360,7 +314,10 @@ func findFailedFollowerIndices(conf *cfg.Config, indices ClusterIndices, followe
} }
table.Sort() table.Sort()
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
fmt.Println(err)
return false
}
return false return false
} }
@@ -377,3 +334,48 @@ func splitArg(arg string) (string, string) {
return parts[0], parts[1] return parts[0], parts[1]
} }
} }
func getClusterData(es *elasticsearch.TypedClient, wg *sync.WaitGroup, reschan chan apiResponse, which string) {
defer wg.Done()
ar := apiResponse{}
arerr := errors.New("")
switch which {
case "health":
res, err := es.Cluster.Health().
Header("content-type", "application/json").
Header("accept", "application/json").
Do(context.Background())
ar.health = res
ar.which = ResponseHealth
arerr = err
case "info":
res, err := es.Info().
Header("content-type", "application/json").
Header("accept", "application/json").
Do(context.Background())
ar.info = res
ar.which = ResponseInfo
arerr = err
case "ccrstats":
res, err := es.Ccr.Stats().
Header("content-type", "application/json").
Header("accept", "application/json").
Do(context.Background())
ar.ccr = res
ar.which = ResponseCcr
arerr = err
}
if arerr != nil {
ar.error = fmt.Errorf("failed to get cluster health: %s", arerr)
}
reschan <- ar
}

View File

@@ -28,12 +28,6 @@ import (
"github.com/elastic/go-elasticsearch/v9/typedapi/types/enums/healthstatus" "github.com/elastic/go-elasticsearch/v9/typedapi/types/enums/healthstatus"
) )
type Settings struct {
wait_for_active_shards string
number_of_shards int
number_of_replicas int
}
// used for completion // used for completion
func IndexNames(conf *cfg.Config) ([]string, error) { func IndexNames(conf *cfg.Config) ([]string, error) {
res, err := conf.DefaultCluster.ES.Cat.Indices(). res, err := conf.DefaultCluster.ES.Cat.Indices().
@@ -42,7 +36,7 @@ func IndexNames(conf *cfg.Config) ([]string, error) {
Do(context.Background()) Do(context.Background())
if err != nil { if err != nil {
return nil, fmt.Errorf("Error getting indicies: %s", err) return nil, fmt.Errorf("failed to get indicies: %s", err)
} }
indices := make([]string, len(res)) indices := make([]string, len(res))
@@ -65,7 +59,7 @@ func IndexList(conf *cfg.Config) error {
res, err := cat.Do(context.Background()) res, err := cat.Do(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("Error getting indicies: %s", err) return fmt.Errorf("failed to get indicies: %s", err)
} }
slog.Debug("ES result", "indicies", res) slog.Debug("ES result", "indicies", res)
@@ -92,7 +86,9 @@ func IndexList(conf *cfg.Config) error {
} }
table.Sort() table.Sort()
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
return err
}
return nil return nil
} }
@@ -131,7 +127,9 @@ func IndexShow(conf *cfg.Config, index string) error {
{"uuid", *res[index].Settings.Index.Uuid}, {"uuid", *res[index].Settings.Index.Uuid},
} }
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
return err
}
return nil return nil
} }

View File

@@ -28,7 +28,7 @@ func NodeList(conf *cfg.Config) error {
// get nodes // get nodes
nodes, err := conf.DefaultCluster.ES.Cat.Nodes().Do(context.Background()) nodes, err := conf.DefaultCluster.ES.Cat.Nodes().Do(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("Error getting nodes: %s", err) return fmt.Errorf("failed to get nodes: %s", err)
} }
slog.Debug("ES result", "nodes", nodes) slog.Debug("ES result", "nodes", nodes)
@@ -49,7 +49,9 @@ func NodeList(conf *cfg.Config) error {
} }
table.Sort() table.Sort()
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
return err
}
return nil return nil
} }

View File

@@ -62,7 +62,7 @@ func Search(conf *cfg.Config, q string) error {
}). }).
Do(context.Background()) Do(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("Error running search (esdsl): %s", err) return fmt.Errorf("failed to run search (esdsl): %s", err)
} }
slog.Debug("ES result", "search", res) slog.Debug("ES result", "search", res)

View File

@@ -44,7 +44,7 @@ func SnapshotList(conf *cfg.Config) error {
// get partial indicies // get partial indicies
ires, err := conf.DefaultCluster.ES.Cat.Indices().Do(context.Background()) ires, err := conf.DefaultCluster.ES.Cat.Indices().Do(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("Error getting indicies: %s", err) return fmt.Errorf("failed to get indicies: %s", err)
} }
indicies := map[string]int{} indicies := map[string]int{}
@@ -57,7 +57,7 @@ func SnapshotList(conf *cfg.Config) error {
// get snapshots // get snapshots
sres, err := conf.DefaultCluster.ES.Cat.Snapshots().Do(context.Background()) sres, err := conf.DefaultCluster.ES.Cat.Snapshots().Do(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("Error getting snapshots: %s", err) return fmt.Errorf("failed to get snapshots: %s", err)
} }
slog.Debug("ES result", "indicies", sres) slog.Debug("ES result", "indicies", sres)
@@ -97,7 +97,9 @@ func SnapshotList(conf *cfg.Config) error {
} }
table.Sort() table.Sort()
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
return err
}
return nil return nil
} }
@@ -135,7 +137,9 @@ func SnapshotShow(conf *cfg.Config, snapshot string) error {
{"shards-successful", fmt.Sprintf("%d", snap.Shards.Successful)}, {"shards-successful", fmt.Sprintf("%d", snap.Shards.Successful)},
} }
table.PrintMarkdown() if err := table.PrintMarkdown(); err != nil {
return err
}
return nil return nil
} }

View File

@@ -84,11 +84,11 @@ func (data *Table) PrintMarkdown() error {
table.Header(data.headers) table.Header(data.headers)
if err := table.Bulk(data.entries); err != nil { if err := table.Bulk(data.entries); err != nil {
return fmt.Errorf("Failed to add data to table renderer: %s", err) return fmt.Errorf("failed to add data to table renderer: %s", err)
} }
if err := table.Render(); err != nil { if err := table.Render(); err != nil {
fmt.Errorf("Failed to render table: %s", err) return fmt.Errorf("failed to render table: %s", err)
} }
fmt.Println(tableString.String()) fmt.Println(tableString.String())