11package cli
22
33import (
4+ "context"
45 "crypto/tls"
56 "encoding/json"
67 "fmt"
@@ -27,14 +28,16 @@ import (
2728 "github.com/replicatedhq/troubleshoot/pkg/supportbundle"
2829 "github.com/spf13/viper"
2930 spin "github.com/tj/go-spin"
31+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
32+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3033 "k8s.io/apimachinery/pkg/labels"
3134 "k8s.io/client-go/kubernetes"
3235 "k8s.io/client-go/rest"
3336 "k8s.io/klog/v2"
3437)
3538
3639func runTroubleshoot (v * viper.Viper , arg []string ) error {
37- if v .GetBool ("load-cluster-specs" ) == false && len (arg ) < 1 {
40+ if ! v .GetBool ("load-cluster-specs" ) && len (arg ) < 1 {
3841 return errors .New ("flag load-cluster-specs must be set if no specs are provided on the command line" )
3942 }
4043
@@ -107,105 +110,15 @@ func runTroubleshoot(v *viper.Viper, arg []string) error {
107110 }
108111
109112 if v .GetBool ("load-cluster-specs" ) {
110- labelSelector := strings .Join (v .GetStringSlice ("selector" ), "," )
111-
112- parsedSelector , err := labels .Parse (labelSelector )
113- if err != nil {
114- return errors .Wrap (err , "unable to parse selector" )
115- }
116-
117- config , err := k8sutil .GetRESTConfig ()
118- if err != nil {
119- return errors .Wrap (err , "failed to convert kube flags to rest config" )
120- }
121-
122- client , err := kubernetes .NewForConfig (config )
123- if err != nil {
124- return errors .Wrap (err , "failed to convert create k8s client" )
125- }
126-
127- namespace := ""
128-
129- if v .GetString ("namespace" ) != "" {
130- namespace = v .GetString ("namespace" )
131- } else {
132- IsNamespacedScopeRBAC , err := k8sutil .IsNamespacedScopeRBAC (client )
133- if err != nil {
134- return errors .Wrap (err , "failed to check if cluster is namespaced" )
135- }
136-
137- if ! IsNamespacedScopeRBAC {
138- kubeconfig := k8sutil .GetKubeconfig ()
139- namespace , _ , _ = kubeconfig .Namespace ()
140- }
141- }
142-
143- var bundlesFromCluster []string
144-
145- // Search cluster for Troubleshoot objects in cluster
146- bundlesFromSecrets , err := specs .LoadFromSecretMatchingLabel (client , parsedSelector .String (), namespace , specs .SupportBundleKey )
147- if err != nil {
148- klog .Errorf ("failed to load support bundle spec from secrets: %s" , err )
149- }
150- bundlesFromCluster = append (bundlesFromCluster , bundlesFromSecrets ... )
151-
152- bundlesFromConfigMaps , err := specs .LoadFromConfigMapMatchingLabel (client , parsedSelector .String (), namespace , specs .SupportBundleKey )
153- if err != nil {
154- klog .Errorf ("failed to load support bundle spec from secrets: %s" , err )
155- }
156- bundlesFromCluster = append (bundlesFromCluster , bundlesFromConfigMaps ... )
157-
158- for _ , bundle := range bundlesFromCluster {
159- multidocs := strings .Split (string (bundle ), "\n ---\n " )
160- parsedBundleFromSecret , err := supportbundle .ParseSupportBundleFromDoc ([]byte (multidocs [0 ]))
161- if err != nil {
162- klog .Errorf ("failed to parse support bundle spec: %s" , err )
163- continue
164- }
165-
166- if mainBundle == nil {
167- mainBundle = parsedBundleFromSecret
168- } else {
169- mainBundle = supportbundle .ConcatSpec (mainBundle , parsedBundleFromSecret )
170- }
171-
172- parsedRedactors , err := supportbundle .ParseRedactorsFromDocs (multidocs )
173- if err != nil {
174- klog .Errorf ("failed to parse redactors from doc: %s" , err )
175- continue
176- }
177-
178- additionalRedactors .Spec .Redactors = append (additionalRedactors .Spec .Redactors , parsedRedactors ... )
179- }
180-
181- var redactorsFromCluster []string
182-
183- // Search cluster for Troubleshoot objects in ConfigMaps
184- redactorsFromSecrets , err := specs .LoadFromSecretMatchingLabel (client , parsedSelector .String (), namespace , specs .RedactorKey )
113+ sbFromCluster , redactorsFromCluster , err := loadClusterSpecs ()
185114 if err != nil {
186- klog . Errorf ( "failed to load redactor specs from config maps: %s" , err )
115+ return err
187116 }
188- redactorsFromCluster = append (redactorsFromCluster , redactorsFromSecrets ... )
189-
190- redactorsFromConfigMaps , err := specs .LoadFromConfigMapMatchingLabel (client , parsedSelector .String (), namespace , specs .RedactorKey )
191- if err != nil {
192- klog .Errorf ("failed to load redactor specs from config maps: %s" , err )
193- }
194- redactorsFromCluster = append (redactorsFromCluster , redactorsFromConfigMaps ... )
195-
196- for _ , redactor := range redactorsFromCluster {
197- multidocs := strings .Split (string (redactor ), "\n ---\n " )
198- parsedRedactors , err := supportbundle .ParseRedactorsFromDocs (multidocs )
199- if err != nil {
200- klog .Errorf ("failed to parse redactors from doc: %s" , err )
201- }
202-
203- additionalRedactors .Spec .Redactors = append (additionalRedactors .Spec .Redactors , parsedRedactors ... )
204- }
205-
206- if mainBundle == nil {
117+ if sbFromCluster == nil {
207118 return errors .New ("no specs found in cluster" )
208119 }
120+ mainBundle = supportbundle .ConcatSpec (mainBundle , sbFromCluster )
121+ additionalRedactors .Spec .Redactors = append (additionalRedactors .Spec .Redactors , redactorsFromCluster .Spec .Redactors ... )
209122 }
210123
211124 if mainBundle == nil {
@@ -346,6 +259,162 @@ the %s Admin Console to begin analysis.`
346259 return nil
347260}
348261
262+ // loadClusterSpecs loads the support bundle and redactor specs from the cluster
263+ // based on troubleshoot.io/kind=support-bundle label selector. We search for secrets
264+ // and configmaps with the label selector and parse the data as a support bundle. If the
265+ // user does not have sufficient permissions to list & read secrets and configmaps from
266+ // all namespaces, we will fallback to trying each namespace individually, and eventually
267+ // default to the configured kubeconfig namespace.
268+ func loadClusterSpecs () (* troubleshootv1beta2.SupportBundle , * troubleshootv1beta2.Redactor , error ) {
269+ var parsedBundle * troubleshootv1beta2.SupportBundle
270+ redactors := & troubleshootv1beta2.Redactor {}
271+
272+ v := viper .GetViper () // It's singleton, so we can use it anywhere
273+
274+ klog .Info ("Discover troubleshoot specs from cluster" )
275+
276+ labelSelector := strings .Join (v .GetStringSlice ("selector" ), "," )
277+
278+ parsedSelector , err := labels .Parse (labelSelector )
279+ if err != nil {
280+ return nil , nil , errors .Wrap (err , "unable to parse selector" )
281+ }
282+
283+ config , err := k8sutil .GetRESTConfig ()
284+ if err != nil {
285+ return nil , nil , errors .Wrap (err , "failed to convert kube flags to rest config" )
286+ }
287+
288+ client , err := kubernetes .NewForConfig (config )
289+ if err != nil {
290+ return nil , nil , errors .Wrap (err , "failed to convert create k8s client" )
291+ }
292+
293+ // List of namespaces we want to search for secrets and configmaps with support bundle specs
294+ namespaces := []string {}
295+ ctx := context .Background ()
296+
297+ if v .GetString ("namespace" ) != "" {
298+ // Just progress with the namespace provided
299+ namespaces = []string {v .GetString ("namespace" )}
300+ } else {
301+ // Check if I can read secrets and configmaps in all namespaces
302+ ican , err := k8sutil .CanIListAndGetAllSecretsAndConfigMaps (ctx , client )
303+ if err != nil {
304+ return nil , nil , errors .Wrap (err , "failed to check if I can read secrets and configmaps" )
305+ }
306+ klog .V (1 ).Infof ("Can I read any secrets and configmaps: %v" , ican )
307+
308+ if ican {
309+ // I can read secrets and configmaps in all namespaces
310+ // No need to iterate over all namespaces
311+ namespaces = []string {"" }
312+ } else {
313+ // Get list of all namespaces and try to find specs from each namespace
314+ nsList , err := client .CoreV1 ().Namespaces ().List (ctx , metav1.ListOptions {})
315+ if err != nil {
316+ if k8serrors .IsForbidden (err ) {
317+ kubeconfig := k8sutil .GetKubeconfig ()
318+ ns , _ , err := kubeconfig .Namespace ()
319+ if err != nil {
320+ return nil , nil , errors .Wrap (err , "failed to get namespace from kubeconfig" )
321+ }
322+ // If we are not allowed to list namespaces, just use the default namespace
323+ // configured in the kubeconfig
324+ namespaces = []string {ns }
325+ } else {
326+ return nil , nil , errors .Wrap (err , "failed to list namespaces" )
327+ }
328+ }
329+
330+ for _ , ns := range nsList .Items {
331+ namespaces = append (namespaces , ns .Name )
332+ }
333+ }
334+ }
335+
336+ var bundlesFromCluster []string
337+
338+ // Search cluster for support bundle specs
339+ klog .V (1 ).Infof ("Search support bundle specs from [%q] namespaces using %q selector" , strings .Join (namespaces , ", " ), parsedSelector .String ())
340+ for _ , ns := range namespaces {
341+ bundlesFromSecrets , err := specs .LoadFromSecretMatchingLabel (client , parsedSelector .String (), ns , specs .SupportBundleKey )
342+ if err != nil {
343+ if ! k8serrors .IsForbidden (err ) {
344+ klog .Errorf ("failed to load support bundle spec from secrets: %s" , err )
345+ } else {
346+ klog .Warningf ("Reading secrets from %q namespace forbidden" , ns )
347+ }
348+ }
349+ bundlesFromCluster = append (bundlesFromCluster , bundlesFromSecrets ... )
350+
351+ bundlesFromConfigMaps , err := specs .LoadFromConfigMapMatchingLabel (client , parsedSelector .String (), ns , specs .SupportBundleKey )
352+ if err != nil {
353+ if ! k8serrors .IsForbidden (err ) {
354+ klog .Errorf ("failed to load support bundle spec from configmap: %s" , err )
355+ } else {
356+ klog .Warningf ("Reading configmaps from %q namespace forbidden" , ns )
357+ }
358+ }
359+ bundlesFromCluster = append (bundlesFromCluster , bundlesFromConfigMaps ... )
360+ }
361+
362+ for _ , bundle := range bundlesFromCluster {
363+ multidocs := strings .Split (string (bundle ), "\n ---\n " )
364+ parsedBundle , err = supportbundle .ParseSupportBundleFromDoc ([]byte (multidocs [0 ]))
365+ if err != nil {
366+ klog .Errorf ("failed to parse support bundle spec: %s" , err )
367+ continue
368+ }
369+
370+ parsedRedactors , err := supportbundle .ParseRedactorsFromDocs (multidocs )
371+ if err != nil {
372+ klog .Errorf ("failed to parse redactors from doc: %s" , err )
373+ continue
374+ }
375+
376+ redactors .Spec .Redactors = append (redactors .Spec .Redactors , parsedRedactors ... )
377+ }
378+
379+ var redactorsFromCluster []string
380+
381+ // 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 )
390+ }
391+ }
392+ redactorsFromCluster = append (redactorsFromCluster , redactorsFromSecrets ... )
393+
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 )
400+ }
401+ }
402+ redactorsFromCluster = append (redactorsFromCluster , redactorsFromConfigMaps ... )
403+ }
404+
405+ for _ , redactor := range redactorsFromCluster {
406+ multidocs := strings .Split (string (redactor ), "\n ---\n " )
407+ parsedRedactors , err := supportbundle .ParseRedactorsFromDocs (multidocs )
408+ if err != nil {
409+ klog .Errorf ("failed to parse redactors from doc: %s" , err )
410+ }
411+
412+ redactors .Spec .Redactors = append (redactors .Spec .Redactors , parsedRedactors ... )
413+ }
414+
415+ return parsedBundle , redactors , nil
416+ }
417+
349418func parseTimeFlags (v * viper.Viper ) (* time.Time , error ) {
350419 var (
351420 sinceTime time.Time
0 commit comments