Skip to content

Commit

Permalink
Add filters support - revised
Browse files Browse the repository at this point in the history
  • Loading branch information
naemono committed Nov 7, 2022
1 parent 151b870 commit dc6aa72
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 80 deletions.
66 changes: 9 additions & 57 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,26 @@
package main

import (
"fmt"
"log"
"os"
"strings"
"time"

"github.com/elastic/eck-diagnostics/internal"
internal_filters "github.com/elastic/eck-diagnostics/internal/filters"
"github.com/spf13/cobra"
)

var (
filters []string
diagParams = internal.Params{}
elasticTypeKey = "common.k8s.elastic.co/type"
elasticsearchNameFormat = "%s.k8s.elastic.co/cluster-name"
elasticNameFormat = "%s.k8s.elastic.co/name"
filters []string
diagParams = internal.Params{}
)

func main() {
cmd := &cobra.Command{
Use: "eck-diagnostics",
Short: "ECK support diagnostics tool",
Long: "Dump ECK and Kubernetes data for support and troubleshooting purposes.",
PreRunE: formatFilters,
PreRunE: validateFilters,
RunE: func(cmd *cobra.Command, args []string) error {
return internal.Run(diagParams)
},
Expand All @@ -55,59 +51,15 @@ func main() {
}
}

func formatFilters(_ *cobra.Command, _ []string) error {
var typ, name string
if len(filters) == 0 {
return nil
}
for _, filter := range filters {
filterSlice := strings.Split(filter, "=")
if len(filterSlice) != 2 {
return fmt.Errorf("Invalid filter: %s", filter)
}
k, v := filterSlice[0], filterSlice[1]
switch k {
case "type":
{
if typ != "" {
return fmt.Errorf("Only a single type filter is supported.")
}
typ = v
}
case "name":
{
if name != "" {
return fmt.Errorf("Only a single name filter is supported.")
}
name = v
}
default:
return fmt.Errorf("Invalid filter key: %s. Only 'type', and 'name' are supported.", k)
}
}
if typ == "" {
return fmt.Errorf("Invalid Filter: missing 'type'")
}
if name == "" {
return fmt.Errorf("Invalid Filter: missing 'name'")
func validateFilters(_ *cobra.Command, _ []string) error {
filter, err := internal_filters.New(filters)
if err != nil {
return err
}
diagParams.LabelSelector = expandfilter(typ, name)
diagParams.Filter = filter
return nil
}

func expandfilter(typ, name string) string {
var elasticfilter string
elasticfilter += elasticTypeKey + "=" + strings.ToLower(typ) + ","
switch typ {
case "elasticsearch":
elasticfilter += fmt.Sprintf(elasticsearchNameFormat, strings.ToLower(typ)) + "=" + name
default:
elasticfilter += fmt.Sprintf(elasticNameFormat, strings.ToLower(typ)) + "=" + name
}

return elasticfilter
}

func exitWithError(err error) {
if err != nil {
log.Printf("Error: %v", err)
Expand Down
31 changes: 17 additions & 14 deletions internal/diag.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/elastic/eck-diagnostics/internal/archive"
"github.com/elastic/eck-diagnostics/internal/filters"
"github.com/elastic/eck-diagnostics/internal/log"
"k8s.io/apimachinery/pkg/util/version"

Expand All @@ -38,7 +39,7 @@ type Params struct {
RunAgentDiagnostics bool
Verbose bool
StackDiagnosticsTimeout time.Duration
LabelSelector string
Filter filters.Filter
}

// AllNamespaces returns a slice containing all namespaces from which we want to extract diagnostic data.
Expand Down Expand Up @@ -82,18 +83,20 @@ func Run(params Params) error {
return err
}

// Filter is intentionally empty in many of these, as Elastic labels
// are not applied to these resources.
zipFile.Add(map[string]func(io.Writer) error{
"version.json": func(writer io.Writer) error {
return kubectl.Version(writer)
},
"nodes.json": func(writer io.Writer) error {
return kubectl.Get("nodes", "", "", writer)
return kubectl.Get("nodes", "", filters.Filter{}, writer)
},
"podsecuritypolicies.json": func(writer io.Writer) error {
return kubectl.Get("podsecuritypolicies", "", "", writer)
return kubectl.Get("podsecuritypolicies", "", filters.Filter{}, writer)
},
"storageclasses.json": func(writer io.Writer) error {
return kubectl.Get("storageclasses", "", "", writer)
return kubectl.Get("storageclasses", "", filters.Filter{}, writer)
},
"clusterroles.txt": func(writer io.Writer) error {
return kubectl.Describe("clusterroles", "elastic", "", writer)
Expand All @@ -110,7 +113,7 @@ func Run(params Params) error {

operatorVersions = append(operatorVersions, detectECKVersion(clientSet, ns, params.ECKVersion))

zipFile.Add(getResources(kubectl.Get, ns, params.LabelSelector, []string{
zipFile.Add(getResources(kubectl.Get, ns, params.Filter, []string{
"statefulsets",
"pods",
"services",
Expand Down Expand Up @@ -143,7 +146,7 @@ LOOP:
default:
}
logger.Printf("Extracting Kubernetes diagnostics from %s\n", ns)
zipFile.Add(getResources(kubectl.Get, ns, params.LabelSelector, []string{
zipFile.Add(getResources(kubectl.Get, ns, params.Filter, []string{
"statefulsets",
"replicasets",
"deployments",
Expand All @@ -156,36 +159,36 @@ LOOP:
"controllerrevisions",
}))

zipFile.Add(getResources(kubectl.GetElastic, ns, params.LabelSelector, []string{
zipFile.Add(getResources(kubectl.GetElastic, ns, params.Filter, []string{
"kibana",
"elasticsearch",
"apmserver",
}))

// labelSelector is intentionally empty, as Elastic labels
// Filter is intentionally here, as Elastic labels
// are not applied to these resources.
zipFile.Add(getResources(kubectl.Get, ns, "", []string{
zipFile.Add(getResources(kubectl.Get, ns, filters.Filter{}, []string{
"persistentvolumes",
"events",
"networkpolicies",
"serviceaccount",
}))

if maxOperatorVersion.AtLeast(version.MustParseSemantic("1.2.0")) {
zipFile.Add(getResources(kubectl.GetElastic, ns, params.LabelSelector, []string{
zipFile.Add(getResources(kubectl.GetElastic, ns, params.Filter, []string{
"enterprisesearch",
"beat",
}))
}

if maxOperatorVersion.AtLeast(version.MustParseSemantic("1.4.0")) {
zipFile.Add(getResources(kubectl.GetElastic, ns, params.LabelSelector, []string{
zipFile.Add(getResources(kubectl.GetElastic, ns, params.Filter, []string{
"agent",
}))
}

if maxOperatorVersion.AtLeast(version.MustParseSemantic("1.6.0")) {
zipFile.Add(getResources(kubectl.GetElastic, ns, params.LabelSelector, []string{
zipFile.Add(getResources(kubectl.GetElastic, ns, params.Filter, []string{
"elasticmapsserver",
}))
}
Expand Down Expand Up @@ -251,12 +254,12 @@ func getLogs(k *Kubectl, zipFile *archive.ZipFile, ns string, selector ...string

// 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, string, io.Writer) error, ns string, labelSelector string, rs []string) map[string]func(io.Writer) error {
func getResources(f func(string, string, filters.Filter, io.Writer) error, ns string, filter filters.Filter, rs []string) map[string]func(io.Writer) error {
m := map[string]func(io.Writer) error{}
for _, r := range rs {
resource := r
m[archive.Path(ns, resource+".json")] = func(w io.Writer) error {
return f(resource, ns, labelSelector, w)
return f(resource, ns, filter, w)
}
}
return m
Expand Down
105 changes: 105 additions & 0 deletions internal/filters/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package filters

import (
"fmt"
"strings"

"k8s.io/utils/strings/slices"
)

var (
validTypes = []string{"agent", "apm", "beat", "elasticsearch", "enterprisesearch", "kibana", "maps"}
elasticTypeKey = "common.k8s.elastic.co/type"
elasticsearchNameFormat = "%s.k8s.elastic.co/cluster-name"
elasticNameFormat = "%s.k8s.elastic.co/name"
)

type Filter struct {
source []string
typ string
name string
labelSelector string
}

func (f Filter) LabelSelector() string {
return f.labelSelector
}

func (f Filter) Type() string {
return f.typ
}

func (f Filter) Name() string {
return f.name
}

func New(source []string) (Filter, error) {
filter := Filter{
source: source,
}
return filter.validate()
}

func (f Filter) validate() (Filter, error) {
if len(f.source) == 0 {
return f, nil
}
var typ, name string
for _, filter := range f.source {
filterSlice := strings.Split(filter, "=")
if len(filterSlice) != 2 {
return f, fmt.Errorf("Invalid filter: %s", filter)
}
k, v := filterSlice[0], filterSlice[1]
switch k {
case "type":
{
if typ != "" {
return f, fmt.Errorf("Only a single type filter is supported.")
}
typ = v
}
case "name":
{
if name != "" {
return f, fmt.Errorf("Only a single name filter is supported.")
}
name = v
}
default:
return f, fmt.Errorf("Invalid filter key: %s. Only 'type', and 'name' are supported.", k)
}
}
if typ == "" {
return f, fmt.Errorf("Invalid Filter: missing 'type'")
}
if err := validateType(typ); err != nil {
return f, err
}
if name == "" {
return f, fmt.Errorf("Invalid Filter: missing 'name'")
}
f.typ, f.name = typ, name
f.labelSelector = convertFilterToLabelSelector(f.typ, f.name)
return f, nil
}

func validateType(typ string) error {
if !slices.Contains(validTypes, typ) {
return fmt.Errorf("invalid type: %s, supported types: %v", typ, validTypes)
}
return nil
}

func convertFilterToLabelSelector(typ, name string) string {
var elasticfilter string
elasticfilter += elasticTypeKey + "=" + strings.ToLower(typ) + ","
switch typ {
case "elasticsearch":
elasticfilter += fmt.Sprintf(elasticsearchNameFormat, strings.ToLower(typ)) + "=" + name
default:
elasticfilter += fmt.Sprintf(elasticNameFormat, strings.ToLower(typ)) + "=" + name
}

return elasticfilter
}
Loading

0 comments on commit dc6aa72

Please sign in to comment.