Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: doctor print should include child Pollers into optional parent Pollers #2641

Merged
merged 5 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 101 additions & 21 deletions cmd/tools/doctor/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,107 @@ func doDoctorCmd(cmd *cobra.Command, _ []string) {
var config = cmd.Root().PersistentFlags().Lookup("config")
var confPaths = cmd.Root().PersistentFlags().Lookup("confpath")

doDoctor(conf.ConfigPath(config.Value.String()), confPaths.Value.String())
pathI := conf.ConfigPath(config.Value.String())
confPath := confPaths.Value.String()
out := doDoctor(pathI)
fmt.Println(out)
checkAll(pathI, confPath)
}

func doDoctor(path string, confPath string) {
if opts.ShouldPrintConfig {
contents, err := os.ReadFile(path)
func doDoctor(path string) string {
contents, err := os.ReadFile(path)
if err != nil {
fmt.Printf("error reading config file. err=%+v\n", err)
return ""
}
parentRoot, err := printRedactedConfig(path, contents)
if err != nil {
fmt.Printf("error processing parent config file=[%s] %+v\n", path, err)
return ""
}

// Extract Poller_files field from parentRoot
var pollerFiles []string
for i, node := range parentRoot.Content[0].Content {
if node.Value == "Poller_files" {
for _, childNode := range parentRoot.Content[0].Content[i+1].Content {
pollerFiles = append(pollerFiles, childNode.Value)
}
break
}
}

for _, childPathPattern := range pollerFiles {
matches, err := filepath.Glob(childPathPattern)
if err != nil {
fmt.Printf("error reading config file. err=%+v\n", err)
return
fmt.Printf("error matching glob pattern: %v\n", err)
continue
}
for _, childPath := range matches {
childContents, err := os.ReadFile(childPath)
if err != nil {
fmt.Printf("error reading child file. err=%+v\n", err)
continue
}
childRoot, err := printRedactedConfig(childPath, childContents)
if err != nil {
fmt.Printf("error processing child config file=[%s] %+v\n", childPath, err)
continue
}

// Merge childRoot into parentRoot
mergeYamlNodes(parentRoot, childRoot)
}
}

marshaled, err := yaml.Marshal(parentRoot)
if err != nil {
fmt.Printf("error marshalling yaml sanitized from config file=[%s] %+v\n", path, err)
return ""
}
out := string(marshaled)
return out
}

func mergeYamlNodes(parent, child *yaml.Node) {
// Find the Pollers section in the parent node
var parentPollers *yaml.Node
for i, node := range parent.Content[0].Content {
if node.Value == "Pollers" {
parentPollers = parent.Content[0].Content[i+1]
break
}
}

// If the parent node doesn't have a Pollers section, create one
if parentPollers == nil {
parent.Content[0].Content = append(parent.Content[0].Content, &yaml.Node{
Kind: yaml.ScalarNode,
Value: "Pollers",
}, &yaml.Node{Kind: yaml.MappingNode})
parentPollers = parent.Content[0].Content[len(parent.Content[0].Content)-1]
}

// Create a map of the parent node's pollers
parentPollerNames := make(map[string]bool)
for _, node := range parentPollers.Content {
if node.Kind == yaml.ScalarNode {
parentPollerNames[node.Value] = true
}
}

// Find the Pollers section in the child node
for i, node := range child.Content[0].Content {
if node.Value == "Pollers" {
// Append any child pollers that aren't already in the parent node
for _, childPoller := range child.Content[0].Content[i+1].Content {
if _, exists := parentPollerNames[childPoller.Value]; !exists {
parentPollers.Content = append(parentPollers.Content, childPoller)
}
}
break
}
printRedactedConfig(path, contents)
}
checkAll(path, confPath)
}

// checkAll runs all doctor checks
Expand All @@ -123,8 +211,8 @@ func checkAll(path string, confPath string) {

cfg := conf.Config
confPaths := filepath.SplitList(confPath)
anyFailed := false
anyFailed = !checkUniquePromPorts(cfg).isValid || anyFailed
var anyFailed bool
anyFailed = !checkUniquePromPorts(cfg).isValid
anyFailed = !checkPollersExportToUniquePromPorts(cfg).isValid || anyFailed
anyFailed = !checkExporterTypes(cfg).isValid || anyFailed
anyFailed = !checkConfTemplates(confPaths).isValid || anyFailed
Expand Down Expand Up @@ -424,26 +512,18 @@ func checkPollersExportToUniquePromPorts(config conf.HarvestConfig) validation {
return valid
}

func printRedactedConfig(path string, contents []byte) string {
func printRedactedConfig(path string, contents []byte) (*yaml.Node, error) {
root := &yaml.Node{}
err := yaml.Unmarshal(contents, root)
if err != nil {
fmt.Printf("error reading config file=[%s] %+v\n", path, err)
return ""
return nil, err
}
var nodes []*yaml.Node
collectNodes(root, &nodes)
sanitize(nodes)
removeComments(root)

marshaled, err := yaml.Marshal(root)
if err != nil {
fmt.Printf("error marshalling yaml sanitized from config file=[%s] %+v\n", path, err)
return ""
}
result := string(marshaled)
fmt.Println(result)
return result
return root, nil
}

func sanitize(nodes []*yaml.Node) {
Expand Down
43 changes: 41 additions & 2 deletions cmd/tools/doctor/doctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package doctor

import (
"github.com/netapp/harvest/v2/pkg/conf"
"gopkg.in/yaml.v3"
"os"
"strings"
"testing"
)
Expand All @@ -21,13 +23,50 @@ func TestRedaction(t *testing.T) {
func assertRedacted(t *testing.T, input, redacted string) {
t.Helper()
redacted = strings.TrimSpace(redacted)
input = printRedactedConfig("test", []byte(input))
input = strings.TrimSpace(input)

inputNode, err := printRedactedConfig("test", []byte(input))
if err != nil {
t.Fatalf("error redacting input node: %v", err)
}
inputBytes, err := yaml.Marshal(inputNode)
if err != nil {
t.Fatalf("error marshalling input node: %v", err)
}
input = strings.TrimSpace(string(inputBytes))

if input != redacted {
t.Fatalf(`input=[%s] != redacted=[%s]`, input, redacted)
}
}

func TestDoDoctor(t *testing.T) {
type test struct {
parentPath string
outPath string
}

tests := []test{
{"testdata/merge/merge1/parent.yml", "testdata/merge/merge1/out.yml"},
{"testdata/merge/merge2/parent.yml", "testdata/merge/merge2/out.yml"},
{"testdata/merge/merge3/parent.yml", "testdata/merge/merge3/out.yml"},
}
for _, tt := range tests {

output := doDoctor(tt.parentPath)

outBytes, err := os.ReadFile(tt.outPath)
if err != nil {
t.Fatalf("failed to read expected output file: %v", err)
}

expectedOutput := string(outBytes)

if output != expectedOutput {
t.Errorf("%s unexpected output:\ngot:\n%v\n\nwant:\n%v", tt.outPath, output, expectedOutput)
}
}
}

func TestConfigToStruct(t *testing.T) {
conf.TestLoadHarvestConfig("testdata/testConfig.yml")
if conf.Config.Defaults.Password != "123#abc" {
Expand Down
8 changes: 8 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge1/child.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Pollers:
u2:
datacenter: u2
addr: 2.2.2.2
username: user
password: pass
exporters:
- prometheus1
28 changes: 28 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge1/out.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Exporters:
prometheus1:
exporter: Prometheus
port_range: 12990-14000
Defaults:
collectors:
- Zapi
- ZapiPerf
- Ems
use_insecure_tls: true
prefer_zapi: true
Poller_files:
- testdata/merge/merge1/child.yml
Pollers:
sar:
datacenter: dc1
addr: -REDACTED-
username: -REDACTED-
password: -REDACTED-
exporters:
- prometheus1
u2:
datacenter: u2
addr: -REDACTED-
username: -REDACTED-
password: -REDACTED-
exporters:
- prometheus1
23 changes: 23 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge1/parent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Exporters:
prometheus1:
exporter: Prometheus
port_range: 12990-14000
Defaults:
collectors:
- Zapi
- ZapiPerf
- Ems
use_insecure_tls: true
prefer_zapi: true

Poller_files:
- testdata/merge/merge1/child.yml

Pollers:
sar:
datacenter: dc1
addr: 1.1.1.1
username: user
password: pass
exporters:
- prometheus1
8 changes: 8 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge2/child/child.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Pollers:
u2:
datacenter: u2
addr: 2.2.2.2
username: user
password: pass
exporters:
- prometheus1
8 changes: 8 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge2/child/child2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Pollers:
u3:
datacenter: u3
addr: 3.3.3.3
username: user
password: pass
exporters:
- prometheus1
28 changes: 28 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge2/out.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Exporters:
prometheus1:
exporter: Prometheus
port_range: 12990-14000
Defaults:
collectors:
- Zapi
- ZapiPerf
- Ems
use_insecure_tls: true
prefer_zapi: true
Poller_files:
- testdata/merge/merge2/child/*.yml
Pollers:
u2:
datacenter: u2
addr: -REDACTED-
username: -REDACTED-
password: -REDACTED-
exporters:
- prometheus1
u3:
datacenter: u3
addr: -REDACTED-
username: -REDACTED-
password: -REDACTED-
exporters:
- prometheus1
15 changes: 15 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge2/parent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Exporters:
prometheus1:
exporter: Prometheus
port_range: 12990-14000
Defaults:
collectors:
- Zapi
- ZapiPerf
- Ems
use_insecure_tls: true
prefer_zapi: true

Poller_files:
- testdata/merge/merge2/child/*.yml

19 changes: 19 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge3/out.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Exporters:
prometheus1:
exporter: Prometheus
port_range: 12990-14000
Defaults:
collectors:
- Zapi
- ZapiPerf
- Ems
use_insecure_tls: true
prefer_zapi: true
Pollers:
sar:
datacenter: dc1
addr: -REDACTED-
username: -REDACTED-
password: -REDACTED-
exporters:
- prometheus1
20 changes: 20 additions & 0 deletions cmd/tools/doctor/testdata/merge/merge3/parent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Exporters:
prometheus1:
exporter: Prometheus
port_range: 12990-14000
Defaults:
collectors:
- Zapi
- ZapiPerf
- Ems
use_insecure_tls: true
prefer_zapi: true

Pollers:
sar:
datacenter: dc1
addr: 1.1.1.1
username: user
password: pass
exporters:
- prometheus1
Loading
Loading