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
778 changes: 673 additions & 105 deletions cli/cmd/lint.go

Large diffs are not rendered by default.

41 changes: 24 additions & 17 deletions cli/cmd/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,17 @@ repl-lint:
t.Fatalf("failed to load config: %v", err)
}

// Test extractAndDisplayImagesFromConfig
err = r.extractAndDisplayImagesFromConfig(context.Background(), config)
// Test extractImagesFromConfig
imageResults, err := r.extractImagesFromConfig(context.Background(), config)

if err != nil {
t.Errorf("unexpected error: %v", err)
}

if imageResults != nil {
r.displayImages(imageResults)
}

w.Flush()
output := buf.String()

Expand Down Expand Up @@ -179,7 +183,10 @@ func TestExtractAndDisplayImagesFromConfig_NoCharts(t *testing.T) {
}

// Should handle no charts gracefully
err = r.extractAndDisplayImagesFromConfig(context.Background(), config)
imageResults, err := r.extractImagesFromConfig(context.Background(), config)
if err == nil && imageResults != nil {
r.displayImages(imageResults)
}

// Should get error about no charts
if err == nil {
Expand Down Expand Up @@ -232,21 +239,17 @@ repl-lint:
t.Fatalf("failed to load config: %v", err)
}

// Should handle errors gracefully
err = r.extractAndDisplayImagesFromConfig(context.Background(), config)
// Should get an error for non-existent chart path (validated by GetChartPathsFromConfig)
_, err = r.extractImagesFromConfig(context.Background(), config)

// Error expected due to invalid chart path
// We expect an error because the chart path doesn't exist
if err == nil {
t.Error("expected error for invalid chart path")
t.Error("expected error for non-existent chart path")
}

w.Flush()
output := buf.String()

// Should still have tried to extract
if !strings.Contains(output, "Extracting images") {
t.Error("expected 'Extracting images' message even on error")
}
// Since we got an error, we don't display anything
// This is the correct behavior - fail fast on invalid paths
// The test verified that we correctly return an error for non-existent paths
}

func TestExtractAndDisplayImagesFromConfig_MultipleCharts(t *testing.T) {
Expand Down Expand Up @@ -346,7 +349,10 @@ repl-lint:
}

