Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/troubleshoot/cli/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,14 +513,14 @@ func TestReadLinesFromReader(t *testing.T) {
content: "line1\nline2\nline3\n",
maxBytes: 1000,
wantLen: 3,
wantLast: "line3",
wantLast: "line3\n",
},
{
name: "content exceeds limit",
content: "line1\nline2\nline3\nline4\nline5\n",
maxBytes: 15, // Only allows first 2 lines plus truncation marker
wantLen: 3,
wantLast: "... (content truncated due to size)",
wantLast: "... (content truncated due to size)\n",
},
{
name: "empty content",
Expand Down
53 changes: 50 additions & 3 deletions pkg/collect/autodiscovery/discoverer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type Discoverer struct {
client kubernetes.Interface
rbacChecker *RBACChecker
expander *ResourceExpander
kotsDetector *KotsDetector
rbacReporter *RBACReporter
}

// NewDiscoverer creates a new autodiscovery discoverer
Expand All @@ -36,12 +38,16 @@ func NewDiscoverer(clientConfig *rest.Config, client kubernetes.Interface) (*Dis
}

expander := NewResourceExpander()
kotsDetector := NewKotsDetector(client)
rbacReporter := NewRBACReporter()

return &Discoverer{
clientConfig: clientConfig,
client: client,
rbacChecker: rbacChecker,
expander: expander,
kotsDetector: kotsDetector,
rbacReporter: rbacReporter,
}, nil
}

Expand All @@ -65,7 +71,7 @@ func (d *Discoverer) DiscoverFoundational(ctx context.Context, opts DiscoveryOpt
}

// Generate foundational collectors
foundationalCollectors := d.generateFoundationalCollectors(namespaces, opts)
foundationalCollectors := d.generateFoundationalCollectors(discoveryCtx, namespaces, opts)

// Apply RBAC filtering if enabled
if opts.RBACCheck {
Expand All @@ -77,6 +83,13 @@ func (d *Discoverer) DiscoverFoundational(ctx context.Context, opts DiscoveryOpt
}
}

// Generate RBAC remediation report if there were permission issues
if d.rbacReporter.HasWarnings() {
d.rbacReporter.GeneratePermissionSummary()
d.rbacReporter.GenerateRemediationReport()
d.rbacReporter.SummarizeCollectionResults(len(foundationalCollectors) + d.rbacReporter.GetFilteredCollectorCount())
}

klog.V(2).Infof("Discovered %d foundational collectors", len(foundationalCollectors))
return foundationalCollectors, nil
}
Expand Down Expand Up @@ -139,12 +152,44 @@ func (d *Discoverer) getTargetNamespaces(ctx context.Context, requestedNamespace
}

