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
66 changes: 36 additions & 30 deletions cli/cmd/image_extraction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ func TestExtractImagesFromConfig_ChartWithRequiredValues_WithMatchingHelmChartMa
}

func TestExtractImagesFromConfig_ChartWithRequiredValues_NoHelmChartManifest(t *testing.T) {
// Test that extraction fails when manifests are not configured
// Test that discovery returns empty map when manifests are not configured (lenient)
// Validation happens at a higher level (in lint command)
chartPath := getAbsTestDataPath(t, filepath.Join("testdata", "image-extraction", "chart-with-required-values-test", "chart"))

config := &tools.Config{
Expand All @@ -101,18 +102,19 @@ func TestExtractImagesFromConfig_ChartWithRequiredValues_NoHelmChartManifest(t *
Manifests: []string{}, // No manifests configured
}

// Try to extract HelmChart manifests - should fail because manifests are required
_, err := lint2.DiscoverHelmChartManifests(config.Manifests)

// Should fail because manifests are required
if err == nil {
t.Fatal("Expected error when manifests not configured, got nil")
// Discovery is lenient - returns empty map instead of error
helmCharts, err := lint2.DiscoverHelmChartManifests(config.Manifests)
if err != nil {
t.Fatalf("DiscoverHelmChartManifests should not error on empty manifests: %v", err)
}

// Error should mention manifests configuration
if !strings.Contains(err.Error(), "no manifests configured") {
t.Errorf("Expected error about manifests configuration, got: %v", err)
// Should return empty map (lenient discovery)
if len(helmCharts) != 0 {
t.Errorf("Expected empty map, got %d HelmCharts", len(helmCharts))
}

// Note: Validation that charts need manifests happens in the lint command
// This tests only the discovery layer behavior
}


Expand Down Expand Up @@ -247,7 +249,8 @@ func TestExtractImagesFromConfig_NoCharts_ReturnsError(t *testing.T) {
}

func TestExtractImagesFromConfig_NoManifests_ReturnsError(t *testing.T) {
// Test that manifests are required for image extraction
// Test that discovery is lenient when manifests array is empty
// Validation happens at lint command level
chartPath := getAbsTestDataPath(t, filepath.Join("testdata", "image-extraction", "simple-chart-test", "chart"))

config := &tools.Config{
Expand All @@ -257,18 +260,19 @@ func TestExtractImagesFromConfig_NoManifests_ReturnsError(t *testing.T) {
Manifests: []string{}, // No manifests configured
}

// Try to extract HelmChart manifests - should fail because manifests are required
_, err := lint2.DiscoverHelmChartManifests(config.Manifests)

// Should fail because manifests are required
if err == nil {
t.Fatal("Expected error when manifests not configured, got nil")
// Discovery is lenient - returns empty map instead of error
helmCharts, err := lint2.DiscoverHelmChartManifests(config.Manifests)
if err != nil {
t.Fatalf("DiscoverHelmChartManifests should not error on empty manifests: %v", err)
}

// Error should mention manifests configuration
if !strings.Contains(err.Error(), "no manifests configured") {
t.Errorf("Expected error about manifests configuration, got: %v", err)
// Should return empty map (lenient discovery)
if len(helmCharts) != 0 {
t.Errorf("Expected empty map, got %d HelmCharts", len(helmCharts))
}

// Note: Validation that charts require manifests happens in runLint()
// This tests only the discovery layer behavior (which is now lenient)
}


Expand Down Expand Up @@ -316,7 +320,8 @@ func TestExtractImagesFromConfig_EmptyBuilder_FailsToRender(t *testing.T) {
}

func TestExtractImagesFromConfig_NoHelmChartInManifests_FailsDiscovery(t *testing.T) {
// Test that manifests with other K8s resources but no HelmChart kind fail discovery
// Test that discovery is lenient when manifests contain no HelmCharts
// Validation happens at lint command level
chartPath := getAbsTestDataPath(t, filepath.Join("testdata", "image-extraction", "no-helmchart-test", "chart"))
manifestGlob := getAbsTestDataPath(t, filepath.Join("testdata", "image-extraction", "no-helmchart-test", "manifests")) + "/*.yaml"

Expand All @@ -327,16 +332,17 @@ func TestExtractImagesFromConfig_NoHelmChartInManifests_FailsDiscovery(t *testin
Manifests: []string{manifestGlob},
}

// Try to extract HelmChart manifests - should fail because manifests don't contain HelmCharts
_, err := lint2.DiscoverHelmChartManifests(config.Manifests)

// Should fail because manifests are configured but contain no HelmCharts
if err == nil {
t.Fatal("Expected error when manifests configured but no HelmCharts found, got nil")
// Discovery is lenient - returns empty map when no HelmCharts found
helmCharts, err := lint2.DiscoverHelmChartManifests(config.Manifests)
if err != nil {
t.Fatalf("DiscoverHelmChartManifests should not error when no HelmCharts found: %v", err)
}

// Error should mention no HelmChart resources found
if !strings.Contains(err.Error(), "no HelmChart resources found") {
t.Errorf("Expected error about no HelmCharts, got: %v", err)
// Should return empty map (lenient discovery)
if len(helmCharts) != 0 {
t.Errorf("Expected empty map when no HelmCharts in manifests, got %d", len(helmCharts))
}

// Note: Validation that charts need HelmChart manifests happens in ValidateChartToHelmChartMapping()
// Discovery layer is lenient and allows mixed directories
}
82 changes: 50 additions & 32 deletions cli/cmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,18 @@ func extractAllPathsAndMetadata(ctx context.Context, config *tools.Config, verbo
return nil, err
}
result.Preflights = preflights
}

// Get HelmChart manifests (used for v1beta3 preflight validation)
// HelmChart manifests are optional - only required for v1beta3 preflights
// Discover HelmChart manifests ONCE (used by preflight rendering, support bundle analysis, image extraction, validation)
if len(config.Manifests) > 0 {
helmChartManifests, err := lint2.DiscoverHelmChartManifests(config.Manifests)
if err != nil {
// Only error if it's not a "no manifests found" error
if !strings.Contains(err.Error(), "no HelmChart resources found") {
return nil, err
}
// No manifests found is OK - set empty map
result.HelmChartManifests = make(map[string]*lint2.HelmChartManifest)
} else {
result.HelmChartManifests = helmChartManifests
return nil, fmt.Errorf("failed to discover HelmChart manifests: %w", err)
}
result.HelmChartManifests = helmChartManifests
} else {
// No manifests configured - return empty map (validation will check if needed)
result.HelmChartManifests = make(map[string]*lint2.HelmChartManifest)
}

// Discover support bundles
Expand All @@ -104,29 +102,10 @@ func extractAllPathsAndMetadata(ctx context.Context, config *tools.Config, verbo
return nil, err
}
result.SupportBundles = sbPaths

// Get HelmChart manifests if not already extracted
if result.HelmChartManifests == nil {
helmChartManifests, err := lint2.DiscoverHelmChartManifests(config.Manifests)
if err != nil {
// Support bundles don't require HelmChart manifests - only error if manifests are explicitly configured
// but fail to parse. If no HelmChart manifests exist, that's fine (return empty map).
if len(config.Manifests) > 0 {
// Check if error is "no HelmChart resources found" - that's acceptable
if err != nil && !strings.Contains(err.Error(), "no HelmChart resources found") {
return nil, err
}
}
// Set empty map so we don't try to extract again
result.HelmChartManifests = make(map[string]*lint2.HelmChartManifest)
} else {
result.HelmChartManifests = helmChartManifests
}
}
}

// Extract charts with metadata (ONLY for verbose mode)
if verbose && len(config.Charts) > 0 {
// Extract charts with metadata (needed for validation and image extraction)
if len(config.Charts) > 0 {
chartsWithMetadata, err := lint2.GetChartsWithMetadataFromConfig(config)
if err != nil {
return nil, err
Expand Down Expand Up @@ -197,11 +176,19 @@ func (r *runners) runLint(cmd *cobra.Command, args []string) error {
// Convert to manifests glob patterns for compatibility
config.Manifests = append(config.Manifests, sbPaths...)

// Auto-discover HelmChart manifests (needed for chart validation)
helmChartPaths, err := lint2.DiscoverHelmChartPaths(filepath.Join(".", "**"))
if err != nil {
return errors.Wrap(err, "failed to discover HelmChart manifests")
}
config.Manifests = append(config.Manifests, helmChartPaths...)

// Print what was discovered
fmt.Fprintf(r.w, "Discovered resources:\n")
fmt.Fprintf(r.w, " - %d Helm chart(s)\n", len(chartPaths))
fmt.Fprintf(r.w, " - %d Preflight spec(s)\n", len(preflightPaths))
fmt.Fprintf(r.w, " - %d Support Bundle spec(s)\n\n", len(sbPaths))
fmt.Fprintf(r.w, " - %d Support Bundle spec(s)\n", len(sbPaths))
fmt.Fprintf(r.w, " - %d HelmChart manifest(s)\n\n", len(helmChartPaths))
r.w.Flush()

// If nothing was found, exit early
Expand Down Expand Up @@ -231,6 +218,37 @@ func (r *runners) runLint(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "failed to extract paths and metadata")
}

// Validate chart-to-HelmChart mapping if charts are configured
if len(config.Charts) > 0 {
// Charts configured but no manifests - error early
if len(config.Manifests) == 0 {
return errors.New("charts are configured but no manifests paths provided\n\n" +
"HelmChart manifests (kind: HelmChart) are required for each chart.\n" +
"Add manifest paths to your .replicated config:\n\n" +
"manifests:\n" +
" - \"./manifests/**/*.yaml\"")
}

// Validate mapping using already-extracted metadata
validationResult, err := lint2.ValidateChartToHelmChartMapping(
extracted.ChartsWithMetadata, // Already populated in extraction
extracted.HelmChartManifests,
)
if err != nil {
// Hard error - stop before linting
return errors.Wrap(err, "chart validation failed")
}

// Display warnings (orphaned HelmChart manifests)
if r.outputFormat == "table" && len(validationResult.Warnings) > 0 {
for _, warning := range validationResult.Warnings {
fmt.Fprintf(r.w, "Warning: %s\n", warning)
}
fmt.Fprintln(r.w)
r.w.Flush()
}
}

// Extract and display images if verbose mode is enabled
if r.args.lintVerbose && len(extracted.ChartsWithMetadata) > 0 {
imageResults, err := r.extractImagesFromCharts(cmd.Context(), extracted.ChartsWithMetadata, extracted.HelmChartManifests)
Expand Down
Loading
Loading