Skip to content

Commit e5e26ee

Browse files
authored
fix(support-bundle): default in-cluster collectors in host support bundle (#1394)
* fix(support-bundle): default in-cluster collectors in host support bundle Ensure cluster-resources and cluster-info collectors are present only when a support bundle spec contains in-cluster collectors. * Various improvements * Improve error messages * Util function appending elements to a nil slice that allows adding specs to an empty slice of collectors/analysers/redactors * Fix failing test
1 parent d4623d9 commit e5e26ee

File tree

11 files changed

+128
-32
lines changed

11 files changed

+128
-32
lines changed

cmd/analyze/cli/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func runAnalyzers(v *viper.Viper, bundlePath string) error {
2727
} else {
2828
if !util.IsURL(specPath) {
2929
// TODO: Better error message when we do not have a file/url etc
30-
return fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
30+
return fmt.Errorf("%s is not a URL and was not found", specPath)
3131
}
3232

3333
req, err := http.NewRequest("GET", specPath, nil)

cmd/collect/cli/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func runCollect(v *viper.Viper, arg string) error {
5858
collectorContent = b
5959
} else {
6060
if !util.IsURL(arg) {
61-
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
61+
return fmt.Errorf("%s is not a URL and was not found", arg)
6262
}
6363

6464
req, err := http.NewRequest("GET", arg, nil)

cmd/troubleshoot/cli/analyze.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func downloadAnalyzerSpec(specPath string) (string, error) {
9191
specContent = string(b)
9292
} else {
9393
if !util.IsURL(specPath) {
94-
return "", fmt.Errorf("%s is not a URL and was not found (err %s)", specPath, err)
94+
return "", fmt.Errorf("%s is not a URL and was not found", specPath)
9595
}
9696

9797
req, err := http.NewRequest("GET", specPath, nil)

cmd/troubleshoot/cli/run.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ the %s Admin Console to begin analysis.`
233233
}
234234

235235
// loadSupportBundleSpecsFromURIs loads support bundle specs from URIs
236-
func loadSupportBundleSpecsFromURIs(ctx context.Context, kinds *loader.TroubleshootKinds) (*loader.TroubleshootKinds, error) {
236+
func loadSupportBundleSpecsFromURIs(ctx context.Context, kinds *loader.TroubleshootKinds) error {
237237
remoteRawSpecs := []string{}
238238
for _, s := range kinds.SupportBundlesV1Beta2 {
239239
if s.Spec.Uri != "" && util.IsURL(s.Spec.Uri) {
@@ -252,12 +252,18 @@ func loadSupportBundleSpecsFromURIs(ctx context.Context, kinds *loader.Troublesh
252252
}
253253

254254
if len(remoteRawSpecs) == 0 {
255-
return kinds, nil
255+
return nil
256256
}
257257

258-
return loader.LoadSpecs(ctx, loader.LoadOptions{
258+
moreKinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{
259259
RawSpecs: remoteRawSpecs,
260260
})
261+
if err != nil {
262+
return err
263+
}
264+
265+
kinds.Add(moreKinds)
266+
return nil
261267
}
262268

263269
func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface) (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta2.Redactor, error) {
@@ -270,11 +276,9 @@ func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface)
270276

271277
// Load additional specs from support bundle URIs
272278
if !viper.GetBool("no-uri") {
273-
moreKinds, err := loadSupportBundleSpecsFromURIs(ctx, kinds)
279+
err := loadSupportBundleSpecsFromURIs(ctx, kinds)
274280
if err != nil {
275281
klog.Warningf("unable to load support bundles from URIs: %v", err)
276-
} else {
277-
kinds.Add(moreKinds)
278282
}
279283
}
280284

@@ -304,25 +308,27 @@ func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface)
304308
}
305309

306310
for _, c := range kinds.CollectorsV1Beta2 {
307-
mainBundle.Spec.Collectors = append(mainBundle.Spec.Collectors, c.Spec.Collectors...)
311+
mainBundle.Spec.Collectors = util.Append(mainBundle.Spec.Collectors, c.Spec.Collectors)
308312
}
309313

310314
for _, hc := range kinds.HostCollectorsV1Beta2 {
311-
mainBundle.Spec.HostCollectors = append(mainBundle.Spec.HostCollectors, hc.Spec.Collectors...)
315+
mainBundle.Spec.HostCollectors = util.Append(mainBundle.Spec.HostCollectors, hc.Spec.Collectors)
312316
}
313317

314-
// Ensure cluster info and cluster resources collectors are in the merged spec
315-
// We need to add them here so when we --dry-run, these collectors are included.
316-
// supportbundle.runCollectors duplicates this bit. We'll need to refactor it out later
317-
// when its clearer what other code depends on this logic e.g KOTS
318-
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
319-
mainBundle.Spec.Collectors,
320-
troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}},
321-
)
322-
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
323-
mainBundle.Spec.Collectors,
324-
troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}},
325-
)
318+
if mainBundle.Spec.Collectors != nil {
319+
// If we have in-cluster collectors, ensure cluster info and cluster resources
320+
// collectors are in the merged spec. We need to add them here so when we --dry-run,
321+
// these collectors are included. supportbundle.runCollectors duplicates this bit.
322+
// We'll need to refactor it out later when its clearer what other code depends on this logic e.g KOTS
323+
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
324+
mainBundle.Spec.Collectors,
325+
troubleshootv1beta2.Collect{ClusterInfo: &troubleshootv1beta2.ClusterInfo{}},
326+
)
327+
mainBundle.Spec.Collectors = collect.EnsureCollectorInList(
328+
mainBundle.Spec.Collectors,
329+
troubleshootv1beta2.Collect{ClusterResources: &troubleshootv1beta2.ClusterResources{}},
330+
)
331+
}
326332

327333
additionalRedactors := &troubleshootv1beta2.Redactor{
328334
TypeMeta: metav1.TypeMeta{
@@ -334,7 +340,7 @@ func loadSpecs(ctx context.Context, args []string, client kubernetes.Interface)
334340
},
335341
}
336342
for _, r := range kinds.RedactorsV1Beta2 {
337-
additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, r.Spec.Redactors...)
343+
additionalRedactors.Spec.Redactors = util.Append(additionalRedactors.Spec.Redactors, r.Spec.Redactors)
338344
}
339345

340346
return mainBundle, additionalRedactors, nil

cmd/troubleshoot/cli/run_test.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"context"
5+
"encoding/json"
56
"net/http"
67
"net/http/httptest"
78
"strings"
@@ -48,11 +49,13 @@ spec:
4849
require.NoError(t, err)
4950
require.NotNil(t, kinds)
5051

51-
moreKinds, err := loadSupportBundleSpecsFromURIs(ctx, kinds)
52+
assert.Len(t, kinds.SupportBundlesV1Beta2, 1)
53+
assert.NotNil(t, kinds.SupportBundlesV1Beta2[0].Spec.Collectors[0].ConfigMap)
54+
err = loadSupportBundleSpecsFromURIs(ctx, kinds)
5255
require.NoError(t, err)
5356

54-
require.Len(t, moreKinds.SupportBundlesV1Beta2, 1)
55-
assert.NotNil(t, moreKinds.SupportBundlesV1Beta2[0].Spec.Collectors[0].ClusterInfo)
57+
require.Len(t, kinds.SupportBundlesV1Beta2, 2)
58+
assert.NotNil(t, kinds.SupportBundlesV1Beta2[1].Spec.Collectors[0].ClusterInfo)
5659
}
5760

5861
func Test_loadSupportBundleSpecsFromURIs_TimeoutError(t *testing.T) {
@@ -76,8 +79,14 @@ func Test_loadSupportBundleSpecsFromURIs_TimeoutError(t *testing.T) {
7679
httputil.GetHttpClient().Timeout = before
7780
}()
7881

79-
kindsAfter, err := loadSupportBundleSpecsFromURIs(ctx, kinds)
82+
beforeJSON, err := json.Marshal(kinds)
8083
require.NoError(t, err)
8184

82-
assert.Equal(t, kinds, kindsAfter)
85+
err = loadSupportBundleSpecsFromURIs(ctx, kinds)
86+
require.NoError(t, err)
87+
88+
afterJSON, err := json.Marshal(kinds)
89+
require.NoError(t, err)
90+
91+
assert.JSONEq(t, string(beforeJSON), string(afterJSON))
8392
}

internal/specs/specs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func LoadFromCLIArgs(ctx context.Context, client kubernetes.Interface, args []st
167167
rawSpecs = append(rawSpecs, content...)
168168
} else {
169169
if !util.IsURL(v) {
170-
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, fmt.Errorf("%s is not a URL and was not found (err %s)", v, err))
170+
return nil, types.NewExitCodeError(constants.EXIT_CODE_SPEC_ISSUES, fmt.Errorf("%s is not a URL and was not found", v))
171171
}
172172

173173
// Download preflight specs

internal/util/util.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,25 @@ func EstimateNumberOfLines(text string) int {
5252
}
5353
return n
5454
}
55+
56+
// Append appends elements in src to target.
57+
// We have this function because of how append()
58+
// treats nil slices the same as empty slices.
59+
// An empty array in YAML like below is not the
60+
// same as when the array is not specified.
61+
//
62+
// spec:
63+
// collectors: []
64+
func Append[T any](target []T, src []T) []T {
65+
// Do nothing only if src is nil
66+
if src == nil {
67+
return target
68+
}
69+
70+
// In case target is nil, we need to initialize it
71+
// since append() will not do it for us when len(src) == 0
72+
if target == nil {
73+
target = []T{}
74+
}
75+
return append(target, src...)
76+
}

internal/util/util_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,55 @@ func Test_EstimateNumberOfLines(t *testing.T) {
194194
})
195195
}
196196
}
197+
198+
func TestAppend(t *testing.T) {
199+
tests := []struct {
200+
name string
201+
target []string
202+
src []string
203+
want []string
204+
}{
205+
{
206+
name: "empty target",
207+
target: []string{},
208+
src: []string{"a", "b", "c"},
209+
want: []string{"a", "b", "c"},
210+
},
211+
{
212+
name: "empty src",
213+
target: []string{"a", "b", "c"},
214+
src: []string{},
215+
want: []string{"a", "b", "c"},
216+
},
217+
{
218+
name: "non-empty target and src",
219+
target: []string{"a", "b", "c"},
220+
src: []string{"d", "e", "f"},
221+
want: []string{"a", "b", "c", "d", "e", "f"},
222+
},
223+
{
224+
name: "nil target",
225+
target: nil,
226+
src: []string{"a", "b", "c"},
227+
want: []string{"a", "b", "c"},
228+
},
229+
{
230+
name: "nil src",
231+
target: []string{"a", "b", "c"},
232+
src: nil,
233+
want: []string{"a", "b", "c"},
234+
},
235+
{
236+
name: "nil target and empty src",
237+
target: nil,
238+
src: []string{},
239+
want: []string{},
240+
},
241+
}
242+
for _, tt := range tests {
243+
t.Run(tt.name, func(t *testing.T) {
244+
got := Append(tt.target, tt.src)
245+
assert.Equal(t, tt.want, got, "Append() = %v, want %v", got, tt.want)
246+
})
247+
}
248+
}

pkg/loader/loader.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ func (l *specLoader) loadFromStrings(rawSpecs ...string) (*TroubleshootKinds, er
153153
// For secrets and configmaps, extract support bundle, redactor or preflight specs
154154
// For troubleshoot kinds, pass them through
155155
for _, rawDoc := range multiRawDocs {
156+
if rawDoc == "" {
157+
continue
158+
}
159+
156160
var parsed parsedDoc
157161

158162
err := yaml.Unmarshal([]byte(rawDoc), &parsed)

pkg/supportbundle/load.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func loadSpec(arg string) ([]byte, error) {
225225
}
226226

227227
if !util.IsURL(arg) {
228-
return nil, fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
228+
return nil, fmt.Errorf("%s is not a URL and was not found", arg)
229229
}
230230

231231
spec, err := loadSpecFromURL(arg)

0 commit comments

Comments
 (0)