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
2 changes: 1 addition & 1 deletion chartvalidator/checker/appsets.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,4 @@ func str(v any) string {
return s
}
return fmt.Sprintf("%v", v)
}
}
97 changes: 50 additions & 47 deletions chartvalidator/checker/engine_app_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"path/filepath"
"sync"
)

Expand All @@ -21,75 +22,84 @@ type AppCheckerEngine struct {
resultChan chan AppCheckResult
errorChan chan ErrorResult

ChartRenderingEngine *ChartRenderingEngine
ChartRenderingEngine *ChartRenderingEngine
ManifestValidationEngine *ManifestValidationEngine
ImageExtractionEngine *ImageExtractionEngine
ImageExtractionEngine *ImageExtractionEngine
DockerValidationEngine *DockerImageValidationEngine

context context.Context
executor CommandExecutor
context context.Context
executor CommandExecutor

workerWaitGroup sync.WaitGroup

name string
}

func NewAppCheckerEngine(context context.Context, outputDir string) *AppCheckerEngine {
func NewAppCheckerEngine(context context.Context, outputDir string, apiVersions []string) *AppCheckerEngine {

errorChan := make(chan ErrorResult)

cre := ChartRenderingEngine{
inputChan: make(chan ChartRenderParams),
resultChan: make(chan RenderResult),
errorChan: errorChan,
outputDir: outputDir,
context: context,
executor: &RealCommandExecutor{},
name: "ChartRenderer",
inputChan: make(chan ChartRenderParams),
resultChan: make(chan RenderResult),
errorChan: errorChan,
outputDir: outputDir,
context: context,
executor: &RealCommandExecutor{},
name: "ChartRenderer",
apiVersions: apiVersions,
}

// Construct absolute path to schemas directory
schemasDir, err := filepath.Abs("schemas")
if err != nil {
// If we can't get the absolute path, use relative path as fallback
schemasDir = "schemas"
}

mve := ManifestValidationEngine{
inputChan: cre.resultChan,
resultChan: make(chan ManifestValidationResult),
errorChan: errorChan,
context: context,
executor: &RealCommandExecutor{},
name: "ManifestValidator",
inputChan: cre.resultChan,
resultChan: make(chan ManifestValidationResult),
errorChan: errorChan,
context: context,
executor: &RealCommandExecutor{},
name: "ManifestValidator",
schemasDir: schemasDir,
workerWaitGroup: sync.WaitGroup{},
}

iee := ImageExtractionEngine{
inputChan: mve.resultChan,
outputChan: make(chan ImageExtractionResult),
errorChan: errorChan,
context: context,
name: "ImageExtractor",
inputChan: mve.resultChan,
outputChan: make(chan ImageExtractionResult),
errorChan: errorChan,
context: context,
name: "ImageExtractor",
workerWaitGroup: sync.WaitGroup{},
}

dve := DockerImageValidationEngine{
inputChan: iee.outputChan,
outputChan: make(chan DockerImageValidationResult),
context: context,
executor: &RealCommandExecutor{},
name: "DockerValidator",
cache: map[string]DockerImageValidationResult{},
pending: map[string]*sync.WaitGroup{},
cacheLock: sync.RWMutex{},
inputChan: iee.outputChan,
outputChan: make(chan DockerImageValidationResult),
context: context,
executor: &RealCommandExecutor{},
name: "DockerValidator",
cache: map[string]DockerImageValidationResult{},
pending: map[string]*sync.WaitGroup{},
cacheLock: sync.RWMutex{},
workerWaitGroup: sync.WaitGroup{},
}

return &AppCheckerEngine{
inputChan: make(chan AppCheckInstruction),
resultChan: make(chan AppCheckResult),
errorChan: make(chan ErrorResult),

context: context,
executor: &RealCommandExecutor{},
context: context,
executor: &RealCommandExecutor{},

ChartRenderingEngine: &cre,
ChartRenderingEngine: &cre,
ManifestValidationEngine: &mve,
ImageExtractionEngine: &iee,
ImageExtractionEngine: &iee,
DockerValidationEngine: &dve,

name: "AppChecker",
Expand All @@ -98,7 +108,7 @@ func NewAppCheckerEngine(context context.Context, outputDir string) *AppCheckerE

func (engine *AppCheckerEngine) allDoneWorker() {
engine.workerWaitGroup.Wait()
logEngineDebug(engine.name,-1,"all workers done, closing output channel")
logEngineDebug(engine.name, -1, "all workers done, closing output channel")
close(engine.resultChan)
}

Expand All @@ -113,7 +123,7 @@ func (engine *AppCheckerEngine) Start(workerCount int) {
// Pour the input instructions into the chart renderer
engine.workerWaitGroup.Add(1)
go engine.pumpAppCheckInstructionsToChartRenderer()
engine.workerWaitGroup.Add(1)
engine.workerWaitGroup.Add(1)
go engine.pumpOutputsToAppCheckResults()

go engine.allDoneWorker()
Expand Down Expand Up @@ -147,14 +157,7 @@ func (engine *AppCheckerEngine) pumpOutputsToAppCheckResults() {
func (engine *AppCheckerEngine) pumpAppCheckInstructionsToChartRenderer() {
defer engine.workerWaitGroup.Done()
for instruction := range engine.inputChan {
engine.ChartRenderingEngine.inputChan <- ChartRenderParams{
Env: instruction.Chart.Env,
ChartName: instruction.Chart.ChartName,
RepoURL: instruction.Chart.RepoURL,
ChartVersion: instruction.Chart.ChartVersion,
BaseValuesFile: instruction.Chart.BaseValuesFile,
ValuesOverride: instruction.Chart.ValuesOverride,
}
engine.ChartRenderingEngine.inputChan <- instruction.Chart
}
close(engine.ChartRenderingEngine.inputChan)
}
}
31 changes: 19 additions & 12 deletions chartvalidator/checker/engine_chart_rendering.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ import (
"sync"
)


type ChartRenderingEngine struct {
inputChan chan ChartRenderParams
resultChan chan RenderResult
errorChan chan ErrorResult

outputDir string
context context.Context
executor CommandExecutor
name string
outputDir string
context context.Context
executor CommandExecutor
name string
workerWaitGroup sync.WaitGroup
apiVersions []string
}

type RenderResult struct {
Chart ChartRenderParams
Chart ChartRenderParams
ManifestPath string
}

Expand All @@ -36,7 +36,7 @@ func (engine *ChartRenderingEngine) Start(workerCount int) {
}

for i := 0; i < workerCount; i++ {
engine.workerWaitGroup.Add(1)
engine.workerWaitGroup.Add(1)
go func(workerId int) {
engine.worker(workerId)
}(i)
Expand All @@ -46,7 +46,7 @@ func (engine *ChartRenderingEngine) Start(workerCount int) {

func (engine *ChartRenderingEngine) allDoneWorker() {
engine.workerWaitGroup.Wait()
logEngineDebug(engine.name,-1,"all workers done, closing output channel")
logEngineDebug(engine.name, -1, "all workers done, closing output channel")
close(engine.resultChan)
}

Expand Down Expand Up @@ -74,7 +74,6 @@ func (engine *ChartRenderingEngine) worker(workerId int) {
}
}


func (engine *ChartRenderingEngine) renderSingleChart(chart ChartRenderParams, workerId int) (*RenderResult, error) {

if !engine.executor.FileExists(chart.BaseValuesFile) {
Expand All @@ -96,16 +95,24 @@ func (engine *ChartRenderingEngine) renderSingleChart(chart ChartRenderParams, w
"-f", chart.ValuesOverride,
"--version", chart.ChartVersion,
"--include-crds",
"--kube-version", kubernetesVersion,
}

// Add API versions if any
for _, apiVer := range engine.apiVersions {
if apiVer != "" {
args = append(args, "--api-versions", apiVer)
}
}

logEngineDebug(engine.name, workerId, fmt.Sprintf("helm %s", strings.Join(args, " ")))
cmd := engine.executor.CommandContext(engine.context, "helm", args...)

// Set working directory to current directory so relative paths work
if wd, err := os.Getwd(); err == nil {
cmd.SetDir(wd)
}

output, err := cmd.CombinedOutput()
if err != nil {
msg := fmt.Sprintf("helm command failed: %s\nOutput: %s", err.Error(), string(output))
Expand All @@ -122,7 +129,7 @@ func (engine *ChartRenderingEngine) renderSingleChart(chart ChartRenderParams, w
logEngineWarning(engine.name, workerId, msg)
return nil, fmt.Errorf("failed to get absolute path for output dir: %w", err)
}

randStr := generateRandomString(6)
filename := fmt.Sprintf("%s_%s.yaml", chart.ChartName, randStr)
outputPath := filepath.Join(absOutputDir, filename)
Expand Down
19 changes: 10 additions & 9 deletions chartvalidator/checker/engine_chart_rendering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import (
// Helper function to create and start a chart rendering engine
func createEngine(mockExecutor *MockCommandExecutor, includeErrorChan bool) *ChartRenderingEngine {
engine := &ChartRenderingEngine{
inputChan: make(chan ChartRenderParams),
resultChan: make(chan RenderResult),
outputDir: "test_output",
context: context.Background(),
executor: mockExecutor,
inputChan: make(chan ChartRenderParams),
resultChan: make(chan RenderResult),
outputDir: "test_output",
context: context.Background(),
executor: mockExecutor,
apiVersions: []string{"something", "something-else"},
}

if includeErrorChan {
engine.errorChan = make(chan ErrorResult)
}

engine.Start(1)
return engine
}
Expand All @@ -43,7 +44,7 @@ func TestRenderBasics(t *testing.T) {
assertChartFieldsMatch(t, testChart, result.Chart)

// Verify the command that was executed
expectedCommand := "helm template test-chart --release-name test-chart --repo https://example.com/charts -f values.yaml -f override.yaml --version 1.0.0 --include-crds"
expectedCommand := "helm template test-chart --release-name test-chart --repo https://example.com/charts -f values.yaml -f override.yaml --version 1.0.0 --include-crds --kube-version 1.33.0 --api-versions something --api-versions something-else"
actualCommand := mockExecutor.GetFullCommand()
assert.Equal(t, expectedCommand, actualCommand)
}
Expand All @@ -65,4 +66,4 @@ func TestRenderBaseFileNotExist(t *testing.T) {
assert.Equal(t, errorResult.Chart.ChartName, testChart.ChartName)
assert.NotNil(t, errorResult.Error)
assert.Contains(t, errorResult.Error.Error(), "base values file does not exist")
}
}
Loading