Files
a8fb5210f4 Advanced analysis clean (#1868)
* created roadmap and yaml claude agent

* Update roadmap.md

* feat: Clean advanced analysis implementation - core agents, engine, artifacts

* Remove unrelated files - keep only advanced analysis implementation

* fix: Fix goroutine leak in hosted agent rate limiter

- Added stop channel and stopped flag to RateLimiter struct
- Modified replenishTokens to listen for stop signal and exit cleanly
- Added Stop() method to gracefully shutdown rate limiter
- Added Stop() method to HostedAgent to cleanup rate limiter on shutdown

Fixes cursor bot issue: Rate Limiter Goroutine Leak

* fix: Fix analyzer config and model validation bugs

Bug 1: Analyzer Config Missing File Path
- Added filePath to DeploymentStatus analyzer config in convertAnalyzerToSpec
- Sets namespace-specific path (cluster-resources/deployments/{namespace}.json)
- Falls back to generic path (cluster-resources/deployments.json) if no namespace
- Fixes LocalAgent.analyzeDeploymentStatus backward compatibility

Bug 2: HealthCheck Fails Model Validation
- Changed Ollama model validation from prefix match to exact match
- Prevents false positives where llama2:13b would match request for llama2:7b
- Ensures agent only reports healthy when exact model is available

Both fixes address cursor bot reported issues and maintain backward compatibility.

* fixing lint errors

* fixing lint errors

* adding CLI flags

* fix: resolve linting errors for CI

- Remove unnecessary nil check in host_kernel_configs.go (len() for nil slices is zero)
- Remove unnecessary fmt.Sprintf() calls in ceph.go for static strings
- Apply go fmt formatting fixes

Fixes failing lint CI check

* fix: resolve CI failures in build-test workflow and Ollama tests

1. Fix GitHub Actions workflow logic error:
   - Replace problematic contains() expression with explicit job result checks
   - Properly handle failure and cancelled states for each job
   - Prevents false positive failures in success summary job

2. Fix Ollama agent parseLLMResponse panics:
   - Add proper error handling for malformed JSON in LLM responses
   - Return error when JSON is found but invalid (instead of silent fallback)
   - Add error when no meaningful content can be parsed from response
   - Prevents nil pointer dereference in test assertions

Fixes failing build-test/success and build-test/test CI checks

* fix: resolve all CI failures and cursor bot issues

1. Fix disable-ollama flag logic bug:
   - Remove disable-ollama from advanced analysis trigger condition
   - Prevents unintended advanced analysis mode when no agents registered
   - Allows proper fallback to legacy analysis

2. Fix diff test consistency:
   - Update test expectations to match function behavior (lines with newlines)
   - Ensures consistency between streaming and non-streaming diff paths

3. Fix Ollama agent error handling:
   - Add proper error return for malformed JSON in LLM responses
   - Add meaningful content validation for markdown parsing
   - Prevents nil pointer panics in test assertions

4. Fix analysis engine mock agent:
   - Mock agent now processes and returns results for all provided analyzers
   - Fixes test expectation mismatch (expected 8 results, got 1)

Resolves all failing CI checks: lint, test, and success workflow logic

---------

Co-authored-by: Noah Campbell <noah.edward.campbell@gmail.com>
2025-09-30 11:44:46 -05:00

953 lines
28 KiB
Go

package artifacts
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/pkg/errors"
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
"github.com/replicatedhq/troubleshoot/pkg/constants"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"k8s.io/klog/v2"
)
// ArtifactManager handles generation and management of analysis artifacts
type ArtifactManager struct {
outputDir string
templateDir string
formatters map[string]ArtifactFormatter
generators map[string]ArtifactGenerator
validators map[string]ArtifactValidator
}
// ArtifactFormatter formats analysis results into different output formats
type ArtifactFormatter interface {
Format(ctx context.Context, result *analyzer.AnalysisResult) ([]byte, error)
ContentType() string
FileExtension() string
}
// ArtifactGenerator generates specific types of artifacts
type ArtifactGenerator interface {
Generate(ctx context.Context, result *analyzer.AnalysisResult) (*Artifact, error)
Name() string
Description() string
}
// ArtifactValidator validates artifact content
type ArtifactValidator interface {
Validate(ctx context.Context, data []byte) error
Schema() string
}
// Artifact represents a generated analysis artifact
type Artifact struct {
Name string `json:"name"`
Type string `json:"type"`
Format string `json:"format"`
ContentType string `json:"contentType"`
Size int64 `json:"size"`
Path string `json:"path"`
Metadata ArtifactMetadata `json:"metadata"`
Content []byte `json:"-"`
}
// ArtifactMetadata provides additional information about the artifact
type ArtifactMetadata struct {
CreatedAt time.Time `json:"createdAt"`
Generator string `json:"generator"`
Version string `json:"version"`
Summary ArtifactSummary `json:"summary"`
Tags []string `json:"tags,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Checksum string `json:"checksum,omitempty"`
}
// ArtifactSummary provides a high-level summary of the artifact contents
type ArtifactSummary struct {
TotalResults int `json:"totalResults"`
PassCount int `json:"passCount"`
WarnCount int `json:"warnCount"`
FailCount int `json:"failCount"`
ErrorCount int `json:"errorCount"`
Confidence float64 `json:"confidence,omitempty"`
AgentsUsed []string `json:"agentsUsed"`
TopCategories []string `json:"topCategories,omitempty"`
CriticalIssues int `json:"criticalIssues"`
}
// ArtifactOptions configures artifact generation
type ArtifactOptions struct {
OutputDir string
Formats []string // e.g., ["json", "html", "yaml"]
IncludeMetadata bool
IncludeRaw bool
IncludeCorrelations bool
CompressOutput bool
Templates map[string]string
CustomFields map[string]interface{}
}
// NewArtifactManager creates a new artifact manager
func NewArtifactManager(outputDir string) *ArtifactManager {
am := &ArtifactManager{
outputDir: outputDir,
formatters: make(map[string]ArtifactFormatter),
generators: make(map[string]ArtifactGenerator),
validators: make(map[string]ArtifactValidator),
}
// Register default formatters
am.registerDefaultFormatters()
// Register default generators
am.registerDefaultGenerators()
// Register default validators
am.registerDefaultValidators()
return am
}
// GenerateArtifacts generates all configured artifacts from analysis results
func (am *ArtifactManager) GenerateArtifacts(ctx context.Context, result *analyzer.AnalysisResult, opts *ArtifactOptions) ([]*Artifact, error) {
ctx, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, "ArtifactManager.GenerateArtifacts")
defer span.End()
if result == nil {
return nil, errors.New("analysis result cannot be nil")
}
if opts == nil {
opts = &ArtifactOptions{
Formats: []string{"json"},
IncludeMetadata: true,
IncludeCorrelations: true,
}
}
if opts.OutputDir != "" {
am.outputDir = opts.OutputDir
}
// Ensure output directory exists
if err := os.MkdirAll(am.outputDir, 0755); err != nil {
return nil, errors.Wrap(err, "failed to create output directory")
}
var artifacts []*Artifact
// Generate primary analysis.json artifact
analysisArtifact, err := am.generateAnalysisJSON(ctx, result, opts)
if err != nil {
klog.Errorf("Failed to generate analysis.json: %v", err)
} else {
artifacts = append(artifacts, analysisArtifact)
}
// Generate format-specific artifacts
for _, format := range opts.Formats {
if format == "json" {
continue // Already generated above
}
artifact, err := am.generateFormatArtifact(ctx, result, format, opts)
if err != nil {
klog.Errorf("Failed to generate %s artifact: %v", format, err)
continue
}
if artifact != nil {
artifacts = append(artifacts, artifact)
}
}
// Generate supplementary artifacts
if supplementaryArtifacts, err := am.generateSupplementaryArtifacts(ctx, result, opts); err == nil {
artifacts = append(artifacts, supplementaryArtifacts...)
}
// Generate remediation guide if requested
if opts.IncludeMetadata {
if remediationArtifact, err := am.generateRemediationGuide(ctx, result, opts); err == nil {
artifacts = append(artifacts, remediationArtifact)
}
}
span.SetAttributes(
attribute.Int("total_artifacts", len(artifacts)),
attribute.StringSlice("formats", opts.Formats),
)
return artifacts, nil
}
// generateAnalysisJSON creates the primary analysis.json artifact
func (am *ArtifactManager) generateAnalysisJSON(ctx context.Context, result *analyzer.AnalysisResult, opts *ArtifactOptions) (*Artifact, error) {
ctx, span := otel.Tracer(constants.LIB_TRACER_NAME).Start(ctx, "ArtifactManager.generateAnalysisJSON")
defer span.End()
// Enhance the analysis result with additional metadata for the artifact
enhancedResult := am.enhanceAnalysisResult(result, opts)
// Format as JSON
data, err := json.MarshalIndent(enhancedResult, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal analysis result")
}
// Validate JSON structure
if validator, exists := am.validators["json"]; exists {
if err := validator.Validate(ctx, data); err != nil {
klog.Warningf("Analysis JSON validation failed: %v", err)
}
}
// Create artifact
artifact := &Artifact{
Name: "analysis.json",
Type: "analysis",
Format: "json",
ContentType: "application/json",
Size: int64(len(data)),
Content: data,
Metadata: ArtifactMetadata{
CreatedAt: time.Now(),
Generator: "troubleshoot-analysis-engine",
Version: "1.0.0",
Summary: am.generateSummary(result),
Tags: []string{"analysis", "primary"},
},
}
// Write to file
artifactPath := filepath.Join(am.outputDir, artifact.Name)
if err := am.writeArtifact(artifact, artifactPath); err != nil {
return nil, errors.Wrap(err, "failed to write analysis.json")
}
artifact.Path = artifactPath
span.SetAttributes(
attribute.String("artifact_name", artifact.Name),
attribute.Int64("artifact_size", artifact.Size),
)
return artifact, nil
}
// generateFormatArtifact creates artifacts in specific formats
func (am *ArtifactManager) generateFormatArtifact(ctx context.Context, result *analyzer.AnalysisResult, format string, opts *ArtifactOptions) (*Artifact, error) {
formatter, exists := am.formatters[format]
if !exists {
return nil, errors.Errorf("unsupported format: %s", format)
}
data, err := formatter.Format(ctx, result)
if err != nil {
return nil, errors.Wrapf(err, "failed to format as %s", format)
}
filename := fmt.Sprintf("analysis.%s", formatter.FileExtension())
artifact := &Artifact{
Name: filename,
Type: "analysis",
Format: format,
ContentType: formatter.ContentType(),
Size: int64(len(data)),
Content: data,
Metadata: ArtifactMetadata{
CreatedAt: time.Now(),
Generator: "troubleshoot-analysis-engine",
Version: "1.0.0",
Summary: am.generateSummary(result),
Tags: []string{"analysis", format},
},
}
// Write to file
artifactPath := filepath.Join(am.outputDir, filename)
if err := am.writeArtifact(artifact, artifactPath); err != nil {
return nil, errors.Wrapf(err, "failed to write %s artifact", format)
}
artifact.Path = artifactPath
return artifact, nil
}
// generateSupplementaryArtifacts creates additional helpful artifacts
func (am *ArtifactManager) generateSupplementaryArtifacts(ctx context.Context, result *analyzer.AnalysisResult, opts *ArtifactOptions) ([]*Artifact, error) {
var artifacts []*Artifact
// Generate summary artifact
summaryArtifact, err := am.generateSummaryArtifact(ctx, result, opts)
if err == nil {
artifacts = append(artifacts, summaryArtifact)
}
// Generate insights artifact
insightsArtifact, err := am.generateInsightsArtifact(ctx, result, opts)
if err == nil {
artifacts = append(artifacts, insightsArtifact)
}
// Generate correlation matrix if requested
if opts.IncludeCorrelations {
correlationArtifact, err := am.generateCorrelationArtifact(ctx, result, opts)
if err == nil {
artifacts = append(artifacts, correlationArtifact)
}
}
return artifacts, nil
}
// generateSummaryArtifact creates a high-level summary artifact
func (am *ArtifactManager) generateSummaryArtifact(ctx context.Context, result *analyzer.AnalysisResult, opts *ArtifactOptions) (*Artifact, error) {
summary := struct {
Overview analyzer.AnalysisSummary `json:"overview"`
TopIssues []*analyzer.AnalyzerResult `json:"topIssues"`
Categories map[string]int `json:"categories"`
Agents []analyzer.AgentMetadata `json:"agents"`
Recommendations []string `json:"recommendations"`
GeneratedAt time.Time `json:"generatedAt"`
}{
Overview: result.Summary,
Categories: am.categorizeResults(result.Results),
Agents: result.Metadata.Agents,
TopIssues: am.getTopIssues(result.Results, 10),
Recommendations: am.generateRecommendations(result),
GeneratedAt: time.Now(),
}
data, err := json.MarshalIndent(summary, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal summary")
}
artifact := &Artifact{
Name: "summary.json",
Type: "summary",
Format: "json",
ContentType: "application/json",
Size: int64(len(data)),
Content: data,
Metadata: ArtifactMetadata{
CreatedAt: time.Now(),
Generator: "troubleshoot-analysis-engine",
Version: "1.0.0",
Summary: am.generateSummary(result),
Tags: []string{"summary", "overview"},
},
}
artifactPath := filepath.Join(am.outputDir, artifact.Name)
if err := am.writeArtifact(artifact, artifactPath); err != nil {
return nil, errors.Wrap(err, "failed to write summary artifact")
}
artifact.Path = artifactPath
return artifact, nil
}
// generateInsightsArtifact creates an insights and correlation artifact
func (am *ArtifactManager) generateInsightsArtifact(ctx context.Context, result *analyzer.AnalysisResult, opts *ArtifactOptions) (*Artifact, error) {
insights := struct {
KeyFindings []string `json:"keyFindings"`
Patterns []Pattern `json:"patterns"`
Correlations []analyzer.Correlation `json:"correlations"`
Trends []Trend `json:"trends"`
Recommendations []RemediationInsight `json:"recommendations"`
GeneratedAt time.Time `json:"generatedAt"`
}{
KeyFindings: am.extractKeyFindings(result.Results),
Patterns: am.identifyPatterns(result.Results),
Correlations: result.Metadata.Correlations,
Trends: am.analyzeTrends(result.Results),
Recommendations: am.generateRemediationInsights(result.Remediation),
GeneratedAt: time.Now(),
}
data, err := json.MarshalIndent(insights, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal insights")
}
artifact := &Artifact{
Name: "insights.json",
Type: "insights",
Format: "json",
ContentType: "application/json",
Size: int64(len(data)),
Content: data,
Metadata: ArtifactMetadata{
CreatedAt: time.Now(),
Generator: "troubleshoot-analysis-engine",
Version: "1.0.0",
Summary: am.generateSummary(result),
Tags: []string{"insights", "patterns", "correlations"},
},
}
artifactPath := filepath.Join(am.outputDir, artifact.Name)
if err := am.writeArtifact(artifact, artifactPath); err != nil {
return nil, errors.Wrap(err, "failed to write insights artifact")
}
artifact.Path = artifactPath
return artifact, nil
}
// generateCorrelationArtifact creates a correlation matrix artifact
func (am *ArtifactManager) generateCorrelationArtifact(ctx context.Context, result *analyzer.AnalysisResult, opts *ArtifactOptions) (*Artifact, error) {
correlations := am.buildCorrelationMatrix(result.Results)
data, err := json.MarshalIndent(correlations, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal correlations")
}
artifact := &Artifact{
Name: "correlations.json",
Type: "correlations",
Format: "json",
ContentType: "application/json",
Size: int64(len(data)),
Content: data,
Metadata: ArtifactMetadata{
CreatedAt: time.Now(),
Generator: "troubleshoot-analysis-engine",
Version: "1.0.0",
Summary: am.generateSummary(result),
Tags: []string{"correlations", "relationships"},
},
}
artifactPath := filepath.Join(am.outputDir, artifact.Name)
if err := am.writeArtifact(artifact, artifactPath); err != nil {
return nil, errors.Wrap(err, "failed to write correlations artifact")
}
artifact.Path = artifactPath
return artifact, nil
}
// generateRemediationGuide creates a detailed remediation guide
func (am *ArtifactManager) generateRemediationGuide(ctx context.Context, result *analyzer.AnalysisResult, opts *ArtifactOptions) (*Artifact, error) {
guide := struct {
Summary string `json:"summary"`
PriorityActions []analyzer.RemediationStep `json:"priorityActions"`
Categories map[string][]analyzer.RemediationStep `json:"categories"`
Prerequisites []string `json:"prerequisites"`
Automation AutomationGuide `json:"automation"`
GeneratedAt time.Time `json:"generatedAt"`
}{
Summary: am.generateRemediationSummary(result.Remediation),
PriorityActions: am.getPriorityActions(result.Remediation, 5),
Categories: am.categorizeRemediationSteps(result.Remediation),
Prerequisites: am.identifyPrerequisites(result.Remediation),
Automation: am.generateAutomationGuide(result.Remediation),
GeneratedAt: time.Now(),
}
data, err := json.MarshalIndent(guide, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal remediation guide")
}
artifact := &Artifact{
Name: "remediation-guide.json",
Type: "remediation",
Format: "json",
ContentType: "application/json",
Size: int64(len(data)),
Content: data,
Metadata: ArtifactMetadata{
CreatedAt: time.Now(),
Generator: "troubleshoot-analysis-engine",
Version: "1.0.0",
Summary: am.generateSummary(result),
Tags: []string{"remediation", "guide", "actions"},
},
}
artifactPath := filepath.Join(am.outputDir, artifact.Name)
if err := am.writeArtifact(artifact, artifactPath); err != nil {
return nil, errors.Wrap(err, "failed to write remediation guide")
}
artifact.Path = artifactPath
return artifact, nil
}
// Helper types for insights and patterns
type Pattern struct {
Type string `json:"type"`
Description string `json:"description"`
Count int `json:"count"`
Confidence float64 `json:"confidence"`
Examples []string `json:"examples,omitempty"`
}
type Trend struct {
Category string `json:"category"`
Direction string `json:"direction"` // "improving", "degrading", "stable"
Confidence float64 `json:"confidence"`
Description string `json:"description"`
}
type RemediationInsight struct {
Category string `json:"category"`
Priority int `json:"priority"`
Impact string `json:"impact"`
Effort string `json:"effort"`
Description string `json:"description"`
}
type AutomationGuide struct {
AutomatableSteps int `json:"automatableSteps"`
ManualSteps int `json:"manualSteps"`
Scripts []Script `json:"scripts,omitempty"`
}
type Script struct {
Name string `json:"name"`
Description string `json:"description"`
Language string `json:"language"`
Content string `json:"content"`
Prerequisites []string `json:"prerequisites,omitempty"`
}
// Helper methods for analysis and insights
func (am *ArtifactManager) enhanceAnalysisResult(result *analyzer.AnalysisResult, opts *ArtifactOptions) *analyzer.AnalysisResult {
// Create a copy to avoid modifying the original
enhanced := &analyzer.AnalysisResult{
Results: result.Results,
Remediation: result.Remediation,
Summary: result.Summary,
Metadata: result.Metadata,
Errors: result.Errors,
}
// Add artifact-specific metadata
enhanced.Metadata.Timestamp = time.Now()
// Add custom fields if provided
if opts.CustomFields != nil {
// Note: In a real implementation, you'd need to extend the struct
// or use a more flexible data structure
}
return enhanced
}
func (am *ArtifactManager) generateSummary(result *analyzer.AnalysisResult) ArtifactSummary {
summary := ArtifactSummary{
TotalResults: len(result.Results),
PassCount: result.Summary.PassCount,
WarnCount: result.Summary.WarnCount,
FailCount: result.Summary.FailCount,
ErrorCount: result.Summary.ErrorCount,
Confidence: result.Summary.Confidence,
AgentsUsed: result.Summary.AgentsUsed,
TopCategories: am.getTopCategories(result.Results, 5),
CriticalIssues: am.countCriticalIssues(result.Results),
}
return summary
}
func (am *ArtifactManager) categorizeResults(results []*analyzer.AnalyzerResult) map[string]int {
categories := make(map[string]int)
for _, result := range results {
if result.Category != "" {
categories[result.Category]++
}
}
return categories
}
func (am *ArtifactManager) getTopIssues(results []*analyzer.AnalyzerResult, limit int) []*analyzer.AnalyzerResult {
// Filter for failed results
var failedResults []*analyzer.AnalyzerResult
for _, result := range results {
if result.IsFail {
failedResults = append(failedResults, result)
}
}
// Sort by confidence (higher first)
sort.Slice(failedResults, func(i, j int) bool {
return failedResults[i].Confidence > failedResults[j].Confidence
})
// Return top N
if len(failedResults) > limit {
return failedResults[:limit]
}
return failedResults
}
func (am *ArtifactManager) getTopCategories(results []*analyzer.AnalyzerResult, limit int) []string {
categories := am.categorizeResults(results)
// Convert to slice for sorting
type categoryCount struct {
name string
count int
}
var categoryCounts []categoryCount
for name, count := range categories {
categoryCounts = append(categoryCounts, categoryCount{name, count})
}
// Sort by count (descending)
sort.Slice(categoryCounts, func(i, j int) bool {
return categoryCounts[i].count > categoryCounts[j].count
})
// Extract top category names
var topCategories []string
for i, cc := range categoryCounts {
if i >= limit {
break
}
topCategories = append(topCategories, cc.name)
}
return topCategories
}
func (am *ArtifactManager) countCriticalIssues(results []*analyzer.AnalyzerResult) int {
count := 0
for _, result := range results {
if result.IsFail && strings.Contains(strings.ToLower(result.Severity), "critical") {
count++
}
}
return count
}
func (am *ArtifactManager) generateRecommendations(result *analyzer.AnalysisResult) []string {
var recommendations []string
// Generate high-level recommendations based on analysis results
if result.Summary.FailCount > 0 {
recommendations = append(recommendations,
fmt.Sprintf("Address %d failed checks to improve system health", result.Summary.FailCount))
}
if result.Summary.WarnCount > result.Summary.PassCount {
recommendations = append(recommendations,
"Review warning conditions to prevent potential issues")
}
// Category-specific recommendations
categories := am.categorizeResults(result.Results)
for category, count := range categories {
if count >= 5 {
recommendations = append(recommendations,
fmt.Sprintf("Focus attention on %s category (%d issues)", category, count))
}
}
return recommendations
}
func (am *ArtifactManager) extractKeyFindings(results []*analyzer.AnalyzerResult) []string {
var findings []string
for _, result := range results {
if result.IsFail && result.Confidence > 0.8 {
findings = append(findings, result.Message)
}
}
// Limit to most important findings
if len(findings) > 10 {
findings = findings[:10]
}
return findings
}
func (am *ArtifactManager) identifyPatterns(results []*analyzer.AnalyzerResult) []Pattern {
var patterns []Pattern
// Pattern: Multiple failures in same category
categoryFailures := make(map[string]int)
for _, result := range results {
if result.IsFail && result.Category != "" {
categoryFailures[result.Category]++
}
}
for category, count := range categoryFailures {
if count >= 3 {
patterns = append(patterns, Pattern{
Type: "category-failure-cluster",
Description: fmt.Sprintf("Multiple failures in %s category", category),
Count: count,
Confidence: 0.8,
})
}
}
return patterns
}
func (am *ArtifactManager) analyzeTrends(results []*analyzer.AnalyzerResult) []Trend {
// Placeholder for trend analysis
// In a real implementation, this would compare with historical data
return []Trend{
{
Category: "overall",
Direction: "stable",
Confidence: 0.7,
Description: "System health appears stable",
},
}
}
func (am *ArtifactManager) buildCorrelationMatrix(results []*analyzer.AnalyzerResult) map[string]interface{} {
// Placeholder for correlation analysis
correlations := make(map[string]interface{})
// Simple correlation example: failures in same namespace
namespaceFailures := make(map[string][]string)
for _, result := range results {
if result.IsFail && result.InvolvedObject != nil {
ns := result.InvolvedObject.Namespace
if ns != "" {
namespaceFailures[ns] = append(namespaceFailures[ns], result.Title)
}
}
}
correlations["namespace_failures"] = namespaceFailures
return correlations
}
func (am *ArtifactManager) generateRemediationSummary(steps []analyzer.RemediationStep) string {
if len(steps) == 0 {
return "No remediation steps required"
}
automatable := 0
highPriority := 0
for _, step := range steps {
if step.IsAutomatable {
automatable++
}
if step.Priority >= 8 {
highPriority++
}
}
return fmt.Sprintf("Found %d remediation steps: %d high priority, %d automatable",
len(steps), highPriority, automatable)
}
func (am *ArtifactManager) getPriorityActions(steps []analyzer.RemediationStep, limit int) []analyzer.RemediationStep {
// Sort by priority (higher first)
sorted := make([]analyzer.RemediationStep, len(steps))
copy(sorted, steps)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Priority > sorted[j].Priority
})
if len(sorted) > limit {
return sorted[:limit]
}
return sorted
}
func (am *ArtifactManager) categorizeRemediationSteps(steps []analyzer.RemediationStep) map[string][]analyzer.RemediationStep {
categories := make(map[string][]analyzer.RemediationStep)
for _, step := range steps {
category := step.Category
if category == "" {
category = "general"
}
categories[category] = append(categories[category], step)
}
return categories
}
func (am *ArtifactManager) identifyPrerequisites(steps []analyzer.RemediationStep) []string {
var prerequisites []string
// Common prerequisites based on remediation categories
categoryPrereqs := map[string]string{
"infrastructure": "Admin access to cluster nodes",
"networking": "Network configuration permissions",
"storage": "Storage admin permissions",
"security": "Security policy modification rights",
}
categories := make(map[string]bool)
for _, step := range steps {
if step.Category != "" {
categories[step.Category] = true
}
}
for category := range categories {
if prereq, exists := categoryPrereqs[category]; exists {
prerequisites = append(prerequisites, prereq)
}
}
return prerequisites
}
func (am *ArtifactManager) generateAutomationGuide(steps []analyzer.RemediationStep) AutomationGuide {
automatable := 0
manual := 0
for _, step := range steps {
if step.IsAutomatable {
automatable++
} else {
manual++
}
}
// Generate sample scripts for automatable steps
var scripts []Script
if automatable > 0 {
scripts = append(scripts, Script{
Name: "automated-remediation.sh",
Description: "Automated remediation script",
Language: "bash",
Content: "#!/bin/bash\n# Automated remediation steps\necho 'Running automated fixes...'\n",
Prerequisites: []string{"kubectl", "admin access"},
})
}
return AutomationGuide{
AutomatableSteps: automatable,
ManualSteps: manual,
Scripts: scripts,
}
}
func (am *ArtifactManager) generateRemediationInsights(steps []analyzer.RemediationStep) []RemediationInsight {
var insights []RemediationInsight
// Group by category and generate insights
categories := am.categorizeRemediationSteps(steps)
for category, categorySteps := range categories {
highPriorityCount := 0
automatableCount := 0
for _, step := range categorySteps {
if step.Priority >= 8 {
highPriorityCount++
}
if step.IsAutomatable {
automatableCount++
}
}
var impact, effort string
if highPriorityCount > len(categorySteps)/2 {
impact = "high"
} else {
impact = "medium"
}
if automatableCount > len(categorySteps)/2 {
effort = "low"
} else {
effort = "medium"
}
insights = append(insights, RemediationInsight{
Category: category,
Priority: highPriorityCount,
Impact: impact,
Effort: effort,
Description: fmt.Sprintf("%d steps in %s category, %d high priority",
len(categorySteps), category, highPriorityCount),
})
}
return insights
}
func (am *ArtifactManager) writeArtifact(artifact *Artifact, path string) error {
file, err := os.Create(path)
if err != nil {
return errors.Wrap(err, "failed to create artifact file")
}
defer file.Close()
_, err = file.Write(artifact.Content)
if err != nil {
return errors.Wrap(err, "failed to write artifact content")
}
return nil
}
// Registration methods for formatters, generators, and validators
func (am *ArtifactManager) registerDefaultFormatters() {
am.formatters["json"] = &JSONFormatter{}
am.formatters["yaml"] = &YAMLFormatter{}
am.formatters["html"] = &HTMLFormatter{}
am.formatters["text"] = &TextFormatter{}
}
func (am *ArtifactManager) registerDefaultGenerators() {
// Register specific artifact generators
am.generators["summary"] = &SummaryGenerator{}
am.generators["insights"] = &InsightsGenerator{}
am.generators["remediation"] = &RemediationGenerator{}
}
func (am *ArtifactManager) registerDefaultValidators() {
am.validators["json"] = &JSONValidator{}
am.validators["yaml"] = &YAMLValidator{}
}
// RegisterFormatter registers a custom formatter
func (am *ArtifactManager) RegisterFormatter(name string, formatter ArtifactFormatter) {
am.formatters[name] = formatter
}
// RegisterGenerator registers a custom generator
func (am *ArtifactManager) RegisterGenerator(name string, generator ArtifactGenerator) {
am.generators[name] = generator
}
// RegisterValidator registers a custom validator
func (am *ArtifactManager) RegisterValidator(name string, validator ArtifactValidator) {
am.validators[name] = validator
}
// WriteTo writes an artifact to a specific writer
func (am *ArtifactManager) WriteTo(artifact *Artifact, writer io.Writer) error {
_, err := writer.Write(artifact.Content)
return err
}