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
107 changes: 32 additions & 75 deletions cli/cmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ func (r *runners) runLint(cmd *cobra.Command, args []string) error {
if err != nil {
return errors.Wrap(err, "failed to discover preflight specs")
}
// Add preflights without valuesPath - they'll be linted using simple mode
for _, preflightPath := range preflightPaths {
config.Preflights = append(config.Preflights, tools.PreflightConfig{Path: preflightPath})
}
Expand Down Expand Up @@ -315,81 +314,46 @@ func (r *runners) lintPreflightSpecs(cmd *cobra.Command, config *tools.Config) (
}
}

results := &PreflightLintResults{
Enabled: true,
Specs: make([]PreflightLintResult, 0),
// Discover HelmChart manifests once (needed for templated preflights)
helmChartManifests, err := lint2.GetHelmChartManifestsFromConfig(config)
if err != nil {
return nil, errors.Wrap(err, "failed to discover HelmChart manifests")
}

// Check if preflights have valuesPath configured
// If any preflight has valuesPath, use full templated linting
// Otherwise, use simple linting without chart context
hasValuesPath := false
for _, pf := range config.Preflights {
if pf.ValuesPath != "" {
hasValuesPath = true
break
}
// Get preflight paths with values information
preflights, err := lint2.GetPreflightWithValuesFromConfig(config)
if err != nil {
return nil, errors.Wrap(err, "failed to expand preflight paths")
}

if hasValuesPath {
// Full templated linting path (requires HelmChart manifests)
helmChartManifests, err := lint2.GetHelmChartManifestsFromConfig(config)
if err != nil {
return nil, errors.Wrap(err, "failed to discover HelmChart manifests")
}

preflights, err := lint2.GetPreflightWithValuesFromConfig(config)
if err != nil {
return nil, errors.Wrap(err, "failed to expand preflight paths")
}

for _, pf := range preflights {
lint2Result, err := lint2.LintPreflight(
cmd.Context(),
pf.SpecPath,
pf.ValuesPath,
pf.ChartName,
pf.ChartVersion,
helmChartManifests,
preflightVersion,
)
if err != nil {
return nil, errors.Wrapf(err, "failed to lint preflight spec: %s", pf.SpecPath)
}

preflightResult := PreflightLintResult{
Path: pf.SpecPath,
Success: lint2Result.Success,
Messages: convertLint2Messages(lint2Result.Messages),
Summary: calculateResourceSummary(lint2Result.Messages),
}
results.Specs = append(results.Specs, preflightResult)
}
} else {
// Simple linting path (no chart context needed)
preflightPaths, err := lint2.GetPreflightPathsFromConfig(config)
results := &PreflightLintResults{
Enabled: true,
Specs: make([]PreflightLintResult, 0, len(preflights)),
}

// Lint all preflight specs and collect results
for _, pf := range preflights {
lint2Result, err := lint2.LintPreflight(
cmd.Context(),
pf.SpecPath,
pf.ValuesPath,
pf.ChartName,
pf.ChartVersion,
helmChartManifests,
preflightVersion,
)
if err != nil {
return nil, errors.Wrap(err, "failed to expand preflight paths")
return nil, errors.Wrapf(err, "failed to lint preflight spec: %s", pf.SpecPath)
}

for _, specPath := range preflightPaths {
lint2Result, err := lint2.LintPreflightSimple(
cmd.Context(),
specPath,
preflightVersion,
)
if err != nil {
return nil, errors.Wrapf(err, "failed to lint preflight spec: %s", specPath)
}

preflightResult := PreflightLintResult{
Path: specPath,
Success: lint2Result.Success,
Messages: convertLint2Messages(lint2Result.Messages),
Summary: calculateResourceSummary(lint2Result.Messages),
}
results.Specs = append(results.Specs, preflightResult)
// Convert to structured format
preflightResult := PreflightLintResult{
Path: pf.SpecPath,
Success: lint2Result.Success,
Messages: convertLint2Messages(lint2Result.Messages),
Summary: calculateResourceSummary(lint2Result.Messages),
}
results.Specs = append(results.Specs, preflightResult)
}

// Display results in table format (only if table output)
Expand Down Expand Up @@ -571,13 +535,6 @@ func (r *runners) extractImagesFromConfig(ctx context.Context, config *tools.Con
return nil, errors.Wrap(err, "failed to discover HelmChart manifests")
}

// If manifests were configured but no HelmCharts were found, return an error
// This typically means the manifests directory exists but contains no HelmChart resources,
// which is problematic when extracting images from charts that may need builder values
if len(config.Manifests) > 0 && len(helmChartManifests) == 0 {
return nil, errors.New("no HelmChart resources found in configured manifests")
}

// Collect all images from all charts
imageMap := make(map[string]imageextract.ImageRef) // For deduplication
var allWarnings []imageextract.Warning
Expand Down
13 changes: 8 additions & 5 deletions pkg/lint2/helmchart.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,14 @@ func DiscoverHelmChartManifests(manifestGlobs []string) (map[string]*HelmChartMa
}
}

// Return helmCharts map (may be empty)
// HelmChart manifests are optional - they're only needed if:
// 1. Preflights use HelmChart builder values for templating
// 2. Image extraction needs builder values from HelmChart manifests
// In auto-discovery mode, manifests may only contain SupportBundle specs
// Fail-fast if no HelmCharts found
// Both preflight linting and image extraction require HelmCharts when manifests are configured
if len(helmCharts) == 0 {
return nil, fmt.Errorf("no HelmChart resources found in manifests\n"+
"At least one HelmChart manifest is required when manifests are configured.\n"+
"Checked patterns: %v", manifestGlobs)
}

return helmCharts, nil
}

Expand Down
30 changes: 18 additions & 12 deletions pkg/lint2/helmchart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,17 @@ spec:
pattern := filepath.Join(tmpDir, "*.yaml")
manifests, err := DiscoverHelmChartManifests([]string{pattern})

// Invalid HelmCharts are skipped, returning empty map (not an error)
// HelmCharts are optional - only needed if preflights use builder values
if err != nil {
t.Fatalf("unexpected error when invalid HelmCharts are skipped: %v", err)
// With fail-fast validation, we expect an error when no valid HelmCharts found
if err == nil {
t.Fatal("expected error when all HelmCharts are invalid (fail-fast), got nil")
}

if !contains(err.Error(), "no HelmChart resources found") {
t.Errorf("expected error about no HelmCharts found, got: %v", err)
}

if len(manifests) != 0 {
t.Errorf("expected empty manifests map when all HelmCharts are invalid, got %d manifests", len(manifests))
if manifests != nil {
t.Errorf("expected nil manifests on error, got %d manifests", len(manifests))
}
})

Expand All @@ -342,14 +345,17 @@ spec:
pattern := filepath.Join(tmpDir, "*.yaml")
manifests, err := DiscoverHelmChartManifests([]string{pattern})

// Invalid YAML files are skipped, returning empty map (not an error)
// HelmCharts are optional - only needed if preflights use builder values
if err != nil {
t.Fatalf("unexpected error when invalid YAML is skipped: %v", err)
// With fail-fast validation, we expect an error when no valid HelmCharts found
if err == nil {
t.Fatal("expected error when all files are invalid (fail-fast), got nil")
}

if !contains(err.Error(), "no HelmChart resources found") {
t.Errorf("expected error about no HelmCharts found, got: %v", err)
}

if len(manifests) != 0 {
t.Errorf("expected empty manifests map when all files are invalid, got %d manifests", len(manifests))
if manifests != nil {
t.Errorf("expected nil manifests on error, got %d manifests", len(manifests))
}
})

Expand Down
43 changes: 0 additions & 43 deletions pkg/lint2/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,49 +197,6 @@ func LintPreflight(
}, nil
}

// LintPreflightSimple executes preflight lint without template rendering.
// This is suitable for non-templated preflights or when chart context is unavailable.
// The preflight CLI tool will validate YAML syntax, required fields, and structure.
func LintPreflightSimple(
ctx context.Context,
specPath string,
preflightVersion string,
) (*LintResult, error) {
// Use resolver to get preflight binary
resolver := tools.NewResolver()
preflightPath, err := resolver.Resolve(ctx, tools.ToolPreflight, preflightVersion)
if err != nil {
return nil, fmt.Errorf("resolving preflight: %w", err)
}

// Execute preflight lint with JSON output for easier parsing
cmd := exec.CommandContext(ctx, preflightPath, "lint", "--format", "json", specPath)
output, err := cmd.CombinedOutput()

// preflight lint returns exit code 2 if there are errors,
// but we still want to parse and display the output
outputStr := string(output)

// Parse the JSON output
messages, parseErr := ParsePreflightOutput(outputStr)
if parseErr != nil {
// If we can't parse the output, return both the parse error and original error
if err != nil {
return nil, fmt.Errorf("preflight lint failed and output parsing failed: %w\nParse error: %v\nOutput: %s", err, parseErr, outputStr)
}
return nil, fmt.Errorf("failed to parse preflight lint output: %w\nOutput: %s", parseErr, outputStr)
}

// Determine success based on exit code
// Exit code 0 = no errors, exit code 2 = validation errors
success := err == nil

return &LintResult{
Success: success,
Messages: messages,
}, nil
}

// ParsePreflightOutput parses preflight lint JSON output into structured messages.
// Uses the common troubleshoot.sh JSON parsing infrastructure.
func ParsePreflightOutput(output string) ([]LintMessage, error) {
Expand Down