Skip to content

Commit 97dcae9

Browse files
authored
Ability to use sprig functions in analyzer templates (#1745)
* Ability to use sprig functions in analyzer templates
1 parent b80f38a commit 97dcae9

9 files changed

+52
-107
lines changed

internal/util/util.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212
"text/template"
1313

14+
"github.com/Masterminds/sprig/v3"
1415
"golang.org/x/text/cases"
1516
"golang.org/x/text/language"
1617
)
@@ -94,7 +95,7 @@ func IsInCluster() bool {
9495
// RenderTemplate renders a template and returns the result as a string
9596
func RenderTemplate(tpl string, data interface{}) (string, error) {
9697
// Create a new template and parse the letter into it
97-
t, err := template.New("data").Parse(tpl)
98+
t, err := template.New("data").Funcs(sprig.FuncMap()).Parse(tpl)
9899
if err != nil {
99100
return "", err
100101
}

internal/util/util_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,20 @@ func TestRenderTemplate(t *testing.T) {
284284
want: "Hello, <no value>!",
285285
wantErr: false,
286286
},
287+
{
288+
name: "template with sprig function works",
289+
tpl: "{{ \"hello \" | upper }}{{ .Name }}",
290+
data: map[string]string{"Name": "World"},
291+
want: "HELLO World",
292+
wantErr: false,
293+
},
294+
{
295+
name: "template with undefined sprig function errors",
296+
tpl: "{{ \"hello \" | upp }}{{ .Name }}",
297+
data: map[string]string{"Name": "World"},
298+
want: "",
299+
wantErr: true,
300+
},
287301
}
288302
for _, tt := range tests {
289303
t.Run(tt.name, func(t *testing.T) {

pkg/analyze/cluster_container_statuses.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package analyzer
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"fmt"
76
"path/filepath"
87
"slices"
98
"strings"
10-
"text/template"
119

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

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

252-
var m bytes.Buffer
253-
err = msgTmpl.Execute(&m, info)
245+
renderedMsg, err := util.RenderTemplate(message, info)
254246
if err != nil {
255247
klog.V(2).Infof("failed to render message template: %v", err)
256248
return out
257249
}
258250

259-
return strings.TrimSpace(m.String())
251+
return strings.TrimSpace(renderedMsg)
260252
}

pkg/analyze/cluster_pod_statuses.go

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package analyzer
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"fmt"
76
"path/filepath"
87
"strings"
9-
"text/template"
108

119
"github.com/pkg/errors"
10+
"github.com/replicatedhq/troubleshoot/internal/util"
1211
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
1312
"github.com/replicatedhq/troubleshoot/pkg/constants"
1413
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
@@ -190,31 +189,19 @@ func clusterPodStatuses(analyzer *troubleshootv1beta2.ClusterPodStatuses, getChi
190189
pod.Status.Message = "None"
191190
}
192191

193-
tmpl := template.New("pod")
194-
195192
// template the title
196-
titleTmpl, err := tmpl.Parse(r.Title)
197-
if err != nil {
198-
return nil, errors.Wrap(err, "failed to create new title template")
199-
}
200-
var t bytes.Buffer
201-
err = titleTmpl.Execute(&t, pod)
193+
renderedTitle, err := util.RenderTemplate(r.Title, pod)
202194
if err != nil {
203-
return nil, errors.Wrap(err, "failed to execute template")
195+
return nil, errors.Wrap(err, "failed to render template")
204196
}
205-
r.Title = t.String()
197+
r.Title = renderedTitle
206198

207199
// template the message
208-
msgTmpl, err := tmpl.Parse(r.Message)
200+
renderedMsg, err := util.RenderTemplate(r.Message, pod)
209201
if err != nil {
210202
return nil, errors.Wrap(err, "failed to create new title template")
211203
}
212-
var m bytes.Buffer
213-
err = msgTmpl.Execute(&m, pod)
214-
if err != nil {
215-
return nil, errors.Wrap(err, "failed to execute template")
216-
}
217-
r.Message = strings.TrimSpace(m.String())
204+
r.Message = strings.TrimSpace(renderedMsg)
218205

219206
// add to results, break and check the next pod
220207
allResults = append(allResults, &r)

pkg/analyze/event.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package analyzer
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"fmt"
76
"path"
87
"regexp"
98
"strconv"
109
"strings"
11-
"text/template"
1210

1311
"github.com/pkg/errors"
12+
"github.com/replicatedhq/troubleshoot/internal/util"
1413
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
1514
"github.com/replicatedhq/troubleshoot/pkg/constants"
1615
corev1 "k8s.io/api/core/v1"
@@ -198,21 +197,14 @@ func decorateMessage(message string, event *corev1.Event) string {
198197
if event == nil {
199198
return message
200199
}
200+
201201
out := fmt.Sprintf("Event matched. Reason: %s Name: %s Message: %s", event.Reason, event.InvolvedObject.Name, event.Message)
202202

203-
tmpl := template.New("event")
204-
msgTmpl, err := tmpl.Parse(message)
203+
renderedMsg, err := util.RenderTemplate(message, event)
205204
if err != nil {
206205
klog.V(2).Infof("failed to parse message template: %v", err)
207206
return out
208207
}
209208

210-
var m bytes.Buffer
211-
err = msgTmpl.Execute(&m, event)
212-
if err != nil {
213-
klog.V(2).Infof("failed to render message template: %v", err)
214-
return out
215-
}
216-
217-
return strings.TrimSpace(m.String())
209+
return strings.TrimSpace(renderedMsg)
218210
}

pkg/analyze/host_filesystem_performance.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package analyzer
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"fmt"
76
"log"
87
"strings"
9-
"text/template"
108
"time"
119

1210
"github.com/pkg/errors"
11+
"github.com/replicatedhq/troubleshoot/internal/util"
1312
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
1413
"github.com/replicatedhq/troubleshoot/pkg/collect"
1514
)
@@ -171,18 +170,12 @@ func doCompareHostFilesystemPerformance(operator string, actual time.Duration, d
171170
}
172171

173172
func renderFSPerfOutcome(outcome string, fsPerf collect.FSPerfResults) string {
174-
t, err := template.New("").Parse(outcome)
175-
if err != nil {
176-
log.Printf("Failed to parse filesystem performance outcome: %v", err)
177-
return outcome
178-
}
179-
var buf bytes.Buffer
180-
err = t.Execute(&buf, fsPerf)
173+
rendered, err := util.RenderTemplate(outcome, fsPerf)
181174
if err != nil {
182175
log.Printf("Failed to render filesystem performance outcome: %v", err)
183176
return outcome
184177
}
185-
return buf.String()
178+
return rendered
186179
}
187180

188181
func (a *AnalyzeHostFilesystemPerformance) analyzeSingleNode(content collectedContent, currentTitle string) ([]*AnalyzeResult, error) {

pkg/analyze/host_system_packages.go

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import (
99
"strings"
1010
"unicode"
1111

12-
"text/template"
13-
1412
"github.com/pkg/errors"
13+
"github.com/replicatedhq/troubleshoot/internal/util"
1514
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
1615
"github.com/replicatedhq/troubleshoot/pkg/collect"
1716
)
@@ -158,20 +157,12 @@ func compareSystemPackagesConditionalToActual(conditional string, templateMap ma
158157
return true, nil
159158
}
160159

161-
tmpl := template.New("conditional")
162-
163-
conditionalTmpl, err := tmpl.Parse(conditional)
164-
if err != nil {
165-
return false, errors.Wrap(err, "failed to create new when template")
166-
}
167-
168-
var when bytes.Buffer
169-
err = conditionalTmpl.Execute(&when, templateMap)
160+
when, err := util.RenderTemplate(conditional, templateMap)
170161
if err != nil {
171-
return false, errors.Wrap(err, "failed to execute when template")
162+
return false, errors.Wrap(err, "failed to render when template")
172163
}
173164

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

226-
tmpl := template.New("package")
227-
228217
r.Title = currentTitle
229218

230219
// template the message
231-
msgTmpl, err := tmpl.Parse(r.Message)
220+
renderedMsg, err := util.RenderTemplate(r.Message, templateMap)
232221
if err != nil {
233222
return nil, errors.Wrap(err, "failed to create new message template")
234223
}
235-
var m bytes.Buffer
236-
err = msgTmpl.Execute(&m, templateMap)
237-
if err != nil {
238-
return nil, errors.Wrap(err, "failed to execute message template")
239-
}
240-
r.Message = m.String()
224+
r.Message = renderedMsg
241225

242226
// add to results, break and check the next pod
243227
allResults = append(allResults, &r)

pkg/analyze/k8s_node_metrics.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package analyzer
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"fmt"
76
"path/filepath"
87
"regexp"
98
"strconv"
109
"strings"
11-
"text/template"
1210

1311
"github.com/pkg/errors"
12+
"github.com/replicatedhq/troubleshoot/internal/util"
1413
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
1514
"k8s.io/klog/v2"
1615
kubeletv1alpha1 "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
@@ -299,18 +298,11 @@ func renderTemplate(tmpMsg string, data any) string {
299298
return tmpMsg
300299
}
301300

302-
t, err := template.New("msg").Parse(tmpMsg)
301+
rendered, err := util.RenderTemplate(tmpMsg, data)
303302
if err != nil {
304-
klog.V(2).Infof("Failed to parse template: %s", err)
303+
klog.V(2).Infof("Failed to render template: %s", err)
305304
return tmpMsg
306305
}
307306

308-
var m bytes.Buffer
309-
err = t.Execute(&m, data)
310-
if err != nil {
311-
klog.V(2).Infof("Failed to execute template: %s", err)
312-
return tmpMsg
313-
}
314-
315-
return m.String()
307+
return rendered
316308
}

pkg/analyze/text_analyze.go

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package analyzer
22

33
import (
4-
"bytes"
54
"fmt"
65
"path/filepath"
76
"regexp"
87
"strconv"
98
"strings"
10-
"text/template"
119

1210
"github.com/pkg/errors"
11+
"github.com/replicatedhq/troubleshoot/internal/util"
1312
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
1413
)
1514

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

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

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

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

262-
// templateRegExGroup takes a tpl and replaces the variables using matches.
263-
func templateRegExGroup(tpl string, matches map[string]string) (string, error) {
264-
t, err := template.New("").Parse(tpl)
265-
if err != nil {
266-
return "", err
267-
}
268-
var msg bytes.Buffer
269-
err = t.Execute(&msg, matches)
270-
if err != nil {
271-
return "", err
272-
}
273-
return msg.String(), nil
274-
}
275-
276261
func compareRegex(conditional string, foundMatches map[string]string) (bool, error) {
277262
if conditional == "" {
278263
return true, nil
@@ -287,6 +272,11 @@ func compareRegex(conditional string, foundMatches map[string]string) (bool, err
287272
operator := parts[1]
288273
lookForValue := parts[2]
289274

275+
// handle empty strings
276+
if lookForValue == "''" || lookForValue == `""` {
277+
lookForValue = ""
278+
}
279+
290280
foundValue, ok := foundMatches[lookForMatchName]
291281
if !ok {
292282
// not an error, just wasn't matched

0 commit comments

Comments
 (0)