// generateFoundationalCollectors creates the standard set of foundational collectors
func (d *Discoverer) generateFoundationalCollectors(namespaces []string, opts DiscoveryOptions) []CollectorSpec {
func (d *Discoverer) generateFoundationalCollectors(ctx context.Context, namespaces []string, opts DiscoveryOptions) []CollectorSpec {
var collectors []CollectorSpec

// Always include cluster-level info
collectors = append(collectors, d.generateClusterInfoCollectors()...)

// KOTS-aware discovery: Detect and add KOTS-specific collectors
if kotsApps, err := d.kotsDetector.DetectKotsApplications(ctx); err == nil && len(kotsApps) > 0 {
klog.Infof("Found %d KOTS applications, generating KOTS-specific collectors", len(kotsApps))
kotsCollectors := d.kotsDetector.GenerateKotsCollectors(kotsApps)
collectors = append(collectors, kotsCollectors...)

// Log the KOTS collectors for debugging
for _, kotsCollector := range kotsCollectors {
klog.V(2).Infof("Added KOTS collector: %s (type: %s, namespace: %s)",
kotsCollector.Name, kotsCollector.Type, kotsCollector.Namespace)
}
} else if err != nil {
klog.V(2).Infof("KOTS detection failed (non-fatal): %v", err)
} else {
klog.V(2).Info("No KOTS applications detected in cluster")
}

// Generate standard KOTS diagnostic collectors for troubleshooting (when not in test mode)
// These attempt to collect expected KOTS resources even if no apps are detected
// This creates valuable error files when resources are missing (important for support)
if !opts.TestMode {
standardKotsCollectors := d.kotsDetector.GenerateStandardKotsCollectors(ctx)
collectors = append(collectors, standardKotsCollectors...)

klog.V(2).Infof("Added %d standard KOTS diagnostic collectors", len(standardKotsCollectors))
for _, stdCollector := range standardKotsCollectors {
klog.V(2).Infof("Added standard KOTS collector: %s (creates error file if missing)", stdCollector.Name)
}
} else {
klog.V(2).Info("Skipping standard KOTS collectors in test mode")
}

// Add namespace-scoped collectors for each target namespace
for _, namespace := range namespaces {
collectors = append(collectors, d.generateNamespacedCollectors(namespace, opts)...)
Expand Down Expand Up @@ -287,7 +332,9 @@ func (d *Discoverer) applyRBACFiltering(ctx context.Context, collectors []Collec
if allowedKeys[key] {
filteredCollectors = append(filteredCollectors, collector)
} else {
klog.V(3).Infof("Filtered out collector %s due to RBAC permissions", collector.Name)
// FIXED: Replace silent filtering with user-visible warnings
d.rbacReporter.ReportFilteredCollector(collector, "insufficient RBAC permissions")
d.rbacReporter.ReportMissingPermission(resource.Kind, resource.Namespace, "get,list", collector.Name)
}
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/collect/autodiscovery/discoverer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func TestDiscoverer_DiscoverFoundational(t *testing.T) {
IncludeImages: false,
RBACCheck: false,
Timeout: 10 * time.Second,
TestMode: true,
},
wantCollectorTypes: map[CollectorType]int{
CollectorTypeClusterInfo: 1,
Expand All @@ -119,6 +120,7 @@ func TestDiscoverer_DiscoverFoundational(t *testing.T) {
IncludeImages: true,
RBACCheck: false,
Timeout: 10 * time.Second,
TestMode: true,
},
wantCollectorTypes: map[CollectorType]int{
CollectorTypeClusterInfo: 1,
Expand All @@ -138,6 +140,7 @@ func TestDiscoverer_DiscoverFoundational(t *testing.T) {
IncludeImages: false,
RBACCheck: false,
Timeout: 10 * time.Second,
TestMode: true,
},
wantMinCollectors: 8, // 2 cluster + 3*2 namespace collectors
wantErr: false,
Expand All @@ -149,6 +152,7 @@ func TestDiscoverer_DiscoverFoundational(t *testing.T) {
IncludeImages: false,
RBACCheck: false,
Timeout: 10 * time.Second,
TestMode: true,
},
wantMinCollectors: 2, // At least cluster collectors
wantErr: false,
Expand Down Expand Up @@ -409,7 +413,7 @@ func TestDiscoverer_generateFoundationalCollectors(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
collectors := discoverer.generateFoundationalCollectors(tt.namespaces, tt.opts)
collectors := discoverer.generateFoundationalCollectors(context.Background(), tt.namespaces, tt.opts)

if len(collectors) < tt.wantMinCount {
t.Errorf("generateFoundationalCollectors() returned %d collectors, want at least %d",
Expand Down
8 changes: 8 additions & 0 deletions pkg/collect/autodiscovery/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type DiscoveryOptions struct {
AugmentMode bool
// Timeout for discovery operations
Timeout time.Duration
// TestMode disables KOTS diagnostic collectors for cleaner testing
TestMode bool
}

// CollectorSpec represents a collector specification that can be converted to troubleshootv1beta2.Collect
Expand Down Expand Up @@ -65,6 +67,7 @@ const (
CollectorTypeClusterInfo CollectorType = "clusterInfo"
CollectorTypeClusterResources CollectorType = "clusterResources"
CollectorTypeImageFacts CollectorType = "imageFacts"
CollectorTypeData CollectorType = "data"
)

// CollectorSource indicates the origin of a collector
Expand All @@ -74,6 +77,7 @@ const (
SourceFoundational CollectorSource = "foundational"
SourceYAML CollectorSource = "yaml"
SourceAugmented CollectorSource = "augmented"
SourceKOTS CollectorSource = "kots"
)

// Resource represents a Kubernetes resource for RBAC checking
Expand Down Expand Up @@ -129,6 +133,10 @@ func (c CollectorSpec) ToTroubleshootCollect() (*troubleshootv1beta2.Collect, er
if data, ok := c.Spec.(*troubleshootv1beta2.Data); ok {
collect.Data = data
}
case CollectorTypeData:
if data, ok := c.Spec.(*troubleshootv1beta2.Data); ok {
collect.Data = data
}
// Add more cases as needed for other collector types
}

Expand Down
Loading
Loading