Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add zarf prepare lint to perform schema validation #2075

Merged
merged 106 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
9e83f8f
early WIP have an example of a yaml file being read and validated
AustinAbro321 Oct 16, 2023
1a77654
refactor
AustinAbro321 Oct 16, 2023
fb3a56e
WIP refactor
AustinAbro321 Oct 16, 2023
353ed3f
refactor validate schema to work with unmarshalled yaml
AustinAbro321 Oct 16, 2023
719bf9a
wip
AustinAbro321 Oct 16, 2023
ccf26ad
wip
AustinAbro321 Oct 17, 2023
610c088
WIP have test working with sucessful and unsuccsful example
AustinAbro321 Oct 19, 2023
db1c19a
adding bad zarf file for testing
AustinAbro321 Oct 19, 2023
faebd0a
WIP
AustinAbro321 Oct 19, 2023
8ea6213
merge
AustinAbro321 Oct 19, 2023
063c438
refactor schema unit tests
AustinAbro321 Oct 19, 2023
96efdb8
moving lint
AustinAbro321 Oct 19, 2023
6485174
WIP
AustinAbro321 Oct 19, 2023
93238ed
WIP e2e tests functional
AustinAbro321 Oct 19, 2023
ad05c6b
fix test lint
AustinAbro321 Oct 20, 2023
5b28577
lint tests working
AustinAbro321 Oct 20, 2023
bb8eaf6
adding tests, adding success mesage, taking away negative lookahead r…
AustinAbro321 Oct 24, 2023
e28be8e
add yamls for tests
AustinAbro321 Oct 24, 2023
aec9660
Merge branch 'main' into validate-schema-1667
AustinAbro321 Oct 24, 2023
e2a632d
yaml extension partially tested
AustinAbro321 Nov 1, 2023
09d1c07
moving lint back to prepare
AustinAbro321 Nov 1, 2023
9cc9d26
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 1, 2023
61e8f60
WIP test working
AustinAbro321 Nov 6, 2023
35c8e09
lint feature is working
AustinAbro321 Nov 7, 2023
fa03f35
refactoring
AustinAbro321 Nov 7, 2023
a0c307d
adding comment to exported function
AustinAbro321 Nov 7, 2023
3376a3e
unexporting validate schema
AustinAbro321 Nov 7, 2023
73d4de8
moving lint to test 11
AustinAbro321 Nov 7, 2023
8d0a09f
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 7, 2023
d6dc0b5
tests working
AustinAbro321 Nov 7, 2023
0d414ee
warning if someone has zarf_pkg_tmpl somewhere
AustinAbro321 Nov 7, 2023
14e9804
refactor
AustinAbro321 Nov 7, 2023
78b76f0
changing format of message
AustinAbro321 Nov 7, 2023
03ea445
fixing comment
AustinAbro321 Nov 7, 2023
ec1eb04
fix comment
AustinAbro321 Nov 7, 2023
f5f484f
comment
AustinAbro321 Nov 7, 2023
b82f857
refactor tests
AustinAbro321 Nov 7, 2023
347a8d1
refactor tests
AustinAbro321 Nov 8, 2023
fba2afe
refactor e2e, make docs and schema
AustinAbro321 Nov 8, 2023
84e82c3
refactor e2e
AustinAbro321 Nov 8, 2023
094a192
refactor to use sprintf
AustinAbro321 Nov 8, 2023
3b3aa75
exporting template var string
AustinAbro321 Nov 8, 2023
5a0028e
Merge branch 'main' into validate-schema-1667
bdw617 Nov 8, 2023
fa6ff16
moved lint into packager
AustinAbro321 Nov 8, 2023
317a809
Merge branch 'validate-schema-1667' of github.com:defenseunicorns/zar…
AustinAbro321 Nov 8, 2023
02a39b7
move 11 to 12
AustinAbro321 Nov 8, 2023
3bd8a7d
with files this time
AustinAbro321 Nov 8, 2023
5013c9f
changing to use more maintained json package
AustinAbro321 Nov 9, 2023
5b67dea
removing old jsonschema package
AustinAbro321 Nov 9, 2023
1e99845
changed language
AustinAbro321 Nov 9, 2023
94fad1c
make docs
AustinAbro321 Nov 9, 2023
b27054e
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 9, 2023
4750a1a
refactor tests
AustinAbro321 Nov 9, 2023
167a78d
refactor func name
AustinAbro321 Nov 9, 2023
762c34e
refactor lint
AustinAbro321 Nov 9, 2023
eb2ae98
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 13, 2023
d1aa79c
Merge branch 'main' into validate-schema-1667
Racer159 Nov 13, 2023
6d0e927
Merge branch 'validate-schema-1667' of github.com:defenseunicorns/zar…
AustinAbro321 Nov 13, 2023
c8a66e2
matching style guide better
AustinAbro321 Nov 14, 2023
f22c59a
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 14, 2023
527c26b
removing local helm plugin so docs are correct
AustinAbro321 Nov 14, 2023
251b5e8
changing back to xeipuuv/gojsonschema
AustinAbro321 Nov 15, 2023
509e719
changing lint tests to be from yaml string rather than file
AustinAbro321 Nov 15, 2023
5a42ffa
adding validator, moving to defenseunicorns/jsonschema
AustinAbro321 Nov 16, 2023
48acf97
fix file path unit tests
AustinAbro321 Nov 16, 2023
7d695f9
moving some magic strings into types
AustinAbro321 Nov 16, 2023
1806957
fix comment
AustinAbro321 Nov 16, 2023
0db8eef
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 16, 2023
269c118
refactoring
AustinAbro321 Nov 16, 2023
2e785cf
refactor validator
AustinAbro321 Nov 16, 2023
37f92d9
Merge branch 'main' into validate-schema-1667
Racer159 Nov 16, 2023
743ecd7
WIP introducing validator struct
AustinAbro321 Nov 17, 2023
d240e86
tests working with refactored validator, still more refactoring todo
AustinAbro321 Nov 17, 2023
a02647a
refactoring
AustinAbro321 Nov 17, 2023
a9eb84f
validator refactor
AustinAbro321 Nov 17, 2023
386e1ff
Merge branch 'validate-schema-1667' of github.com:defenseunicorns/zar…
AustinAbro321 Nov 17, 2023
50e85a0
refactoring validator
AustinAbro321 Nov 17, 2023
acbf48e
change commment
AustinAbro321 Nov 17, 2023
739aaa9
moving errors to type error
AustinAbro321 Nov 17, 2023
1dac9bc
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 17, 2023
48eaf4b
renaming package to lint to make it easier to different object and pa…
AustinAbro321 Nov 17, 2023
6b9d3f9
moving get schema file inside of lint
AustinAbro321 Nov 17, 2023
bf58868
making warnings with numbers work with yq
AustinAbro321 Nov 17, 2023
bc4eb7b
changign validatezarfschema to return a pointer
AustinAbro321 Nov 27, 2023
ee5cce0
separating validator object into it's own file
AustinAbro321 Nov 27, 2023
bce2796
lint comment
AustinAbro321 Nov 27, 2023
0bd8d4a
implementing error in validate
AustinAbro321 Nov 27, 2023
4eb7fc1
updating zarf docs for lint
AustinAbro321 Nov 27, 2023
63bcb8c
refactoring message to use table function and printing a table for wa…
AustinAbro321 Nov 27, 2023
3106628
formmating lint table
AustinAbro321 Nov 28, 2023
7443793
exported function comment
AustinAbro321 Nov 28, 2023
515c2f2
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 28, 2023
714ba99
exiting when there is a warning or error
AustinAbro321 Nov 28, 2023
a6c3ad7
Merge branch 'main' into validate-schema-1667
Racer159 Nov 29, 2023
5b549d2
Apply suggestions from code review
AustinAbro321 Nov 29, 2023
da445e5
fixing language to be lint
AustinAbro321 Nov 29, 2023
ca7cc1b
changing order of functions
AustinAbro321 Nov 29, 2023
3a95e03
refactor validate schema
AustinAbro321 Nov 29, 2023
37df5f7
making error strings yqable
AustinAbro321 Nov 29, 2023
47e724d
fix .(root) exit only on error now
AustinAbro321 Nov 29, 2023
8e4a723
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 29, 2023
170938f
make docs and schema
AustinAbro321 Nov 29, 2023
140a5b8
refactor validator
AustinAbro321 Nov 29, 2023
b7a7b4c
make warning actually yq compatiable, refactor message
AustinAbro321 Nov 29, 2023
ec0a969
Merge branch 'main' into validate-schema-1667
AustinAbro321 Nov 29, 2023
a19e30d
Update docs/3-create-a-zarf-package/index.md
AustinAbro321 Nov 29, 2023
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
4 changes: 4 additions & 0 deletions docs/2-the-zarf-cli/100-cli-commands/zarf_prepare_lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

