From d0d1d69791dfdca90bcba6c8b6304543ca3107d4 Mon Sep 17 00:00:00 2001 From: "T. von Dein" Date: Mon, 15 Jun 2026 15:18:21 +0200 Subject: [PATCH] add index template {ls,sh} (#33) --- README.md | 8 + cmd/cluster_settings.go | 6 +- cmd/index.go | 7 +- cmd/index_template.go | 203 ++++++++++++++ cmd/search.go | 14 +- cmd/utilities.go | 35 +++ go.mod | 4 +- go.sum | 52 ++-- pkg/cfg/config.go | 40 ++- pkg/es/cluster_settings.go | 32 +-- pkg/es/cluster_util.go | 42 +++ pkg/es/index.go | 6 +- pkg/es/index_template.go | 449 ++++++++++++++++++++++++++++++ pkg/es/index_template_settings.go | 61 ++++ pkg/es/repl.go | 16 +- 15 files changed, 875 insertions(+), 100 deletions(-) create mode 100644 cmd/index_template.go create mode 100644 cmd/utilities.go create mode 100644 pkg/es/index_template.go create mode 100644 pkg/es/index_template_settings.go diff --git a/README.md b/README.md index 2c9e6d9..2512a92 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,11 @@ Command tree: list set status + datastream + create + delete + list + show debug doc add @@ -90,6 +95,9 @@ Command tree: list modify show + template + list + show node list show diff --git a/cmd/cluster_settings.go b/cmd/cluster_settings.go index f7d8bd9..b394816 100644 --- a/cmd/cluster_settings.go +++ b/cmd/cluster_settings.go @@ -27,7 +27,7 @@ import ( ) const ( - SETTINGS = `https://www.elastic.co/docs/reference/elasticsearch/configuration-reference` + SETTINGS = `` ) func ClusterSettings(conf *cfg.Config) *cli.Command { @@ -85,7 +85,9 @@ func ClusterSettingsSet(conf *cfg.Config) *cli.Command { Name: "set", Usage: "set|update cluster settings", Aliases: []string{"set", "update"}, - UsageText: "set [options] setting:value [setting:value ...]\n\nReference: " + SETTINGS, + UsageText: "set [options] setting:value [setting:value ...]", + CustomHelpTemplate: addReference( + "https://www.elastic.co/docs/reference/elasticsearch/configuration-reference"), Flags: []cli.Flag{ &cli.BoolFlag{ diff --git a/cmd/index.go b/cmd/index.go index 8b40687..60b2de7 100644 --- a/cmd/index.go +++ b/cmd/index.go @@ -41,8 +41,11 @@ func Index(conf *cfg.Config) *cli.Command { IndexClose(conf), IndexAllocation(conf), IndexModify(conf), - IndexAlias(conf), IndexFields(conf), + + // sub commands + IndexAlias(conf), + IndexTemplate(conf), }, } } @@ -165,6 +168,7 @@ Valid field mapping types: integer, text, date, keyword`, Usage: "number of replicas to create", Destination: &conf.Replicas, Aliases: []string{"r"}, + Value: 1, }, }, @@ -240,6 +244,7 @@ func IndexModify(conf *cfg.Config) *cli.Command { Usage: "number of replicas", Destination: &conf.Replicas, Aliases: []string{"r"}, + Value: 1, }, }, diff --git a/cmd/index_template.go b/cmd/index_template.go new file mode 100644 index 0000000..96b6084 --- /dev/null +++ b/cmd/index_template.go @@ -0,0 +1,203 @@ +/* +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 cmd + +import ( + "context" + "errors" + "fmt" + + "codeberg.org/scip/esctl/pkg/cfg" + "codeberg.org/scip/esctl/pkg/es" + + "github.com/urfave/cli/v3" +) + +func IndexTemplate(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "template", + Aliases: []string{"tpl"}, + Usage: "manage index templates", + + Commands: []*cli.Command{ + IndexTemplateList(conf), + IndexTemplateShow(conf), + IndexTemplateCreate(conf, false), + IndexTemplateCreate(conf, true), + IndexTemplateDelete(conf), + }, + } +} + +func IndexTemplateList(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "list", + Aliases: []string{"ls"}, + Usage: "list index templates", + + Action: func(ctx context.Context, cmd *cli.Command) error { + return es.IndexTemplateList(conf) + }, + } +} + +func IndexTemplateShow(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "show", + Aliases: []string{"sh"}, + Usage: "show details about an index template", + + Action: func(ctx context.Context, cmd *cli.Command) error { + index := cmd.Args().Get(0) + if index == "" { + return errors.New("no index template specified") + } + + return es.IndexTemplateShow(conf, cmd.Args().Get(0)) + }, + + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + complete(cmd, Cindex) + }, + } +} + +func IndexTemplateCreate(conf *cfg.Config, modify bool) *cli.Command { + name := "create" + alias := "+" + required := true + + if modify { + name = "modify" + alias = "mod" + required = false + } + + return &cli.Command{ + Name: name, + Aliases: []string{alias}, + Usage: name + " a new index template", + UsageText: name + ` ...`, + CustomHelpTemplate: addReference( + `Valid field mapping types: integer, text, date, keyword + +Doc for index settings: +https://www.elastic.co/docs/reference/elasticsearch/index-settings +`), + + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "data-stream", + Usage: "template creates a data stream", + Destination: &conf.Stream, + Aliases: []string{"S"}, + }, + &cli.BoolFlag{ + Name: "allow-auto-create", + Usage: "automatically create indices", + Destination: &conf.AutoCreate, + Aliases: []string{"a"}, + }, + &cli.StringFlag{ + Name: "mode", + Usage: "index mode, one of: standard, timeseries, logsdb or lookup", + Destination: &conf.Mode, + Aliases: []string{"m"}, + }, + &cli.StringFlag{ + Name: "retention", + Usage: "data retention, e.g. -1 (infinitely), 30d, 3m or 1y (data stream only)", + Destination: &conf.Retention, + Aliases: []string{"r"}, + Required: required, + }, + &cli.IntFlag{ + Name: "priority", + Usage: "index template priority", + Destination: &conf.Priority, + Aliases: []string{"p"}, + }, + &cli.StringSliceFlag{ + Name: "index-pattern", + Usage: "index matching pattern", + Destination: &conf.Patterns, + Aliases: []string{"i"}, + Required: required, + }, + &cli.StringSliceFlag{ + Name: "settings", + Usage: "individual template settings, format: name:value, multiple possible", + Destination: &conf.Settings, + Aliases: []string{"s"}, + }, + &cli.StringSliceFlag{ + Name: "component", + Usage: "include component template, multiple possible", + Destination: &conf.Components, + Aliases: []string{"c"}, + }, + &cli.StringSliceFlag{ + Name: "meta", + Usage: "custom meta field, format: name:value, multiple possible", + Destination: &conf.Meta, + Aliases: []string{"M"}, + }, + &cli.StringSliceFlag{ + Name: "aliases", + Usage: "set up aliases to associate with indices", + Destination: &conf.Aliases, + Aliases: []string{"A"}, + }, + }, + + Action: func(ctx context.Context, cmd *cli.Command) error { + args := cmd.Args() + if args.Len() == 0 { + return fmt.Errorf("no name specified") + } + + mappings := args.Slice()[1:] + + if modify { + return es.IndexTemplateModify(conf, args.Get(0), mappings) + } + + return es.IndexTemplateCreate(conf, args.Get(0), mappings) + }, + } +} + +func IndexTemplateDelete(conf *cfg.Config) *cli.Command { + return &cli.Command{ + Name: "delete", + Aliases: []string{"rm"}, + Usage: "delete an index template", + + Action: func(ctx context.Context, cmd *cli.Command) error { + index := cmd.Args().Get(0) + if index == "" { + return errors.New("no index template specified") + } + + return es.IndexTemplateDelete(conf, cmd.Args().Get(0)) + }, + + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + complete(cmd, Cindex) + }, + } +} diff --git a/cmd/search.go b/cmd/search.go index 2ebfc94..0f33cd7 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -25,7 +25,7 @@ import ( "github.com/urfave/cli/v3" ) -const SearchUsage = ` might be one of: +const SearchUsage = `field might be one of: =: Must match !=: Must not match @@ -43,15 +43,15 @@ For datetime range format refer to: https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#date-math For timestamp formats refer to: -https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-date-format -` +https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-date-format` func Search(conf *cfg.Config) *cli.Command { return &cli.Command{ - Name: "search", - Aliases: []string{"/"}, - Usage: "search within an index", - UsageText: "search [options] [<[field]pattern> ...]\n" + SearchUsage, + Name: "search", + Aliases: []string{"/"}, + Usage: "search within an index", + UsageText: "search [options] [<[field]pattern> ...]\n", + CustomHelpTemplate: addReference(SearchUsage), Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/utilities.go b/cmd/utilities.go new file mode 100644 index 0000000..85702eb --- /dev/null +++ b/cmd/utilities.go @@ -0,0 +1,35 @@ +/* +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 cmd + +import ( + "fmt" + "strings" + + "github.com/urfave/cli/v3" +) + +func addReference(ref string) string { + indentedRef := []string{} + + for _, line := range strings.Split(ref, "\n") { + indentedRef = append(indentedRef, " "+line) + } + return fmt.Sprintf("%s\nREFERENCE:\n%s\n", + cli.SubcommandHelpTemplate, + strings.Join(indentedRef, "\n")) +} diff --git a/go.mod b/go.mod index 9e38700..3945ec7 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/fatih/color v1.19.0 github.com/mattn/go-isatty v0.0.22 github.com/olekukonko/tablewriter v1.1.4 + github.com/tidwall/gjson v1.19.0 github.com/tlinden/yadu v0.1.3 github.com/urfave/cli/v3 v3.9.1-0.20260524212652-be8b79d0c8de gopkg.in/yaml.v3 v3.0.1 @@ -37,19 +38,16 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/goccy/go-json v0.10.6 // indirect - github.com/lmittmann/tint v1.1.3 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-runewidth v0.0.23 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.8 // indirect - github.com/tidwall/gjson v1.19.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/sys v0.42.0 // indirect ) diff --git a/go.sum b/go.sum index 973f050..61994e5 100644 --- a/go.sum +++ b/go.sum @@ -2,28 +2,24 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/elastic/elastic-transport-go/v8 v8.9.0 h1:KeT/2P54F0xS0S8Y3Pf+tFDg4HmBgReQMB+BMz8dDAs= -github.com/elastic/elastic-transport-go/v8 v8.9.0/go.mod h1:ssMTvNS2hwf7CaiGsRRsx4gQHFZ/jS/DkLcISxekWzc= github.com/elastic/elastic-transport-go/v8 v8.11.0 h1:taYmqC2M6+fZt/+W+ENYh/W5L9+KrlJGOSbEJs8egWc= github.com/elastic/elastic-transport-go/v8 v8.11.0/go.mod h1:DZQ0szCNywc9F+C9l/Kkd4n69SvJVj0I3yK1Of7s3l8= -github.com/elastic/go-elasticsearch/v9 v9.3.2 h1:nvtvfN/Gsp/rzUPz/9yILwDAsYJ3s5L0VmhR16zPKCA= -github.com/elastic/go-elasticsearch/v9 v9.3.2/go.mod h1:ubKUMJCJbX5V/gW5MIn2NQZyaEZ61ubXwJmD5UMNrM8= github.com/elastic/go-elasticsearch/v9 v9.3.4 h1:vnqXl6jnlA+ZwlfRaKF9BR9woZC1KSB17gtyvgf8zVU= github.com/elastic/go-elasticsearch/v9 v9.3.4/go.mod h1:ubKUMJCJbX5V/gW5MIn2NQZyaEZ61ubXwJmD5UMNrM8= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -33,31 +29,34 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I= -github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= -github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= -github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8= github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw= github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -66,10 +65,6 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY= github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA= -github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= -github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= -github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c= -github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/urfave/cli/v3 v3.9.1-0.20260524212652-be8b79d0c8de h1:ESKPiS7inVoBnv4FmgGNZdjWBI/wmvaragoyD3D9nM4= github.com/urfave/cli/v3 v3.9.1-0.20260524212652-be8b79d0c8de/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -78,18 +73,15 @@ go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go index 29228bb..bb80b87 100644 --- a/pkg/cfg/config.go +++ b/pkg/cfg/config.go @@ -48,19 +48,31 @@ type Cluster struct { } type Config struct { - ConfigFile string // -c - CurrentCluster string // -C - Debug bool // -d - Output string // -o - Clusters map[string]*Cluster - DefaultCluster *Cluster - Index string // index: -i - Failed, Partials bool // index: flags - Shards, Replicas int // index create+allocation: -s -r - Wait bool // index create: -w - Primary bool // index allocation: -p - Searchable bool // index fields: -s - Aggretable bool // index fields: -a + ConfigFile string // -c + CurrentCluster string // -C + Debug bool // -d + Output string // -o + Clusters map[string]*Cluster + DefaultCluster *Cluster + Index string // index: -i + Failed, Partials bool // index: flags + Shards, Replicas int // index create+allocation: -s -r + Wait bool // index create: -w + Primary bool // index allocation: -p + Searchable bool // index fields: -s + Aggretable bool // index fields: -a + + Priority int // index template create: -p + Settings []string // index template create: -s + Patterns []string // index template create: -i + Components []string // index template create: -c + Meta []string // index template create: -M + Aliases []string // index template create: -A + Stream bool // index template create: -S + AutoCreate bool // index template create: -a + Mode string // index template create: -a + Retention string // index template create: -r + From, To, MaxItems int // search: flags Filter []string // search: -F Path string // search+doc sh: -p @@ -75,7 +87,7 @@ type Config struct { Ascending bool // sort: -a Exclude string // cluster compare: -e (regexp) All, Verbose bool // cluster status: -a -v - Persistent, Transient, Default bool // -p -t -D cluster settings set + Persistent, Transient, Default bool // cluster settings set: -p -t -D Force bool // ccr follower renew: -f HaveJQ bool // determined at runtime by ourselfes DebugHTTP bool // root: --debug-http diff --git a/pkg/es/cluster_settings.go b/pkg/es/cluster_settings.go index e27fb91..4b0b5cd 100644 --- a/pkg/es/cluster_settings.go +++ b/pkg/es/cluster_settings.go @@ -27,36 +27,6 @@ import ( "github.com/urfave/cli/v3" ) -// recursively traverse the raw settings hash and build a flat map -// consisting of the translated path and its value. -// -// e.g. -// logger: -// -// org: -// elasticsearch: -// transport: -// OutboundHandler: "ERROR" -// -// gets: -// -// logger.org.elasticsearch.transport.OutboundHandler: "ERROR" -func getJsonPath(raw map[string]any, topic string) map[string]string { - paths := map[string]string{} - - for name, data := range raw { - path := topic + "." + name - switch value := data.(type) { - case string: - paths[path] = value - case map[string]any: - paths = getJsonPath(value, path) - } - } - - return paths -} - func ClusterSettingsList(conf *cfg.Config) error { res, err := conf.DefaultCluster.ES.Cluster.GetSettings(). Header("content-type", "application/json"). @@ -87,7 +57,7 @@ func ClusterSettingsList(conf *cfg.Config) error { return fmt.Errorf("failed to unmarshall setting for topic %s: %s", topic, err) } - paths := getJsonPath(data, topic) + paths := getJsonPath(map[string]string{}, data, topic) slog.Debug("settings", topic, paths) for setting, value := range paths { diff --git a/pkg/es/cluster_util.go b/pkg/es/cluster_util.go index 019920c..35a6697 100644 --- a/pkg/es/cluster_util.go +++ b/pkg/es/cluster_util.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "regexp" + "strconv" "strings" "sync" @@ -397,3 +398,44 @@ func getClusterData(es *elasticsearch.TypedClient, wg *sync.WaitGroup, reschan c reschan <- ar } + +// recursively traverse the raw settings hash and build a flat map +// consisting of the translated path and its value. +// +// e.g. +// logger: +// +// org: +// elasticsearch: +// transport: +// OutboundHandler: "ERROR" +// +// gets: +// +// logger.org.elasticsearch.transport.OutboundHandler: "ERROR" +func getJsonPath(paths map[string]string, raw map[string]any, topic string) map[string]string { + for name, data := range raw { + path := topic + "." + name + + switch value := data.(type) { + case string: + paths[path] = value + case *string: + paths[path] = *value + case int: + paths[path] = strconv.Itoa(value) + case *int: + paths[path] = strconv.Itoa(*value) + case map[string]any: + paths = getJsonPath(paths, value, path) + case []any: + val := []string{} + for _, item := range value { + val = append(val, fmt.Sprintf("%v", item)) + } + paths[path] = strings.Join(val, ",") + } + } + + return paths +} diff --git a/pkg/es/index.go b/pkg/es/index.go index 0aace00..a14bc45 100644 --- a/pkg/es/index.go +++ b/pkg/es/index.go @@ -126,11 +126,7 @@ func IndexList(conf *cfg.Config) error { } table.Sort() - if err := table.Print(); err != nil { - return err - } - - return nil + return table.Print() } func IndexShow(conf *cfg.Config, indexpattern string) error { diff --git a/pkg/es/index_template.go b/pkg/es/index_template.go new file mode 100644 index 0000000..cd6e187 --- /dev/null +++ b/pkg/es/index_template.go @@ -0,0 +1,449 @@ +/* +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" + "encoding/json" + "errors" + "fmt" + "log/slog" + "slices" + "strconv" + "strings" + + "codeberg.org/scip/esctl/pkg/cfg" + "codeberg.org/scip/esctl/pkg/printer" + "github.com/elastic/go-elasticsearch/v9/typedapi/esdsl" + "github.com/elastic/go-elasticsearch/v9/typedapi/types" +) + +// used for completion +func IndexTemplateList(conf *cfg.Config) error { + res, err := conf.DefaultCluster.ES.Indices.GetIndexTemplate(). + Header("content-type", "application/json"). + Header("accept", "application/json"). + Do(context.Background()) + + if err != nil { + return fmt.Errorf("failed to get index templates: %s", esErrorString(err)) + } + + slog.Debug("res", "index templates", res) + + table := printer.NewTable(conf, 5, len(res.IndexTemplates)) + table.Addheaders("name", "description", "priority") + + for idx, tpl := range res.IndexTemplates { + desc, err := json.Marshal(tpl.IndexTemplate.Meta_["description"]) + if err != nil { + return fmt.Errorf("failed to unmarshal meta json data: %w", err) + } + + table.Entries[idx] = []string{ + tpl.Name, + string(desc), + fmt.Sprintf("%d", tpl.IndexTemplate.Priority), + } + } + + table.Sort() + return table.Print() +} + +func IndexTemplateShow(conf *cfg.Config, tplname string) error { + res, err := conf.DefaultCluster.ES.Indices.GetIndexTemplate(). + Name(tplname). + Header("content-type", "application/json"). + Header("accept", "application/json"). + Do(context.Background()) + + if err != nil { + return fmt.Errorf("failed to get index template: %s", esErrorString(err)) + } + + slog.Debug("res", "index template", res) + + if len(res.IndexTemplates) != 1 { + return errors.New("multiple or no index template matched the pattern") + } + + tpl := res.IndexTemplates[0] + + table := printer.NewTable(conf, 2, 6) + table.Addheaders("index template property", "value") + + desc, err := json.Marshal(tpl.IndexTemplate.Meta_["description"]) + if err != nil { + return fmt.Errorf("failed to unmarshal meta json data: %w", err) + } + + hasds := tpl.IndexTemplate.DataStream != nil + + aliases := []string{} + for alias := range tpl.IndexTemplate.Template.Aliases { + aliases = append(aliases, alias) + } + + table.Entries = [][]string{ + {"name", tpl.Name}, + {"description", string(desc)}, + {"index patterns", strings.Join(tpl.IndexTemplate.IndexPatterns, ",")}, + {"composed of", strings.Join(tpl.IndexTemplate.ComposedOf, ",")}, + {"data stream enabled", fmt.Sprintf("%t", hasds)}, + {"aliases", strings.Join(aliases, ",")}, + } + + if err := table.Print(); err != nil { + return err + } + + table = printer.NewTable(conf, 2, 0) + table.Addheaders("index setting property", "value") + + err = getIndexTemplateSettings(conf, tplname, table) + if err != nil { + return nil + } + + fmt.Println() + if err := table.Print(); err != nil { + return err + } + + table = printer.NewTable(conf, 2, 0) + table.Addheaders("index field mapping", "type") + + for name, field := range tpl.IndexTemplate.Template.Mappings.Properties { + typeval := "" + + switch val := field.(type) { + case *types.IntegerNumberProperty: + typeval = val.Type + case *types.KeywordProperty: + typeval = val.Type + case *types.DateProperty: + typeval = val.Type + } + + table.Entries = append(table.Entries, []string{ + name, typeval, + }) + } + + fmt.Println() + if err := table.Print(); err != nil { + return err + } + + return nil +} + +func IndexTemplateCreate(conf *cfg.Config, name string, mappings []string) error { + settings := esdsl.NewIndexSettings() + maps := esdsl.NewIndexTemplateMapping() + + create := conf.DefaultCluster.ES.Indices.PutIndexTemplate(name). + Header("content-type", "application/json"). + Header("accept", "application/json") + + if conf.Shards > 0 { + settings = settings.NumberOfShards(strconv.Itoa(conf.Shards)) + } + + if conf.Replicas > 0 { + settings = settings.NumberOfReplicas(strconv.Itoa(conf.Replicas)) + } + + if conf.Stream { + create.DataStream(esdsl.NewDataStreamVisibility()) + + if conf.Retention != "" { + maps.Lifecycle( + esdsl.NewDataStreamLifecycle(). + DataRetention( + esdsl.NewDuration().String(conf.Retention))) + } + } + + if conf.AutoCreate { + create.AllowAutoCreate(true) + } + + if conf.Mode != "" { + if !slices.Contains([]string{"standard", "timeseries", "logsdb", "lookup"}, conf.Mode) { + return errors.New("mode must be one of: standard, timeseries, logsdb or lookup") + } + + settings = settings.Mode(conf.Mode) + } + + if len(mappings) > 0 { + typemaps := esdsl.NewTypeMapping() + + for _, mapping := range mappings { + parts := strings.Split(mapping, ":") + if len(parts) != 2 { + return fmt.Errorf( + "invalid mapping %s, expect (type: integer, text, date, keyword)", + mapping) + } + + switch parts[1] { + case "text": + typemaps.AddProperty(parts[0], esdsl.NewTextProperty()) + case "integer": + typemaps.AddProperty(parts[0], esdsl.NewIntegerNumberProperty()) + case "date": + typemaps.AddProperty(parts[0], esdsl.NewDateProperty()) + case "keyword": + typemaps.AddProperty(parts[0], esdsl.NewKeywordProperty()) + } + } + + maps.Mappings(typemaps) + } + + if len(conf.Components) > 0 { + create.ComposedOf(conf.Components...) + } + + for _, alias := range conf.Aliases { + maps.AddAlias(alias, esdsl.NewAlias()) + } + + if len(conf.Meta) > 0 { + metadata := map[string]json.RawMessage{} + + for _, meta := range conf.Meta { + parts := strings.Split(meta, ":") + if len(parts) != 2 { + return errors.New("meta data must be in the form key:value") + } + + msg, err := json.Marshal(parts[1]) + if err != nil { + return fmt.Errorf("failed to json marshal metadata %s: %w", meta, err) + } + + metadata[parts[0]] = msg + } + + create.Meta_(esdsl.NewMetadata(metadata)) + } + + if len(conf.Settings) > 0 { + usersettings := map[string]json.RawMessage{} + + for _, meta := range conf.Settings { + parts := strings.Split(meta, ":") + if len(parts) != 2 { + return errors.New("settings data must be in the form key:value") + } + + msg, err := json.Marshal(parts[1]) + if err != nil { + return fmt.Errorf("failed to json marshal metadata %s: %w", meta, err) + } + + usersettings[parts[0]] = msg + } + + settings = settings.IndexSettings(usersettings) + } + + maps.Settings(settings) + create.Template(maps) + create.IndexPatterns(conf.Patterns...) + + _, err := create.Do(context.Background()) + + if err != nil { + return fmt.Errorf("failed to create index template: %s", esErrorString(err)) + } + + return nil +} + +// FIXME: func's too long, refactor +// FIXME: it's not yet possible to remove items in lists (aliases, mappings, settings), just overwrite them +func IndexTemplateModify(conf *cfg.Config, name string, mappings []string) error { + maps := esdsl.NewIndexTemplateMapping() + + // load existing index mapping + res, err := conf.DefaultCluster.ES.Indices.GetIndexTemplate(). + Name(name). + Header("content-type", "application/json"). + Header("accept", "application/json"). + Do(context.Background()) + + if err != nil { + return fmt.Errorf("failed to get index template: %s", esErrorString(err)) + } + + slog.Debug("res", "index template", res) + + if len(res.IndexTemplates) != 1 { + return errors.New("multiple or no index template matched the pattern") + } + + tpl := res.IndexTemplates[0] + + // our modify PUT request + modify := conf.DefaultCluster.ES.Indices.PutIndexTemplate(name). + Header("content-type", "application/json"). + Header("accept", "application/json") + + // load existing settings, if any + settings := tpl.IndexTemplate.Template.Settings + + // pre fill mappings and aliases + maps.Mappings(tpl.IndexTemplate.Template.Mappings) + maps.Aliases(tpl.IndexTemplate.Template.Aliases) + + // pre fill meta, if any + metadata := map[string]json.RawMessage{} + for key, value := range tpl.IndexTemplate.Meta_ { + metadata[key] = value + } + + // pre fill components + modify.ComposedOf(tpl.IndexTemplate.ComposedOf...) + + if conf.Stream { + modify.DataStream(esdsl.NewDataStreamVisibility()) + + if conf.Retention != "" { + maps.Lifecycle( + esdsl.NewDataStreamLifecycle(). + DataRetention( + esdsl.NewDuration().String(conf.Retention))) + } + } + + modify.AllowAutoCreate(conf.AutoCreate) + + if conf.Mode != "" { + if !slices.Contains([]string{"standard", "timeseries", "logsdb", "lookup"}, conf.Mode) { + return errors.New("mode must be one of: standard, timeseries, logsdb or lookup") + } + + *settings.Mode = conf.Mode + } + + if len(mappings) > 0 { + typemaps := esdsl.NewTypeMapping() + + for _, mapping := range mappings { + parts := strings.Split(mapping, ":") + if len(parts) != 2 { + return fmt.Errorf( + "invalid mapping %s, expect (type: integer, text, date, keyword)", + mapping) + } + + switch parts[1] { + case "text": + typemaps.AddProperty(parts[0], esdsl.NewTextProperty()) + case "integer": + typemaps.AddProperty(parts[0], esdsl.NewIntegerNumberProperty()) + case "date": + typemaps.AddProperty(parts[0], esdsl.NewDateProperty()) + case "keyword": + typemaps.AddProperty(parts[0], esdsl.NewKeywordProperty()) + } + } + + maps.Mappings(typemaps) + } + + if len(conf.Components) > 0 { + modify.ComposedOf(conf.Components...) + } + + for _, alias := range conf.Aliases { + maps.AddAlias(alias, esdsl.NewAlias()) + } + + if len(conf.Meta) > 0 { + for _, meta := range conf.Meta { + parts := strings.Split(meta, ":") + if len(parts) != 2 { + return errors.New("meta data must be in the form key:value") + } + + msg, err := json.Marshal(parts[1]) + if err != nil { + return fmt.Errorf("failed to json marshal metadata %s: %w", meta, err) + } + + metadata[parts[0]] = msg + } + + } + + modify.Meta_(esdsl.NewMetadata(metadata)) + + if len(conf.Settings) > 0 { + usersettings := map[string]json.RawMessage{} + + for _, meta := range conf.Settings { + parts := strings.Split(meta, ":") + if len(parts) != 2 { + return errors.New("settings data must be in the form key:value") + } + + msg, err := json.Marshal(parts[1]) + if err != nil { + return fmt.Errorf("failed to json marshal metadata %s: %w", meta, err) + } + + usersettings[parts[0]] = msg + } + + settings.IndexSettings = usersettings + } + + patterns := tpl.IndexTemplate.IndexPatterns + if len(conf.Patterns) > 0 { + patterns = conf.Patterns + } + + maps.Settings(settings) + modify.Template(maps) + modify.IndexPatterns(patterns...) + + _, err = modify.Do(context.Background()) + + if err != nil { + return fmt.Errorf("failed to modify index template: %s", esErrorString(err)) + } + + return nil +} + +func IndexTemplateDelete(conf *cfg.Config, name string) error { + _, err := conf.DefaultCluster.ES.Indices.DeleteIndexTemplate(name). + Header("content-type", "application/json"). + Header("accept", "application/json"). + Do(context.Background()) + if err != nil { + return fmt.Errorf("failed to delete index template: %s", esErrorString(err)) + } + + return nil +} diff --git a/pkg/es/index_template_settings.go b/pkg/es/index_template_settings.go new file mode 100644 index 0000000..5923e83 --- /dev/null +++ b/pkg/es/index_template_settings.go @@ -0,0 +1,61 @@ +package es + +import ( + "encoding/json" + "fmt" + + "codeberg.org/scip/esctl/pkg/cfg" + "codeberg.org/scip/esctl/pkg/printer" +) + +type TplTemplateData struct { + Settings map[string]any `json:"settings"` + Aliases map[string]any `json:"aliases"` + Lifecycle map[string]any `json:"lifecycle"` + Mappings map[string]any `json:"mappings"` +} + +type TplTemplate struct { + Template TplTemplateData `json:"template"` +} + +type TplTpl struct { + IndexTemplate TplTemplate `json:"index_template"` +} + +type Tpl struct { + IndexTemplates []TplTpl `json:"index_templates"` +} + +func getIndexTemplateSettings(conf *cfg.Config, tplname string, table *printer.Table) error { + raw, err := CallAPI(conf, "GET", "/_index_template/"+tplname, "") + if err != nil { + return err + } + + data := Tpl{} + if err = json.Unmarshal(raw, &data); err != nil { + return err + } + + if conf.Debug { + if err := prettyfiJson(conf, raw); err != nil { + return err + } + } + + if len(data.IndexTemplates) == 0 { + return fmt.Errorf("index_template %s not found", tplname) + } + + tpl := data.IndexTemplates[0].IndexTemplate.Template.Settings + for topic, val := range tpl { + paths := getJsonPath(map[string]string{}, val.(map[string]any), topic) + + for setting, value := range paths { + table.Entries = append(table.Entries, []string{setting, fmt.Sprintf("%v", value)}) + } + } + + return nil +} diff --git a/pkg/es/repl.go b/pkg/es/repl.go index ab30120..2d96ef7 100644 --- a/pkg/es/repl.go +++ b/pkg/es/repl.go @@ -110,12 +110,14 @@ func Repl(conf *cfg.Config) error { fmt.Println(err) } - err = CallAPI(conf, parts[0], parts[1], data) + raw, err := CallAPI(conf, parts[0], parts[1], data) if err != nil { fmt.Printf("failed to call API: %s\n", esErrorString(err)) } - reader.SetPrompt("> ") + if err := prettyfiJson(conf, raw); err != nil { + fmt.Println(err) + } } return nil @@ -125,7 +127,7 @@ func encodeAuth(username, password string) string { return base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) } -func CallAPI(conf *cfg.Config, verb, path, data string) error { +func CallAPI(conf *cfg.Config, verb, path, data string) ([]byte, error) { verb = strings.ToUpper(verb) // we're using port-forwards anyway @@ -137,7 +139,7 @@ func CallAPI(conf *cfg.Config, verb, path, data string) error { req, err := http.NewRequest(verb, conf.DefaultCluster.Uri+path, bytes.NewBuffer([]byte(data))) if err != nil { - return err + return nil, err } req.Header.Add("Content-Type", "application/json") @@ -147,16 +149,16 @@ func CallAPI(conf *cfg.Config, verb, path, data string) error { // actually execute the request resp, err := client.Do(req) if err != nil { - return err + return nil, err } // Read and print response body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("failed to read response body: %s", err) + return nil, fmt.Errorf("failed to read response body: %s", err) } - return prettyfiJson(conf, body) + return body, nil } func prettyfiJson(conf *cfg.Config, raw []byte) error {