Skip to content

Commit

Permalink
Ability to use sprig functions in analyzer templates (#1745)
Browse files Browse the repository at this point in the history
* Ability to use sprig functions in analyzer templates
  • Loading branch information
sgalsaleh authored Feb 21, 2025
1 parent b80f38a commit 97dcae9
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 107 deletions.
3 changes: 2 additions & 1 deletion internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"text/template"

"github.com/Masterminds/sprig/v3"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
Expand Down Expand Up @@ -94,7 +95,7 @@ func IsInCluster() bool {
// RenderTemplate renders a template and returns the result as a string
func RenderTemplate(tpl string, data interface{}) (string, error) {
// Create a new template and parse the letter into it
t, err := template.New("data").Parse(tpl)
t, err := template.New("data").Funcs(sprig.FuncMap()).Parse(tpl)
if err != nil {
return "", err
}
Expand Down
14 changes: 14 additions & 0 deletions internal/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,20 @@ func TestRenderTemplate(t *testing.T) {
want: "Hello, <no value>!",
wantErr: false,
},
{
name: "template with sprig function works",
tpl: "{{ \"hello \" | upper }}{{ .Name }}",
data: map[string]string{"Name": "World"},
want: "HELLO World",
wantErr: false,
},
{
name: "template with undefined sprig function errors",
tpl: "{{ \"hello \" | upp }}{{ .Name }}",
data: map[string]string{"Name": "World"},
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
16 changes: 4 additions & 12 deletions pkg/analyze/cluster_container_statuses.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package analyzer

import (
"bytes"
"encoding/json"
"fmt"
"path/filepath"
"slices"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/constants"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -240,21 +239,14 @@ func renderContainerMessage(message string, info *matchedContainerInfo) string {
if info == nil {
return message
}
out := fmt.Sprintf("Container matched. Container: %s, Namespace: %s, Pod: %s", info.ContainerName, info.Namespace, info.PodName)

tmpl := template.New("container")
msgTmpl, err := tmpl.Parse(message)
if err != nil {
klog.V(2).Infof("failed to parse message template: %v", err)
return out
}
out := fmt.Sprintf("Container matched. Container: %s, Namespace: %s, Pod: %s", info.ContainerName, info.Namespace, info.PodName)

var m bytes.Buffer
err = msgTmpl.Execute(&m, info)
renderedMsg, err := util.RenderTemplate(message, info)
if err != nil {
klog.V(2).Infof("failed to render message template: %v", err)
return out
}

return strings.TrimSpace(m.String())
return strings.TrimSpace(renderedMsg)
}
25 changes: 6 additions & 19 deletions pkg/analyze/cluster_pod_statuses.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package analyzer

import (
"bytes"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
Expand Down Expand Up @@ -190,31 +189,19 @@ func clusterPodStatuses(analyzer *troubleshootv1beta2.ClusterPodStatuses, getChi
pod.Status.Message = "None"
}

tmpl := template.New("pod")

// template the title
titleTmpl, err := tmpl.Parse(r.Title)
if err != nil {
return nil, errors.Wrap(err, "failed to create new title template")
}
var t bytes.Buffer
err = titleTmpl.Execute(&t, pod)
renderedTitle, err := util.RenderTemplate(r.Title, pod)
if err != nil {
return nil, errors.Wrap(err, "failed to execute template")
return nil, errors.Wrap(err, "failed to render template")
}
r.Title = t.String()
r.Title = renderedTitle

// template the message
msgTmpl, err := tmpl.Parse(r.Message)
renderedMsg, err := util.RenderTemplate(r.Message, pod)
if err != nil {
return nil, errors.Wrap(err, "failed to create new title template")
}
var m bytes.Buffer
err = msgTmpl.Execute(&m, pod)
if err != nil {
return nil, errors.Wrap(err, "failed to execute template")
}
r.Message = strings.TrimSpace(m.String())
r.Message = strings.TrimSpace(renderedMsg)

// add to results, break and check the next pod
allResults = append(allResults, &r)
Expand Down
16 changes: 4 additions & 12 deletions pkg/analyze/event.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package analyzer

import (
"bytes"
"encoding/json"
"fmt"
"path"
"regexp"
"strconv"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/constants"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -198,21 +197,14 @@ func decorateMessage(message string, event *corev1.Event) string {
if event == nil {
return message
}

out := fmt.Sprintf("Event matched. Reason: %s Name: %s Message: %s", event.Reason, event.InvolvedObject.Name, event.Message)

tmpl := template.New("event")
msgTmpl, err := tmpl.Parse(message)
renderedMsg, err := util.RenderTemplate(message, event)
if err != nil {
klog.V(2).Infof("failed to parse message template: %v", err)
return out
}

var m bytes.Buffer
err = msgTmpl.Execute(&m, event)
if err != nil {
klog.V(2).Infof("failed to render message template: %v", err)
return out
}

return strings.TrimSpace(m.String())
return strings.TrimSpace(renderedMsg)
}
13 changes: 3 additions & 10 deletions pkg/analyze/host_filesystem_performance.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package analyzer

import (
"bytes"
"encoding/json"
"fmt"
"log"
"strings"
"text/template"
"time"

"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
Expand Down Expand Up @@ -171,18 +170,12 @@ func doCompareHostFilesystemPerformance(operator string, actual time.Duration, d
}

func renderFSPerfOutcome(outcome string, fsPerf collect.FSPerfResults) string {
t, err := template.New("").Parse(outcome)
if err != nil {
log.Printf("Failed to parse filesystem performance outcome: %v", err)
return outcome
}
var buf bytes.Buffer
err = t.Execute(&buf, fsPerf)
rendered, err := util.RenderTemplate(outcome, fsPerf)
if err != nil {
log.Printf("Failed to render filesystem performance outcome: %v", err)
return outcome
}
return buf.String()
return rendered
}

func (a *AnalyzeHostFilesystemPerformance) analyzeSingleNode(content collectedContent, currentTitle string) ([]*AnalyzeResult, error) {
Expand Down
28 changes: 6 additions & 22 deletions pkg/analyze/host_system_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import (
"strings"
"unicode"

"text/template"

"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/collect"
)
Expand Down Expand Up @@ -158,20 +157,12 @@ func compareSystemPackagesConditionalToActual(conditional string, templateMap ma
return true, nil
}

tmpl := template.New("conditional")

conditionalTmpl, err := tmpl.Parse(conditional)
if err != nil {
return false, errors.Wrap(err, "failed to create new when template")
}

var when bytes.Buffer
err = conditionalTmpl.Execute(&when, templateMap)
when, err := util.RenderTemplate(conditional, templateMap)
if err != nil {
return false, errors.Wrap(err, "failed to execute when template")
return false, errors.Wrap(err, "failed to render when template")
}

t, err := strconv.ParseBool(when.String())
t, err := strconv.ParseBool(when)
if err != nil {
return false, errors.Wrap(err, "failed to parse templated when expression as a boolean")
}
Expand Down Expand Up @@ -223,21 +214,14 @@ func (a *AnalyzeHostSystemPackages) analyzeSingleNode(content collectedContent,
continue
}

tmpl := template.New("package")

r.Title = currentTitle

// template the message
msgTmpl, err := tmpl.Parse(r.Message)
renderedMsg, err := util.RenderTemplate(r.Message, templateMap)
if err != nil {
return nil, errors.Wrap(err, "failed to create new message template")
}
var m bytes.Buffer
err = msgTmpl.Execute(&m, templateMap)
if err != nil {
return nil, errors.Wrap(err, "failed to execute message template")
}
r.Message = m.String()
r.Message = renderedMsg

// add to results, break and check the next pod
allResults = append(allResults, &r)
Expand Down
16 changes: 4 additions & 12 deletions pkg/analyze/k8s_node_metrics.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package analyzer

import (
"bytes"
"encoding/json"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"k8s.io/klog/v2"
kubeletv1alpha1 "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
Expand Down Expand Up @@ -299,18 +298,11 @@ func renderTemplate(tmpMsg string, data any) string {
return tmpMsg
}

t, err := template.New("msg").Parse(tmpMsg)
rendered, err := util.RenderTemplate(tmpMsg, data)
if err != nil {
klog.V(2).Infof("Failed to parse template: %s", err)
klog.V(2).Infof("Failed to render template: %s", err)
return tmpMsg
}

var m bytes.Buffer
err = t.Execute(&m, data)
if err != nil {
klog.V(2).Infof("Failed to execute template: %s", err)
return tmpMsg
}

return m.String()
return rendered
}
28 changes: 9 additions & 19 deletions pkg/analyze/text_analyze.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package analyzer

import (
"bytes"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/replicatedhq/troubleshoot/internal/util"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)

Expand Down Expand Up @@ -210,7 +209,7 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho

if isMatch {
result.IsFail = true
tplMessage, err := templateRegExGroup(outcome.Fail.Message, foundMatches)
tplMessage, err := util.RenderTemplate(outcome.Fail.Message, foundMatches)
if err != nil {
return result, errors.Wrap(err, "failed to template message in outcome.Fail block")
}
Expand All @@ -227,7 +226,7 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho

if isMatch {
result.IsWarn = true
tplMessage, err := templateRegExGroup(outcome.Warn.Message, foundMatches)
tplMessage, err := util.RenderTemplate(outcome.Warn.Message, foundMatches)
if err != nil {
return result, errors.Wrap(err, "failed to template message in outcome.Warn block")
}
Expand All @@ -244,7 +243,7 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho

if isMatch {
result.IsPass = true
tplMessage, err := templateRegExGroup(outcome.Pass.Message, foundMatches)
tplMessage, err := util.RenderTemplate(outcome.Pass.Message, foundMatches)
if err != nil {
return result, errors.Wrap(err, "failed to template message in outcome.Pass block")
}
Expand All @@ -259,20 +258,6 @@ func analyzeRegexGroups(pattern string, collected []byte, outcomes []*troublesho
return result, nil
}

// templateRegExGroup takes a tpl and replaces the variables using matches.
func templateRegExGroup(tpl string, matches map[string]string) (string, error) {
t, err := template.New("").Parse(tpl)
if err != nil {
return "", err
}
var msg bytes.Buffer
err = t.Execute(&msg, matches)
if err != nil {
return "", err
}
return msg.String(), nil
}

func compareRegex(conditional string, foundMatches map[string]string) (bool, error) {
if conditional == "" {
return true, nil
Expand All @@ -287,6 +272,11 @@ func compareRegex(conditional string, foundMatches map[string]string) (bool, err
operator := parts[1]
lookForValue := parts[2]

// handle empty strings
if lookForValue == "''" || lookForValue == `""` {
lookForValue = ""
}

foundValue, ok := foundMatches[lookForMatchName]
if !ok {
// not an error, just wasn't matched
Expand Down

0 comments on commit 97dcae9

Please sign in to comment.