// Extract images
err = r.extractAndDisplayImagesFromConfig(context.Background(), config)
imageResults, err := r.extractImagesFromConfig(context.Background(), config)
if err == nil && imageResults != nil {
r.displayImages(imageResults)
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
Expand All @@ -361,7 +367,8 @@ repl-lint:
if !strings.Contains(output, "redis") {
t.Error("expected to find redis image from chart2")
}
if !strings.Contains(output, "2 chart(s)") {
t.Error("expected message about 2 charts")
// The new implementation shows total unique images instead of chart count
if !strings.Contains(output, "unique images") {
t.Error("expected message about unique images")
}
}
147 changes: 147 additions & 0 deletions cli/cmd/lint_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package cmd

import (
"time"

"github.com/replicatedhq/replicated/pkg/imageextract"
"github.com/replicatedhq/replicated/pkg/lint2"
)

// JSONLintOutput represents the complete JSON output structure for lint results
type JSONLintOutput struct {
Metadata LintMetadata `json:"metadata"`
HelmResults *HelmLintResults `json:"helm_results,omitempty"`
PreflightResults *PreflightLintResults `json:"preflight_results,omitempty"`
SupportBundleResults *SupportBundleLintResults `json:"support_bundle_results,omitempty"`
Summary LintSummary `json:"summary"`
Images *ImageExtractResults `json:"images,omitempty"` // Only if --verbose
}

// LintMetadata contains execution context and environment information
type LintMetadata struct {
Timestamp string `json:"timestamp"`
ConfigFile string `json:"config_file"`
HelmVersion string `json:"helm_version,omitempty"`
CLIVersion string `json:"cli_version"`
}

// HelmLintResults contains all Helm chart lint results
type HelmLintResults struct {
Enabled bool `json:"enabled"`
Charts []ChartLintResult `json:"charts"`
}

// ChartLintResult represents lint results for a single Helm chart
type ChartLintResult struct {
Path string `json:"path"`
Success bool `json:"success"`
Messages []LintMessage `json:"messages"`
Summary ResourceSummary `json:"summary"`
}

// PreflightLintResults contains all Preflight spec lint results
type PreflightLintResults struct {
Enabled bool `json:"enabled"`
Specs []PreflightLintResult `json:"specs"`
}

// PreflightLintResult represents lint results for a single Preflight spec
type PreflightLintResult struct {
Path string `json:"path"`
Success bool `json:"success"`
Messages []LintMessage `json:"messages"`
Summary ResourceSummary `json:"summary"`
}

// SupportBundleLintResults contains all Support Bundle spec lint results
type SupportBundleLintResults struct {
Enabled bool `json:"enabled"`
Specs []SupportBundleLintResult `json:"specs"`
}

// SupportBundleLintResult represents lint results for a single Support Bundle spec
type SupportBundleLintResult struct {
Path string `json:"path"`
Success bool `json:"success"`
Messages []LintMessage `json:"messages"`
Summary ResourceSummary `json:"summary"`
}

// LintMessage represents a single lint issue (wraps lint2.LintMessage with JSON tags)
type LintMessage struct {
Severity string `json:"severity"` // ERROR, WARNING, INFO
Path string `json:"path,omitempty"`
Message string `json:"message"`
}

// ResourceSummary contains counts by severity for a resource
type ResourceSummary struct {
ErrorCount int `json:"error_count"`
WarningCount int `json:"warning_count"`
InfoCount int `json:"info_count"`
}

// LintSummary contains overall statistics across all linted resources
type LintSummary struct {
TotalResources int `json:"total_resources"`
PassedResources int `json:"passed_resources"`
FailedResources int `json:"failed_resources"`
TotalErrors int `json:"total_errors"`
TotalWarnings int `json:"total_warnings"`
TotalInfo int `json:"total_info"`
OverallSuccess bool `json:"overall_success"`
}

// ImageExtractResults contains extracted image information
type ImageExtractResults struct {
Images []imageextract.ImageRef `json:"images"`
Warnings []imageextract.Warning `json:"warnings"`
Summary ImageSummary `json:"summary"`
}

// ImageSummary contains summary statistics for extracted images
type ImageSummary struct {
TotalImages int `json:"total_images"`
UniqueImages int `json:"unique_images"`
}

// Helper functions to convert between types

// convertLint2Messages converts lint2.LintMessage slice to LintMessage slice
func convertLint2Messages(messages []lint2.LintMessage) []LintMessage {
result := make([]LintMessage, len(messages))
for i, msg := range messages {
result[i] = LintMessage{
Severity: msg.Severity,
Path: msg.Path,
Message: msg.Message,
}
}
return result
}

// calculateResourceSummary calculates summary from lint messages
func calculateResourceSummary(messages []lint2.LintMessage) ResourceSummary {
summary := ResourceSummary{}
for _, msg := range messages {
switch msg.Severity {
case "ERROR":
summary.ErrorCount++
case "WARNING":
summary.WarningCount++
case "INFO":
summary.InfoCount++
}
}
return summary
}

// newLintMetadata creates metadata for the lint output
func newLintMetadata(configFile, helmVersion, cliVersion string) LintMetadata {
return LintMetadata{
Timestamp: time.Now().UTC().Format(time.RFC3339),
ConfigFile: configFile,
HelmVersion: helmVersion,
CLIVersion: cliVersion,
}
}
1 change: 1 addition & 0 deletions cli/cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type runnerArgs struct {
lintReleaseChart string
lintReleaseFailOn string
lintVerbose bool
lintOutputFile string
releaseOptional bool
releaseRequired bool
releaseNotes string
Expand Down
48 changes: 48 additions & 0 deletions cli/print/lint_results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package print

import (
"encoding/json"
"fmt"
"text/tabwriter"

"github.com/pkg/errors"
)

// LintOutput represents the complete lint output structure
// This is imported from cli/cmd but redefined here to avoid circular imports
type LintOutput interface{}

// LintResults formats and prints lint results in the specified format
func LintResults(format string, w *tabwriter.Writer, output interface{}) error {
switch format {
case "table":
// Table format is handled by the display functions in lint.go
// This function is only called for non-table formats
return errors.New("table format should be handled by display functions")
case "json":
return printLintResultsJSON(w, output)
default:
return errors.Errorf("invalid format: %s. Supported formats: json, table", format)
}
}

// printLintResultsJSON outputs lint results as formatted JSON
func printLintResultsJSON(w *tabwriter.Writer, output interface{}) error {
// Marshal to JSON with pretty printing
jsonBytes, err := json.MarshalIndent(output, "", " ")
if err != nil {
return errors.Wrap(err, "failed to marshal lint results to JSON")
}

// Write JSON to output
if _, err := fmt.Fprintln(w, string(jsonBytes)); err != nil {
return errors.Wrap(err, "failed to write JSON output")
}

// Flush the writer
if err := w.Flush(); err != nil {
return errors.Wrap(err, "failed to flush output")
}

return nil
}
10 changes: 3 additions & 7 deletions examples/.replicated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ promoteToChannelIds: []
promoteToChannelNames: []
charts: [
{
path: "../pkg/imageextract/testdata/helm-chart",
path: "./helm-chart",
chartVersion: "",
appVersion: "",
},
]
preflights: [
{
path: "./preflights/stuff",
path: "./preflights/**",
valuesPath: "./chart/something", # directory to corresponding helm chart
}
]
releaseLabel: "" ## some sort of semver pattern?
manifests: ["replicated/**/*.yaml"]
manifests: ["./support-bundles/**"]
repl-lint:
version: 1
linters:
Expand All @@ -26,10 +26,6 @@ repl-lint:
disabled: false
support-bundle:
disabled: false
embedded-cluster:
disabled: true
kots:
disabled: true
tools:
helm: "3.19.0"
preflight: "0.123.9"
Expand Down
Loading