Add CI pipelines, fix linting issues (#12)

This commit is contained in:
T. von Dein
2026-05-07 15:01:18 +02:00
parent 5e82a3706b
commit 38ed9de0b4
14 changed files with 257 additions and 97 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
Elasticsearch CLI

View File

@@ -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]

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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())