Skip to content

Commit f9dbccd

Browse files
authored
feat(support-bundle): add support labels for troubleshoot.io and troubleshoot.sh (#1203)
1 parent 08a1075 commit f9dbccd

File tree

13 files changed

+293
-52
lines changed

13 files changed

+293
-52
lines changed

cmd/troubleshoot/cli/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
6868
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
6969
cmd.Flags().Bool("interactive", true, "enable/disable interactive mode")
7070
cmd.Flags().Bool("collect-without-permissions", true, "always generate a support bundle, even if it some require additional permissions")
71-
cmd.Flags().StringSliceP("selector", "l", []string{"troubleshoot.io/kind=support-bundle"}, "selector to filter on for loading additional support bundle specs found in secrets within the cluster")
71+
cmd.Flags().StringSliceP("selector", "l", []string{"troubleshoot.sh/kind=support-bundle"}, "selector to filter on for loading additional support bundle specs found in secrets within the cluster")
7272
cmd.Flags().Bool("load-cluster-specs", false, "enable/disable loading additional troubleshoot specs found within the cluster. required when no specs are provided on the command line")
7373
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
7474
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")

cmd/troubleshoot/cli/run.go

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"os/signal"
1111
"path/filepath"
12+
"reflect"
1213
"strings"
1314
"sync"
1415
"time"
@@ -19,6 +20,7 @@ import (
1920
"github.com/pkg/errors"
2021
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
2122
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
23+
"github.com/replicatedhq/troubleshoot/pkg/constants"
2224
"github.com/replicatedhq/troubleshoot/pkg/convert"
2325
"github.com/replicatedhq/troubleshoot/pkg/httputil"
2426
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
@@ -257,7 +259,7 @@ the %s Admin Console to begin analysis.`
257259
}
258260

259261
// loadClusterSpecs loads the support bundle and redactor specs from the cluster
260-
// based on troubleshoot.io/kind=support-bundle label selector. We search for secrets
262+
// based on the provided labels. By default this will be troubleshoot.io/kind=support-bundle and troubleshoot.sh/kind=support-bundle label selectors. We search for secrets
261263
// and configmaps with the label selector and parse the data as a support bundle. If the
262264
// user does not have sufficient permissions to list & read secrets and configmaps from
263265
// all namespaces, we will fallback to trying each namespace individually, and eventually
@@ -269,7 +271,13 @@ func loadClusterSpecs() (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta
269271

270272
klog.Info("Discover troubleshoot specs from cluster")
271273

272-
labelSelector := strings.Join(v.GetStringSlice("selector"), ",")
274+
selectors := v.GetStringSlice("selector")
275+
if reflect.DeepEqual(selectors, []string{"troubleshoot.sh/kind=support-bundle"}) {
276+
// Its the default selector so we append troubleshoot.io/kind=support-bundle to it due to backwards compatibility
277+
selectors = append(selectors, "troubleshoot.io/kind=support-bundle")
278+
}
279+
280+
labelSelector := strings.Join(selectors, ",")
273281

274282
parsedSelector, err := labels.Parse(labelSelector)
275283
if err != nil {
@@ -331,28 +339,35 @@ func loadClusterSpecs() (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta
331339

332340
var bundlesFromCluster []string
333341

342+
parsedSelectorStrings, err := specs.SplitTroubleshootSecretLabelSelector(client, parsedSelector)
343+
if err != nil {
344+
klog.Errorf("failed to parse troubleshoot labels selector %s", err)
345+
}
346+
334347
// Search cluster for support bundle specs
335-
klog.V(1).Infof("Search support bundle specs from [%q] namespaces using %q selector", strings.Join(namespaces, ", "), parsedSelector.String())
336-
for _, ns := range namespaces {
337-
bundlesFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), ns, specs.SupportBundleKey)
338-
if err != nil {
339-
if !k8serrors.IsForbidden(err) {
340-
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
341-
} else {
342-
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
348+
for _, parsedSelectorString := range parsedSelectorStrings {
349+
klog.V(1).Infof("Search support bundle specs from [%q] namespace using %q selector", strings.Join(namespaces, ", "), parsedSelectorString)
350+
for _, ns := range namespaces {
351+
bundlesFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelectorString, ns, constants.SupportBundleKey)
352+
if err != nil {
353+
if !k8serrors.IsForbidden(err) {
354+
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
355+
} else {
356+
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
357+
}
343358
}
344-
}
345-
bundlesFromCluster = append(bundlesFromCluster, bundlesFromSecrets...)
359+
bundlesFromCluster = append(bundlesFromCluster, bundlesFromSecrets...)
346360

347-
bundlesFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), ns, specs.SupportBundleKey)
348-
if err != nil {
349-
if !k8serrors.IsForbidden(err) {
350-
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
351-
} else {
352-
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
361+
bundlesFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelectorString, ns, constants.SupportBundleKey)
362+
if err != nil {
363+
if !k8serrors.IsForbidden(err) {
364+
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
365+
} else {
366+
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
367+
}
353368
}
369+
bundlesFromCluster = append(bundlesFromCluster, bundlesFromConfigMaps...)
354370
}
355-
bundlesFromCluster = append(bundlesFromCluster, bundlesFromConfigMaps...)
356371
}
357372

358373
parsedBundle := &troubleshootv1beta2.SupportBundle{}
@@ -379,27 +394,29 @@ func loadClusterSpecs() (*troubleshootv1beta2.SupportBundle, *troubleshootv1beta
379394
var redactorsFromCluster []string
380395

381396
// Search cluster for redactor specs
382-
klog.V(1).Infof("Search redactor specs from [%q] namespaces using %q selector", strings.Join(namespaces, ", "), parsedSelector.String())
383-
for _, ns := range namespaces {
384-
redactorsFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelector.String(), ns, specs.RedactorKey)
385-
if err != nil {
386-
if !k8serrors.IsForbidden(err) {
387-
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
388-
} else {
389-
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
397+
for _, parsedSelectorString := range parsedSelectorStrings {
398+
klog.V(1).Infof("Search redactor specs from [%q] namespace using %q selector", strings.Join(namespaces, ", "), parsedSelectorString)
399+
for _, ns := range namespaces {
400+
redactorsFromSecrets, err := specs.LoadFromSecretMatchingLabel(client, parsedSelectorString, ns, constants.RedactorKey)
401+
if err != nil {
402+
if !k8serrors.IsForbidden(err) {
403+
klog.Errorf("failed to load support bundle spec from secrets: %s", err)
404+
} else {
405+
klog.Warningf("Reading secrets from %q namespace forbidden", ns)
406+
}
390407
}
391-
}
392-
redactorsFromCluster = append(redactorsFromCluster, redactorsFromSecrets...)
408+
redactorsFromCluster = append(redactorsFromCluster, redactorsFromSecrets...)
393409

394-
redactorsFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelector.String(), ns, specs.RedactorKey)
395-
if err != nil {
396-
if !k8serrors.IsForbidden(err) {
397-
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
398-
} else {
399-
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
410+
redactorsFromConfigMaps, err := specs.LoadFromConfigMapMatchingLabel(client, parsedSelectorString, ns, constants.RedactorKey)
411+
if err != nil {
412+
if !k8serrors.IsForbidden(err) {
413+
klog.Errorf("failed to load support bundle spec from configmap: %s", err)
414+
} else {
415+
klog.Warningf("Reading configmaps from %q namespace forbidden", ns)
416+
}
400417
}
418+
redactorsFromCluster = append(redactorsFromCluster, redactorsFromConfigMaps...)
401419
}
402-
redactorsFromCluster = append(redactorsFromCluster, redactorsFromConfigMaps...)
403420
}
404421

405422
for _, redactor := range redactorsFromCluster {

docs/preflight.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ preflight [url] [flags]
2828
--context string The name of the kubeconfig context to use
2929
--cpuprofile string File path to write cpu profiling data
3030
--debug enable debug logging
31+
--disable-compression If true, opt-out of response compression for all requests to the server
3132
--format string output format, one of human, json, yaml. only used when interactive is set to false (default "human")
3233
-h, --help help for preflight
3334
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
@@ -44,10 +45,12 @@ preflight [url] [flags]
4445
--tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
4546
--token string Bearer token for authentication to the API server
4647
--user string The name of the kubeconfig user to use
48+
-v, --v Level number for the log level verbosity
4749
```
4850

4951
### SEE ALSO
5052

53+
* [preflight oci-fetch](preflight_oci-fetch.md) - Fetch a preflight from an OCI registry and print it to standard out
5154
* [preflight version](preflight_version.md) - Print the current version and exit
5255

53-
###### Auto generated by spf13/cobra on 3-Jan-2023
56+
###### Auto generated by spf13/cobra on 8-Jun-2023

docs/preflight_oci-fetch.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
## preflight oci-fetch
2+
3+
Fetch a preflight from an OCI registry and print it to standard out
4+
5+
```
6+
preflight oci-fetch [URI] [flags]
7+
```
8+
9+
### Options
10+
11+
```
12+
-h, --help help for oci-fetch
13+
```
14+
15+
### Options inherited from parent commands
16+
17+
```
18+
--collect-without-permissions always run preflight checks even if some require permissions that preflight does not have (default true)
19+
--collector-image string the full name of the collector image to use
20+
--collector-pullpolicy string the pull policy of the collector image
21+
--cpuprofile string File path to write cpu profiling data
22+
--debug enable debug logging
23+
--format string output format, one of human, json, yaml. only used when interactive is set to false (default "human")
24+
--interactive interactive preflights (default true)
25+
--memprofile string File path to write memory profiling data
26+
-o, --output string specify the output file path for the preflight checks
27+
--selector string selector (label query) to filter remote collection nodes on.
28+
--since string force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.
29+
--since-time string force pod logs collectors to return logs after a specific date (RFC3339)
30+
```
31+
32+
### SEE ALSO
33+
34+
* [preflight](preflight.md) - Run and retrieve preflight checks in a cluster
35+
36+
###### Auto generated by spf13/cobra on 8-Jun-2023

docs/support-bundle.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ support-bundle [urls...] [flags]
2525
--collect-without-permissions always generate a support bundle, even if it some require additional permissions (default true)
2626
--context string The name of the kubeconfig context to use
2727
--cpuprofile string File path to write cpu profiling data
28-
--debug enable debug logging
28+
--debug enable debug logging. This is equivalent to --v=0
29+
--disable-compression If true, opt-out of response compression for all requests to the server
2930
-h, --help help for support-bundle
3031
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
3132
--interactive enable/disable interactive mode (default true)
@@ -38,13 +39,14 @@ support-bundle [urls...] [flags]
3839
--redact enable/disable default redactions (default true)
3940
--redactors strings names of the additional redactors to use
4041
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
41-
-l, --selector strings selector to filter on for loading additional support bundle specs found in secrets within the cluster (default [troubleshoot.io/kind=support-bundle])
42+
-l, --selector strings selector to filter on for loading additional support bundle specs found in secrets within the cluster (default [troubleshoot.sh/kind=support-bundle])
4243
-s, --server string The address and port of the Kubernetes API server
4344
--since string force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.
4445
--since-time string force pod logs collectors to return logs after a specific date (RFC3339)
4546
--tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
4647
--token string Bearer token for authentication to the API server
4748
--user string The name of the kubeconfig user to use
49+
-v, --v Level number for the log level verbosity
4850
```
4951

5052
### SEE ALSO
@@ -53,4 +55,4 @@ support-bundle [urls...] [flags]
5355
* [support-bundle redact](support-bundle_redact.md) - Redact information from a generated support bundle archive
5456
* [support-bundle version](support-bundle_version.md) - Print the current version and exit
5557

56-
###### Auto generated by spf13/cobra on 3-Jan-2023
58+
###### Auto generated by spf13/cobra on 8-Jun-2023

pkg/constants/constants.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,11 @@ const (
5959
EXIT_CODE_WARN = 4
6060

6161
// Troubleshoot label constants
62-
SupportBundleKey = "support-bundle-spec"
63-
RedactorKey = "redactor-spec"
64-
PreflightKey = "preflight.yaml" // Shouldn't this be "preflight-spec"?
62+
SupportBundleKey = "support-bundle-spec"
63+
RedactorKey = "redactor-spec"
64+
TroubleshootIOLabelKey = "troubleshoot.io/kind"
65+
TroubleshootSHLabelKey = "troubleshoot.sh/kind"
66+
PreflightKey = "preflight.yaml" // Shouldn't this be "preflight-spec"?
6567

6668
// Troubleshoot spec constants
6769
Troubleshootv1beta2Kind = "troubleshoot.sh/v1beta2"

pkg/specs/specs.go

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
11
package specs
22

3-
import "github.com/replicatedhq/troubleshoot/pkg/constants"
3+
import (
4+
"github.com/pkg/errors"
5+
"github.com/replicatedhq/troubleshoot/pkg/constants"
6+
"k8s.io/apimachinery/pkg/labels"
7+
"k8s.io/client-go/kubernetes"
8+
"k8s.io/klog/v2"
9+
)
410

5-
const (
6-
// Preserved for backwards compatibility
11+
// SplitTroubleshootSecretLabelSelector splits a label selector into two selectors, if applicable:
12+
// 1. troubleshoot.io/kind=support-bundle and non-troubleshoot (if contains) labels selector.
13+
// 2. troubleshoot.sh/kind=support-bundle and non-troubleshoot (if contains) labels selector.
14+
func SplitTroubleshootSecretLabelSelector(client kubernetes.Interface, labelSelector labels.Selector) ([]string, error) {
715

8-
// Deprecated: Use constants.SupportBundleKey instead
9-
SupportBundleKey = constants.SupportBundleKey
10-
// Deprecated: Use constants.RedactorKey instead
11-
RedactorKey = constants.RedactorKey
12-
)
16+
klog.V(1).Infof("Split %q selector into troubleshoot and non-troubleshoot labels selector separately, if applicable", labelSelector.String())
17+
18+
selectorRequirements, selectorSelectable := labelSelector.Requirements()
19+
if !selectorSelectable {
20+
return nil, errors.Errorf("Selector %q is not selectable", labelSelector.String())
21+
}
22+
23+
var troubleshootReqs, otherReqs []labels.Requirement
24+
25+
for _, req := range selectorRequirements {
26+
if req.Key() == constants.TroubleshootIOLabelKey || req.Key() == constants.TroubleshootSHLabelKey {
27+
troubleshootReqs = append(troubleshootReqs, req)
28+
} else {
29+
otherReqs = append(otherReqs, req)
30+
}
31+
}
32+
33+
parsedSelectorStrings := make([]string, 0)
34+
// Combine each troubleshoot requirement with other requirements to form new selectors
35+
if len(troubleshootReqs) == 0 {
36+
return []string{labelSelector.String()}, nil
37+
}
38+
39+
for _, tReq := range troubleshootReqs {
40+
reqs := append(otherReqs, tReq)
41+
newSelector := labels.NewSelector().Add(reqs...)
42+
parsedSelectorStrings = append(parsedSelectorStrings, newSelector.String())
43+
}
44+
45+
return parsedSelectorStrings, nil
46+
}

pkg/specs/specs_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package specs
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"k8s.io/apimachinery/pkg/labels"
8+
)
9+
10+
func Test_SplitTroubleshootSecretLabelSelector(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
selectorString string
14+
expectedSelectors []string
15+
expectedError bool
16+
}{
17+
{
18+
name: "Split both troubleshoot and non-troubleshoot labels",
19+
selectorString: "troubleshoot.io/kind=support-bundle,troubleshoot.sh/kind=support-bundle,a=b",
20+
expectedSelectors: []string{
21+
"a=b,troubleshoot.io/kind=support-bundle",
22+
"a=b,troubleshoot.sh/kind=support-bundle",
23+
},
24+
expectedError: false,
25+
},
26+
{
27+
name: "Split only troubleshoot.io label",
28+
selectorString: "troubleshoot.io/kind=support-bundle",
29+
expectedSelectors: []string{"troubleshoot.io/kind=support-bundle"},
30+
expectedError: false,
31+
},
32+
{
33+
name: "Split only troubleshoot.sh label",
34+
selectorString: "troubleshoot.sh/kind=support-bundle",
35+
expectedSelectors: []string{"troubleshoot.sh/kind=support-bundle"},
36+
expectedError: false,
37+
},
38+
{
39+
name: "Split only non-troubleshoot label",
40+
selectorString: "a=b",
41+
expectedSelectors: []string{"a=b"},
42+
expectedError: false,
43+
},
44+
}
45+
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
selector, err := labels.Parse(tt.selectorString)
49+
if err != nil {
50+
t.Errorf("Error parsing selector string: %v", err)
51+
return
52+
}
53+
54+
gotSelectors, err := SplitTroubleshootSecretLabelSelector(nil, selector)
55+
if (err != nil) != tt.expectedError {
56+
t.Errorf("Expected error: %v, got: %v", tt.expectedError, err)
57+
return
58+
}
59+
60+
assert.ElementsMatch(t, tt.expectedSelectors, gotSelectors)
61+
})
62+
}
63+
}

test/validate-support-bundle-e2e.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,15 @@ if ! grep "labelled-support-bundle-2 \*\*\*HIDDEN\*\*\*" "$tmpdir/$bundle_direct
9999
echo "Hidden content not found in redacted echo-hi-2 file"
100100
exit 1
101101
fi
102+
103+
if ! grep "labelled-support-bundle-3 \*\*\*HIDDEN\*\*\*" "$tmpdir/$bundle_directory_name/echo-hi-3"; then
104+
echo "$(cat $tmpdir/$bundle_directory_name/echo-hi-3)"
105+
echo "Hidden content not found in redacted echo-hi-3 file"
106+
exit 1
107+
fi
108+
109+
if ! grep "labelled-support-bundle-4 \*\*\*HIDDEN\*\*\*" "$tmpdir/$bundle_directory_name/echo-hi-4"; then
110+
echo "$(cat $tmpdir/$bundle_directory_name/echo-hi-4)"
111+
echo "Hidden content not found in redacted echo-hi-4 file"
112+
exit 1
113+
fi

0 commit comments

Comments
 (0)