feat(stern/stern): add queries

This commit is contained in:
Kevin Franklin Kim 2025-04-04 16:45:36 +02:00
parent 2ac4f35ff5
commit bb54a33cb5
No known key found for this signature in database
3 changed files with 179 additions and 10 deletions

View File

@ -12,11 +12,16 @@ import (
"github.com/foomo/posh/pkg/readline"
"github.com/foomo/posh/pkg/shell"
"github.com/foomo/posh/pkg/util/suggests"
"github.com/pkg/errors"
"github.com/spf13/viper"
)
type (
Command struct {
l log.Logger
name string
cfg Config
configKey string
kubectl *kubectl.Kubectl
squadron squadron.Squadron
commandTree tree.Root
@ -30,6 +35,18 @@ type (
// ~ Options
// ------------------------------------------------------------------------------------------------
func CommandWithName(v string) CommandOption {
return func(o *Command) {
o.name = v
}
}
func CommandWithConfigKey(v string) CommandOption {
return func(o *Command) {
o.configKey = v
}
}
func CommandWithNamespaceFn(v NamespaceFn) CommandOption {
return func(o *Command) {
o.namespaceFn = v
@ -40,11 +57,13 @@ func CommandWithNamespaceFn(v NamespaceFn) CommandOption {
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, squadron squadron.Squadron, opts ...CommandOption) *Command {
func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, squadron squadron.Squadron, opts ...CommandOption) (*Command, error) {
inst := &Command{
l: l.Named("stern"),
kubectl: kubectl,
squadron: squadron,
l: l.Named("stern"),
name: "stern",
configKey: "stern",
kubectl: kubectl,
squadron: squadron,
namespaceFn: func(cluster, fleet, squadron string) string {
if fleet == "default" {
return squadron
@ -58,6 +77,11 @@ func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, squadron squadron.Squadr
opt(inst)
}
}
if err := viper.UnmarshalKey(inst.configKey, &inst.cfg); err != nil {
return nil, err
}
inst.commandTree = tree.New(&tree.Node{
Name: "stern",
Description: "Tail your logs with stern",
@ -72,13 +96,17 @@ func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, squadron squadron.Squadr
Description: "Tail by query",
Args: tree.Args{
{
Name: "query",
Name: "name",
Repeat: true,
Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {
return suggests.List(inst.cfg.QueryNames(r.Args().From(2)...))
},
},
},
Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error {
fs.Default().Bool("only-log-lines", false, "Print only log lines")
fs.Default().Int("tail", -1, "The number of lines from the end of the logs to show")
fs.Default().String("all-namespaces", "", "If present, tail across all namespaces")
fs.Default().Bool("all-namespaces", false, "If present, tail across all namespaces")
fs.Default().String("namespace", "", "Kubernetes namespace to use")
fs.Default().String("container", "", "Container name when multiple containers in pod (default \".*\")")
fs.Default().String("exclude", "", "Regex of log lines to exclude")
@ -104,6 +132,43 @@ func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, squadron squadron.Squadr
},
Execute: inst.tailQuery,
},
{
Name: "raw",
Description: "Tail by raw query",
Args: tree.Args{
{
Name: "query",
},
},
Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error {
fs.Default().Bool("only-log-lines", false, "Print only log lines")
fs.Default().Int("tail", -1, "The number of lines from the end of the logs to show")
fs.Default().Bool("all-namespaces", false, "If present, tail across all namespaces")
fs.Default().String("namespace", "", "Kubernetes namespace to use")
fs.Default().String("container", "", "Container name when multiple containers in pod (default \".*\")")
fs.Default().String("exclude", "", "Regex of log lines to exclude")
fs.Default().String("exclude-container", "", "Exclude a Container name")
fs.Default().String("include", "", "Regex of log lines to include")
fs.Default().String("output", "default", "Specify predefined template")
fs.Default().String("selector", "", "Selector (label query) to filter on. If present, default to \".*\" for the pod-query.")
fs.Default().String("since", "default", "Return logs newer than a relative duration like 5s, 2m, or 3")
fs.Default().String("template", "default", "Template to use for log lines")
fs.Internal().String("profile", "", "Profile to use.")
if err := fs.Default().SetValues("output", "raw", "json", "extjson", "ppextjson"); err != nil {
return err
}
if r.Args().HasIndex(0) {
if err := fs.Internal().SetValues("profile", inst.kubectl.Cluster(r.Args().At(0)).Profiles(ctx)...); err != nil {
return err
}
if err := fs.Default().SetValues("namespace", inst.kubectl.Cluster(r.Args().At(0)).Namespaces(ctx, "")...); err != nil {
return err
}
}
return nil
},
Execute: inst.tailRaw,
},
{
Name: "squadron",
Description: "Tail by squadron unit",
@ -150,7 +215,7 @@ func NewCommand(l log.Logger, kubectl *kubectl.Kubectl, squadron squadron.Squadr
},
})
return inst
return inst, nil
}
// ------------------------------------------------------------------------------------------------
@ -191,15 +256,28 @@ func (c *Command) tail(ctx context.Context, r *readline.Readline, args ...string
return err
}
return shell.New(ctx, c.l, "stern").
cmd := shell.New(ctx, c.l, "stern").
Env(c.kubectl.Cluster(cluster).Env(profile)).
Args(args...).
Args(fs.Visited().Args()...).
Args(r.AdditionalArgs()...).
Run()
Args(r.AdditionalArgs()...)
return cmd.Run()
}
func (c *Command) tailQuery(ctx context.Context, r *readline.Readline) error {
queries := c.cfg.FindQueries(r.Args().From(2)...)
if queries == nil {
return errors.New("query not found")
}
var args []string
for _, query := range queries {
args = append(args, query.Query...)
}
return c.tail(ctx, r, args...)
}
func (c *Command) tailRaw(ctx context.Context, r *readline.Readline) error {
return c.tail(ctx, r, r.Args().At(2))
}

45
stern/stern/config.go Normal file
View File

@ -0,0 +1,45 @@
package stern
import (
"sort"
"github.com/samber/lo"
)
type Config struct {
Queries map[string]Query `json:"queries" yaml:"queries"`
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c Config) FindQueries(names ...string) []Query {
if len(names) == 0 {
return nil
}
query, ok := c.Queries[names[0]]
if !ok {
return nil
}
ret := []Query{query}
if queries := query.FindQueries(names[1:]...); queries != nil {
ret = append(ret, queries...)
}
return ret
}
func (c Config) QueryNames(names ...string) []string {
if len(names) == 0 {
ret := lo.Keys(c.Queries)
sort.Strings(ret)
return ret
}
query, ok := c.Queries[names[0]]
if !ok {
ret := lo.Keys(c.Queries)
sort.Strings(ret)
return ret
}
return query.QueryNames(names[1:]...)
}

46
stern/stern/query.go Normal file
View File

@ -0,0 +1,46 @@
package stern
import (
"sort"
"github.com/samber/lo"
)
type Query struct {
Query []string `json:"query" yaml:"query"`
Queries map[string]Query `json:"queries" yaml:"queries"`
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (q Query) FindQueries(names ...string) []Query {
if len(names) == 0 {
return nil
}
query, ok := q.Queries[names[0]]
if !ok {
return nil
}
ret := []Query{query}
if queries := query.FindQueries(names[1:]...); queries != nil {
ret = append(ret, queries...)
}
return ret
}
func (q Query) QueryNames(names ...string) []string {
if len(names) == 0 {
ret := lo.Keys(q.Queries)
sort.Strings(ret)
return ret
}
query, ok := q.Queries[names[0]]
if !ok {
ret := lo.Keys(q.Queries)
sort.Strings(ret)
return ret
}
return query.QueryNames(names[1:]...)
}