diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..89b839a --- /dev/null +++ b/.goreleaser.yaml @@ -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 }}) diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml new file mode 100644 index 0000000..b064c87 --- /dev/null +++ b/.woodpecker/build.yaml @@ -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 ./... diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml new file mode 100644 index 0000000..cac4261 --- /dev/null +++ b/.woodpecker/release.yaml @@ -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 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ea92e68 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -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. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..104353a --- /dev/null +++ b/CONTRIBUTING.md @@ -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! diff --git a/README.md b/README.md index d30a288..0747d4c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![status-badge](https://ci.codeberg.org/api/badges/16999/status.svg)](https://ci.codeberg.org/repos/16999) + # esctl Elasticsearch CLI diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go index edb9876..b6c34d6 100644 --- a/pkg/cfg/config.go +++ b/pkg/cfg/config.go @@ -77,7 +77,9 @@ func (conf *Config) Init() error { } } - conf.SetupES() + if err := conf.SetupES(); err != nil { + return err + } if conf.CurrentCluster != "" { current, exists := conf.Clusters[conf.CurrentCluster] diff --git a/pkg/es/cluster.go b/pkg/es/cluster.go index 3a827fa..1f70269 100644 --- a/pkg/es/cluster.go +++ b/pkg/es/cluster.go @@ -78,7 +78,7 @@ func ClusterCompare(conf *cfg.Config, leader, follower string) error { } 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) @@ -115,7 +115,9 @@ func ClusterList(conf *cfg.Config) error { } table.Sort() - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + return err + } return nil } @@ -126,7 +128,7 @@ func ClusterStatus(conf *cfg.Config) error { clusters := []string{} if conf.All { - for key, _ := range conf.Clusters { + for key := range conf.Clusters { clusters = append(clusters, key) } } else { @@ -186,8 +188,8 @@ func ClusterStatus(conf *cfg.Config) error { table.Addheaders(cluster, "status") table.entries = [][]string{ - {"Cluster Name", Colorize(*&clusterhealth.Status.Name, clusterhealth.ClusterName)}, - {"ES Version", fmt.Sprintf("%s", info.Version.Int)}, + {"Cluster Name", Colorize(clusterhealth.Status.Name, clusterhealth.ClusterName)}, + {"ES Version", info.Version.Int}, {"Active Shards", fmt.Sprintf("%d", clusterhealth.ActiveShards)}, {"Active Primary Shards", fmt.Sprintf("%d", clusterhealth.ActivePrimaryShards)}, {"Indicies", fmt.Sprintf("%d", len(clusterhealth.Indices))}, @@ -195,7 +197,9 @@ func ClusterStatus(conf *cfg.Config) error { {"AutoFollow (success/failed indices)", ccrfollowing}, } - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + return err + } } return nil @@ -244,9 +248,11 @@ func ClusterSettingsList(conf *cfg.Config) error { } table.entries = entries - table.Sort() - table.PrintMarkdown() + + if err := table.PrintMarkdown(); err != nil { + return err + } return nil } diff --git a/pkg/es/cluster_util.go b/pkg/es/cluster_util.go index 5de0a86..1572187 100644 --- a/pkg/es/cluster_util.go +++ b/pkg/es/cluster_util.go @@ -30,68 +30,6 @@ import ( "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 { stats, err := conf.Clusters[leader].ES.Ccr.Stats(). Header("content-type", "application/json"). @@ -138,8 +76,8 @@ func checkClusterStatus(conf *cfg.Config, leader, follower string) bool { table.entries = [][]string{ {"Cluster Name", - Colorize(*&status[leader].Status.Name, status[leader].ClusterName), - Colorize(*&status[follower].Status.Name, status[follower].ClusterName), + Colorize(status[leader].Status.Name, status[leader].ClusterName), + Colorize(status[follower].Status.Name, status[follower].ClusterName), }, {"Active Shards", fmt.Sprintf("%d", status[leader].ActiveShards), @@ -159,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" { return true @@ -215,7 +156,11 @@ func findIlmErrors(conf *cfg.Config, leader, follower string) bool { } table.Sort() - table.PrintMarkdown() + + if err := table.PrintMarkdown(); err != nil { + fmt.Println(err) + return false + } } } @@ -285,7 +230,10 @@ func findIndicesOnlyOnLeader(conf *cfg.Config, indices ClusterIndices, leader, f } table.Sort() - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + fmt.Println(err) + return false + } return false } @@ -332,7 +280,10 @@ func findOrphanedIndices(conf *cfg.Config, indices ClusterIndices, leader, follo } table.Sort() - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + fmt.Println(err) + return false + } return false } @@ -363,7 +314,10 @@ func findFailedFollowerIndices(conf *cfg.Config, indices ClusterIndices, followe } table.Sort() - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + fmt.Println(err) + return false + } return false } diff --git a/pkg/es/index.go b/pkg/es/index.go index bb65180..fd83a8e 100644 --- a/pkg/es/index.go +++ b/pkg/es/index.go @@ -28,12 +28,6 @@ import ( "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 func IndexNames(conf *cfg.Config) ([]string, error) { res, err := conf.DefaultCluster.ES.Cat.Indices(). @@ -42,7 +36,7 @@ func IndexNames(conf *cfg.Config) ([]string, error) { Do(context.Background()) 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)) @@ -65,7 +59,7 @@ func IndexList(conf *cfg.Config) error { res, err := cat.Do(context.Background()) 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) @@ -92,7 +86,9 @@ func IndexList(conf *cfg.Config) error { } table.Sort() - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + return err + } return nil } @@ -131,7 +127,9 @@ func IndexShow(conf *cfg.Config, index string) error { {"uuid", *res[index].Settings.Index.Uuid}, } - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + return err + } return nil } diff --git a/pkg/es/node.go b/pkg/es/node.go index 9f8a094..bc54679 100644 --- a/pkg/es/node.go +++ b/pkg/es/node.go @@ -28,7 +28,7 @@ func NodeList(conf *cfg.Config) error { // get nodes nodes, err := conf.DefaultCluster.ES.Cat.Nodes().Do(context.Background()) 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) @@ -49,7 +49,9 @@ func NodeList(conf *cfg.Config) error { } table.Sort() - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + return err + } return nil } diff --git a/pkg/es/search.go b/pkg/es/search.go index 7df1ae3..13298ab 100644 --- a/pkg/es/search.go +++ b/pkg/es/search.go @@ -62,7 +62,7 @@ func Search(conf *cfg.Config, q string) error { }). Do(context.Background()) 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) diff --git a/pkg/es/snapshot.go b/pkg/es/snapshot.go index 53812ce..2ccb499 100644 --- a/pkg/es/snapshot.go +++ b/pkg/es/snapshot.go @@ -44,7 +44,7 @@ func SnapshotList(conf *cfg.Config) error { // get partial indicies ires, err := conf.DefaultCluster.ES.Cat.Indices().Do(context.Background()) if err != nil { - return fmt.Errorf("Error getting indicies: %s", err) + return fmt.Errorf("failed to get indicies: %s", err) } indicies := map[string]int{} @@ -57,7 +57,7 @@ func SnapshotList(conf *cfg.Config) error { // get snapshots sres, err := conf.DefaultCluster.ES.Cat.Snapshots().Do(context.Background()) 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) @@ -97,7 +97,9 @@ func SnapshotList(conf *cfg.Config) error { } table.Sort() - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + return err + } return nil } @@ -135,7 +137,9 @@ func SnapshotShow(conf *cfg.Config, snapshot string) error { {"shards-successful", fmt.Sprintf("%d", snap.Shards.Successful)}, } - table.PrintMarkdown() + if err := table.PrintMarkdown(); err != nil { + return err + } return nil } diff --git a/pkg/es/table.go b/pkg/es/table.go index 3bedef4..6a6d552 100644 --- a/pkg/es/table.go +++ b/pkg/es/table.go @@ -84,11 +84,11 @@ func (data *Table) PrintMarkdown() error { table.Header(data.headers) 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 { - fmt.Errorf("Failed to render table: %s", err) + return fmt.Errorf("failed to render table: %s", err) } fmt.Println(tableString.String())