mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
Merge e64a1735d3 into fcdf3854b0
This commit is contained in:
commit
79919d1327
35 changed files with 501 additions and 61 deletions
|
|
@ -97,7 +97,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string
|
|||
return
|
||||
}
|
||||
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, skipSchemaValidation)
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidationAndPath(chart, cvals, options, caps, skipSchemaValidation, linter.ChartDir)
|
||||
if err != nil {
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any,
|
|||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides, skipSchemaValidation))
|
||||
linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(linter.ChartDir, valueOverrides, skipSchemaValidation))
|
||||
}
|
||||
|
||||
func validateValuesFileExistence(valuesPath string) error {
|
||||
|
|
@ -53,7 +53,10 @@ func validateValuesFileExistence(valuesPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error {
|
||||
func validateValuesFile(chartDir string, overrides map[string]any, skipSchemaValidation bool) error {
|
||||
valuesPath := filepath.Join(chartDir, "values.yaml")
|
||||
schemaPath := filepath.Join(chartDir, "values.schema.json")
|
||||
|
||||
values, err := common.ReadValuesFile(valuesPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse YAML: %w", err)
|
||||
|
|
@ -67,8 +70,6 @@ func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaV
|
|||
coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides)
|
||||
coalescedValues = util.CoalesceTables(coalescedValues, values)
|
||||
|
||||
ext := filepath.Ext(valuesPath)
|
||||
schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json"
|
||||
schema, err := os.ReadFile(schemaPath)
|
||||
if len(schema) == 0 {
|
||||
return nil
|
||||
|
|
@ -78,7 +79,7 @@ func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaV
|
|||
}
|
||||
|
||||
if !skipSchemaValidation {
|
||||
return util.ValidateAgainstSingleSchema(coalescedValues, schema)
|
||||
return util.ValidateAgainstSingleSchemaWithPath(coalescedValues, schema, schemaPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -66,8 +66,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) {
|
|||
not:well[]{}formed
|
||||
`
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml))
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, map[string]any{}, false); err == nil {
|
||||
if err := validateValuesFile(tmpdir, map[string]any{}, false); err == nil {
|
||||
t.Fatal("expected values file to fail parsing")
|
||||
}
|
||||
}
|
||||
|
|
@ -77,8 +76,7 @@ func TestValidateValuesFileSchema(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, map[string]any{}, false); err != nil {
|
||||
if err := validateValuesFile(tmpdir, map[string]any{}, false); err != nil {
|
||||
t.Fatalf("Failed validation with %s", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,9 +87,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, map[string]any{}, false)
|
||||
err := validateValuesFile(tmpdir, map[string]any{}, false)
|
||||
if err == nil {
|
||||
t.Fatal("expected values file to fail parsing")
|
||||
}
|
||||
|
|
@ -105,9 +101,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, map[string]any{}, true)
|
||||
err := validateValuesFile(tmpdir, map[string]any{}, true)
|
||||
if err != nil {
|
||||
t.Fatal("expected values file to pass parsing because of skipSchemaValidation")
|
||||
}
|
||||
|
|
@ -121,8 +115,7 @@ func TestValidateValuesFileSchemaOverrides(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, overrides, false); err != nil {
|
||||
if err := validateValuesFile(tmpdir, overrides, false); err != nil {
|
||||
t.Fatalf("Failed validation with %s", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -157,9 +150,7 @@ func TestValidateValuesFile(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(tt.yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, tt.overrides, false)
|
||||
err := validateValuesFile(tmpdir, tt.overrides, false)
|
||||
|
||||
switch {
|
||||
case err != nil && tt.errorMessage == "":
|
||||
|
|
|
|||
|
|
@ -127,6 +127,9 @@ type Install struct {
|
|||
// Used by helm template to add the release as part of OutputDir path
|
||||
// OutputDir/<ReleaseName>
|
||||
UseReleaseName bool
|
||||
// ChartDir is the local directory path of the chart, used for resolving
|
||||
// relative $ref in JSON schemas. Empty for remote charts.
|
||||
ChartDir string
|
||||
// TakeOwnership will ignore the check for helm annotations and take ownership of the resources.
|
||||
TakeOwnership bool
|
||||
PostRenderer postrenderer.PostRenderer
|
||||
|
|
@ -363,7 +366,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
|
|||
IsInstall: !isUpgrade,
|
||||
IsUpgrade: isUpgrade,
|
||||
}
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chrt, vals, options, caps, i.SkipSchemaValidation)
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidationAndPath(chrt, vals, options, caps, i.SkipSchemaValidation, i.ChartDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ func TestLintChart(t *testing.T) {
|
|||
name: "chart-with-schema",
|
||||
chartPath: "testdata/charts/chart-with-schema",
|
||||
},
|
||||
{
|
||||
name: "chart-with-schema-ref",
|
||||
chartPath: "testdata/charts/chart-with-schema-ref",
|
||||
},
|
||||
{
|
||||
name: "archived-chart-with-schema-ref",
|
||||
chartPath: "testdata/charts/chart-with-schema-ref.tgz",
|
||||
},
|
||||
{
|
||||
name: "chart-with-schema-negative",
|
||||
chartPath: "testdata/charts/chart-with-schema-negative",
|
||||
|
|
|
|||
BIN
pkg/action/testdata/charts/chart-with-schema-ref.tgz
vendored
Normal file
BIN
pkg/action/testdata/charts/chart-with-schema-ref.tgz
vendored
Normal file
Binary file not shown.
3
pkg/action/testdata/charts/chart-with-schema-ref/Chart.yaml
vendored
Normal file
3
pkg/action/testdata/charts/chart-with-schema-ref/Chart.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
apiVersion: v2
|
||||
name: chart-with-schema-ref
|
||||
version: 0.1.0
|
||||
4
pkg/action/testdata/charts/chart-with-schema-ref/name.schema.json
vendored
Normal file
4
pkg/action/testdata/charts/chart-with-schema-ref/name.schema.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "string"
|
||||
}
|
||||
7
pkg/action/testdata/charts/chart-with-schema-ref/values.schema.json
vendored
Normal file
7
pkg/action/testdata/charts/chart-with-schema-ref/values.schema.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "$ref": "name.schema.json" }
|
||||
}
|
||||
}
|
||||
1
pkg/action/testdata/charts/chart-with-schema-ref/values.yaml
vendored
Normal file
1
pkg/action/testdata/charts/chart-with-schema-ref/values.yaml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
name: "test"
|
||||
|
|
@ -113,6 +113,9 @@ type Upgrade struct {
|
|||
HideNotes bool
|
||||
// SkipSchemaValidation determines if JSON schema validation is disabled.
|
||||
SkipSchemaValidation bool
|
||||
// ChartDir is the local directory path of the chart, used for resolving
|
||||
// relative $ref in JSON schemas. Empty for remote charts.
|
||||
ChartDir string
|
||||
// Description is the description of this operation
|
||||
Description string
|
||||
Labels map[string]string
|
||||
|
|
@ -296,7 +299,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str
|
|||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chart, vals, options, caps, u.SkipSchemaValidation)
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidationAndPath(chart, vals, options, caps, u.SkipSchemaValidation, u.ChartDir)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import (
|
|||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -74,14 +76,42 @@ func newHTTPURLLoader() *HTTPURLLoader {
|
|||
|
||||
// ValidateAgainstSchema checks that values does not violate the structure laid out in schema
|
||||
func ValidateAgainstSchema(ch chart.Charter, values map[string]any) error {
|
||||
return ValidateAgainstSchemaWithPath(ch, values, "")
|
||||
}
|
||||
|
||||
func ValidateAgainstSchemaWithPath(ch chart.Charter, values map[string]any, chartDir string) error {
|
||||
chrt, err := chart.NewAccessor(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert chartDir to absolute path for $ref resolution.
|
||||
// If chartDir is empty (e.g., chart loaded from .tgz archive), absChartPath
|
||||
// remains empty and a synthetic path will be used instead.
|
||||
var absChartPath string
|
||||
if chartDir != "" {
|
||||
var err error
|
||||
absChartPath, err = filepath.Abs(chartDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
if chrt.Schema() != nil {
|
||||
slog.Debug("chart name", "chart-name", chrt.Name())
|
||||
err := ValidateAgainstSingleSchema(values, chrt.Schema())
|
||||
|
||||
var schemaPath string
|
||||
if absChartPath != "" {
|
||||
// Use the chart directory for $ref resolution
|
||||
schemaPath = filepath.Join(absChartPath, "values.schema.json")
|
||||
} else {
|
||||
// No chart directory (e.g., chart loaded from .tgz archive).
|
||||
// Use a synthetic path - $ref resolution will not work, but main schema validation will.
|
||||
schemaPath = "/values.schema.json"
|
||||
}
|
||||
|
||||
err := ValidateAgainstSingleSchemaWithPath(values, chrt.Schema(), schemaPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&sb, "%s:\n", chrt.Name())
|
||||
sb.WriteString(err.Error())
|
||||
|
|
@ -108,7 +138,15 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]any) error {
|
|||
continue
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSchema(subchart, subchartValues); err != nil {
|
||||
var subchartPath string
|
||||
if absChartPath != "" {
|
||||
subchartPath = resolveSubchartDir(
|
||||
filepath.Join(absChartPath, "charts"),
|
||||
sub.Name(),
|
||||
sub.Schema(),
|
||||
)
|
||||
}
|
||||
if err := ValidateAgainstSchemaWithPath(subchart, subchartValues, subchartPath); err != nil {
|
||||
sb.WriteString(err.Error())
|
||||
}
|
||||
}
|
||||
|
|
@ -122,6 +160,12 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]any) error {
|
|||
|
||||
// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema
|
||||
func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reterr error) {
|
||||
return ValidateAgainstSingleSchemaWithPath(values, schemaJSON, "/values.schema.json")
|
||||
}
|
||||
|
||||
// ValidateAgainstSingleSchemaWithPath checks that values does not violate the structure laid out in this schema.
|
||||
// schemaPath is the absolute path to the schema file, used to resolve relative $ref references.
|
||||
func ValidateAgainstSingleSchemaWithPath(values common.Values, schemaJSON []byte, schemaPath string) (reterr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
reterr = fmt.Errorf("unable to validate schema: %s", r)
|
||||
|
|
@ -146,12 +190,14 @@ func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reter
|
|||
|
||||
compiler := jsonschema.NewCompiler()
|
||||
compiler.UseLoader(loader)
|
||||
err = compiler.AddResource("file:///values.schema.json", schema)
|
||||
|
||||
schemaURL := "file://" + schemaPath
|
||||
err = compiler.AddResource(schemaURL, schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
validator, err := compiler.Compile("file:///values.schema.json")
|
||||
validator, err := compiler.Compile(schemaURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -193,6 +239,41 @@ func (l urnLoader) Load(urlStr string) (any, error) {
|
|||
return jsonschema.UnmarshalJSON(strings.NewReader("true"))
|
||||
}
|
||||
|
||||
// resolveSubchartDir finds the on-disk directory for a subchart under chartsDir.
|
||||
// Returns "" when no directory can be found, which disables $ref resolution.
|
||||
func resolveSubchartDir(chartsDir, effectiveName string, schema []byte) string {
|
||||
// Direct match; handles the common non-aliased case in one syscall.
|
||||
candidate := filepath.Join(chartsDir, effectiveName)
|
||||
if info, err := os.Stat(candidate); err == nil && info.IsDir() {
|
||||
return candidate
|
||||
}
|
||||
|
||||
// The effective name didn't match a directory likely an alias.
|
||||
// Scan charts/ subdirectories and match by schema content.
|
||||
if len(schema) == 0 {
|
||||
return ""
|
||||
}
|
||||
entries, err := os.ReadDir(chartsDir)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(chartsDir, e.Name(), "values.schema.json"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// getAliasDependency shallow-copies the chart, so schema bytes in memory
|
||||
// are identical to the file originally loaded from this directory.
|
||||
if bytes.Equal(data, schema) {
|
||||
return filepath.Join(chartsDir, e.Name())
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Note, JSONSchemaValidationError is used to wrap the error from the underlying
|
||||
// validation package so that Helm has a clean interface and the validation package
|
||||
// could be replaced without changing the Helm SDK API.
|
||||
|
|
@ -209,7 +290,12 @@ func (e JSONSchemaValidationError) Error() string {
|
|||
|
||||
// This string prefixes all of our error details. Further up the stack of helm error message
|
||||
// building more detail is provided to users. This is removed.
|
||||
errStr = strings.TrimPrefix(errStr, "jsonschema validation failed with 'file:///values.schema.json#'\n")
|
||||
// Remove the "jsonschema validation failed with 'file://...#'" line regardless of the path
|
||||
if strings.HasPrefix(errStr, "jsonschema validation failed with 'file://") {
|
||||
if idx := strings.Index(errStr, "#'\n"); idx != -1 {
|
||||
errStr = errStr[idx+3:] // Skip past "#'\n"
|
||||
}
|
||||
}
|
||||
|
||||
// The extra new line is needed for when there are sub-charts.
|
||||
return errStr + "\n"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -177,11 +178,12 @@ func TestValidateAgainstSchemaNegative(t *testing.T) {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
expectedErrString := `subchart:
|
||||
- at '': missing property 'age'
|
||||
`
|
||||
if errString != expectedErrString {
|
||||
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
|
||||
expectedValidationError := "missing property 'age'"
|
||||
if !strings.Contains(errString, "subchart:") {
|
||||
t.Errorf("Error string should contain 'subchart:', got: %s", errString)
|
||||
}
|
||||
if !strings.Contains(errString, expectedValidationError) {
|
||||
t.Errorf("Error string should contain '%s', got: %s", expectedValidationError, errString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,12 +243,63 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
expectedErrString := `subchart:
|
||||
- at '/data': no items match contains schema
|
||||
- at '/data/0': got number, want string
|
||||
`
|
||||
if errString != expectedErrString {
|
||||
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
|
||||
expectedValidationErrors := []string{
|
||||
"no items match contains schema",
|
||||
"got number, want string",
|
||||
}
|
||||
if !strings.Contains(errString, "subchart:") {
|
||||
t.Errorf("Error string should contain 'subchart:', got: %s", errString)
|
||||
}
|
||||
for _, expectedErr := range expectedValidationErrors {
|
||||
if !strings.Contains(errString, expectedErr) {
|
||||
t.Errorf("Error string should contain '%s', got: %s", expectedErr, errString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateWithRelativeSchemaReferences tests schema validation with relative $ref paths
|
||||
// This mimics the behavior of "helm lint ." where the schema is in the current directory
|
||||
func TestValidateWithRelativeSchemaReferencesCurrentDir(t *testing.T) {
|
||||
values, err := common.ReadValuesFile("./testdata/current-dir-test/test-values.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading YAML file: %s", err)
|
||||
}
|
||||
schemaPath := "./testdata/current-dir-test/values.schema.json"
|
||||
schema, err := os.ReadFile(schemaPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading JSON schema file: %s", err)
|
||||
}
|
||||
|
||||
absSchemaPath, err := filepath.Abs(schemaPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting absolute path: %s", err)
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSingleSchemaWithPath(values, schema, absSchemaPath); err != nil {
|
||||
t.Errorf("Error validating Values against Schema with relative references: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateWithRelativeSchemaReferencesSubfolder tests schema validation with relative $ref paths
|
||||
// This mimics the behavior of "helm lint subfolder" where the schema is in a subdirectory
|
||||
func TestValidateWithRelativeSchemaReferencesSubfolder(t *testing.T) {
|
||||
values, err := common.ReadValuesFile("./testdata/subdir-test/subfolder/test-values.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading YAML file: %s", err)
|
||||
}
|
||||
schemaPath := "./testdata/subdir-test/subfolder/values.schema.json"
|
||||
schema, err := os.ReadFile(schemaPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading JSON schema file: %s", err)
|
||||
}
|
||||
|
||||
absSchemaPath, err := filepath.Abs(schemaPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting absolute path: %s", err)
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSingleSchemaWithPath(values, schema, absSchemaPath); err != nil {
|
||||
t.Errorf("Error validating Values against Schema with relative references from subfolder: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,3 +442,170 @@ func TestValidateAgainstSchema_InvalidSubchartValuesType_NoPanic(t *testing.T) {
|
|||
t.Fatal("expected an error when subchart values have invalid type, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Test that $ref resolution works for aliased subcharts.
|
||||
// When a subchart has an alias (e.g., mysql aliased as "database"),
|
||||
// processDependencyEnabled rewrites Metadata.Name to the alias,
|
||||
// but the on-disk directory retains the original name (charts/mysql/).
|
||||
// The schema validator must find the correct directory to resolve $ref.
|
||||
func TestValidateAgainstSchemaWithPath_AliasedSubchartRef(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// On-disk layout: charts/mysql/ contains the schema files.
|
||||
// The directory name is the ORIGINAL chart name, not the alias.
|
||||
mysqlDir := filepath.Join(tmpDir, "charts", "mysql")
|
||||
if err := os.MkdirAll(mysqlDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
baseSchema := []byte(`{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"port": { "type": "integer", "minimum": 1 }
|
||||
},
|
||||
"required": ["port"]
|
||||
}`)
|
||||
if err := os.WriteFile(filepath.Join(mysqlDir, "base.schema.json"), baseSchema, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subchartSchemaBytes := []byte(`{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": { "$ref": "./base.schema.json" }
|
||||
},
|
||||
"required": ["config"]
|
||||
}`)
|
||||
if err := os.WriteFile(filepath.Join(mysqlDir, "values.schema.json"), subchartSchemaBytes, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// In-memory chart: Metadata.Name is the ALIAS ("database"),
|
||||
// simulating what processDependencyEnabled does after loading.
|
||||
subchart := &chart.Chart{
|
||||
Metadata: &chart.Metadata{Name: "database"},
|
||||
Schema: subchartSchemaBytes,
|
||||
}
|
||||
chrt := &chart.Chart{
|
||||
Metadata: &chart.Metadata{Name: "testchart"},
|
||||
}
|
||||
chrt.AddDependency(subchart)
|
||||
|
||||
vals := map[string]any{
|
||||
"database": map[string]any{
|
||||
"config": map[string]any{
|
||||
"port": 3306,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSchemaWithPath(chrt, vals, tmpDir); err != nil {
|
||||
t.Errorf("expected no error for valid values with aliased subchart $ref, got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that $ref resolution works when multiple aliases point to the same chart.
|
||||
// Both aliased subcharts should resolve $ref through the single on-disk directory.
|
||||
func TestValidateAgainstSchemaWithPath_MultipleAliasesSameChart(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
mysqlDir := filepath.Join(tmpDir, "charts", "mysql")
|
||||
if err := os.MkdirAll(mysqlDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
baseSchema := []byte(`{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"port": { "type": "integer", "minimum": 1 }
|
||||
},
|
||||
"required": ["port"]
|
||||
}`)
|
||||
if err := os.WriteFile(filepath.Join(mysqlDir, "base.schema.json"), baseSchema, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
subchartSchemaBytes := []byte(`{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": { "$ref": "./base.schema.json" }
|
||||
},
|
||||
"required": ["config"]
|
||||
}`)
|
||||
if err := os.WriteFile(filepath.Join(mysqlDir, "values.schema.json"), subchartSchemaBytes, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Two aliased subcharts from the same original chart
|
||||
primary := &chart.Chart{
|
||||
Metadata: &chart.Metadata{Name: "primary"},
|
||||
Schema: subchartSchemaBytes,
|
||||
}
|
||||
replica := &chart.Chart{
|
||||
Metadata: &chart.Metadata{Name: "replica"},
|
||||
Schema: subchartSchemaBytes,
|
||||
}
|
||||
chrt := &chart.Chart{
|
||||
Metadata: &chart.Metadata{Name: "testchart"},
|
||||
}
|
||||
chrt.AddDependency(primary)
|
||||
chrt.AddDependency(replica)
|
||||
|
||||
vals := map[string]any{
|
||||
"primary": map[string]any{
|
||||
"config": map[string]any{"port": 3306},
|
||||
},
|
||||
"replica": map[string]any{
|
||||
"config": map[string]any{"port": 3307},
|
||||
},
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSchemaWithPath(chrt, vals, tmpDir); err != nil {
|
||||
t.Errorf("expected no error for multiple aliases of same chart, got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that validation proceeds gracefully when an aliased subchart has no
|
||||
// matching directory on disk (e.g., the subchart is an archived .tgz).
|
||||
// $ref resolution is disabled but main schema validation still works.
|
||||
func TestValidateAgainstSchemaWithPath_AliasedSubchartNoDir(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create empty charts/ directory — no subdirectory matching any name
|
||||
if err := os.MkdirAll(filepath.Join(tmpDir, "charts"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Schema without $ref — validates independently
|
||||
subchartSchemaBytes := []byte(`{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"port": { "type": "integer" }
|
||||
},
|
||||
"required": ["port"]
|
||||
}`)
|
||||
|
||||
subchart := &chart.Chart{
|
||||
Metadata: &chart.Metadata{Name: "database"},
|
||||
Schema: subchartSchemaBytes,
|
||||
}
|
||||
chrt := &chart.Chart{
|
||||
Metadata: &chart.Metadata{Name: "testchart"},
|
||||
}
|
||||
chrt.AddDependency(subchart)
|
||||
|
||||
vals := map[string]any{
|
||||
"database": map[string]any{
|
||||
"port": 5432,
|
||||
},
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSchemaWithPath(chrt, vals, tmpDir); err != nil {
|
||||
t.Errorf("expected no error when aliased subchart dir missing (graceful fallback), got: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
pkg/chart/common/util/testdata/current-dir-test/base.schema.json
vendored
Normal file
10
pkg/chart/common/util/testdata/current-dir-test/base.schema.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
3
pkg/chart/common/util/testdata/current-dir-test/test-values.yaml
vendored
Normal file
3
pkg/chart/common/util/testdata/current-dir-test/test-values.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
user:
|
||||
name: "John Doe"
|
||||
age: 30
|
||||
14
pkg/chart/common/util/testdata/current-dir-test/values.schema.json
vendored
Normal file
14
pkg/chart/common/util/testdata/current-dir-test/values.schema.json
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"$ref": "./base.schema.json"
|
||||
},
|
||||
"age": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["user", "age"]
|
||||
}
|
||||
10
pkg/chart/common/util/testdata/subdir-test/shared.schema.json
vendored
Normal file
10
pkg/chart/common/util/testdata/subdir-test/shared.schema.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["config"]
|
||||
}
|
||||
3
pkg/chart/common/util/testdata/subdir-test/subfolder/test-values.yaml
vendored
Normal file
3
pkg/chart/common/util/testdata/subdir-test/subfolder/test-values.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
appConfig:
|
||||
config: "production"
|
||||
replicas: 3
|
||||
14
pkg/chart/common/util/testdata/subdir-test/subfolder/values.schema.json
vendored
Normal file
14
pkg/chart/common/util/testdata/subdir-test/subfolder/values.schema.json
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"appConfig": {
|
||||
"$ref": "../shared.schema.json"
|
||||
},
|
||||
"replicas": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"required": ["appConfig", "replicas"]
|
||||
}
|
||||
|
|
@ -34,6 +34,12 @@ func ToRenderValues(chrt chart.Charter, chrtVals map[string]any, options common.
|
|||
//
|
||||
// This takes both ReleaseOptions and Capabilities to merge into the render values.
|
||||
func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) {
|
||||
return ToRenderValuesWithSchemaValidationAndPath(chrt, chrtVals, options, caps, skipSchemaValidation, "")
|
||||
}
|
||||
|
||||
// ToRenderValuesWithSchemaValidationAndPath is like ToRenderValuesWithSchemaValidation but accepts chartDir
|
||||
// for resolving relative $ref in JSON schemas.
|
||||
func ToRenderValuesWithSchemaValidationAndPath(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool, chartDir string) (common.Values, error) {
|
||||
if caps == nil {
|
||||
caps = common.DefaultCapabilities
|
||||
}
|
||||
|
|
@ -60,7 +66,7 @@ func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]
|
|||
}
|
||||
|
||||
if !skipSchemaValidation {
|
||||
if err := ValidateAgainstSchema(chrt, vals); err != nil {
|
||||
if err := ValidateAgainstSchemaWithPath(chrt, vals, chartDir); err != nil {
|
||||
return top, fmt.Errorf("values don't meet the specifications of the schema(s) in the following chart(s):\n%w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func (t *templateLinter) Lint() {
|
|||
return
|
||||
}
|
||||
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, t.skipSchemaValidation)
|
||||
valuesToRender, err := util.ToRenderValuesWithSchemaValidationAndPath(chart, cvals, options, caps, t.skipSchemaValidation, t.linter.ChartDir)
|
||||
if err != nil {
|
||||
t.linter.RunLinterRule(support.ErrorSev, templatesDir, err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any,
|
|||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, valueOverrides, skipSchemaValidation))
|
||||
linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(linter.ChartDir, valueOverrides, skipSchemaValidation))
|
||||
}
|
||||
|
||||
func validateValuesFileExistence(valuesPath string) error {
|
||||
|
|
@ -53,7 +53,10 @@ func validateValuesFileExistence(valuesPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error {
|
||||
func validateValuesFile(chartDir string, overrides map[string]any, skipSchemaValidation bool) error {
|
||||
valuesPath := filepath.Join(chartDir, "values.yaml")
|
||||
schemaPath := filepath.Join(chartDir, "values.schema.json")
|
||||
|
||||
values, err := common.ReadValuesFile(valuesPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse YAML: %w", err)
|
||||
|
|
@ -67,8 +70,6 @@ func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaV
|
|||
coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides)
|
||||
coalescedValues = util.CoalesceTables(coalescedValues, values)
|
||||
|
||||
ext := filepath.Ext(valuesPath)
|
||||
schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json"
|
||||
schema, err := os.ReadFile(schemaPath)
|
||||
if len(schema) == 0 {
|
||||
return nil
|
||||
|
|
@ -78,7 +79,7 @@ func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaV
|
|||
}
|
||||
|
||||
if !skipSchemaValidation {
|
||||
return util.ValidateAgainstSingleSchema(coalescedValues, schema)
|
||||
return util.ValidateAgainstSingleSchemaWithPath(coalescedValues, schema, schemaPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -66,8 +66,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) {
|
|||
not:well[]{}formed
|
||||
`
|
||||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml))
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, map[string]any{}, false); err == nil {
|
||||
if err := validateValuesFile(tmpdir, map[string]any{}, false); err == nil {
|
||||
t.Fatal("expected values file to fail parsing")
|
||||
}
|
||||
}
|
||||
|
|
@ -77,8 +76,7 @@ func TestValidateValuesFileSchema(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, map[string]any{}, false); err != nil {
|
||||
if err := validateValuesFile(tmpdir, map[string]any{}, false); err != nil {
|
||||
t.Fatalf("Failed validation with %s", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,9 +87,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, map[string]any{}, false)
|
||||
err := validateValuesFile(tmpdir, map[string]any{}, false)
|
||||
if err == nil {
|
||||
t.Fatal("expected values file to fail parsing")
|
||||
}
|
||||
|
|
@ -105,9 +101,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, map[string]any{}, true)
|
||||
err := validateValuesFile(tmpdir, map[string]any{}, true)
|
||||
if err != nil {
|
||||
t.Fatal("expected values file to pass parsing because of skipSchemaValidation")
|
||||
}
|
||||
|
|
@ -121,8 +115,7 @@ func TestValidateValuesFileSchemaOverrides(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
if err := validateValuesFile(valfile, overrides, false); err != nil {
|
||||
if err := validateValuesFile(tmpdir, overrides, false); err != nil {
|
||||
t.Fatalf("Failed validation with %s", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -157,9 +150,7 @@ func TestValidateValuesFile(t *testing.T) {
|
|||
tmpdir := ensure.TempFile(t, "values.yaml", []byte(tt.yaml))
|
||||
createTestingSchema(t, tmpdir)
|
||||
|
||||
valfile := filepath.Join(tmpdir, "values.yaml")
|
||||
|
||||
err := validateValuesFile(valfile, tt.overrides, false)
|
||||
err := validateValuesFile(tmpdir, tt.overrides, false)
|
||||
|
||||
switch {
|
||||
case err != nil && tt.errorMessage == "":
|
||||
|
|
|
|||
|
|
@ -269,6 +269,13 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Only set ChartDir for directory-based charts to enable $ref resolution.
|
||||
// Archived charts (.tgz) are loaded into memory without filesystem extraction,
|
||||
// so $ref resolution is not supported for them.
|
||||
if fi, err := os.Stat(cp); err == nil && fi.IsDir() {
|
||||
client.ChartDir = cp
|
||||
}
|
||||
|
||||
slog.Debug("Chart path", "path", cp)
|
||||
|
||||
p := getter.All(settings)
|
||||
|
|
|
|||
|
|
@ -231,6 +231,11 @@ func TestInstall(t *testing.T) {
|
|||
cmd: "install schema testdata/testcharts/chart-with-schema-and-subchart --set lastname=doe --set subchart-with-schema.age=-25 --skip-schema-validation",
|
||||
golden: "output/schema.txt",
|
||||
},
|
||||
{
|
||||
name: "install with schema file containing $ref",
|
||||
cmd: "install reftest testdata/testcharts/chart-with-schema-ref",
|
||||
golden: "output/schema-ref.txt",
|
||||
},
|
||||
// Install deprecated chart
|
||||
{
|
||||
name: "install with warning about deprecated chart",
|
||||
|
|
|
|||
|
|
@ -166,6 +166,11 @@ func TestTemplateCmd(t *testing.T) {
|
|||
cmd: fmt.Sprintf("template '%s' -f %s/extra_values.yaml", chartPath, chartPath),
|
||||
golden: "output/template-subchart-cm-set-file.txt",
|
||||
},
|
||||
{
|
||||
name: "template with schema file containing $ref",
|
||||
cmd: "template reftest testdata/testcharts/chart-with-schema-ref",
|
||||
golden: "output/template-schema-ref.txt",
|
||||
},
|
||||
}
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
|
|
|||
7
pkg/cmd/testdata/output/schema-ref.txt
vendored
Normal file
7
pkg/cmd/testdata/output/schema-ref.txt
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
NAME: reftest
|
||||
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
|
||||
NAMESPACE: default
|
||||
STATUS: deployed
|
||||
REVISION: 1
|
||||
DESCRIPTION: Install complete
|
||||
TEST SUITE: None
|
||||
1
pkg/cmd/testdata/output/template-schema-ref.txt
vendored
Normal file
1
pkg/cmd/testdata/output/template-schema-ref.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
8
pkg/cmd/testdata/output/upgrade-schema-ref.txt
vendored
Normal file
8
pkg/cmd/testdata/output/upgrade-schema-ref.txt
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
Release "reftest" has been upgraded. Happy Helming!
|
||||
NAME: reftest
|
||||
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
|
||||
NAMESPACE: default
|
||||
STATUS: deployed
|
||||
REVISION: 2
|
||||
DESCRIPTION: Upgrade complete
|
||||
TEST SUITE: None
|
||||
3
pkg/cmd/testdata/testcharts/chart-with-schema-ref/Chart.yaml
vendored
Normal file
3
pkg/cmd/testdata/testcharts/chart-with-schema-ref/Chart.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
apiVersion: v2
|
||||
name: chart-with-schema-ref
|
||||
version: 0.1.0
|
||||
4
pkg/cmd/testdata/testcharts/chart-with-schema-ref/name.schema.json
vendored
Normal file
4
pkg/cmd/testdata/testcharts/chart-with-schema-ref/name.schema.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "string"
|
||||
}
|
||||
7
pkg/cmd/testdata/testcharts/chart-with-schema-ref/values.schema.json
vendored
Normal file
7
pkg/cmd/testdata/testcharts/chart-with-schema-ref/values.schema.json
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "$ref": "name.schema.json" }
|
||||
}
|
||||
}
|
||||
1
pkg/cmd/testdata/testcharts/chart-with-schema-ref/values.yaml
vendored
Normal file
1
pkg/cmd/testdata/testcharts/chart-with-schema-ref/values.yaml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
name: "test"
|
||||
|
|
@ -187,6 +187,13 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
// Only set ChartDir for directory-based charts to enable $ref resolution.
|
||||
// Archived charts (.tgz) are loaded into memory without filesystem extraction,
|
||||
// so $ref resolution is not supported for them.
|
||||
if fi, err := os.Stat(chartPath); err == nil && fi.IsDir() {
|
||||
client.ChartDir = chartPath
|
||||
}
|
||||
|
||||
p := getter.All(settings)
|
||||
vals, err := valueOpts.MergeValues(p)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -190,6 +190,12 @@ func TestUpgradeCmd(t *testing.T) {
|
|||
golden: "output/upgrade-uninstalled-with-keep-history.txt",
|
||||
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, rcommon.StatusUninstalled)},
|
||||
},
|
||||
{
|
||||
name: "upgrade with schema file containing $ref",
|
||||
cmd: "upgrade reftest testdata/testcharts/chart-with-schema-ref",
|
||||
golden: "output/upgrade-schema-ref.txt",
|
||||
rels: []*release.Release{relMock("reftest", 1, ch)},
|
||||
},
|
||||
}
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue