Skip to content

Commit f3777be

Browse files
authored
feat: Add Strict flag to LoadSpecs API (#1279)
* feat: Add Strict flag to LoadSpecs API Strict flag which can be used to toggle between true (raising errors if a document is invalid) and false (ignoring invalid documents, perhaps logging a warning). * Granular error handling multidocs in secrets and configmaps * Fix failing test
1 parent addc2ce commit f3777be

File tree

5 files changed

+269
-80
lines changed

5 files changed

+269
-80
lines changed

internal/traces/exporter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ var (
2222
printer = message.NewPrinter(language.English)
2323
)
2424

25-
const legend = "Suceeded (S), eXcluded (X), Failed (F)\n"
25+
const legend = "Succeeded (S), eXcluded (X), Failed (F)\n"
2626

2727
// FUTURE WORK: This exporter should only be used by troubleshoot CLIs
2828
// until the following issue is addressed:

internal/traces/exporter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func TestExporter_GetSummary(t *testing.T) {
7676
},
7777
want: `
7878
============ Collectors summary =============
79-
Suceeded (S), eXcluded (X), Failed (F)
79+
Succeeded (S), eXcluded (X), Failed (F)
8080
=============================================
8181
all-logs (S) : 60,000ms
8282
host-os (S) : 1,000ms
@@ -118,7 +118,7 @@ failed-collector (F) : 1ms`,
118118
},
119119
want: `
120120
============= Analyzers summary =============
121-
Suceeded (S), eXcluded (X), Failed (F)
121+
Succeeded (S), eXcluded (X), Failed (F)
122122
=============================================
123123
host-cpu (S) : 60,000ms
124124
cluster-version (S) : 1,000ms

pkg/loader/loader.go

Lines changed: 56 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package loader
22

33
import (
44
"context"
5-
"strings"
65

76
"github.com/pkg/errors"
87
"github.com/replicatedhq/troubleshoot/internal/util"
@@ -35,20 +34,34 @@ type parsedDoc struct {
3534
type LoadOptions struct {
3635
RawSpecs []string
3736
RawSpec string
37+
38+
// If true, the loader will return an error if any of the specs are not valid
39+
// else the invalid specs will be ignored
40+
Strict bool
3841
}
3942

4043
// LoadSpecs takes sources to load specs from and returns a TroubleshootKinds object
4144
// that contains all the parsed troubleshoot specs.
4245
//
43-
// The fetched specs need to be yaml documents. The documents can be a multidoc yaml
44-
// separated by "---" which get split and parsed one at a time. This function will
45-
// return an error if any of the documents are not valid yaml. If Secrets or ConfigMaps
46-
// are found, they will be parsed and the support bundle, redactor or preflight spec
47-
// will be extracted from them, else they will be ignored.
48-
// Any other yaml documents will be ignored.
46+
// The fetched specs should be yaml documents. The documents can be multidoc yamls
47+
// separated by "---" which get split and parsed one at a time. All troubleshoot
48+
// specs are extracted from the documents and returned in a TroubleshootKinds object.
49+
//
50+
// If Secrets or ConfigMaps are found, they are parsed and the support bundle, redactor
51+
// or preflight spec extracted from them. All other yaml documents will be ignored.
52+
//
53+
// If the `Strict` flag is set to true, this function will return an error if any of
54+
// the documents are not valid, else the invalid documents will be ignored.
4955
func LoadSpecs(ctx context.Context, opt LoadOptions) (*TroubleshootKinds, error) {
5056
opt.RawSpecs = append(opt.RawSpecs, opt.RawSpec)
51-
return loadFromStrings(opt.RawSpecs...)
57+
l := specLoader{
58+
strict: opt.Strict,
59+
}
60+
return l.loadFromStrings(opt.RawSpecs...)
61+
}
62+
63+
type specLoader struct {
64+
strict bool
5265
}
5366

5467
type TroubleshootKinds struct {
@@ -78,13 +91,13 @@ func NewTroubleshootKinds() *TroubleshootKinds {
7891
}
7992

8093
// loadFromStrings accepts a list of strings (exploded) which should be yaml documents
81-
func loadFromStrings(rawSpecs ...string) (*TroubleshootKinds, error) {
94+
func (l *specLoader) loadFromStrings(rawSpecs ...string) (*TroubleshootKinds, error) {
8295
splitdocs := []string{}
8396
multiRawDocs := []string{}
8497

8598
// 1. First split multidoc yaml documents.
8699
for _, rawSpec := range rawSpecs {
87-
multiRawDocs = append(multiRawDocs, strings.Split(rawSpec, "\n---\n")...)
100+
multiRawDocs = append(multiRawDocs, util.SplitYAML(rawSpec)...)
88101
}
89102

90103
// 2. Go through each document to see if it is a configmap, secret or troubleshoot kind
@@ -95,13 +108,19 @@ func loadFromStrings(rawSpecs ...string) (*TroubleshootKinds, error) {
95108

96109
err := yaml.Unmarshal([]byte(rawDoc), &parsed)
97110
if err != nil {
111+
if !l.strict {
112+
continue
113+
}
98114
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Wrapf(err, "failed to parse yaml: '%s'", string(rawDoc)))
99115
}
100116

101117
if isConfigMap(parsed) || isSecret(parsed) {
102118
// Extract specs from configmap or secret
103119
obj, _, err := decoder.Decode([]byte(rawDoc), nil, nil)
104120
if err != nil {
121+
if !l.strict {
122+
continue
123+
}
105124
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES,
106125
errors.Wrapf(err, "failed to decode raw spec: '%s'", string(rawDoc)),
107126
)
@@ -110,13 +129,13 @@ func loadFromStrings(rawSpecs ...string) (*TroubleshootKinds, error) {
110129
// 3. Extract the raw troubleshoot specs
111130
switch v := obj.(type) {
112131
case *v1.ConfigMap:
113-
specs, err := getSpecFromConfigMap(v)
132+
specs, err := l.getSpecFromConfigMap(v)
114133
if err != nil {
115134
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
116135
}
117136
splitdocs = append(splitdocs, specs...)
118137
case *v1.Secret:
119-
specs, err := getSpecFromSecret(v)
138+
specs, err := l.getSpecFromSecret(v)
120139
if err != nil {
121140
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, err)
122141
}
@@ -133,21 +152,31 @@ func loadFromStrings(rawSpecs ...string) (*TroubleshootKinds, error) {
133152
}
134153

135154
// 4. Then load the specs into the kinds struct
136-
return loadFromSplitDocs(splitdocs)
155+
return l.loadFromSplitDocs(splitdocs)
137156
}
138157

139-
func loadFromSplitDocs(splitdocs []string) (*TroubleshootKinds, error) {
158+
func (l *specLoader) loadFromSplitDocs(splitdocs []string) (*TroubleshootKinds, error) {
140159
kinds := NewTroubleshootKinds()
141160

142161
for _, doc := range splitdocs {
143162
converted, err := docrewrite.ConvertToV1Beta2([]byte(doc))
144163
if err != nil {
145-
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Wrapf(err, "failed to convert doc to troubleshoot.sh/v1beta2 kind: '%s'", doc))
164+
if !l.strict {
165+
continue
166+
}
167+
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES,
168+
errors.Wrapf(err, "failed to convert doc to troubleshoot.sh/v1beta2 kind: '\n%s'", doc),
169+
)
146170
}
147171

148172
obj, _, err := decoder.Decode([]byte(converted), nil, nil)
149173
if err != nil {
150-
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, errors.Wrapf(err, "failed to decode '%s'", converted))
174+
if !l.strict {
175+
continue
176+
}
177+
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES,
178+
errors.Wrapf(err, "failed to decode '%s'", converted),
179+
)
151180
}
152181

153182
switch spec := obj.(type) {
@@ -193,98 +222,52 @@ func isConfigMap(parsedDocHead parsedDoc) bool {
193222
}
194223

195224
// getSpecFromConfigMap extracts multiple troubleshoot specs from a secret
196-
func getSpecFromConfigMap(cm *v1.ConfigMap) ([]string, error) {
225+
func (l *specLoader) getSpecFromConfigMap(cm *v1.ConfigMap) ([]string, error) {
197226
specs := []string{}
198227

199228
str, ok := cm.Data[constants.SupportBundleKey]
200229
if ok {
201-
spec, err := validateYaml(str)
202-
if err != nil {
203-
return nil, err
204-
}
205-
specs = append(specs, util.SplitYAML(spec)...)
230+
specs = append(specs, util.SplitYAML(str)...)
206231
}
207232
str, ok = cm.Data[constants.RedactorKey]
208233
if ok {
209-
spec, err := validateYaml(str)
210-
if err != nil {
211-
return nil, err
212-
}
213-
specs = append(specs, util.SplitYAML(spec)...)
234+
specs = append(specs, util.SplitYAML(str)...)
214235
}
215236
str, ok = cm.Data[constants.PreflightKey]
216237
if ok {
217-
spec, err := validateYaml(str)
218-
if err != nil {
219-
return nil, err
220-
}
221-
specs = append(specs, util.SplitYAML(spec)...)
238+
specs = append(specs, util.SplitYAML(str)...)
222239
}
223240

224241
return specs, nil
225242
}
226243

227244
// getSpecFromSecret extracts multiple troubleshoot specs from a secret
228-
func getSpecFromSecret(secret *v1.Secret) ([]string, error) {
245+
func (l *specLoader) getSpecFromSecret(secret *v1.Secret) ([]string, error) {
229246
specs := []string{}
230247

231248
specBytes, ok := secret.Data[constants.SupportBundleKey]
232249
if ok {
233-
spec, err := validateYaml(string(specBytes))
234-
if err != nil {
235-
return nil, err
236-
}
237-
specs = append(specs, util.SplitYAML(spec)...)
250+
specs = append(specs, util.SplitYAML(string(specBytes))...)
238251
}
239252
specBytes, ok = secret.Data[constants.RedactorKey]
240253
if ok {
241-
spec, err := validateYaml(string(specBytes))
242-
if err != nil {
243-
return nil, err
244-
}
245-
specs = append(specs, util.SplitYAML(spec)...)
254+
specs = append(specs, util.SplitYAML(string(specBytes))...)
246255
}
247256
specBytes, ok = secret.Data[constants.PreflightKey]
248257
if ok {
249-
spec, err := validateYaml(string(specBytes))
250-
if err != nil {
251-
return nil, err
252-
}
253-
specs = append(specs, util.SplitYAML(spec)...)
258+
specs = append(specs, util.SplitYAML(string(specBytes))...)
254259
}
255260
str, ok := secret.StringData[constants.SupportBundleKey]
256261
if ok {
257-
spec, err := validateYaml(str)
258-
if err != nil {
259-
return nil, err
260-
}
261-
specs = append(specs, util.SplitYAML(spec)...)
262+
specs = append(specs, util.SplitYAML(str)...)
262263
}
263264
str, ok = secret.StringData[constants.RedactorKey]
264265
if ok {
265-
spec, err := validateYaml(str)
266-
if err != nil {
267-
return nil, err
268-
}
269-
specs = append(specs, util.SplitYAML(spec)...)
266+
specs = append(specs, util.SplitYAML(str)...)
270267
}
271268
str, ok = secret.StringData[constants.PreflightKey]
272269
if ok {
273-
spec, err := validateYaml(str)
274-
if err != nil {
275-
return nil, err
276-
}
277-
specs = append(specs, util.SplitYAML(spec)...)
270+
specs = append(specs, util.SplitYAML(str)...)
278271
}
279272
return specs, nil
280273
}
281-
282-
func validateYaml(raw string) (string, error) {
283-
var parsed map[string]any
284-
err := yaml.Unmarshal([]byte(raw), &parsed)
285-
if err != nil {
286-
return "", errors.Wrapf(err, "failed to parse yaml: '%s'", string(raw))
287-
}
288-
289-
return raw, nil
290-
}

0 commit comments

Comments
 (0)