Repository: datreeio/datree
Pull request #421 (2022-02-20 11:39:25)

feat: create yaml schema validation command


Patch:
diff --git a/bl/evaluation/evaluator.go b/bl/evaluation/evaluator.go
index 6d30e1f..c701473 100644
--- a/bl/evaluation/evaluator.go
+++ b/bl/evaluation/evaluator.go
@@ -1,7 +1,6 @@
 package evaluation
 
 import (
-	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/ciContext"
 	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/datreeio/datree/pkg/extractor"
@@ -60,7 +59,7 @@ func (e *Evaluator) CreateEvaluation(cliId string, cliVersion string, k8sVersion
 	return createEvaluationResponse, err
 }
 
-func (e *Evaluator) UpdateFailedYamlValidation(invalidFiles []*validation.InvalidYamlFile, evaluationId int, stopEvaluation bool) error {
+func (e *Evaluator) UpdateFailedYamlValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
 	invalidFilesPaths := []*string{}
 	for _, file := range invalidFiles {
 		invalidFilesPaths = append(invalidFilesPaths, &file.Path)
@@ -73,7 +72,7 @@ func (e *Evaluator) UpdateFailedYamlValidation(invalidFiles []*validation.Invali
 	return err
 }
 
-func (e *Evaluator) UpdateFailedK8sValidation(invalidFiles []*validation.InvalidK8sFile, evaluationId int, stopEvaluation bool) error {
+func (e *Evaluator) UpdateFailedK8sValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
 	invalidFilesPaths := []*string{}
 	for _, file := range invalidFiles {
 		invalidFilesPaths = append(invalidFilesPaths, &file.Path)
diff --git a/bl/evaluation/non_interactive_evaluator_types.go b/bl/evaluation/non_interactive_evaluator_types.go
index fa9d758..5efb475 100644
--- a/bl/evaluation/non_interactive_evaluator_types.go
+++ b/bl/evaluation/non_interactive_evaluator_types.go
@@ -1,15 +1,15 @@
 package evaluation
 
 import (
-	"github.com/datreeio/datree/bl/validation"
+	"github.com/datreeio/datree/pkg/extractor"
 )
 
 type FormattedOutput struct {
 	PolicyValidationResults []*FormattedEvaluationResults   `yaml:"policyValidationResults" json:"policyValidationResults" xml:"policyValidationResults"`
 	PolicySummary           *PolicySummary                  `yaml:"policySummary" json:"policySummary" xml:"policySummary"`
 	EvaluationSummary       NonInteractiveEvaluationSummary `yaml:"evaluationSummary" json:"evaluationSummary" xml:"evaluationSummary"`
-	YamlValidationResults   []*validation.InvalidYamlFile   `yaml:"yamlValidationResults" json:"yamlValidationResults" xml:"yamlValidationResults"`
-	K8sValidationResults    []*validation.InvalidK8sFile    `yaml:"k8sValidationResults" json:"k8sValidationResults" xml:"k8sValidationResults"`
+	YamlValidationResults   []*extractor.InvalidFile        `yaml:"yamlValidationResults" json:"yamlValidationResults" xml:"yamlValidationResults"`
+	K8sValidationResults    []*extractor.InvalidFile        `yaml:"k8sValidationResults" json:"k8sValidationResults" xml:"k8sValidationResults"`
 }
 
 type NonInteractiveEvaluationResults struct {
diff --git a/bl/evaluation/printer.go b/bl/evaluation/printer.go
index 85c36be..4b9d0b6 100644
--- a/bl/evaluation/printer.go
+++ b/bl/evaluation/printer.go
@@ -9,7 +9,7 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/datreeio/datree/bl/validation"
+	"github.com/datreeio/datree/pkg/extractor"
 
 	"github.com/datreeio/datree/pkg/printer"
 	"gopkg.in/yaml.v2"
@@ -21,7 +21,7 @@ type Printer interface {
 	PrintEvaluationSummary(summary printer.EvaluationSummary, k8sVersion string)
 }
 
-func PrintResults(results ResultType, invalidYamlFiles []*validation.InvalidYamlFile, invalidK8sFiles []*validation.InvalidK8sFile, evaluationSummary printer.EvaluationSummary, loginURL string, outputFormat string, printer Printer, k8sVersion string, policyName string) error {
+func PrintResults(results ResultType, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, evaluationSummary printer.EvaluationSummary, loginURL string, outputFormat string, printer Printer, k8sVersion string, policyName string) error {
 	if outputFormat == "json" || outputFormat == "yaml" || outputFormat == "xml" {
 		nonInteractiveEvaluationResults := results.NonInteractiveEvaluationResults
 		if nonInteractiveEvaluationResults == nil {
@@ -87,7 +87,7 @@ func xmlOutput(formattedOutput *FormattedOutput) error {
 	return nil
 }
 
-func textOutput(results *EvaluationResults, invalidYamlFiles []*validation.InvalidYamlFile, invalidK8sFiles []*validation.InvalidK8sFile, evaluationSummary printer.EvaluationSummary, url string, printer Printer, k8sVersion string, policyName string) error {
+func textOutput(results *EvaluationResults, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, evaluationSummary printer.EvaluationSummary, url string, printer Printer, k8sVersion string, policyName string) error {
 	pwd, err := os.Getwd()
 	if err != nil {
 		return err
@@ -110,7 +110,7 @@ func textOutput(results *EvaluationResults, invalidYamlFiles []*validation.Inval
 	return nil
 }
 
-func parseInvalidYamlFilesToWarnings(invalidYamlFiles []*validation.InvalidYamlFile) []printer.Warning {
+func parseInvalidYamlFilesToWarnings(invalidYamlFiles []*extractor.InvalidFile) []printer.Warning {
 	var warnings []printer.Warning
 
 	for _, invalidFile := range invalidYamlFiles {
@@ -126,7 +126,7 @@ func parseInvalidYamlFilesToWarnings(invalidYamlFiles []*validation.InvalidYamlF
 	return warnings
 }
 
-func parseInvalidK8sFilesToWarnings(invalidK8sFiles []*validation.InvalidK8sFile, k8sVersion string) []printer.Warning {
+func parseInvalidK8sFilesToWarnings(invalidK8sFiles []*extractor.InvalidFile, k8sVersion string) []printer.Warning {
 	var warnings []printer.Warning
 
 	for _, invalidFile := range invalidK8sFiles {
@@ -144,7 +144,7 @@ func parseInvalidK8sFilesToWarnings(invalidK8sFiles []*validation.InvalidK8sFile
 	return warnings
 }
 
-func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*validation.InvalidYamlFile, invalidK8sFiles []*validation.InvalidK8sFile, pwd string, k8sVersion string) ([]printer.Warning, error) {
+func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*extractor.InvalidFile, invalidK8sFiles []*extractor.InvalidFile, pwd string, k8sVersion string) ([]printer.Warning, error) {
 	var warnings = []printer.Warning{}
 
 	warnings = append(warnings, parseInvalidYamlFilesToWarnings(invalidYamlFiles)...)
@@ -201,7 +201,7 @@ func parseToPrinterWarnings(results *EvaluationResults, invalidYamlFiles []*vali
 	return warnings, nil
 }
 
-func GetWarningExtraMessages(invalidFile *validation.InvalidK8sFile) []printer.ExtraMessage {
+func GetWarningExtraMessages(invalidFile *extractor.InvalidFile) []printer.ExtraMessage {
 	var extraMessages []printer.ExtraMessage
 
 	if IsHelmFile(invalidFile.Path) {
diff --git a/bl/files/files.go b/bl/files/files.go
index 430e8e5..bd38935 100644
--- a/bl/files/files.go
+++ b/bl/files/files.go
@@ -2,35 +2,17 @@ package files
 
 import (
 	"bytes"
-	"fmt"
-	"os"
-	"path/filepath"
-
 	"gopkg.in/yaml.v3"
+	"os"
 
-	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/extractor"
 )
 
 type UnknownStruct map[string]interface{}
 
-func ToAbsolutePath(path string) (string, error) {
-	absolutePath, err := filepath.Abs(path)
-	if err != nil {
-		return "", err
-	}
-
-	fileInfo, _ := os.Stat(absolutePath)
-	if fileInfo != nil && !fileInfo.IsDir() {
-		return filepath.Abs(absolutePath)
-	}
-
-	return "", fmt.Errorf("failed parsing absolute path %s", path)
-}
-
-func ExtractFilesConfigurations(paths []string, concurrency int) (chan *extractor.FileConfigurations, chan *validation.InvalidYamlFile) {
+func ExtractFilesConfigurations(paths []string, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile) {
 	filesConfigurationsChan := make(chan *extractor.FileConfigurations, concurrency)
-	invalidFilesChan := make(chan *validation.InvalidYamlFile, concurrency)
+	invalidFilesChan := make(chan *extractor.InvalidFile, concurrency)
 
 	go func() {
 		defer func() {
@@ -40,21 +22,10 @@ func ExtractFilesConfigurations(paths []string, concurrency int) (chan *extracto
 
 		for _, path := range paths {
 
-			absolutePath, err := ToAbsolutePath(path)
-			if err != nil {
-				invalidFilesChan <- &validation.InvalidYamlFile{Path: path, ValidationErrors: []error{&validation.InvalidYamlError{ErrorMessage: err.Error()}}}
-				continue
-			}
-
-			content, err := extractor.ReadFileContent(absolutePath)
-			if err != nil {
-				invalidFilesChan <- &validation.InvalidYamlFile{Path: absolutePath, ValidationErrors: []error{&validation.InvalidYamlError{ErrorMessage: err.Error()}}}
-				continue
-			}
+			configurations, absolutePath, invalidYamlFile := extractor.ExtractConfigurationsFromYamlFile(path)
 
-			configurations, err := extractor.ParseYaml(content)
-			if err != nil {
-				invalidFilesChan <- &validation.InvalidYamlFile{Path: absolutePath, ValidationErrors: []error{&validation.InvalidYamlError{ErrorMessage: err.Error()}}}
+			if invalidYamlFile != nil {
+				invalidFilesChan <- invalidYamlFile
 				continue
 			}
 
@@ -66,7 +37,7 @@ func ExtractFilesConfigurations(paths []string, concurrency int) (chan *extracto
 }
 
 func ExtractYamlFileToUnknownStruct(path string) (UnknownStruct, error) {
-	absolutePath, err := ToAbsolutePath(path)
+	absolutePath, err := extractor.ToAbsolutePath(path)
 	if err != nil {
 		return nil, err
 	}
diff --git a/bl/validation/k8sValidator.go b/bl/validation/k8sValidator.go
index 4ad217b..cae5706 100644
--- a/bl/validation/k8sValidator.go
+++ b/bl/validation/k8sValidator.go
@@ -22,21 +22,13 @@ func New() *K8sValidator {
 	return &K8sValidator{}
 }
 
-type InvalidFile struct {
-	Path             string  `yaml:"path" json:"path" xml:"path"`
-	ValidationErrors []error `yaml:"errors" json:"errors" xml:"errors"`
-}
-type InvalidYamlFile InvalidFile
-
-type InvalidK8sFile InvalidFile
-
 func (val *K8sValidator) InitClient(k8sVersion string, ignoreMissingSchemas bool, schemaLocations []string) {
 	val.validationClient = newKubconformValidator(k8sVersion, ignoreMissingSchemas, schemaLocations)
 }
 
-func (val *K8sValidator) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *InvalidK8sFile) {
+func (val *K8sValidator) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile) {
 	validK8sFilesConfigurationsChan := make(chan *extractor.FileConfigurations, concurrency)
-	invalidK8sFilesChan := make(chan *InvalidK8sFile, concurrency)
+	invalidK8sFilesChan := make(chan *extractor.InvalidFile, concurrency)
 
 	go func() {
 		defer func() {
@@ -48,7 +40,7 @@ func (val *K8sValidator) ValidateResources(filesConfigurationsChan chan *extract
 
 			isValid, validationErrors, err := val.validateResource(fileConfigurations.FileName)
 			if err != nil {
-				invalidK8sFilesChan <- &InvalidK8sFile{
+				invalidK8sFilesChan <- &extractor.InvalidFile{
 					Path:             fileConfigurations.FileName,
 					ValidationErrors: []error{err},
 				}
@@ -57,7 +49,7 @@ func (val *K8sValidator) ValidateResources(filesConfigurationsChan chan *extract
 			if isValid {
 				validK8sFilesConfigurationsChan <- fileConfigurations
 			} else {
-				invalidK8sFilesChan <- &InvalidK8sFile{
+				invalidK8sFilesChan <- &extractor.InvalidFile{
 					Path:             fileConfigurations.FileName,
 					ValidationErrors: validationErrors,
 				}
diff --git a/bl/validation/main.go b/bl/validation/main.go
index 89ec95c..cf4c589 100644
--- a/bl/validation/main.go
+++ b/bl/validation/main.go
@@ -19,11 +19,3 @@ func (e *InvalidK8sSchemaError) usageSuggestion() string {
 	}
 	return ""
 }
-
-type InvalidYamlError struct {
-	ErrorMessage string
-}
-
-func (e *InvalidYamlError) Error() string {
-	return fmt.Sprintf("yaml validation error: %s\n", e.ErrorMessage)
-}
diff --git a/cmd/root.go b/cmd/root.go
index bba8c48..63e6cb4 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -7,6 +7,7 @@ import (
 	"github.com/datreeio/datree/cmd/completion"
 	"github.com/datreeio/datree/cmd/config"
 	"github.com/datreeio/datree/cmd/publish"
+	schema_validator "github.com/datreeio/datree/cmd/schema-validator"
 	"github.com/datreeio/datree/cmd/test"
 	"github.com/datreeio/datree/cmd/version"
 	"github.com/datreeio/datree/pkg/cliClient"
@@ -14,6 +15,7 @@ import (
 	"github.com/datreeio/datree/pkg/fileReader"
 	"github.com/datreeio/datree/pkg/localConfig"
 	"github.com/datreeio/datree/pkg/printer"
+	"github.com/datreeio/datree/pkg/yamlSchemaValidator"
 	"github.com/spf13/cobra"
 )
 
@@ -60,6 +62,11 @@ func init() {
 	}))
 
 	rootCmd.AddCommand(completion.New())
+
+	rootCmd.AddCommand(schema_validator.New(&schema_validator.YamlSchemaValidatorCommandContext{
+		YamlSchemaValidator: app.context.YamlSchemaValidator,
+		Printer:             app.context.Printer,
+	}))
 }
 
 func Execute() error {
@@ -67,13 +74,14 @@ func Execute() error {
 }
 
 type context struct {
-	LocalConfig  *localConfig.LocalConfig
-	Evaluator    *evaluation.Evaluator
-	CliClient    *cliClient.CliClient
-	Messager     *messager.Messager
-	Printer      *printer.Printer
-	Reader       *fileReader.FileReader
-	K8sValidator *validation.K8sValidator
+	LocalConfig         *localConfig.LocalConfig
+	Evaluator           *evaluation.Evaluator
+	CliClient           *cliClient.CliClient
+	Messager            *messager.Messager
+	Printer             *printer.Printer
+	Reader              *fileReader.FileReader
+	K8sValidator        *validation.K8sValidator
+	YamlSchemaValidator *yamlSchemaValidator.YamlSchemaValidator
 }
 
 type app struct {
@@ -87,13 +95,14 @@ func startup() *app {
 
 	return &app{
 		context: &context{
-			LocalConfig:  localConfig,
-			Evaluator:    evaluation.New(cliClient),
-			CliClient:    cliClient,
-			Messager:     messager.New(cliClient),
-			Printer:      printer,
-			Reader:       fileReader.CreateFileReader(nil),
-			K8sValidator: validation.New(),
+			LocalConfig:         localConfig,
+			Evaluator:           evaluation.New(cliClient),
+			CliClient:           cliClient,
+			Messager:            messager.New(cliClient),
+			Printer:             printer,
+			Reader:              fileReader.CreateFileReader(nil),
+			K8sValidator:        validation.New(),
+			YamlSchemaValidator: yamlSchemaValidator.New(),
 		},
 	}
 }
diff --git a/cmd/schema-validator/main.go b/cmd/schema-validator/main.go
new file mode 100644
index 0000000..6f777ea
--- /dev/null
+++ b/cmd/schema-validator/main.go
@@ -0,0 +1,71 @@
+package schema_validator
+
+import (
+	"fmt"
+
+	"github.com/datreeio/datree/pkg/extractor"
+	"github.com/spf13/cobra"
+	"github.com/xeipuuv/gojsonschema"
+)
+
+type Result = gojsonschema.Result
+
+type YamlSchemaValidator interface {
+	Validate(yamlSchema string, yaml string) (*Result, error)
+}
+
+type YamlSchemaValidationPrinter interface {
+	PrintYamlSchemaResults(result *Result, error error)
+}
+
+type YamlSchemaValidatorCommandContext struct {
+	YamlSchemaValidator YamlSchemaValidator
+	Printer             YamlSchemaValidationPrinter
+}
+
+func New(ctx *YamlSchemaValidatorCommandContext) *cobra.Command {
+	schemaValidator := &cobra.Command{
+		Use:    "schema-validator",
+		Short:  "Execute schema validation to yaml files for given yaml schema",
+		Long:   "Execute schema validation to yaml files for given yaml schema. Input should be glob or 1 yaml schema file and 1 yaml file",
+		Hidden: true,
+		Args: func(cmd *cobra.Command, args []string) error {
+			if len(args) != 2 {
+				errMessage := "Requires 2 args\n"
+				return fmt.Errorf(errMessage)
+			}
+			return nil
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			schemaPath := args[0]
+			yamlPath := args[1]
+
+			_, _, invalidYamlFile := extractor.ExtractConfigurationsFromYamlFile(yamlPath)
+			if invalidYamlFile != nil {
+				ctx.Printer.PrintYamlSchemaResults(nil, invalidYamlFile.ValidationErrors[0])
+				return invalidYamlFile.ValidationErrors[0]
+			}
+
+			schemaContent, err := extractor.ReadFileContent(schemaPath)
+			if err != nil {
+				ctx.Printer.PrintYamlSchemaResults(nil, err)
+				return err
+			}
+			yamlContent, err := extractor.ReadFileContent(yamlPath)
+			if err != nil {
+				ctx.Printer.PrintYamlSchemaResults(nil, err)
+				return err
+			}
+
+			result, err := ctx.YamlSchemaValidator.Validate(schemaContent, yamlContent)
+			ctx.Printer.PrintYamlSchemaResults(result, err)
+
+			if err != nil {
+				return err
+			}
+
+			return nil
+		},
+	}
+	return schemaValidator
+}
diff --git a/go.mod b/go.mod
index 3bb27dc..4388e36 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
 	github.com/briandowns/spinner v1.12.0
 	github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807
 	github.com/fatih/color v1.10.0
+	github.com/ghodss/yaml v1.0.0
 	github.com/kyokomi/emoji v2.2.4+incompatible
 	github.com/lithammer/shortuuid v3.0.0+incompatible
 	github.com/olekukonko/tablewriter v0.0.5
@@ -15,6 +16,7 @@ require (
 	github.com/spf13/cobra v1.1.3
 	github.com/spf13/viper v1.7.1
 	github.com/stretchr/testify v1.7.0
+	github.com/xeipuuv/gojsonschema v1.2.0
 	github.com/yannh/kubeconform v0.4.8
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
@@ -47,7 +49,6 @@ require (
 	github.com/tklauser/numcpus v0.2.2 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
-	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect
 	golang.org/x/text v0.3.3 // indirect
 	gopkg.in/ini.v1 v1.51.0 // indirect
diff --git a/go.sum b/go.sum
index abf02b4..1ae4f72 100644
--- a/go.sum
+++ b/go.sum
@@ -51,6 +51,7 @@ github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
diff --git a/internal/fixtures/fails-yaml-file.yaml b/internal/fixtures/fails-yaml-file.yaml
new file mode 100644
index 0000000..a7acd4c
--- /dev/null
+++ b/internal/fixtures/fails-yaml-file.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: apps/v1
+kind: Service2
+metadata:
+  name: pass-policy
+  labels:
+    owner: me
+    environment: prod
+    app: web
+spec:
+  containers:
+    - name: nginx
+      image: nginx:1.14.2
+      ports:
+        - containerPort: 80
diff --git a/internal/fixtures/jsonSchema/validYaml.yaml b/internal/fixtures/jsonSchema/validYaml.yaml
new file mode 100644
index 0000000..138706a
--- /dev/null
+++ b/internal/fixtures/jsonSchema/validYaml.yaml
@@ -0,0 +1,5 @@
+napiVersion: v1
+policies:
+  - name: "policy1"
+    isDefault: true
+  - name: "policy1"
diff --git a/internal/fixtures/jsonSchema/yamlSchema.yaml b/internal/fixtures/jsonSchema/yamlSchema.yaml
new file mode 100644
index 0000000..7a0590c
--- /dev/null
+++ b/internal/fixtures/jsonSchema/yamlSchema.yaml
@@ -0,0 +1,6 @@
+type: object
+properties:
+  apiVersion:
+    type: string
+required:
+  - apiVersion
diff --git a/internal/fixtures/passes-yaml-file.yaml b/internal/fixtures/passes-yaml-file.yaml
new file mode 100644
index 0000000..ddc99ab
--- /dev/null
+++ b/internal/fixtures/passes-yaml-file.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: apps/v1
+kind: Service
+metadata:
+  name: pass-policy
+  labels:
+    owner: me
+    environment: prod
+    app: web
+spec:
+  containers:
+    - name: nginx
+      image: nginx:1.14.2
+      ports:
+        - containerPort: 80
diff --git a/internal/fixtures/schema-validator-fail.yaml b/internal/fixtures/schema-validator-fail.yaml
new file mode 100644
index 0000000..d8b06b6
--- /dev/null
+++ b/internal/fixtures/schema-validator-fail.yaml
@@ -0,0 +1,7 @@
+type: object
+properties:
+  kind:
+    type: strings
+    enum:
+      - Ingress
+      - Service
diff --git a/internal/fixtures/schema-validator-pass.yaml b/internal/fixtures/schema-validator-pass.yaml
new file mode 100644
index 0000000..fafcc1e
--- /dev/null
+++ b/internal/fixtures/schema-validator-pass.yaml
@@ -0,0 +1,7 @@
+type: object
+properties:
+  kind:
+    type: string
+    enum:
+      - Ingress
+      - Service
diff --git a/pkg/deploymentConfig/default.go b/pkg/deploymentConfig/default.go
index 1bc5aa9..e8fb7db 100644
--- a/pkg/deploymentConfig/default.go
+++ b/pkg/deploymentConfig/default.go
@@ -1,6 +1,5 @@
-// +build !main
-// +build !staging
-// +build !dev
+//go:build !main && !staging && !dev
+// +build !main,!staging,!dev
 
 package deploymentConfig
 
diff --git a/pkg/deploymentConfig/dev.go b/pkg/deploymentConfig/dev.go
index 32270bb..85ff59b 100644
--- a/pkg/deploymentConfig/dev.go
+++ b/pkg/deploymentConfig/dev.go
@@ -1,3 +1,4 @@
+//go:build dev
 // +build dev
 
 package deploymentConfig
diff --git a/pkg/deploymentConfig/production.go b/pkg/deploymentConfig/production.go
index 1495a5e..dfe4ecc 100644
--- a/pkg/deploymentConfig/production.go
+++ b/pkg/deploymentConfig/production.go
@@ -1,3 +1,4 @@
+//go:build main
 // +build main
 
 package deploymentConfig
diff --git a/pkg/deploymentConfig/staging.go b/pkg/deploymentConfig/staging.go
index 14f50d2..65a71c2 100644
--- a/pkg/deploymentConfig/staging.go
+++ b/pkg/deploymentConfig/staging.go
@@ -1,3 +1,4 @@
+//go:build staging
 // +build staging
 
 package deploymentConfig
diff --git a/pkg/extractor/extractor.go b/pkg/extractor/extractor.go
index ee49fd5..1d9db78 100644
--- a/pkg/extractor/extractor.go
+++ b/pkg/extractor/extractor.go
@@ -2,16 +2,64 @@ package extractor
 
 import (
 	"bytes"
+	"fmt"
 	"io"
 	"os"
+	"path/filepath"
 
 	"gopkg.in/yaml.v3"
 )
 
+type InvalidFile struct {
+	Path             string  `yaml:"path" json:"path" xml:"path"`
+	ValidationErrors []error `yaml:"errors" json:"errors" xml:"errors"`
+}
+
+type InvalidYamlError struct {
+	ErrorMessage string
+}
+
+func (e *InvalidYamlError) Error() string {
+	return fmt.Sprintf("yaml validation error: %s\n", e.ErrorMessage)
+}
+
 type FileReader interface {
 	ReadFileContent(filepath string) (string, error)
 }
 
+func ToAbsolutePath(path string) (string, error) {
+	absolutePath, err := filepath.Abs(path)
+	if err != nil {
+		return "", err
+	}
+
+	fileInfo, _ := os.Stat(absolutePath)
+	if fileInfo != nil && !fileInfo.IsDir() {
+		return filepath.Abs(absolutePath)
+	}
+
+	return "", fmt.Errorf("failed parsing absolute path %s", path)
+}
+
+func ExtractConfigurationsFromYamlFile(path string) (*[]Configuration, string, *InvalidFile) {
+	absolutePath, err := ToAbsolutePath(path)
+	if err != nil {
+		return nil, "", &InvalidFile{Path: path, ValidationErrors: []error{&InvalidYamlError{ErrorMessage: err.Error()}}}
+	}
+
+	content, err := ReadFileContent(absolutePath)
+	if err != nil {
+		return nil, "", &InvalidFile{Path: absolutePath, ValidationErrors: []error{&InvalidYamlError{ErrorMessage: err.Error()}}}
+	}
+
+	configurations, err := ParseYaml(content)
+	if err != nil {
+		return nil, "", &InvalidFile{Path: absolutePath, ValidationErrors: []error{&InvalidYamlError{ErrorMessage: err.Error()}}}
+	}
+
+	return configurations, absolutePath, nil
+}
+
 type Configuration map[string]interface{}
 
 type FileConfigurations struct {
diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go
index 348e880..9ab574b 100644
--- a/pkg/printer/printer.go
+++ b/pkg/printer/printer.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"io"
 
+	"github.com/xeipuuv/gojsonschema"
+
 	"github.com/fatih/color"
 	"github.com/olekukonko/tablewriter"
 )
@@ -53,6 +55,7 @@ type Warning struct {
 	InvalidK8sInfo  InvalidK8sInfo
 	ExtraMessages   []ExtraMessage
 }
+type YamlSchemaValidatorResults = gojsonschema.Result
 
 func (p *Printer) SetTheme(theme *Theme) {
 	p.Theme = theme
@@ -92,6 +95,28 @@ func (p *Printer) printK8sValidationWarning(warning Warning) {
 	fmt.Fprintln(out)
 }
 
+func (p *Printer) PrintYamlSchemaResults(result *YamlSchemaValidatorResults, error error) {
+	if result == nil {
+		p.printInColor("INVALID FILE PATH\n", p.Theme.Colors.RedBold)
+		return
+	}
+	if result.Errors() != nil {
+		p.printInColor("Input does NOT pass validation against schema\n", p.Theme.Colors.RedBold)
+		var errorsAsString = ""
+		for _, desc := range result.Errors() {
+			fmt.Printf("- %s\n", desc)
+			errorsAsString = errorsAsString + desc.String() + "\n"
+		}
+		p.printInColor(errorsAsString, p.Theme.Colors.RedBold)
+		return
+	}
+	if error == nil {
+		p.printInColor("Input PASSES validation against schema\n", p.Theme.Colors.Green)
+	} else {
+		p.printInColor("The File Is Invalid\n", p.Theme.Colors.RedBold)
+	}
+}
+
 func (p *Printer) PrintWarnings(warnings []Warning) {
 	for _, warning := range warnings {
 		p.printInColor(warning.Title, p.Theme.Colors.Yellow)
diff --git a/pkg/yamlSchemaValidator/validator.go b/pkg/yamlSchemaValidator/validator.go
new file mode 100644
index 0000000..28d22d7
--- /dev/null
+++ b/pkg/yamlSchemaValidator/validator.go
@@ -0,0 +1,27 @@
+package yamlSchemaValidator
+
+import (
+	"github.com/ghodss/yaml"
+	"github.com/xeipuuv/gojsonschema"
+)
+
+type YamlSchemaValidator struct {
+}
+
+func New() *YamlSchemaValidator {
+	return &YamlSchemaValidator{}
+}
+
+type Result = gojsonschema.Result
+
+func (jsv *YamlSchemaValidator) Validate(schemaContent string, yamlContent string) (*Result, error) {
+
+	jsonSchema, _ := yaml.YAMLToJSON([]byte(schemaContent))
+
+	json, _ := yaml.YAMLToJSON([]byte(yamlContent))
+
+	schemaLoader := gojsonschema.NewStringLoader(string(jsonSchema))
+	documentLoader := gojsonschema.NewStringLoader(string(json))
+
+	return gojsonschema.Validate(schemaLoader, documentLoader)
+}


Test patch:
diff --git a/bl/evaluation/printer_test.go b/bl/evaluation/printer_test.go
index ae63bac..12d999c 100644
--- a/bl/evaluation/printer_test.go
+++ b/bl/evaluation/printer_test.go
@@ -7,7 +7,7 @@ import (
 	"os"
 	"testing"
 
-	"github.com/datreeio/datree/bl/validation"
+	"github.com/datreeio/datree/pkg/extractor"
 
 	"github.com/datreeio/datree/pkg/printer"
 	"github.com/stretchr/testify/assert"
@@ -32,8 +32,8 @@ func (c *mockPrinter) PrintEvaluationSummary(summary printer.EvaluationSummary,
 
 type printResultsTestCaseArgs struct {
 	results           ResultType
-	invalidYamlFiles  []*validation.InvalidYamlFile
-	invalidK8sFiles   []*validation.InvalidK8sFile
+	invalidYamlFiles  []*extractor.InvalidFile
+	invalidK8sFiles   []*extractor.InvalidFile
 	evaluationSummary printer.EvaluationSummary
 	loginURL          string
 	outputFormat      string
@@ -227,8 +227,8 @@ func print_resultst(outputFormat string) *printResultsTestCase {
 					FormattedEvaluationResults: []*FormattedEvaluationResults{},
 				},
 			},
-			invalidYamlFiles:  []*validation.InvalidYamlFile{},
-			invalidK8sFiles:   []*validation.InvalidK8sFile{},
+			invalidYamlFiles:  []*extractor.InvalidFile{},
+			invalidK8sFiles:   []*extractor.InvalidFile{},
 			evaluationSummary: printer.EvaluationSummary{},
 			loginURL:          "login/url",
 			outputFormat:      outputFormat,
diff --git a/bl/files/files_test.go b/bl/files/files_test.go
index ca7d529..65b1e59 100644
--- a/bl/files/files_test.go
+++ b/bl/files/files_test.go
@@ -5,6 +5,8 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/datreeio/datree/pkg/extractor"
+
 	"github.com/stretchr/testify/assert"
 )
 
@@ -26,7 +28,7 @@ func TestToAbsolutePath(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			absolutePath, _ := ToAbsolutePath(tt.args.path)
+			absolutePath, _ := extractor.ToAbsolutePath(tt.args.path)
 			assert.Equal(t, tt.expected.path, absolutePath)
 		})
 	}
diff --git a/cmd/test/main.go b/cmd/test/main.go
index 67a8287..474ef44 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -10,7 +10,6 @@ import (
 	"github.com/datreeio/datree/bl/evaluation"
 	"github.com/datreeio/datree/bl/files"
 	"github.com/datreeio/datree/bl/messager"
-	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/ciContext"
 	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/pkg/errors"
@@ -28,8 +27,8 @@ import (
 type Evaluator interface {
 	Evaluate(filesConfigurations []*extractor.FileConfigurations, evaluationResponse *cliClient.CreateEvaluationResponse, isInteractiveMode bool) (evaluation.ResultType, error)
 	CreateEvaluation(cliId string, cliVersion string, k8sVersion string, policyName string, ciContext *ciContext.CIContext) (*cliClient.CreateEvaluationResponse, error)
-	UpdateFailedYamlValidation(invalidFiles []*validation.InvalidYamlFile, evaluationId int, stopEvaluation bool) error
-	UpdateFailedK8sValidation(invalidFiles []*validation.InvalidK8sFile, evaluationId int, stopEvaluation bool) error
+	UpdateFailedYamlValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error
+	UpdateFailedK8sValidation(invalidFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error
 }
 
 type Messager interface {
@@ -37,7 +36,7 @@ type Messager interface {
 }
 
 type K8sValidator interface {
-	ValidateResources(filesConfigurations chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *validation.InvalidK8sFile)
+	ValidateResources(filesConfigurations chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile)
 	InitClient(k8sVersion string, ignoreMissingSchemas bool, schemaLocations []string)
 	GetK8sFiles(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.FileConfigurations)
 }
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index b864bdc..2d36a5d 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -3,7 +3,6 @@ package test
 import (
 	"testing"
 
-	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/ciContext"
 	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/datreeio/datree/pkg/extractor"
@@ -30,12 +29,12 @@ func (m *mockEvaluator) CreateEvaluation(cliId string, cliVersion string, k8sVer
 	return args.Get(0).(*cliClient.CreateEvaluationResponse), args.Error(1)
 }
 
-func (m *mockEvaluator) UpdateFailedYamlValidation(invalidYamlFiles []*validation.InvalidYamlFile, evaluationId int, stopEvaluation bool) error {
+func (m *mockEvaluator) UpdateFailedYamlValidation(invalidYamlFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
 	args := m.Called(invalidYamlFiles, evaluationId, stopEvaluation)
 	return args.Error(0)
 }
 
-func (m *mockEvaluator) UpdateFailedK8sValidation(invalidK8sFiles []*validation.InvalidK8sFile, evaluationId int, stopEvaluation bool) error {
+func (m *mockEvaluator) UpdateFailedK8sValidation(invalidK8sFiles []*extractor.InvalidFile, evaluationId int, stopEvaluation bool) error {
 	args := m.Called(invalidK8sFiles, evaluationId, stopEvaluation)
 	return args.Error(0)
 }
@@ -64,9 +63,9 @@ type K8sValidatorMock struct {
 	mock.Mock
 }
 
-func (kv *K8sValidatorMock) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *validation.InvalidK8sFile) {
+func (kv *K8sValidatorMock) ValidateResources(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.InvalidFile) {
 	args := kv.Called(filesConfigurationsChan, concurrency)
-	return args.Get(0).(chan *extractor.FileConfigurations), args.Get(1).(chan *validation.InvalidK8sFile)
+	return args.Get(0).(chan *extractor.FileConfigurations), args.Get(1).(chan *extractor.InvalidFile)
 }
 
 func (kv *K8sValidatorMock) GetK8sFiles(filesConfigurationsChan chan *extractor.FileConfigurations, concurrency int) (chan *extractor.FileConfigurations, chan *extractor.FileConfigurations) {
@@ -298,10 +297,10 @@ func newFilesConfigurations(path string) []*extractor.FileConfigurations {
 	return filesConfigurations
 }
 
-func newInvalidK8sFilesChan() chan *validation.InvalidK8sFile {
-	invalidFilesChan := make(chan *validation.InvalidK8sFile, 1)
+func newInvalidK8sFilesChan() chan *extractor.InvalidFile {
+	invalidFilesChan := make(chan *extractor.InvalidFile, 1)
 
-	invalidFile := &validation.InvalidK8sFile{
+	invalidFile := &extractor.InvalidFile{
 		Path:             "invalid/path",
 		ValidationErrors: []error{},
 	}
diff --git a/cmd/test/validationManager.go b/cmd/test/validationManager.go
index 1a9bb7b..bd65406 100644
--- a/cmd/test/validationManager.go
+++ b/cmd/test/validationManager.go
@@ -1,24 +1,23 @@
 package test
 
 import (
-	"github.com/datreeio/datree/bl/validation"
 	"github.com/datreeio/datree/pkg/extractor"
 )
 
 type ValidationManager struct {
-	invalidYamlFiles            []*validation.InvalidYamlFile
-	invalidK8sFiles             []*validation.InvalidK8sFile
+	invalidYamlFiles            []*extractor.InvalidFile
+	invalidK8sFiles             []*extractor.InvalidFile
 	validK8sFilesConfigurations []*extractor.FileConfigurations
 	ignoredFiles                []extractor.FileConfigurations
 }
 
-func (v *ValidationManager) AggregateInvalidYamlFiles(invalidFilesChan chan *validation.InvalidYamlFile) {
+func (v *ValidationManager) AggregateInvalidYamlFiles(invalidFilesChan chan *extractor.InvalidFile) {
 	for invalidFile := range invalidFilesChan {
 		v.invalidYamlFiles = append(v.invalidYamlFiles, invalidFile)
 	}
 }
 
-func (v *ValidationManager) InvalidYamlFiles() []*validation.InvalidYamlFile {
+func (v *ValidationManager) InvalidYamlFiles() []*extractor.InvalidFile {
 	return v.invalidYamlFiles
 }
 
@@ -26,13 +25,13 @@ func (v *ValidationManager) InvalidYamlFilesCount() int {
 	return len(v.invalidYamlFiles)
 }
 
-func (v *ValidationManager) AggregateInvalidK8sFiles(invalidFilesChan chan *validation.InvalidK8sFile) {
+func (v *ValidationManager) AggregateInvalidK8sFiles(invalidFilesChan chan *extractor.InvalidFile) {
 	for invalidFile := range invalidFilesChan {
 		v.invalidK8sFiles = append(v.invalidK8sFiles, invalidFile)
 	}
 }
 
-func (v *ValidationManager) InvalidK8sFiles() []*validation.InvalidK8sFile {
+func (v *ValidationManager) InvalidK8sFiles() []*extractor.InvalidFile {
 	return v.invalidK8sFiles
 }
 
@@ -77,4 +76,3 @@ func (v *ValidationManager) IgnoredFiles() []*extractor.FileConfigurations {
 func (v *ValidationManager) IgnoredFilesCount() int {
 	return len(v.ignoredFiles)
 }
-