Verifies the package schema

## Synopsis

Verifies the package schema and warns the user if they have variables that won't be evaluated

```
zarf prepare lint [ DIRECTORY ] [flags]
```
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ var lintCmd = &cobra.Command{
Args: cobra.MaximumNArgs(1),
Aliases: []string{"l"},
Short: lang.CmdPrepareLintShort,
Long: lang.CmdPrepareLintLong,
Run: func(cmd *cobra.Command, args []string) {
baseDir := ""
AustinAbro321 marked this conversation as resolved.
Show resolved Hide resolved
if len(args) > 0 {
Expand All @@ -219,7 +220,7 @@ var lintCmd = &cobra.Command{
var err error
baseDir, err = os.Getwd()
if err != nil {
message.Fatalf(err, lang.CmdPrepareFindImagesErr, err.Error())
message.Fatalf(err, lang.CmdPrepareLintErr, err.Error())
}
}
validator, err := lint.ValidateZarfSchema(baseDir)
Expand Down
2 changes: 2 additions & 0 deletions src/config/lang/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ $ zarf package publish ./path/to/dir oci://my-registry.com/my-namespace
CmdPrepareFlagKubeVersion = "Override the default helm template KubeVersion when performing a package chart template"

CmdPrepareLintShort = "Verifies the package schema"
AustinAbro321 marked this conversation as resolved.
Show resolved Hide resolved
CmdPrepareLintLong = "Verifies the package schema and warns the user if they have variables that won't be evaluated"
CmdPrepareLintErr = "Unable to lint package: %s"

// zarf tools
CmdToolsShort = "Collection of additional tools to make airgap easier"
Expand Down
44 changes: 22 additions & 22 deletions src/pkg/message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,28 @@ func Truncate(text string, length int, invert bool) string {
return textEscaped
}

// Table prints a padded table containing the specified header and data
func Table(header []string, data [][]string) {
pterm.Println()

if len(header) > 0 {
header[0] = fmt.Sprintf(" %s", header[0])
}

table := pterm.TableData{
header,
}

for _, row := range data {
if len(row) > 0 {
row[0] = fmt.Sprintf(" %s", row[0])
}
table = append(table, pterm.TableData{row}...)
}

pterm.DefaultTable.WithHasHeader().WithData(table).Render()
}

func debugPrinter(offset int, a ...any) {
printer := pterm.Debug.WithShowLineNumber(logLevel > 2).WithLineNumberOffset(offset)
now := time.Now().Format(time.RFC3339)
Expand All @@ -340,25 +362,3 @@ func debugPrinter(offset int, a ...any) {
func errorPrinter(offset int) *pterm.PrefixPrinter {
return pterm.Error.WithShowLineNumber(logLevel > 2).WithLineNumberOffset(offset)
}

// Table prints a padded table containing the specified header and data
func Table(header []string, data [][]string) {
pterm.Println()

if len(header) > 0 {
header[0] = fmt.Sprintf(" %s", header[0])
}

table := pterm.TableData{
header,
}

for _, row := range data {
if len(row) > 0 {
row[0] = fmt.Sprintf(" %s", row[0])
}
table = append(table, pterm.TableData{row}...)
}

pterm.DefaultTable.WithHasHeader().WithData(table).Render()
}
36 changes: 23 additions & 13 deletions src/pkg/packager/lint/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ package lint

import (
"embed"
"errors"
"fmt"
"path/filepath"
"regexp"
"strings"

"github.com/defenseunicorns/zarf/src/pkg/layout"
Expand All @@ -17,11 +17,6 @@ import (
"github.com/xeipuuv/gojsonschema"
)

const (
validatorInvalidPrefix = "schema is invalid:"
validatorWarningPrefix = "zarf schema warning:"
)

// ZarfSchema is exported so main.go can embed the schema file
var ZarfSchema embed.FS

Expand All @@ -35,21 +30,21 @@ func ValidateZarfSchema(path string) (*Validator, error) {
validator := Validator{}
var err error
if err := utils.ReadYaml(filepath.Join(path, layout.ZarfYAML), &validator.typedZarfPackage); err != nil {
return &validator, err
return nil, err
}

checkForVarInComponentImport(&validator)

if validator.jsonSchema, err = getSchemaFile(); err != nil {
return &validator, err
return nil, err
}

if err := utils.ReadYaml(filepath.Join(path, layout.ZarfYAML), &validator.untypedZarfPackage); err != nil {
return &validator, err
return nil, err
}

if err = validateSchema(&validator); err != nil {
return &validator, err
return nil, err
}

return &validator, nil
Expand All @@ -58,15 +53,28 @@ func ValidateZarfSchema(path string) (*Validator, error) {
func checkForVarInComponentImport(validator *Validator) {
for i, component := range validator.typedZarfPackage.Components {
if strings.Contains(component.Import.Path, types.ZarfPackageTemplatePrefix) {
validator.addWarning(fmt.Sprintf("component.[%d].import.path will not resolve ZARF_PKG_TMPL_* variables", i))
validator.addWarning(fmt.Sprintf(".component.[%d].import.path: Will not resolve ZARF_PKG_TMPL_* variables", i))
AustinAbro321 marked this conversation as resolved.
Show resolved Hide resolved
}
if strings.Contains(component.Import.URL, types.ZarfPackageTemplatePrefix) {
validator.addWarning(fmt.Sprintf("component.[%d].import.url will not resolve ZARF_PKG_TMPL_* variables", i))
validator.addWarning(fmt.Sprintf(".component.[%d].import.url: Will not resolve ZARF_PKG_TMPL_* variables", i))
}
}

}

func makeFieldPathYqCompat(field string) string {
if field == "(root)" {
return field
}
// \b is a metacharacter that will stop at the next non-word character (including .)
// https://regex101.com/r/pIRPk0/1
re := regexp.MustCompile(`(\b\d+\b)`)

wrappedField := re.ReplaceAllString(field, "[$1]")

return fmt.Sprintf(".%s", wrappedField)
}

func validateSchema(validator *Validator) error {
schemaLoader := gojsonschema.NewBytesLoader(validator.jsonSchema)
documentLoader := gojsonschema.NewGoLoader(validator.untypedZarfPackage)
Expand All @@ -78,7 +86,9 @@ func validateSchema(validator *Validator) error {

if !result.Valid() {
for _, desc := range result.Errors() {
validator.addError(errors.New(desc.String()))
err := fmt.Errorf(
"%s: %s", makeFieldPathYqCompat(desc.Field()), desc.Description())
validator.addError(err)
}
}

Expand Down
24 changes: 18 additions & 6 deletions src/pkg/packager/lint/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func readAndUnmarshalYaml[T interface{}](t *testing.T, yamlString string) T {
var unmarshalledYaml T
err := goyaml.Unmarshal([]byte(yamlString), &unmarshalledYaml)
if err != nil {
t.Errorf("error unmarshalling yaml %v", err)
t.Errorf("error unmarshalling yaml: %v", err)
}
return unmarshalledYaml
}
Expand All @@ -61,7 +61,7 @@ func TestValidateSchema(t *testing.T) {
t.Helper()
file, err := os.ReadFile("../../../../zarf.schema.json")
if err != nil {
t.Errorf("error reading file: %s", err)
t.Errorf("error reading file: %v", err)
}
return file
}
Expand All @@ -79,8 +79,8 @@ func TestValidateSchema(t *testing.T) {
validator := Validator{untypedZarfPackage: unmarshalledYaml, jsonSchema: getZarfSchema(t)}
err := validateSchema(&validator)
require.NoError(t, err)
require.EqualError(t, validator.errors[0], "components.0.import: Additional property not-path is not allowed")
require.EqualError(t, validator.errors[1], "components.1.import.path: Invalid type. Expected: string, given: integer")
require.EqualError(t, validator.errors[0], ".components.[0].import: Additional property not-path is not allowed")
require.EqualError(t, validator.errors[1], ".components.[1].import.path: Invalid type. Expected: string, given: integer")
})

t.Run("Template in component import success", func(t *testing.T) {
Expand All @@ -94,8 +94,20 @@ func TestValidateSchema(t *testing.T) {
unmarshalledYaml := readAndUnmarshalYaml[types.ZarfPackage](t, badZarfPackage)
validator := Validator{typedZarfPackage: unmarshalledYaml}
checkForVarInComponentImport(&validator)
require.Equal(t, validator.warnings[0], "component.[2].import.path will not resolve ZARF_PKG_TMPL_* variables")
require.Equal(t, validator.warnings[1], "component.[3].import.url will not resolve ZARF_PKG_TMPL_* variables")
require.Equal(t, validator.warnings[0], ".component.[2].import.path: Will not resolve ZARF_PKG_TMPL_* variables")
require.Equal(t, validator.warnings[1], ".component.[3].import.url: Will not resolve ZARF_PKG_TMPL_* variables")
})

t.Run("Wrap standalone numbers in bracket", func(t *testing.T) {
input := "components12.12.import.path"
expected := ".components12.[12].import.path"
acutal := makeFieldPathYqCompat(input)
require.Equal(t, expected, acutal)
})

t.Run("root doesn't change", func(t *testing.T) {
input := "(root)"
acutal := makeFieldPathYqCompat(input)
require.Equal(t, input, acutal)
})
}
36 changes: 15 additions & 21 deletions src/pkg/packager/lint/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,21 @@ type Validator struct {
untypedZarfPackage interface{}
}

func (v Validator) printWarningTable() {
if !v.IsSuccess() {
// DisplayFormattedMessage message sent to user based on validator results
func (v Validator) DisplayFormattedMessage() {
if !v.hasWarnings() && !v.hasErrors() {
message.Success(fmt.Sprintf("Schema validation successful for %q", v.typedZarfPackage.Metadata.Name))
AustinAbro321 marked this conversation as resolved.
Show resolved Hide resolved
}
v.printValidationTable()
}

// IsSuccess returns true if there are not any errors
func (v Validator) IsSuccess() bool {
return !v.hasErrors()
}

func (v Validator) printValidationTable() {
if v.hasWarnings() || v.hasErrors() {
header := []string{"Type", "Message"}
connectData := [][]string{}
for _, warning := range v.warnings {
Expand All @@ -38,10 +51,6 @@ func (v Validator) printWarningTable() {
}
}

func (v Validator) getFormatedSuccess() string {
return fmt.Sprintf("Schema validation successful for %q", v.typedZarfPackage.Metadata.Name)
}

func (v Validator) hasWarnings() bool {
return len(v.warnings) > 0
}
Expand All @@ -57,18 +66,3 @@ func (v *Validator) addWarning(message string) {
func (v *Validator) addError(err error) {
v.errors = append(v.errors, err)
}

// IsSuccess returns true if there are not any warnings or errors
func (v Validator) IsSuccess() bool {
return !v.hasWarnings() && !v.hasErrors()
}

// DisplayFormattedMessage Displays the message to the user with proper warnings, failures, or success
// Will exit if there are errors
func (v Validator) DisplayFormattedMessage() {
if v.IsSuccess() {
message.Success(v.getFormatedSuccess())
} else {
v.printWarningTable()
}
}
5 changes: 3 additions & 2 deletions src/test/e2e/12_lint_test.go
AustinAbro321 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ func TestLint(t *testing.T) {
path := filepath.Join("src", "test", "packages", "12-lint")
_, stderr, err := e2e.Zarf("prepare", "lint", path)
require.Error(t, err, "Require an exit code since there was warnings / errors")
require.Contains(t, stderr, "components.0.import: Additional property not-path is not allowed")
require.Contains(t, stderr, "component.[2].import.path")
require.Contains(t, stderr, ".components.[0].import: Additional property not-path is not allowed")
require.Contains(t, stderr, "component.[2].import.path: Will not resolve ZARF_PKG_TMPL_* variables")
require.Contains(t, stderr, ".variables: Invalid type. Expected: array, given: null")
})

t.Run("zarf test lint success", func(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion src/test/packages/12-lint/zarf.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
kind: ZarfInitConfig
metadata:
name: init
description: Testing bad yaml
description1: Testing bad yaml


variables:

components:
- name: first-test-component
Expand Down
Loading