Skip to content
Draft
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
8 changes: 5 additions & 3 deletions src/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type Test struct {
ignoredLines []string
}

func (test *Test) Run(theChart *chart.Chart, installAction *action.Install, rootPath string, ignorePatterns []string, schema *cue.Value) error {
func (test *Test) Run(theChart *chart.Chart, installAction *action.Install, rootPath string, ignorePatterns []string, schema *cue.Value, cacheDir, schemaDir string) error {
testValuesPath := filepath.Join(rootPath, test.name, "values.yaml")
testValues, err := loadValuesFile(testValuesPath)
if err != nil {
Expand Down Expand Up @@ -164,7 +164,7 @@ func (test *Test) Run(theChart *chart.Chart, installAction *action.Install, root
}

// Validate
err = validateManifest(test, release.Manifest)
err = validateManifest(test, release.Manifest, cacheDir, schemaDir)
if err != nil {
return fmt.Errorf("validating manifest: %w", err)
}
Expand Down Expand Up @@ -510,6 +510,8 @@ type RunOptions struct {
IgnorePatterns []string
Schema *cue.Value
Concurrency int
CacheDir string
SchemaDir string
HelmOptions
}

Expand Down Expand Up @@ -580,7 +582,7 @@ func (suite TestSuite) Run(opts RunOptions) error {
theChart.Metadata.AppVersion = appVersion
}

if err := test.Run(theChart, installAction, opts.RootFS, opts.IgnorePatterns, opts.Schema); err != nil {
if err := test.Run(theChart, installAction, opts.RootFS, opts.IgnorePatterns, opts.Schema, opts.CacheDir, opts.SchemaDir); err != nil {
e <- fmt.Errorf("running test %s: %w", test.name, err)
return
}
Expand Down
45 changes: 40 additions & 5 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
Expand All @@ -28,13 +29,26 @@ var (
normalize bool
)

func defaultCacheDir() string {
if dir := os.Getenv("XDG_CACHE_HOME"); dir != "" {
return filepath.Join(dir, "testchart")
}
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, ".cache", "testchart")
}

func main() {
var testPath string
var namespace string
var release string
var chartVersion string
var appVersion string
var concurrency int
var cacheDir string
var schemaDir string
isUpdate := false
var ignorePatterns []string

Expand All @@ -55,13 +69,15 @@ func main() {
rootCmd.PersistentFlags().StringVar(&debugOutput, "debug", "", "location to render failed install output manifests for debugging")
rootCmd.PersistentFlags().IntVarP(&concurrency, "concurrency", "c", runtime.GOMAXPROCS(0), "test run concurrency")
rootCmd.PersistentFlags().BoolVar(&normalize, "normalize", true, "normalize output. Disabling allows you to view raw helm templating.")
rootCmd.PersistentFlags().StringVar(&cacheDir, "cache-dir", defaultCacheDir(), "directory for caching downloaded schemas (set to empty string to disable)")
rootCmd.PersistentFlags().StringVar(&schemaDir, "schema-dir", "", "local checkout of yannh/kubernetes-json-schema to use instead of downloading schemas over HTTP")

runCmd := &cobra.Command{
Use: "run [test1 test2 ...]",
Short: "Run unit tests",
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runTests(args, testPath, namespace, release, chartVersion, appVersion, isUpdate, ignorePatterns, concurrency)
return runTests(args, testPath, namespace, release, chartVersion, appVersion, isUpdate, ignorePatterns, concurrency, cacheDir, schemaDir)
},
}

Expand All @@ -71,7 +87,7 @@ func main() {
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
isUpdate = true
return runTests(args, testPath, namespace, release, chartVersion, appVersion, isUpdate, ignorePatterns, concurrency)
return runTests(args, testPath, namespace, release, chartVersion, appVersion, isUpdate, ignorePatterns, concurrency, cacheDir, schemaDir)
},
}

Expand All @@ -93,7 +109,7 @@ func main() {
}
}

func runTests(args []string, testPath, namespace, releaseName, chartVersion, appVersion string, isUpdate bool, ignorePatterns []string, concurrency int) error {
func runTests(args []string, testPath, namespace, releaseName, chartVersion, appVersion string, isUpdate bool, ignorePatterns []string, concurrency int, cacheDir, schemaDir string) error {
if _, err := os.Stat(testPath); os.IsNotExist(err) {
fmt.Println("No tests found")
return nil
Expand Down Expand Up @@ -127,6 +143,8 @@ func runTests(args []string, testPath, namespace, releaseName, chartVersion, app
IgnorePatterns: ignorePatterns,
Schema: schema,
Concurrency: concurrency,
CacheDir: cacheDir,
SchemaDir: schemaDir,
HelmOptions: HelmOptions{
Namespace: namespace,
Release: releaseName,
Expand Down Expand Up @@ -196,8 +214,25 @@ func loadValuesFile(filePath string) (map[string]any, error) {
return data, nil
}

func validateManifest(test *Test, manifest string) error {
v, err := validator.New(nil, validator.Opts{Strict: true, IgnoreMissingSchemas: true})
func buildSchemaLocations(schemaDir string) []string {
if schemaDir != "" {
local := "file://" + filepath.Join(schemaDir, "{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}", "{{ .ResourceKind }}{{ .KindSuffix }}.json")
return []string{local}
}
return nil // default: kubeconform uses yannh/kubernetes-json-schema
}

func validateManifest(test *Test, manifest string, cacheDir, schemaDir string) error {
if cacheDir != "" && schemaDir == "" {
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
return fmt.Errorf("creating cache directory: %w", err)
}
}
effectiveCacheDir := cacheDir
if schemaDir != "" {
effectiveCacheDir = ""
}
v, err := validator.New(buildSchemaLocations(schemaDir), validator.Opts{Strict: true, IgnoreMissingSchemas: true, Cache: effectiveCacheDir})
if err != nil {
return fmt.Errorf("initializing validator: %w", err)
}
Expand Down