Skip to content

Commit

Permalink
Merge pull request #662 from vmware-tanzu/data-value-schema-disable-v…
Browse files Browse the repository at this point in the history
…alidation

Data value schema disable validation
  • Loading branch information
pivotaljohn authored May 13, 2022
2 parents 1ad01a6 + acb6f06 commit 258de64
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 39 deletions.
146 changes: 146 additions & 0 deletions pkg/cmd/template/assert_validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,3 +702,149 @@ values: #@ data.values
})

}

func TestAssertValidateOnDataValuesAreSkippedWhenDisabled(t *testing.T) {
t.Run("via the --dangerous-data-values-disable-validation flag", func(t *testing.T) {
t.Run("in the root library", func(t *testing.T) {
opts := cmdtpl.NewOptions()
opts.DataValuesFlags.SkipValidation = true
opts.DataValuesFlags.Inspect = true
dataValuesYAML := `#@ load("@ytt:assert", "assert")
#@data/values
---
#@assert/validate ("nothing is valid", lambda v: False)
foo: bar
`
expected := `foo: bar
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("schema.yml", []byte(dataValuesYAML))),
})

assertSucceedsDocSet(t, filesToProcess, expected, opts)
})
t.Run("or in any evaluated private libraries, regardless", func(t *testing.T) {
opts := cmdtpl.NewOptions()
opts.DataValuesFlags.SkipValidation = true
configYAML := `
#@ load("@ytt:template", "template")
#@ load("@ytt:library", "library")
--- #@ template.replace(library.get("lib").eval())
#! even if validations are explicitly enabled...
--- #@ template.replace(library.get("lib", dangerous_data_values_disable_validation=False).eval())
`

libValuesYAML := `#@ load("@ytt:assert", "assert")
#@data/values
---
#@assert/validate ("nothing is valid", lambda v: False)
foo: bar
`

libConfigYAML := `
#@ load("@ytt:data", "data")
---
values: #@ data.values
`

expected := `values:
foo: bar
---
values:
foo: bar
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("config.yml", []byte(configYAML))),
files.MustNewFileFromSource(files.NewBytesSource("_ytt_lib/lib/values.yml", []byte(libValuesYAML))),
files.MustNewFileFromSource(files.NewBytesSource("_ytt_lib/lib/config.yml", []byte(libConfigYAML))),
})

assertSucceedsDocSet(t, filesToProcess, expected, opts)
})
})
t.Run("via the dangerous_data_values_disable_validation= kwarg", func(t *testing.T) {
t.Run("in the evaluated library", func(t *testing.T) {
opts := cmdtpl.NewOptions()
configYAML := `
#@ load("@ytt:template", "template")
#@ load("@ytt:library", "library")
--- #@ template.replace(library.get("lib", dangerous_data_values_disable_validation=True).eval())
`

libValuesYAML := `#@ load("@ytt:assert", "assert")
#@data/values
---
#@assert/validate ("nothing is valid", lambda v: False)
foo: bar
`

libConfigYAML := `
#@ load("@ytt:data", "data")
---
values: #@ data.values
`

expected := `values:
foo: bar
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("config.yml", []byte(configYAML))),
files.MustNewFileFromSource(files.NewBytesSource("_ytt_lib/lib/values.yml", []byte(libValuesYAML))),
files.MustNewFileFromSource(files.NewBytesSource("_ytt_lib/lib/config.yml", []byte(libConfigYAML))),
})

assertSucceedsDocSet(t, filesToProcess, expected, opts)
})
t.Run("or any of its dependencies, regardless", func(t *testing.T) {
opts := cmdtpl.NewOptions()
configYAML := `
#@ load("@ytt:template", "template")
#@ load("@ytt:library", "library")
--- #@ template.replace(library.get("foo", dangerous_data_values_disable_validation=True).eval())
`
fooConfigYAML := `
#@ load("@ytt:template", "template")
#@ load("@ytt:library", "library")
--- #@ template.replace(library.get("bar").eval())
--- #@ template.replace(library.get("bar", dangerous_data_values_disable_validation=False).eval())
`

barValuesYAML := `#@ load("@ytt:assert", "assert")
#@data/values
---
#@assert/validate ("nothing is valid", lambda v: False)
foo: bar
`

barConfigYAML := `
#@ load("@ytt:data", "data")
---
values: #@ data.values
`

expected := `values:
foo: bar
---
values:
foo: bar
`

filesToProcess := files.NewSortedFiles([]*files.File{
files.MustNewFileFromSource(files.NewBytesSource("config.yml", []byte(configYAML))),
files.MustNewFileFromSource(files.NewBytesSource("_ytt_lib/foo/config.yml", []byte(fooConfigYAML))),
files.MustNewFileFromSource(files.NewBytesSource("_ytt_lib/foo/_ytt_lib/bar/values.yml", []byte(barValuesYAML))),
files.MustNewFileFromSource(files.NewBytesSource("_ytt_lib/foo/_ytt_lib/bar/config.yml", []byte(barConfigYAML))),
})

assertSucceedsDocSet(t, filesToProcess, expected, opts)
})
})
}
16 changes: 11 additions & 5 deletions pkg/cmd/template/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
"github.com/vmware-tanzu/carvel-ytt/pkg/yamlmeta"
)

// Options both holds all settings for and implements the "template" command.
//
// For proper initialization, always use NewOptions().
type Options struct {
IgnoreUnknownComments bool
ImplicitMapKeyOverrides bool
Expand Down Expand Up @@ -112,11 +115,14 @@ func (o *Options) RunWithFiles(in Input, ui ui.UI) Output {
return Output{Err: err}
}

libraryExecutionFactory := workspace.NewLibraryExecutionFactory(ui, workspace.TemplateLoaderOpts{
IgnoreUnknownComments: o.IgnoreUnknownComments,
ImplicitMapKeyOverrides: o.ImplicitMapKeyOverrides,
StrictYAML: o.StrictYAML,
})
libraryExecutionFactory := workspace.NewLibraryExecutionFactory(
ui,
workspace.TemplateLoaderOpts{
IgnoreUnknownComments: o.IgnoreUnknownComments,
ImplicitMapKeyOverrides: o.ImplicitMapKeyOverrides,
StrictYAML: o.StrictYAML,
},
o.DataValuesFlags.SkipValidation)

libraryCtx := workspace.LibraryExecutionContext{Current: rootLibrary, Root: rootLibrary}
rootLibraryExecution := libraryExecutionFactory.New(libraryCtx)
Expand Down
11 changes: 8 additions & 3 deletions pkg/cmd/template/data_values_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/k14s/starlark-go/starlark"
"github.com/vmware-tanzu/carvel-ytt/pkg/experiments"
"github.com/vmware-tanzu/carvel-ytt/pkg/filepos"
"github.com/vmware-tanzu/carvel-ytt/pkg/files"
"github.com/vmware-tanzu/carvel-ytt/pkg/template"
Expand All @@ -35,14 +36,15 @@ type DataValuesFlags struct {

FromFiles []string

Inspect bool
InspectSchema bool
Inspect bool
InspectSchema bool
SkipValidation bool

EnvironFunc func() []string
ReadFileFunc func(string) ([]byte, error)
}

// Set registers data valuse ingestion flags and wires-up those flags up to this
// Set registers data values ingestion flags and wires-up those flags up to this
// DataValuesFlags to be set when the corresponding cobra.Command is executed.
func (s *DataValuesFlags) Set(cmdFlags CmdFlags) {
cmdFlags.StringArrayVar(&s.EnvFromStrings, "data-values-env", nil, "Extract data values (as strings) from prefixed env vars (format: PREFIX for PREFIX_all__key1=str) (can be specified multiple times)")
Expand All @@ -55,6 +57,9 @@ func (s *DataValuesFlags) Set(cmdFlags CmdFlags) {
cmdFlags.StringArrayVar(&s.FromFiles, "data-values-file", nil, "Set multiple data values via a YAML file (format: /file/path.yml) (can be specified multiple times)")

cmdFlags.BoolVar(&s.Inspect, "data-values-inspect", false, "Calculate the final data values (applying any overlays) and display that result")
if experiments.IsValidationsEnabled() {
cmdFlags.BoolVar(&s.SkipValidation, "dangerous-data-values-disable-validation", false, "Skip validating data values (not recommended: may result in templates failing or invalid output)")
}
cmdFlags.BoolVar(&s.InspectSchema, "data-values-schema-inspect", false, "Determine the complete schema for data values (applying any overlays) and display the result (only OpenAPI v3.0 is supported, see --output)")
}

Expand Down
34 changes: 20 additions & 14 deletions pkg/workspace/library_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ import (
"github.com/vmware-tanzu/carvel-ytt/pkg/yamlmeta"
)

// LibraryExecution is the total set of configuration and dependencies that are used to accomplish the execution of a
// Library.
type LibraryExecution struct {
libraryCtx LibraryExecutionContext
ui ui.UI
templateLoaderOpts TemplateLoaderOpts
libraryExecFactory *LibraryExecutionFactory
libraryCtx LibraryExecutionContext
ui ui.UI
templateLoaderOpts TemplateLoaderOpts
libraryExecFactory *LibraryExecutionFactory
skipDataValuesValidation bool // when true, any validation rules present on data values are skipped
}

type EvalResult struct {
Expand All @@ -34,15 +37,15 @@ type EvalExport struct {
Symbols starlark.StringDict
}

func NewLibraryExecution(libraryCtx LibraryExecutionContext,
ui ui.UI, templateLoaderOpts TemplateLoaderOpts,
libraryExecFactory *LibraryExecutionFactory) *LibraryExecution {
// NewLibraryExecution configures a new instance of a LibraryExecution.
func NewLibraryExecution(libraryCtx LibraryExecutionContext, ui ui.UI, templateLoaderOpts TemplateLoaderOpts, libraryExecFactory *LibraryExecutionFactory, skipDataValuesValidation bool) *LibraryExecution {

return &LibraryExecution{
libraryCtx: libraryCtx,
ui: ui,
templateLoaderOpts: templateLoaderOpts,
libraryExecFactory: libraryExecFactory,
libraryCtx: libraryCtx,
ui: ui,
templateLoaderOpts: templateLoaderOpts,
libraryExecFactory: libraryExecFactory,
skipDataValuesValidation: skipDataValuesValidation,
}
}

Expand Down Expand Up @@ -95,7 +98,10 @@ func (ll *LibraryExecution) Values(valuesOverlays []*datavalues.Envelope, schema
return nil, nil, err
}

return values, libValues, ll.validateValues(values)
if !ll.skipDataValuesValidation {
err = ll.validateValues(values)
}
return values, libValues, err
}

// validateValues runs validations from @assert/validate annotations in Data Values for the current library.
Expand Down Expand Up @@ -150,8 +156,8 @@ func (ll *LibraryExecution) filesByAnnotation(annName template.AnnotationName, l
return valuesFiles, nil
}

// Eval runs this LibraryExecution, evaluating all templates in this library and then applying overlays over that
// result.
// Eval given the final data values (as the parameter values) runs this LibraryExecution, evaluating all templates in
// this library and then applying overlays over that result.
//
// Returns the final set of Documents and output files.
// Returns an error if any template fails to evaluate, any overlay fails to apply, or if one or more "Envelopes" were
Expand Down
30 changes: 24 additions & 6 deletions pkg/workspace/library_execution_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,42 @@ import (
"github.com/vmware-tanzu/carvel-ytt/pkg/cmd/ui"
)

// LibraryExecutionContext holds the total set of inputs that are involved in a LibraryExecution.
type LibraryExecutionContext struct {
Current *Library
Root *Library
Current *Library // the target library that will be executed/evaluated.
Root *Library // reference to the root library to support accessing "absolute path" loading of files.
}

// LibraryExecutionFactory holds configuration for and produces instances of LibraryExecution's.
type LibraryExecutionFactory struct {
ui ui.UI
templateLoaderOpts TemplateLoaderOpts

skipDataValuesValidation bool
}

func NewLibraryExecutionFactory(ui ui.UI, templateLoaderOpts TemplateLoaderOpts) *LibraryExecutionFactory {
return &LibraryExecutionFactory{ui, templateLoaderOpts}
// NewLibraryExecutionFactory configures a new instance of a LibraryExecutionFactory.
func NewLibraryExecutionFactory(ui ui.UI, templateLoaderOpts TemplateLoaderOpts, skipDataValuesValidation bool) *LibraryExecutionFactory {
return &LibraryExecutionFactory{ui, templateLoaderOpts, skipDataValuesValidation}
}

// WithTemplateLoaderOptsOverrides produces a new LibraryExecutionFactory identical to this one, except it configures
// its TemplateLoader with the merge of the supplied TemplateLoaderOpts over this factory's configuration.
func (f *LibraryExecutionFactory) WithTemplateLoaderOptsOverrides(overrides TemplateLoaderOptsOverrides) *LibraryExecutionFactory {
return NewLibraryExecutionFactory(f.ui, f.templateLoaderOpts.Merge(overrides))
return NewLibraryExecutionFactory(f.ui, f.templateLoaderOpts.Merge(overrides), f.skipDataValuesValidation)
}

// ThatSkipsDataValuesValidations produces a new LibraryExecutionFactory identical to this one, except it might also
// skip running validation rules over Data Values.
//
// If a LibraryExecutionFactory has already been configured to skip validations, calling this method with `true` has
// no effect. This stems from the assumption that the downstream user is the most informed whether validations ought to
// be run.
func (f *LibraryExecutionFactory) ThatSkipsDataValuesValidations(skipDataValuesValidation bool) *LibraryExecutionFactory {
return NewLibraryExecutionFactory(f.ui, f.templateLoaderOpts, f.skipDataValuesValidation || skipDataValuesValidation)
}

// New produces a new instance of a LibraryExecution, set with the configuration and dependencies of this factory.
func (f *LibraryExecutionFactory) New(ctx LibraryExecutionContext) *LibraryExecution {
return NewLibraryExecution(ctx, f.ui, f.templateLoaderOpts, f)
return NewLibraryExecution(ctx, f.ui, f.templateLoaderOpts, f, f.skipDataValuesValidation)
}
Loading

0 comments on commit 258de64

Please sign in to comment.