Skip to content

Commit

Permalink
Add "kubectl get all" and "kubectl get elastc"
Browse files Browse the repository at this point in the history
This should fulfill demands for elastic#57
  • Loading branch information
sakurai-youhei committed Apr 6, 2023
1 parent c46f8c3 commit 0b54525
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 3 deletions.
25 changes: 22 additions & 3 deletions internal/diag.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package internal
import (
"bytes"
"context"
"fmt"
"io"
"os"
"os/signal"
Expand Down Expand Up @@ -130,6 +131,10 @@ func Run(params Params) error {
},
})

zipFile.Add(getResources(kubectl.GetInHumanReadable, ns, filters.Filters{}, []string{
"all",
}, "get-%s.txt"))

// Filters is intentionally empty here, as label filtering doesn't apply to
// pods in the operator namespace.
if err := kubectl.Logs(ns, "", filters.Filters{}, zipFile.Create); err != nil {
Expand Down Expand Up @@ -201,6 +206,15 @@ LOOP:
},
})

zipFile.Add(getResources(kubectl.GetInHumanReadable, ns, params.Filters, []string{
"all",
}, "get-%s.txt"))

// Filters is intentionally empty here, as labels are not applied to Elastic resources
zipFile.Add(getResources(kubectl.GetInHumanReadable, ns, filters.Filters{}, []string{
"elastic",
}, "get-%s.txt"))

getLogs(kubectl, zipFile, ns, params.Filters,
"common.k8s.elastic.co/type=elasticsearch",
"common.k8s.elastic.co/type=kibana",
Expand Down Expand Up @@ -255,12 +269,17 @@ func getLogs(k *Kubectl, zipFile *archive.ZipFile, ns string, filters filters.Fi
}

// getResources produces a map of filenames to functions that will when invoked retrieve the resources identified by rs
// and add write them to a writer passed to said functions.
func getResources(f func(string, string, filters.Filters, io.Writer) error, ns string, filters filters.Filters, rs []string) map[string]func(io.Writer) error {
// and add write them to a writer passed to said functions. filenames are determined using format (default is "%s.json") with rs.
func getResources(f func(string, string, filters.Filters, io.Writer) error, ns string, filters filters.Filters, rs []string, format ...string) map[string]func(io.Writer) error {
fname := "%s.json"
if len(format) > 0 {
fname = format[0]
}

m := map[string]func(io.Writer) error{}
for _, r := range rs {
resource := r
m[archive.Path(ns, resource+".json")] = func(w io.Writer) error {
m[archive.Path(ns, fmt.Sprintf(fname, resource))] = func(w io.Writer) error {
return f(resource, ns, filters, w)
}
}
Expand Down
14 changes: 14 additions & 0 deletions internal/filters/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ func (f Filters) Matches(lbls map[string]string) bool {
return false
}

// MatchesAgainstString will determine if the given labels string matches
// any of the Filter's label selectors.
func (f Filters) MatchesAgainstString(labelsString string) bool {
pairs := strings.Split(labelsString, ",")
labels := make(map[string]string, len(pairs))

for _, pair := range pairs {
kv := strings.SplitN(pair, "=", 2)
labels[kv[0]] = kv[1]
}

return f.Matches(labels)
}

// Contains will check if any of the filters of named type 'typ'
// contain a filter for an object named 'name'.
func (f Filters) Contains(name, typ string) bool {
Expand Down
15 changes: 15 additions & 0 deletions internal/filters/filters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package filters
import (
"fmt"
"reflect"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -265,5 +266,19 @@ func TestFilters_Matches(t *testing.T) {
t.Errorf("Filters.Matches() = %v, want %v", got, tt.want)
}
})
t.Run(tt.name, func(t *testing.T) {
pairs := []string{}
for key, value := range tt.labels {
pairs = append(pairs, fmt.Sprintf("%s=%s", key, value))
}
labelsString := strings.Join(pairs, ",") // KEY_1=VAL_1,KEY_2=VAL_2

f := Filters{
byType: tt.filterMap,
}
if got := f.MatchesAgainstString(labelsString); got != tt.want {
t.Errorf("Filters.MatchesAgainstString() = %v, want %v", got, tt.want)
}
})
}
}
62 changes: 62 additions & 0 deletions internal/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/cmd/exec"
"k8s.io/kubectl/pkg/cmd/get"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/describe"
"k8s.io/kubectl/pkg/polymorphichelpers"
Expand Down Expand Up @@ -225,6 +226,67 @@ func (c Kubectl) getFiltered(resource, namespace string, w io.Writer, filter fun
return printer.PrintObj(&filtered, w)
}

// GetInHumanReadable retrieves the K8s objects of type resource in namespace and marshals them into the writer w.
// If filters is not empty, this will only return resources within the cluster that its labels match
// at least one of the filter's label selectors.
func (c Kubectl) GetInHumanReadable(resource, namespace string, filters internal_filters.Filters, w io.Writer) error {
execErrOut := io.Discard
if c.verbose {
execErrOut = c.errOut
}

pipe_r, pipe_w := io.Pipe()
defer pipe_r.Close()

// GetOptions.Run() will output to the pipe writer.
options := get.NewGetOptions("eck-diagnostics", genericclioptions.IOStreams{In: nil, Out: pipe_w, ErrOut: execErrOut})
cmd := get.NewCmdGet(options.CmdParent, c.factory, options.IOStreams)

// Suppresses output to stderr
options.IgnoreNotFound = true
// Needs to show labels for filtering
ShowLabels := true
options.PrintFlags.HumanReadableFlags.ShowLabels = &ShowLabels

if err := options.Complete(c.factory, cmd, []string{resource}); err != nil {
return err
}

// Needs to set ns options here since the above Complete() overrides them using values from factory.
options.Namespace = namespace
options.ExplicitNamespace = true

if err := options.Validate(); err != nil {
return err
}

go func() {
defer pipe_w.Close()
// Simulates "kubectl --ignore-not-found --show-labels --namespace {{namespace}} get {{resource}}"
options.Run(c.factory, cmd, []string{resource})
}()

// Bridges pipe reader to the writer w with or without filtering.
if filters.Empty() {
_, err := io.Copy(w, pipe_r)
return err
} else {
scanner, index := bufio.NewScanner(pipe_r), 0
for scanner.Scan() {
line := scanner.Text() // either blank, header, or resource line
if line == "" {
fmt.Fprintln(w, line)
} else if strings.HasPrefix(line, "NAME ") {
index = strings.LastIndex(line, "LABELS")
fmt.Fprintln(w, line)
} else if index > 0 && filters.MatchesAgainstString(line[index:]) {
fmt.Fprintln(w, line)
}
}
return scanner.Err()
}
}

// getResources retrieves the K8s objects of type resource and returns a resource.Result.
func (c Kubectl) getResources(resource string, namespace string) (*resource.Result, error) {
r := c.factory.NewBuilder().
Expand Down

0 comments on commit 0b54525

Please sign in to comment.